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.