views:

190

answers:

1

I have a partial view that is bound to an object Cart. Cart has a collection of CartLines. My view is below:

        <tbody>
        <% foreach (var line in Model.Lines) { %>
        <tr>
            <td align="center"><%=Html.CatalogImage(line.Product.DefaultImage, 80) %></td>
            <td align="left">
                <%=Html.ActionLink(line.Product.Name, "Product", "Catalog",  
                    new { productId = line.Product.Id }, new { title = "View " + line.Product.Name })%>
            </td>
            <td align="right"><%= line.Product.Price.ToString("c")%></td>
            <td align="center">
                <%=Html.Hidden("lines[" + i + "].key", line.Product.Id) %>
                <%=Html.TextBox("lines[" + i + "].value", line.Quantity, new { @class = "quantity" })%>
            </td>
            <td align="right"><%= (line.LineTotal).ToString("c")%></td>
            <td>
                <%using (Ajax.BeginForm("RemoveFromCart", "Cart", 
                      new {ProductId = line.Product.Id, returnUrl = ViewData["returnUrl"]}, 
                      new AjaxOptions { UpdateTargetId="cart", LoadingElementId="loading" }))
                  {%>                           
                        <input type="image" src="<%=AppHelper.ImageUrl("delete.gif")%>" value="Remove item"  />
                <%} %>
            </td>
        </tr>
        <% i++; } %>
    </tbody>

There are two things to note. The first is that I am using a form per line for removing items.

The second is that I had attempted to allow users to change the quantity of line items and then click an update button to pass all the changes to the controller action:

        // POST: /Cart/Update
    [HttpPost]
    public ActionResult Update(Cart cart, IDictionary<int,int> lines, string returnUrl)
    {
        foreach (var line in lines) {
            Product p = _catalogService.GetProduct(line.Key);
            cart.UpdateItem(p, line.Value);
        }

        if (Request.IsAjaxRequest())
            return PartialView("Cart", cart);
        else
            return RedirectToAction("Index", new { returnUrl });
    }

Note that I am using a dictionary since I am only concerned about the product and quantity. I don't really like the fact that I am having to retrieve the product again before calling cart.UpdateItem but I couldn't figure out how to pass the Product from the model to my action instead of the id.

The main problem however, is rather stupidly I wrapped the entire cart in a form so that I could post back the values and then spent a good hour wondering why things were not working correctly in IE - doh! nested forms

So I am stuck on how to get round this. I want the ability to remove items individually but allow a user to change item quantities and then pass all changes at once to the controller. I can't use links for my remove action as I would need to use javascript to force a post and everything must work without javascript enabled.

[Update]

Would a better solution be to allow updates on my custom model binder? This way I could make changes inside my view and post the cart object back to the controller - although I'm not sure whether this is possible with child collections (Cart.CartItems). I've had a look on sites like Amazon and it would appear they wrap the entire cart in a form and both global update buttons and indidivual remove item buttons post back to the same action when javascript is disabled. Any help would be appreciated.

Thanks, Ben

A: 

There is only one way here and thats the ugly way. Have 1 form around everything. Then in the action you have to check which button was pressed (you get the name of the button in the request).

It gets even more ugly with differences in firefox and ie. If you have a button pressed ie or firefox (Dont remember which one) not only sends the name of the pressed button, but also the location where the button was pressed.

You have more options if your solution can rely on JS enabled browsers. But thats another story.

Malcolm Frexner
I've had a look at a few ecommerce stores using MVC and how they handle javascript disabled. It would seem most wrap the cart in one form for quantity updates and it would appear they are using standard get requests for removing items from the cart - considered bad practice no?
Ben
Yes performing an update via get is not a good way.
Malcolm Frexner
Hmm, am not sure how to get round this then. I can add an update button per line and have a load more forms (giving me a form for update and a form for remove - per line) or I can resort to using links for removing items then use jscript to hijack the link and perform a post. Then I could have a single form for the update button. However, if someone disables javascript I then have the problem that my remove links will now perform a GET. This is one thing that would be so easy in a normal web forms app :(
Ben