Sergio and the sigil

ASP.NET MVC with jQuery SuperLoad

Posted by Sergio on 2009-08-23
The other day I wrote about the jQuery SuperLoad plugin but I couldn't offer any real working sample of an application using it. Today I'm here to rectify that and talk about the new sample code I've recently added to the project repository.

Adding products to your shopping cart

The application has a single page, which lists a few books that you can add to your shopping cart. The cart is initially empty and when we add items to it, both the cart item list and the total price (at the top of the page) get updated from a single Ajax call.

The client side

Here's sample of what each of the products' HTML looks like. They each are inside a div which's id contains the product's id.
<div id="product_8" class="product">
	<img src="../../Content/images/prod_8.jpg" align="left" />

	<div class="title">Product 8</div>
	<div class="prodPrice" >$8.88</div>
	Qty: <input type="text" size="2"  maxlength="3" 
				class="quantity" value="1"/>
	<input type="button" value="Add to cart" class="addButton" />
</div>
We just need to fire one Ajax call whenever one of those "Add to cart" buttons is clicked. Here's the code that does that.
$('.addButton').click(function() {
	var prod = $(this).parent('.product');
	var prodId = prod.attr('id').split('_')[1];
	var qty = prod.find('.quantity').val();

	$.superLoad({
		url: '/Shopping/AddItem',
		type: 'POST',
		data: { product: prodId, quantity: qty },
		success: function() { $('#empty').remove(); }
	});
	
});
As you can see, we are making a superLoad() call to the AddItem action in the ShoppingController. Since this is a data modification call, we chose to use an HTTP POST request. The posted data goes in the data option. To tidy up things, we delete the empty text from the cart (if it's still there) once the call completes successfully.

Multiple results from a single action

The challenge on the server side is to come up with a sustainable method of reusing existing actions and combine them in a single action result, formatted to SuperLoad's liking and returned to the browser. To address that issue the sample comes with an implementation of a composite action result class specifically built for the response format we are trying to create. That class is the SuperLoadResult, listed below.
public class SuperLoadResult : ActionResult
{
	public IEnumerable<SuperLoadAjaxContent> ContentItems { get; private set; }

	public SuperLoadResult(params SuperLoadAjaxContent[] contentItems)
	{
		ContentItems = new List<SuperLoadAjaxContent>();
		((List<SuperLoadAjaxContent>)ContentItems).AddRange(contentItems);
	}

	public override void ExecuteResult(ControllerContext context)
	{
		context.HttpContext.Response.ContentType = MediaTypeNames.Text.Html;

		context.HttpContext.Response.Write("<div class=\"ajax-response\">");
		foreach (var item in ContentItems)
		{
			context.HttpContext.Response.Write(
				string.Format("<div class=\"ajax-content\" title=\"{0} {1}\">",
			                  item.Command.CommandText, item.Selector));
			item.GetResult().ExecuteResult(context);
			context.HttpContext.Response.Write("</div>");
		}
		context.HttpContext.Response.Write( "</div>");
	}
}
I bet things will become clearer once I show it being used from the AddItem action. So here it goes.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult AddItem(int product, int quantity)
{
	var sku = GetProduct(product);
	var item = new CartItem(sku, quantity);
	var cart = GetCurrentCart();
	cart.Items.Add(item);

	//add all the separate action results that we want into 
	// a SuperLoad result, mapping each one to the right 
	// selector and update type.
	return new SuperLoadResult(
		new SuperLoadAjaxContent("#cartTotal", 
		                  SuperLoadCommand.Update, 
						  () => CartTotal(cart)),

		new SuperLoadAjaxContent("#cart", 
		                  SuperLoadCommand.Append, 
						  () => CartItem(item))
		);
}
That last parameter to the SuperLoadAjaxContent's constructor is what will be invoked to provide an ActionResult-derived object, which will be executed and injected in the right place of the combined response. If I needed to update more elements, I could simply pass more instances of SuperLoadAjaxContent to my SuperLoadResult. Both CartTotal and CartItem are regular action methods that return a partial view from an .ascx template. Here's how simple the CartItem action is.
public ActionResult CartItem(CartItem item)
{
	return PartialView("CartItem", item);
}
Again, the entire sample is available for browsing, forking, or downloading at the repository page.

jQuery SuperLoad

Posted by Sergio on 2009-08-21

An important part of working with jQuery is to create plugins to simplify repetitive tasks. I write jQuery plugins all the time for internal consumption of my applications.

Most of them are not of enough general purpose to be shared publicly. Recently I created one that could stand on its own and I chose to make it available if anyone is interested.

Multiple Element Ajax Updates

jQuery SuperLoad came out of the necessity to update more than one element in a page without needing to do everything with JavaScript (and you know I'm not the one that avoids JavaScript).

All of the page elements I needed to update with Ajax came from page segments (partials, user controls, etc) that I had available on my server side. It seemed appropriate to go back to the server to get updated versions of that same HTML. But I didn't want to issue a separate $.ajax() or $(elem).load for each one of them.

Using SuperLoad I can now return something like the following from my server application and have more than one element updated.

<div class="ajax-content">
	<div title="!appendTo #content .shoppingList">
		<li>
			<img src="/images/prod12345.png" class="product-image">
			<div class="prodSummary">
				<div class="prodTitle">ACME Product</div>
				<div class="prodPrice">$12.34</div>
			</div>
		</li>
	</div>
	<div title="!update #orderTotal">
		Total: <span class="totalPrice">$47.22</span>
		<div class="specialOffer">Eligible for free shipping!</div>
	</div>
	<script>
		$('#orderTotal specialOffer').effect('highlight');
	</script>
</div>

All that is needed for this to be correctly applied to the right elements (selected by "#content .shoppingList" and "#orderTotal" is a line of code similar to this example.

$.superLoad({ 
	url: '/shopping/addItem', 
	type: 'POST', 
	data: {product: 12345} 
});

Check the plugin source code comments or the github repository page for more details.