NFurness-esristaff

Adding to the JS API

Blog Post created by NFurness-esristaff Employee on Aug 9, 2016

Often you'll want to extend existing functionality on an API class. You could write stand-alone functions, or even a helper object, but occasionally this functionality might nicely complement the exiting functionality on the class and would make most sense to be part of the class itself.

 

Here's a case in point. The 4.0 MapView and SceneView has a UI overlay that you can add HTML elements and Dojo Widgets to. I just wanted to be able to turn this on or off entirely. After digging around a bit I was able to build some functions to do just this:

 

function hideUI(view) {
  _setUIVisible(view, false);
}

function showUI(view) {
  _setUIVisible(view, true);
}

function _setUIVisible(view, visible) {
  var items = view.ui._components;

  for (var i=0; i<items.length; i++) {
    var component = view.ui.find(items[i]);
    setComponentVisible(component, visible);
  }

  function setComponentVisible(component, visible) {
    var widget = component.widget;
    if (widget) {
      widget.visible = visible;
    } else {
      component.node.style.display = visible ? "" : "none";
    }
  }
}

 

But this code isn't very portable. You have to either create global functions or embed these in your own functions and limit their accessibility. That just leads to bad things like Copy & Paste duplication, which is just more maintenance.

 

No global variables, ever!

So, to add this to the appropriate JS API class, I had to wrap a little Dojo around it, and of course do some renaming. Using Dojo's extend() function you can add behavior to existing Dojo classes. So the following code gets hold of the UI class and adds my functionality to it.

 

require([
  "esri/views/ui/UI"
], function(UI) {
  var extension = {
    hide: function() {
      _setUIVisible(this, false);
    },
    show: function() {
      _setUIVisible(this, true);
    }
  };

  UI.extend(extension);

  function _setUIVisible(ui, visible) {
    var items = ui._components;

    for (var i=0; i<items.length; i++) {
      var component = ui.find(items[i]);
      setComponentVisible(component, visible);
    }

    function setComponentVisible(component, visible) {
      var widget = component.widget;
      if (widget) {
        widget.visible = visible;
      } else {
        component.node.style.display = visible ? "" : "none";
      }
    }
  }
});

 

You'll notice I've renamed the showUI() and hideUI() functions to show() and hide() because they now exist on the UI class itself. So you would call view.ui.hide() and view.ui.show(). And the method that does all the work doesn't even make its way onto the UI class, reducing clutter and the risk of stepping on other methods and properties.

 

Danger!

And that raises a very important point. If the JS API team add this functionality down the line, my code might trample all over it, or worse! This is of course a "very bad thing". So, to be a little safer and politer, let's add logic to make sure we're not overwriting anything.

 

require([
  "esri/views/ui/UI"
], function(UI) {
  /// View UI Helper Functions.
  var extension = {
    hide: function() {
      _setUIVisible(this, false);
    },
    show: function() {
      _setUIVisible(this, true);
    }
  };

  safeExtend(UI, extension);

  ...

  function safeExtend(classToExtend, extension) {
    var existing = Object.getOwnPropertyNames(extension).filter(function (item) {
      return UI.prototype[item] !== undefined;
    });

    for (var i=0; i < existing.length; i++) {
      console.warn("'" + existing[i] + "' already exists on class " + classToExtend.prototype.declaredClass + ": Skipping…");
      delete extension[existing[i]];
    }

    classToExtend.extend(extension);

    return existing;
  }
});

 

The above code takes the extension we want to make to the UI class, but first removes anything the UI class already implements, logging a warning to the browser console. We don't need to return anything from the safeExtend() function, but anyone calling it can check whether there was a conflict if they want to.

 

Here's the complete code.

 

Don't go crazy

I don't recommend doing this often. You need to be thoughtful (scroll down to the "Considerations" section of this article on doing similar stuff in iOS). But this code is small, nicely compartmentalized, and even though it leans on the internal _components variable, not very intrusive. Commonly it is more appropriate to subclass or create a helper object. However, this highlights a couple of neat ways to interact with Dojo and the JS API so I figured I'd share it.

 

Enjoy!

Outcomes