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.jsgisGeoProcessing.js
gisOperation.js
Helper dataMapping.js
eventAggregator.js
Modules layerSelectionModule.js
legendModule.jsmapModule.js
toolbarEditModule.js
toolbarSimpleModule.js
ViewModels mainViewModel.js
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) {
},
…
}
});
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.
could you please send us this source code to be reusable
ReplyDeleteHi, Great.. Tutorial is just awesome..It is really helpful for a newbie like me..
ReplyDeleteI 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
David Walsh is Mozilla’s senior web developer, and the core developer for the MooTools
ReplyDeleteJavascript 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
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.
ReplyDeleteWebsite: davidwalsh.name
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.
ReplyDeleteWebsite: davidwalsh.name
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.
ReplyDeleteWebsite: davidwalsh.name
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.
ReplyDeleteWebsite: davidwalsh.name
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.
ReplyDeleteWebsite: davidwalsh.name
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.
ReplyDeleteWebsite: davidwalsh.name
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.
ReplyDeleteWebsite: davidwalsh.name
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.
ReplyDeleteWebsite: davidwalsh.name