Utility function to process "changedata" messages emitted by Connector object.
module.exports = changeDataHandler;
_.extend(changeDataHandler, {
setTransactionFlag: setTransactionFlag,
getTransactionFlag: getTransactionFlag,
passTransactionFlag: passTransactionFlag,
postTransactionFinished: postTransactionFinished
});
Posts message on this to indicate the end of transaction unless inChangeTransaction
is true
.
function postTransactionFinished() {
this.postMessageSync('datachanges', { transaction: false, changes: [] });
}
Option name | Type | Description |
---|---|---|
msg | String | should be "changedata" here |
data | Object | batch of data change desciption objects |
callback | Function | callback to call before and after the data is processed |
subscriber to "changedata" event emitted by Connector object to enable reactive connections
Used by Data facet, Model and ModelPath. Can be used by any object that implements get/set/del/splice api and sets data deeply to the whole tree.
Object should call changeDataHandler.initialize.call(this)
in its constructor.
TODO: optimize messages list to avoid setting duplicate values down the tree
function changeDataHandler(message, data, callback) {
processChanges.call(this, data.changes, callback);
}
// map of message types to methods
var CHANGE_TYPE_TO_METHOD_MAP = {
'added': 'set',
'changed': 'set',
'deleted': 'del',
'removed': 'del'
};
Option name | Type | Description |
---|---|---|
callback | Function | optional callback that is called with |
Processes queued "changedata" messages.
Posts "changestarted" and "changecompleted" messages and calls callback
function processChanges(transaction, callback) {
notify.call(this, callback, false);
processTransaction.call(this,
prepareTransaction(
validateTransaction(transaction)));
notify.call(this, callback, true);
}
function notify(callback, changeFinished) {
callback && callback(null, changeFinished);
this.postMessage(changeFinished ? 'changecompleted' : 'changestarted');
}
Option name | Type | Description |
---|---|---|
transaction | Array | transaction of data changes |
return | Array |
Checks that all messages from the transaction come from the same source.
Hack: reverses the transaction if it comes from the Data facet
Returns the reference to the transaction (for chaining)
function validateTransaction(transaction) {
var source = transaction[0].source
, sameSource = true;
if (transaction.length > 1) {
for (var i = 1, len = transaction.length; i < len; i++)
if (transaction[i].source != source) {
logger.error('changedata: changes from different sources in the same transaction, sources:', transaction[i].source.name, source.name);
sameSource = false;
source = transaction[i].source;
}
}
return transaction;
}
function prepareTransaction(transaction) {
var todo = []
, pathsToSplice = []
, pathsToChange = []
, hadSplice
, exitLoop = {};
try { transaction.forEach(checkChange); }
catch (e) { if (e != exitLoop) throw e; }
return todo;
function checkChange(data) {
(data.type == 'splice' ? checkSplice : checkMethod)(data);
}
function checkSplice(data) {
var parsedPath = pathUtils.parseAccessPath(data.path);
var parentPathChanged = pathsToChange.some(function(parentPath) {
if (parsedPath.length < parentPath.length) return;
return _pathIsParentOf(parentPath, parsedPath);
});
if (parentPathChanged) return;
todo.push(data);
if (! config.debug) throw exitLoop;
pathsToSplice.push(parsedPath);
hadSplice = true;
}
function checkMethod(data) {
var parsedPath = pathUtils.parseAccessPath(data.path);
var parentPathSpliced = pathsToSplice && pathsToSplice.some(function(parentPath) {
if (parsedPath.length <= parentPath.length
|| parsedPath[parentPath.length].syntax != 'array') return;
return _pathIsParentOf(parentPath, parsedPath);
});
if (parentPathSpliced) return;
if (hadSplice) logger.error('changedata: child change is executed after splice; probably data source did not emit message with data.type=="finished"');
var parentPathChanged = pathsToChange.some(function(parentPath) {
if (parsedPath.length <= parentPath.length) return;
return _pathIsParentOf(parentPath, parsedPath);
});
if (parentPathChanged) return;
pathsToChange.push(parsedPath);
todo.push(data);
}
function _pathIsParentOf(parentPath, childPath) {
return parentPath.every(function(pathNode, index) {
return pathNode.property == childPath[index].property;
});
}
}
function processTransaction(transaction) {
transaction.forEach(processChange, this);
postTransactionFinished.call(this, false);
function processChange(data) {
var modelPath = this.path(data.path, data.type != 'removed' && data.type != 'deleted');
if (! modelPath) return;
(data.type == 'splice' ? executeSplice : executeMethod)(modelPath, data);
}
}
function executeSplice(modelPath, data) {
var index = data.index
, howMany = data.removed.length
, spliceArgs = [index, howMany];
spliceArgs = spliceArgs.concat(data.newValue.slice(index, index + data.addedCount));
setTransactionFlag(modelPath.splice, true);
modelPath.splice.apply(modelPath, spliceArgs);
}
function executeMethod(modelPath, data) {
var methodName = CHANGE_TYPE_TO_METHOD_MAP[data.type];
if (methodName) {
setTransactionFlag(modelPath[methodName], true);
modelPath[methodName](data.newValue);
} else
logger.error('unknown data change type');
}