Skip to content

Minicart

Simon Frost edited this page Nov 11, 2020 · 1 revision

The minicart is built from two components: minicart.js and sidebar.js.

minicart.js provides the skeleton and exposes logic for opening, closing and updating the contennnts of the minicart.

sidebar.js exposes the logic for displaying the contents of the minicart. It is based on the Ui Component /lib/web/mage/dropdown.js.

The PHTML template

The UI Component is initialised in the template /vendor/magento/module-checkout/view/frontend/templates/cart/minicart.phtml.

    <?php if ($block->getIsNeedToDisplaySideBar()): ?>
        <div class="block block-minicart"
             data-role="dropdownDialog"
             data-mage-init='{"dropdownDialog":{
                "appendTo":"[data-block=minicart]",
                "triggerTarget":".showcart",
                "timeout": "2000",
                "closeOnMouseLeave": false,
                "closeOnEscape": true,
                "triggerClass":"active",
                "parentClass":"active",
                "buttons":[]}}'>
            <div id="minicart-content-wrapper" data-bind="scope: 'minicart_content'">
                <!-- ko template: getTemplate() --><!-- /ko -->
            </div>
            <?= $block->getChildHtml('minicart.addons') ?>
        </div>

The data-mage-init attribute is populated with a JS object which configures the dialog component which appears when the mini cart is clicked on.

At the bottom of the PHTML template, there is a script block which intialises the mini cart UI Component:

    <script type="text/x-magento-init">
    {
        "[data-block='minicart']": {
            "Magento_Ui/js/core/app": <?= /* @escapeNotVerified */ $block->getJsLayout() ?>
        },
        "*": {
            "Magento_Ui/js/block-loader": "<?= /* @escapeNotVerified */ $block->getViewFileUrl('images/loader-1.gif') ?>"
        }
    }
    </script>

Which produces something like this:

    <script type="text/x-magento-init">
    {
        "[data-block='minicart']": {
            "Magento_Ui/js/core/app": {"components":{"minicart_content":{"children":{"subtotal.container":{"children":{"subtotal":{"children":{"subtotal.totals":{"config":{"display_cart_subtotal_incl_tax":1,"display_cart_subtotal_excl_tax":0,"template":"Magento_Tax\/checkout\/minicart\/subtotal\/totals"},"children":{"subtotal.totals.msrp":{"component":"Magento_Msrp\/js\/view\/checkout\/minicart\/subtotal\/totals","config":{"displayArea":"minicart-subtotal-hidden","template":"Magento_Msrp\/checkout\/minicart\/subtotal\/totals"}}},"component":"Magento_Tax\/js\/view\/checkout\/minicart\/subtotal\/totals"}},"component":"uiComponent","config":{"template":"Magento_Checkout\/minicart\/subtotal"}}},"component":"uiComponent","config":{"displayArea":"subtotalContainer"}},"item.renderer":{"component":"uiComponent","config":{"displayArea":"defaultRenderer","template":"Trespass_MiniBasket\/minicart\/item\/default"},"children":{"item.image":{"component":"Magento_Catalog\/js\/view\/image","config":{"template":"Magento_Catalog\/product\/image","displayArea":"itemImage"}},"checkout.cart.item.price.sidebar":{"component":"uiComponent","config":{"template":"Magento_Checkout\/minicart\/item\/price","displayArea":"priceSidebar"}}}},"extra_info":{"component":"uiComponent","config":{"displayArea":"extraInfo"}},"promotion":{"component":"uiComponent","config":{"displayArea":"promotion"}}},"config":{"itemRenderer":{"default":"defaultRenderer","simple":"defaultRenderer","virtual":"defaultRenderer"},"template":"Trespass_MiniBasket\/minicart\/content"},"component":"Magento_Checkout\/js\/view\/minicart"}},"types":[]}        },
        "*": {
            "Magento_Ui/js/block-loader": "http://trespass-m2.local/uk/static/frontend/Trespass/default/en_GB/images/loader-1.gif"
        }
    }
    </script>

This is a tree of all the components which will be initialised when this one is.

The [data-block='minicart'] refers to the data-block attribute of the enclosing div:

<div data-block="minicart" class="minicart-wrapper">
    <a class="action showcart" href="<?= /* @escapeNotVerified */ $block->getShoppingCartUrl() ?>"
       data-bind="scope: 'minicart_content'">
        <span class="text"><?= /* @escapeNotVerified */ __('My Cart') ?></span>
        <span class="counter qty empty"
              data-bind="css: { empty: !!getCartParam('summary_count') == false }, blockLoader: isLoading">
            <span class="counter-number"><!-- ko text: getCartParam('summary_count') --><!-- /ko --></span>
            <span class="counter-label">
            <!-- ko if: getCartParam('summary_count') -->
                <!-- ko text: getCartParam('summary_count') --><!-- /ko -->
                <!-- ko i18n: 'items' --><!-- /ko -->
            <!-- /ko -->
            </span>
        </span>
    </a>
    <?php if ($block->getIsNeedToDisplaySideBar()): ?>
        <div class="block block-minicart" ...

Another script block exposes the serialised config for the UI Component:

    <script>
        window.checkout = <?= /* @escapeNotVerified */ $block->getSerializedConfig() ?>;
    </script>

Which produces:

    <script>
        window.checkout = {"shoppingCartUrl":"http:\/\/trespass-m2.local\/uk\/checkout\/cart\/","checkoutUrl":"http:\/\/trespass-m2.local\/uk\/checkout\/","updateItemQtyUrl":"http:\/\/trespass-m2.local\/uk\/checkout\/sidebar\/updateItemQty\/","removeItemUrl":"http:\/\/trespass-m2.local\/uk\/checkout\/sidebar\/removeItem\/","imageTemplate":"Magento_Catalog\/product\/image_with_borders","baseUrl":"http:\/\/trespass-m2.local\/uk\/","minicartMaxItemsVisible":3,"websiteId":"2","maxItemsToDisplay":3,"storeId":"2","customerLoginUrl":"http:\/\/trespass-m2.local\/uk\/customer\/account\/login\/referer\/aHR0cDovL3RyZXNwYXNzLW0yLmxvY2FsL3VrLw%2C%2C\/","isRedirectRequired":false,"autocomplete":"off","captcha":{"user_login":{"isCaseSensitive":false,"imageHeight":50,"imageSrc":"","refreshUrl":"http:\/\/trespass-m2.local\/uk\/captcha\/refresh\/","isRequired":false,"timestamp":1570889172}}};
    </script>

The config is defined in the class html/vendor/magento/module-checkout/Block/Cart/Sidebar.php:

    /**
     * Returns minicart config
     *
     * @return array
     */
    public function getConfig()
    {
        return [
            'shoppingCartUrl' => $this->getShoppingCartUrl(),
            'checkoutUrl' => $this->getCheckoutUrl(),
            'updateItemQtyUrl' => $this->getUpdateItemQtyUrl(),
            'removeItemUrl' => $this->getRemoveItemUrl(),
            'imageTemplate' => $this->getImageHtmlTemplate(),
            'baseUrl' => $this->getBaseUrl(),
            'minicartMaxItemsVisible' => $this->getMiniCartMaxItemsCount(),
            'websiteId' => $this->_storeManager->getStore()->getWebsiteId(),
            'maxItemsToDisplay' => $this->getMaxItemsToDisplay(),
            'storeId' => $this->_storeManager->getStore()->getId()
        ];
    }

The JavaScript

The PHTML template outputs a lot of JS configuration. This is then passed to the UI Component files when they are initialised.

The JS file is html/vendor/magento/module-checkout/view/frontend/web/js/view/minicart.js.

The minicart UI Component uses the Magento core component 'sidebar', located at: html/vendor/magento/module-checkout/view/frontend/web/js/sidebar.js.

The sidebar is itself based on a more generic core component called 'dropdown': html/lib/web/mage/dropdown.js, which is itself a wrapper for the jQuery UI Dialog widget (see https://api.jqueryui.com/dialog/).

The options for the jQuery UI Dialog widget are merged with the options declared in the dropdown, sidebar components. The options defined in the data-mage-init attribute of the block-minicart div are also merged with the options object.

When the dropdowndialogopen event is triggered (basically when the mini cart is clicked) the initSidebar method is called:

    // Some properties used by the initSidebar method
    var sidebarInitialized = false,
        addToCartCalls = 0,
        miniCart;

    // The miniCart DOM element
    miniCart = $('[data-block=\'minicart\']');

    /**
     * @return {Boolean}
     */
    function initSidebar() {
        if (miniCart.data('mageSidebar')) {
            miniCart.sidebar('update');
        }

        if (!$('[data-role=product-item]').length) {
            // If there are no items in the mini cart, exit early
            return false;
        }
        miniCart.trigger('contentUpdated');

        // Exit early if sidebar was already initialised, to prevent initialisation running twice
        if (sidebarInitialized) {
            return false;
        }
        sidebarInitialized = true;

        // Define options which will be passed to the dialog jQuery UI widget
        // The window.checkout.* values were all output in the PHTML template
        miniCart.sidebar({
            'targetElement': 'div.block.block-minicart',
            'url': {
                'checkout': window.checkout.checkoutUrl,
                'update': window.checkout.updateItemQtyUrl,
                'remove': window.checkout.removeItemUrl,
                'loginUrl': window.checkout.customerLoginUrl,
                'isRedirectRequired': window.checkout.isRedirectRequired
            },
            'button': {
                'checkout': '#top-cart-btn-checkout',
                'remove': '#mini-cart a.action.delete',
                'close': '#btn-minicart-close'
            },
            'showcart': {
                'parent': 'span.counter',
                'qty': 'span.counter-number',
                'label': 'span.counter-label'
            },
            'minicart': {
                'list': '#mini-cart',
                'content': '#minicart-content-wrapper',
                'qty': 'div.items-total',
                'subtotal': 'div.subtotal span.price',
                'maxItemsVisible': window.checkout.minicartMaxItemsVisible
            },
            'item': {
                'qty': ':input.cart-item-qty',
                'button': ':button.update-cart-item'
            },
            'confirmMessage': $.mage.__('Are you sure you would like to remove this item from the shopping cart?')
        });
    }

    // Execute all of the above when the 'dropdowndialogopen' event is triggered
    miniCart.on('dropdowndialogopen', function () {
        initSidebar();
    });