Wednesday, March 7, 2012

ARCGIS JavaScript API – CREATION OF AN APPLICATION FRAMEWORK – PART I.


1.     Architecture


In previous documents I explained how you can encapsulate the ArcGis Silverlight API’s into a component library to simplify the creation of application based on some high level functionality. At the same time there was also a strict separation between GUI and the functionality of tools and commands. In JavaScript I will outline how we can use the same patterns to create a more high level JavaScript component library and also make code free HTML pages.

The  exercise I will do is the same as with the Silverlight application, namely I will produce a small general ArcGis Desktop Editor based on some of the functionality of ArcGis Desktop 10. To make the application ready for the future, a HTML5 environment is used. The configuration file is the same XML file as used in the Silverlight application.

The migration of the Silverlight components and classed consist of the following jobs to be done:

·         Migrate the GIS services into a JavaScript class (function) implementing all methods that are defined in the interfaces. Because there is almost one to one relation between the ArcGis Silverlight API functionality and the ArcGis JavaScript API functionality, the migration path is relatively trivial,  the latter is mainly because the two API’s are based on the REST services of ArcGis Server.

·         Configuration of the application is based on the same XML configuration file of the Silverlight Application. The only difference is that I use a WCF service to convert the XML file into a JSON object. This is a very simple WCF service and can also be easy written into other languages as JAVA. You could also use a JSON file that is transferred to the client.

·         In Silverlight I use a framework PRISM and a pattern MVVM that separates GUI and business logic and also implements IoC (inversion of control) and DI (dependency injection). MVVM is built into Silverlight, PRISM is an additional library. In JavaScript we can use MVVM through a library KNOCKOUTJS. The library will mainly be used to handle dependency properties (text and events) on the HTML pages. By stripping of the code from HTML pages, it will be a lot easier to use the same pages in other devices like mobiles. I hope later to illustrate this using some specific JavaScript libraries that will help us to achieve this. I will not use IoC and DI because we do not have the concept of modules as in Silverlight, and that it is also  in JavaScript to make the GIS libraries global over the different modules.  However some patterns found in PRISM will be implemented in JavaScript.

·         In tise exercise,  the different parts that makes up a solution will be implemented as module libraries. Examples are the map module, legend module and different toolbar modules. As in the Silverlight solution, the modules must be independent from each other, no reference is made to each other. A PRISM pattern will be used to communicate between the different modules.

·         Mandatory JavaScript libraries used are jquery, DOJO and KNOCKOUTJS

·         Use of CCS3 can be used to format the application. Because web design is not the focus of these series of documents, little attention will be done to the CSS formatting.

2.     Application development structure


The application architecture consists of different JavaScript library organized in different folders as outlined below :

Gis                           gisCommonTasks.js
                                       gisEditing.js
                                       gisGeoProcessing.js
                                       gisOperation.js

Helper                    dataMapping.js
                                       eventAggregator.js

Modules                 layerSelectionModule.js
                                       legendModule.js
                                       mapModule.js
                                       toolbarEditModule.js
                                       toolbarSimpleModule.js

ViewModels                mainViewModel.js

 The GIS libraries corresponds to what I did in Silverlight. The main function is to encapsulate the ArcGis JavaScript API into more usable high level methods. Doing so, less experienced ArcGis developers can step into the GIS development team.  The GIS libraries use the JavaScript pattern ‘enclosure’ to expose only the methods, similar to using interfaces in the Silverlight C# implementation of the GIS classes.  By using this pattern it becomes more simple to migrate the Silverlight libraries I developed .
The Helper libraries are support libraries for GIS and modules. ‘dataMapping’ contains classes we need  in the application.

Module libraries consists of an important part of the application. Because de ArcGis components must be created in JavaScript through DOJO, the main job will be the building of these visual components. Some hooks on events will be needed as we cannot use the view model to bind to these components. The only module that is needed, is the map module, all other modules are optional.
ViewModels consist of the view model needed for the HTML page. A big part of the view model will be interfacing with the different GIS services. Because there is no specific data handling, no models are defined. All interactions with the REST services happens through the GIS services as its nature is generic.

3.     The application template


As previous explained, a lot of JavaScript libraries are needed at the startup of the page. Below you see an overview of the current JavaScript libraries used.

<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.2.min.js"></script>

<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.14/jquery-ui.min.js"></script>
<script type="text/javascript" src="scripts/knockout-2.0.0.js"></script>
<script type="text/javascript" src="scripts/Helper/dataMapping.js"></script>
<script type="text/javascript" src="scripts/Helper/eventAggregator.js"></script>
<script type="text/javascript" src="scripts/Gis/gisOperation.js"></script>
<script type="text/javascript" src="scripts/Modules/legendModule.js"></script>
<script type="text/javascript" src="scripts/Modules/mapModule.js"></script>
<script type="text/javascript" src="scripts/globalVars.js"></script>
<script type="text/javascript" src="scripts/ViewModels/mainViewModel.js"></script>
<script type="text/javascript" src="scripts/startup.js"></script>

This comes after we loaded the Dojo and ArcGis API libraries.
The body has a very simple layout. The layout consist of Dojo containers organized in a classical way with a header, two columns consisting of a tab container and a simple container, followed by a footer.

The header will host different toolbars as it was the case in the Silverlight application. The tab container will host our legend, search layers and a query information. The right column will host the map. The footer will display different informations.
<body class="claro">
         <div id="appLayout" class="arcgisLayout" 
                          data-dojo-type="dijit.layout.BorderContainer" 
                          data-dojo-props="design: 'headline'">
                 <div class="edgePanel" id="header"
                          data-dojo-type="dijit.layout.ContentPane"                
                          data-dojo-props="region: 'top'">
                          <div data-bind="html: applicationIcon"></div>
                          <label data-bind="text: applicationName"></label>
                 </div>
                 <div id="tocPanel" class="leftPane" 
                          data-dojo-type="dijit.layout.TabContainer" 
                          data-dojo-props="region: 'left', tabPosition: 'bottom'">
                          <div data-dojo-type="dijit.layout.ContentPane" title="Legend">
                                   <div id="legendRegion">
                                   </div>
                          </div>
                          <div data-dojo-type="dijit.layout.ContentPane" title="Select">
                                   <div id="selectRegion">
                                   </div>
                          </div>
                          <div data-dojo-type="dijit.layout.ContentPane" title="Query">
                                   <div id="queryRegion">
                                   </div>
                          </div>
                 </div>
                 <div id="mapPanel" class="edgePanel"
                          data-dojo-type="dijit.layout.ContentPane"            
                          data-dojo-props="region: 'center'">
                          <div id="mapRegion">
                          </div>
                 </div>
                 <div class="edgePanel" id="footer"
                          data-dojo-type="dijit.layout.ContentPane" 
                          data-dojo-props="region: 'bottom'">bottom
                 </div>
         </div>
</body>

The result will look something  like this:

4.     Startup


In Silverlight we use a bootstrapper class to start the application. The bootstrapper is part of PRISM an is responsible for the setup of the MEF container that will be used for IoC and DI. In JavaScript I will use a startup JavaScript that will kick off the application.
The first step in the startup is getting the configuration from the server. This will be done through  an AJAX call to the server that hosts a WCF service. At return we will have the full configuration into a JSON object ‘configuration’. This is a global variable, making it easy to access configuration data through the whole project.

Next we need to create the GIS objects. Because the GIS objects are global, we can use them within modules and view models. Only the methods are exposed in the GIS objects.
Finally, the module objects are created and initialized. The map module must be initialized as last, this is because of the publish / subscribe pattern of the eventAggregator class used to communicate when the map has been loaded and the other modules.

5.     Configuration


Through the ready function of jquery the configuration is retrieved doing an Ajax call. In the success method we get the configuration as a JSON object.  Once this is done, we can start building the map dynamically from the layers defined in the configuration XML file. If modules have no dependency of the map, you can place them everywhere after the configuration assignment otherwise you must initialize and subscribe to the map loaded event before the map initialization.

$(document).ready(
         function () {
             try {
                 GetConfiguration();
             } catch (exception) { }
         }
);
 
function GetConfiguration() {
    Type = "GET";
    Url = GetConfigURl;
    DataType = "jsonp";
    Data = 'application=' + applic;
    ProcessData = false;
    method = "GetAConfig";
    CallService();
}
 
function ServiceSucceeded(result) {
    if (DataType == "jsonp") {
        if (method == "GetAConfig") {
            configuration = result;
            // Create the Gis classes, making its interface available           
            iGisOperation = new gisOperation();
            // Initialise the modules, start the map module as last
            legendModule = new legendModule();
            legendModule.initLegend("legendRegion");
            mapModule = new mapModule();            
            // Configuration done, start creation of the map
            mapModule.initMap("mapRegion", mapLayersInitialized);
        }
        else {
            resultObject = result.GetEmployeeResult;
            var string = result.Name + " \n " + result.Address;
            alert(string);
        }
    }
}

6.     EventAggregator


To communicate between modules, without having a tight connection between the modules, I implemented some functionality in JavaScript similar to the EventAggregator class of PRISM in Silverlight.
This class uses two methods to realize the communication:

·         Subscribe – This method will add to a list of event handlers a callback function that will be called when an event is triggered.  Optional a tag can be saved at the same moment for use in the callback function. The event that is triggered, is defined by a name and can return data to the callback function.

·         Publish - When this method executes, all callback functions related to the event are fired and optional data is passed.

This simple implementation makes it easier to communicate between modules, without  the publisher having knowledge of who needed attention when the event is fired . A good example is the map module, which publish the map loaded event. Modules as the legend will only start populating the labels after the map has been loaded and all information about the layers is available. The same is true for the view model which can only update information related to the configuration or GIS information.

De eventAggregator class implementation :

var eventAggregator = (function () {
    var events = new Array();
    return {
        subscribe: function (eventName, eventAction, tagData) {
            events.push(new eventElement(eventName, eventAction, tagData));
        },
        publish: function (eventName, eventData) {
            for (var i = 0; i < events.length; i++) {
                if (eventName == events[i].eventName) {
                    if (events[i].eventAction != null)
                        events[i].eventAction(eventName, eventData, events[i].tagData);
                }
            }
        }
    }
});

7.     GIS Services


Migrating the Silverlight C# application towards a JavaScript application consist mostly of migrating the C# code of the services into JavaScript code. Lucky,  because the Silverlight API and JavaScript API are an encapsulation of the REST services provided by the ArcGis server, much of the code is similar. In some cases data has to be retrieved in a different way with JavaScript API than in the case of the Silverlight  API.  Sometimes LINQ is used  to replace loops in C#, resulting in more code to be written in JavaScript.
Let’s see how I convert the C# GisOperations.cs class in to a JavaScript Class.

First create a JavaScript class based on the ‘enclosure’ pattern like this :
var gisOperation = (function () {
         // Private variables
         var map;
         var ArcGISServiceType = { Unknown: 0, Cached: 1, Dynamic: 2, Image: 3, Feature: 4 };
         var geometryService = null;
         var layerCount = 0;
         var layerInitialised = 0;
         var mapInitialiseComplete = null;
         var baseMapLayerInfos;
         var featureLayerInfos;
         var layersData;
         var graphicsLayerSelect = null;
         var isFeatureLayersLoaded = false;
         var selectSymbol;
         var flInfo;
         return {
                 setMap: function (mapControl) {
                 
                 },
                 getMap: function () {
                                           },
                 initialize: function (geometryServiceUrl) {
                          
                 },
                 getSelectLayer: function () {
                          
                 },
                 getFeatureLayer: function (layerName) {
                 },

               

         }
});

 As you can see, it is very simple to use the interfaces I use in Silverlight C#, and put them into a JavaScript class. Only the functions in the return are exposed in the application, similar as interfaces are used in the Silverlight Application.
In Silverlight we had an interface IGisOperations that’s looks like

       public interface IGisOperations
       {
             Map GetMap();
             void Initialize(string geometryServiceUrl);
             GraphicsLayer GetRoutingLayer();
             GraphicsLayer GetSelectLayer();
             FeatureLayer GetFeatureLayer(string layerName);
             void SetLayers(Map mapControl);
             void AddNewDynamicLayer(string layerUrl, bool visible, string layerName, ArcGISServiceType serviceType);
             void AddNewDynamicLayer(string layerUrl, bool visible, string layerName, int index, ArcGISServiceType serviceType);
             void AddNewFeatureLayer(string layerUrl, bool visible, string layerName);
             void RemoveDynamicLayer(string layerName);
             void AttributeQueryTask_Async(string whereValue, string whereField, string layerName, string fieldType, ESRI.ArcGIS.Client.Geometry.Geometry geometry);
             void AttributeQueryTask_ExecuteCompleted(object sender, QueryEventArgs args);
             void AttributeQueryTask_Failed(object sender, TaskFailedEventArgs args);
             void SetFinishedEvent(ResultsHandler finishedOperation);
             void SetCompleteDrawEvent(DrawCompleteHandeler drawComplete);
             void ResetCompleteDrawEvent(DrawCompleteHandeler drawComplete);
             void SetMapInitialiseCompleteEvent(InitialiseCompleteHandler mapInitialiseComplete);
             MarkerSymbol GetSelectionMarkerSymbol();
             LineSymbol GetSelectionLineSymbol();
             FillSymbol GetSelectionFillSymbol();
             void SetSelectionMarkerSymbol(MarkerSymbol symbol);
             void SetSelectionLineSymbol(LineSymbol symbol);
             void SetSelectionFillSymbol(FillSymbol symbol);
             void SetDrawMode(DrawMode drawMode);
             void SetDrawModeContinuous(DrawMode drawMode);
             void DisableDrawMode();
             void EnableDrawMode();
             void MapZoom(string action, Geometry geometry);
             FeatureLayerInfo GetFeatureLayerInfo(int id);
             FeatureLayerInfo GetFeatureLayerInfo(string layerName);
             GeometryService GetGeometryService();
             void CenterAndZoom(MapPoint point, double resolution);
             IList<LayerData> GetLayersData();
             double Resolution2Scale(double resolution);
             double Scale2Resolution(double scale);
             IList<FeatureLayerInfo> GetFeatureLayerInfos();
             IList<BaseMapLayerInfo> GetBaseMapLayerInfos();
             void CreateGeometry(DrawMode drawMode, DrawCompleteHandeler drawComplete);
             void SetMapSize(int offset);
             void SetTabControlVisible(bool tabControlVisible);
             int GetLayerTocSelected();
             void SetLayerTocSelected(int layerIndex);
             void ZoomTo(Geometry geometry);
             Envelope GetNormalizedExtent(Envelope extent);
       }

All this methods with the corresponding parameters must be added in the return of the gisOperation function. Functions used before the return statement are private functions in the class like the variables.

8.     Migration of adding a dynamic layer


In this section you can see how the functionality for adding a dynamic layer in Silverlight / C# is transformed into JavaScript. The method that we will implement is AddNewDynamicLayer.

In Silverlight/C# we have :

public void AddNewDynamicLayer(string layerUrl, bool visible, string layerName, ArcGISServiceType serviceType)
{
      if (serviceType == ArcGISServiceType.Dynamic)
      {
            ArcGISDynamicMapServiceLayer mapLayer = new ArcGISDynamicMapServiceLayer()
            {
                  Url = layerUrl,
                  DisableClientCaching = true,
                  ID = layerName,
                  Visible = visible
            };
            mapLayer.Initialized += InitializedDynamicLayer;
            mapLayer.InitializationFailed += layer_InitializationFailed;
            mapControl.Layers.Add(mapLayer);
      }
      else if (serviceType == ArcGISServiceType.Image)
      {
            ArcGISTiledMapServiceLayer mapLayer = new ArcGISTiledMapServiceLayer()
            {
                  Url = layerUrl,
                  ID = layerName,
                  Visible = visible
            };
            mapLayer.Initialized += InitializedDynamicLayer;
            mapLayer.InitializationFailed += layer_InitializationFailed;
            mapControl.Layers.Add(mapLayer);
      }  
}

In JavaScript we the code looks like that:

function _addNewDynamicLayer(layerUrl, visible, layerName, serviceType) {
         var mapLayer;
         if (serviceType == ArcGISServiceType.Dynamic) {
                  mapLayer = new esri.layers.ArcGISDynamicMapServiceLayer(layerUrl,
                         id: layerName,
                          visible: visible
                  });
                 dojo.connect(mapLayer, "onLoad"function (layer) {
                          layer_Initialized(layer);
                 });
                 dojo.connect(mapLayer, "onError"function (err) {
                          layer_InitializationFailed(err);
                 });
         }
         else if (serviceType == ArcGISServiceType.Image) {
                 mapLayer = new esri.layers.ArcGISTiledMapServiceLayer(layerUrl,
                        id: layerName,
                          visible: visible
                 });
                 dojo.connect(mapLayer, "onLoad"function (layer) {
                          layer_Initialized(layer);
                 });
                 dojo.connect(mapLayer, "onError"function (err) {
                          layer_InitializationFailed(err);
                 });
         }
}

As you can see in the example above, there is not much difference between the C# and JavaScript  development.  As we see later, the major difference of the API’s is in the widgets.

In the next document I will go deeper how after the map is loaded, all information about layers is available and can be used to build a custom legend. As in the Silverlight application, once the map is loaded, all information needed for  building a custom or editing widgets is available and can be exposed through our view model.

11 comments:

  1. could you please send us this source code to be reusable

    ReplyDelete
  2. Hi, Great.. Tutorial is just awesome..It is really helpful for a newbie like me..
    I am a regular follower of your blog. Really very informative post you shared here.
    Kindly keep blogging. If anyone wants to become a Front end developer learn from Javascript Training in Chennai .
    or Javascript Training in Chennai.
    Nowadays JavaScript has tons of job opportunities on various vertical industry. ES6 Training in Chennai

    ReplyDelete
  3. David Walsh is Mozilla’s senior web developer, and the core developer for the MooTools
    Javascript Framework. David’s blog reflects his skills in HTML/5, JS and CSS, and offers a ton
    of engaging advice and insight into front-end technologies. Even more obvious is his passion
    for open source contribution and trial-and-error development, making his blog one of the
    most honest and engaging around.
    Website: davidwalsh.name

    ReplyDelete
  4. David Walsh is Mozilla’s senior web developer, and the core developer for the MooTools Javascript Framework. David’s blog reflects his skills in HTML/5, JS and CSS, and offers a ton of engaging advice and insight into front-end technologies. Even more obvious is his passion for open source contribution and trial-and-error development, making his blog one of the most honest and engaging around.
    Website: davidwalsh.name

    ReplyDelete
  5. David Walsh is Mozilla’s senior web developer, and the core developer for the MooTools Javascript Framework. David’s blog reflects his skills in HTML/5, JS and CSS, and offers a ton of engaging advice and insight into front-end technologies. Even more obvious is his passion for open source contribution and trial-and-error development, making his blog one of the most honest and engaging around.
    Website: davidwalsh.name

    ReplyDelete
  6. David Walsh is Mozilla’s senior web developer, and the core developer for the MooTools Javascript Framework. David’s blog reflects his skills in HTML/5, JS and CSS, and offers a ton of engaging advice and insight into front-end technologies. Even more obvious is his passion for open source contribution and trial-and-error development, making his blog one of the most honest and engaging around.
    Website: davidwalsh.name

    ReplyDelete
  7. David Walsh is Mozilla’s senior web developer, and the core developer for the MooTools Javascript Framework. David’s blog reflects his skills in HTML/5, JS and CSS, and offers a ton of engaging advice and insight into front-end technologies. Even more obvious is his passion for open source contribution and trial-and-error development, making his blog one of the most honest and engaging around.
    Website: davidwalsh.name

    ReplyDelete
  8. David Walsh is Mozilla’s senior web developer, and the core developer for the MooTools Javascript Framework. David’s blog reflects his skills in HTML/5, JS and CSS, and offers a ton of engaging advice and insight into front-end technologies. Even more obvious is his passion for open source contribution and trial-and-error development, making his blog one of the most honest and engaging around.
    Website: davidwalsh.name

    ReplyDelete
  9. David Walsh is Mozilla’s senior web developer, and the core developer for the MooTools Javascript Framework. David’s blog reflects his skills in HTML/5, JS and CSS, and offers a ton of engaging advice and insight into front-end technologies. Even more obvious is his passion for open source contribution and trial-and-error development, making his blog one of the most honest and engaging around.
    Website: davidwalsh.name

    ReplyDelete
  10. David Walsh is Mozilla’s senior web developer, and the core developer for the MooTools Javascript Framework. David’s blog reflects his skills in HTML/5, JS and CSS, and offers a ton of engaging advice and insight into front-end technologies. Even more obvious is his passion for open source contribution and trial-and-error development, making his blog one of the most honest and engaging around.
    Website: davidwalsh.name

    ReplyDelete
  11. David Walsh is Mozilla’s senior web developer, and the core developer for the MooTools Javascript Framework. David’s blog reflects his skills in HTML/5, JS and CSS, and offers a ton of engaging advice and insight into front-end technologies. Even more obvious is his passion for open source contribution and trial-and-error development, making his blog one of the most honest and engaging around.
    Website: davidwalsh.name

    ReplyDelete