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 uiElement
s
#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'