ciril
Version:
A javascript data binding library
507 lines (418 loc) • 14.6 kB
JavaScript
;
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Transformer = undefined;
exports.wrap = wrap;
exports.NodeConstructor = NodeConstructor;
var _flowgraph = require('./flowgraph');
var _flowgraph2 = _interopRequireDefault(_flowgraph);
var _uuid = require('uuid');
var _uuid2 = _interopRequireDefault(_uuid);
var _isEqual = require('lodash/lang/isEqual');
var _isEqual2 = _interopRequireDefault(_isEqual);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function conservativeMerge(target) {
for (var _len = arguments.length, sources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
sources[_key - 1] = arguments[_key];
}
sources.forEach(function (source) {
Object.getOwnPropertyNames(source).forEach(function (name) {
if (name === 'constructor' || target[name]) return;
Object.defineProperty(target, name, Object.getOwnPropertyDescriptor(source, name));
});
});
}
function createTransformer(fn) {
return new Transformer(fn);
}
function createSimpleWrapper(obj) {
var node = new FlowNode(obj);
return node;
}
function createWrapper(obj) {
if (!obj.hasOwnProperty('state')) return createSimpleWrapper(obj);
var node = new FlowNode(null, false);
// Add FlowNode fields
conservativeMerge(obj, node, FlowNode.prototype);
obj.register();
return obj;
}
/**
* Wrap the given object or function or value in a FlowNode.
* @param obj
* the object, function, or value to wrap
* @return
* the wrapper FlowNode
*/
function wrap(obj) {
if (obj instanceof FlowNode) return obj.register();
switch (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) {
case 'function':
return createTransformer(obj);
case 'object':
return createWrapper(obj);
default:
return createSimpleWrapper(obj);
}
}
/**
* Constructor for creating a FlowNode. Should be
* called if creating a mixin class with FlowNode.
*/
function NodeConstructor() {
var initialState = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
var register = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1];
Object.defineProperty(this, 'dirty', {
writable: true,
value: false
});
Object.defineProperty(this, 'uuid', {
value: _uuid2.default.v4()
});
Object.defineProperty(this, 'state', {
writable: true,
value: initialState
});
Object.defineProperty(this, 'changed', {
writable: true,
value: false
});
if (register) _flowgraph2.default.register(this);
}
/**
* A FlowNode stores some state and can have
* input and output nodes. When extending this class,
* the primary implementation details to consider
* are how input data is handled in setState(), and
* how object data is serialized in getState().
*
* The core subclasses of FlowNode are Transformer
* and DataNode.
*/
var FlowNode = function () {
/**
* On creation, a FlowNode registers itself
* with the FlowGraph.
*/
function FlowNode() {
var initialState = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
var register = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1];
_classCallCheck(this, FlowNode);
NodeConstructor.call(this, initialState, register);
}
/**
* Same as Ciril.register(this).
*/
_createClass(FlowNode, [{
key: 'register',
value: function register() {
_flowgraph2.default.register(this);
return this;
}
/**
* Same as Ciril.isRegistered(this).
*/
}, {
key: 'isRegistered',
value: function isRegistered() {
return _flowgraph2.default.isRegistered(this);
}
/**
* Bind this node to a transformer node.
* @param fn
* the transformer function
* @return
* the transformer node
*/
}, {
key: 'transform',
value: function transform(fn) {
var transformer = new Transformer(fn);
_flowgraph2.default.bind(this, transformer);
return transformer;
}
}, {
key: 'bind',
/**
* Same as Ciril.bind(this, ...destinations)
* @param destinations
* the destination nodes
* @return
* the last node in destinations
*/
value: function bind() {
for (var _len2 = arguments.length, destinations = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
destinations[_key2] = arguments[_key2];
}
return this.bindAll(destinations);
}
/**
* Same as Ciril.bindAll(this, ...destinations)
* @param destinations
* the destination nodes
* @return
* the last node in destinations
*/
}, {
key: 'bindAll',
value: function bindAll(destinations) {
var dests = destinations.map(function (e) {
return _flowgraph2.default.isRegistered(e) ? e : wrap(e);
});
_flowgraph2.default.bindAll(this, dests);
return dests[dests.length - 1];
}
/**
* Same as Ciril.synchronize(this, ..nodes).
* @param nodes
* the nodes to synchronize with
* @return
* this node
*/
}, {
key: 'synchronize',
value: function synchronize() {
for (var _len3 = arguments.length, nodes = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
nodes[_key3] = arguments[_key3];
}
nodes.reduce(function (p, c, i, a) {
return p.bind(c);
}, this);
return this;
}
/**
* Same as Ciril.synchronize(this, ..nodes).
* @param nodes
* the nodes to synchronize with
* @return
* this node
*/
}, {
key: 'synchronizeAll',
value: function synchronizeAll(nodes) {
nodes.reduce(function (p, c, i, a) {
return p.bind(c);
}, this);
return this;
}
/**
* Same as Ciril.unbind(this, ...destinations).
* @param destinations
* the destination nodes
*/
}, {
key: 'unbind',
value: function unbind() {
for (var _len4 = arguments.length, destinations = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
destinations[_key4] = arguments[_key4];
}
_flowgraph2.default.unbindAll(this, destinations);
}
/**
* Same as Ciril.unbindAll(this, destinations).
* @param destinations
*/
}, {
key: 'unbindAll',
value: function unbindAll(destinations) {
_flowgraph2.default.unbindAll(this, destinations);
}
/**
* Same as Ciril.update(this).
*/
}, {
key: 'update',
value: function update() {
return _flowgraph2.default.update(this);
}
/**
* Same as Ciril.updateSync(this).
*/
}, {
key: 'updateSync',
value: function updateSync() {
_flowgraph2.default.updateSync(this);
}
/**
* Ckecks whether this node has been
* marked dirty, which is if its state
* is out of date.
* @return
* true iff the node is dirty.
*/
}, {
key: 'isDirty',
value: function isDirty() {
return this.dirty;
}
/**
* Mark this node as dirty or clean.
* Be careful using this method, as it
* affects the update algorithm. It is
* meant to be used by Ciril for
* bookkeeping purposes.
* @param dirty
* true iff marking dirty
*/
}, {
key: 'markDirty',
value: function markDirty(dirty) {
this.dirty = dirty;
}
/**
* Same as cirl.bindInputs(this, ...inputs).
* @param inputs
* the input nodes
* @return
* this node
*/
}, {
key: 'bindInputs',
value: function bindInputs() {
for (var _len5 = arguments.length, inputs = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
inputs[_key5] = arguments[_key5];
}
_flowgraph2.default.bindAllInputs(this, inputs);
return this;
}
/**
* Same as Ciril.bindAllInputs(this, inputs).
* @param inputs
* the input nodes
* @return
* this node
*/
}, {
key: 'bindAllInputs',
value: function bindAllInputs(inputs) {
_flowgraph2.default.bindAllInputs(this, inputs);
return this;
}
/**
* Same as Ciril.remove(this).
*/
}, {
key: 'remove',
value: function remove() {
_flowgraph2.default.remove(this);
}
/**
* Get this node's state.
* @return
* the node's state
*/
}, {
key: 'getState',
value: function getState() {
return this.state;
}
/**
* Set this node's state.
* @param args
* the input state objects
* @return
* true iff state changed
*/
}, {
key: 'setState',
value: function setState() {
for (var _len6 = arguments.length, args = Array(_len6), _key6 = 0; _key6 < _len6; _key6++) {
args[_key6] = arguments[_key6];
}
if (!args.reduce(function (p, c, i, a) {
return p && (0, _isEqual2.default)(a[0], a[i]);
})) {
console.warn('setState(...): Inconsistent state ' + 'detected, make sure transforms are correct.\n ' + ('inputs: ' + args));
}
if (!(0, _isEqual2.default)(this.state, args[0])) {
this.state = args[0];
this.changed = true;
return true;
}
this.changed = false;
return false;
}
/**
* Called before node is removed.
* Should be overriden.
*/
}, {
key: 'onRemove',
value: function onRemove() {}
/**
* Called before an input is unbound.
* Should be overriden.
*/
}, {
key: 'onUnbindInput',
value: function onUnbindInput(input) {}
/**
* Called after an input is bound.
* Should be overriden.
*/
}, {
key: 'onBindInput',
value: function onBindInput(input) {}
/**
* Called before an output is unbound.
* Should be overriden.
*/
}, {
key: 'onUnbind',
value: function onUnbind(node) {}
/**
* Called after an output is bound.
* Should be overriden.
*/
}, {
key: 'onBind',
value: function onBind(node) {}
}]);
return FlowNode;
}();
/**
* A Transformer represents a functional transform
* from an input state to an output state. Its purpose
* in the FlowGraph is to compute values from input
* data.
*/
exports.default = FlowNode;
var Transformer = exports.Transformer = function (_FlowNode) {
_inherits(Transformer, _FlowNode);
function Transformer(fn) {
_classCallCheck(this, Transformer);
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Transformer).call(this));
_this.fn = fn;
return _this;
}
/**
* Compute new state based on input state.
* @param args
* the input state objects
* @return
* true iff state changed
*/
_createClass(Transformer, [{
key: 'setState',
value: function setState() {
for (var _len7 = arguments.length, args = Array(_len7), _key7 = 0; _key7 < _len7; _key7++) {
args[_key7] = arguments[_key7];
}
var state = this.fn.apply(this, args);
if (!(0, _isEqual2.default)(this.state, state)) {
this.state = state;
this.changed = true;
return true;
}
this.changed = false;
return false;
}
}]);
return Transformer;
}(FlowNode);