Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Ordering Form Collection Parameters For MonoRail using jQuery

On my current app we are using ajax heavily for updating forms, often with the response getting injected into an table or unordered list. The entire form is then posted for the action which will actually change state in the domain.

For example, say we have a 'NewItem' form (and have several properties):

<form action="myaction.castle">
    
   
<label for="newItem_Name">Name</label>
   
<input type="text" name="newItem.Name" id="newItem_Name"/>
    <
label for="newItem_Phone">Name</label>
   
<input type="text" name="newItem.Phone" id="newItem_Phone"/>
   
<!--10 other properties-->
   
<input type="submit">Save</input>
</form>
 
With the action on the controller:
public void myaction([DataBind("newItem")] ItemDTO item) 
{
  
PropertyBag["item"] = item;
  
RenderView("ViewWithSophisticatedFormattingForItems");
}
 
This simply pushes back an item using the data provided and jquery appends the table with:
<tr><td><% output item.Name %></td></tr>
<tr style='display:none;'><td><% output Form.TextField("item.Name") %></td></tr>
 
The client will toggle between 'readonly' and 'edit' modes to change data inline.
 
So this would result in multiple rows of 'item.Name' and 'item.Phone', etc. and the DataBinder will choke if you try this:
public void UpdateToDomain([DataBind("item")] List<ItemDTO> items) 
{
  
//do stuff in domain
}

As magical as MonoRail and her SmartDispatcherController is, she has to know which value belongs to which item in a collection. The lack of indices in the params will therefore cause arrays of 'Value's for a single ItemDTO instance.

My first thought was to do a custom implementation of IParameterBinder + DataBinder combo that parses out the params and tries to split them into multiple instances. This is too difficult though since not all fields may be present for an instance.

So to keep the items in a collection (like table rows or unordered lists) nicely indexed, I created a plugin that handles all the form elements for me. After ajax calls that load data into a table or ul, I can just call the plugin on the rows and all forms elements remain in their appropriate order. Similarly I can unindex forms for posting in the format the action expects. I can similarly just reorder indexes. 

This just preserves the id and name convention used throughout monorail (with "_" and "[index]"  delimiters respectively). It also changes the 'for' attribute on the label elements for fun.

This is chainable.

To use:

$("table.specimens > tbody tr.edit").indices("on"); //index all rows with class 'edit' 
$("table.specimens > tbody tr.edit").indices("off"); //remove index on all rows with class '.edit'
$("table.specimens > tbody tr.edit").indices("ordered"); //ordered existing indices
$("table.specimens > tbody tr.edit").indices();//toggle indexed/nonindexed mode
//specify the parameterName prefix if you are indexing on a nested collection
$("table.specimens > tbody tr.edit").indices("on",{parameterName:'mainclass.nestedObjects'});
//customize whether a new row should increment the index (default is to skip increments on items with class 'repeat-index')
$("table.specimens > tbody tr.edit").indices("on",{isRepeatedIndex:function(){...});

 

And the plugin:

/***indices plugin for ordering form elements in a group
    @mode (String) : 
        "on" (index elements if not already indexed), 
        "off" (unindex elements if already indexed), 
        "toggle" (toggle between "on" and "off" modes),
        "ordered" (reorder indices of indexed items, or )
    @options (hash) :
        @parameterName (String) : the parameter name to index; if empty, uses the 'name' attribute up to the first '.'
        @isRepeatedIndex (predicate[current item]) : return 'true' to repeat the index (for things like hidden rows, etc); default is a class name 'repeat-index'
    
***/

        
(function($) {
   
$.fn.indices = function(mode, options) {
       
mode = mode || "toggle";
       
var defaults = {
           
parameterName: "[a-zA-Z0-9]+",
           
isRepeatedIndex: function(item) { return $(item).is(".repeat-index"); }
       
};
       
var opt = $.extend(defaults, options);

       
var reIndexedName = new RegExp("^(" + opt.parameterName + ")\\[(\\d+)\\]\\.", "gi");
       
var reIndexedId = new RegExp("^(" + opt.parameterName.replace(".", "_") + ")_\\d+\\_", "gi");
       
var reNonIndexedName = new RegExp("^(" + opt.parameterName + ")\\.", "gi");
       
var reNonIndexedId = new RegExp("^(" + opt.parameterName.replace(".", "_") + ")_", "gi");
       
var changeStrategy = null;
       
var ndx = -1;

       
var unindexElement = function(el, ndx) {
           
if (el.name) {
               
$(el).attr("name", el.name.replace(reIndexedName, "$1."));
           
}
           
if (el.id) {
               
$(el).attr("id", el.id.replace(reIndexedId, "$1_"));
           
}
           
if ($(el).attr("for")) {
               
$(el).attr("for", $(el).attr("for").replace(reIndexedId, "$1_"));
           
}
       
};

       
var indexElement = function(el, ndx) {
           
if (el.name) {
               
$(el).attr("name", el.name.replace(reNonIndexedName, "$1[" + ndx + "]."));
           
}
           
if (el.id) {
               
$(el).attr("id", el.id.replace(reNonIndexedId, "$1_" + ndx + "_"));
           
}
           
if ($(el).attr("for")) {
               
$(el).attr("for", $(el).attr("for").replace(reNonIndexedId, "$1_" + ndx + "_"));
           
}
       
};

       
var reindexElement = function(el, ndx) {
           
if (el.name) {
               
$(el).attr("name", el.name.replace(reIndexedName, "$1[" + ndx + "]."));
           
}
           
if (el.id) {
               
$(el).attr("id", el.id.replace(reIndexedId, "$1_" + ndx + "_"));
           
}
           
if ($(el).attr("for")) {
               
$(el).attr("for", $(el).attr("for").replace(reIndexedId, "$1_" + ndx + "_"));
           
}
       
};
       
return this.each(function(idx, item) {
           
if (!opt.isRepeatedIndex(item)) {
               
ndx++;
           
}

           
$(this).find(":input,label").each(function() {

               
if (!changeStrategy) {
                   
//evaluate only once per collection
                    if (!this.name && !this.id) {
                       
//nothing to do here
                        return;
                   
}
                   
changeStrategy = function() { }; //default empty function
                    var isIndexed = this.name ? reIndexedName.test(this.name) : this.id ? reIndexedId.test(this.id) : false;
                   
if (mode == "ordered" && !isIndexed) {
                       
mode = "on"; //we already do ordered indexing so just use the 'on' mode
                    }

                   
if (isIndexed) {
                       
if (mode == "off" || mode == "toggle") {
                           
changeStrategy = unindexElement;
                       
} else if (mode == "ordered") {
                           
changeStrategy = reindexElement
                       
}
                   
} else {
                       
if (mode == "on" || mode == "toggle") {
                           
changeStrategy = indexElement;
                       
}
                   
}
               
}
               
changeStrategy(this, ndx); //fire

           
});
       
});

   
};

 

I don't like having to scrub the data like this on the client side, but I really can't think of a way to keep parameters being sent valid while still updating data in small chunks without passing indexes back and forth. Any other approaches would be great to hear!


Posted 11-25-2008 10:37 PM by Michael Nichols
Filed under: ,

[Advertisement]

Comments

Patrick Steele's .NET Blog wrote Ordering Form Collection Parameters For MonoRail using jQuery
on 11-26-2008 7:45 AM

Mike Nichols wrote a neat jQuery plugin to automatically order a list of items (like &lt;TR&gt;'s or

Patrick Steele wrote Ordering Form Collection Parameters For MonoRail using jQuery
on 11-26-2008 8:06 AM

Mike Nichols wrote a neat jQuery plugin to automatically order a list of items (like &lt;TR&gt;&#39;s

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
Umbraco
NServiceBus
RavenDb
Web Sequence Diagrams
Ducksboard<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)