Skip to content

Instantly share code, notes, and snippets.

@lsm
Created Apr 15, 2015
Embed
What would you like to do?
'use strict';
module.exports = {
re: function(fnName, context) {
var fn;
context = context || this;
if ('function' === typeof fnName) {
fn = fnName;
fnName = fn.toString().match(/^\s*function\s*(\S*)\s*\(/)[1];
} else {
fn = context[fnName];
}
if (typeof fn !== 'function') {
throw new Error('First argument must be a function or method name of a function in context.');
}
if (!fnName) {
throw new Error('Please give your function a name.');
}
context.__reactors__ = context.__reactors__ || {};
var reactors = context.__reactors__;
var re = reactors[fnName];
if (re === undefined) {
// variable for holding actors
var _actors = {};
// generate initiator function
re = function() {
var v = fn.apply(context, arguments);
Object.keys(_actors).forEach(function(actorName) {
var actor = _actors[actorName];
actor._fn.apply(actor._context, actor._arguments);
});
return v;
};
// replace original function with initiator
context[fnName] = re;
// allow call original function without reactive behaviours
re.silently = function() {
return fn.apply(context, arguments);
};
// generate actor function which will be reran upon calling initiator function
re.act = function(actFn, actContext) {
var actFnName;
if (typeof actFn === 'string') {
actFnName = actFn;
actFn = actContext[actFn];
}
if (typeof actFn !== 'function') {
throw new Error('First argument should either be function or name of the function in context.');
}
var actor = _actors[actFn];
if (actor === undefined) {
actor = {};
var _fn = function() {
var _context = actContext || this;
actor._context = _context;
actor._arguments = arguments;
return actFn.apply(_context, arguments);
};
// keep the origin actor function
_fn.actFn = actFn;
actor._fn = _fn;
actor.re = re;
actor.act = re.act;
actor.stop = function() {
re.stop(actFn, actContext);
return re;
};
_actors[actFn] = actor;
if (actContext && actContext[actFnName] === actFn) {
// replace original act fn for this case
actContext[actFnName] = _fn;
// keep the name so we can restore the original function
actor._name = actFnName;
}
}
return actor;
};
// remove actor
re.stop = function(actFn, actContext) {
var actFnName;
if (typeof actFn === 'string') {
actFnName = actFn;
actFn = actContext[actFn];
}
if (typeof actFn !== 'function') {
throw new Error('First argument should either be function or name of the function in context.');
}
actFn = actFn.actFn || actFn;
var actor = _actors[actFn];
if (actor._context) {
// restore original actor function
actor._context[actor._name] = actFn;
}
// remove this actor from the actors list
delete _actors[actFn];
};
// restore initiator function to its original implementation
re.restore = function() {
Object.keys(_actors).forEach(function(actorName) {
var actor = _actors[actorName];
actor.stop();
});
context[fnName] = fn;
// cleanup cached reactor
delete reactors[fnName];
};
// keep the generated reactive function to avoid recreation
reactors[fnName] = re;
}
return re;
}
};
/* global describe, it */
var re = require('../react').re;
require('should');
describe('re-act', function() {
function Model() {
this.data = {};
}
Model.prototype = {
set: function(key, value) {
this.data[key] = value;
},
get: function get(key) {
return this.data[key];
},
re: re
};
function View(model) {
this.model = model;
this.result = '';
}
View.prototype = {
render: function(data) {
this.result = [data, this.model.get('email')].join(' | ');
}
};
var counter = 0;
function count() {
counter++;
}
var model = new Model();
var view = new View(model);
describe('#re', function() {
it('should convert a normal function to an initiator and keep its original feature', function() {
var email = '11@example.com';
var reSet = model.re('set');
reSet.should.be.Function;
model.set.should.equal(reSet);
model.set('email', email);
model.get('email').should.equal(email);
var _get = model.get;
function get(key) {
return _get.call(this, key);
}
var reactGet = re(get, model);
reactGet.should.be.Function;
model.get.should.be.reactGet;
reactGet('email').should.equal(email);
});
describe('#act', function() {
var actorCount;
it('should convert normal function to actor function and rerun it upon calling the initiator', function() {
actorCount = model.set.act(count);
actorCount.re.should.equal(model.set);
actorCount.act.should.equal(model.set.act);
counter.should.equal(0);
model.set('email', '22@example.com');
counter.should.equal(1);
model.get('email').should.equal('22@example.com');
});
it('should be chainable', function() {
actorCount.act('render', view).act.should.be.Function;
});
it('should keep the most recent arguments when rerun is triggered', function() {
view.render('data1');
view.result.should.equal('data1 | 22@example.com');
model.set('email', '33@example.com');
view.result.should.equal('data1 | 33@example.com');
counter.should.equal(2);
view.render('data2');
model.set('email', '44@example.com');
view.result.should.equal('data2 | 44@example.com');
counter.should.equal(3);
});
describe('#stop', function() {
it('should only stop rerun the stopped actor function', function() {
actorCount.stop.should.be.Function;
actorCount.stop();
model.set('email', '55@example.com');
counter.should.equal(3);
model.get('email').should.equal('55@example.com');
view.result.should.equal('data2 | 55@example.com');
});
});
});
describe('#silently', function() {
it('should call the initiator without trigger rerun of any actors', function() {
model.set.silently('email', '66@example.com');
counter.should.equal(3);
model.get('email').should.equal('66@example.com');
view.result.should.equal('data2 | 55@example.com');
});
});
describe('#restore', function() {
it('should stop all actor functions and restore any converted functions to their original implementation', function() {
model.set.restore();
model.set.should.equal(Model.prototype.set);
view.render.should.equal(View.prototype.render);
model.set('email', '77@example.com');
counter.should.equal(3);
model.get('email').should.equal('77@example.com');
view.result.should.equal('data2 | 55@example.com');
});
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment