cerebral-model-immutable
Version:
Immutable Model layer for Cerebral
214 lines (192 loc) • 5.78 kB
JavaScript
var Baobab = require('baobab');
function deepmerge(target, src) {
var array = Array.isArray(src);
var dst = array && [] || {};
if (array) {
target = target || [];
dst = src.slice();
src.forEach(function(e, i) {
if (typeof dst[i] === 'undefined') {
dst[i] = e;
} else if (typeof e === 'object') {
dst[i] = deepmerge(target[i], e);
}
});
} else {
if (target && typeof target === 'object') {
Object.keys(target).forEach(function (key) {
dst[key] = target[key];
})
}
Object.keys(src).forEach(function (key) {
if (typeof src[key] !== 'object' || !src[key]) {
dst[key] = src[key];
}
else {
if (!target[key]) {
dst[key] = src[key];
} else {
dst[key] = deepmerge(target[key], src[key]);
}
}
});
}
return dst;
};
function createForcedChange(state, changes) {
function traverse(currentPath, path, currentChangePath) {
if (
!Array.isArray(currentPath) &&
typeof currentPath === 'object' &&
currentPath !== null &&
Object.keys(currentPath).length
) {
Object.keys(currentPath).forEach(function (key) {
path.push(key)
currentChangePath[key] = traverse(currentPath[key], path, {})
path.pop()
})
return currentChangePath
}
return true
}
traverse(state, [], changes)
return changes
}
var Model = function (initialState, options) {
options = options || {};
var tree = new Baobab(initialState, options);
function update(changes, path) {
path.reduce(function (changes, key, index) {
if (index === path.length - 1 && !changes[key]) {
changes[key] = true
} else if (changes[key] === true) {
changes[key] = {}
} else if (!changes[key]) {
changes[key] = {}
}
return changes[key];
}, changes);
return changes;
}
var model = function (controller) {
controller.on('modulesLoaded', function () {
initialState = tree.toJSON()
})
function onUpdate(event) {
var changes = event.data.paths.reduce(update, {})
controller.emit('flush', changes);
}
tree.on('update', onUpdate);
controller.on('change', function () {
tree.commit();
});
controller.on('reset', function () {
tree.set(initialState)
tree.commit();
var forcedChanges = createForcedChange(initialState, {})
controller.emit('flush', forcedChanges)
});
controller.on('seek', function (seek, recording) {
recording.initialState.forEach(function (state) {
tree.set(state.path, state.value)
});
tree.commit();
var forcedChanges = createForcedChange(tree.get(), {})
controller.emit('flush', forcedChanges)
});
return {
tree: tree,
logModel: function () {
return tree.get();
},
accessors: {
get: function (path) {
return tree.get(path);
},
toJSON: function () {
return tree.toJSON();
},
toJS: function (path) {
return tree.get(path);
},
serialize: function (path) {
return tree.serialize(path);
},
export: function () {
return tree.serialize();
},
keys: function (path) {
return Object.keys(tree.get(path));
},
findWhere: function (path, obj) {
var keysCount = Object.keys(obj).length;
return tree.get(path).filter(function (item) {
return Object.keys(item).filter(function (key) {
return key in obj && obj[key] === item[key];
}).length === keysCount;
}).pop();
}
},
mutators: {
set: function (path, value) {
tree.set(path, value);
},
import: function (newState) {
var newState = deepmerge(initialState, newState);
tree.set(newState);
},
unset: function (path, keys) {
if (keys) {
keys.forEach(function (key) {
tree.unset(path.concat(key));
})
} else {
tree.unset(path);
}
},
push: function (path, value) {
tree.push(path, value);
},
splice: function () {
var args = [].slice.call(arguments);
tree.splice.call(tree, args.shift(), args);
},
merge: function (path, value) {
tree.merge(path, value);
},
concat: function (path, value) {
tree.apply(path, function (existingValue) {
return existingValue.concat(value);
});
},
pop: function (path) {
var val;
tree.apply(path, function (existingValue) {
var copy = existingValue.slice();
val = copy.pop();
return copy;
});
return val;
},
shift: function (path) {
var val;
tree.apply(path, function (existingValue) {
var copy = existingValue.slice();
val = copy.shift();
return copy;
});
return val;
},
unshift: function (path, value) {
tree.unshift(path, value);
}
}
};
};
model.tree = tree;
return model;
};
Model.monkey = Baobab.monkey;
Model.dynamicNode = Baobab.dynamicNode;
module.exports = Model;