| Current Path : /home/rtorresani/www/vendor/magento/module-ui/view/base/web/js/lib/core/element/ |
| Current File : //home/rtorresani/www/vendor/magento/module-ui/view/base/web/js/lib/core/element/element.js |
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
/**
* @api
*/
define([
'ko',
'underscore',
'mageUtils',
'uiRegistry',
'uiEvents',
'uiClass',
'./links',
'../storage/local'
], function (ko, _, utils, registry, Events, Class, links) {
'use strict';
var Element;
/**
* Creates observable property using knockouts'
* 'observableArray' or 'observable' methods,
* depending on a type of 'value' parameter.
*
* @param {Object} obj - Object to whom property belongs.
* @param {String} key - Key of the property.
* @param {*} value - Initial value.
*/
function observable(obj, key, value) {
var method = Array.isArray(value) ? 'observableArray' : 'observable';
if (_.isFunction(obj[key]) && !ko.isObservable(obj[key])) {
return;
}
if (ko.isObservable(value)) {
value = value();
}
ko.isObservable(obj[key]) ?
obj[key](value) :
obj[key] = ko[method](value);
}
/**
* Creates observable property using 'track' method.
*
* @param {Object} obj - Object to whom property belongs.
* @param {String} key - Key of the property.
* @param {*} value - Initial value.
*/
function accessor(obj, key, value) {
if (_.isFunction(obj[key]) || ko.isObservable(obj[key])) {
return;
}
obj[key] = value;
if (!ko.es5.isTracked(obj, key)) {
ko.track(obj, [key]);
}
}
Element = _.extend({
defaults: {
_requested: {},
containers: [],
exports: {},
imports: {},
links: {},
listens: {},
name: '',
ns: '${ $.name.split(".")[0] }',
provider: '',
registerNodes: true,
source: null,
statefull: {},
template: '',
tracks: {},
storageConfig: {
provider: 'localStorage',
namespace: '${ $.name }',
path: '${ $.storageConfig.provider }:${ $.storageConfig.namespace }'
},
maps: {
imports: {},
exports: {}
},
modules: {
storage: '${ $.storageConfig.provider }'
}
},
/**
* Initializes model instance.
*
* @returns {Element} Chainable.
*/
initialize: function () {
this._super()
.initObservable()
.initModules()
.initStatefull()
.initLinks()
.initUnique();
return this;
},
/**
* Initializes observable properties.
*
* @returns {Element} Chainable.
*/
initObservable: function () {
_.each(this.tracks, function (enabled, key) {
if (enabled) {
this.track(key);
}
}, this);
return this;
},
/**
* Parses 'modules' object and creates
* async wrappers for specified components.
*
* @returns {Element} Chainable.
*/
initModules: function () {
_.each(this.modules, function (name, property) {
if (name) {
this[property] = this.requestModule(name);
}
}, this);
if (!_.isFunction(this.source)) {
this.source = registry.get(this.provider);
}
return this;
},
/**
* Called when current element was injected to another component.
*
* @param {Object} parent - Instance of a 'parent' component.
* @returns {Collection} Chainable.
*/
initContainer: function (parent) {
this.containers.push(parent);
return this;
},
/**
* Initializes statefull properties
* based on the keys of 'statefull' object.
*
* @returns {Element} Chainable.
*/
initStatefull: function () {
_.each(this.statefull, function (path, key) {
if (path) {
this.setStatefull(key, path);
}
}, this);
return this;
},
/**
* Initializes links between properties.
*
* @returns {Element} Chainbale.
*/
initLinks: function () {
return this.setListeners(this.listens)
.setLinks(this.links, 'imports')
.setLinks(this.links, 'exports')
.setLinks(this.exports, 'exports')
.setLinks(this.imports, 'imports');
},
/**
* Initializes listeners of the unique property.
*
* @returns {Element} Chainable.
*/
initUnique: function () {
var update = this.onUniqueUpdate.bind(this),
uniqueNs = this.uniqueNs;
this.hasUnique = this.uniqueProp && uniqueNs;
if (this.hasUnique) {
this.source.on(uniqueNs, update, this.name);
}
return this;
},
/**
* Makes specified property to be stored automatically.
*
* @param {String} key - Name of the property
* that will be stored.
* @param {String} [path=key] - Path to the property in storage.
* @returns {Element} Chainable.
*/
setStatefull: function (key, path) {
var link = {};
path = !_.isString(path) || !path ? key : path;
link[key] = this.storageConfig.path + '.' + path;
this.setLinks(link, 'imports')
.setLinks(link, 'exports');
return this;
},
/**
* Updates property specified in uniqueNs
* if elements' unique property is set to 'true'.
*
* @returns {Element} Chainable.
*/
setUnique: function () {
var property = this.uniqueProp;
if (this[property]()) {
this.source.set(this.uniqueNs, this.name);
}
return this;
},
/**
* Creates 'async' wrapper for the specified component
* using uiRegistry 'async' method and caches it
* in a '_requested' components object.
*
* @param {String} name - Name of requested component.
* @returns {Function} Async module wrapper.
*/
requestModule: function (name) {
var requested = this._requested;
if (!requested[name]) {
requested[name] = registry.async(name);
}
return requested[name];
},
/**
* Returns path to elements' template.
*
* @returns {String}
*/
getTemplate: function () {
return this.template;
},
/**
* Checks if template was specified for an element.
*
* @returns {Boolean}
*/
hasTemplate: function () {
return !!this.template;
},
/**
* Returns value of the nested property.
*
* @param {String} path - Path to the property.
* @returns {*} Value of the property.
*/
get: function (path) {
return utils.nested(this, path);
},
/**
* Sets provided value as a value of the specified nested property.
* Triggers changes notifications, if value has mutated.
*
* @param {String} path - Path to property.
* @param {*} value - New value of the property.
* @returns {Element} Chainable.
*/
set: function (path, value) {
var data = this.get(path),
diffs;
diffs = !_.isFunction(data) && !this.isTracked(path) ?
utils.compare(data, value, path) :
false;
utils.nested(this, path, value);
if (diffs) {
this._notifyChanges(diffs);
}
return this;
},
/**
* Removes nested property from the object.
*
* @param {String} path - Path to the property.
* @returns {Element} Chainable.
*/
remove: function (path) {
var data = utils.nested(this, path),
diffs;
if (_.isUndefined(data) || _.isFunction(data)) {
return this;
}
diffs = utils.compare(data, undefined, path);
utils.nestedRemove(this, path);
this._notifyChanges(diffs);
return this;
},
/**
* Creates observable properties for the current object.
*
* If 'useTrack' flag is set to 'true' then each property will be
* created with a ES5 get/set accessor descriptors, instead of
* making them an observable functions.
* See 'knockout-es5' library for more information.
*
* @param {Boolean} [useAccessors=false] - Whether to create an
* observable function or to use property accesessors.
* @param {(Object|String|Array)} properties - List of observable properties.
* @returns {Element} Chainable.
*
* @example Sample declaration and equivalent knockout methods.
* this.key = 'value';
* this.array = ['value'];
*
* this.observe(['key', 'array']);
* =>
* this.key = ko.observable('value');
* this.array = ko.observableArray(['value']);
*
* @example Another syntaxes of the previous example.
* this.observe({
* key: 'value',
* array: ['value']
* });
*/
observe: function (useAccessors, properties) {
var model = this,
trackMethod;
if (typeof useAccessors !== 'boolean') {
properties = useAccessors;
useAccessors = false;
}
trackMethod = useAccessors ? accessor : observable;
if (_.isString(properties)) {
properties = properties.split(' ');
}
if (Array.isArray(properties)) {
properties.forEach(function (key) {
trackMethod(model, key, model[key]);
});
} else if (typeof properties === 'object') {
_.each(properties, function (value, key) {
trackMethod(model, key, value);
});
}
return this;
},
/**
* Delegates call to 'observe' method but
* with a predefined 'useAccessors' flag.
*
* @param {(String|Array|Object)} properties - List of observable properties.
* @returns {Element} Chainable.
*/
track: function (properties) {
this.observe(true, properties);
return this;
},
/**
* Checks if specified property is tracked.
*
* @param {String} property - Property to be checked.
* @returns {Boolean}
*/
isTracked: function (property) {
return ko.es5.isTracked(this, property);
},
/**
* Invokes subscribers for the provided changes.
*
* @param {Object} diffs - Object with changes descriptions.
* @returns {Element} Chainable.
*/
_notifyChanges: function (diffs) {
diffs.changes.forEach(function (change) {
this.trigger(change.path, change.value, change);
}, this);
_.each(diffs.containers, function (changes, name) {
var value = utils.nested(this, name);
this.trigger(name, value, changes);
}, this);
return this;
},
/**
* Extracts all stored data and sets it to element.
*
* @returns {Element} Chainable.
*/
restore: function () {
var ns = this.storageConfig.namespace,
storage = this.storage();
if (storage) {
utils.extend(this, storage.get(ns));
}
return this;
},
/**
* Stores value of the specified property in components' storage module.
*
* @param {String} property
* @param {*} [data=this[property]]
* @returns {Element} Chainable.
*/
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;
},
/**
* Extracts specified property from storage.
*
* @param {String} [property] - Name of the property
* to be extracted. If not specified then all of the
* stored will be returned.
* @returns {*}
*/
getStored: function (property) {
var ns = this.storageConfig.namespace,
path = utils.fullPath(ns, property),
storage = this.storage(),
data;
if (storage) {
data = storage.get(path);
}
return data;
},
/**
* Removes stored property.
*
* @param {String} property - Property to be removed from storage.
* @returns {Element} Chainable.
*/
removeStored: function (property) {
var ns = this.storageConfig.namespace,
path = utils.fullPath(ns, property);
this.storage('remove', path);
return this;
},
/**
* Destroys current instance along with all of its' children.
* @param {Boolean} skipUpdate - skip collection update when element to be destroyed.
*/
destroy: function (skipUpdate) {
this._dropHandlers()
._clearRefs(skipUpdate);
},
/**
* Removes events listeners.
* @private
*
* @returns {Element} Chainable.
*/
_dropHandlers: function () {
this.off();
if (_.isFunction(this.source)) {
this.source().off(this.name);
} else if (this.source) {
this.source.off(this.name);
}
return this;
},
/**
* Removes all references to current instance and
* calls 'destroy' method on all of its' children.
* @private
* @param {Boolean} skipUpdate - skip collection update when element to be destroyed.
*
* @returns {Element} Chainable.
*/
_clearRefs: function (skipUpdate) {
registry.remove(this.name);
this.containers.forEach(function (parent) {
parent.removeChild(this, skipUpdate);
}, this);
return this;
},
/**
* Overrides 'EventsBus.trigger' method to implement events bubbling.
*
* @param {...*} arguments - Any number of arguments that should be passed to the events' handler.
* @returns {Boolean} False if event bubbling was canceled.
*/
bubble: function () {
var args = _.toArray(arguments),
bubble = this.trigger.apply(this, args),
result;
if (!bubble) {
return false;
}
this.containers.forEach(function (parent) {
result = parent.bubble.apply(parent, args);
if (result === false) {
bubble = false;
}
});
return !!bubble;
},
/**
* Callback which fires when property under uniqueNs has changed.
*/
onUniqueUpdate: function (name) {
var active = name === this.name,
property = this.uniqueProp;
this[property](active);
},
/**
* Clean data form data source.
*
* @returns {Element}
*/
cleanData: function () {
if (this.source && this.source.componentType === 'dataSource') {
if (this.elems) {
_.each(this.elems(), function (val) {
val.cleanData();
});
} else {
this.source.remove(this.dataScope);
}
}
return this;
},
/**
* Fallback data.
*/
cacheData: function () {
this.cachedComponent = utils.copy(this);
},
/**
* Update configuration in component.
*
* @param {*} oldValue
* @param {*} newValue
* @param {String} path - path to value.
* @returns {Element}
*/
updateConfig: function (oldValue, newValue, path) {
var names = path.split('.'),
index = _.lastIndexOf(names, 'config') + 1;
names = names.splice(index, names.length - index).join('.');
this.set(names, newValue);
return this;
}
}, Events, links);
return Class.extend(Element);
});