Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Jesper Ordrup 969 posts 1374 karma points MVP
    Jan 05, 2016 @ 14:17
    Jesper Ordrup
    0

    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

  • Rusty Swayne 1655 posts 4985 karma points c-trib
    Jan 05, 2016 @ 17:07
    Rusty Swayne
    1

    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 ....

        /// <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/'
    };
    
  • Jesper Ordrup 969 posts 1374 karma points MVP
    Jan 07, 2016 @ 10:42
    Jesper Ordrup
    0

    Thank you Rusty!! Looks promising.

    /Jesper

  • Simon 687 posts 1061 karma points
    Sep 07, 2016 @ 14:04
    Simon
    0
    Constants.ExtendedDataKeys.CartItemAttributes is not there in Merhcello 2.2.0
    

    How can I do that?

  • Rusty Swayne 1655 posts 4985 karma points c-trib
    Sep 07, 2016 @ 16:45
    Rusty Swayne
    0

    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.

  • Simon 687 posts 1061 karma points
    Sep 07, 2016 @ 19:33
    Simon
    0

    Hi Rusty,

    May I ask you where it will then be used the following:

    extendedData.SetValue(Core.Constants.ExtendedDataKeys.BasketItemCustomerChoice, JsonConvert.SerializeObject(choiceExplainations));
    

    Thanks

  • Rusty Swayne 1655 posts 4985 karma points c-trib
    Sep 07, 2016 @ 19:39
    Rusty Swayne
    0

    It's used in the listings like the basket, summaries and receipts - there just not styled in the starter.

    enter image description here

Please Sign in or register to post replies

Write your reply to:

Draft