As from Dojo 1.7, the pattern of using
modular JavaScript classes was introduced to load JavaScript files
asynchronous, no longer requiring loading the files at the retrieve of a html
files or at least create a much simpler approach of this pattern. With the
version 3.2 of the ArcGis JavaScript library ESRI introduced AMD for its portal
API. ESRI will extend the use of AMD in other modules with later releases.
In this document I will illustrate how we
can use this pattern to refactor the GIS framework described in earlier
documents. You will also see how popular
other libraries can fit into this pattern.
2.
The modular approach
In modular languages the last years new design patterns has been
introduced to create modular and maintainable applications. Two patterns that
emerged are separation of concerns and loose coupling. In the picture below I outlined the flow of
the different modules with the dependencies between them. The way Dojo AMD works can at a certain stage be compared
to the use of PRISM for Silverlight/WPF
applications which is a library that implements the two patterns separation of concerns and
loose coupling .
Dojo AMD is responsible to create classes
and inject there dependencies into the modules which helps us to create the
modular concept. One of the goals of AMD is to restrict the use of global
variables and encapsulate as much as possible into the created classes this
covers the separation of concerns.
In
the previous version of my framework I heavenly relied on global variables that
were created at the startup. With AMD this will no longer be the case, as they
will be maintained by Dojo and can be compared to the pattern of loose
coupling.
In PRISM on Silverlight/WPF there is component
container that contains all objects which can be defined with a single or multiple occurrence.
Most of the modules we will need does
not require multiple instances, but rather requires to be a singleton. Dojo AMD does not have a definition
singletons. The map module, legend module and others requires only a single
instance. Below you will find the blue print of a module definition that we can
use to implement a singleton class in
Dojo AMD . You can find more information on this pattern at
http://unscriptable.com/2011/09/22/amd-module-patterns-singleton/
A typical singleton module has the
structure outlook below.
define ([…],function () {
var
instance;// Add here the local variables and the local functions
function Singleton() {
}
Singleton.prototype
= { // Add here your public methods
}
return (instance || (instance || new Singleton()));
});
When the require function create the class
it always returns the same object. In our application all component modules as map, legend, toolbars are created
as a singleton. This is also the case for the viewmodels in the MVVM pattern,
except the main viewmodel , that I will explain later.
In a future document I will illustrate a
regular AMD class that will create a new type of layer derived from the graphic
layer that I called ‘sqlLayer’ and can have multiple instances. This class will
make it possible to add SQL tables that have a geometry element and that are
not managed by an ArcSde server. A REST service is responsible for doing the
CRUDQ actions. A typical example is the use of a Microsoft
SqlGeometry attribute in a table.
3.
Before and after AMD
illustrated
In the diagram illustration of the
different modules, you can see the
different components of a typical GIS application. With the help of KnockoutJS
the demo application gives us the possibility to make a separation between the
GUI and the business logic. I wanted also separate the GIS modules from the
Business modules. The best solutions is when you can also separate the business
components from the ArcGis JavaScript API.
First look at the situation before using
AMD.
<script type="text/javascript" src="http://serverapi.arcgisonline.com/jsapi/arcgis/?v=3.2" ></script>
<script type="text/javascript">/*------------------------------------*/
// REQUIRE
/*------------------------------------*/
dojo.require("dijit.dijit");
dojo.require("dijit.form.CheckBox");
dojo.require("dijit.layout.BorderContainer");
dojo.require("dijit.layout.ContentPane");
dojo.require("dijit.layout.StackContainer");
dojo.require("dijit.layout.StackController");
dojo.require("dijit.layout.TabContainer");
dojo.require("dijit.layout.AccordionContainer");
dojo.require("dijit.popup");
dojo.require("dijit._base.popup");
dojo.require("dijit.DropDownMenu");
dojo.require("dijit.MenuItem");
dojo.require("dijit.MenuBar");
dojo.require("dijit.PopupMenuBarItem");
dojo.require("dijit.Toolbar");
dojo.require("dijit.registry");
// Esri dijits
dojo.require("esri.arcgis.utils");
dojo.require("esri.dijit.InfoWindow");
dojo.require("esri.dijit.Legend");
dojo.require("esri.dijit.Measurement");
dojo.require("esri.dijit.Scalebar");
dojo.require("esri.geometry");
dojo.require("esri.map");
dojo.require("esri.dijit.editing.Editor-all");
dojo.require("esri.dijit.editing.TemplatePicker-all");
dojo.require("esri.dijit.AttributeInspector-all");
dojo.require("esri.layers.wms");
dojo.require("esri.layers.wmts");
dojo.require("esri.SnappingManager");
dojo.require("esri.symbol");
dojo.require("esri.renderer");
dojo.require("esri.utils");
dojo.require("esri.tasks.query");
dojo.require("esri.toolbars.navigation");
</script>
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.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="https://maps.googleapis.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="scripts/jquery.tmpl.js"></script>
<script type="text/javascript" src="scripts/jquery.livequery.js"></script>
<script type="text/javascript" src="scripts/jquery.xml2json.js"></script>
<script type="text/javascript" src="scripts/Modules/jqueryAgsLegendPlugIn.js"></script>
<script type="text/javascript" src="scripts/Modules/jqueryAgsSelectPlugIn.js"></script>
<script type="text/javascript" src="scripts/knockout-2.0.0.js"></script>
<script type="text/javascript" src="scripts/globalVars.js"></script>
<!-- Supporting JavaScript objects -->
<script type="text/javascript" src="scripts/Helper/eventAggregator.js"></script>
<script type="text/javascript" src="scripts/Helper/dataMapping.js"></script>
<script type="text/javascript" src="scripts/Helper/logUtil.js"></script>
<script type="text/javascript" src="scripts/Helper/genUtil.js"></script>
<!-- Gis JavaScript framework -->
<script type="text/javascript" src="scripts/Gis/gisOperation.js"></script>
<script type="text/javascript" src="scripts/Gis/gisCommonTasks.js"></script>
<script type="text/javascript" src="scripts/Gis/gisEditing.js"></script>
<script type="text/javascript" src="scripts/Gis/gisGeoProcessing.js"></script>
<script type="text/javascript" src="scripts/Gis/gisExtra.js"></script>
<!-- JavaScript GIS Modules -->
<script type="text/javascript" src="scripts/Modules/legendModule.js"></script>
<script type="text/javascript" src="scripts/Modules/layerSelectionModule.js"></script>
<script type="text/javascript" src="Scripts/Modules/commonToolbarModule.js"></script>
<script type="text/javascript" src="scripts/Modules/editToolbarGeneralModule.js"></script>
<script type="text/javascript" src="scripts/Modules/mapModule.js"></script>
<!-- JavaScript View models -->
<script type="text/javascript" src="scripts/ViewModels/mainViewModel.js"></script>
<script type="text/javascript" src="scripts/ViewModels/commonToolbarVM.js"></script>
<script type="text/javascript" src="scripts/ViewModels/editToolbarGeneralVM.js"></script>
<script type="text/javascript" src="scripts/ViewModels/layerSelectionVM.js"></script>
<!-- JavaScript bootstrapper -->
<script type="text/javascript" src="scripts/startup.js"></script>
<!-- END JAVASCRIPT -->
As
you can see, this looks horrible if you not try to group the JavaScript
files into more global files, but then you lose the modularity when you try to
replace one module by another version. The AMD is clearly a solution to this
issue. After transferring to AMD, the
same code now looks like this:
<script type="text/javascript">
var dojoConfig = {
async: true,
tlmSiblingOfDojo: false,
parseOnLoad: true,
packages: [
{ "name": 'jtSoftware', "location": "/Scripts/Gis" },
{ "name": 'helper', "location": "/Scripts/Helper" },
{ "name": 'viewModels', "location": "/Scripts/ViewModels" },
{ "name": 'modules', "location": "/Scripts/Modules" },
{ "name": 'common', "location": "/Scripts", main: "globalDefs" },
{ "name": 'google', "location": 'https://maps.googleapis.com/maps/api/js?
sensor=false'},
{ "name": 'jquery', "location": 'http://ajax.googleapis.com/ajax/libs/jquery/1.8.2', main: 'jquery.min' },
{ "name": 'jqueryui', "location": 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.0', main: 'jquery-ui.min' },
{ "name": 'jquerytmpl', "location": "/Scripts", main: 'jquery.tmpl'},
{ "name": 'jqueryxml', "location": "/Scripts", main: 'jquery.xml2json'},
{ "name": 'ko', "location": 'http://ajax.aspnetcdn.com/ajax/knockout', main: 'knockout-2.1.0' }]
};
</script>
<script type="text/javascript" src="http://serverapi.arcgisonline.com/jsapi/arcgis/?v=3.2"></script>
<!-- JavaScript bootstrapper -->
<script type="text/javascript" src="scripts/bootstrap.js"></script>
<!-- END JAVASCRIPT -->
The result is now a HTML page with little
JavaScript code embedded. This makes a more clear separation between code
behind (JavaScript embedded) and the business logic. Adapting the GUI for
different environments (Mobile, Pad, Desktop) is no longer requiring adapting
the different JavaScript definitions but can be done at module loading.
The loading of external libraries is
defined also with the packages option. Popular libraries as ‘jquery’ and
‘knockoutJS’ can make use of AMD and are
ether AMD compliant or are either support AMD loading as is the case of
‘jquery’. For jquery you must explicit define a property.define.amd.jQuery = true;
· Document ready
· Get configurations
· Create viewmodel(s
· Map module initialization
· Legend Module
· Layer Selection module
· Toolbar modules
During the loading of the modules, the
needed GIS modules are loaded through the AMD pattern. Another important aspect
is the asynchronous starting of the
initialization of the modules. To manage this asynchronous behavior the observer
pattern is needed in pour application. For this I use a custom class
‘EventAggregator’ explained in an earlier document
http://jpenet.blogspot.be/2012/03/arcgis-javascript-api-creation-of.html
The use of publish and subscribe can also
be used with the Dojo library, but for now I stick to this simpler solution. It is very important, at least in the
development stage, to add a lot of logging information for tracking the
asynchronous process. With AMD, you now have two dimensions into the
asynchronous process :
·
Asynchronous processing of GIS
operations
·
Asynchronous loading of the AMD
classes
In the first version of the JavaScript implementation I mostly used the closure pattern .The
refactoring of the modules for AMD (Singleton pattern) is not a hard job, there is already a separation of local
variables, only some renaming is required.
5.
Separation of concern
In the
flowchart of the modules, you see there are three main parts
·
GUI interface
·
Business logic
·
GIS modules
o
GIS Framework
o
ESRI ArcGis JavaScript API
With the use of MVVM through the library
‘knockoutJS’ I already separated the GUI interface from the business logic
implemented with the map module, legend module, select layer module and the
different viewmodel modules.
It is also useful to separate the Business
logic from the ESRI ArcGis JavaScript API.
In the current implementation this Is not the case, but it is not a hard
work to encapsulate all the direct use of ESRI components into the GIS
framework. Although it was not the purpose in this study to replace the ESRI
GIS solution by another GIS solution, by doing a clear separation of the
business logic from the ArcGIS JavaScript library you could create an
application based on another GIS implementation (OpenLayers , GeoServer) by
replacing the GIS framework modules.
For the moment there are a limited number of modules in the
GIS framework. A split into core functionality and separate functional modules
is a next step in implementing AMD. You can by example put all Google
functionality into one separate module. Editing could also be split into base
editing and more advance edit functionality.
6.
A first look at the AMD modules
To illustrate what the look is from the
singleton modules created for the AMD version, I will show what I did with the
map module. The creation of the map module is at the heart of the bootstrap
process. All modules depends on the creation of a map module.
Before we had this as
var mapModule = (function () {
Now the header this looks as:
define(["helper/logging", "helper/eventAggregator",
"jtSoftware/configuration", "jtSoftware/gisOperation", "jtSoftware/gisEditing", "modules/legendModule"],
function (logging, eventAggregator, configuration, gisOperation, gisEditing, legendModule) {
The header shows now the dependency of the
different classes. Through the parameters the Dojo library performs the dependency
injection.
The method functions defined in the map
module in the closure pattern is coded as
return {
init: function (divElement, mapLayersInitialized) {
try {
esri.config.defaults.map.slider = { left: "30px", top: "30px", width: null,
height: "200px" };
var mapControl = new esri.Map(divElement, {});
MapModule.prototype = {
init: function () {
try {
gisOperation.setSlider("30px", "30px", null, "200px");
gisOperation.setMap(commonData.MapRegion);
mapControl = gisOperation.getMap();
In the code above you can see how de
dependency of the ArcGis library in the earlier version was removed by
encapsulating the creation of the map object within the GIS framework. This is the job to be done for making the business layer independed of the GIS architecture choosen.
The
loading of the ESRI components will be kicked off by the GisOperation module of the GIS framework through AMD pattern.
In a next document I will illustrate more
of the practical aspects in detail to move towards AMD implementation.
Excellent! Very complete. I will also include layers for unit tests.
ReplyDeleteAngular 5 Resources
ReplyDelete| Angular 5 Training in Chennai.