odoe

Getting touchy

Blog Post created by odoe on Jan 21, 2015

touchwidget.png

Have you ever been working with your nice and shiny mobile web map and trying to do some sort of drawing or selecting an item on the screen only to realize you can't tell where your fat fingers have touched the map? Wouldn't it be cool if you could somehow get some feedback on how you are interacting with the map? I'll let you in on a dirty secret, it's pretty easy to do.

 

Touchy feely

A while ago I had a user comment that they had issues knowing if they were clicking on the map where they thought they were. Touch screens on mobile devices can be finicky sometimes and I have seen some slower devices be off my as much as an inch on where you thought you were touching on the screen.

 

To deal with this, I made a TouchWidget. What this widget does is simply listen for click events on the map and add a little circle, then after half a second or so, delete the marker. The idea isn't too difficult, but it can be a little jarring to just show and hide a marker. So you can use some of the graphics modules in Dojo to make the transition a little nicer. Here is a neat little tutorial on working with Dojo animations.

Here is the source code for the TouchWidget

define([
  'esri/layers/GraphicsLayer',
  'esri/graphic',
  'esri/symbols/SimpleMarkerSymbol',
  'dojo/on', 'dojo/fx', 'dojo/_base/fx', 'dojo/fx/easing',
  'dojo/aspect', 'dojo/_base/Color',
  'dojo/_base/declare', 'dojo/_base/lang',
  'dijit/_WidgetBase', 'dijit/a11yclick'
], function(
  GraphicsLayer, Graphic, SimpleMarkerSymbol,
  on, fx, coreFx, easing, aspect, Color,
  declare, lang, _WidgetBase, a11yclick
  ) {
  'use strict';
  var hitch = lang.hitch;
  return declare([_WidgetBase], {
    postCreate: function() {
      this.set('delay', this.settings.delay || 500);
      this._symInner = new SimpleMarkerSymbol(SimpleMarkerSymbol.STYLE_CIRCLE, this.settings.innerSize, null, new Color(this.settings.innerColor));
      this._symOuter = new SimpleMarkerSymbol(SimpleMarkerSymbol.STYLE_CIRCLE, this.settings.outerSize, null, new Color(this.settings.outerColor));
      this.touchLayer = new GraphicsLayer();
    },
    startup: function() {
      if (!this.map) {
        this.destroy();
        throw new Error('Must provide a map object to use TouchWidget');
      }
      if (this.map.loaded) {
        this._init();
      } else {
        on.once(this.map, 'load', hitch(this, '_init'));
    },
    // widget methods
    _fxArgs: function(graphic) {
      return {
        node: graphic.getNode(),
        duration: this.delay,
        easing: easing.expoOut
      };
    },
    _fxToCombine: function(graphicOuter, graphicInner) {
      return [
        coreFx.fadeOut(this._fxArgs(graphicOuter)),
        coreFx.fadeOut(this._fxArgs(graphicInner))
      ];
    },
    _onAspectAfterEnd: function(graphicOuter, graphicInner) {
      return lang.hitch(this, function() {
        this.touchLayer.remove(graphicOuter);
        this.touchLayer.remove(graphicInner);
      });
    },
    _onTimeOut: function(graphicOuter, graphicInner) {
      return hitch(this, function() {
        var combined = this._fxToCombine(graphicOuter, graphicInner);
        var f = fx.combine(combined);
        this.own(aspect.after(f, 'onEnd', hitch(this, this._onAspectAfterEnd(graphicOuter, graphicInner))));
        f.play();
      });
    },
    _onTouchClick: function(e) {
      var graphicOuter = new Graphic(e.mapPoint, this._symOuter);
      var graphicInner = new Graphic(e.mapPoint, this._symInner);
      this.touchLayer.add(graphicOuter);
      this.touchLayer.add(graphicInner);
      setTimeout(hitch(this, this._onTimeOut(graphicOuter, graphicInner)), this.delay);
    },
    // private methods
    _init: function() {
      this.map.addLayer(this.touchLayer);
      this.set('loaded', true);
      this.emit('load', {});
      // set up touch handlers
      this.own(on(this.get('map'), a11yclick.click, hitch(this, '_onTouchClick')));
    }
  });
});

 

What this widget is doing is setting up an inner and outer graphic symbol. The size of these symbols can be defined via the parameters passed to the widget. It also adds it's own layer to display these features so you don't have to worry about conflicting with what may be happening on the default GraphicsLayer of the map.

 

Animating nodes

The dojo/fx library doesn't know what a Graphic from the Esri library is, so you'll need to get the actual DOM node of the Graphic to interact with. You can do this via the graphic.getNode() method. Now you can set up a delay using setTimeout to gracefully remove the touch marker from the map. You just need to pass around the graphics in this chain of methods so they are disposed of properly.

 

You can see how this TouchWidget works in this demo.

 

So feel free to touch your maps and get more interactive. Check out my blog for more tips & tricks!

Outcomes