Showing posts with label dojo. Show all posts
Showing posts with label dojo. Show all posts

Thursday, September 17, 2015

Mock your Dojo AMD modules with StubModule.js

When testing AMD modules it is sometimes necessary to verify how it interacts with it's dependencies. For example, you might be writing a module that makes XHR requests using dojo/request and you want to make sure that it's passing the correct parameters. How would you test this? Creating a wrapper around the request method in your module and then spying on that method would work. You could also store the request method as a property of your module and spy on that in your tests. However, both of these solutions lead to messy code and there's something that feels wrong to me when adding code to production modules just for testing purposes.

You might think that it would be as easy as adding a map config to the Dojo loader and pointing dojo/request to a mocked module. While this is a possible solution it means that you have to create a separate file for each mock that you use and it gets very messy if you want to mock the same module multiple times within a single test page (since modules are cached by the loader).

StubModule.js provides a cleaner way to solve this problem. It allows you to stub modules with no dependencies on external files and no side effects to pollute your other tests. It does this by using the map config mentioned above as well as require.undef which is a Dojo-specific method that removes a module from the cache.

Using this tool is fairly straight forward. stub-module.js returns a single method that accepts two parameters. The first is the module identifier (MID) of the module that you want to test. The second is an object with keys that are MID's of the dependencies that you want to mock and values that are the mocked returned values. The method returns a promise that resolves with the stubbed module. For example (using Jasmine):

it('this is a demo', function (done) {
    var stub = jasmine.createSpy('request');
    stubModule('test/Module', {'dojo/request': stub}).then(function (StubbedModule) {
        var testObject = new StubbedModule();
        testObject.makeRequest();
 
        expect(stub).toHaveBeenCalledWith('some/url');
 
        done();
    });
});

To be honest I was surprised that I couldn't find an existing project that met my use case before I wrote this project. Did I miss something? Also, I wonder if the API of this project could be simplified. Any suggestions?

Monday, June 6, 2011

Using The Dojo Build System To Speed Up Your ESRI JavaScript API Apps

[Updated 4-15-2013]
I've come up with a solution for ESRI JSAPI 3.4 and AMD.


As your JavaScript projects get more and more complex, loading all of those Dojo classes can really slow down your load time. All those dojo.require calls add up in a hurry. The Dojo Build System can be a huge help in speeding up the load time and general performance of your apps. For example, a build that I ran on a recent project took the number of JavaScript requests on page load from 53 down to 5. The css request went from 16 down to 4. This ended up cutting the load time in half! Other nice features include stripping out all of the console calls, minifying your JavaScript and interning all of your widget templates.

The rest of this post assumes some familiarity with the Dojo Build System. If you haven't looked at it before, the documentation is worth reading. There's even a fancy new tutorial.

After reading all of the Dojo documentation it's easy to get excited about the possibilities. However, you will quickly find that mixing the ESRI api into the equation makes a big mess of everything. For example, the dojo build system assumes that you are hosting everything yourself. But because ESRI has not released a source/unbuilt version of their api that we can download we are stuck loading Dojo from their servers. The other problem is that when you load the ESRI api you are really loading their layer file which can have a lot of overlap with your layer file thus adding a lot of duplicate code. Not to mention the problems that the build system has when it sees: dojo.require("esri..."); and it doesn't know where to get it. Over the last few months I've developed a solution to overcome these problems and end up with a lean and mean (for the most part) product in the end.

Monday, October 11, 2010

Great Blog Post on Custom Dojo Dijits

In his new blog "Enterprise Dojo", Dan Lee has an outstanding article titled, "Introduction to Custom Dojo Widgets". It gives a great first taste of developing your own custom Dojo Dijits. I was really excited to learn about the slick way to reference DOM nodes within your dijit using dojoattachpoint. Take 10 minutes and give it a read!

Vehicle Tracking With The JavaScript API

Recently, we have been investigating alternatives to our current AVL solution. The problems with our current solution are licensing costs (big $$ per workstation) and ease of use. I thought that I'd give it a try with ESRI's JavaScript API and this is what I came up with.


The vehicles are graphics that are refreshed from the server every 5 seconds using the JavaScript setTimeout() function. I was surprised at how quickly the browser could clear the graphics and reload them from the server. It looks like they are animated instead of reloaded. Here's a sample of the code that I used:

function AddVehicleGraphics(){ SANDY.qt.execute(SANDY.q, function(fSet){ function sortGraphics(a,b){ return (a.attributes.fleetID < b.attributes.fleetID) ? -1 : 1; } // record bread crumbs, if any dojo.forEach(SANDY.crumbsVehicles, function(key){ var crumbGraphic = dojo.filter(SANDY.avlGraphicsLayer.graphics, function(item){ return item.attributes.mapKey == key; })[0]; SANDY.avlGraphicsLayer.remove(crumbGraphic); // required to allow appropriate refresh on history graphics layer crumbGraphic.setInfoTemplate(SANDY.crumbsTipTemplate); crumbGraphic.setSymbol(SANDY.crumbSymbol); // get associate graphics layer var gLayer = SANDY.mapDij.map.getLayer(key); gLayer.add(crumbGraphic); }); // clear graphics layer SANDY.avlGraphicsLayer.clear(); SANDY.chartsDij.clearContent(); // sort the graphics alphabetically so they are sorted when adding to active vehicles table. fSet.features.sort(sortGraphics); dojo.forEach(fSet.features, function(g){ var symbol; switch (g.attributes.defaultIcon) { case "PLOW TRUCK": symbol = dojo.clone(SANDY.truckSymbol); break; case "SWEEPER": symbol = dojo.clone(SANDY.sweeperSymbol); break; case "SUV": symbol = dojo.clone(SANDY.puSymbol); break; } // rotate symbol symbol.setAngle(g.attributes.headingDegrees); g.setSymbol(symbol); // round mph g.attributes.speedMPH = Math.round(g.attributes.speedMPH); // add to graphics layer SANDY.avlGraphicsLayer.add(g); // add to vehicles list / bold all vehicles that have updated in the last 30 minutes var content; var activeCutOff = new Date().setMinutes(new Date().getMinutes() - 30); if (new Date(g.attributes.timeAtCoordinate) > activeCutOff){ content = "<b>" + g.attributes.fleetID + "</b>"; } else { content = g.attributes.fleetID; } SANDY.chartsDij._addContent(content); // update date updated text var now = new Date(); dojo.byId("update-date").innerHTML = now.toLocaleTimeString(); dojo.style("update-date-background", "backgroundColor", "white"); }); if (fSet.features.length === 0){ SANDY.chartsDij._addContent(SANDY.noVehiclesMsg); } }, function(error){ // change background to indicate error dojo.style("update-date-background", "backgroundColor", "red"); }); }


The other cool thing that I was able to do in this project was link to live UDOT traffic cameras. After getting permission from them, I was able to determine the unique URL for each of them from the CommuterLink website and build a feature class with their locations and urls. It was easy after that to publish them as a map service and load them as graphics onto my map. I used a custom infoTip window from ESRI's code gallery and some mouse events to show the camera images.




// camera graphics dojo.connect(SANDY.mapDij.map.graphics, "onMouseOver", function(evt){ // store old symbol to save for mouse out function SANDY.origSymbol = evt.graphic.symbol; // get orig color rgb's var r = SANDY.origSymbol.color.r; var g = SANDY.origSymbol.color.g; var b = SANDY.origSymbol.color.b; // highlight opacity var a = 0.5; // change symbol to highlight symbol var biggerSize = evt.graphic.symbol.size + 4; evt.graphic.setSymbol(dojo.clone(evt.graphic.symbol).setSize(biggerSize).setColor([r, g, b, a])); SANDY.iTip.setContent(evt.graphic.getContent()); SANDY.iTip.show(evt.screenPoint, SANDY.mapDij.map.height, SANDY.mapDij.map.width); }); dojo.connect(SANDY.mapDij.map.graphics, "onMouseOut", function(evt){ // change symbol back evt.graphic.setSymbol(SANDY.origSymbol); SANDY.iTip.hide(); }); dojo.connect(SANDY.mapDij.map.graphics, "onClick", function(evt){ // hide infowindow SANDY.mapDij.map.infoWindow.hide(); });


The next step after we upgrade to ArcGIS Server 10 is to use the new TimeSlider dijit to enable users to view historical data. Sadly, because I'm changing jobs, I will not be around to implement this cool functionality.  :(

Any other suggestions? Anyone else using the JavaScript API for vehicle tracking? I'd love to hear your experiences.

Thursday, August 12, 2010

Local GIS: Utah County Parcel Map

The Utah County Parcel Map JavaScript application is a great, local example of the ESRI JavaScript API. You can easily recognize the use of Dojo in the layout and controls in the pane on the left. It's a simple and clean application that's easy to use.

I also like the use of the Local Government Infrastructure Base Map Template. I think that I'm going to take a look at this template to see if it can help my ugly base map service.

Dave H. and Darin S. from Utah County GIS are raising the bar!

Monday, July 26, 2010

Dojo Dijits: Object Oriented JavaScript Goodness

Object Oriented Programming is a programming style that views the world as a set of objects with properties and methods. When programming with JavaScript, it's easy to fall into a scripting mindset and fire off code in one giant method. This makes your code hard to read and debug. It can also lead to copying/pasting and redundant code. The first step to breaking this habit is to refactor as much as possible. However, this can only take you so far. The next step is breaking out sets of related methods into separate objects. Fortunately for those of us that work with the ArcGIS API for JavaScript, we have access to the wonderful world of Dojo Dijits.

If you've worked with the JS API before you have probably already used a Dojo Dijit. These are prepackaged code and markup that you can plug right into your applications. Dijit.form.Button and Dijit.form.Slider are very common examples. These are great, but the real fun comes in creating your own.

This article by Matthew Russell was my first introduction to creating your own custom Dijits (look for "Creating You Own Widgets"). It's not as complicated as you would think. The building blocks consist of three files (links to my files are included):
  • An .html file containing the markup code (hit view source to see the code).
  • A .js file containing all of the JavaScript.
  • A .css file containing all of your styling.
    After doing the work to create your own Dijit, it's as easy to use as any regular Dojo Dijit. I've created a full screen map dijit that I use for several of my applications. It includes goodies such as map resizing, an animated navigation toolbar, basemap layers and error wiring. It's great to spend time only once refining my design and code and then easily reusing it. And when I make improvements, all of my applications are automatically updated.

    Here's what it looks like in the current app that I'm currently working on. You'll see references to the map dijit and a tools digit.
    HTML:
    <body class="nihilo"> <div id="mapDijit" dojoType="mapDijits.mapDijit_FullScreen"></div> <div id="toolsDijit" dojoType="mapDijits.toolsDijit"></div>


    JavaScript:
    <script type="text/javascript"> //require newly created dijit dojo.require("mapDijits.mapDijit_FullScreen"); dojo.require("mapDijits.toolsDijit"); dojo.require("dojo.parser"); // global var g = {}; g.baseURL = "http://gis.sandy.utah.gov/ArcGIS/rest/services/"; g.PermitsURL = g.baseURL + "Public_Works/Road_Cut_Permits/MapServer"; function init(){ // get map dijit and tools dijit var mapDij = dijit.byId("mapDijit"); var toolsDij = dijit.byId("toolsDijit"); // set map dijit for tools dijit toolsDij.mapDijit = mapDij; toolsDij.setup(); // add permits layer mapDij.addLayer(g.PermitsURL, cached=false, wireErrors=true); // add buttons toolsDij.addButton(label="Add Line", iconClass="drawLineIcon", method=drawLine); toolsDij.addButton(label="Add Point", iconClass="drawPointIcon", method=drawPoint); }


    You can see it in action in my Core Samples and Pavement Markings live applications. Anyone else using custom Dijits out there?