milo.registry.facets.get('Data')
Facet to give access to DOM data
var Data = _.createSubclass(ComponentFacet, 'Data');
Data facet instance methods
_.extendProto(Data, {
start: Data$start,
getState: Data$getState,
setState: Data$setState,
get: Data$get,
set: Data$set,
del: Data$del,
splice: Data$splice,
len: Data$len,
path: Data$path,
getPath: Data$getPath,
getKey: Data$getKey,
_get: Data$_get,
_set: Data$_set,
_del: Data$_del,
_splice: Data$_splice,
_len: Data$_len,
_setScalarValue: Data$_setScalarValue,
_getScalarValue: Data$_getScalarValue,
_bubbleUpDataChange: Data$_bubbleUpDataChange,
_queueDataChange: Data$_queueDataChange,
_postDataChanges: Data$_postDataChanges,
_prepareMessageSource: _prepareMessageSource
});
facetsRegistry.add(Data);
module.exports = Data;
ModelPath methods added to Data prototype
['push', 'pop', 'unshift', 'shift'].forEach(function(methodName) {
var method = Model.Path.prototype[methodName];
_.defineProperty(Data.prototype, methodName, method);
});
// these methods will be wrapped to support "*" pattern subscriptions
var proxyDataSourceMethods = {
// value: 'value',
trigger: 'trigger'
};
Data facet instance method
Starts Data facet
Called by component after component is initialized.
function Data$start() {
// change messenger methods to work with "*" subscriptions (like Model class)
pathUtils.wrapMessengerMethods.call(this);
ComponentFacet.prototype.start.apply(this, arguments);
// get/set methods to set data of element
this.elData = getElementDataAccess(this.owner.el);
this._dataChangesQueue = [];
this._prepareMessageSource();
// store facet data path
this._path = '.' + this.owner.name;
// current value
this._value = this.get();
// prepare internal and external messengers
// this._prepareMessengers();
// subscribe to DOM event and accessors' messages
this.onSync('', onOwnDataChange);
// message to mark the end of batch on the current level
this.onSync('datachangesfinished', onDataChangesFinished);
// changes in scope children with Data facet
this.onSync('childdata', onChildData);
// to enable reactive connections
this.onSync('changedata', changeDataHandler);
}
Data facet instance method
Create and connect internal and external messengers of Data facet.
External messenger's methods are proxied on the Data facet and they allows "*" subscriptions.
// function _prepareMessengers() {
// Data facet will post all its changes on internal messenger
// var internalMessenger = new Messenger(this);
// message source to connect internal messenger to external
// var internalMessengerSource = new MessengerMessageSource(this, undefined, new ModelMsgAPI, internalMessenger);
// external messenger to which all model users will subscribe,
// that will allow "*" subscriptions and support "changedata" message api.
// var externalMessenger = new Messenger(this, Messenger.defaultMethods, internalMessengerSource);
// _.defineProperties(this, {
// _messenger: externalMessenger,
// _internalMessenger: internalMessenger
// });
// }
Option name | Type | Description |
---|---|---|
msg | String | |
[data] | Object |
Subscriber to datachangesfinished event.
Calls the method to post changes batch and bubbles up the message
function onDataChangesFinished(msg, data) {
this._postDataChanges(data.inTransaction);
var parentData = this.scopeParent();
if (parentData) parentData.postMessage('datachangesfinished', data);
}
Option name | Type | Description |
---|---|---|
value | Object, String, Number | value to be set. If the value if scalar, it will be set on component's element, if the value is object - on DOM tree inside component |
return | Object, String, Number |
Data facet instance method
Sets data in DOM hierarchy recursively.
Returns the object with the data actually set (can be different, if components matching some properties are missing).
function Data$set(value) {
var inTransaction = getTransactionFlag(Data$set);
try {
return executeHook.call(this, 'set', arguments);
} catch (e) {
if (e != noHook) throw e;
}
setTransactionFlag(this._set, inTransaction);
var oldValue = this._value
, newValue = this._set(value);
// this message triggers onOwnDataChange, as well as actuall DOM change
// so the parent gets notified
var msg = { path: '', type: 'changed',
newValue: newValue, oldValue: oldValue };
setTransactionFlag(msg, inTransaction);
this.postMessage('', msg);
return newValue;
}
function Data$_set(value) {
var inTransaction = getTransactionFlag(Data$_set);
var valueSet;
if (value !== null && typeof value == 'object') {
if (Array.isArray(value)) {
valueSet = [];
var listFacet = this.owner.list;
if (listFacet){
var listLength = listFacet.count()
, newItemsCount = value.length - listLength;
if (newItemsCount >= 3) {
listFacet._addItems(newItemsCount);
listFacet._updateDataPaths(listLength, listFacet.count());
}
value.forEach(function(childValue, index) {
setChildData.call(this, valueSet, childValue, index, '[$$]');
}, this);
var listCount = listFacet.count()
, removeCount = listCount - value.length;
while (removeCount-- > 0)
listFacet._removeItem(value.length);
} else
logger.warn('Data: setting array data without List facet');
} else {
valueSet = {};
_.eachKey(value, function(childValue, key) {
setChildData.call(this, valueSet, childValue, key, '.$$');
}, this);
}
} else
valueSet = this._setScalarValue(value);
this._value = valueSet;
return valueSet;
function setChildData(valueSet, childValue, key, pathSyntax) {
var childPath = pathSyntax.replace('$$', key);
var childDataFacet = this.path(childPath, typeof childValue != 'undefined');
if (childDataFacet) {
setTransactionFlag(childDataFacet.set, inTransaction);
valueSet[key] = childDataFacet.set(childValue);
}
}
}
Data facet instance method
Deletes component from view and scope, only in case it has Item facet on it
function Data$del() {
var inTransaction = getTransactionFlag(Data$del);
try {
var result = executeHook.call(this, 'del');
postTransactionFinished.call(this, inTransaction);
return result;
} catch (e) {
if (e != noHook) throw e;
}
var oldValue = this._value;
setTransactionFlag(this._del, inTransaction);
this._del();
// this message triggers onOwnDataChange, as well as actuall DOM change
// so the parent gets notified
var msg = { path: '', type: 'deleted', oldValue: oldValue };
setTransactionFlag(msg, inTransaction);
this.postMessage('', msg);
}
function Data$_del() {
var inTransaction = getTransactionFlag(Data$_del);
setTransactionFlag(this._set, inTransaction);
this._set();
}
Option name | Type | Description |
---|---|---|
deepGet | Boolean | true by default |
return | Object |
Data facet instance method
Get structured data from DOM hierarchy recursively
Returns DOM data
function Data$get(deepGet) {
try {
return executeHook.call(this, 'get', arguments);
} catch (e) {
if (e != noHook) throw e;
}
return this._get(deepGet);
}
function Data$_get(deepGet) {
if (deepGet === false) // a hack to enable getting shallow state
return;
var comp = this.owner
, scopeData;
if (comp.list) {
scopeData = [];
comp.list.each(function(listItem, index) {
scopeData[index] = listItem.data.get();
});
if (comp.container)
comp.container.scope._each(function(scopeItem, name) {
if (! comp.list.contains(scopeItem) && scopeItem.data)
scopeData[name] = scopeItem.data.get();
});
} else if (comp.container) {
scopeData = {};
comp.container.scope._each(function(scopeItem, name) {
if (scopeItem.data)
scopeData[name] = scopeItem.data.get();
});
} else
scopeData = this._getScalarValue();
this._value = scopeData;
return scopeData;
}
Option name | Type | Description |
---|---|---|
spliceIndex | Integer | index to delete/insert at |
spliceHowMany | Integer | number of items to delete |
arguments | List | optional items to insert |
return | Array |
Data facet instance method
Splices List items. Requires List facet to be present on component. Works in the same way as array splice.
Returns data retrieved from removed items
function Data$splice(spliceIndex, spliceHowMany) { //, ... arguments
var inTransaction = getTransactionFlag(Data$splice);
var result;
try {
result = executeHook.call(this, 'splice', arguments);
postTransactionFinished.call(this, inTransaction);
return result;
} catch (e) {
if (e != noHook) throw e;
}
setTransactionFlag(this._splice, inTransaction);
result = this._splice.apply(this, arguments);
if (!result) return;
var msg = { path: '', type: 'splice',
index: result.spliceIndex,
removed: result.removed,
addedCount: result.addedCount,
newValue: this._value };
setTransactionFlag(msg, inTransaction);
this.postMessage('', msg);
return result.removed;
}
var noHook = {};
function executeHook(methodName, args) {
var hook = this.config[methodName];
switch (typeof hook) {
case 'function':
return hook.apply(this.owner, args);
case 'string':
return this.owner[hook].apply(this.owner, args);
default:
throw noHook;
}
}
function Data$_splice(spliceIndex, spliceHowMany) { //, ... arguments
var inTransaction = getTransactionFlag(Data$_splice);
var listFacet = this.owner.list;
if (! listFacet)
return logger.warn('Data: cannot use splice method without List facet');
var removed = [];
var listLength = listFacet.count();
arguments[0] = spliceIndex =
modelUtils.normalizeSpliceIndex(spliceIndex, listLength);
if (spliceHowMany > 0 && listLength > 0) {
for (var i = spliceIndex; i < spliceIndex + spliceHowMany; i++) {
var item = listFacet.item(spliceIndex);
if (item) {
var itemData = item.data.get();
listFacet._removeItem(spliceIndex);
} else
logger.warn('Data: no item for index', i);
removed.push(itemData);
}
listFacet._updateDataPaths(spliceIndex, listFacet.count());
}
var added = [];
var argsLen = arguments.length
, addItems = argsLen > 2
, addedCount = argsLen - 2;
if (addItems) {
listFacet._addItems(addedCount, spliceIndex);
for (var i = 2, j = spliceIndex; i < argsLen; i++, j++) {
item = listFacet.item(j);
if (item) {
setTransactionFlag(item.data.set, inTransaction);
itemData = item.data.set(arguments[i]);
} else
logger.warn('Data: no item for index', j);
added.push(itemData);
}
// change paths of items that were added and items after them
listFacet._updateDataPaths(spliceIndex, listFacet.count());
}
// if (Array.isArray(this._value)) {
// _.prependArray(added, [spliceIndex, spliceHowMany]);
// Array.prototype.splice.apply(this._value, added);
// } else
this._value = this.get();
return {
spliceIndex: spliceIndex,
removed: removed,
addedCount: addItems ? addedCount : 0
};
}
function Data$len() {
try {
return executeHook.call(this, 'len');
} catch (e) {
if (e != noHook) throw e;
}
return this._len();
}
function Data$_len() {
if (this.owner.list) return this.owner.list.count();
else logger.error('Data: len called without list facet');
}
Option name | Type | Description |
---|---|---|
accessPath | String | data access path |
Data facet instance method
Returns data facet of a child component (by scopes) corresponding to the path
function Data$path(accessPath, createItem) {
// createItem = true; // this hack seems to be no longer needed...
if (! accessPath)
return this;
var parsedPath = pathUtils.parseAccessPath(accessPath);
var currentComponent = this.owner;
for (var i = 0, len = parsedPath.length; i < len; i++) {
var pathNode = parsedPath[i]
, nodeKey = pathUtils.getPathNodeKey(pathNode);
if (pathNode.syntax == 'array' && currentComponent.list) {
var itemComponent = currentComponent.list.item(nodeKey);
if (! itemComponent && createItem !== false) {
itemComponent = currentComponent.list._addItem(nodeKey);
itemComponent.data._path = pathNode.property;
}
currentComponent = itemComponent;
} else if (currentComponent.container)
currentComponent = currentComponent.container.scope[nodeKey];
var currentDataFacet = currentComponent && currentComponent.data;
if (! currentDataFacet)
break;
}
return currentDataFacet;
}
Data facet instance method
Returns path to access this data facet from parent (using path method)
function Data$getPath() {
return this._path;
}
Data facet instance method
Returns key to access the value related to this data facet on the value related to parent data facet.
If component has List facet, returns index
function Data$getKey() {
var path = this._path;
return path[0] == '['
? +path.slice(1, -1) // remove "[" and "]"
: path.slice(1); // remove leading "."
}
Option name | Type | Description |
---|---|---|
deepState, | Boolean | true by default |
return | Object |
Data facet instance method
Called by Component.prototype.getState
to get facet's state
Returns DOM data
function Data$getState(deepState) {
return { state: this.get(deepState) };
}
Option name | Type | Description |
---|---|---|
state | Object | data to set on facet's model |
Data facet instance method
Called by Component.prototype.setState
to set facet's state
Simply sets model data
function Data$setState(state) {
return this.set(state.state);
}