This repository has been archived by the owner on Aug 23, 2018. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
328 additions
and
9 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
node_modules/ | ||
lib-cov/ | ||
*.sublime-* | ||
.DS_Store |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
lib-cov/ | ||
*.sublime-* | ||
.DS_Store | ||
test/ | ||
Makefile | ||
*.md | ||
.jshintrc | ||
.git* |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,17 @@ | ||
# Project Name | ||
# Advice Functional Mixins | ||
|
||
Project description | ||
This tiny library implements the advice functional mixins presented in Twitter's [Angus Croll][croll]'s presentation [How we learned to stop worrying and love Javascript][slides]. | ||
|
||
## Installation | ||
|
||
`npm install advice` | ||
|
||
## Features | ||
|
||
Based on Croll's [gist][], the advice library augments a target object with `after`, `before`, and `around`. I added 2 more mixins well suited for node's callback style, `hijackBefore` and `hijackAfter`. | ||
|
||
|
||
|
||
[croll]: https://github.com/angus-c/ | ||
[slides]: https://speakerdeck.com/u/anguscroll/p/how-we-learned-to-stop-worrying-and-love-javascript | ||
[gist]: https://gist.github.com/2864853 |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module.exports = require('./lib/advice') |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
/*globals define*/ | ||
|
||
/* | ||
** | ||
** Copyright Martin Angers 2012, the MIT License | ||
** | ||
** Based on Twitter's presentation: | ||
** https://speakerdeck.com/u/anguscroll/p/how-we-learned-to-stop-worrying-and-love-javascript | ||
** | ||
** And this gist by Angus Croll | ||
** https://gist.github.com/2864853 | ||
** | ||
** Usage: | ||
** var withAdvice = require('./advice') | ||
** withAdvice.call(targetObject) | ||
** | ||
** ...then... | ||
** targetObject.before('someMethod', function() { | ||
** // Something to do before someMethod | ||
** }) | ||
** | ||
** ...and finally... | ||
** targetObject.someMethod() // Calls the method inserted using `before`, then `someMethod()` | ||
*/ | ||
;(function(global) { | ||
|
||
"use strict"; | ||
|
||
// Define the bind method (the AngularJS' bind implementation is used, see: | ||
// https://github.com/angular/angular.js/blob/master/src/Angular.js#L664 ) | ||
function bindFn(self, fn) { | ||
var slice = Array.prototype.slice, | ||
curryArgs = arguments.length > 2 ? slice(arguments, 2) : [] | ||
|
||
if (typeof fn == 'function' && !(fn instanceof RegExp)) { | ||
return curryArgs.length ? function() { | ||
return arguments.length ? | ||
fn.apply(self, curryArgs.concat(slice.call(arguments, 0))) : | ||
fn.apply(self, curryArgs) | ||
} | ||
: function() { | ||
return arguments.length ? | ||
fn.apply(self, arguments) : | ||
fn.call(self) | ||
} | ||
} else { | ||
// in IE, native methods are not functions so they cannot be bound (note: they don't need to be) | ||
return fn | ||
} | ||
} | ||
|
||
// fn is a supporting object that implements around, before, after, hijackBefore and hijackAfter | ||
var fn = { | ||
around: function(base, wrapped) { | ||
return function() { | ||
var args = Array.prototype.slice.call(arguments) | ||
|
||
// Around calls the new method, passing the original method as the first argument. | ||
// It is up to the new method to decide when to call the original. | ||
return wrapped.apply(this, [bindFn(this, base)].concat(args)) | ||
} | ||
}, | ||
before: function(base, before) { | ||
// Before uses "around" and calls the original method AFTER the new method, returning | ||
// the result of the original method. | ||
return fn.around(base, function() { | ||
var args = Array.prototype.slice.call(arguments), | ||
orig = args.shift() | ||
|
||
before.apply(this, args) | ||
return (orig).apply(this, args) | ||
}) | ||
}, | ||
after: function(base, after) { | ||
// After uses "around" and calls the original method BEFORE the new method, returning | ||
// the result of the original method. | ||
return fn.around(base, function() { | ||
var args = Array.prototype.slice.call(arguments), | ||
orig = args.shift(), | ||
res = orig.apply(this, args) | ||
|
||
after.apply(this, args) | ||
return res | ||
}) | ||
}, | ||
hijackBefore: function(base, hijack) { | ||
// Hijcak before calls the hijack method, intercepts the callback, and calls the | ||
// base method. It basically chains the methods in this order: hijack->base->originalCb. | ||
// If the hijack method returns an error as first argument, the base method is skipped | ||
// and the error is sent to the original callback. | ||
return function() { | ||
var args = Array.prototype.slice.call(arguments), | ||
origCb = args.pop(), | ||
self = this | ||
|
||
hijack.apply(this, args.concat(function(er) { | ||
if (er) { | ||
origCb(er) | ||
} else { | ||
base.apply(self, args.concat(origCb)) | ||
} | ||
})) | ||
} | ||
}, | ||
hijackAfter: function(base, hijack) { | ||
// Hijcak after calls the base method, hijacks the callback parameter by passing a private method | ||
// as callback, then calls the hijack method with the arguments provided by the original, adding | ||
// the original callback as last argument. It basically chains the methods in this order: | ||
// base->privateCb->hijack->originalCb. | ||
return function() { | ||
var args = Array.prototype.slice.call(arguments), | ||
origCb = args.pop(), | ||
res, | ||
self = this | ||
|
||
res = base.apply(this, args.concat(function() { | ||
var argscb = Array.prototype.slice.call(arguments) | ||
// On callback, call the hijack method, passing the original callback as last argument | ||
hijack.apply(self, argscb.concat(origCb)) | ||
})) | ||
return res | ||
} | ||
} | ||
} | ||
|
||
// mixin augments target object with around, before and after methods | ||
// method is the base method, advice is the augmenting function | ||
var advice = function() { | ||
var mixins = ['before', 'after', 'around', 'hijackAfter', 'hijackBefore'], | ||
applyMixin = function(m) { | ||
this[m] = function(method, advice) { | ||
if (typeof this[method] === 'function') { | ||
return this[method] = fn[m](this[method], advice) | ||
} else { | ||
return this[method] = advice | ||
} | ||
} | ||
} | ||
|
||
// Avoid using forEach since it can be used in browser | ||
for (var i = 0; i < mixins.length; i++) { | ||
applyMixin.call(this, mixins[i]) | ||
} | ||
} | ||
|
||
// Module exports | ||
if (module && module.exports) { | ||
module.exports = advice | ||
} else { | ||
global['advice'] = advice | ||
} | ||
|
||
// AMD registration | ||
if (typeof define === 'function' && define.amd) { | ||
define('advice', function() { | ||
return advice | ||
}) | ||
} | ||
|
||
}(this)) |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
var expect = require('expect.js'), | ||
sinon = require('sinon'), | ||
sut = require(process.env.ADV_COV ? '../lib-cov/advice' : '../lib/advice') | ||
|
||
describe('advice', function() { | ||
it('should export a function', function() { | ||
expect(sut).to.be.a('function') | ||
}) | ||
|
||
it('should augment an object with the mixins', function() { | ||
var obj = { | ||
fn: function() {} | ||
} | ||
|
||
sut.call(obj) | ||
expect(obj).to.only.have.keys('before', 'after', 'around', 'hijackBefore', 'hijackAfter', 'fn') | ||
}) | ||
|
||
it('should leave the object working as before', function() { | ||
var obj = { | ||
fn: function() {return 'test'} | ||
} | ||
|
||
sut.call(obj) | ||
expect(obj.fn()).to.be('test') | ||
}) | ||
|
||
describe('.before()', function() { | ||
it('should be a function on the augmented object', function() { | ||
var obj = {} | ||
sut.call(obj) | ||
expect(obj.before).to.be.a('function') | ||
}) | ||
|
||
it('should call the before function as well as the base target', function() { | ||
var num = 0, | ||
obj = { | ||
fn: function(val) { | ||
num += val * 2 | ||
return num | ||
} | ||
}, | ||
beforeFn = function(val) { | ||
num += val | ||
return num | ||
}, | ||
spy = sinon.spy(beforeFn) | ||
|
||
sut.call(obj) | ||
obj.before('fn', spy) | ||
|
||
expect(obj.fn(2)).to.be(6) | ||
expect(spy.calledOnce).to.be(true) | ||
}) | ||
|
||
it('should call the before function before the base target', function() { | ||
var num = 0, | ||
obj = { | ||
fn: function(val) { | ||
num += val * 2 | ||
return num | ||
} | ||
}, | ||
beforeFn = function(val) { | ||
num += val | ||
return num | ||
}, | ||
spyBase = sinon.spy(obj, 'fn'), | ||
spyBefore = sinon.spy(beforeFn) | ||
|
||
sut.call(obj) | ||
obj.before('fn', spyBefore) | ||
obj.fn(4) | ||
|
||
expect(spyBase.calledOnce).to.be(true) | ||
expect(spyBefore.calledOnce).to.be(true) | ||
expect(spyBefore.calledBefore(spyBase)).to.be(true) | ||
}) | ||
}) | ||
|
||
describe('.after()', function() { | ||
it('should be a function on the augmented object', function() { | ||
var obj = {} | ||
sut.call(obj) | ||
expect(obj.after).to.be.a('function') | ||
}) | ||
|
||
it('should call the after function as well as the base target', function() { | ||
var num = 0, | ||
obj = { | ||
fn: function(val) { | ||
num += val * 2 | ||
console.log(num) | ||
return num | ||
} | ||
}, | ||
afterFn = function(val) { | ||
num += val | ||
console.log(num) | ||
return num | ||
}, | ||
spy = sinon.spy(afterFn) | ||
|
||
sut.call(obj) | ||
obj.after('fn', spy) | ||
obj.fn(5) | ||
|
||
expect(num).to.be(15) | ||
expect(spy.calledOnce).to.be(true) | ||
}) | ||
|
||
it('should call the after function after the base target', function() { | ||
var num = 0, | ||
obj = { | ||
fn: function(val) { | ||
num += val * 2 | ||
return num | ||
} | ||
}, | ||
afterFn = function(val) { | ||
num += val | ||
return num | ||
}, | ||
spyBase = sinon.spy(obj, 'fn'), | ||
spyAfter = sinon.spy(afterFn) | ||
|
||
sut.call(obj) | ||
obj.after('fn', spyAfter) | ||
obj.fn(4) | ||
|
||
expect(spyBase.calledOnce).to.be(true) | ||
expect(spyAfter.calledOnce).to.be(true) | ||
expect(spyAfter.calledAfter(spyBase)).to.be(true) | ||
}) | ||
}) | ||
}) |