Practical ASP.NET
Handling Multiple Records in the Client
Peter moves on from working with one record to working with multiple records and explores Microsoft's current templating solution.
Two columns back (
Retrieving and Displaying a Single Object with dataView and WCF) I downloaded the data for a single Customer object from my Web service using only client-side code. In my last column (
Updating Data with the dataContext and WCF Data Services) I enabled updates for that single record, again using only client-side code. In this column, I'll retrieve and handle updates for multiple records: All the orders for a Customer (though I'll only work with two properties: OrderID and ShipCity)
The first step is to create a table with a body that can repeat table rows for each Order that's retrieved. There's nothing here that I haven't discussed in those previous columns:
<table>
<thead><tr><td>Order Id</td><td>Ship To</td></tr></thead>
<tbody id="ordData" class="sys-template">
<tr>
<td>{{OrderID}}</td>
<td>
<input id="orderDate" type="text" sys:value="{binding ShipCity}"/>
</td>
</tr>
</tbody>
</table>
Now I need to create a dataView and bind it to the table body, the dataContext, and the method that will supply me with Order objects (which, since I'm using a WCF Data Service, is just called "Orders"). Again, there's nothing here not discussed in the previous columns:
vOrds = Sys.create.dataView("#ordData",
{
dataProvider: dNwind,
fetchOperation: "Orders"
});
Finally, I need to specify which Order objects to retrieve by adding some code to the method I'm calling when the user selects a Customer from the drop down list. Again, this new code is very similar to the code I used to retrieve a single object:
var custId = ddl.options[ddl.selectedIndex].value;
var parms = { $filter: "CustomerID eq '" + custId + "'" };
vOrds.set_fetchParameters(parms);
vOrds.fetchData();
And that's it: I have a table displaying the orders for the customer selected in the drop down list. Now it's time to allow updates.
Adding Updates
Saving changes that the user makes to existing orders in the table is easy: As before, I just call the SaveChanges method on my dataContext object. The dataContext and dataView will transfer the new values out of the table and back to my WCF Web Service which will update my database. For inserts, I could try and add a blank row into my table for users to enter new order information. But it's a lot easier to add a second table of text boxes to the page right below my order table and let the user add new values there. So that's what I did.
Deletes are more complicated, however. For deletes, I need to know which Order the user wants to delete. To do that, I first set the onCommand property on my dataView to the name of a function. Whenever one of the predefined commands in the dataView is fired, this method will be called. Here, I've used a function I've called onHandleOrder:
vOrds = Sys.create.dataView("#ordData",
{
dataProvider: dNwind,
fetchOperation: "Orders",
onCommand: onHandleOrder
});
To get one of those commands raised, I need to add the sys:command attribute to an element in my Orders table. I added another column to my table and put a button in it. Adding the sys:command attribute, set to "Delete", to the button causes the button to raise the dataView's Delete command. I also add the sys:commandargument attribute to the button, which allows me to specify an argument to be passed to my function. For the commandargument, I'm using the predefined $index value, which returns the position of the data item bound to this row:
<tbody id="ordData" class="sys-template">
<tr>
<td>
<input type="button" sys:command="Delete"
sys:commandargument="{{$index}}" value="Delete"/>
</td>
<td>{{OrderID}}</td>
…rest of table…
My onHandleOrder function has to accept a single parameter (which I've called args). This args parameter is used to pass the command name and argument. If the command name is "Delete", I retrieve the position of the data item from the command argument. As I did when deleting a customer in my last column, I can use that to retrieve the data item from the dataView, use that item with the dataContext's removeEntity method to mark the item for deletion, and save my changes which will update the database:
function onHandleOrder(args)
{
switch (args.get_commandName())
{
case "Delete":
var idx = args.get_commandArgument();
var enty = vOrds.get_data()[idx];
dNwind.removeEntity(enty);
dNwind.saveChanges();
}
And I'm done
I've ignored a whole bunch of stuff in this project. Templating has special support for creating master/detail forms, for instance, which I may look at in a later column. As the syntax for my delete function suggests, I could have put all of my update code in that function. And I've ignored the other dataView commands ("Select" is very cool).
But it's time to return to what works right now in the versions of ASP.NET that most people are using. In my next column, I'm going to pause to reflect on the future of server-side code but then it's back to "practical" ASP.NET.
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/.