Wednesday, May 9, 2012

ARCGIS JavaScript API – BASIC EDITING COMMANDS SECTION 1 – PART V


1.     Introduction


In this document I will explain how basic editing can be done through the ArcGis JavaScript API. With the Silverlight API, basic editing is much sampler than in Javascript. The edit tool in Silverlight offers much more methods than does the editor in the JavaScript API. So in the JavaScript implementation I do not use the editor tool dijit but instead I use the edit toolbar. The only dijit used in the editing is the template picker, which is used for creating features. Simple feature creation is done by the draw toolbar component.
In the coming 10.1 version you will be able to use versioning when doing web editing. I hope later to add support for this new important feature.

Basic feature editing consists of the following commands:

·         Select features for a feature class

·         Clear all selected features for a feature class

·         Select the feature layer for editing

·         Create new features

·         Delete selected features

·         Edit attribute data for a feature

·         Save update or creation of a feature

·         Undo changes

·         Create features from a symbol template

Below is the schema how the JavaScript application framework is build. The basic idea is separation of the GUI from GIS commands and tools  and the ArcGis Javascript API. The main webpage has almost no Javascript code embedded. Only some initialization code for the dojo library can be found on the page.
To keep the GIS framework library independent from the application, there is no interaction between the view model and the ArcGis Javascript API.  knockoutJS plays an important role for  the exchange of data between the web page and the view models.
Before you can do editing on feature classes, a proxy URL must be installed to handle a POST that exceed the 2048 character size limit. Lucky, ESRI provide us the full source code for the implementation of the proxy URL that replace the POST request with a GET when the maximum size is reached. The GET request has no size limitations. You find all the information for implementing the proxy URL at http://help.arcgis.com/en/webapi/javascript/arcgis/help/jshelp_start.htm .



The toolbar used for doing the basic editing consists of a dropdown list,9 buttons and a checkbox used for displaying a template of symbols. All this elements interact with the view model of the web page.


2.     Selections


Before editing can be done, an editable layer must be selected. This can be done through a combo box that is built with the feature layers. The data source of the combo box is an observable collection from knockoutJS. By using an observable collection the selection is automatically reflected in the view model. The implementation is illustrated below :

HTML :

<select data-bind="options: editLayers, optionsText: 'layerName', value: selectedLayer, optionsCaption: 'Choose edit layer'" style="vertical-align:top"></select>



View model :

    this.editLayers = ko.observableArray();

    this.selectedLayer = ko.observable();

The array editLayers contains the title and the layer id of the feature layer. Using the layer id we set the layer information in the GisEditing class.  In setting the layer information, I also add at the same time the selection symbols so that after drawing the result it is always visible even if the feature is not saved in the database yet.

        setEditLayer: function (featureLayerName) {

            var featureLayer = GisOperation.getMap().getLayer(featureLayerName);

            if (featureLayer != null) {

                currentFeatureLayer = featureLayer;

                if (currentFeatureLayer.geometryType == "esriGeometryPolygon") {

                    currentFeatureLayer.setSelectionSymbol(gisOperation.getSelectionFillSymbol());

                    currentDrawGeometryType = esri.toolbars.Draw.POLYGON;

                    currentSymbol = gisOperation.getSelectionFillSymbol();

                }

                else if (currentFeatureLayer.geometryType == "esriGeometryPolyline") {

                    currentFeatureLayer.setSelectionSymbol(gisOperation.getSelectionLineSymbol());

                    currentDrawGeometryType = esri.toolbars.Draw.POLYLINE;

                    currentSymbol = gisOperation.getSelectionLineSymbol();

                }

                else if (currentFeatureLayer.geometryType == "esriGeometryPoint") {

                    currentFeatureLayer.setSelectionSymbol(gisOperation.getSelectionMarkerSymbol());

                    currentDrawGeometryType = esri.toolbars.Draw.POINT;

                    currentSymbol = gisOperation.getSelectionMarkerSymbol();

                }

                return true;

            }

            else

                return false;

        },

Now everything is ready for doing the creation and editing of features.

3.     Create feature


As of all command and tools, the processing starts at the main view model using two exposed methods : execute and canexecute using the same technique used in the viewmodel of Silverlight.

this.addExecute = function () {
editToolbarGeneralViewModel.addCommand(setEditActive, this.selectedLayer().layerName);
    };

this.addCanExecute = ko.computed(function () {
        return !this.isEditActive();
}, this);



The canexecute is used to enable or disable commands. The reason for adding this extra method is to block some command during the processing of commands and tools. When you press create a feature, you expect that only the save or undo button is available, other buttons like edit a feature or delete a feature must be disabled. The use of knockoutJS will help us to enable or disable this without a lot of coding.

First I create an observable isEditActive  that will reflect if some Create / Update / Delete is active. By setting this observable to a new value all properties CanExecute get also a new value.

<input data-bind="click: selectExecute, enable: selectCanExecute" class="buttonTool" id="btnSelect" type="image" src="Images/SelectionSelectTool32.png"  />

<input data-bind="click: clearSelectExecute, enable: clearSelectCanExecute" class="buttonTool" id="btnClearSelect" type="image" src="Images/SelectionClearSelection32.png"  />

<input data-bind="click: deleteExecute, enable: deleteCanExecute" class="buttonTool" id="btnDelete" type="image" src="Images/EditingFixErrorTool32.png"  />

<input data-bind="click: addExecute, enable: addCanExecute" class="buttonTool" id="btnAdd" type="image" src="Images/EditingPolygonTool32.png"  />

<input data-bind="click: editExecute, enable: editCanExecute" class="buttonTool" id="btnEdit" type="image" src="Images/EditingEditShape32.png"  />

<input data-bind="click: movePointExecute, enable: movePointCanExecute" class="buttonTool" id="btnMovePoint" type="image" src="Images/TinEditingTinNodeMove32.png"  />

<input data-bind="click: attributeExecute, enable: attributeCanExecute" class="buttonTool" id="btnAttribute" type="image" src="Images/EditingCreateFeaturesWindowShow32.png"/>

<input data-bind="click: saveExecute, enable: saveCanExecute" class="buttonTool" id="btnSave" type="image" src="Images/EditingSaveEdits32.png"/>

<input data-bind="click: undoExecute, enable: undoCanExecute" class="buttonTool" id="btnUndo" type="image" src="Images/EditUndo32.png"/>



Instead of using jQuery to do the dom processing for enable / disable buttons, a much simpler solution is the use of the view model implementation of knockoutJS.

In Javascript I used the draw toolbar component for doing the drawing of new features. In Silverlight I could use the editor for doing the creation of features. As I explained earlier, the edit dijit has only limited methods exposed, so making it not suitable for custom edit toolbars. Still creating a feature is only a couple of lines.


startCreateOperation: function (editOperationComplete) {

  if (currentFeatureLayer == null)

                return;

  try {

     currentOperation = "CreateFeature";

     currentGeometry = null;

     drawCreateEndHandlerHandle = dojo.connect(gisOperation.getDraw(), "onDrawEnd", drawCreateEndHandler);

     gisOperation.getDraw().activate(currentDrawGeometryType);

  } catch (err) {

     logMessage("E", "startCreateOperation failed -->" + err.message, "gisEditing");

  }

},


After creation of the geometry, a callback is defined to display the result. Because we don’t do directly save, the geometry is first put on the graphics layer. Only during a save the actual feature is displayed on the map.

    // Eventhandler for the end of a geometry drawn

    function drawCreateEndHandler(geometry) {

        try {

            currentGeometry = geometry;

            // Drawn graphic with the geometry drawn

            if (currentOperation == "CreateFeature") {

                var newGraphic = new esri.Graphic(currentGeometry, currentSymbol, currentAttributes);

                gisOperation.getMap().graphics.add(newGraphic);

                gisOperation.getMap().graphics.refresh();

                // start attribute editing



            }

            if (drawCreateEndHandlerHandle != null)

                dojo.disconnect(drawCreateEndHandlerHandle);

        } catch (err) {

            logMessage("E", "drawCreateEndHandler failed -->" + err.message, "gisEditing");

        }

    }


Because this result is only a geometry, the feature contains null values for all its attributes. I will add later attribute editing during the creation of geometries. This will only result in the calling of an attribute edit.

4.     Edit of the geometry of a feature


The command consist of the combination of two actions. First, select a feature with the select button. Next press the edit vertices button to activate the display of vertices.

The editing of vertices makes use of the edit toolbar component. Initialization is done during the init of the GisEditing component.

editTool = new esri.toolbars.Edit(gisOperation.getMap());

To start editing of vertices,  activate the edit toolbar with the correct parameters for the selected feature.

        startEditOperation: function (editOperationComplete) {

            if (currentFeatureLayer == null)

                return;

            try {

                if (currentFeatureLayer.getSelectedFeatures().length == 1) {

                    currentOperation = "EditVertices";

                    currentAttributes = currentFeatureLayer.getSelectedFeatures()[0].attributes;

                    editTool.activate(esri.toolbars.Edit.MOVE | esri.toolbars.Edit.EDIT_VERTICES,

                    currentFeatureLayer.getSelectedFeatures()[0]);

                }

            } catch (err) {

                logMessage("E", "startEditOperation failed -->" + err.message, "gisEditing");

            }

        },



Ending of the edit is done through a save or undo command. This version only handle single edit operations.

5.     Creation of features


As part of the basic editing functionality, we support two ways to create features :

·         Simple create new feature

·         Create feature based on a template of feature types

The use of templates is only a front end for a simpler creation of features with some attribute values already filled in. This functionality relies entirely on the simple feature creation.

The creation of feature is not build upon the editor dijit but rather on a simple draw action using the draw toolbar. At the end of the draw operation a new feature is build based on the geometry and on the contents of initialized attributes. In the case of a simple feature creation, the attribute table is empty, for use of template, attributes are prefilled with values coming from the exposed templates of the feature service.

startCreateOperation: function (editOperationComplete) {

   if (currentFeatureLayer == null)

        return;

   try {

      currentOperation = "CreateFeature";

      currentGeometry = null;

      drawCreateEndHandlerHandle = dojo.connect(gisOperation.getDraw(), "onDrawEnd", drawCreateEndHandler);

      gisOperation.getDraw().activate(currentDrawGeometryType);

   } catch (err) {

      logMessage("E", "startCreateOperation failed -->" + err.message, "gisEditing");

   }

},

As you can see, the creation is based on the draw action. At the end of the draw action, control is passed to a callback method. This method does not do a lot of processing, simple save the geometry for later use in the save and display the result on the graphic layer so that you can see what has been drawn.

    // Eventhandler for the end of a geometry drawn

    function drawCreateEndHandler(geometry) {

        try {

            currentGeometry = geometry;

            // Drawn graphic with the geometry drawn

            if (currentOperation == "CreateFeature") {

                var newGraphic = new esri.Graphic(currentGeometry, currentSymbol, currentAttributes);

                gisOperation.getMap().graphics.add(newGraphic);

                gisOperation.getMap().graphics.refresh();

                // start attribute editing



            }

            if (drawCreateEndHandlerHandle != null)

                dojo.disconnect(drawCreateEndHandlerHandle);

        } catch (err) {

            logMessage("E", "drawCreateEndHandler failed -->" + err.message, "gisEditing");

        }

    }

I will later add support for attribute editing at the end of the creation of the feature. As you can see now this will be done in the callback method.

Use of templates

Creation of feature with the help of a template of feature types is a special way of creating features. The main difference is that by using feature types, you can use attribute values that has been prefilled in and exposed by the feature service.

To simplify the job, I use the template picker dijit from the ArcGis  JavaScript API. This dijit can be used independent of the editor dijit and makes the creation of a template having all the different feature types much easier.

Because the template picker is a visual object, I have to create a div that will serve as container for this object. The creation of the template is done in the view model of the edit toolbar. In principle this violates the MVVM rules, but because we don’t have the same possibilities as in Silverlight this was the easiest way to do. You could try to build it yourself with a knockoutJS template, but displaying symbols will result in much more code to write.

By using this ESRI dijit, we still need to do some extra work to handle the selection on the template.
·         We must keep track of the attribute values that are related to the feature type.

·         Start the create feature on the feature class of the feature type

This is not so difficult to do as you can see in the code below
       

initializeTemplatePicker: function (templateDiv, setEditActive) {

   try {

      setEditActiveEdit = setEditActive;

      viewModel.visibleTemplates(true);

      var contentHolder = "<div id='" + templateDiv + "'></div>";

      require(["dojo/dom-construct"], function (domConstruct) {

         domConstruct.place(contentHolder, "editTemplates", "first");

      });

      templatePicker = new esri.dijit.editing.TemplatePicker({

         featureLayers: GisOperation.getFeatureLayers(),

         rows: "auto",

         columns: "auto",

         showTooltip: true,

         style: "height: 100%; width: 250px;"

      }, templateDiv);

      templatePicker.startup();

      dojo.connect(templatePicker, "onSelectionChange", function () {

          var selected = templatePicker.getSelected();

          GisEditing.initAttributes(selected.template.prototype.attributes);

          editToolbarGeneralViewModel.addCommand(setEditActive, selected.featureLayer.name);

      });

   } catch (err) {

       logMessage("E", "error in initializeTemplatePicker ->" + err.name + "\n" + err.message, "editToolbarGeneralVM");

   }

},

The template dijit is created in code and added to the DOM and not in the HTML code because of problems with the destroy method of the ESRI dijit component. By creating the dijit in code you can dynamically add and delete the template in code. All the prefilled attributes can be found in the template prototype property.

The checkbox will create the edit templates by adding the template picker to the corresponding window. Simply clicking on the icon will start the create functionality.


Another possibility is creating a custom template from scratch by using the items object in the constructor. This will however include more coding at the selection level. In our general approach for implementing an webmap editor, using the template picker based on edit layers is the more straightl  solution.

Putting ESRI objects by code into an accordion container can be tricky. Elements of the container are only available after it has been selected. So you must use the selectChild event to create the template picker.

var accResults = dijit.byId("leftAccordion");

dojo.connect(accResults, "selectChild", function (childPane) {

    if (childPane.id == "editTemplates" && viewModel.useTemplates()) {

        if (!editToolbarGeneralViewModel.isTemplatePickerInitialized()) {                                  editToolbarGeneralViewModel.initializeTemplatePicker(TemplatePickerDiv, setEditActive);

        }

    }

});           

In a next document I will explain in detail how the save or undo commands work.

Wednesday, April 25, 2012

ARCGIS JavaScript API – USING BASIC GIS TOOLS AND COMMANDS – PART IV


1.     Introduction

 In previous articles I showed you how we can setup a map  infrastructure for consuming different dynamic layers and feature layers. I showed you how we can build  different tables with information coming from these layers. In this article I will introduce you in the creation of some basic command and tools that will interfere with the layers in the map. In the implementation of these actions I will use more actively the ViewModel  pattern found in the knockoutJS  javascript  library.

2.     Toolbar

The toolbar that will be discussed in this article consist of the following toolbar items :

·         Zoom In tool
·         Zoom out tool
·         Pan command
·         Inspect tool
·         Measure tools






All these tools and commands have their main implementation in the common ViewModel but have a more detailed implementation in a ‘common toolbar’ ViewModel. Doing so, the main ViewModel is a light weight class with little or no logic behind, and it sole purpose is to interact  with the view (the html page). New here is also the use of templates from the knockoutJS  javascript  library.
3.     Logging

To make debugging easier, I introduced a logging component. The logging happens through a rest service and is stored in a log file at the server. This approach can be useful to  trace all errors that occurs in the application. However for debugging purpose this can also be very useful. Because a lot of methods works asynchronous, having an extended log file can help you debug errors due to inconsistent sequence of operations. Below you can see an example of a log file. I use a debug variable to enable or disable extended logging. Important is adding logging when asynchronous actions occurs. As you can see in de log file example, initialization does not always happens in the sequence of the call to the rest services.

24/04/2012 - 18:05:30 - I - startup - getconfiguration start
24/04/2012 - 18:05:30 - I - startup - getconfiguration finished
24/04/2012 - 18:05:30 - I - eventAggregator-subscribe - mapLoaded
24/04/2012 - 18:05:30 - I - eventAggregator-subscribe - legendBuild
24/04/2012 - 18:05:30 - I - addNewDynamicLayer - http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer
24/04/2012 - 18:05:30 - I - addNewDynamicLayer - http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer
24/04/2012 - 18:05:30 - I - mapModule - init finished
24/04/2012 - 18:05:30 - I - startup - startup finished
24/04/2012 - 18:05:30 - I - layer_Initialized - http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer
24/04/2012 - 18:05:30 - I - addNewFeatureLayer - http://sampleserver3.arcgisonline.com/ArcGIS/rest/services/Fire/Sheep/FeatureServer/0
24/04/2012 - 18:05:30 - I - addNewFeatureLayer - http://sampleserver3.arcgisonline.com/ArcGIS/rest/services/Fire/Sheep/FeatureServer/1
24/04/2012 - 18:05:30 - I - layer_Initialized - http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer
24/04/2012 - 18:05:31 - I - featureLayer_Initialized - http://sampleserver3.arcgisonline.com/ArcGIS/rest/services/Fire/Sheep/FeatureServer/1
24/04/2012 - 18:05:31 - I - featureLayer_Initialized - http://sampleserver3.arcgisonline.com/ArcGIS/rest/services/Fire/Sheep/FeatureServer/0
24/04/2012 - 18:05:31 - I - eventAggregator-publish - mapLoaded - initializeVM
24/04/2012 - 18:05:31 - I - eventAggregator-publish - mapLoaded - legendBuild
24/04/2012 - 18:05:31 - I - legendModule - start legend build
24/04/2012 - 18:05:33 - I - legendModule - build legend done
24/04/2012 - 18:05:33 - I - eventAggregator-publish - legendBuild - selectBuild


4.     Navigation tools

The migration of the Silverlight C# implementation of Zoom in and Zoom out is very easy. As you can see by comparing the two implementations, a lot of coding is similar between C# and JavaScript.

Button commands

JavaScript Code
/// <summary>
/// Zoom In task
/// </summary>
   zoomInTask: function () {
      currentCommand = "ZoomIn";
      iGisOperation.setCompleteDrawEvent(drawComplete);
      iGisOperation.setDrawMode(esri.toolbars.Draw.EXTENT); // Start draw tool with a rectangle
   },
/// <summary>
/// Zoom Out task
/// </summary>
   zoomOutTask: function () {
      currentCommand = "ZoomOut";
      iGisOperation.setCompleteDrawEvent(drawComplete);
      iGisOperation.setDrawMode(esri.toolbars.Draw.EXTENT); // Start draw tool with a rectangle
   }

C# Code

/// <summary>
/// Zoom In task
/// </summary>
  public void ZoomInTask()
  {
       currentCommand = "ZoomIn";
       gisOperations.SetCompleteDrawEvent(DrawComplete);
       gisOperations.SetDrawMode(DrawMode.Rectangle); // Start draw tool with a rectangle
  }
/// <summary>
/// Zoom out task
/// </summary>
  public void ZoomOutTask()
  {
       currentCommand = "ZoomOut";
       gisOperations.SetCompleteDrawEvent(DrawComplete);
       gisOperations.SetDrawMode(DrawMode.Rectangle); // Start draw tool with a rectangle
  }

Activation of the zoom action

JavaScript code
iGisOperation.mapZoom(currentCommand, args.getExtent());
iGisOperations.setDrawMode(esri.toolbars.Draw.EXTENT);


C# code

gisOperations.MapZoom(currentCommand, args.Geometry);
gisOperations.SetDrawMode(DrawMode.Rectangle);

Next I use the zoom in the map method, this is the  main method used for  the zoom in and  the zoom out actions. Below is the comparison between the JavaScript and C# code.

Zoom action

JavaScript Code
mapZoom: function (action, geometry) {
  try {
        var multiplier;
        if (action == "ZoomIn") {
            map.setExtent(geometry.getExtent());
        }
        Else if (action == "ZoomOut") {
            var currentExtent = map.extent;
            var zoomBoxExtent = geometry.getExtent();
            var zoomBoxCenter = zoomBoxExtent.getCenter();
            var whRatioCurrent = currentExtent.getWidth() / currentExtent.getHeight();
            var whRatioZoomBox = zoomBoxExtent.getWidth() / zoomBoxExtent.getHeight();
            var newEnv = null;
            if (whRatioZoomBox > whRatioCurrent) {
            // use width
                multiplier = currentExtent.getWidth() / zoomBoxExtent.getWidth();
               var newWidthMapUnits = currentExtent.getWidth() * multiplier;
               newEnv = new esri.geometry.Extent(zoomBoxCenter.x - (newWidthMapUnits / 2), zoomBoxCenter.y,
                zoomBoxCenter.x + (newWidthMapUnits / 2), zoomBoxCenter.y, map.spatialReference);
            }
            else {
            // use height
                multiplier = currentExtent.getHeight() / zoomBoxExtent.getHeight();
                var newHeightMapUnits = currentExtent.getHeight() * multiplier;
                newEnv = new esri.geometry.Extent(zoomBoxCenter.x, zoomBoxCenter.y - (newHeightMapUnits / 2),
               zoomBoxCenter.x, zoomBoxCenter.y + (newHeightMapUnits / 2), map.spatialReference);
            }
            if (newEnv != null)
                            map.setExtent(newEnv.getExtent());
                    }
     } catch (e) {
           throw new Error("zoom action failed. " + e.name + "\n" + e.message);
     }
}

C# code

/// <summary>
/// Zoom in and out task
/// </summary>
/// <param name="action">ZoomIn or ZoomOut</param>
/// <param name="geometry">Rectangle to be zoomed</param>
public void MapZoom(string action, ESRI.ArcGIS.Client.Geometry.Geometry geometry)
{
  try
    {
       if (action.Equals("ZoomIn"))
       {
         mapControl.ZoomTo(geometry as Envelope);
       }
       Else if (action.Equals("ZoomOut"))
       {
         Envelope currentExtent = mapControl.Extent;
         Envelope zoomBoxExtent = geometry as Envelope;
         MapPoint zoomBoxCenter = zoomBoxExtent.GetCenter();
         double whRatioCurrent = currentExtent.Width / currentExtent.Height;
         double whRatioZoomBox = zoomBoxExtent.Width / zoomBoxExtent.Height;
         Envelope newEnv = null;
         if (whRatioZoomBox > whRatioCurrent)
         // use width
         {
           double multiplier = currentExtent.Width / zoomBoxExtent.Width;
           double newWidthMapUnits = currentExtent.Width * multiplier;
           newEnv = new Envelope(new MapPoint(zoomBoxCenter.X - (newWidthMapUnits / 2), zoomBoxCenter.Y),
          new MapPoint(zoomBoxCenter.X + (newWidthMapUnits / 2), zoomBoxCenter.Y));
         }
         else
         // use height
         {
           double multiplier = currentExtent.Height / zoomBoxExtent.Height;
           double newHeightMapUnits = currentExtent.Height * multiplier;
           newEnv = new Envelope(new MapPoint(zoomBoxCenter.X, zoomBoxCenter.Y - (newHeightMapUnits / 2)),
           new MapPoint(zoomBoxCenter.X, zoomBoxCenter.Y + (newHeightMapUnits / 2)));
         }
         if (newEnv != null)
           mapControl.ZoomTo(newEnv);
     }
  }
  catch (Exception ex)
  {
       messageBoxCustom.Show(String.Format("MapZoom-{0}", ex.Message),
       GisTexts.SevereError,MessageBoxCustomEnum.MessageBoxButtonCustom.Ok);
  }
}

As you can see for the two different language environment,  the same logic can be used for the two  projects.  The fact that the same code can be used is mainly due that both environments are based on the ArcGis Api’s that on its turn is based on the rest services from the ArcGis server. 

5.     Inspect tool

With the inspect tool we come to a more sophisticated tool. The inspect tool will retrieve all features that intersects with the point  indicated on the map using a predefined buffer.  The layers used on the map for the query must be checked on the selection tab of the tab control.  The results containing the attributes will be displayed on the query tab. To simplify the display, I used a html embedded template of knockoutJS that make use of dependency properties of the ViewModel easy to show.

Step 1 : Start a draw action of a point selection.

infoCommand: function () {
  try {
    iGisOperation.getMeasurement().clearResult();
    iGisCommonTasks.setFinishedInfoEvent(handleInfoResults);
    iGisCommonTasks.infoQuery(true);
  } catch (e) {
    logMessage("E", "IQ-01", e.Message);
  }
 }

Step 2: Start the draw action in the infoQuery method. When the tool is finished, the result will be returned in the callback function  ‘handleInfoResults’

        infoQuery: function (isSpatialQuery) {
            // Info implementation , using ESRI API
            iGisOperation.resetCompleteDrawEvent(drawComplete);
            if (isSpatialQuery)
                currentCommand = "Info";
            else
                currentCommand = "AddressInfo";
            iGisOperation.setCompleteDrawEvent(drawComplete);
            iGisOperation.setDrawMode(esri.toolbars.Draw.POINT);
        }
Step 3: Start the spatial query for all the selected layers.

if (currentCommand == "Info" || currentCommand == "AddressInfo") {
  // Use ESRI API for Info
  var layer = null;
  var i;
  var query, queryTask;
  iGisOperation.setDrawMode(null);
  iGisOperation.resetCompleteDrawEvent();
  result = new Array();
  errorCount = 0;
  errorMessages = '';
  totalCountLayers = 0;
  currentCountLayers = 0;
  if (currentCommand == "Info") {
    totalCountLayers = iGisOperation.getLayersSelectionInfos().length;
    for (i = 0; i < iGisOperation.getLayersSelectionInfos().length; i++) {
      if (iGisOperation.getLayersSelectionInfos()[i].selected && iGisOperation.getLayersSelectionInfos()[i].layer.type == 'Feature Layer') {
        queryTask = new esri.tasks.QueryTask(iGisOperation.getLayersSelectionInfos()[i].url);
        query = new esri.tasks.Query();
        query.geometry = args;
        query.outFields = ["*"];
        query.spatialRelationship = esri.tasks.SPATIAL_REL_INTERSECTS;
        queryTask.execute(query, function (features) {
           currentCountLayers += 1;
           if (!features.name) {
              if (features.features.length > 0)
                 result.push(features);
           }
           queryInfosComplete();
        }, function (e) {
           currentCountLayers += 1;
           errorCount += 1;
           errorMessages += ">";
           errorMessages += e.message;
           errorMessages += "\n";
           queryInfosComplete();
         });
       }
       else
         currentCountLayers += 1;
    }
  }

The function ‘queryInfosComplete’ is used to verify if all layers has been handled, if this is the case, the callback function is called to handle over the results to the view model.
    function queryInfosComplete() {
        if (currentCountLayers >= totalCountLayers) {
            if (finishedInfoOperation != null)
                finishedInfoOperation(result);
        }
    }

Step 4: Handle the results to the calling view model. We build here a list of all attributes found and that is used to scroll through.

    var attributesResults;
    var currentItem;
    function handleInfoResults(results) {
        try {
            iGisCommonTasks.setFinishedInfoEvent(null);
            attributesResults = new Array();
            var attributeItem;
            for (var i = 0; i < results.length; i++) {
                var resultFeatures = results[i].features;
                for (var j = 0; j < resultFeatures.length; j++) {
                    var attributes = new Array();
                    for (var item in resultFeatures[j].attributes) {
                        attributeItem =
                            new attribute(ko.observable(item), ko.observable(resultFeatures[j].attributes[item]));
                        attributes.push(attributeItem);
                    }
                    attributesResults.push(attributes);
                }
            }
            if (attributesResults.length > 0) {
                viewModel.resultAttributes(attributesResults[0]);
                viewModel.resultCount((attributesResults.length));
                currentItem = 1;
                viewModel.resultIndex(currentItem);
                viewModel.resultVisible(true);
            }
            else {
                alert("No results found");
            }
        } catch (e) {
            logMessage("E", "error in handleInfoResults->" + e.name + "\n" + e.message, "commonToolbarVM");
        }
    }
An array of attributes is extracted from the results is used to display attributes of one feature at a time.
Two buttons are used  to scroll through the results. The query tab looks like the example below.





The html code that is used for displaying the result above with the knockoutJS data binding is :
<div id="queryResults" data-bind="visible: resultVisible">
   <table class="tableResult">
       <thead ><span data-bind="text: resultHeader" style="font-size:medium"/>
            <th scope="col">Field</th>
            <th scope="col">Value</th>
       </thead>
       <tbody data-bind="foreach: resultAttributes">
            <tr>
                <td data-bind="text: field">
                </td>
                <td data-bind="text: value">
                </td>
            </tr>   
        </tbody>
   </table>
   <input id="nextResult" type="button" value="Next"  data-bind="click: nextResultExecute, enable: nextResultCanExecute" />
   <input id="prevResult" type="button" value="Previous" data-bind="click: prevResultExecute, enable: prevResultCanExecute" />
   <input id="closeResult" type="button" value="Close" data-bind="click: closeResultExecute, enable: closeResultCanExecute"  />
</div>

6.     Measure tools

In the Silverlight ArcGis API, this is a simple tool that can be implemented without writing custom code. The implementation happens through the interaction library from blend.
In JavaScript the job is much harder if you don’t want to use the ESRI widget and create your own button implementation. Creation of a custom measurement consist of the following steps:
·         Add the measurement widget to the html page during the onload event of the map.
·         Activate the measure tools through button commands.
·         In the callback method the geometry drawn is returned.
·         Call the geometry service for calculating area and length of the drawn geometry.
·         Return the values to the ViewModel for displaying .

Step : Add measurement widget in the init method of the map module

            dojo.connect(mapControl, 'onLoad', function (map) {
                //resize the map when the browser resizes
                dojo.connect(dijit.byId('map'), 'resize', map, map.resize);
                initToolbar(map);
            });
Step: Activate the measure tool in the widget.

measureCommand: function (tool) {
   iGisOperation.getMeasurement().clearResult();
   iGisOperation.getMeasurement().setTool(tool, true);
   dojo.connect(iGisOperation.getMeasurement(), "onMeasureEnd", function
      (activeTool, geometry) {
      this.setTool(activeTool, false);
      // calcuate the area or length of the geometry 
      if (activeTool == "area") {
        iGisGeoProcessing.calculateAreaLength(geometry, function
          calcResults(areaPolygon, lenPolygon) {
          if (areaPolygon != null)
           viewModel.message1("Area is " + areaPolygon.toFixed(0) + " m2 and the length is " + lenPolygon.toFixed(0) + " m");
          else
            viewModel.message1("Error occured during the calculation, see log file");
         });
       }
       else if (activeTool == "distance") {
         iGisGeoProcessing.calculateLength(geometry, function calcResults(lenPolyline) {
           if (lenPolyline != null)
             viewModel.message1("Length is " + lenPolyline.toFixed(0) + " m");
           else
             viewModel.message1("Error occured during the calculation, see log file");
          });
        }
      });
 }

Step  Calculate the area or length.

Two methods are added to the geo processing class to calculate the area and / or length of geometries. Later I will add more methods to this class for supporting the feature editing to
calculateAreaLength: function (geometry, callBack) {
   var areasAndLengthParams = new esri.tasks.AreasAndLengthsParameters();
   areasAndLengthParams.lengthUnit = esri.tasks.GeometryService.UNIT_METER;
   areasAndLengthParams.areaUnit = esri.tasks.GeometryService.UNIT_METERS;
   iGisOperation.getGeometryService().simplify([geometry],
      function (simplifiedGeometries) {
         areasAndLengthParams.polygons = simplifiedGeometries;
         iGisOperation.getGeometryService().areasAndLengths(areasAndLengthParams,
            function outputAreaAndLength(result) {
               var areaPolygon = result.areas[0];
               callBack(result.areas[0], result.lengths[0]);
            }, function errorAreaLength(err) {
            logMessage("E", "calculate area failed -->" + err.message, "commonToolbarVM");
                 callBack(null,null);
         });
   });
}

7.     Conclusion

By implementing these tools we already touched a lot of geometry processing. In the case of the inspect tool, I discover one important difference when executing spatial queries. In the Silverlight API you can add a tag to the query, so when the results are returned from the query, the tag can be used to identify the result set.  In the JavaScript API, no tag can be added to the query. The result of this is that I could not identify the origin (layer) from the features. I will later see if there is a way to work around this lack of information in the JavaScript API.

The use of geometry services will play an important role when we start to do feature editing. In the next document I will introduce some basic editing functionality.