Categories


Archives


Recent Posts


Categories


Understanding the Limitations of sections.xml

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

Updated for Magento 2! No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

One of the challenges Magento 2, (and all “full stack” oriented frameworks), face is data synchronization between the front end and the server. Server data will always represent the “source of truth” for any particular piece of data, but good front end developers will always be looking to reduce to number of round trips they make to the server. Magento 2 faces an additional cultural challenge in that it remains a PHP framework, and most developers doing Magento 2 work are going to rely on its many code injection features to change how server side data is saved. In other words, reducing a client side round trip may work out of the box, but may result in stale data if new server side code runs during ajax requests.

I’ve notice that, whenever a developer asks for help along these lines the mysterious sections.xml files are often mentioned as an option. Magento’s dev docs are typically professional yet cryptic, and while there are some decent explanations of how the sections.xml file work from a backend perspective no one seems to explain or understand the limited scope of what these configuration files can do for you.

The following is not a complete sections.xml tutorial, but hopefully contains enough information for you to get started.

What are Magento Sections

Magento has a RequireJS module named Magento_Customer/js/customer-data. This module implements a dictionary like object that allows you you get and save data. Setting data is easy

requirejs(['Magento_Customer/js/customer-data'], function(customerData){
    customerData.set('someKey', 'some value');
});

Getting data is also easy, but has a small twist. When you fetch the data, you get a Knockout observable

requirejs(['Magento_Customer/js/customer-data'], function(customerData){
    var observableObject = customerData.get('someKey', 'some value');

    //call returned observable to get value
    console.log(observableObject());
});    

Knockout’s observables allow you to create user interfaces that dynamically update when you update the value in the observable.

Behind the scenes, Magento stores these values using both cookies and the browser’s “localStorage” mechanism. The cookie is named section_data_ids, holds a list of each key (someKey above), and is used as a rudimentary cache-invalidation mechanism (see below).

Values are saved into localStorage using jQuery’s localStorage API

$.initNamespaceStorage('mage-cache-storage').localStorage;

Folks conversant with Magento’s RequireJS libraries may be surprised that they’re not using the Magento_Ui/js/lib/core/storage/local module to store values. Folks familiar with Magento’s engineering practices will be less surprised.

As you may have surmised, Magento considers each key in the Magento_Customer/js/customer-data registry to be a “section”.

What Does sections.xml Do?

To understand what sections.xml files do, we first need to talk about a new RequireJS module: Magento_Customer/js/section-config. Every front end Magento HTML page contains the following x-magento-init script.

<script type="text/x-magento-init">
    {
        "*": {
            "Magento_Customer/js/section-config": {
                "sections": {
                    "stores/store/switch": "*",
                    "directory/currency/switch": "*",
                    /* ... more information ... */,
                },
                "clientSideSections": ["checkout-data"],
                "baseUrls": ["http://magento-2-1-3.dev/"]
            }
        }
    }        
</script>

This tag is rendered by the following bit of Layout Update XML

<!-- File: vendor/magento/module-customer/view/frontend/layout/default.xml -->
<block  name="customer.section.config"
        class="MagentoCustomerBlockSectionConfig"
        template="Magento_Customer::js/section-config.phtml"/>

The JSON object that gets passed to Magento_Customer/js/section-config (truncated above) is a serialized representation of the data in sections.xml. The purpose of adding this to every page is to ensure that RequireJS developers have access to this configuration information.

Next, we’ll need to take a look at the definition file for the Magento_Customer/js/customer-data RequireJS module.

#File: vendor/magento/module-customer/view/frontend/web/js/customer-data.js
define([
    'jquery',
    'underscore',
    'ko',
    'Magento_Customer/js/section-config',
    'mage/storage',
    'jquery/jquery-storageapi'
], function ($, _, ko, sectionConfig, mageStorage) {    

/*...*/

    $(document).on('ajaxComplete', function (event, xhr, settings) {
        var sections,
            redirects;

        if (settings.type.match(/post|put/i)) {
            sections = sectionConfig.getAffectedSections(settings.url);

            if (sections) {
                customerData.invalidate(sections);
                redirects = ['redirect', 'backUrl'];

                if (_.isObject(xhr.responseJSON) && !_.isEmpty(_.pick(xhr.responseJSON, redirects))) {
                    return;
                }
                customerData.reload(sections, true);
            }
        }
    });

    /**
     * Events listener
     */
    $(document).on('submit', function (event) {
        var sections;

        if (event.target.method.match(/post|put/i)) {
            sections = sectionConfig.getAffectedSections(event.target.action);

            if (sections) {
                customerData.invalidate(sections);
            }
        }
    });
}

/*...*/

First, notice that Magento_Customer/js/section-config is imported as sectionConfig. Then, look to the jQuery event listeners. The first time you load the Magento_Customer/js/customer-data module, Magento sets up an ajaxComplete listener, and a form submit listener.

The ajaxComplete listener will, after every ajax request that’s made via jQuery, check the sectionConfig object. If this sectionConfig object contains a URL that matches the just requested URL, and the URL was requested via an HTTP POST or an HTTP PUT, then Magento will “reload” the section. Reloading a section happens in the customerData.reload call. A reload will pass the sections, via AJAX, to the http://magento.example.com/customer/section/load URL and use the returned JSON to reset the value in the key/section/observable.

The form submit listener will, whenever any form gets submitted, try to match the submission URL with a URL/section from sectionConfig. If a match is found, then that section will be “invalidated”. Invalidating a section is a way of telling the Magento_Customer/js/customer-data object that a section needs to be refreshed, without fetching it. The idea is other code will keep an eye what needs to be invalidated and decide when to update it. For those curious about implementation the details, it seems like Magento stores invalidation information in the aforementioned section_data_ids cookie.

In plain english? Not possible. In less complicated english? If you store data in the Magento_Customer/js/customer-data registry, and include a configuration for your key/section in sections.xml, Magento will automatically update the values in that key/section whenever an ajax request is made to the URL configured in sections.xml. The updated data comes from a request to the following URL

URL:
http://magento.example.com/customer/section/load/     

Controller File:
vendor/magento/module-customer/Controller/Section/Load.php

The previously mentioned StackExchange and dev docs links contain information on how to configure Magento so this URL returns data from a PHP class you provide.

For example, this sections.xml configuration

#File: vendor/magento/module-customer/etc/frontend/sections.xml
<action name="customer/ajax/login">
    <section name="checkout-data"/>
    <section name="cart"/>
</action>

tells Magento that, whenever the customer/ajax/login URL is loaded via AJAX, that Magento should also update the values in the checkout-data and cart keys of the Magento_Customer/js/customer-data registry. Again, updated values are loaded from the http://magento.example.com/customer/section/load URL.

The reason you care: If Magento, or a third party developer (you), uses a section from Magento_Customer/js/customer-data to populate a KnockoutJS or UI Component binding/dom-element, then Magento will automatically update the UI when these ajax requests are made. This automatic updating happens because these values are stored as KnockoutJS observables. If there’s other programatic logic that references a value from Magento_Customer/js/customer-data, then that data will be updated when ajax requests are made.

This is a powerful, but limited, feature. If the code in question does not use the Magento_Customer/js/customer-data repository to display data, then sections.xml will do nothing for you. You’ll need to update the information in your javascript application with your own code.

Copyright © Alan Storm 1975 – 2019 All Rights Reserved

Originally Posted: 3rd February 2017