More dojo array processing goodness

I use dojo.forEach everywhere. One thing that I occasionally need that dojo.forEach can’t do (as far as I know) is break out of the loop early. Thanks to Eugene Lazutkin I realized that I can just use dojo.some or dojo.every to break early. By the way, you should read that whole article - there’s lots of good stuff there.

Let’s say that you’ve got an array of objects and you need to know the index of one of the objects in the array based on a property value. Consider these two functions that will both accomplish this:

GetArrayItemIndex: function(ary, item, fieldName)
{
    var i = -1, result;
    dojo.forEach(ary, function(aryItem)
    {
        i++;
        if (aryItem[fieldName] === item[fieldName]) { result = i; }
    });

    return result;
}

GetArrayItemIndex2: function(ary, item, fieldName)
{
    //using some instead of forEach to break early
    var i = -1;
    dojo.some(ary, function(aryItem)
    {
        i++;
        return (aryItem[fieldName] === item[fieldName]);
  });

      return i;
}

GetArrayItemIndex uses dojo.forEach so it will go through every item in the array even if the item we’re looking for is at index 0. GetArrayItemIndex2 uses dojo.some to short-circuit the loop when we locate the item we’re looking for. I’ve been wondering about this for a while - I’m glad I finally googled it today and discovered Eugene’s post.

Fun with dojo.io.iframe.send

One of my projects has had functionality to export a map image for some time. To get the map image, I just call (javascript) window.open and point the url to my http handler that will return the map image (with additional querystring parameters that specify the map parameters). This was working fine but now the customer wants to include lots of other markup on the map, including stuff they’ve drawn on the map with the nifty drawing tools I’ve provided them. This presented a bit of a problem because if I continued to just use window.open, the querystring was gonna get really long. So I realized I’d have to post the map parameters to my handler. But as far as I know, there’s no way to post data when you call window.open. So what to do?

The first thing I thought of was that I could post the parameters using dojo.xhrPost and then prepare the image and return the url to it. Then I’d call window.open with the url returned from the first request. I didn’t really like that. For one thing, it would be two requests, but for another, I’d have to keep that map image around in some manner.

So I decided to use dojo.io.iframe.send. This ended up working very well but I did run into a few issues that I thought I’d share.

One thing that I didn’t realize at first is that dojo expects the response to be an html document. And if you want to return anything other than an html document (json or text for example), you must wrap it in an html textarea. Dojo will look inside the first textarea in the response document and return to your handle, load, or error functions whatever it finds there in the format you specified in handleAs (defaulting to text if you did not specify one).

Another thing I ran into was that my dojo.io.iframe.send call would only work once per page load. I’m not exactly sure why that is. I found a suggestion that you set a timeout on the __IoArgs object passed to iframe.send. That worked in the sense that I could call it again after the timeout expired but I have no idea how long this operation is going to take so it’s hard to know what to set the timeout to. My solution was to keep a reference to the dojo.Deferred object returned by iframe.send and call cancel on it before trying to send another request like so:

if (this.getMapImageDeferred)
{
  this.getMapImageDeferred.cancel();
}


This seems to work fine but I do have to check in my errBack function whether the request was canceled:

_mapImageCallbackError: function(response, ioArgs)
{
  if (response.message === 'Deferred Cancelled')
  {
    return response;
  }
    // handle the error...
},


The last thing I ran into really had me stumped. I was setting method = “post” in the __IoArgs object passed to iframe.send but it was always using get. I read a few things here and there that kind of seemed to suggest that if I passed a form with its method set to ‘post’ into __IoArgs that might do the trick. And it did! So here’s what I’m doing now:

//create a form with method=post
var form = document.createElement('form');
dojo.attr(form, 'method', 'post');
document.body.appendChild(form);

//create my postdata
var content = {
  /*set a bunch of parameters here that
  will be posted to the http handler*/
};

//make the request
this.getMapImageDeferred = dojo.io.iframe.send({
  url: path + 'map.mapimage.aspx',
  form: form,
  content: content,
  error: dojo.hitch(this, this._mapImageCallbackError)
});

//get rid of the form created above
document.body.removeChild(form);


I’d be interested to know if anyone else has run into these issues and how you resolved them.

Re-throwing exceptions in .Net

I knew that I read somewhere that there was a very important difference between throw; and throw ex; when you catch an exception and want to re throw it. But I couldn’t remember what the difference was. Thanks to this post I now know. If you use throw ex, the stack trace info will be overriden. So it is almost always preferable to use just throw.

Extending dojo query part 2

One of the cool things about the dojo NodeList is that most (all?) of the methods of NodeList return the list so you can chain NodeList operations ala jQuery. I realized that I had overlooked this with my removeAttr method when I wrote this:

dojo.query('#okConfirmBtn', dialogId).removeAttr('disabled').
  connect('onclick', clickFunc);



This would fail because removeAttr doesn’t return anything. It’s a simple change though, here’s the updated code:

//extend nodelist to have a removeAttr function
dojo.extend(dojo.NodeList, {
  removeAttr: function(attribute){
    return this.forEach(function(item) {
      dojo.removeAttr(item, attribute);
    });
  }
});


I also created another extension to the NodeList, a widgets method. This filters the NodeList to only nodes that are widgets and returns a NodeList of the widgets (not the dom nodes, the actual widgets).

/*extend nodelist to have a widgets function that
filters the list and returns a nodelist of dijit widgets*/
dojo.extend(dojo.NodeList, {
  widgets: function(){
    return this.filter(function(item)
    {
      var widget = dijit.byNode(item);
      if (widget) { return true; }
    }).map(function(widget) { return dijit.byNode(widget); });
  }
});

Extending dojo.query

I’ve been making more and more use of dojo.query. In case you’re not familiar with it, it basically allows you to query the dom using css syntax. It returns a NodeList which is an array of dom nodes with some extra dojo goodness built in. Like many (all?) of the dojo array processing functions, some of which I discussed here. NodeList also has some handy methods like addClass, removeClass, toggleClass, style, addContent, and a few others. NodeList.attr will set an attribute on every node in the nodelist. Very handy, but there’s no NodeList.removeAttr, I’m not sure why. I needed this functionality in a bunch of places. At first I was just using NodeList.forEach, but then I realized it would be better to just extend NodeList like so:

dojo.extend(dojo.NodeList, {
  removeAttr: function(attribute){
    this.forEach(function(x) { dojo.removeAttr(x, attribute); });
  }
});



That little bit of code adds the removeAttr function to NodeList! Now I can just do this:

dojo.query('foo').removeAttr('bar');



I’m doing something else with this that I think is interesting. I’m using alternative syntax (that probably has a name but I don’t know what it is) to reduce the amount of code I’m writing. For instance, instead of writing this:

if (valid)
{
  dojo.query('.detailSaveButton').removeAttr('disabled');
}
else
{
  dojo.query('.detailSaveButton').attr('disabled', 'disabled');
}



I’m doing this:

var action = (valid) ? 'removeAttr' : 'attr';
dojo.query('.detailSaveButton')[action](’disabled’, ‘disabled’);



Note that the second argument being passed in will be ignored if action == ‘removeAttr’.

Asp.Net MVC + dojo dialog

I’ve bee working a ton with dojo and we recently started doing several projects using Asp.Net MVC.

I’ve made fairly extensive use of dojo dialogs (dijit.Dialog) and thus far I’ve usually just declared the dialog in markup because it was easy and most of the samples do it that way. But I was never totally satisfied with that. But last week I realized something cool: you can create a dojo dialog in javascript and set its href property to an Asp.Net mvc controller method that returns a view user control! This is cool for lots of reasons: you can separate the dialog markup from the rest of it, you only create the dialog if you actually need it, and best of all, the view user control can be used to conditionally render the dialog (enable/ disable controls, show different controls, etc) based on what you’ve got in viewdata, your model, or session state.

So I realized this should work in theory but I expected to have to fiddle with it to get it to actually work. Nope, it just works.

Some sample code:

Controller method:

public ActionResult GetDialog()
{
    var model = new RoleDialogModel();

    model.foo = this.foo;

    return View("RoleDialog", model);
}

Pretty straightforward, just create the model, set some properties and return the view, passing it the model. I won’t show you the view - just create a view user control and stick some markup in it.

Javascript:

showDialog: function(title, action, evt)
{
    //first check if it's there so we don't create a duplicate
    var dialog = dijit.byId('fooDialog');
    if (dialog) { dialog.destroyRecursive(); }

    dialog = new dijit.Dialog({
        refreshOnShow: true,
        id: 'fooDialog',
        title: title,
        onDownloadEnd: dojo.hitch(this, this.initializeDialog, action, evt)
    });

    dialog.setHref(this.dialogUrl);

    dialog.show();
}

There are a couple of things to notice here. First, I check if I’ve already created the dialog, dojo will puke if you try to create two objects with the same id. If it’s there I destroy it. I was previously just resetting its properties but the title wouldn’t change. I know I saw a post somewhere about how to do that but for now, I’m just destroying it and re-creating it. I set onDownloadEnd to an initialization function which hooks up ui events, etc. You should do any dijit widget stuff here, to be sure the widgets are there when you go looking for them. The setHref function sets the href of the dialog to the controller method above. I realized after I got this working that setHref is deprecated and you should use the href property instead but it was working so I didn’t want to mess with it.

I think this is a great way to handle dojo dialogs in an Asp.Net MVC app. I plan to continue using this approach and refine it going forward.

Accessing ASP.Net Web Services With Dojo (part 2)

It looks like Dave ran into an issue similar to the one I discussed in my previous post on this subject. Only he solved the problem. I should have been using dojo.rawXhrPost.

My dojo top ten (part 1 array processing)

I’ve been using the dojo toolkit a lot lately and I’ve come to really like it. Except for the documentation. The Book of Dojo is pretty good (though not comprehensive enough) but the api documentation sucks. There are a few resources on the web that are helpful but we’ve been relying heavily on two books: Dojo the Definitive Guide and Mastering Dojo.

So this is the first in a series of posts on my top ten dojo functions.

Dojo’s array processing functions basically make Javascript 1.6 array functions available in all browsers. These are extremely powerful functions that let you filter, transform, and query arrays.

I’m going to skip indexOf, lastIndexOf, some, every, and forEach (which are great, go look them up) and focus on map, and filter.

I’ve used dojo.map alot lately to transform the elements in an array. A somewhat contrived example: You get some json data back from a web service that is an array of objects. You’ve got to pass that array to another function but it expects each element of the array to have a name property instead of the title property they’ve got. Just call

var newAry =
  dojo.map(ary, function(x){ x.name = x.title; return x; });



and voila, you’ve got a new array, and each element now has a name property that is the same as the title property.

I’ve also been using dojo.filter to filter arrays. Say you’ve got an array of objects and you want to work with a subset of them. You can use dojo.filter to filter the array. Just pass it the array and a function that evaluates each element and returns true if it should be included in the filtered array and false if not. Using the above example, lets say we want to return all elements in the array whose title starts with ‘A’. Just do this:

var newAry =
  dojo.filter(ary, function(x){ return x.title.indexOf('A') == 0; });



As I said, these are extremely powerful functions and I’ve only scratched the surface in this post.

My first Virtual Earth post

So I’ve got a page with a virtual earth map into which I load a bunch of shapes. When the user mouses over a shape, I wanted the fill color to change and the infobox to popup. My initial plan was to use onmouseover and onmouseout but I found that when you mouse over the infobox, the onmouseout event fires. So instead of using the onmouseover and onmouseout events, I handled everything in onmousemove and keep track of my ‘current’ shape (_currentWatershed). I also check to see whether the shape the mouse is over is the same as the _currentWatershed and if so, I don’t do anything.

function mouseMoveHandler(e)
{
    if (e.elementID == null)
    {   //if we moused off all features
        //and there was a _currentWatershed set
        if (_currentWatershed != null)
        {
            _currentWatershed.SetFillColor(_watershedsFillColor);
            _map.HideInfoBox(_currentWatershed);
            _currentWatershed = null;
        }
    }
    else
    {
        var shape = _map.GetShapeByID(e.elementID);
        if (shape != null &&
            shape.GetShapeLayer() == _watershedsLayer &&
                shape != _currentWatershed)
        {
            if (_currentWatershed != null)
                _currentWatershed.SetFillColor(_watershedsFillColor);

            _currentWatershed = shape;
            _currentWatershed.SetFillColor(_watershedsHoverFillColor);
            _map.HideInfoBox();

            //shows the infobox at the mouse position when this event fires
            _map.ShowInfoBox(_currentWatershed, new VEPixel(e.mapX, e.mapY));
        }
    }
}

Accessing ASP.NET Web Services with Dojo

A while back, Dave wrote a post on this subject. Last week, I decided to pick up where he left off.

I’ve got some web services that take parameters so I decided to try to figure out how to pass the parameters up using dojo.xhrGet. You’ll have to check out Dave’s post for the fundamentals but here’s what I did. I created a criteria object with the parameters that I needed to pass to the webservice (the vals object was returned by a call to my dojo dialog’s getValues method):

var criteria = { “projectName”:’”‘ + vals.projectName + ‘”‘, “bufferDistance”:vals.bufferDistance };

then I added the criteria to my dojo.xhrGet (note the ‘content: criteria’ part):

dojo.xhrGet({
    url: 'Services/FuelsSearchService.asmx/GetByProject',
    handleAs: 'json',
    contentType: "application/json; charset=utf-8",
    content: criteria,
    load: handleFuelsResults,
    error: callbackError,
    timeout: 10000
});

And it works!

Well, not right away. See those funky extra quotes around the projectName value? Well it wouldn’t work until I added those. Even though you and I know that vals.projectName is a string, dojo knows it’s a string, and javascript knows it’s a string, Microsoft’s javascript deserialization stuff apparently doesn’t know that. I kept getting “Error: bad http response code:500″. But when I actually looked at the response in Firebug, it said, “Invalid JSON primitive: Cities.” Cities is the value of vals.projectName. So I added the extra quotes and voila, it works. But it’s ugly. Anybody know another way?