Widget development
Widgets are reusable user-interface components and are key to providing a rich user experience. The ArcGIS Maps SDK for JavaScript provides the ability to build custom widgets that fit your unique requirements.
This guide topic discusses the basic fundamentals of custom widget development and specific areas that you should focus on when transitioning to this framework. The foundation for creating custom widgets remains consistent, regardless of the widget's intended functionality. The Additional information section has extra resources to help get you started.
Development requirements
Prior to creating your own custom widgets, make certain you meet the minimum requirements including being able to build the application locally on your machine. This process has several more steps when compared to creating a vanilla JavaScript application hosted on a web server and run in a browser.
TypeScript
The suggested approach to widget development is through TypeScript. One of the many advantages of this approach is the use of decorators to enhance functionality. There is an excellent TypeScript Setup guide page that provides basic steps to set up your TypeScript development environment with the ArcGIS Maps SDK for JavaScript. There are also many great online resources that go into detail on what TypeScript is, why it is used, and how you use it. Becoming familiarized with these basics will make the widget development process much easier.
JSX
JSX is a JavaScript extension syntax that allows us to describe the widget user interface similarly to HTML. It is commonly associated with React, but it can also be used in other implementations. It looks similar to HTML in that it can be used inline with JavaScript. Custom widgets are built using the .tsx
extension which combines TypeScript with JSX, and it is compiled directly to JavaScript.
Familiarity with esri/core/Accessor
Accessor is one of the core features of 4.x and is the base for all classes. This includes custom widgets because they extend an existing API class to create a subclass, or child class. The custom widget inherits the methods and properties from its parent class by calling the super()
method. Please see the Implementing Accessor topic for additional details and usage patterns.
@arcgis/core
The @arcgis/core ES modules have the same underlying API functionality as the AMD modules. It is recommended to use @arcgis/core
for building custom widgets because ES modules work seamless with almost all major JavaScript frameworks and build tools because they do not need a specialized AMD loader, and they can be consumed natively in the browser and in modern build tools. The modules are installed locally on your development machine using npm.
Widget life cycle
Before you begin developing, it's important to have a general understanding of a widget's life cycle. Regardless of the type of widget, the general concepts specific to its life cycle remain the same. These are:
- constructor (params) - This is where the widget is initially created while setting any needed properties. Since the widget is derived from Accessor, you get access to getting, setting, and watching properties as discussed in the Working with properties topic.
- postInitialize() - This method is called after the widget is created but before the UI is rendered.
- render() - This is the only required method and is used to render the UI.
- destroy() - Method to release the widget instance.
TypeScript decorators
Widget development takes advantage of TypeScript decorators, and the API uses decorators as the underlying glue that is used to create classes. This allows for enhancing common behaviors in existing properties, methods, and constructors at design time. We discuss the most common types of widget decorators below.
@subclass()
The @subclass() decorator is used to extend an API class. It also allows you to specify a declared
property as a string in the constructor. This helps the API differentiate between the existing class that you are extending where the declared
is read-only, and the custom one you are building.
The snippet below imports and extends the base Widget
class and defines the UI in the render method. JSX is used to define the UI. In this simple scenario, a div
element is created and defines a string called John Smith
as its content.
@property()
The @property() decorator is used to define an Accessor property. Any property defined with this decorator can now be get
and set
. In addition, you can watch
for any property changes.
@alias Of()
The @aliasOf() decorator creates a two-way binding between the property it decorates and an inner property of one of its members.
Widget implementation
The following steps provide a simple overview of the steps needed when implementing your own custom widget:
Create a new widget class
Start by extending the base Widget class and specifying the declared
in the @subclass()
constructor.
Implement properties and methods
Next, you can implement any properties and/or methods specific for that widget. This snippet shows how to take advantage of decorators for these properties.
By default, functions referenced in your elements will have this
set to the actual element. Optionally, you can use the bind attribute to update this
. The following binds the _on
callback method which is used when listening for name
property updates. This is displayed in the postInitialize method below.
The postInitialize method is called when the widget's properties are ready, but before it is rendered. In the snippet below we are watching for the name
property. Once updated, it calls the _on
callback method. The watchUtils.init() call returns a WatchHandle object which is then passed into own(). This helps tidy up any resources once the widget is destroyed.
Render the widget
After the properties are implemented, the widget's UI is rendered using JSX. This is handled in the widget's render method, which is the only required method needed for widget implementation.
Please note that widgets created as JSX elements are not yet supported. For example, the following snippet will not work.
const search = <
Lastly, call destroy on the widget to dispose of the widget and free up all resources registered with the own()
method referenced in postInitialize below.
Export module
At the very end of the code page, add a line to export the module. Exporting makes the capabilities of the widget available via an import
statement.
Completed code
The jsapi-custom-widget
sample shows the entire widget.tsx
file. This TypeScript file uses this extension to indicate that the class uses JSX, e.g. .ts + .jsx = .tsx
.
Widget rendering
The properties listed below can be used for rendering the widget:
- classes: A utility method used for building the value for a widget's class property. This aids in simplifying CSS class setup.
- styles: Allows styles to be changed dynamically.
- afterCreate: This callback method executes after the node is added to the DOM. Any child nodes and properties have already been applied. Use this method within render to access the real DOM node. It is also possible to use per element.
- afterUpdate: This callback method executes every time the node is updated.
- bind: This property is used to set the value of
this
for event handlers. - key: This is used to uniquely identify a DOM node among its siblings. This is important if you have sibling elements with the same selector and the elements are added/removed dynamically.
In addition to the methods mentioned above, there is also a storeNode convenience method. You would use this to assign an HTMLElement DOM node reference to a variable. This uses a custom data attribute, data-node-ref
, to store a reference to an element's DOM node. In order for this to work correctly, it must also be bound to the widget instance, e.g. bind={this}
, as shown in the snippet below.
Additional information
Please refer to these additional links for further information: