Practical ASP.NET
Supporting Updates with jQuery Data Linking
In my last column (
Displaying and Filtering Data with jQuery Templates) I created a page that retrieved data from a Web service using a combination of jQuery (with its new templating feature), standard ASP.NET technology and JavaScript. This column leverages jQuery's new data linking technology to support updates. Data linking allows you to link a property on an object to a field on a form. The databinding is two way: If you update the field then the property is updated; if you update the property (using the data plugin) the change appears in the form.
I first need to add the data link library to my project. Unlike the templating library I used in the previous columns, I couldn't find a reference to the library in the Microsoft CDN (though I didn't spend a lot of time looking). So I downloaded the code from where the jQuery project keeps it on the github site, stuffed it into a file called data-link.js in the Scripts folder in my project, and added a reference to the library to the ScriptManager:
<asp:scriptmanager runat="server" ID="Scriptmanager1">
<Scripts>
<asp:ScriptReference Path=
"http://AJAX.microsoft.com/AJAX/jquery/jquery-1.4.4.min.js" />
<asp:ScriptReference Path=
"http://AJAX.microsoft.com/AJAX/jquery.templates/
beta1/jquery.tmpl.min.js" />
<asp:ScriptReference Path="~/Scripts/data-link.js" />
<asp:ScriptReference Path="~/Scripts/DisplayOrders.js" />
</Scripts>
<Services>
<asp:ServiceReference Path="~/NorthwindData.svc" />
</Services>
</asp:scriptmanager>
I then extend the template I created in my last column that displays the Order object's RequiredDate in a textbox. I now assign the textbox a unique id that consists of the text RDate plus the OrderId for the current row. The resulting line in the template looks like this:
<td><input type="text" id="RDate{{= OrderId}}"
value="{{= RequiredDate}}"/></td>
As shown in my last column, the Order Data Transfer Objects that I retrieved from my Web service are kept in an array called ords. Now I need to go through the array, using the new data linking feature to tie the RequiredDate property for each object to the appropriate RDate + OrderId textbox.
To link an object to a field, you retrieve a reference to the page's form and call the link plugin. You pass to the link plugin the object whose property is to be bound, the name of the property to be bound, and the id of the control in the template to be bound.
This example, for instance, loops through all the Order objects in my ords collection. For each object, I use the link plugin to bind its RequiredDate property. The RequiredDate property is bound to the control that has its id attribute set to "RDate" plus the Order's OrderId:
for (pos in ords) {
$("form").link(ords[pos], {
RequiredDate: {
name: "RDate" + ords[pos].OrderId
}
});
}
With that done, as the user makes changes to an RDate textbox, the RequiredDate property on the corresponding object is automatically updated. There are simpler syntaxes for the link plugin than I've shown here. However, this syntax lets me insert conversion functions between the object and the control. This example specifies a function (named CheckDate) to be executed when data is passed back from the control to the object:
$("form").link(ords[pos], {
RequiredDate: {
name: "RDate" + ords[pos].OrderId,
convertBack: CheckDate
}
});
Inserting the CheckDate function allows me to check the user's input before updating the object. If I find a problem, I don't return a value and notify the user that there's a problem; if there's no problem, I return the value that I've been passed (and data binding will take of updating the object with the value):
function CheckDate(DateIn) {
var dformat = /^\d{2}\/\d{2}\/\d{4}$/;
if (!dformat.test(DateIn)){
alert("Bad date");
}
else {
var mth = DateIn.split("/")[0];
var dy = DateIn.split("/")[1];
var yr = DateIn.split("/")[2];
var d = new Date(yr, mth - 1, dy);
if ((d.getMonth() + 1 != mth) ||
(d.getDate() != dy) ||
(d.getFullYear() != yr)) {
alert("Bad Date");
}
else {
return DateIn;
}
}
}
My last step is to return that array of orders to a Web service that will update the database. I add a button to my page to call a method on my Web service and pass my collection of Order objects to it. Since I set the Web service up in ScriptManager, this is all the code that I need:
function UpdateOrders() {
var nwData = new PHVIS.NorthwindData();
nwData.UpdateOrders(ords, OrdersUpdated, FailOrders);
}
On the Web service, my UpdateOrders method accepts the collection of DTOs passed from that code, finds the corresponding objects in the database and updates the objects' properties. Once I've processed all the objects, I use SaveChanges to push the updates to the database:
<OperationContract()> _
Public Function UpdateOrders( _
ByVal ords() As dtoOrder) As Boolean
Dim nw As New northwndEntities
Dim dtoOrd As dtoOrder
For Each ord As dtoOrder In ords
dtoOrd = ord
Dim ordr = (From o As Orders In nw.Orders _
Where (o.OrderID = dtoOrd.OrderId) _
Select o).First
ordr.RequiredDate = ord.RequiredDate
Next
nw.SaveChanges()
Return True
End Function
At this point, I've got a page that displays and updates multiple objects. In my next column I'm going to add support for deletes using another feature of the jQuery templating extensions.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.