Merchello is 100% MVC out of the box. I've personally done quite a several AJAX carts using straight JQuery and also have done a few using angular - single page checkout.
Here are some snippets from the site I'm building at the moment to give you an idea - but note we're not done with them yet ....
/// <summary>
/// Adds an item to the cart.
/// </summary>
/// <param name="model">
/// The <see cref="AddItemModel"/>.
/// </param>
/// <returns>
/// The <see cref="ActionResult"/> which will either be a partial view when async or redirect to current Umbraco page as a fallback.
/// </returns>
[HttpPost]
public ActionResult DoAddToCart(AddItemModel model)
{
var merchello = new MerchelloHelper(false);
this.Basket.EnableDataModifiers = StoreHelper.IncludeVat();
var product = merchello.Query.Product.GetByKey(model.ProductKey);
if (model.OptionChoices != null && model.OptionChoices.Any())
{
var extendedData = new ExtendedDataCollection();
var variant = product.GetProductVariantDisplayWithAttributes(model.OptionChoices);
//// serialize the attributes here as they are need in the design
extendedData.SetValue(Constants.ExtendedDataKeys.CartItemAttributes, JsonConvert.SerializeObject(variant.Attributes));
this.Basket.AddItem(variant, variant.Name, model.Quantity, extendedData);
}
else
{
this.Basket.AddItem(product, product.Name, model.Quantity);
}
this.Basket.Save();
if (this.Request.IsAjaxRequest())
{
// TODO RSS replace with the partial for the pop window
return this.Content("Form submitted");
}
//// NO JS
return this.RedirectToCurrentUmbracoPage();
}
/// <summary>
/// Updates the cart quantities.
/// </summary>
/// <param name="model">
/// The model.
/// </param>
/// <returns>
/// The <see cref="ActionResult"/>.
/// </returns>
[HttpPost]
public ActionResult UpdateQuantities(CartModel model)
{
foreach (var item in model.CartItems.Where(item => this.Basket.Items.First(x => x.Key == item.Key).Quantity != item.Quantity))
{
this.Basket.UpdateQuantity(item.Key, item.Quantity);
}
this.Basket.Save();
if (this.Request.IsAjaxRequest())
{
return this.PartialView("Cart", this.Basket.ToCartModel());
}
LogHelper.Info<CartController>("Synchronous cart update");
return this.RedirectToCurrentUmbracoPage();
}
JavaScript snippets for the Cart class
var Cart = {
// initializes cart
init: function () {
Cart.AddToCartBoxes.init();
Cart.CartQuantity.init();
},
// refreshes the button count and total labels
refreshLabels: function () {
var cartQty = $('.cart-qty');
if (cartQty.length > 0) {
var url = Cart.apiEndpoint + "postrefreshcartlabels";
$.ajax({
type: 'POST',
url: url,
dataType: 'html',
contentType: "application/json; charset=utf-8",
data: JSON.stringify(Cart.nonce),
success: function (data) {
var refresh = JSON.parse(data);
// updates the nav button and the cart sidebar
$.each(cartQty, function (index, elem) {
$(elem).text(refresh.cartItemCount);
});
// total - cart view aside
var total = $('#cart-total');
if (total.length > 0) {
$(total).text(refresh.total);
}
},
error: function (jqXhr, textStatus, errorThrown) {
console.info(errorThrown);
}
});
}
},
// responsible for the drop downs on the cart page
CartQuantity : {
init: function () {
// hide the update button and initializes the cart quantity drop downs
var submit = $('#btn-update-cart');
if (submit.length > 0) {
$(submit).hide();
var qtyddls = $('[data-quantity]');
console.info(qtyddls.length);
$.each(qtyddls, function (index, ddl) {
$(ddl).change(function () {
Cart.CartQuantity.updateCart();
});
});
}
},
// updates a cart item quantity with ddl change
updateCart: function () {
var frm = $('#cartform');
if (!frm.length) {
return;
}
var url = Cart.surfaceEndpoint + 'updatequantities';
$.ajax({
type: 'POST',
url: url,
data: $(frm).serialize(),
success: function (data) {
var replacement = $(data);
var existing = $("#cartdetails");
if (existing.length > 0) {
$(existing).replaceWith(replacement);
}
// have to rebind the event handlers here
Cart.CartQuantity.init();
Cart.refreshLabels();
},
error: function (jqXhr, textStatus, errorThrown) {
console.info(errorThrown);
}
});
}
},
// object handles all of the AddToCartBoxes on the page
AddToCartBoxes: {
// initializes the object
init: function () {
// ='True'
var addToCart = $("div[data-a2casync]");
if (addToCart.length > 0) {
Cart.AddToCartBoxes.bind();
}
},
// handles the binding of the boxes
bind: function () {
// get the array of product keys that have variants
var keys = [];
var a2Ccontrols = $("[data-a2casync]");
$.each(a2Ccontrols, function (index, elem) {
var frm = $(elem).find('form');
$(frm).submit(function () {
Cart.AddToCartBoxes.doAddToCart(this);
return false;
});
if ($(elem).attr('data-a2casync') === 'True') {
var key = $(elem).data('addtocart');
keys.push(key);
}
});
// get the collection of pricing tables
if (keys.length > 0) {
var url = Cart.apiEndpoint + "GetProductDataTables";
$.ajax({
type: 'POST',
url: url,
dataType: 'json',
contentType: "application/json; charset=utf-8",
data: JSON.stringify(keys),
success: function (data) {
$.each(data, function (idx, table) {
Cart.AddToCartBoxes.buildTable(table);
var control = $("[data-addtocart='" + table.productKey + "']");
var ddls = $(control).find("[data-product='option']");
_.each(ddls, function(ddl) {
$(ddl).change(function () {
// update the prices with the updated variant information
Cart.AddToCartBoxes.updateView(Cart.AddToCartBoxes.getProductDataTable(table.productKey));
});
});
// initial load variant information
Cart.AddToCartBoxes.updateView(Cart.AddToCartBoxes.getProductDataTable(table.productKey));
});
},
error: function (jqXhr, textStatus, errorThrown) {
console.info(errorThrown);
}
});
}
},
// updates the AddToCart view
updateView: function(productData) {
// Get the control
var control = $("[data-addtocart='" + productData.productKey + "']");
// find the options dropdowns and get the selected choice (attribute keys)
var ddls = $(control).find("[data-product='option']");
var keys = [];
_.each(ddls, function(ddl) {
keys.push($(ddl).val());
});
// find the matching product data row for the keys
var row = productData.getRowByMatchKeys(keys);
var container = $(control).find("[data-pricing='variant']");
var url = Cart.surfaceEndpoint + 'additempricing';
// TODO RSS actaully already have all the data needed here.
// Lee mentioned he wanted everything in partials so I'm using just posting the information
// to populate. It will make formatting easier.
$.ajax({
type: 'POST',
url: url,
dataType: 'html',
contentType: "application/json; charset=utf-8",
data: JSON.stringify(row),
success: function (content) {
$(container).html(content);
},
error: function (jqXhr, textStatus, errorThrown) {
console.info(errorThrown);
}
});
},
// adds the item to the cart
doAddToCart: function (frm) {
if ($(frm).valid()) {
var url = Cart.surfaceEndpoint + 'doaddtocart';
$.ajax({
url: url,
type: 'POST',
data: $(frm).serialize(),
success: function (result) {
Cart.refreshLabels();
// TODO Lee partial for popup using result
},
error: function (jqXhr, textStatus, errorThrown) {
console.info(errorThrown);
}
});
}
return false;
},
// class instantiation
buildTable : function(data) {
var table = new ProductDataTable();
table.productKey = data.productKey;
$.each(data.rows, function(index, dataRow) {
table.rows.push($.extend(new ProductDataTableRow(), dataRow));
});
Cart.AddToCartBoxes.productDataTables.push(table);
},
// gets the pricing table for the product key
getProductDataTable: function(productKey) {
return _.find(Cart.AddToCartBoxes.productDataTables, function (t) { return t.productKey === productKey; });
},
// the collection of pricing tables for this page
productDataTables: []
},
// the store data API endpoint
apiEndpoint: '/umbraco/FRS/StoreDataApi/',
surfaceEndpoint: '/umbraco/surface/cart/'
};
Merchello Ajax style suport?
Hi Rusty and co,
It seems that Merchello shop features is 100% MVC out of the box?
Anyone with experience of creating ajax style "add to basket", "checkout flow" for Merchello?
best Jesper
Hey Jesper,
Merchello is 100% MVC out of the box. I've personally done quite a several AJAX carts using straight JQuery and also have done a few using angular - single page checkout.
Here are some snippets from the site I'm building at the moment to give you an idea - but note we're not done with them yet ....
JavaScript snippets for the Cart class
Thank you Rusty!! Looks promising.
/Jesper
How can I do that?
Hey Simon,
This post was written in the conceptual stages and it looks like some of the naming changed.
See: https://github.com/Merchello/Merchello/blob/merchello-dev/src/Merchello.Web/Controllers/BasketControllerBase%7BT%7D.cs#L164
Edit: Most of this stuff is in the merchello.ui.js file so you may not have to rewrite it if your using the FastTrack build.
Hi Rusty,
May I ask you where it will then be used the following:
Thanks
It's used in the listings like the basket, summaries and receipts - there just not styled in the starter.
is working on a reply...