Magento 2引入了一种新的方式来将产品添加到购物车中。系统现在提供完整的异步(ajax)进程,但在默认应用程序状态下进程本身未激活,它需要在模板内的脚本调用中进行一些手动调整。

 

介绍

尽管Magento ajax添加到购物车过程提供更好的体验和可用性,(大多数用户在将每个产品添加到购物车中之后,会因为连续地页面重载而感到沮丧)Magento通过简单传输完成工作并显示成功或错误消息,虽然这确实是一个坚实的基础,但我们仍然看到一些使用性问题。让我们从客户的角度来考虑一下,我们正在浏览商店,目前位于产品页面上,随时可以触发购物车按钮将我们想要的产品添加到购物车中。我们向下滚动了一点,可能是为了检查产品规格。按钮是可见的,但页面顶部和标题迷你不是。我们正在触发按钮。当进程开始时,按钮将其默认状态形式“add to cart”更改为“adding”,当流程完成时,将状态更改为“added”,然后返回到默认状态,这是在我们的产品实际添加后我们在页面上唯一的指示。

这种情况给我们留下了很大的改进空间。我们如何改进这个过程?很简单,ajax完成,我们就可以触发滚动事件滚动页面的顶部,,一旦minicart得到更新,我们就可以触发minicart UI对话框打开并告诉我们完整的体验以及minicart实际为我们我们提供的内容(可以查看购物车、结帐或继续购物)。

在这篇博客中,我将演示一种可行的方法。

 

启用ajax添加到购物车进程

首先,我们需要熟悉背后的架构。

[magento2_root_dir] /vendor/magento/module_catalog/view/frontend/templates/product/view/add-to-cart.phtml中,有一个addtocart.js组件的脚本调用,我们可以看到它的“bindSubmit”方法被设置为false。第一步是将其更改为true,从而启用ajax添加到购物车流程。

在主题中覆盖此文件

<script type="text/x-magento-init">
    {
        "#product_addtocart_form": {
            "catalogAddToCart": {
                "bindSubmit": false // change it to true
            }
        }
    }
</script>

好的,我们已经成功启用了ajax添加到购物车流程。下一步是什么?从模板中的脚本初始化代码片段中,我们可以得到有价值的信息,即脚本实际负责该流程。脚本别名catalogAddToCart实际上指向[magento2_root_dir] /vendor/magento/module_catalog/view/frontend/web/js/catalog-add-to-cart.js javascript组件。我们不会修改脚本本身,我们将进一步扩展ajaxSubmit方法,这样我们就可以在单独的地方进行自定义修改,并尽可能保持流程的其余部分的整洁。该过程需要对jQuery UI widget factory有专门的了解,以便熟悉该方法。

 

扩展小部件负责建立ajax调用

主要目标是扩展$.mage.catalogAddToCart小部件并修改它的ajaxSubmit方法。如果您不熟悉UI factory widget扩展方法,我建议在继续之前先熟悉UI widget factory。我不会详细介绍核心扩展过程,你可以在这里熟悉jQuery文档站点。

我们将在自定义主题中创建新的require-js脚本组件,并使用require-js define module procedure将catalog-add-to-cart脚本作为依赖项调用。

我们将在自定义主题中创建新的require-js脚本组件,且使用require-js 定义模块过程作为调用catalog-add-to-cart脚本的依赖。

首先,在[magento2_root_dir]/app/design/frontend/[your_vendor_dir]/[your_theme_dir]/web/js/目录中创建脚本为了本文的目的,我们将调用magease-ajax-cart.js,脚本需要定义为require js模块。在脚本内部,我们将扩展$ .mage.catalogAddToCart并覆盖ajaxSubmit方法,添加滚动动画以在ajax调用完成后滚动到页面顶部。从原始文件中简单复制整个ajaxSubmit方法,下面的代码演示了如何做到这一点。

/**
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
define([
    'jquery',
    'mage/translate',
    'jquery/ui',
    'Magento_Catalog/js/catalog-add-to-cart'
], function($, $t) {
    "use strict";
    $.widget('magease.ajax', $.mage.catalogAddToCart, {
 
        ajaxSubmit: function(form) {
            var self = this;
            $(self.options.minicartSelector).trigger('contentLoading');
            self.disableAddToCartButton(form);
 
            // setting global variable required for customized ajax cart process
 
            window.ajaxCartTransport = false;
 
            $.ajax({
                url: form.attr('action'),
                data: form.serialize(),
                type: 'post',
                dataType: 'json',
                beforeSend: function() {
                    if (self.isLoaderEnabled()) {
                        $('body').trigger(self.options.processStart);
                    }
                },
                success: function(res) {
                    if (self.isLoaderEnabled()) {
                        $('body').trigger(self.options.processStop);
                    }
 
                    if (res.backUrl) {
                        window.location = res.backUrl;
                        return;
                    }
                    if (res.messages) {
                        $(self.options.messagesSelector).html(res.messages);
                    }
                    if (res.minicart) {
                        $(self.options.minicartSelector).replaceWith(res.minicart);
                        $(self.options.minicartSelector).trigger('contentUpdated');
                    }
                    if (res.product && res.product.statusText) {
                        $(self.options.productStatusSelector)
                            .removeClass('available')
                            .addClass('unavailable')
                            .find('span')
                            .html(res.product.statusText);
                    }
                    self.enableAddToCartButton(form);
 
                    // animate scrolling to the top of the page 
 
                    $("html, body").animate({ scrollTop: 0 }, 1000, function() {});
 
                    // changing global variable value to true (this flag enables communication with update minicart logic)
 
                    window.ajaxCartTransport = true;
                }
            });
        }
    });
 
    return $.magease.ajax;
});

 

在requirejs-config中映射新创建的自定义js组件

第二步是将脚本映射到主题目录根目录下的requirejs-config.js文件中。

var config = {
    map: {
        "*": {
            "custom-ajaxcart": "js/magease-ajax-cart"
        }
    }
};

不要忘记检查脚本是否实际加载。

 

在模板文件中初始化新的自定义js组件

第三步是实际返回到初始化原始脚本的模板,并初始化自定义脚本而不是默认脚本。请记住,我们仍然将默认脚本加载为依赖项。

<script type="text/x-magento-init">
    {
        "#product_addtocart_form": {
            // we need to call our script by alias previously mapped in requirejs-config.js file
            "custom-ajaxcart": {
                "bindSubmit": true
            }
        }
    }
</script>

现在回到我们的自定义脚本文件,你可以看到我们依赖于success方法,一旦我们从服务器获得状态200 OK。我们使用jQuery将滚动事件设置为滚动到页面顶部。您可以使用您喜欢的任何方法,但是由于Magento中已经包含jQuery,所以我使用了它。

也许你现在正在质疑自己,除了滚动到顶部方法之外,为什么代码中没有其他东西?我们的代码逻辑在哪里负责打开minicart对话框?答案是,没有代码!为什么?原因很简单,除了指示后请求实际完成之外,我们仍然没有得到更多。Magento发出另一个/单独的调用,用更新的状态填充minicart。我们需要知道这个调用,我们需要在使用新数据更新minicart后打开购物车对话框。

我们不想从这里立即展开minicart UI对话框,因为数据仍然没有更新。打开对话框的最佳时间是在更新minicart并填充新数据之后。由于这两个进程在前端没有连接,我们需要设置某种标志,以便知道何时打开购物车对话框。您将看到,我在文件顶部创建了ajaxCartTransport变量,并将该变量分配给window对象,使其成为全局变量并始终可访问。这个标志将进一步连接我们的进程,一旦ajax成功并完成滚动过程,您将看到我们正在强制将变量值更改为true。这是我们在下一步中用来触发购物车对话框打开的标志。

现在我们知道ajax调用在哪里了,下一步是找到使用新数据更新minicart的逻辑。

 

修改负责更新minicart数据的js组件

该逻辑位于[magento2_root_dir] /vendor/magento/module-checkout/view/frontend/web/js/view/minicart.js文件。由于这个文件是UI组件和独立逻辑的混合,我建议您在主题中复制并覆盖整个脚本。

现在,我们需要找到数据更新的实际位置。它在UI组件更新方法内部,在更新方法中,我们将添加我们的小逻辑来扩展minicart下拉对话框,下面是一个简单的例子

 /**
         * Update mini shopping cart content.
         *
         * @param {Object} updatedCart
         * @returns void
         */
        update: function (updatedCart) {
            _.each(updatedCart, function (value, key) {
                if (!this.cart.hasOwnProperty(key)) {
                    this.cart[key] = ko.observable();
                }
                this.cart[key](value);
 
                // our logic for opening the minicart
 
                if(window.ajaxCartTransport == true) {
 
                    // finding the minicart wrapper element
 
                    var minicart = $('[data-block="minicart"]');
 
                    // finding the dropdown element itself and invoking dropdownDialog open method 
 
                    minicart.find('[data-role="dropdownDialog"]').dropdownDialog('open');
 
                    // setting our custom global variable immediately back to false
 
                    window.ajaxCartTransport = false;
                }
            }, this);
        },

我们在这做了什么?首先,我们检查了我们的标志,以确保只有在ajax调用不久前触发时才展开对话框。我们将检查全局变量ajaxCartTransport是否设置为true,如果是,我们将打开minicart对话框。我们将以最佳方式打开它,调用ui .dropdowndialog('open')方法。JQuery widget factory 提供了一个很好的功能,小部件方法调用我们需要确保之后立即将我们的标志设置为false,以防止在未连接到我们的ajax添加到购物车自定义进程的每个其他更新进程上打开minicart时出错。