GIS Life Blog - Page 4

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Latest Activity

(463 Posts)
ReneRubalcava
Esri Frequent Contributor

esrijs-mixins.jpg

Have you ever started working on an application with some custom widgets and you begin to notice that maybe your widget is starting to grow a little unruly? Maybe it's gone from 50 lines of code to 100, maybe 200, 1000? Don't be ashamed, depending on your needs, some widgets can require a lot of stuff to get the job done, and that's what we're all about right? Getting the job done.

Take a step back and start looking at how maybe you can break that widget up. Are you doing stuff like adding graphics to the map based on certain widget events? Are you listening for map activity to do some analysis using the current map extent and another service that may not be loaded in the map? Maybe you just need to send the users current location to a FeatureService every time a widget is activated, but you'd like to port this functionality across all widgets? I don't know your use case, but if you can step back and start breaking your widget down into smaller pieces, maybe we can start making things easier to manage.

Break up the behavior

A good way of looking at widget composition is looking at the behavior you need in your widget. For example, let's look at the ever famous dijit/_WidgetBase that you probably have used to build your own widgets. When you extend a _WidgetBase, you are given a set of behaviors most notably the dijit lifecycle, which is a set of methods you can override to build your widget during it's creation. Fore more details about custom widgets and the dijit lifecycle, you can check out this video on my blog. You also get another set of behavior, which is the set/get methods for attributes from dojo/Stateful, that allows you to watch for changes.

Now if you want this widget to use a templated HTML string to define it's user interface, you can extend the dijit/_TemplatedMixin that adds one simple property for you, the templateString. That one some property allows you to build an entire UI around your widget. That's pretty powerful when you think about it.

Be on your best behavior

So how exactly could you start incorporating mixins into your own projects? Let's assume you notice that in your widgets you are constantly adding the ability to recenter the map when something happens. One way you can accomplish this is to break that behavior out into a mixin. I don't know if it's convention, but I notice in the dijit library that mixins are prefixed with an underscore, so let's make a mixin called _RecenterMixin.

That could look something like this:

define([
  'dojo/_base/declare'
], function(declare) {
  return declare(null, {
    // this mixin works under the assumption
    // that you have a map assigned to your widget.
    // it also assumes the map was initialized with
    // a center and zoom
    recenter: function() {
      var map = this.get('map');
      var params = map._mapParams;
      if (params.center) {
        map.centerAt(params.center);
      }
    }
  });
});

So now you can make a widget that extends this mixin and just call the recenter method when you need it. I can use it in a widget that wraps BasemapGallery and recenter the map when the base map is changed.

define([
  'dojo/_base/declare',
  'dojo/_base/lang',
  'dojo/dom',
  'dojo/dom-construct',
  'esri/domUtils',
  'esri/dijit/BasemapGallery',
  'dijit/_WidgetBase',
  'dijit/_TemplatedMixin',
  './_RecenterMixin',
  'dojo/text!./templates/BasemapSwitcher.html'
], function(
  declare, lang, dom, domConstruct,
  esriDomUtils, BasemapGallery,
  _WidgetBase, _TemplatedMixin,
  _RecenterMixin,
  template
) {
  return declare([_WidgetBase, _TemplatedMixin, _RecenterMixin], {
    templateString: template,
    postCreate: function() {
      var node = dom.byId('map_root');
      esriDomUtils.hide(this.domNode);
      domConstruct.place(this.domNode, node);
      var map = this.get('map');
      this.gallery = new BasemapGallery({
        map: map,
        showArcGISBasemaps: true
      }, this.bmNode);
      this.gallery.startup();
      this.gallery.on('selection-change', lang.hitch(this, function() {
        this.recenter(); // MIXIN MAGIC
      }));
    },
    hide: function() {
      esriDomUtils.hide(this.domNode);
    },
    show: function() {
      esriDomUtils.show(this.domNode);
    }
  });
});

Looking at this sample, I could even move the hide/show methods to a mixin and reuse it elsewhere

Wait a second

You might be asking yourself what is the difference between something like _WidgetBase and a mixin? Typically, a mixin on it's own is pretty useless. In the example above, if you just extended _RecenterMixin alone, you wouldn't have the postCreate method or a templateString to work with. A mixin may even depend on something like _WidgetBase, hooking into lifecycle methods. At the core of it, you are still extending modules, extending the behavior or widgets.

I put a demo repo of this project up on github for you to play with.

So go out there and see if you can start breaking out mixins in your project and maybe simplify the maintenance of your application. You might find you really like the ability to reuse behavior among different widgets, get more use out of all the hard work you put in to your code.

For more geodev tips and tricks, check out my blog.

more
0 0 458
ReneRubalcava
Esri Frequent Contributor

esri-list.jpg

Recently I was asked about how one might go about working with finding layers in the map. This is one of those things that can be approached in different ways.

  1. Manage layers (Tiled/Dynamic/Feature) added to map.
  2. Find layers in a service by Name, not just ID.

In particular is number 2 above. I've been there. You put an awesome app together, you've got queries built, maybe some custom search functionality, cool spatial analyses based on the returned results. things are working awesome and then someone adds a new layer to a map service and all your hopes and dreams come shattering around you. Yeah, it happens. The needs of a map service change and services can be used for multiple applications. If you are the one handling the server updates as well as development, it's annoying, sure, but not so bad to maintain. If you are the poor soul who is at the will of the ArcGIS Server admin who seems to relish in rearranging the order of the layers just to feast on your tears, I feel for you.

Managing Map Services

I can't offer you some grand solution or quick fix, but I can offer up some tips on how to get to know your data and ways you can search it. Let's start simple, just managing services in a map.

The documentation has a real easy sample on how to get all the layers from map. This is a great first step, because now you are not dependent on layerIds to find your data. Fantastic! But you should also get in the habit of providing an id property to your layers when you create them.

Something like this:

var dLayer = new ArcGISDynamicMapServiceLayer(url, {
  id: "HydroStuff" // useful for searching later on
});

This lets you do this:

var layer = map.getLayer("HydroStuff");

Ok, not groundbreaking stuff I know, but when working with your apps, this makes it very simple to make sure you are doing queries or selections on the correct layers. You can also provide the id in the JSON of the WebMap Spec of the layer​. It should be noted, the docs say the id is for the position in the map, but so far (crosses fingers) I haven't had issue tweaking this on my own.

That's all pretty cool, but you can also get a little fancy with your searching if the names are similar or you don't trust your ArcGIS Server admin not to mess with the names too...

var getLayersFuzzySearch = function(m, term) {
  var queries = arrayUtils.filter(m.layerIds, function(x) {
    return x.toLowerCase().indexOf(term.toLowerCase()) > -1;
  });
  return arrayUtils.map(queries, function(x) {
    return m.getLayer(x);
  });
};
var fuzzy = getLayersFuzzySearch(map, "hydro");

This may not be ideal in all situations, but it does come in handy.

Getting to the layer of the matter

But how, you ask, do you deal with actual layers in a service that may change position? Well, I'll throw this little sample out there to give you an idea.

var layerMap = {}; // A layer map to hold a reference to all layers by name
// List out the layers in a service
// Useful when working with FeatureServices
esriRequest({
  url: url,
  content: { f: "json" },
  handleAs: "json",
  callbackParamName: "callback"
}).then(function(x) {
  return x.layers;
}).then(function(x) {
  return arrayUtils.map(x, function(item) {
    layerMap[item.name] = item.id; //dictionary that sucker!
    return domConstruct.create("li", {
      "data-layer-id": item.id,
      innerHTML: "ID: " + item.id + " | Name: " + item.name
    });
  });
}).then(function(x) {
  var node = document.getElementById("layer-items");
  arrayUtils.map(x, function(item) {
    node.appendChild(item);
  });
});

This little sample writes the layers out to the page for you, but what is cool is the layerMap object being populated. This is now set up with the names to point to layer ids. So if you want to get the id of a layer, just reference it by name, such as layerMap["Rivers"], which will return 1 in this scenario.

If your ArcGIS Server admin is changing positions of your layers and the names, you probably got them a lousy Secret Santa gift and should make amends.

I put a quick JSBin to help demonstrate some of the stuff above. There is no super solution to reference layers by name in a service most of the time, but the data is there for you to write up a solution of your own. The above just happens to be how I like to handle it when I'm unsure of finalness (that can't be a real word) of the map services to mitigate any pains down the road. If you have some other sleeker solutions, I'd love to see them. Your pains may have been greater than my own.

For more geodev tips tricks, check out my blog.

more
0 0 713
ReneRubalcava
Esri Frequent Contributor

esri-choices.jpg

If you are using the ArcGIS API for JavaScript to build a moderately sized application, you are probably building it from different modules. If that's the case, you are probably using a standard dojoConfig as described in the online documentation. What you may not be aware of is that the global dojoConfig isn't the only way to start the party.

No one true path

So here is how you might traditionally set up your dojoConfig start your app.

Option 1

var dojoConfig = { /*stuff*/ };
require(['dependency1', 'dependency2'], function(dep1, dep2) {
  /* awesome web dev stuff */
});

You could also add it to the script tag.

Option 2

<script src="/* esrijs api url */" data-dojo-config="isDebug:true, packages=[/* stuff */]"></script>

But I find this won't really work when a larger config may be needed.

This is another viable approach.

Option 3

require({
  packages: /* stuff */
});
require(['dependency1', 'dependency2'], function(dep1, dep2) {
 /* awesome web dev stuff */
});

But there is yet another way to configure and start your application.

Option 4

require({
  packages: [{
     name: 'app', location: /*somewhere*/
  }. {
    /* more packages */
  }]
}, ['app']);
// app/main.js in your app
require(['dependency1', 'dependency2'], function(dep1, dep2) {
 /* awesome web dev stuff */
});

As we saw in this post on Dojo modules, when you ask for a whole package, such as a directory with Dojo, it assumes there is a main.js file in the directory for that package. That's how this works.

Some caveats

I'm not sure if I've written about using ES6 for ArcGIS JS Dev, but I did do a presentation on it at the most recent Developer Summit. You can see the video here. One of the caveats when using ES6 is that transpilers, like Babel will convert import statements to define statements, but there is no way to get a require statement. So it is still up to you to create at least one single require entry point for your application to get started.

So this:

import map from 'esri/map';

Turns into this:

define(['esri/map'], function(Map) { /*stuff*/ });

Ok. it's not exactly that, transpilers when compiling to AMD modules will use the exports and modules modules to do some stuff that may look odd in the compiled code, but the end result is basically as shown above.

For this reason, I typically do this now.

<script>dojoConfig = { /* config stuff */ };</script>
<script src="//js.arcgis.com/3.13/"></script>
<script>require(['app/main'], function(){});</script>
// app/main.js
define(['dependency1', 'dependency2'], function(dep1, dep2) {
 /* awesome web dev stuff */
});

You can see a sample of this in a testing repo where I was using TypeScript.

Enjoy your choices

So there is more than one way to start a Dojo app. This just goes to show there is some flexibility in the toolkit to fit your developer needs. I'd suggest experimenting with them and finding what best fits your needs. You may even find that a different approach is appropriate in different situations, so it's at least a good idea to know them. This is one of those nuanced bits of knowledge that just gives you better insight into how Dojo works.

For more geodev tips and tricks, check out my blog.

more
2 0 2,156
ReneRubalcava
Esri Frequent Contributor

esri-dojo-amd.jpg

A question on the forums recently popped up that I thought maybe I could explain better in blog post form rather than a single reply. How do I migrate from legacy Dojo to modern Dojo AMD style? That's a valid question, and one I struggled with when Dojo 1.7 was released. You can refer back to the Migrating to 3.0 in the docs to get an idea of the changes.

I think at this point, people are familiar that dojo.declare is now a module called dojo/_base/declare, but it's how to use AMD bits that causes some confusion.

I think some of the hurdles is just wrapping your head around AMD. I had some brain twisting moments with it at first, as I wasn't sure when to use require or when to use define or how to work with modules. My initial struggles were with requirejs and I had no mentor to really help me out, but I scraped through docs and google groups postings to figure it out, so when Dojo 1.7 was released with the AMD loader, I was better prepared.

Let's break it down to it's simplest form and what you will see in most examples on the ArcGIS Developers site.

require(["esri/map", "dojo/domReady!"], function(Map) { 
  var map = new Map("map", {
    center: [-118, 34.5],
    zoom: 8,
    basemap: "topo"
  });
});

There is the dreaded require method. If you have ever worked in C++/Java or similar languages you may be familiar with the main() function. This is the function that occurs when an application starts up, it's where the party gets started. The require method is the main function of AMD. So somewhere in the ArcGIS API for JavaScript modules downloaded via CDN when you add the script tag to your page is a JavaScript file called map.js in a folder called esri that looks like this.

define(['dependency1', 'dependency2'], function(dependency1, dependency2) {
  var Map;
  /*magic unicorn stuff*/
  return Map;
});

The require method will go look for that file and load it asynchronously to your application. When it is done loading, the function in your application is then called, the stuff that defines what a Map is (very existential) is returned from the function. Notice the that this module has it's own dependencies, and in the function they are loaded in the order that they are asked for. THIS IS IMPORTANT. Order matters. These dependencies could functions or objects, or maybe strings. Below is a diagram from my book that may help you out.

amd.jpg

In my book, I use a file called run.js that does the require method defining my dojoConfig and a main.js that actually starts doing the work. That just happens to be my style, but it's not the definitive style.

Some people get tripped up by the use of main.js and honestly, I did too at first. A good point of reference is the Advanced Modules tutorial from Dojo. I'll quote the important bit under Configuring the Loader.

  • main (optional, default = main.js😞 used to discover the correct module to load if someone tries to require the package itself. For example, if you were to try to require "dojo", the actual file that would be loaded is "/js/dojo/main.js". Since we’ve overridden this property for the "my" package, if someone required "my", they would actually load "/js/my/app.js".
    If we tried to require "util", which is not a defined package, the loader would try to load "/js/util.js". You should always define all of your packages in the loader configuration.

Here is a sample of what one of my dojoConfigs, my require entry point, may look like.

var pathRX = new RegExp(/\/[^\/]+$/)
  , locationPath = location.pathname.replace(pathRX, '');
require({
  packages: [{
    name: 'widgets',
    location: locationPath + 'js/widgets'
  }, {
    name: 'utils',
    location: locationPath + 'js/utils'
  }, {
    name: 'app',
    location: locationPath + 'js'
  }]
}, ['app']);

In the require method, the first parameter is the dojoConfig object, the second is the ['app'] dependency, this is my entry point. So according to the docs above, by simply asking for 'app', by default it will look for 'app/main.js'.

If all this is still too confusing, you can still just define a global dojoConfig object to configure your packages and do a plain older require to start your application. Something like this.

<body class="nihilo">
  <script>
    var pathRX = new RegExp(/\/[^\/]+$/)
      , locationPath = location.pathname.replace(pathRX, '');
    var dojoConfig = {
      async: true,
      isDebug: true,
      packages: [
        { name: 'xstyle', location: locationPath + '/bower_components/xstyle' },
        { name: 'mayhem', location: locationPath + '/bower_components/mayhem/dist' },
        { name: 'app', location: locationPath + 'js' }
      ],
      tlmSiblingOfDojo: false
    };
  </script>
  <script>require(['app/start'], function() {})</script>
</body>

Hopefully that clears some stuff up a bit.

You could learn a lot of this via the Dojo docs and especially the CDN Modules tutorial. This page covers some Legacy to Modern Dojo bits in depth. Here is a migration guide. Here is a legacy Dojo to AMD converter you can also try. I've never used it, but it could be a good starting point. I doubt it will work 100% with all Esri modules, but it's worth a shot.

I hope that helps out a bit if you are still struggling with migrating Legacy Dojo applications to modern  Dojo. It may take some time to let it all sink in, but it will. For more geodev tips and tricks, check out my blog.

more
7 4 7,570
ReneRubalcava
Esri Frequent Contributor

esri-github.png

So about two years ago or so Esri joined the world of developers who are using github on a regular basis to host and share code. As a developer, I'm sure there were many at Esri who were just ecstatic about being able to share their code and maybe more importantly get the community of current and new GIS developers involved. If you haven't yet gone through the Esri Github pages, you really should check it out.

So of course, there is an Esri Github page you can search by programming language to find a wealth of samples and projects.

I'm mainly a JavaScript guy, so some of my recommended projects to check out are as follows in no particular order:

Esri/jsapi-resources · GitHub

Esri/Terraformer · GitHub

Esri/esri-leaflet · GitHub

Esri/bootstrap-map-js · GitHub

Esri/angular-esri-map · GitHub

Esri/dojo-bootstrap-map-js · GitHub

There's also quite a few in the weeds projects that do some heavy lifting or are just plain interesting.

Esri/koop · GitHub

Esri/pushlet · GitHub

Esri/wind-js · GitHub

Then you can start delving into other languages and some pretty neat spatial tools.

Esri/geometry-api-java · GitHub

Esri/rabbitmq-for-geoevent · GitHub

Esri/spatial-framework-for-hadoop · GitHub

Esri/R-toolbox-py · GitHub

That's just a small sampling of the projects available via the Esri repos. Some of the widgets using the ArcGIS API for JavaScript are available in the repo and since they are on github, you could even contribute to them. That's the beauty of having code on github. There are plenty of samples and templates in their repo that you can use.

Going through the repos, I see plenty of Esri people submitting issues and pull-requests, but I also see a lot of regular users doing the same thing and contributing to projects. It can be as simple as submitting an update to the documentation or updating an project to use the latest version of an API. It is after all, all about community.

For more geodev tips and tricks, check out my blog.

more
2 0 2,180
ReneRubalcava
Esri Frequent Contributor

esri-leaflet-plugins.jpg

Esri-Leaflet is a fantastic library that will let you use ArcGIS Services with Leaflet. I won't go in to detail about Leaflet itself, you can read plenty about it online, but the main thing is that it has a very easy to understand API and a thriving community. It may not have all the bells and whistles that the ArcGIS API for JavaScript has to work with ArcGIS data, such as many of the ArcGIS widgets, but it does have a very extensive list of community maintained plugins and controls​. I've talked about how to write a custom Leaflet control before.

These extensive plugin and control libraries can also be used pretty seamlessly with Esri-Leaflet as well, after all, it's just another plugin built on top of Leaflet and that opens a wide door of possibilities!

Swimming in Cache

Let's start with an easy one. Say for example you needed to add some sort of offline functionality to your webmap, such as storing the tiles in case you lose a connection. There's a plugin for that! This plugin also requires that you add PouchDB to your application. PouchDB is great as it makes it very easy to handle the little nuances involved with browser support for various storage options, especially important on mobile devices. I've written about using PouchDB for ArcGIS JavaScript apps before. For this plugin, all you have to do is add it via a script tag and magically it will start storing the tiles in a local database for you when you add a single useCache:true option the basemapLayer.

L.esri.basemapLayer('Topographic', {
  useCache: true
}).addTo(map);

Here is a demo. If you use something like the Chrome DevTools to view the IndexedDB for example, you can see the tile data being stored.

tilesstored.jpg

That is pretty cool and all you have to do is pass an option the basemapLayer. The Plugin extends the TileLayer and so does the basemapLayer​ in Esri-Leaflet, so you get this functionality for free.

Keeping it realtime

Another neat plugin for Leaflet is the realtime plugin. The realtime plugin is cool because it can do realtime by either working with a pub/sub service or by polling a service that will provide updates. It's flexible in this regard. Of course ideally you would want your service to return data in GeoJSON. Once you have added this plugin to your project, you can just add it like any other layer and when it's updated, have the map follow the latest location. For fun, in this example, we are drawing a line as the location is updated to trace it's route. Now you can feel like you're working with Jack Bauer!

var realtime = L.realtime({
  url: 'https://wanderdrone.appspot.com/',
  crossOrigin: true,
  type: 'json'
}, {
  interval: 3 * 1000
}).addTo(map).on('update', function(data) {
  // trace the update path on the map
  var coords = data.features.undefined.geometry.coordinates;
  polyline.addLatLng(L.latLng(coords[1], coords[0]));
  map.fitBounds(realtime.getBounds(), {
    maxZoom: 3
  });
});

Here is a working demo of this in action.

Baby, it's cold outside

There are a plenty of controls available for Leaflet as well. A control is usually distinguished from a plugin as a visual component that you can interact with or displays some information on the map (think widgets). Plugins usually provide some enhanced map functionality. A simple, yet useful control is the Weather widget. This widget requires jQuery and it's own css file, but once those are added, you can use it in your application.

L.control.weather({
  lang: "es",
  units: "metric"
}).addTo(map); 

Here is a demo of the widget.

Lean on your friends

This is only a tiny sample of how you can incorporate plugins and controls into your application. Leaflet has a very active community and there are lots of plugins and controls available for it. So if you are using the Esri-Leaflet plugin to work ArcGIS Online or ArcGIS Server services, you now have access to a whole variety of tools you just might find useful.  Esri-Leaflet even has components that are separate plugins like a Heatmap FeatureLayer.

For more geodev tips and tricks, check out my blog.

more
1 0 1,598
ReneRubalcava
Esri Frequent Contributor

where_was_i.jpg

Every now and then I'm asked to make a map do something it normally wouldn't do, but actually makes perfect sense. Astonishing, I know.

A while ago a user asked if the map could save some information if they had to close the browser and come back later. One of these things was the maps last location. They also wanted auto-save in edits and other things, but the same methods applied here will work for that as well.

So I set out like a happy little developer and of course I thought, I'll just use windows.onunload​, actually onbeforeunload is the recommended way, and to be safe I'll dojo/_base/unload to handle any quirks. I figured I'll just use LocalStorage and voila, super-happy customer. And it really is that simple... unless you're using an iOS device (or course it had to be an iOS issue). So onbeforeunload isn't supported in mobile Safari, that was a bummer.

Then I figured, well, why not update the location on each extent-change event when using an iOS device. This worked and I spent a good five minutes giving myself pats on the back (I also had an itch).

So what does all this madness look like?

I present some code!

require([
  'esri/map',
  'dojo/on',
  'dojo/_base/unload',
  'dojo/domReady!'
], function (
        Map, on, baseUnload
) {
  var supports_local_storage = function supports_local_storage() {
    var test = 'has_local';
    try {
      localStorage.setItem(test, test);
      localStorage.removeItem(test);
      return true;
    } catch (e) {
      return false;
    }
  };
  var map = new Map("mapDiv", {
    center: [-118, 34.5],
    zoom: 8,
    basemap: "topo"
  });
  if (supports_local_storage()) {
    var vals, data, iOS, handler;
    vals = localStorage.getItem(location.href + '--location');
    if (vals) {
      data = JSON.parse(vals);
      map.centerAndZoom(data.center, data.zoom);
    }
    // handle this bug https://bugs.webkit.org/show_bug.cgi?id=19324
    // In my testing, a refresh of the browser in iOS will not fire
    // window.onbeforeunload, so if iOS, use map event to write
    // zoom and center to localStorage
    iOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent);
    handler = function() {
      var loc = {
        center: map.extent.getCenter(),
        zoom: map.getLevel()
      };
      localStorage.setItem(location.href + '--location', JSON.stringify(loc));
    };
    if (!iOS) {
      baseUnload.addOnUnload(handler);
    } else {
      on(map, 'extent-change', handler);
    }
  }
});

Depending on browser support, you may need to use dojo/json instead of the native JSON to get this working you poor poor soul.

And that's all there is to it. The location gets saved to LocalStorage as needed and when the application starts again, it checks to see if there is a saved location and will load it up again.

Easy sauce.

self-five.gif

You can see a demo in action here.

For more geodev tips and tricks, check out my blog.

more
0 0 2,067
ReneRubalcava
Esri Frequent Contributor

esri-react2.jpg

Not too long ago, I did a blog post on using the ArcGIS JavaScript API with ReactJS for widgets. I've been talking to a few people about React lately, so I thought I might revisit the subject here. The biggest question you might ask, is why use React? Personally, I think React has a really elegant API. When you start using it and begin to find how easy it is to compose your components that make up the user interface, I think that's when React really shines. A neat feature of React is the use of a virtual DOM. Basically, if you update the state of your application at the root, it will rerender the entire applications DOM. It sounds like it would be slow, but it's really not. It can be, if you do something silly like try to create a very large JSON editor **ahem**. You can read more about how the updates occur here.

It's all just DOM

If you are familiar with working with Dijits and the Dijit lifecycle, React components have a similar lifecycle. In both cases it's beneficial to get to know them. React uses an optional syntax called JSX. It's important to remember that JSX is totally optional. Some folks get all up in arms about mixing DOM with JavaScript, but that's not the case. JSX is simply used to help you define your DOM structure. It still has to be compiled to pure JavaScript in order to in the browser and there are tools for that.

Widgets for the masses

The source code for the sample application is available here. I won't go over the entire application in detail, but I do want to highlight the widgets.

First off there is a widget called Locator that simply displays the map coordinates of the cursor.

/** @jsx React.DOM */
define([
  'react',
  'dojo/topic',
  'helpers/NumFormatter'
], function(
  React,
  topic,
  format
) {
  var fixed = format(3);
  var LocatorWidget = React.createClass({
    getInitialState: function() {
      return {
        x: 0,
        y: 0
      };
    },
    componentDidMount: function() {
      this.handler = this.props.map.on('mouse-move', function(e) {
        this.update(e.mapPoint);
      }.bind(this));
    },
    componentWillUnMount: function() {
      this.handler.remove();
    },
    update: function(data) {
      this.setState(data);
      topic.publish('map-mouse-move', data);
    },
    render: function() {
      return (
        <div className='well'>
          <label>x: {fixed(this.state.x)}</label>
          <br/>
          <label>y: {fixed(this.state.y)}</label>
        </div>
      );
    }
  });
  return LocatorWidget;
});

So this component is pretty simple. It will just display coordinates and uses dojo/topic to publish those coordinates to the application. I've talked about dojo/topic before. The next component is a little more interesting. It has a button that will activate a draw tool and a label that will display the distance being drawn.

/** @jsx React.DOM */
define([
  'react',
  'esri/toolbars/draw',
  'esri/geometry/geometryEngine',
  'dojo/topic',
  'dojo/on',
  'helpers/NumFormatter'
], function(
  React,
  Draw, geomEngine,
  topic, on,
  format
) {
  var fixed = format(3);
  var DrawToolWidget = React.createClass({
    getInitialState: function() {
      return {
        startPoint: null,
        btnText: 'Draw Line',
        distance: 0,
        x: 0,
        y: 0
      };
    },
    componentDidMount: function() {
      this.draw = new Draw(this.props.map);
      this.handler = this.draw.on('draw-end', this.onDrawEnd);
      this.subscriber = topic.subscribe(
        'map-mouse-move', this.mapCoordsUpdate
      );
    },
    componentWillUnMount: function() {
      this.handler.remove();
      this.subscriber.remove();
    },
    onDrawEnd: function(e) {
      this.draw.deactivate();
      this.setState({
        startPoint: null,
        btnText: 'Draw Line'
      });
    },
    mapCoordsUpdate: function(data) {
      this.setState(data);
      // not sure I like this conditional check
      if (this.state.startPoint) {
        this.updateDistance(data);
      }
    },
    updateDistance: function(endPoint) {
      var distance = geomEngine.distance(this.state.startPoint, endPoint);
      this.setState({ distance: distance });
    },
    drawLine: function() {
      this.setState({ btnText: 'Drawing...' });
      this.draw.activate(Draw.POLYLINE);
      on.once(this.props.map, 'click', function(e) {
        this.setState({ startPoint: e.mapPoint });
        // soo hacky, but Draw.LINE interaction is odd to use
        on.once(this.props.map, 'click', function() {
          this.onDrawEnd();
        }.bind(this));
      }.bind(this))
    },
    render: function() {
      return (
        <div className='well'>
          <button className='btn btn-primary' onClick={this.drawLine}>
            {this.state.btnText}
          </button>
          <hr />
          <p>
            <label>Distance: {fixed(this.state.distance)}</label>
          </p>
        </div>
      );
    }
  });
  return DrawToolWidget;
});

This component is a little more involved, but it will use the geometryEngine to calculate the distance between the start point and the end point in a straight line. I do some ugly hacky-ness to stop the drawing interaction after a single line segment, because single drawing with the Draw tool is a little odd behavior (in my opinion). If you wanted to track the total distance of the polyline segments while being drawn, it's a little more involved and you would need to update the start point with the last end point and accumulate the distances as you go along. Sounds like a nice reduce function. I'll leave that exercise up to you. If I spent more time on this, I might separate some of the logic happening here into helper methods and maybe clean up some listeners, but as it stands, it works pretty well.

The last thing you need to do is actually render these components on the page.

/** @jsx React.DOM */
define([
  'react',
  'dojo/query',
  'dojo/dom',
  'dojo/dom-construct',
  './components/Locator',
  './components/DrawTools'
], function(
  React,
  query, dom, domConstruct,
  Locator, DrawTools
) {
  var createContainer = function() {
    var c = query('.widget-container');
    if (c.length) {
      return c.shift();
    }
    var container = domConstruct.create('div', {
      className: 'widget-container'
    }, dom.byId('map_root'), 'first');
    return container;
  };
  var addContainer = function(map) {
    React.render(
      <div>
        <Locator map={map} />
        <DrawTools map={map} />
      </div>,
      createContainer());
  };
  return {
    addContainer: addContainer
  };
});

This is where React will render the components to the defined DOM element on the page and I can pass the map to my widgets as properties. I didn't really discuss props or propTypes, but you can read more how they work here. Also here is a nice write-up on Properties vs State.

Get hacking

You can see a demo of this application in action here. In case you missed it the source code is here.

As you can see, this is just another example of being able to integrate any library or framework of your choice in your ArcGIS API for JavaScript applications. There are certain parts of Dojo that you need to use (and some are very handy, such as dojo/topic), but beyond that it's not too hard to mix-n-match your JavaScript libraries. React is not a total framework, and the FB devs will be the first to tell you that, but it does do the V in MVC very well. So hack away folks and most of all enjoy it!

For more geodev tips and tricks, check out my blog.

more
1 0 6,755
ReneRubalcava
Esri Frequent Contributor

dojo_developer.jpg

Are you using the ArcGIS API for JavaScript on a regular basis? Do you want to take your development skills to the next level? Maybe you want to use the Web App Builder and create custom widgets. Maybe you're happy just throwing in some jQuery into your application and calling it day. Or maybe you want to push your skills a little further. I've said it before, but if you want to learn how to use the ArcGIS API for JavaScript, you need to learn yourself some Dojo.

Where to start

To get your feet wet, Esri provides a couple of quick guides on writing a class. By the way, JavaScript doesn't really have classes, but we can fake it. They even have a quick tutorial on writing a custom widget. I will disagree with one thing in that sample though. In the constructor:

this.domNode = srcRefNode;

The Dijit module _WidgetBase will handle this for you as part of the Dijit life cycle. You can read more about the lifecycle of a widget here.

Which brings me to the next resource I would recommend. The Dojo tutorials. The tutorial section covers everything from transitioning from 1.6 Dojo to modern Dojo to even creating builds. Although if you want custom builds of your app, I highly recommend grunt-esri-slurp or the ArcGIS JavaScript Web Optimizer.

You will probably also spend a lot of time in the Dojo reference guide. The samples in the reference guide are meant to introduce you to the concepts of the modules and at times may be a little confusing. If I had one wish, it would be to have both DOM attributed and pure code samples for some Dijit stuff.. Then there is the API documentation. The API docs can be a little difficult to navigate depending on your browser, but they do provide the nitty-gritty of what properties/methods are available. I've spent many a sunny afternoon in the dojox/lang/functional docs.

If you're thinking oh man, I don't want to spend a lot of time in docs, I don't know what to tell you. I work with a lot of different libraries, frameworks and languages and docs are the lifeblood of every single one of them. Roll up your sleeves, dig in and find some diamonds.

Next level

Another great resource for modern Dojo development, including updates to familiar modules is the Sitepen blog. You can learn more about the new Dojo testing tool called intern as well as dstore and dmodel. You'll even learn more about using modules like xstyle to do some really cool stuff. And of course, if you really want to dig in and see how things work, you can look at the Dojo source code. This is how I was able to figure out how to extend dojo/on for my purposes. You could even check out a very new framework using Dojo called Mayhem. It is still baking and in development, but it has some very nice tooling included. I'm still wading through this one myself. I also wrote an intro ArcGIS Web Development book that includes a lot of Dojo basics to get you up to speed for building ArcGIS API for JavaScript applications.

The point

If you want to really push your skills in working with the ArcGIS API for JavaScript, one of the first steps you need to do is get familiar with Dojo and it's capabilities. I promise you will improve your overall skills more than you imagined. You can still use other JavaScript libraries in your ArcGIS JS API apps, like React, Angular or Backbone, but Dojo is foundational to that knowledge and will only help you in the long run. Don't settle for meh, shoot for HELLZYEAH!

For more geodev tips and tricks, check out my blog.

more
6 2 1,843
ReneRubalcava
Esri Frequent Contributor

chess_globe_flag.jpg

Every now and then a question comes up in the forum about how to do selections of geometries or graphics after they've run some sort of analysis, such as a buffer or drive-time. It makes sense, you've got this nice new polygon or something added to your map after you ran your fancy-schmancy analysis and you want to make it mean something.

The thing to remember is that the results from your analysis are just more geometries and there are tools in the API to deal with it.

Polygon Circus

Let's look at a common one, the Polygon. A Polygon has some methods built it into it that let you extract geometries so you can do something with them. There is the getCentroid method, which uses the maths here. If my memory serves me correctly, you could get a point returned from outside the polygon if the polygon is some odd u-shape. I'm sure someone will correct me if I'm wrong. There is also the getExtent method, which you may have used to set the extent of the map to a polygon, this just returns a rectangular extent of the polygon. A more interesting one is the contains method. It makes sense to have this on the Polygon as it's a quick way to check if your point is inside the polygon. You could iterate over a list of geometries and check them like polygon.contains(point). Simple enough. There are lots of other interesting methods you may find useful in the Polygon module.

Just select the things

A lot of times, if i know I have a layer that will provide some sort of value to my map after I perform an analysis, I'll add it as a FeatureLayer so that I can use the selectFeatures method when I need it. So let's say you do a viewshed analysis in your map and you want to see what points, such as parcels or schools are within that viewshed. You can run your analysis, grab the geometries from the result of the analysis and select the features from the layer to display them on the map. Again, those selected features are just geometries, so you can use those geometries to do further analyses such as find the nearest hospitals or fire stations, whatever floats your boat. You could do something like this after doing a viewshed analysis.

var analysisTool = new CreateViewshed(params, "toolPane");
analysisTool.startup();
analysisTool.on("job-result", function(result) {
  analysisTool.set("disableRunAnalysis", false);
  var resultLayer = new FeatureLayer(result.value.url || result.value, {
    outFields: ['*'],
    infoTemplate: new InfoTemplate()
  });
  map.addLayer(resultLayer);
  if (result.value.featureSet) {
    // get features from the result
    var features = result.value.featureSet.features;
    // only need the geometries, not full graphic
    var geometries = features.map(function(x) {
      return x.geometry;
    });
    // union geometries with geometryEngine
    var geometry = geometryEngine.union(geometries);
    var query = new Query();
    query.geometry = geometry;
    // select the things
    census.selectFeatures(query, FeatureLayer.SELECTION_NEW);
  }
});

* This sample is available on jsbin, modified from an esri example. Requires an ArcGIS Online/Developers login for analysis tools.

That's not too difficult to do. Notice the use of the geometryEngine in there. This is still in beta, but it's in 3.13 and I'm just starting to mess around with it. In the case above, you don't really need it, but it comes in handy when you have multiple geometries of the same type and you just need to mash them together to further analysis, such as a selection. If you were not using a FeatureLayer and only dealing with graphics on the map, the geometryEngine would be the go-to tool to do stuff like this. As I use it more, I'll post more about it.

So give it a shot, there are a lot of tools and options available to you when you need to make sense of your geometries in your application. Don't be afraid to experiment and see what you can break.

For more geodev tips and tricks, check out my blog.

more
0 0 1,173
111 Subscribers