Dashboard > MonoRail > Home > script.aculo.us Ajax Sorting
script.aculo.us Ajax Sorting
Added by Adam Tybor, last edited by Adam Tybor on Jul 06, 2007  (view change) show comment
Labels: 


Using the scriptaculous library creating drag & drop sortable lists is a breeze.  The example uses brail and based on a recent patch added to the Binder Trunk.

Code 

View.brail

<ul id="PhoneNumber_list">
  <?brail i = 0; for ph in PhoneNumbers : ?>
    <li id="phonenumber_${ph.Id}" class="sortable">
      ${Form.HiddenField("phonenumbers[${i}].id")}
      ${Form.TextField("phonenumbers[${i}].phone")}
      <img class="handle" style="cursor:move; padding-left: .25em;" alt="reorder item" src="${siteroot}/Assets/img/reorder-icon.png" width="14" height="14" title="Re-Order Items" />
    </li>
  <?brail i++; end ?>
</ul>
<script type="text/javascript">
Sortable.create("PhoneNumber_list",
		{ handle:"handle",
		  only:"sortable",
		  onUpdate:function(ul) {
				new Ajax.Updater("", "${siteroot}/Contact/ContactMethods/ReOrder.rails?type=PhoneNumber" + "&contactId=${contact.Id}",
					{parameters:Sortable.serialize(ul), method:'get', evalScripts:true});
			}
		});
</script>

The above view creates an unordered list that will allow the user to use drag and drop to reorder the elements in the list. Upon each change in order an ajax request will be sending the id's of the phonenumbers in the new order.

Laying out the Container

It is recommended to use an unordered list for the drag and drop to work properly in all browsers.  There are known browser restrictions preventing the use of table.  The only real key here is setting the id of the container.  The id will be used in the ajax request as the name of parameter which will contain the values in the sorted order.

Creating the Items 

When creating the items there a couple options.  First is the id of the item.  Using prototype serialization the id should have a prefix, an underscore, then the value you want sent back to the server.  In this example we are creating an id "phonenumber_Guid" where Guid is being replaced by the unique Id of the PhoneNumber.  Next we add a class name of "sortable".  We use this to restrict scriptaculous to only allowing items tagged sortable to be drag and droppable.  This is optional.  Finally we add an image with the class "handle".  This is also optional tells scriptaculous to create a drag and drop handle on this element.  By default the entire list item element would be draggable, but since we allow editing we want to give the user a specific handle to use to reorder.

Initializing the Drag & Drop 

The final piece of the view is making the container sortable.  This achieve by calling Sortable.create and passing any options you want.  See the script.aculo.us documentation for more help.  This initialization also uses a callback function when the list changes.  The callback function simply serializes the list and sends the values back to the server in an Ajax request.

ReOrder.brailjs

page.VisualEffect('Highlight', "${contactMethodType}_list")

Uses JSGeneration to highlight the container indicating a successful save.

ContactMethodsController.cs

public class ContactMethodsController : BaseController
{
	private readonly IContactMethodsService contactMethodsService;

	public ContactMethodsController(IContactMethodsService contactMethodsService)
	{
		this.contactMethodsService = contactMethodsService;
	}

	public void View()
	{
		PropertyBag["PhoneNumbers"] = null; // A Collection of phone numbers
	}

	public void ReOrder(string type, Guid contactId, Guid[] phonenumber_list)
	{
		contactMethodsService(type, contactId, phonenumber_list);
		PropertyBag["contactMethodType"] = type;
	}
}

View

Just fill a collection of phonenumbers to display.

ReOrder 

The ReOrder method is what will handle the ajax request.  This simple binds the parameter values, notice the name of the Guid array.  Then delegates the responsibility of persisting the changes to the service.  This controller assumes you are using Windsor and injecting the service in the Controller constructor.  For sake of simplicity I have not done any error handling. 

PhoneNumber.cs

public class PhoneNumber
{
	private Guid id;
	private string phone;

	public Guid Id
	{
		get { return id; }
		set { id = value; }
	}

	public string Phone
	{
		get { return phone; }
		set { phone = value; }
	}
}

The domain model class used to represent the phone number.

More... 

To Serialize the all the data in the container you can try this function.  There is probably a better way but this is what I started playing with.

function serializeContainer(container)
{
	var s = serializeContainer(container);
	if (s.length > 0)
		return s.substring(0, s.length - 1);
	return s;
}

function serializeContainerR(el, childs, index, data)
{
  if(!data) data = "";
  var e = $(el);
  if(!e) return "";
  if(e.serialize)
  {
		var s = e.serialize();
		if (s && s.length > 0)
			data += s + "&";
  }
  var echilds = e.childElements();
  if (echilds && echilds.length > 0)
    data += serializeContainerR(echilds[0], echilds, 0);
  if(childs && index > -1 && index < childs.length - 1)
    data += serializeContainerR(childs[index + 1], childs, index + 1);
}

Its been a while since I had do recursion... please fix this terriable code!!

Site running on a free Atlassian Confluence Community License granted to Castle Project. Evaluate Confluence today.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.5.4 Build:#809 Jun 12, 2007) - Bug/feature request - Contact Administrators