I’ve made some changes to the showDialog function I discussed in this post. I’ve made it more generic – it now takes dialogId, href, title, queryString, and onDownloadend (a function). That way I can use it to open any dialog in my application. I’m also now not destroying the dialog if it already exists, I’m just resetting the onDownloadEnd and title properties and using my custom setTitle method:
//extend dojo dialog to have a setTitle method
dojo.extend(dijit.Dialog, {
setTitle: function(/*string*/title)
{
this.titleNode.innerHTML = title || "";
}
});
Finally, I’m using the href property instead of setHref which is deprecated. Here’s the new showDialog function:
showDialog: function(dialogId, href, title, onDownloadEnd)
{
//first check if it's there so we don't create a duplicate
var dialog = dijit.byId(dialogId);
if (dialog)
{ //it already exists, reset a few properties
dialog.setTitle(title);
dialog.onDownloadEnd = onDownloadEnd;
dialog.href = href;
}
else
{ //it doesn't exist yet, so create it
dialog = new dijit.Dialog({
refreshOnShow: true,
id: dialogId,
href: href,
title: title,
onDownloadEnd: onDownloadEnd,
onDownloadError: dojo.hitch(this, '_dialogDownloadError', dialogId)
});
}
//show it
dialog.show();
}
This is certainly not earth-shattering but it’s pretty cool. Asp.Net MVC HtmlHelper methods return a string of html and are used to render html elements. They are cool because you can use them to change the way the control is rendered at runtime based on what you’ve got in ViewData or your model. What’s even cooler is that you can pass html attributes (including custom ones like dojoType) to the HtmlAttributes parameter. This allows you to use HtmlHelper methods to render dijit widgets like so:
<%=Html.TextBox("search_StreetAddress",
Model.Criteria.StreetAddress,
new { dojoType = "dijit.form.TextBox", trim = "true" })%>
<%= Html.DropDownList("CityId", Model.CitySelectList,
new { dojoType = "dijit.form.FilteringSelect" })%>
<%= Html.TextBox("Year", Model.Year,
new { dojoType = "dijit.form.NumberTextBox",
constraints = "{min:1900, max:2500, pattern:'0000'}",
required = "true",
rangeMessage = "A 4 digit year is required",
invalidMessage = "A 4 digit year is required" })%>
Since there’s no date literal in javascript, there’s no standard way of serializing/ deserializing dates. MS Ajax uses \/Date(<ticks>)\/ where ticks is the number of milliseconds since midnight 01/01/1970. It’s a reasonable approach – unambiguous and easy to transform in both directions. For a while I was transforming this date object into a date before sticking it into a dojo DateTextBox, and transforming it back before sending it up to the server. But then I got smart and extended dijit.form.DateTextBox to handle this itself. Here’s the code:
dojo.provide('mjuniper.widgets.DateTextBox');
dojo.require('dijit.form.DateTextBox');
/*A dojo datetextbox that handles sending dates
to and recieving dates from asp.net*/
dojo.declare('mjuniper.widgets.DateTextBox',
dijit.form.DateTextBox,
{
// prevent parser from trying to convert to Date object
value: "",
postMixInProperties: function()
{
this.inherited(arguments);
//if it's null, return
if (!this.value)
{
return;
}
if (this.value.indexOf('/Date(') > -1)
{
//extract the ticks from the json object
var ticks =
this.value.substring(this.value.indexOf('(') + 1);
var endChar = (ticks.indexOf('-') === -1) ? ')' : '-';
ticks = ticks.substring(0, ticks.indexOf(endChar));
//instantiate a date
this.value = new Date(parseInt(ticks));
//if it's invalid, set value to null
if (this.value == 'Invalid Date') { this.value = null; }
}
},
/*override the serialize method to write
back to the server in proper format*/
serialize: function(dateObject, options)
{
return '\/Date(' + dateObject.getTime() + ')\/';
},
/*override setValue so we can set it with the
json date object we get from .net*/
setValue: function(dateString)
{
if (dateString.indexOf('/Date(') > -1)
{
//extract the ticks from the json object
var ticks =
dateString.substring(dateString.indexOf('(') + 1);
var endChar = (ticks.indexOf('-') === -1) ? ')' : '-';
ticks = ticks.substring(0, ticks.indexOf(endChar));
arguments[0] = new Date(parseInt(ticks))
this.inherited(arguments);
}
}
});
There are obviously a number of ways I could have handled extracting the ticks from the json string (including a regex, but then I would have had two problems). This one is not very elegant but it’s relatively easy to understand and I was able to get it working quickly.
I think I will call my series of posts dealing with dojo.query and NodeList part two of my dojo top ten and move on to part three.
I’ve come to rely heavily on dojo.hitch but at first I didn’t understand it at all. And I haven’t found many good explanations of it online (except this one) so I’m going to try my hand at a simple concise explanation of hitch.
Here we go: hitch returns a function in which ‘this’ will be whatever is specified as the first argument to hitch.
I’m now using it elsewhere but hitch is particularly useful with callback functions. So here’s an example:
dojo.xhrGet({
url: 'http://mywebservice.com/helloworld',
load: helloworldCallback
So we’re calling a webservice and specifying the callback function as helloworldCallback. This is all well and good but if you try to use ‘this’ in helloworldCallback, say to call other functions in the same module, it may not be what you expect it to be. Enter hitch:
dojo.xhrGet({
url: 'http://mywebservice.com/helloworld',
load: dojo.hitch(this, 'helloworldCallback')
By using hitch in the above example, we guarantee that when we hit the callback function, ‘this’ will refer to what we expect it to (in this case the module that contains both helloworldCallback and the function that does our webservice call).
So I guess this will be the finale to my series of posts on dojo.query. I’m using dojo.query to validate data entered into dojo dialogs. In this post, I discussed getting dialog content from an Asp.Net MVC controller method. In the onDownloadEnd function, I do any setup of the dialog that is necessary including something like this:
var onValueChanged =
dojo.hitch(this, this.dialogValueChanged, dialogId);
//gotta be keyup -
//validation is out of sync if you use keydown or keypress
dojo.query('input[type="text"], textarea', dialogId)
.connect('onkeyup', onValueChanged);
dojo.query('select', dialogId)
.connect('onchange', onValueChanged);
dojo.query('.dijit', dialogId).widgets()
.connect('onChange', onValueChanged);
This query’s for input elements in the dialog and connects each element’s appropriate method to the function where I do the validation (dialogValueChanged). Note the use of the widgets method discussed in this post.
Here’s the validation function:
dialogValueChanged: function(dialogId)
{
//check if everything is valid
var valid = dojo.query('[widgetid], [widgetId]', dialogId)
.widgets().every(function(widget)
{
if (widget.isValid)
{
return widget.isValid();
}
//if it is not a widget
//or does not have an isValid method, return true
return true;
});
//enable/ disable the save button based on whether it's valid
var action = (valid) ? 'removeAttr' : 'attr';
dojo.query('.dialogSaveBtn', dialogId)
[action]('disabled', 'disabled');
}
Here we’re querying for widgets and using our previously discussed widgets method to get the actual widgets (not just the dom nodes). Then we use the every method of NodeList to call the isValid method on each widget. The every method will return false if any of the widgets’ isValid methods returned false. Then we use the value returned from every to set our ‘action’ variable and use query again to enable or disable our save button(s) by adding or removing the disabled attribute. Note the use of the removeAttr method we added to NodeList in this post. Note also that this all depends on all the input elements on the dialog being dijit widgets. This mechanism can be easily extended to handle non-dijit elements too.