Creating a DHTML scale bar with the .NET Web ADF

At the 9.3 release, the .Net Web ADF’s JavaScript library exposes a lot of logic to the client side, allowing you to create a more responsive UI without the need for making calls back to the server. You can get the Web ADF JavaScript library help on the ArcGIS Server Resource Center. This post will show you how to use the JavaScript library to create a pure client-side scale bar using the Microsoft AJAX Client Library patterns.

The Web ADF’s scale bar is generated by the primary map resource using a dynamically generated image. Since the scale potentially can change on every pan, the scale bar image has to be updated frequently, adding additional overhead to the server requests for rendering and fetching these images. Furthermore, if the primary map resource doesn’t support rendering a scale bar, no scale bar is available at all. ArcIMS ArcMap Services don’t support rendering a scale bar. The approach described in this post is the recommended pattern for displaying a scale bar for ArcIMS ArcMap Services.

We can scale a standard <div> box dynamically on the client using JavaScript, and completely eliminate the extra callback, because the map control allows you to listen for and get the current extent of the map. We simply add a handler to the Map’s ‘extentChanged’ event. To do this, we first get a reference to the map object using the $find method parsing in the ClientID of the map as a parameter:

var map = $find('Map1');

Next we add the handler which will be executed every time the extent of the map has changed:

map.add_extentChanged(myExtentChangedHandler);

However, we need to ensure that this handler is not created until the Map has been instantiated on the client. The Microsoft AJAX framework also exposes several life-cycle events, for listening when the page has been fully loaded. So we wrap the above code in an application init event handler and place it right before the </body> tag. Notice how the pattern for adding event handlers is similar to adding handlers to the map. The full example could look something like this:

<script type="text/javascript" language="javascript" >
	function myExtentChangedHandler(sender, args) {
		   alert(sender.get_id() + ' changed extent to: ' + args.current.toString());
	}
	Sys.Application.add_init(function() {
		   var map = $find('Map1');
		   map.add_extentChanged(myExtentChangedHandler);
	});
</script>

Every time you pan the map, you will see a popup window like this:

Message that appears on pan

This gives us the framework for creating a simple scale bar. The next step is to place a box on the page that we can use as a scale bar by changing the width as the pixel size changes.

<div><div id="scalebar" style="background-color: #000000; width: 200px; height: 10px;"></div><span id="scalebarText">100 units</span></div>

<script type="text/javascript" language="javascript" >

function resizeScalebar(sender, args) { //resize event handler
  var pixelSize = sender.get_pixelSize(); //Gets the size on one pixel in map units
  var scalebarWidthInMapUnits = 0.001; //The initial size of the scalebar in map units
  //width in pixels of a scalebar with the current
  //scalebarWidthInMapUnits value and map's pixelsize
  var width = scalebarWidthInMapUnits / pixelSize;
  while(width<20) {
    //keep increasing scalebar size until we have a reasonable and visible size
    scalebarWidthInMapUnits *= 10;
    width = scalebarWidthInMapUnits / pixelSize;
  }
  $get('scalebar').style.width = width + 'px';
  $get('scalebarText').innerHTML = scalebarWidthInMapUnits + ' units';
}
//Hook up the handlers during init
Sys.Application.add_init(function() {
  var map = $find('Map1');
  map.add_extentChanged(resizeScalebar); //fired when extent has changed
  map.add_extentChanging(resizeScalebar); //fired while map is zooming/panning
  resizeScalebar(map,null); //Force initial update of scalebar
});

</script>

This will generate a very simple scale bar that looks something like this:

Simple scale bar

It will automatically adjust its width between 20 and 200 px and show the corresponding width in map units next to it.

The next step is to componentize this into a real client side control that can easily be reused. The Microsoft AJAX Client Library already has a framework in place for creating controls that can inherit from each other and be extended.

A component requires a constructor, and a prototype. In the constructor you define the default properties of the control, and in the prototype you define the properties and methods, including initialize and dispose. Lastly you register the control and optionally the type you derive from, in this case Sys.UI.Control.

Type.registerNamespace('ADF.Samples');

ADF.Samples.DhtmlScaleBar = function(element) {
  ADF.Samples.DhtmlScaleBar.initializeBase(this, [element]);
  this._map = null; //Reference to the map
  this._minWidth = 20; //minimum width of the bar before changing units
}
ADF.Samples.DhtmlScaleBar.prototype = {
  initialize : function () {
    ADF.Samples.DhtmlScaleBar.callBaseMethod(this, 'initialize'); //Call base initialize
    if(this._map === null) { throw Error.argumentNull('map'); }
    this._extentChangedHandler = Function.createDelegate(this,this._updateScalebar);
    //Add listener for when the map is animating its extent
    this._map.add_extentChanging(this._extentChangedHandler);
    //Add listener for when the map has changed its extent
    this._map.add_extentChanged(this._extentChangedHandler);
    this._createBars(); // Creates the bar and text elements
    this._updateScalebar(this._map); //Update the scalebar now
  },
  dispose : function() {
    ADF.Samples.DhtmlScaleBar.callBaseMethod(this, 'dispose');
    if(this._extentChangedHandler && this._map) {
      this._map.remove_extentChanging(this._extentChangedHandler);
      this._map.remove_extentChanged(this._extentChangedHandler);
    }
    if(this._scaleBar) { this.get_element().removeNode(this._scaleBar);}
    if(this._scaleBarText) { this.get_element().removeNode(this._scaleBarText); }
    this._extentChangedHandler = null;
    this._map = null;
    this._scaleBar = null;
    this._scaleBarText = null;
  },
  _createBars : function() { //Create the bar and text
    this._scaleBar = document.createElement('div');
    this._scaleBar.style.backgroundColor = '#000000';
    this._scaleBar.style.float = 'left';
    this.get_element().appendChild(this._scaleBar);
    this._scaleBarText = document.createElement('span');
    this._scaleBarText.style.float = 'left';
    this.get_element().appendChild(this._scaleBarText);
  },
  _updateScalebar : function(sender,args) {
    var pixelSize = sender.get_pixelSize(); //Gets the size of one pixel in map units
    var scalebarWidthInMapUnits = 0.001; //The initial size of the scalebar in map units
    //width in pixels of a scalebar with the current
    //scalebarWidthInMapUnits value and map's pixelsize
    var width = scalebarWidthInMapUnits / pixelSize;
    while(width<this._minWidth) {
      //keep increasing scalebar size until we have a reasonable and visible size
      scalebarWidthInMapUnits *= 10;
      width = scalebarWidthInMapUnits / pixelSize;
    }
    this._scaleBar.style.width = width + 'px';
    this._scaleBarText.innerHTML = scalebarWidthInMapUnits + ' units';
  },
  //Properties:
  get_map : function() { return this._map; },
  set_map : function(value) { this._map = value; },
  get_minWidth : function() { return this._minWidth; },
  set_minWidth : function(value) { this._minWidth = value; }
}
//Register class:
ADF.Samples.DhtmlScaleBar.registerClass('ADF.Samples.DhtmlScaleBar', Sys.UI.Control);

If we add this script to the page, we can now simply create a scale bar using the $create method. The parameters are:

  • Type
  • Object list of properties
  • Object list of event handlers (we don’t have any events here so we use null)
  • Object list of component properties. In our case the map – this ensures that the scale bar is not initialized until the map has been initialized.

Example:

<div id="bar"></div>
<script type="text/javascript" language="javascript" >
Sys.Application.add_init(function() {
  $create(ADF.Samples.DhtmlScaleBar,{"minWidth":20},null,{"map":"Map1"},$get('bar'));
});
</script>

At our code gallery you can download the scale bar control. It has been expanded to look a little better than just a black bar, and it also has a server-side web control that renders the above script to the page. It will analyze the primary map resource and fetch the unit of the map, and allow you to convert it to any display unit you would like on the fly. Finally, there’s some extra logic which will get the scale bar to attempt to use some nice rounded intervals. The final result will look something like below, and will even resize while the zoom animation plays or while you pan toward the poles where the scale changes. You can style the control using the web control style properties like background color, border, font-size etc.

Final scale bar

Click here to get the source code from the Code Gallery

Click here to view a demo of the scale bar

Contributed by Morten Nielsen of the ArcGIS Server .NET software development team

This entry was posted in Services and tagged , , , . Bookmark the permalink.

Leave a Reply

One Comment

  1. manamrajesh says:

    Hi, i need to update the this Scale bar control on Map Pan event. While working on my Map service, it is not updating, but interestingly it is working for arcgis online service. (Definition=”") any help regarding this..?

    Thanks Rajesh