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.

1 comment:

  1. Wow…Thats impressive such a informative blog thanks for sharing it.
    toolbar editor


    ReplyDelete