Thursday, 16 June 2016

Using ArcGIS API for JavaScript Edit functionality without a map AND without the Attribute Inspector Widget

Remember the last post I made? Well, it turns out the client didn't want the use of the Attribute Inspector - she wanted a plain form for editing a feature layer's attributes!! What to do?
Turns out it's not that much different from what we did the last time. This post will describe how we went about it.


JS Bin on jsbin.com

Once again, the objective was to edit attributes of features from a Feature Layer without the confines of a map container.

A basic HTML form was created on a blank page where they could make changes to features without having to see them on a map - choosing instead to reference them on the basis of IDs which are part of their attributes. So what did we do different? Very few things actually.

For starters since we did not have the advantage of having the attribute inspector load up all our required fields, we created the html form for data input as shown below:



  
Choose Work ID:

As you can see, actual field inputs needed to be placed in. I did not place them in an organized manner in this example but I hope you get the general idea of what I was doing. One may notice that the first two inputs are actually dojo comboBoxes. These are the fields that had coded values as domains and thus required only predetermined values to be entered. We therefore had to populate the comboBoxes with values from the feature service. We did this by querying the feature service for coded domain values and loading them into a datastore that feeds the comboBox as shown below:

 //Now query to get distinct values for Project Type
 // query distinct use code values from parcels
 var queryTask = new QueryTask(url);


 var query = new Query();
 query.where = "PROJTYPE is not null";  // query for non-null values
 query.orderByFields = ["PROJTYPE"];    // sort it so we won't have to later
 query.returnGeometry = false;          // turn geometry off, required to be false when using returnDistinctValues
 query.returnDistinctValues = true;
 query.outFields = ["PROJTYPE"];


 queryTask.execute(query, function(results){
  var resultItems = [];
  var resultCount = results.features.length;
  
  var store2 = new Memory({data:[]}); 
    
  dijit.byId("uniqueValuesSelect").set('store',store2);  
  var data = array.map(results.features,lang.hitch(this,function(info,index){  
    var value = info.attributes.PROJTYPE; 
    console.log(info);       
   
    var dataItem = {  
   id:index,  
   name:value  
    };  
    return dataItem;  
  }));  
  store2 = new Memory({data:data});  
  dijit.byId("uniqueValuesSelect").set('store',store2);
 });
 
 //Now query to get distinct values for Work Status
 // query distinct use code values from parcels
 var queryTask2 = new QueryTask(url);


 var query2 = new Query();
 query2.where = "WORKSTATUS is not null";  // query for non-null values
 query2.orderByFields = ["WORKSTATUS"];    // sort it so we won't have to later
 query2.returnGeometry = false;          // turn geometry off, required to be false when using returnDistinctValues
 query2.returnDistinctValues = true;
 query2.outFields = ["WORKSTATUS"];

 //Now use query results to find out the coded domain values for the layer field

 queryTask2.execute(query2, function(results){
  var resultItems = [];
  var resultCount = results.features.length;
  
  var store3 = new Memory({data:[]}); 
    
  dijit.byId("uniqueValuesSelect2").set('store',store3);  
  var data = array.map(results.fields[0].domain.codedValues,lang.hitch(this,function(info,index){  
    var value = info.name; 
    console.log(info);       
   
    var dataItem = {  
   id:index,  
   name:value  
    };  
    return dataItem;  
  }));  
  store3 = new Memory({data:data});  
  dijit.byId("uniqueValuesSelect2").set('store',store3);
 });
   


Once this was done, everything else was pretty easy. On the select-change event of the dropdown menu, the feature service would be queried for the relevant work ID and the query results pointed to the relevant HTML inputs for display as shown below:

//Capture the select - change event of the HTML select element
 on(dom.byId("workID"),"change",function (evt){
 
  //Initialize query based on the selected value
  var selectQuery = new Query();

  selectQuery.where = "WORKID = " + evt.target.value;
  teamsFL.selectFeatures(selectQuery, FeatureLayer.SELECTION_NEW, function(features) {
    if (features.length > 0) {
   //store the current feature
   updateFeature = features[0];
   document.getElementById("uniqueValuesSelect").value = updateFeature.attributes.PROJTYPE;
   document.getElementById("uniqueValuesSelect2").value = updateFeature.attributes.WORKSTATUS;
   document.getElementById("CHARGECODE").value = updateFeature.attributes.CHARGECODE;
   document.getElementById("LOCATION").value = updateFeature.attributes.LOCATION;
   
   console.log(updateFeature.attributes.CHARGECODE);
    }
    else {
   
    }
  }); 
  
  
  
   if(i == 0){
   i++;
   
   //Add the Save button
   var saveButton = new Button({ label: "Save", "class": "saveButton"},domConstruct.create("div"));
   domConstruct.place(saveButton.domNode, "buttonious");
     
   //Add the functionality of the save button
     saveButton.on("click", function() {
     
    updateFeature.attributes.PROJTYPE = document.getElementById("uniqueValuesSelect").value;
    updateFeature.attributes.WORKSTATUS = document.getElementById("uniqueValuesSelect2").value;
    updateFeature.attributes.CHARGECODE = document.getElementById("CHARGECODE").value;
    updateFeature.attributes.LOCATION = document.getElementById("LOCATION").value;
    
    
     
    updateFeature.getLayer().applyEdits(null, [updateFeature], null, function (adds, updates, deletes) {
     alert("Updated feature successfully, OBJECTID: " + updates[0].objectId);
     document.getElementById("CHARGECODE").value = "";
     document.getElementById("LOCATION").value = "";
    }, function (err) {
     //when an error occurs
     alert("Apply Edits Failed: " + err.message);
    })
     });
   
   }else{
   console.log("already added attribute inspector");
   }
    
 });


Saving the changes on the feature service layer followed the same pattern as before, the only difference being that this time, specifications were made as to which input value belonged to which feature attribute as shown below:


updateFeature.attributes.PROJTYPE = document.getElementById("uniqueValuesSelect").value;
updateFeature.attributes.WORKSTATUS = document.getElementById("uniqueValuesSelect2").value;
updateFeature.attributes.CHARGECODE = document.getElementById("CHARGECODE").value;
updateFeature.attributes.LOCATION = document.getElementById("LOCATION").value;
    

That was pretty much it. Of course, the sample given here needs to be made more appealing (don't present such work to a client - dress it up with good formatting and CSS) but that was basically the guts of what our form solution entailed. I hope it was informative and helps someone some day.

Happy Coding!!!

Sunday, 12 June 2016

Using the ArcGIS API for JavaScript AttributeInspector widget without a map

Though I am not currently working as a GIS developer, I am frequently called upon to make custom applications using the ArcGIS API for JavaScript. Recently a colleague of mine and I were faced with the task of creating a form from which to make updates to a Feature Layer without actually loading a map on the interface. Usually, people use the Edit Widget to edit attributes and geometry of features from a Feature Layer but as the linked sample above shows, it is mostly done within the confines of a map container.
The clients we were working for did not want a map container but instead needed a form on a blank page where they could make changes to features without having to see them on a map - choosing instead to reference them on the basis of IDs which are part of their attributes. The following is what we implemented.

JS Bin on jsbin.com

Though you can have an idea of how we tackled the situation through the JSBin above, let's go through a bit of the JavaScript code:

  • After referencing the FeatureLayer as shown below, we initialized a variable to simulate the first loading of the attribute inspector:
       
//Feature Layer representing Capital Projects around Nairobi
var teamsFL = new FeatureLayer("http://services.arcgis.com/CmINIEzurW7Tagtl/arcgis/rest/services/Infrastructural_Alerts/FeatureServer/4", {
      outFields: ["*"]
    });

//Initialize i to show that this is the first time the script is being run
i = 0;
 
 
  • We then set up the process that would allow for querying of a feature from the layer based on the Work ID attribute provided from the dropdown menu. This was bound to the select-change event of the menu:
       
//Initialize query based on the selected value
var selectQuery = new Query();
selectQuery.where = "WORKID = " + evt.target.value;

teamsFL.selectFeatures(selectQuery, FeatureLayer.SELECTION_NEW, function(features) {
if (features.length > 0) {
   //store the current feature
   updateFeature = features[0];
}
else {}
}); 
 
 
  • A definition of the feature attributes that would be available for changing was then specified, along with the type of data field that they were:
       
       //Define the details that will be dislayed in the Attribute Inspector element
      var layerInfos = [
        {
          'featureLayer': teamsFL,
          'showAttachments': false,
          'showDeleteButton': false,
          'isEditable': true,
          'fieldInfos': [
            {'fieldName': 'PROJTYPE', 'isEditable': true},
            {'fieldName': 'WORKSTATUS', 'isEditable': true,"stringFieldOption": AttributeInspector.STRING_FIELD_OPTION_TEXTAREA},
            {'fieldName': 'CHARGECODE', 'isEditable': true,"stringFieldOption": AttributeInspector.STRING_FIELD_OPTION_TEXTAREA},
            {'fieldName': 'ACTSTART', 'isEditable': true},
            {'fieldName': 'LOCATION', 'isEditable': true,"stringFieldOption": AttributeInspector.STRING_FIELD_OPTION_TEXTAREA}
          ]
        }
      ];

 
  • Finally, the Attribute Inspector widget was initialized and its functionality applied i.e. the possibility to post edits to a Feature Service using the applyEdits method. The main posting happens within the on click function of the Save button as shown in the code below:
       
if(i == 0){
   i++;
   //Initialize Attribute Inspector
   attInspector = new AttributeInspector({
    layerInfos: layerInfos
   }, "mapDiv");
   
   //Add the Save button
   var saveButton = new Button({ label: "Save", "class": "saveButton"},domConstruct.create("div"));
     domConstruct.place(saveButton.domNode, attInspector.deleteBtn.domNode, "after");
    
   //Add the functionality of the save button
     saveButton.on("click", function() {
    updateFeature.getLayer().applyEdits(null, [updateFeature], null, function (adds, updates, deletes) {
     
     alert("Updated feature successfully, OBJECTID: " + updates[0].objectId);
    }, function (err) {
     //when an error occurs
     alert("Apply Edits Failed: " + err.message);
    })
     });
     
     
     attInspector.on("attribute-change", function(evt) {
    //store the updates to apply when the save button is clicked
    updateFeature.attributes[evt.fieldName] = evt.fieldValue;
     });
   
   }else{
   console.log("already added attribute inspector");
   }

 

PS: Please note that this sample uses dropdown menus to get the parameter to be used for querying. It is not a generic sample either.  Therefore, you may need to learn the concept well (at least what happens on the save button click event) and apply it to your situation.

For more information on how to utilize the editing capabilities without using a map, check out this and this Stack Overflow Post as well as the ArcGIS API for JavaScript API reference.

Happy Coding!!

Saturday, 11 June 2016

New Direction for the blog

Hi all!!

My name is Laban Karanja a.k.a the Sowanch whose name the site holds. This blog has in the past been primarily used for demos I did with the company I work for and thus has only had filler material thus far. I intend to change that from today starting with this post.

From now on, this blog will primarily focus on solutions to issues I have faced so far on the ArcGIS platform, though I might dabble in a few other GIS platforms as well. I hope someone will find use for these posts as I share them. Though assumptions will be made concerning the reader's proficiency in the use of ArcGIS tools and services, I will make an effort to link key terms to pages which help explain them.

I am committing to start by sharing a solution every week, please hold me to it. Other than that, thank you for reading this and shout out to this guy for inspiring me to do this.