LA.NET [EN]

Oct 26

In one of the previous posts of the series, we’ve seen how easy it is to build a master-detail scenario with the help of the new DataView control. Today, we’ll keep looking at it and we’ll see how we can build an editable grid. The next two images explain the objective of our post. The first shows the grid in browse mode while the second illustrates what it looks like in edit mode:

browsemode

editmode

As you can see, the idea is simple: any row can be in editing or browsing mode (notice that we can only edit one row at the time). While in browsing mode, you can move it to edit mode or you can simply delete it. In edit mode, you can only confirm the changes or cancel them.

If you’ve been playing with the DataView control, then you’ll know that you’ll need two templates: one used for displaying the row in browse mode and another to display it in edit mode. Here’s the HTML I’ve used for the control and for each of the templates:

<body xmlns:sys="javascript:Sys"
      xmlns:dv="javascript:Sys.UI.DataView">
    <div id="view"
          sys:attach="dv"
          dv:data="{{items}}"
          dv:onitemrendering="{{handleItemRendering}}"
          dv:itemtemplate="#browse"
          dv:oncommand="{{handleCommand}}">
    </div>
    <div id="browse"
          class="sys-template">
        <div>
          <span>{{name}}</span> -
          <span>{{address}}</span>
          <a sys:command="edit" href="" 
sys:commandargument="{{$index}}"
onclick="return false;">Edit</a> <a sys:command="delete" href=""
sys:commandargument="{{$index}}"
onclick="return false;">Delete</a> </div> </div> <div id="edit" class="sys-template"> <div> <input type="text" id="name"
sys:value="{binding name, mode = oneWay}"/> <input type="text" id="address"
sys:value="{binding address, mode = oneWay}"/> <a sys:command="update"
sys:commandargument="{{$index}}" href=""
onclick="return false">Update</a> <a sys:command="cancel" href=""
onclick="return false">Cancel</a> </div> </div> </body>

In this case, I’ve opted for using two external templates: the first one uses spans for showing the name and address of each object + the anchors needed for raising edit and delete commands. The second one has some textboxes plus update and cancel “buttons”. By default, the DataView control uses the browsing template (notice the dv:itemtemplate=”#browse” attribute usage).

As you can see, we’re using commands for signaling the operations. We’re also relying on command arguments for getting the current position of the element that raises a command (the only command which doesn’t require knowing the position is the cancel command).

One thing that might have caught your attention is the use of one-way bindings between the INPUT controls and the objects. Unfortunately,that’s the only way to guarantee that the changes made in the textboxes won’t be propagated automatically to the associated objects (in other words,two way bindings won’t help here because they will automatically propagate all the changes to the associated objects and that means the cancel button wouldn’t really work).

Without further ado, lets take a look at the JavaScript:

<script type="text/javascript">
  var items = [
    { name: "luis", address: "fx" },
    { name: "john", address: "lx" },
    { name: "rita", address: "pt" }
  ];
  Sys.require([Sys.components.dataView],
  function () {
Sys.Observer.makeObservable(items); Sys.UI.DataView.prototype.editElement =
f
unction (pos, addingNewItem) { this.__editIndex = pos; this._isNewItem = addingNewItem; } Sys.UI.DataView.prototype.cancelEditElement =
fun
ction () { this.__editIndex = -1; this.__isNewItem = false; } }); function handleCommand(sender, e) { function delItem(sender, e) { items.removeAt(e.get_commandArgument()); } function updateItem(sender, e) { var name = sender.get_contexts()[ e.get_commandArgument()] .get("#name") .value; var address = sender.get_contexts()[ e.get_commandArgument()] .get("#address") .value; //probably do some validation here var item = items[e.get_commandArgument()]; item.name = name; item.address = address;
sender.cancelEditElement(); sender.refresh(); } function editItem(sender, e) { sender.editElement(e.get_commandArgument(), false); sender.refresh(); } function cancelItem(sender, e) { sender.cancelEditElement(); sender.refresh(); } switch (e.get_commandName()) { case "delete": delItem(sender, e); break; case "update": updateItem(sender, e); break; case "cancel": cancelItem(sender, e); break; case "edit": editItem(sender, e); break; default: return; } } function handleItemRendering(sender, e) { function find(item, arr) { for (var i = 0; i < arr.length; i++) { if (arr[i] === item) { return i; } } return -1; } if (sender.__editIndex === undefined || sender.__editIndex === -1 || find(e.get_dataItem(), items) !==
sen
der.__editIndex)
{
return; } e.set_itemTemplate("#edit"); } </script>

 

There are a couple of interesting things going on here:

  • we start by adding two new helper methods to the DataView’s prototype object: editElement and cancelEditElement. The first is used for setting the __editIndex property with the index of the row that should be placed into edit mode; the second method cancels an ongoing editing operation (by setting that property to –1);
  • most of the work happens in the handleCommand function. As  you can see, we’ve got a separated inner function for each command. This was not needed, but I guess it improves reading quite a bit;
  • the edit anchor doesn’t really do much: it will simply set the current edit index to the value it receives from the command argument;
  • cancelling is even simpler since it simply resets the current edit index;
  • removing an item is also simple because we’ve made items an observable array (and that means easy elimination and propagation of that info to the DataView control);
  • updating is the most complicated operation. We start by getting the values of the textboxes (by relying on the get helper method which is available from the current TemplateContext object). After that, we should probably run some sort of validation to ensure that the introduced values are correct. We’ve dismissed that step because this is only demo code. Since the textboxes weren’t bound to the object’s property, we need to update those properties “by hand”. That means we’ll need to use the command argument value to get the current index in order to get a reference to the item that is being edited;
  • several of the operations end up firing a refresh operation. This refresh is needed to ensure that the correct template is used;
  • We need to handle the itemRendering event because we might need to change the default template. When the “current item” is in edit mode, we change the template through the set_itemTemplate helper (as you can see, we needed to write some extra code because currently we can’t get the position of the current item from the event args parameter that is passed to the function).

And that’s it. With a little JS code, we’ve ended with a cool template which lets us add edit in place to a simple grid. We could improve the previous code for supporting new items, but I’ll leave that for you. And that’s it for now. Stay tuned for more on MS AJAX.

2 comments so far

  1. william apken
    6:03 am - 10-27-2009

    Another great article.

    Can you explain a little more about this statement:

    several of the operations end up firing a refresh operation. This refresh is needed to ensure that the correct template is used;

  2. luisabreu
    8:57 am - 10-27-2009

    I forced a refresh because I”m adapting the template in the itemRendering event which is generated when the control renders itself.