Categories


Archives


Recent Posts


Categories


Magento 2: uiElement’s Local Storage Module

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.

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

Earlier, we described the modules defaults for Magento uiElement javascript constructor functions. If you look at the uiElement definition, you’ll see there’s one modules default already configured for all uiElements

#File: vendor/magento/module-ui/view/base/web/js/lib/core/element/element.js

modules: {
    storage: '${ $.storageConfig.provider }'
}

This storage default reaches into the instantiated object’s storageConfig.provider to pull out a uiRegistry key. (see the ES6 Template Literals article if you’re not familiar with the syntax). This storageConfig is another uiElement defaults

#File: vendor/magento/module-ui/view/base/web/js/lib/core/element/element.js

storageConfig: {
    provider: 'localStorage',
    namespace: '${ $.name }',
    path: '${ $.storageConfig.provider }:${ $.storageConfig.namespace }'
},

As we can see, the default provider for all uiElement objects is localStorage. This means if we instantiate a UiElement object

//normally the RequireJS modules would be specified in a define function
reg = requirejs('uiRegistry');
UiElement = requirejs('uiElement');
object    = new UiElement;

The object returned by the storage() accessor method will be the uiRegistry’s localStorage key.

console.log(object.storage() === reg.get('localStorage'));
true

Unless a developer changes these defaults, all uiElement based objects will have access to the localStorage registry item via the storage() method.

What is localStorage

As we’ve learned in the UI Components series, most of the items in Magento’s uiRegistry comes from the Magento_Ui/js/core/app application(s) run via Magento’s x-magento-init scripts. The localStorage item, however, does not come from this application.

If you look at the RequireJS dependencies in the uiElement definition file.

#File: vendor/magento/module-ui/view/base/web/js/lib/core/element/element.js       
define([
    'ko',
    'underscore',
    'mageUtils',
    'uiRegistry',
    'uiEvents',
    'uiClass',
    './links',
    '../storage/local'
],    

you’ll see the ../storage/local dependency (in Magento 2.0.x this is named ./storage, but the same concepts apply). This corresponds to the Magento_Ui/js/lib/core/storage/local RequireJS module. If we look at this module’s source.

#File: vendor/magento//module-ui/view/base/web/js/lib/core/storage/local.js
/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
define([
    'underscore',
    'uiRegistry',
    'mageUtils',
    'uiEvents'
], function (_, registry, utils, EventsBus) {
    'use strict';

    var root = 'appData',
        localStorage = window.localStorage,
        hasSupport,
        storage;    
    //...
    registry.set('localStorage', storage);

    return storage;    
});

We see this module, before returning a prepared storage object, registers that same object with the uiRegistry. This means the first time a programmer uses Magento_Ui/js/lib/core/storage/local, the localStorage registry key will exist.

The Magento_Ui/js/lib/core/storage/local module provides an abstraction around the browser’s localStorage API that lets a client programmer treat a portion of the entire localStorage data store as a single nested data structure.

In plain code? Run the following.

reg = requirejs('uiRegistry');

var toSave = {
    key:'some sort of value'
};

reg.get('localStorage').set('foo', toSave);

Your toSave object is now stored in the browser’s localStorage data store. You can view it using the get method.

console.log( reg.get('localStorage').get('foo'));

If you’re new to localStorage – try closing your browser, re-opening it, re-open your Magento website, and then run the following

reg = requirejs('uiRegistry');

var toSave = {
    key:'some sort of value'
};

console.log( reg.get('localStorage').get('foo') );   

You should see the toSave object has survived a page refresh. The localStorage API provides a way to save data for a user without making a round trip to the server. If you’re interested in learning more, the Mozilla Developer Network docs are a good place to start.

Also, if you’re using Google Chrome’s debugger you can view the entire localStorage data structure by navigating to

Application -> Local Storage -> Your Domain

and looking for the appData key.

uiElement’s store Method

While you can – thanks to javascript’s “everything is public” object model – access Magento’s localStorage abstraction directly

reg = requirejs('uiRegistry');
UiElement = requirejs('uiElement');
object    = new UiElement;

object.storage().set('foo','bar');

every uiElement also gets a store method

#File: vendor/magento/module-ui/view/base/web/js/lib/core/element/element.js
store: function (property, data) {
    var ns = this.storageConfig.namespace,
        path = utils.fullPath(ns, property);

    if (arguments.length < 2) {
        data = this.get(property);
    }

    this.storage('set', path, data);

    return this;
},    

This store method will use the namespace property of the storageConfig to create a dotted.path to save the variable. If we look at storageConfig again.

#File: vendor/magento/module-ui/view/base/web/js/lib/core/element/element.js

storageConfig: {
    /* ... */
    namespace: '${ $.name }',
    /* ... */
},

We can see this namespace points back to the uiElement’s name. By default, a uiElement’s name is blank, which means the path variable

this.storage('set', path, data);

will just be the property name we’re setting. However, in a constructor function that has uiElement as a parent.

OurConstructorFunction = UiElement.extend({
    'defaults':{
          'name':'someUniqueName'
     }
});   

object = new OurConstructorFunction;

object.foo = 'another value';
object.store('foo');

Then that path will be someUniqueName.foo. If your uiElement object has a unique name, the store method allows you to easily save values into localStorage without needing to construct these sort of unique paths yourself.

Be Careful though – multiple objects instantiated from the same uiElement constructor function will have the same namespace (since they have the same name). While we’re exploring the uiElement system as a traditional object oriented programming feature, some of its functionality is tied heavily into assumptions Magento’s engineering team made with the uiRegistry object and Magento_Ui/js/core/app application. In the later context, name is often considered a unique identifier since these objects are registered once. Hopefully we’ll have a chance to explore this in full in in a later article.

Storing Object Properties

Where the store method gets really interesting is here.

OurConstructorFunction = UiElement.extend({
    'defaults':{
          'name':'someUniqueName'
     }
});   

object = new OurConstructorFunction;

object.foo = 'another value';
object.store('foo');

If you call store with a single parameter (i.e. no data) then Magento will save the object’s property value to localStorage. Then, you can use the restore method to reset those values on another page load.

OurConstructorFunction = UiElement.extend({
    'defaults':{
          'name':'someUniqueName'
     }
});   

object = new OurConstructorFunction;

//empty
console.log( object.foo);

//restore values from localStorage
console.restore();  

//contains previous set and `store()`ed value
console.log(object.foo);

The restore method will set any values it finds in localStorage for the current uiElement’s storageConfig.namespace value, so you’ll want to be careful using this functionality. Also, the only place Magento seems to use it is in the Magento_Ui/js/grid/controls/bookmarks/bookmarks module

#File: vendor/magento/module-ui/view/base/web/js/grid/controls/bookmarks/bookmarks.js
initialize: function () {
    utils.limit(this, 'checkState', 5);
    utils.limit(this, 'saveState', 2000);
    utils.limit(this, '_defaultPolyfill', 3000);

    this._super()
        .restore()
        .initStorage()
        .initViews();

    return this;
},

i.e. there’s not a lot out there w/r/t to best practices. Definitely a feature for the bold at this point in Magento’s lifecycle.

Not the Only Local Storage

Finally, it’s worth pointing out that this is not Magento’s only localStorage system. Magento also makes use of jQuery’s localStorage abstraction

#File: vendor/magento/module-customer/view/frontend/web/js/customer-data.js
//...    
var storage = $.initNamespaceStorage('mage-cache-storage').localStorage;
//...
var storageInvalidation = $.initNamespaceStorage('mage-cache-storage-section-invalidation').localStorage;

If you’re look at the raw localStorage entries, all the Magento_Ui/js/lib/core/storage/local entries will be namespaced under the appData key. If there are values outside of that data structure, they probably come from jQuery’s localStorage API. Your best bet at finding where Magento sets these values is to search all the javascript files for the string key

$ find vendor/magento -name '*.js' | xargs grep 'mage-cache-storage'

Copyright © Alan Storm 1975 – 2017 All Rights Reserved

Originally Posted: 28th November 2016