Created
April 15, 2015 07:26
-
-
Save lsm/6f2e7653e862b153d17c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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; | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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 = '[email protected]'; | |
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', '[email protected]'); | |
counter.should.equal(1); | |
model.get('email').should.equal('[email protected]'); | |
}); | |
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 | [email protected]'); | |
model.set('email', '[email protected]'); | |
view.result.should.equal('data1 | [email protected]'); | |
counter.should.equal(2); | |
view.render('data2'); | |
model.set('email', '[email protected]'); | |
view.result.should.equal('data2 | [email protected]'); | |
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', '[email protected]'); | |
counter.should.equal(3); | |
model.get('email').should.equal('[email protected]'); | |
view.result.should.equal('data2 | [email protected]'); | |
}); | |
}); | |
}); | |
describe('#silently', function() { | |
it('should call the initiator without trigger rerun of any actors', function() { | |
model.set.silently('email', '[email protected]'); | |
counter.should.equal(3); | |
model.get('email').should.equal('[email protected]'); | |
view.result.should.equal('data2 | [email protected]'); | |
}); | |
}); | |
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', '[email protected]'); | |
counter.should.equal(3); | |
model.get('email').should.equal('[email protected]'); | |
view.result.should.equal('data2 | [email protected]'); | |
}); | |
}); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment