Simple Custom Widgets for ArcGIS JS API

I came across this while trying to add a custom widget to an ArcGIS JS API map, and found the documentation and method described in the official guide, frustratingly long and convoluted.  Being used to working the Leaflet way, I figured there should be a simpler way of doing this.

The basic principal of adding widgets remains the same, if you use the developer tools in your browser, you can see that every widget on your map is an HTML element, and that the events bound to that element are defined in JavaScript, so why, if I want to add a simple button to my map, do I have to write a file in TypeScript, then compile it and go through a whole lot of node.js?



The answer, I don't. and neither do you. The view.ui.add method works for a widget defined and created through the esri complex method, but it also works for HTML elements, which we can create easily and with minimal fuss in a few lines of JavaScript. 

The simplest way to dynamically create HTML elements in JavaScript is with a simple

document.createElement('div');

This is pretty similar to what custom controls in Leaflet return in their onAdd function, which means this is what the library adds to the page when you tell the map the add a control.
Just like in the example we can either predefine CSS classes to style our new control better, or we can specify every style attribute in the script.
So creating and adding a simple widget to the map can look like this:

togglePrintControl = document.createElement('div');
togglePrintControl.id = "togglePrintControl";
togglePrintControl.className = "esri-widget--button"// widget button class
togglePrintControl.innerText = "Hey";
togglePrintControl.style.fontSize = "18px"// font size
togglePrintControl.style.color = "black"// font color
view.ui.add(togglePrintControl"top-right");

 This is code for a widget I'll use to add and remove (basically show or hide) the print widget the ArcGIS JS API provides, right now it would look like a button because I added a class from the zoom buttons, with a slightly larger font size and black text instead of gray. It currently only shows the word "Hey" and does nothing.


Now in order to make our button do something, we'll add the print widget and write a function that adds or removes it from the mapView. In order for the function to work simply, we'll need to define an ID for the widget when we create it, this makes the view.ui.find method accessible easily.

let printWidget = new Print({
      view: view,
      id: "printWidget",
      printServiceUrl:
        "https://utility.arcgisonline.com/arcgis/rest/services/Utilities/PrintingTools/GPServer/Export%20Web%20Map%20Task"
    });
 
function togglePrint(){
      if(view.ui.find("printWidget")){
        view.ui.remove("printWidget")
      }else{
        view.ui.add(printWidget"bottom-left");
      }
    }
 

Now before we'll redefine the button to show a proper icon, instead of plain text, this both looks better and takes up less space. For this purpose I recommend using Font-GIS, it's a great CSS library adding hundreds of geography and GIS related icons created by Jean-Marc Viglino. Note that if you use it, you'll need to attribute it, but it's still free to use.


The new custom widget should now be defined like this:

togglePrintControl = document.createElement('div');
togglePrintControl.id = "togglePrintControl";
togglePrintControl.className = "esri-widget--button"// widget button class
togglePrintControl.innerHTML = '<i class="fg-map-print"></i>';// printer icon
togglePrintControl.style.fontSize = "18px"// font size
togglePrintControl.style.color = "black"// font color
togglePrintControl.onclick = togglePrint;
view.ui.add(togglePrintControl"top-right");

 And that's it, a few short lines, no compiling, no extra files and no fuss. Our custom qidget at work looks like this. A simple button, with a nice icon by Font-GIS and it helps us keep our map clean by hiding other unneeded widget, we could of course use a more complex function, but this is the basis of creating a simple widget.

The full code over esri's print widget example:

<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no" />
    <title>Print widget | Sample | ArcGIS API for JavaScript 4.20</title>
    <link href="https://viglino.github.io/font-gis/css/font-gis.css" rel="stylesheet" />
    <link rel="stylesheet" href="https://js.arcgis.com/4.20/esri/themes/light/main.css" />

    <style>
      html,
      body,
      #viewDiv {
        padding0;
        margin0;
        height100%;
        width100%;
        overflowhidden;
      }
    </style>

    <script src="https://js.arcgis.com/4.20/"></script>
    <script>
      let printWidgetview;
      require(["esri/views/MapView""esri/widgets/Print""esri/WebMap"], function(MapViewPrintWebMap) {
        var webmap = new WebMap({
          portalItem: {
            // autocasts as new PortalItem()
            id: "d6d830a7184f4971b8a2f42cd774d9a7"
          }
        });
        

        var view = new MapView({
          container: "viewDiv",
          map: webmap
        });

        var printWidget = new Print({
            view: view,
            id: "printWidget",
            printServiceUrl:
              "https://utility.arcgisonline.com/arcgis/rest/services/Utilities/PrintingTools/GPServer/Export%20Web%20Map%20Task"
          });

        view.when(function() {

          togglePrintControl = document.createElement('div');
          togglePrintControl.id = "togglePrintControl";
          togglePrintControl.className = "esri-widget--button"// widget button class
          togglePrintControl.innerHTML = '<i class="fg-map-print"></i>';// printer icon
          togglePrintControl.style.fontSize = "18px"// font size
          togglePrintControl.style.color = "black"// font color
          togglePrintControl.onclick = togglePrint;
          view.ui.add(togglePrintControl"top-right");
          
          

        });
        
        function togglePrint(){
            if(view.ui.find("printWidget")){
              view.ui.remove("printWidget")
            }else{
              view.ui.add(printWidget"bottom-left");
            }
          }
      });
    </script>
  </head>

  <body class="calcite">
    <div id="viewDiv"></div>
  </body>
</html>




 

 

 

 

 

 

 

 

 

 

 

 




Comments