├── .bowerrc ├── .gitignore ├── .jshintignore ├── .jshintrc ├── .npmignore ├── README.md ├── bower.json ├── component.json ├── gulpfile.js ├── header.js ├── index.html ├── lib ├── conduit.js └── conduit.min.js ├── package.json ├── spec ├── conduit.spec.js └── index.html └── src └── conduit.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.suo 3 | *.csproj.user 4 | bin 5 | obj 6 | *.pdb 7 | _ReSharper* 8 | *.ReSharper.user 9 | *.ReSharper 10 | desktop.ini 11 | .eprj 12 | .idea 13 | *node_modules 14 | *npm-debug* 15 | report/ 16 | bower/ -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | ext/ 3 | report/ -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // http://www.jshint.com/docs/ 3 | // Based on node-jshint@0.9.1 4 | 5 | // ENFORCING OPTIONS 6 | // These options tell JSHint to be more strict towards your code. Use them if 7 | // you want to allow only a safe subset of JavaScript—very useful when your 8 | // codebase is shared with a big number of developers with different skill 9 | // levels. 10 | 11 | "bitwise" : true, //prohibits the use of bitwise operators such as ^ (XOR), | (OR) and others 12 | "camelcase" : true, //force all variable names to use either camelCase style or UPPER_CASE with underscores 13 | "curly" : true, //requires you to always put curly braces around blocks in loops and conditionals 14 | "eqeqeq" : true, //prohibits the use of == and != in favor of === and !== 15 | "forin" : true, //requires all `for in` loops to filter object's items with `hasOwnProperty()` 16 | "immed" : true, //prohibits the use of immediate function invocations without wrapping them in parentheses 17 | "indent" : 4, //enforces specific tab width 18 | "latedef" : true, //prohibits the use of a variable before it was defined 19 | "newcap" : true, //requires you to capitalize names of constructor functions 20 | "noarg" : true, //prohibits the use of `arguments.caller` and `arguments.callee` 21 | "noempty" : true, //warns when you have an empty block in your code 22 | "nonew" : true, //prohibits the use of constructor functions for side-effects 23 | "plusplus" : false, //prohibits the use of unary increment and decrement operators 24 | "quotmark" : true, //enforces the consistency of quotation marks used throughout your code 25 | "regexp" : false, //prohibits the use of unsafe `.` in regular expressions 26 | "undef" : true, //prohibits the use of explicitly undeclared variables 27 | "unused" : true, //warns when you define and never use your variables 28 | "strict" : false, //requires all functions to run in EcmaScript 5's strict mode 29 | "trailing" : false, //makes it an error to leave a trailing whitespace in your code 30 | // "maxparams": 0, //set the max number of formal parameters allowed per function, 31 | // "maxdepth": 0, //control how nested do you want your blocks to be 32 | // "maxstatements": 0, //set the max number of statements allowed per function 33 | // "maxcomplexity": 0, //control cyclomatic complexity throughout your code 34 | // "maxlen": 80, //set the maximum length of a line 35 | 36 | // RELAXING OPTIONS 37 | // These options allow you to suppress certain types of warnings. Use them 38 | // only if you are absolutely positive that you know what you are doing. 39 | 40 | "asi" : false, //suppresses warnings about missing semicolons 41 | "boss" : true, //suppresses warnings about the use of assignments in cases where comparisons are expected 42 | "debug" : false, //suppresses warnings about the debugger statements in your code 43 | "eqnull" : false, //suppresses warnings about == null comparisons 44 | "es5" : false, //your code uses ECMAScript 5 specific features such as getters and setters 45 | "esnext" : false, //your code uses ES.next specific features such as const 46 | "evil" : false, //suppresses warnings about the use of eval 47 | "expr" : false, //suppresses warnings about the use of expressions where normally you would expect to see assignments or function calls 48 | "funcscope" : false, //suppresses warnings about declaring variables inside of control structures while accessing them later from the outside 49 | "globalstrict" : false, //suppresses warnings about the use of global strict mode 50 | "iterator" : false, //suppresses warnings about the `__iterator__` property 51 | "lastsemic" : false, //suppresses warnings about missing semicolons, but only when the semicolon is omitted for the last statement in a one-line block 52 | "laxbreak" : false, //suppresses most of the warnings about possibly unsafe line breakings in your code 53 | "laxcomma" : false, //suppresses warnings about comma-first coding style 54 | "loopfunc" : false, //suppresses warnings about functions inside of loops 55 | "multistr" : false, //suppresses warnings about multi-line strings 56 | "onecase" : false, //suppresses warnings about switches with just one case 57 | "proto" : false, //suppresses warnings about the `__proto__` property 58 | "regexdash" : true, //suppresses warnings about unescaped `-` in the end of regular expressions 59 | "scripturl" : false, //suppresses warnings about the use of script-targeted URLs—such as `javascript:...` 60 | "smarttabs" : true, //suppresses warnings about mixed tabs and spaces when the latter are used for alignmnent only 61 | "shadow" : false, //suppresses warnings about variable shadowing 62 | "sub" : false, //suppresses warnings about using `[]` notation when it can be expressed in dot notation 63 | "supernew" : false, //suppresses warnings about "weird" constructions like `new function () { ... }` and `new Object;` 64 | "validthis" : false, //suppresses warnings about possible strict violations when the code is running in strict mode and you use `this` in a non-constructor function 65 | 66 | // ENVIRONMENTS 67 | // These options pre-define global variables that are exposed by popular 68 | // JavaScript libraries and runtime environments—such as browser or node.js. 69 | // Essentially they are shortcuts for explicit declarations like 70 | // /*global $:false, jQuery:false */ 71 | 72 | "browser" : true, //defines globals exposed by modern browsers 73 | "couch" : false, //defines globals exposed by CouchDB 74 | "devel" : true, //defines globals that are usually used for logging poor-man's debugging: `console`, `alert`, etc. 75 | "dojo" : false, //defines globals exposed by the Dojo Toolkit 76 | "jquery" : false, //defines globals exposed by the jQuery JavaScript library 77 | "mootools" : false, //defines globals exposed by the MooTools JavaScript framework 78 | "node" : true, //defines globals available when your code is running inside of the Node runtime environment 79 | "nonstandard" : true, //defines non-standard but widely adopted globals such as `escape` and `unescape` 80 | "prototypejs" : false, //defines globals exposed by the Prototype JavaScript framework 81 | "rhino" : false, //defines globals available when your code is running inside of the Rhino runtime environment 82 | "worker" : true, //defines globals available when your code is running inside of a Web Worker 83 | "wsh" : false, //defines globals available when your code is running as a script for the Windows Script Host 84 | "yui" : false, //defines globals exposed by the YUI JavaScript framework 85 | 86 | "predef" : [ // Custom globals. 87 | "_", 88 | "define" 89 | ], 90 | 91 | // LEGACY 92 | // These options are legacy from JSLint. Aside from bug fixes they will not 93 | // be improved in any way and might be removed at any point. 94 | 95 | "nomen" : false, //disallows the use of dangling `_` in variables 96 | "onevar" : false, //allows only one `var` statement per function 97 | "passfail" : false, //makes JSHint stop on the first error or warning 98 | "white" : false //make JSHint check your source code against Douglas Crockford's JavaScript coding style 99 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | example 2 | node_modules* 3 | spec 4 | src 5 | ext 6 | lib/standard 7 | lib/amd 8 | diags 9 | .gitignore 10 | .idea 11 | header.js 12 | component.json 13 | .npmignore 14 | classic-resolver* 15 | bower* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #ConduitJS v0.3.3 2 | 3 | ConduitJS can be used to *intercept* your methods and give them a pre- and post-invocation pipeline. You can think of it as targeted [AOP](http://en.wikipedia.org/wiki/Aspect-oriented_programming) for JavaScript methods or the [Decorator](http://en.wikipedia.org/wiki/Decorator_pattern) pattern used to target methods specifically. Need the ability to execute behavior just before or after a method is invoked? Want to apply a predicate that determines if the method should actually fire? You can use ConduitJS to make that happen. 4 | 5 | ##Concepts 6 | Conduit supports both asynchronous & synchronous <airquotes>pipelines</airquotes>. (By "pipeline", I mean a series of functions that can be executed *before* and *after* the target method, where each step in the pipeline has the chance to mutate the args being passed to the next step.) 7 | 8 | The primary difference(s) between async and sync Conduits, is that an asynchronous Conduit should be applied to a method that *does not return a value* and where you might want steps in the pipeline to be async (for example, a step might use `setTimeout` - the steps don't *have* to be async, though). Synchronous Conduits should be applied to methods that *return a value*, and by their nature, each step should be synchronous (so don't use async primitives like `setTimeout` and `setImmediate`, etc. in the steps of a sync Conduit). It's currently possible to abort the execution of an async Conduit, but the sync Conduits can't be aborted (this may change if the need arises). The step callbacks have slightly different method signatures in sync vs async Conduits, which we'll explore below. 9 | 10 | (where steps can optionally be async, and a `next` continuation callback is passed into each step, allowing you to determine when it proceeds to the next step) and synchronous pipelines (where a return value is expected, and each step is pased the prior step's return value, with the last return value being the result passed to the caller). 11 | 12 | ##Example Usage 13 | ###`Conduit.Async` 14 | As we noted above, an async Conduit doesn't *have* to use async primitives in steps, but it should be able to handle them. It manages this by passing a `next` continuation argument to each step so that the step can invoke it when it's ready to proceed to the next step, etc. For example, adding a "before" step: 15 | 16 | ```javascript 17 | obj.doStuff.before(function(next, someValue) { 18 | console.log("Pre-invocation step. We can see the value is: " + someValue); 19 | next(someValue); 20 | }); 21 | ``` 22 | 23 | Notice that the "before" step callback function's signature is *exactly the same as the target method, except the `next` callback is inserted as the first argument. Let's look at some more examples: 24 | 25 | ####Making a method an Async-Capable Conduit: 26 | 27 | ```javascript 28 | var obj = { 29 | doStuff: function(someValue) { 30 | console.log("The actual method is doing stuff. The actual value is " + someValue); 31 | } 32 | } 33 | // make the doStuff method a conduit 34 | obj.doStuff = new Conduit.Async({ context: obj, target: obj.doStuff }); 35 | obj.doStuff(1); 36 | // The actual method is doing stuff. The actual value is 1 37 | ``` 38 | 39 | ####Add a pre-invocation step to the pipeline: 40 | 41 | ```javascript 42 | // Conduit's pipeline steps are functions that take a "next" continuation callback 43 | // as the first argument. After that you can have 0-n arguments - whatever your 44 | // original target method signature should be 45 | obj.doStuff.before(function(next, someValue) { 46 | console.log("Pre-invocation step. We can see the value is: " + someValue); 47 | next(someValue); 48 | }); 49 | obj.doStuff(1); 50 | /* 51 | Pre-invocation step. We can see the value is: 1 52 | The actual method is doing stuff. The actual value is 1 53 | */ 54 | ``` 55 | 56 | ####Add a post-invocation step to the pipeline: 57 | 58 | ```javascript 59 | obj.doStuff.after(function(next, someValue) { 60 | console.log("Post-invocation step. We can see the value is: " + someValue); 61 | next(someValue); 62 | }, { phase: "post" }); 63 | obj.doStuff(1); 64 | /* 65 | Pre-invocation step. We can see the value is: 1 66 | The actual method is doing stuff. The actual value is 1 67 | Post-invocation step. We can see the value is: 1 68 | */ 69 | ``` 70 | 71 | ####Adding a pre-invocation step before other pre-invocation steps: 72 | 73 | ```javascript 74 | obj.doStuff.before(function(next, someValue) { 75 | console.log("Another Pre-invocation step. This should execute first..."); 76 | next(someValue); 77 | }, { prepend: true }); 78 | obj.doStuff(1) 79 | /* 80 | Another Pre-invocation step. This should execute first... 81 | Pre-invocation step. We can see the value is: 1 82 | The actual method is doing stuff. The actual value is 1 83 | Post-invocation step. We can see the value is: 1 84 | */ 85 | ``` 86 | 87 | ####Adding a post-invocation step before other post-invocation steps: 88 | 89 | ```javascript 90 | obj.doStuff.after(function(next, someValue) { 91 | console.log("Another Post-invocation step. This should execute before any other post-invocation steps..."); 92 | next(someValue); 93 | }, { prepend: true, phase: "post" }); 94 | obj.doStuff(1) 95 | /* 96 | Another Pre-invocation step. This should execute first... 97 | Pre-invocation step. We can see the value is: 1 98 | The actual method is doing stuff. The actual value is 1 99 | Another Post-invocation step. This should execute before any other post-invocation steps... 100 | Post-invocation step. We can see the value is: 1 101 | */ 102 | ``` 103 | 104 | ####Using a predicate to determine if steps should continue processing: 105 | 106 | ```javascript 107 | var someFlag = false; 108 | obj.doStuff.before(function(next, someValue) { 109 | console.log("Pre-invocation predicate. I will halt execution"); 110 | if(someFlag) { 111 | next(someValue); 112 | } 113 | }); 114 | obj.doStuff(1); 115 | /* 116 | Another Pre-invocation step. This should execute first... 117 | Pre-invocation step. We can see the value is: 1 118 | Pre-invocation predicate. I will halt execution 119 | */ 120 | ``` 121 | 122 | ####Steps can be Asynchronous with `Conduit.Async` 123 | 124 | ```javascript 125 | obj.doStuff.after(function(next, someValue) { 126 | setTimeout(function() { 127 | console.log("Post-invocation step. We can see the value is: " + someValue); 128 | next(someValue); 129 | }, 0); 130 | }, { phase: "post" }); 131 | obj.doStuff(1); 132 | ``` 133 | 134 | ###`Conduit.Sync` 135 | A Synchronous Conduit should be used when you need to capture (or mutate) the return value of the target method. The rules differ slightly with synchronous Conduits: 136 | 137 | * "before" steps are allowed to mutate the arguments being fed to the target method by returning an array of new arguments that will be applied to the next "before" step (or the target method if that's the next method in the chain). If you don't want to override/mutate the arguments, simply don't return anything from the step. A "before" step's method signature matches the target method's. 138 | * Once the target method is invoked, the return value is captured and then passed as the *first argument* to any "after" step. 139 | * "after" steps can mutate the return value by returing a different value. If you don't want to mutate the return value in an "after" step, just don't return anything (or you can explicitly return the `returnValue` argument that was passed into the step as the first arg). 140 | 141 | So, in a nutshell - with synchronous Conduits, "before" steps can mutate the arguments fed the target method and "after" steps can mutate the return value of the target method. 142 | 143 | 144 | ####Making a Method a Synchronous Conduit: 145 | ```javascript 146 | var obj = { 147 | doStuff: function(someValue) { 148 | console.log("The actual method is doing stuff. The actual value is " + someValue); 149 | return "doStuff fn return value"; 150 | } 151 | } 152 | // make the doStuff method a conduit 153 | obj.doStuff = new Conduit.Sync({ context: obj, target: obj.doStuff }); 154 | var result = obj.doStuff(1); 155 | // result = "doStuff fn return value" 156 | // The actual method is doing stuff. The actual value is 1 157 | ``` 158 | 159 | ####Add a pre-invocation step to a synchronous pipeline: 160 | 161 | ```javascript 162 | // Conduit's sync pipeline "before" steps are functions that match 163 | // whatever your original target method signature is. These steps 164 | // can return an array of values that will be used as the new args 165 | // for the next step in the chain 166 | obj.doStuff.before(function(someValue) { 167 | console.log("Pre-invocation step. We can see the value is: " + someValue); 168 | return [someValue + 1]; 169 | }); 170 | var result = obj.doStuff(1); 171 | /* 172 | result = "doStuff fn return value" 173 | Pre-invocation step. We can see the value is: 1 174 | The actual method is doing stuff. The actual value is 2 175 | */ 176 | ``` 177 | 178 | ####Add a post-invocation step to the pipeline: 179 | 180 | ```javascript 181 | // Conduit's sync pipeline "after" steps are functions that match 182 | // whatever your original target method signature is, BUT with the 183 | // return value of the target method or previous before step function 184 | // as the first arg. These steps can return a value that will be 185 | // used as the new return value, and fed to the next step in the chain. 186 | obj.doStuff.after(function(returnValue, someValue) { 187 | console.log("Post-invocation step. We can see the value is: " + returnValue); 188 | return returnValue + " after step"; 189 | }); 190 | var result = obj.doStuff(1); 191 | // result = "doStuff fn return value after step" 192 | /* 193 | Pre-invocation step. We can see the value is: 1 194 | The actual method is doing stuff. The actual value is 2 195 | Post-invocation step. We can see the value is: doStuff fn return value 196 | doStuff fn return value after step 197 | */ 198 | ``` 199 | 200 | ##License 201 | It's MIT. Go forth and fork (please consider contributing back as well!). -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "conduitjs", 3 | "version": "0.3.3", 4 | "description": "Give any method a pipeline....", 5 | "homepage": "https://github.com/ifandelse/ConduitJS", 6 | "keywords": [ 7 | "strategy", 8 | "pipeline", 9 | "AOP", 10 | "proxy", 11 | "decorator", 12 | "step" 13 | ], 14 | "author": "Jim Cowart ", 15 | "repository": { 16 | "type": "git", 17 | "url": "git://github.com/ifandelse/ConduitJS.git" 18 | }, 19 | "main": [ 20 | "lib/conduit.min.js", 21 | "lib/conduit.js" 22 | ], 23 | "dependencies": {}, 24 | "devDependencies": { 25 | "jquery": "~1.10.2", 26 | "bootstrap": "~3.0.3", 27 | "expect": "0.1.2", 28 | "mocha": "~1.17.0" 29 | } 30 | } -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "conduitjs", 3 | "repo": "ifandelse/conduitjs", 4 | "description": "Give any method a pipeline....", 5 | "version": "0.3.3", 6 | "keywords": [ 7 | "strategy", 8 | "pipeline", 9 | "AOP", 10 | "proxy", 11 | "decorator", 12 | "step" 13 | ], 14 | "dependencies": {}, 15 | "scripts": ["lib/conduitjs.min.js", "lib/conduitjs.js"], 16 | "license": "MIT" 17 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require("gulp"); 2 | var fileImports = require("gulp-imports"); 3 | var header = require("gulp-header"); 4 | var beautify = require("gulp-beautify"); 5 | var hintNot = require("gulp-hint-not"); 6 | var uglify = require("gulp-uglify"); 7 | var rename = require("gulp-rename"); 8 | var plato = require("gulp-plato"); 9 | var rimraf = require("gulp-rimraf"); 10 | var gutil = require("gulp-util"); 11 | var express = require("express"); 12 | var path = require("path"); 13 | var pkg = require("./package.json"); 14 | var open = require("open"); 15 | var port = 3080; 16 | 17 | var banner = ["/**", 18 | " * <%= pkg.name %> - <%= pkg.description %>", 19 | " * Author: <%= pkg.author %>", 20 | " * Version: v<%= pkg.version %>", 21 | " * Url: <%= pkg.homepage %>", 22 | " * License: <%= pkg.license.type %>", 23 | " */", 24 | ""].join("\n"); 25 | 26 | gulp.task("combine", function() { 27 | gulp.src(["./src/conduit.js"]) 28 | .pipe(header(banner, { pkg : pkg })) 29 | .pipe(fileImports()) 30 | .pipe(hintNot()) 31 | .pipe(beautify({ indentSize: 4, preserveNewlines: false })) 32 | .pipe(gulp.dest("./lib/")) 33 | .pipe(uglify({ compress: { negate_iife: false }})) 34 | .pipe(header(banner, { pkg : pkg })) 35 | .pipe(rename("conduit.min.js")) 36 | .pipe(gulp.dest("./lib/")); 37 | }); 38 | 39 | gulp.task("default", function() { 40 | gulp.run("combine"); 41 | }); 42 | 43 | gulp.task("report", function () { 44 | gulp.src("./lib/conduit.js") 45 | .pipe(plato("report")); 46 | }); 47 | 48 | var createServer = function(port) { 49 | var p = path.resolve("./"); 50 | var app = express(); 51 | app.use(express.static(p)); 52 | app.listen(port, function() { 53 | gutil.log("Listening on", port); 54 | }); 55 | 56 | return { 57 | app: app 58 | }; 59 | }; 60 | 61 | var servers; 62 | 63 | gulp.task("server", function(){ 64 | gulp.run("combine", "report"); 65 | if(!servers) { 66 | servers = createServer(port); 67 | } 68 | open( "http://localhost:" + port + "/index.html" ); 69 | }); -------------------------------------------------------------------------------- /header.js: -------------------------------------------------------------------------------- 1 | /* 2 | {{{name}}} 3 | Author: {{{author}}} 4 | License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license) 5 | Version {{{version}}} 6 | */ -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ConduitJS 5 | 6 | 7 | 39 | 40 | 41 | 42 | 43 | 49 |
50 |
51 | You're at the root of the repository. Some helpful links: 52 |
53 |
54 | Tests 55 |
56 |
57 | Plato Reports 58 |
59 |
60 | 61 | -------------------------------------------------------------------------------- /lib/conduit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * conduitjs - Give any method a pre/post invocation pipeline.... 3 | * Author: Jim Cowart (http://freshbrewedcode.com/jimcowart) 4 | * Version: v0.3.3 5 | * Url: http://github.com/ifandelse/ConduitJS 6 | * License: MIT 7 | */ 8 | (function (root, factory) { 9 | if (typeof module === "object" && module.exports) { 10 | // Node, or CommonJS-Like environments 11 | module.exports = factory(); 12 | } else if (typeof define === "function" && define.amd) { 13 | // AMD. Register as an anonymous module. 14 | define([], factory(root)); 15 | } else { 16 | // Browser globals 17 | root.Conduit = factory(root); 18 | } 19 | }(this, function (global, undefined) { 20 | function Conduit(options) { 21 | if (typeof options.target !== "function") { 22 | throw new Error("You can only make functions into Conduits."); 23 | } 24 | var _steps = { 25 | pre: options.pre || [], 26 | post: options.post || [], 27 | all: [] 28 | }; 29 | var _defaultContext = options.context; 30 | var _target = options.target; 31 | var _targetStep = { 32 | isTarget: true, 33 | fn: options.sync ? 34 | function () { 35 | var args = Array.prototype.slice.call(arguments, 0); 36 | var result = _target.apply(_defaultContext, args); 37 | return result; 38 | } : function (next) { 39 | var args = Array.prototype.slice.call(arguments, 1); 40 | args.splice(1, 1, _target.apply(_defaultContext, args)); 41 | next.apply(this, args); 42 | } 43 | }; 44 | var _genPipeline = function () { 45 | _steps.all = _steps.pre.concat([_targetStep].concat(_steps.post)); 46 | }; 47 | _genPipeline(); 48 | var conduit = function () { 49 | var idx = 0; 50 | var retval; 51 | var phase; 52 | var next = function next() { 53 | var args = Array.prototype.slice.call(arguments, 0); 54 | var thisIdx = idx; 55 | var step; 56 | var nextArgs; 57 | idx += 1; 58 | if (thisIdx < _steps.all.length) { 59 | step = _steps.all[thisIdx]; 60 | phase = (phase === "target") ? "after" : (step.isTarget) ? "target" : "before"; 61 | if (options.sync) { 62 | if (phase === "before") { 63 | nextArgs = step.fn.apply(step.context || _defaultContext, args); 64 | next.apply(this, nextArgs || args); 65 | } else { 66 | retval = step.fn.apply(step.context || _defaultContext, args) || retval; 67 | next.apply(this, [retval].concat(args)); 68 | } 69 | } else { 70 | step.fn.apply(step.context || _defaultContext, [next].concat(args)); 71 | } 72 | } 73 | }; 74 | next.apply(this, arguments); 75 | return retval; 76 | }; 77 | conduit.steps = function () { 78 | return _steps.all; 79 | }; 80 | conduit.context = function (ctx) { 81 | if (arguments.length === 0) { 82 | return _defaultContext; 83 | } else { 84 | _defaultContext = ctx; 85 | } 86 | }; 87 | conduit.before = function (step, options) { 88 | step = typeof step === "function" ? { 89 | fn: step 90 | } : step; 91 | options = options || {}; 92 | if (options.prepend) { 93 | _steps.pre.unshift(step); 94 | } else { 95 | _steps.pre.push(step); 96 | } 97 | _genPipeline(); 98 | }; 99 | conduit.after = function (step, options) { 100 | step = typeof step === "function" ? { 101 | fn: step 102 | } : step; 103 | options = options || {}; 104 | if (options.prepend) { 105 | _steps.post.unshift(step); 106 | } else { 107 | _steps.post.push(step); 108 | } 109 | _genPipeline(); 110 | }; 111 | conduit.clear = function () { 112 | _steps = { 113 | pre: [], 114 | post: [], 115 | all: [] 116 | }; 117 | _genPipeline(); 118 | }; 119 | conduit.target = function (fn) { 120 | if (fn) { 121 | _target = fn; 122 | } 123 | return _target; 124 | }; 125 | return conduit; 126 | }; 127 | return { 128 | Sync: function (options) { 129 | options.sync = true; 130 | return Conduit.call(this, options) 131 | }, 132 | Async: function (options) { 133 | return Conduit.call(this, options); 134 | } 135 | } 136 | })); -------------------------------------------------------------------------------- /lib/conduit.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * conduitjs - Give any method a pre/post invocation pipeline.... 3 | * Author: Jim Cowart (http://freshbrewedcode.com/jimcowart) 4 | * Version: v0.3.3 5 | * Url: http://github.com/ifandelse/ConduitJS 6 | * License: MIT 7 | */ 8 | (function(t,n){"object"==typeof module&&module.exports?module.exports=n():"function"==typeof define&&define.amd?define([],n(t)):t.Conduit=n(t)})(this,function(){function t(t){if("function"!=typeof t.target)throw new Error("You can only make functions into Conduits.");var n={pre:t.pre||[],post:t.post||[],all:[]},e=t.context,r=t.target,o={isTarget:!0,fn:t.sync?function(){var t=Array.prototype.slice.call(arguments,0),n=r.apply(e,t);return n}:function(t){var n=Array.prototype.slice.call(arguments,1);n.splice(1,1,r.apply(e,n)),t.apply(this,n)}},a=function(){n.all=n.pre.concat([o].concat(n.post))};a();var c=function(){var r,o,a=0,c=function p(){var c,u,i=Array.prototype.slice.call(arguments,0),f=a;a+=1,f=0.4.0" 41 | }, 42 | "dependencies": {}, 43 | "devDependencies": { 44 | "bower": "~1.2.8", 45 | "gulp-util": "~2.2.9", 46 | "gulp": "~3.3.1", 47 | "gulp-imports": "~0.0.1", 48 | "gulp-header": "~1.0.2", 49 | "gulp-hint-not": "~0.0.3", 50 | "gulp-uglify": "~0.1.0", 51 | "gulp-rename": "~0.2.1", 52 | "gulp-plato": "~0.1.0", 53 | "gulp-beautify": "~1.0.3", 54 | "express": "~3.4.7", 55 | "gulp-rimraf": "0.0.8", 56 | "open": "~0.0.4", 57 | "expect.js": "~0.2.0" 58 | }, 59 | "license": { 60 | "type": "MIT", 61 | "url": "http://www.opensource.org/licenses/mit-license.php" 62 | }, 63 | "scripts": { 64 | "test": "mocha ./spec/*.js", 65 | "build": "gulp", 66 | "start": "gulp server" 67 | } 68 | } -------------------------------------------------------------------------------- /spec/conduit.spec.js: -------------------------------------------------------------------------------- 1 | /*global describe, it, after, before, expect */ 2 | (function() { 3 | var Conduit = typeof window === "undefined" ? require("../lib/conduit.js") : window.Conduit; 4 | var expect = typeof window === "undefined" ? require("expect.js") : window.expect; 5 | describe("ConduitJS", function() { 6 | describe("With an Async-Capable Pipeline", function() { 7 | describe("with NO steps in use", function() { 8 | var obj = { 9 | name: "Jimbabwe", 10 | doStuff: function(msg, cb) { 11 | cb("Hi, " + this.name + " - " + msg); 12 | } 13 | }; 14 | before(function() { 15 | obj.doStuff = new Conduit.Async({ 16 | target: obj.doStuff, 17 | context: obj 18 | }); 19 | }); 20 | it("should return the expected value", function() { 21 | obj.doStuff("here's your msg...", function(msg) { 22 | expect(msg).to.be("Hi, Jimbabwe - here's your msg..."); 23 | }); 24 | }); 25 | }); 26 | describe("with 'before' steps in use", function() { 27 | var obj = { 28 | name: "Jimbabwe", 29 | doStuff: function(msg, cb) { 30 | cb("Hi, " + this.name + " - " + msg); 31 | } 32 | }; 33 | var oldMethod; 34 | before(function() { 35 | oldMethod = obj.doStuff; 36 | obj.doStuff = new Conduit.Async({ 37 | target: obj.doStuff, 38 | context: obj 39 | }); 40 | obj.doStuff.before({ 41 | name: "test1", 42 | fn: function(next, msg, cb) { 43 | next("Yo dawg..." + msg, cb); 44 | } 45 | }); 46 | }); 47 | it("should replace the method", function() { 48 | expect(obj.doStuff).to.not.be(oldMethod); 49 | }); 50 | it("should show a strategy in the steps array (along with target method)", function() { 51 | expect(obj.doStuff.steps().length).to.be(2); 52 | }); 53 | it("should return the expected value", function() { 54 | obj.doStuff("here's your msg...", function(msg) { 55 | expect(msg).to.be("Hi, Jimbabwe - Yo dawg...here's your msg..."); 56 | }); 57 | }); 58 | }); 59 | describe("with 'after' steps in use", function() { 60 | var results = []; 61 | var obj = { 62 | name: "Jimbabwe", 63 | doStuff: function() { 64 | results.push("original method invoked"); 65 | } 66 | }; 67 | var oldMethod; 68 | before(function() { 69 | oldMethod = obj.doStuff; 70 | obj.doStuff = new Conduit.Async({ 71 | target: obj.doStuff, 72 | context: obj 73 | }); 74 | obj.doStuff.after({ 75 | fn: function(next, msg) { 76 | results.push("after step invoked"); 77 | next(); 78 | } 79 | }); 80 | }); 81 | it("should replace the method", function() { 82 | expect(obj.doStuff).to.not.be(oldMethod); 83 | }); 84 | it("should show a step in the steps array (along with target method)", function() { 85 | expect(obj.doStuff.steps().length).to.be(2); 86 | }); 87 | it("should execute the methods in the expected order", function() { 88 | obj.doStuff(); 89 | expect(results[0]).to.be("original method invoked"); 90 | expect(results[1]).to.be("after step invoked"); 91 | }); 92 | }); 93 | describe("with 'before' and 'after' steps in use", function() { 94 | var results = []; 95 | var obj = { 96 | name: "Jimbabwe", 97 | doStuff: function() { 98 | results.push("original method invoked"); 99 | } 100 | }; 101 | var oldMethod; 102 | before(function() { 103 | oldMethod = obj.doStuff; 104 | obj.doStuff = new Conduit.Async({ 105 | target: obj.doStuff, 106 | context: obj 107 | }); 108 | obj.doStuff.before({ 109 | fn: function(next, msg) { 110 | results.push("before step invoked"); 111 | next(); 112 | } 113 | }); 114 | obj.doStuff.after({ 115 | fn: function(next, msg) { 116 | results.push("after step invoked"); 117 | next(); 118 | } 119 | }); 120 | }); 121 | it("should replace the method", function() { 122 | expect(obj.doStuff).to.not.be(oldMethod); 123 | }); 124 | it("should show two steps in the steps array (along with target method)", function() { 125 | expect(obj.doStuff.steps().length).to.be(3); 126 | }); 127 | it("should execute the methods in the expected order", function() { 128 | obj.doStuff(); 129 | expect(results[0]).to.be("before step invoked"); 130 | expect(results[1]).to.be("original method invoked"); 131 | expect(results[2]).to.be("after step invoked"); 132 | }); 133 | }); 134 | describe("when clearing steps", function() { 135 | var obj = { 136 | name: "Jimbabwe", 137 | doStuff: function(msg, cb) { 138 | cb("Hi, " + this.name + " - " + msg); 139 | } 140 | }; 141 | var oldMethod; 142 | before(function() { 143 | oldMethod = obj.doStuff; 144 | obj.doStuff = new Conduit.Async({ 145 | target: obj.doStuff, 146 | context: obj 147 | }); 148 | obj.doStuff.before({ 149 | name: "test1", 150 | fn: function(next, msg) { 151 | next("Yo dawg..." + msg); 152 | } 153 | }); 154 | obj.doStuff.clear(); 155 | }); 156 | it("should replace the method", function() { 157 | expect(obj.doStuff).to.not.be(oldMethod); 158 | }); 159 | it("should NOT show a strategy in the array", function() { 160 | expect(obj.doStuff.steps().length).to.be(1); 161 | }); 162 | it("should return the expected value", function() { 163 | it("should return the expected value", function() { 164 | obj.doStuff("here's your msg...", function(msg) { 165 | expect(msg).to.be("Hi, Jimbabwe - here's your msg..."); 166 | }); 167 | }); 168 | }); 169 | }); 170 | describe("when providing a strategy-specific context", function() { 171 | var obj = { 172 | name: "Jimbabwe", 173 | doStuff: function(msg, cb) { 174 | cb("Hi, " + this.name + " - " + msg); 175 | } 176 | }; 177 | var objB = { 178 | name: "Your mom" 179 | }; 180 | var oldMethod; 181 | before(function() { 182 | oldMethod = obj.doStuff; 183 | obj.doStuff = new Conduit.Async({ 184 | target: obj.doStuff, 185 | context: obj 186 | }); 187 | obj.doStuff.before({ 188 | name: "test1", 189 | fn: function(next, msg) { 190 | next("Yo dawg..." + this.name + " says '" + msg + "'"); 191 | }, 192 | context: objB 193 | }); 194 | }); 195 | it("should replace the method", function() { 196 | expect(obj.doStuff).to.not.be(oldMethod); 197 | }); 198 | it("should show a strategy in the steps array along with target", function() { 199 | expect(obj.doStuff.steps().length).to.be(2); 200 | }); 201 | it("should return the expected value", function() { 202 | it("should return the expected value", function() { 203 | obj.doStuff("here's your msg...", function(msg) { 204 | expect(msg).to.be("Hi, Jimbabwe - Yo dawg...Your mom says 'here's your msg...'"); 205 | }); 206 | }); 207 | }); 208 | }); 209 | describe("with asynchronous steps", function() { 210 | var obj = { 211 | name: "Jimbabwe", 212 | doStuff: function(msg, cb) { 213 | cb("Hi, " + this.name + " - " + msg); 214 | } 215 | }; 216 | var oldMethod; 217 | before(function() { 218 | oldMethod = obj.doStuff; 219 | obj.doStuff = new Conduit.Async({ 220 | target: obj.doStuff, 221 | context: obj 222 | }); 223 | obj.doStuff.before(function(next, msg) { 224 | setTimeout(function() { 225 | next("Yo dawg..." + msg + "'"); 226 | }, 0); 227 | }); 228 | }); 229 | it("should return the expected value", function() { 230 | it("should return the expected value", function(done) { 231 | obj.doStuff("here's your msg...", function(msg) { 232 | expect(msg).to.be("Hi, Jimbabwe - Yo dawg...here's your msg..."); 233 | done(); 234 | }); 235 | }); 236 | }); 237 | }); 238 | describe("when replacing the target with a new one", function() { 239 | var obj = { 240 | name: "Jimbabwe", 241 | doStuff: function(msg, cb) { 242 | cb("Hi, " + this.name + " - " + msg); 243 | } 244 | }; 245 | var doStuffNew = function(msg, cb) { 246 | cb("NEW: Hi, " + this.name + " - " + msg); 247 | }; 248 | var oldMethod; 249 | before(function() { 250 | oldMethod = obj.doStuff; 251 | obj.doStuff = new Conduit.Async({ 252 | target: obj.doStuff, 253 | context: obj 254 | }); 255 | obj.doStuff.before({ 256 | name: "test1", 257 | fn: function(next, msg, cb) { 258 | next("Yo dawg..." + this.name + " says '" + msg + "'", cb); 259 | } 260 | }); 261 | obj.doStuff.target(doStuffNew); 262 | }); 263 | it("should replace the method", function() { 264 | expect(obj.doStuff).to.not.be(oldMethod); 265 | }); 266 | it("should show a strategy in the steps array along with target", function() { 267 | expect(obj.doStuff.steps().length).to.be(2); 268 | }); 269 | it("should return the expected value", function() { 270 | obj.doStuff("here's your msg...", function(msg) { 271 | expect(msg).to.be("NEW: Hi, Jimbabwe - Yo dawg...Jimbabwe says 'here's your msg...'"); 272 | }); 273 | }); 274 | }); 275 | }); 276 | describe("With a Sync-only pipeline", function() { 277 | describe("with NO steps in use", function() { 278 | var obj = { 279 | name: "Jimbabwe", 280 | doStuff: function(msg) { 281 | return "Hi, " + this.name + " - " + msg; 282 | } 283 | }; 284 | before(function() { 285 | obj.doStuff = new Conduit.Sync({ 286 | target: obj.doStuff, 287 | context: obj 288 | }); 289 | }); 290 | it("should return the expected value", function() { 291 | expect(obj.doStuff("here's your msg...")).to.be("Hi, Jimbabwe - here's your msg..."); 292 | }); 293 | }); 294 | describe("with 'before' steps in use", function() { 295 | var obj = { 296 | name: "Jimbabwe", 297 | doStuff: function(msg) { 298 | return "Hi, " + this.name + " - " + msg; 299 | } 300 | }; 301 | var oldMethod; 302 | before(function() { 303 | oldMethod = obj.doStuff; 304 | obj.doStuff = new Conduit.Sync({ 305 | target: obj.doStuff, 306 | context: obj 307 | }); 308 | obj.doStuff.before({ 309 | name: "test1", 310 | fn: function(returnVal, msg) { 311 | return "Yo dawg..." + msg; 312 | } 313 | }); 314 | }); 315 | it("should replace the method", function() { 316 | expect(obj.doStuff).to.not.be(oldMethod); 317 | }); 318 | it("should show a strategy in the steps array (along with target method)", function() { 319 | expect(obj.doStuff.steps().length).to.be(2); 320 | }); 321 | it("should return the expected value", function() { 322 | it("should return the expected value", function() { 323 | expect(obj.doStuff("here's your msg...")).to.be("Hi, Jimbabwe - Yo dawg...here's your msg..."); 324 | }); 325 | }); 326 | }); 327 | describe("with 'after' steps in use", function() { 328 | var results = []; 329 | var obj = { 330 | name: "Jimbabwe", 331 | doStuff: function() { 332 | results.push("original method invoked"); 333 | return "doStuff invoked"; 334 | } 335 | }; 336 | var oldMethod; 337 | before(function() { 338 | oldMethod = obj.doStuff; 339 | obj.doStuff = new Conduit.Sync({ 340 | target: obj.doStuff, 341 | context: obj 342 | }); 343 | obj.doStuff.after({ 344 | fn: function(retVal) { 345 | results.push("after step invoked"); 346 | return retVal; 347 | } 348 | }); 349 | }); 350 | it("should replace the method", function() { 351 | expect(obj.doStuff).to.not.be(oldMethod); 352 | }); 353 | it("should show a step in the steps array (along with target method)", function() { 354 | expect(obj.doStuff.steps().length).to.be(2); 355 | }); 356 | it("should execute the methods in the expected order", function() { 357 | expect(obj.doStuff()).to.be("doStuff invoked"); 358 | expect(results[0]).to.be("original method invoked"); 359 | expect(results[1]).to.be("after step invoked"); 360 | }); 361 | }); 362 | describe("with 'before' and 'after' steps in use", function() { 363 | var results = []; 364 | var obj = { 365 | name: "Jimbabwe", 366 | doStuff: function() { 367 | results.push("original method invoked"); 368 | return "doStuff invoked"; 369 | } 370 | }; 371 | var oldMethod; 372 | before(function() { 373 | oldMethod = obj.doStuff; 374 | obj.doStuff = new Conduit.Sync({ 375 | target: obj.doStuff, 376 | context: obj 377 | }); 378 | obj.doStuff.before({ 379 | fn: function(retVal) { 380 | results.push("before step invoked"); 381 | return retVal; 382 | } 383 | }); 384 | obj.doStuff.after({ 385 | fn: function(retVal) { 386 | results.push("after step invoked"); 387 | return retVal; 388 | } 389 | }); 390 | }); 391 | it("should replace the method", function() { 392 | expect(obj.doStuff).to.not.be(oldMethod); 393 | }); 394 | it("should show two steps in the steps array (along with target method)", function() { 395 | expect(obj.doStuff.steps().length).to.be(3); 396 | }); 397 | it("should execute the methods in the expected order", function() { 398 | expect(obj.doStuff()).to.be("doStuff invoked"); 399 | expect(results[0]).to.be("before step invoked"); 400 | expect(results[1]).to.be("original method invoked"); 401 | expect(results[2]).to.be("after step invoked"); 402 | }); 403 | }); 404 | describe("when clearing steps", function() { 405 | var obj = { 406 | name: "Jimbabwe", 407 | doStuff: function(msg) { 408 | return "Hi, " + this.name + " - " + msg; 409 | } 410 | }; 411 | var oldMethod; 412 | before(function() { 413 | oldMethod = obj.doStuff; 414 | obj.doStuff = new Conduit.Sync({ 415 | target: obj.doStuff, 416 | context: obj 417 | }); 418 | obj.doStuff.before({ 419 | name: "test1", 420 | fn: function(msg) { 421 | return "Yo dawg..." + msg; 422 | } 423 | }); 424 | obj.doStuff.clear(); 425 | }); 426 | it("should replace the method", function() { 427 | expect(obj.doStuff).to.not.be(oldMethod); 428 | }); 429 | it("should NOT show a strategy in the array", function() { 430 | expect(obj.doStuff.steps().length).to.be(1); 431 | }); 432 | it("should return the expected value", function() { 433 | expect(obj.doStuff("here's your msg...")).to.be("Hi, Jimbabwe - here's your msg..."); 434 | }); 435 | }); 436 | describe("when providing a strategy-specific context", function() { 437 | var obj = { 438 | name: "Jimbabwe", 439 | doStuff: function(msg) { 440 | return "Hi, " + this.name + " - " + msg; 441 | } 442 | }; 443 | var objB = { 444 | name: "Your mom" 445 | }; 446 | var oldMethod; 447 | before(function() { 448 | oldMethod = obj.doStuff; 449 | obj.doStuff = new Conduit.Sync({ 450 | target: obj.doStuff, 451 | context: obj 452 | }); 453 | obj.doStuff.before({ 454 | name: "test1", 455 | fn: function(msg) { 456 | return ["Yo dawg..." + this.name + " says '" + msg + "'"]; 457 | }, 458 | context: objB 459 | }); 460 | }); 461 | it("should replace the method", function() { 462 | expect(obj.doStuff).to.not.be(oldMethod); 463 | }); 464 | it("should show a strategy in the steps array along with target", function() { 465 | expect(obj.doStuff.steps().length).to.be(2); 466 | }); 467 | it("should return the expected value", function() { 468 | expect(obj.doStuff("here's your msg...")).to.be("Hi, Jimbabwe - Yo dawg...Your mom says 'here's your msg...'"); 469 | }); 470 | }); 471 | describe("When mutating the args in a before step", function() { 472 | var obj = { 473 | name: "Jimbabwe", 474 | doStuff: function(msg) { 475 | return "Hi, " + this.name + " - " + msg; 476 | } 477 | }; 478 | var oldMethod; 479 | before(function() { 480 | oldMethod = obj.doStuff; 481 | obj.doStuff = new Conduit.Sync({ 482 | target: obj.doStuff, 483 | context: obj 484 | }); 485 | obj.doStuff.before({ 486 | name: "test1", 487 | fn: function(msg) { 488 | return ["CONDUIT SEZ: " + msg]; 489 | } 490 | }); 491 | }); 492 | it("should replace the method", function() { 493 | expect(obj.doStuff).to.not.be(oldMethod); 494 | }); 495 | it("should show a strategy in the steps array (along with target method)", function() { 496 | expect(obj.doStuff.steps().length).to.be(2); 497 | }); 498 | it("should return the expected value", function() { 499 | expect(obj.doStuff("here's your msg...")).to.be("Hi, Jimbabwe - CONDUIT SEZ: here\'s your msg..."); 500 | }); 501 | }); 502 | describe("When mutating the value in an after step", function() { 503 | var obj = { 504 | name: "Jimbabwe", 505 | doStuff: function(msg) { 506 | return "Hi, " + this.name + " - " + msg; 507 | } 508 | }; 509 | var oldMethod; 510 | before(function() { 511 | oldMethod = obj.doStuff; 512 | obj.doStuff = new Conduit.Sync({ 513 | target: obj.doStuff, 514 | context: obj 515 | }); 516 | obj.doStuff.after({ 517 | name: "test1", 518 | fn: function(returnVal, msg) { 519 | return "CONDUIT SEZ: " + returnVal; 520 | } 521 | }); 522 | }); 523 | it("should replace the method", function() { 524 | expect(obj.doStuff).to.not.be(oldMethod); 525 | }); 526 | it("should show a strategy in the steps array (along with target method)", function() { 527 | expect(obj.doStuff.steps().length).to.be(2); 528 | }); 529 | it("should return the expected value", function() { 530 | expect(obj.doStuff("here's your msg...")).to.be("CONDUIT SEZ: Hi, Jimbabwe - here's your msg..."); 531 | }); 532 | }); 533 | describe("When mutating the value in before and after steps", function() { 534 | var obj = { 535 | name: "Jimbabwe", 536 | doStuff: function(msg) { 537 | return "Hi, " + this.name + " - " + msg; 538 | } 539 | }; 540 | var oldMethod; 541 | before(function() { 542 | oldMethod = obj.doStuff; 543 | obj.doStuff = new Conduit.Sync({ 544 | target: obj.doStuff, 545 | context: obj 546 | }); 547 | obj.doStuff.before({ 548 | name: "test1", 549 | fn: function(msg) { 550 | return ["CONDUIT SEZ BEFORE: " + msg]; 551 | } 552 | }); 553 | obj.doStuff.after({ 554 | name: "test1", 555 | fn: function(returnVal, msg) { 556 | return returnVal + " CONDUIT SEZ AFTER"; 557 | } 558 | }); 559 | }); 560 | it("should replace the method", function() { 561 | expect(obj.doStuff).to.not.be(oldMethod); 562 | }); 563 | it("should show a strategy in the steps array (along with target method)", function() { 564 | expect(obj.doStuff.steps().length).to.be(3); 565 | }); 566 | it("should return the expected value", function() { 567 | expect(obj.doStuff("here's your msg...")).to.be("Hi, Jimbabwe - CONDUIT SEZ BEFORE: here\'s your msg... CONDUIT SEZ AFTER"); 568 | }); 569 | }); 570 | describe("when replacing the target with a new one", function() { 571 | var obj = { 572 | name: "Jimbabwe", 573 | doStuff: function(msg) { 574 | return "Hi, " + this.name + " - " + msg; 575 | } 576 | }; 577 | var doStuffNew = function(msg) { 578 | return "NEW: Hi, " + this.name + " - " + msg; 579 | }; 580 | var oldMethod; 581 | before(function() { 582 | oldMethod = obj.doStuff; 583 | obj.doStuff = new Conduit.Sync({ 584 | target: obj.doStuff, 585 | context: obj 586 | }); 587 | obj.doStuff.target(doStuffNew); 588 | }); 589 | it("should replace the method", function() { 590 | expect(obj.doStuff).to.not.be(oldMethod); 591 | }); 592 | it("should return the expected value", function() { 593 | expect(obj.doStuff("here's your msg...")).to.be("NEW: Hi, Jimbabwe - here's your msg..."); 594 | }); 595 | }); 596 | }); 597 | }); 598 | }()); -------------------------------------------------------------------------------- /spec/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/conduit.js: -------------------------------------------------------------------------------- 1 | /*global define, module */ 2 | (function(root, factory) { 3 | if (typeof module === "object" && module.exports) { 4 | // Node, or CommonJS-Like environments 5 | module.exports = factory(); 6 | } else if (typeof define === "function" && define.amd) { 7 | // AMD. Register as an anonymous module. 8 | define([], factory(root)); 9 | } else { 10 | // Browser globals 11 | root.Conduit = factory(root); 12 | } 13 | }(this, function(global, undefined) { 14 | function Conduit(options) { 15 | if (typeof options.target !== "function") { 16 | throw new Error("You can only make functions into Conduits."); 17 | } 18 | var _steps = { 19 | pre: options.pre || [], 20 | post: options.post || [], 21 | all: [] 22 | }; 23 | var _defaultContext = options.context; 24 | var _target = options.target; 25 | var _targetStep = { 26 | isTarget: true, 27 | fn: options.sync ? 28 | function() { 29 | var args = Array.prototype.slice.call(arguments, 0); 30 | var result = _target.apply(_defaultContext, args); 31 | return result; 32 | } : function(next) { 33 | var args = Array.prototype.slice.call(arguments, 1); 34 | args.splice(1, 1, _target.apply(_defaultContext, args)); 35 | next.apply(this, args); 36 | } 37 | }; 38 | var _genPipeline = function() { 39 | _steps.all = _steps.pre.concat([_targetStep].concat(_steps.post)); 40 | }; 41 | _genPipeline(); 42 | var conduit = function() { 43 | var idx = 0; 44 | var retval; 45 | var phase; 46 | var next = function next() { 47 | var args = Array.prototype.slice.call(arguments, 0); 48 | var thisIdx = idx; 49 | var step; 50 | var nextArgs; 51 | idx += 1; 52 | if (thisIdx < _steps.all.length) { 53 | step = _steps.all[thisIdx]; 54 | phase = (phase === "target") ? "after" : (step.isTarget) ? "target" : "before"; 55 | if (options.sync) { 56 | if (phase === "before") { 57 | nextArgs = step.fn.apply(step.context || _defaultContext, args); 58 | next.apply(this, nextArgs || args); 59 | } else { 60 | retval = step.fn.apply(step.context || _defaultContext, args) || retval; 61 | next.apply(this, [retval].concat(args)); 62 | } 63 | } else { 64 | step.fn.apply(step.context || _defaultContext, [next].concat(args)); 65 | } 66 | } 67 | }; 68 | next.apply(this, arguments); 69 | return retval; 70 | }; 71 | conduit.steps = function() { 72 | return _steps.all; 73 | }; 74 | conduit.context = function(ctx) { 75 | if (arguments.length === 0) { 76 | return _defaultContext; 77 | } else { 78 | _defaultContext = ctx; 79 | } 80 | }; 81 | conduit.before = function(step, options) { 82 | step = typeof step === "function" ? { 83 | fn: step 84 | } : step; 85 | options = options || {}; 86 | if (options.prepend) { 87 | _steps.pre.unshift(step); 88 | } else { 89 | _steps.pre.push(step); 90 | } 91 | _genPipeline(); 92 | }; 93 | conduit.after = function(step, options) { 94 | step = typeof step === "function" ? { 95 | fn: step 96 | } : step; 97 | options = options || {}; 98 | if (options.prepend) { 99 | _steps.post.unshift(step); 100 | } else { 101 | _steps.post.push(step); 102 | } 103 | _genPipeline(); 104 | }; 105 | conduit.clear = function() { 106 | _steps = { 107 | pre: [], 108 | post: [], 109 | all: [] 110 | }; 111 | _genPipeline(); 112 | }; 113 | conduit.target = function (fn) { 114 | if(fn) { 115 | _target = fn; 116 | } 117 | return _target; 118 | }; 119 | return conduit; 120 | }; 121 | return { 122 | Sync: function(options) { 123 | options.sync = true; 124 | return Conduit.call(this, options) 125 | }, 126 | Async: function(options) { 127 | return Conduit.call(this, options); 128 | } 129 | } 130 | })); --------------------------------------------------------------------------------