diff --git a/example/amd/js/libs/postal/postal.js b/example/amd/js/libs/postal/postal.js index 00dd3e8..dad99dd 100755 --- a/example/amd/js/libs/postal/postal.js +++ b/example/amd/js/libs/postal/postal.js @@ -25,10 +25,145 @@ var postal; + var Strategy = function( options ) { + var _target = options.owner[options.prop]; + if ( typeof _target !== "function" ) { + throw new Error( "Strategies can only target methods." ); + } + var _strategies = []; + var _context = options.context || options.owner; + var strategy = function() { + var idx = 0; + var next = function next() { + var args = Array.prototype.slice.call( arguments, 0 ); + var thisIdx = idx; + var strategy; + idx += 1; + if ( thisIdx < _strategies.length ) { + strategy = _strategies[thisIdx]; + strategy.fn.apply( strategy.context || _context, [next].concat( args ) ); + } else { + _target.apply( _context, args ); + } + }; + next.apply( this, arguments ); + }; + strategy.target = function() { + return _target; + }; + strategy.context = function( ctx ) { + if ( arguments.length === 0 ) { + return _context; + } else { + _context = ctx; + } + }; + strategy.strategies = function() { + return _strategies; + }; + strategy.useStrategy = function( strategy ) { + var idx = 0, + exists = false; + while ( idx < _strategies.length ) { + if ( _strategies[idx].name === strategy.name ) { + _strategies[idx] = strategy; + exists = true; + break; + } + idx += 1; + } + if ( !exists ) { + _strategies.push( strategy ); + } + }; + strategy.reset = function() { + _strategies = []; + }; + if ( options.lazyInit ) { + _target.useStrategy = function() { + options.owner[options.prop] = strategy; + strategy.useStrategy.apply( strategy, arguments ); + }; + _target.context = function() { + options.owner[options.prop] = strategy; + return strategy.context.apply( strategy, arguments ); + }; + return _target; + } else { + return strategy; + } + }; + /* global DistinctPredicate,ConsecutiveDistinctPredicate */ + var strats = { + setTimeout: function(ms) { + return { + name: "setTimeout", + fn: function (next, data, envelope) { + setTimeout(function () { + next(data, envelope); + }, ms); + } + }; + }, + after: function(maxCalls, callback) { + var dispose = _.after(maxCalls, callback); + return { + name: "after", + fn: function (next, data, envelope) { + dispose(); + next(data, envelope); + } + }; + }, + throttle : function(ms) { + return { + name: "throttle", + fn: _.throttle(function(next, data, envelope) { + next(data, envelope); + }, ms) + }; + }, + debounce: function(ms, immediate) { + return { + name: "debounce", + fn: _.debounce(function(next, data, envelope) { + next(data, envelope); + }, ms, !!immediate) + }; + }, + predicate: function(pred) { + return { + name: "predicate", + fn: function(next, data, envelope) { + if(pred.call(this, data, envelope)) { + next.call(this, data, envelope); + } + } + }; + }, + distinct : function(options) { + options = options || {}; + var accessor = function(args) { + return args[0]; + }; + var check = options.all ? + new DistinctPredicate(accessor) : + new ConsecutiveDistinctPredicate(accessor); + return { + name : "distinct", + fn : function(next, data, envelope) { + if(check(data)) { + next(data, envelope); + } + } + }; + } + }; /*jshint -W098 */ - var ConsecutiveDistinctPredicate = function () { + var ConsecutiveDistinctPredicate = function (argsAccessor) { var previous; - return function ( data ) { + return function () { + var data = argsAccessor(arguments); var eq = false; if ( _.isString( data ) ) { eq = data === previous; @@ -42,10 +177,11 @@ }; }; /*jshint -W098 */ - var DistinctPredicate = function () { + var DistinctPredicate = function (argsAccessor) { var previous = []; - return function ( data ) { + return function () { + var data = argsAccessor(arguments); var isDistinct = !_.any( previous, function ( p ) { if ( _.isObject( data ) || _.isArray( data ) ) { return _.isEqual( data, p ); @@ -55,7 +191,7 @@ if ( isDistinct ) { previous.push( data ); } - return isDistinct; + return isDistinct; }; }; /* global postal, SubscriptionDefinition */ @@ -83,9 +219,7 @@ var SubscriptionDefinition = function ( channel, topic, callback ) { this.channel = channel; this.topic = topic; - this.callback = callback; - this.constraints = []; - this.context = null; + this.subscribe(callback); postal.configuration.bus.publish( { channel : postal.configuration.SYSTEM_CHANNEL, topic : "subscription.created", @@ -116,13 +250,7 @@ }, defer : function () { - var self = this; - var fn = this.callback; - this.callback = function ( data, env ) { - setTimeout( function () { - fn.call( self.context, data, env ); - }, 0 ); - }; + this.callback.useStrategy(postal.configuration.strategies.setTimeout(0)); return this; }, @@ -130,26 +258,20 @@ if ( _.isNaN( maxCalls ) || maxCalls <= 0 ) { throw "The value provided to disposeAfter (maxCalls) must be a number greater than zero."; } - var self = this; - var fn = this.callback; - var dispose = _.after( maxCalls, _.bind( function () { - this.unsubscribe(); - }, this ) ); - - this.callback = function () { - fn.apply( self.context, arguments ); - dispose(); - }; - return this; + var self = this; + self.callback.useStrategy(postal.configuration.strategies.after(maxCalls, function() { + self.unsubscribe.call(self); + })); + return self; }, distinctUntilChanged : function () { - this.withConstraint( new ConsecutiveDistinctPredicate() ); + this.callback.useStrategy(postal.configuration.strategies.distinct()); return this; }, distinct : function () { - this.withConstraint( new DistinctPredicate() ); + this.callback.useStrategy(postal.configuration.strategies.distinct({ all: true })); return this; }, @@ -162,22 +284,12 @@ if ( !_.isFunction( predicate ) ) { throw "Predicate constraint must be a function"; } - this.constraints.push( predicate ); + this.callback.useStrategy(postal.configuration.strategies.predicate(predicate)); return this; }, - withConstraints : function ( predicates ) { - var self = this; - if ( _.isArray( predicates ) ) { - _.each( predicates, function ( predicate ) { - self.withConstraint( predicate ); - } ); - } - return self; - }, - withContext : function ( context ) { - this.context = context; + this.callback.context(context); return this; }, @@ -194,13 +306,7 @@ if ( _.isNaN( milliseconds ) ) { throw "Milliseconds must be a number"; } - var self = this; - var fn = this.callback; - this.callback = function ( data, env ) { - setTimeout( function () { - fn.call( self.context, data, env ); - }, milliseconds ); - }; + this.callback.useStrategy(postal.configuration.strategies.setTimeout(milliseconds)); return this; }, @@ -208,13 +314,18 @@ if ( _.isNaN( milliseconds ) ) { throw "Milliseconds must be a number"; } - var fn = this.callback; - this.callback = _.throttle( fn, milliseconds ); + this.callback.useStrategy(postal.configuration.strategies.throttle(milliseconds)); return this; }, subscribe : function ( callback ) { this.callback = callback; + this.callback = new Strategy({ + owner : this, + prop : "callback", + context : this, // TODO: is this the best option? + lazyInit : true + }); return this; } }; @@ -259,13 +370,7 @@ /* global postal */ var fireSub = function ( subDef, envelope ) { if ( !subDef.inactive && postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) { - if ( _.all( subDef.constraints, function ( constraint ) { - return constraint.call( subDef.context, envelope.data, envelope ); - } ) ) { - if ( typeof subDef.callback === "function" ) { - subDef.callback.call( subDef.context, envelope.data, envelope ); - } - } + subDef.callback.call( subDef.callback.context ? subDef.callback.context() : this, envelope.data, envelope ); } }; @@ -366,7 +471,8 @@ bus : localBus, resolver : bindingsResolver, DEFAULT_CHANNEL : "/", - SYSTEM_CHANNEL : "postal" + SYSTEM_CHANNEL : "postal", + strategies : strats }, ChannelDefinition : ChannelDefinition, diff --git a/example/amd/js/libs/postal/postal.min.js b/example/amd/js/libs/postal/postal.min.js index 1d18aba..7b20459 100755 --- a/example/amd/js/libs/postal/postal.min.js +++ b/example/amd/js/libs/postal/postal.min.js @@ -4,4 +4,4 @@ License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license) Version 0.8.9 */ -(function(t,n){"object"==typeof module&&module.exports?module.exports=function(t){return t=t||require("underscore"),n(t)}:"function"==typeof define&&define.amd?define(["underscore"],function(i){return n(i,t)}):t.postal=n(t._,t)})(this,function(t,n){var i,s=function(){var n;return function(i){var s=!1;return t.isString(i)?(s=i===n,n=i):(s=t.isEqual(i,n),n=t.clone(i)),!s}},c=function(){var n=[];return function(i){var s=!t.any(n,function(n){return t.isObject(i)||t.isArray(i)?t.isEqual(i,n):i===n});return s&&n.push(i),s}},e=function(t){this.channel=t||i.configuration.DEFAULT_CHANNEL};e.prototype.subscribe=function(){return 1===arguments.length?new r(this.channel,arguments[0].topic,arguments[0].callback):new r(this.channel,arguments[0],arguments[1])},e.prototype.publish=function(){var t=1===arguments.length?"[object String]"===Object.prototype.toString.call(arguments[0])?{topic:arguments[0]}:arguments[0]:{topic:arguments[0],data:arguments[1]};return t.channel=this.channel,i.configuration.bus.publish(t)};var r=function(t,n,s){this.channel=t,this.topic=n,this.callback=s,this.constraints=[],this.context=null,i.configuration.bus.publish({channel:i.configuration.SYSTEM_CHANNEL,topic:"subscription.created",data:{event:"subscription.created",channel:t,topic:n}}),i.configuration.bus.subscribe(this)};r.prototype={unsubscribe:function(){this.inactive||(this.inactive=!0,i.configuration.bus.unsubscribe(this),i.configuration.bus.publish({channel:i.configuration.SYSTEM_CHANNEL,topic:"subscription.removed",data:{event:"subscription.removed",channel:this.channel,topic:this.topic}}))},defer:function(){var t=this,n=this.callback;return this.callback=function(i,s){setTimeout(function(){n.call(t.context,i,s)},0)},this},disposeAfter:function(n){if(t.isNaN(n)||0>=n)throw"The value provided to disposeAfter (maxCalls) must be a number greater than zero.";var i=this,s=this.callback,c=t.after(n,t.bind(function(){this.unsubscribe()},this));return this.callback=function(){s.apply(i.context,arguments),c()},this},distinctUntilChanged:function(){return this.withConstraint(new s),this},distinct:function(){return this.withConstraint(new c),this},once:function(){return this.disposeAfter(1),this},withConstraint:function(n){if(!t.isFunction(n))throw"Predicate constraint must be a function";return this.constraints.push(n),this},withConstraints:function(n){var i=this;return t.isArray(n)&&t.each(n,function(t){i.withConstraint(t)}),i},withContext:function(t){return this.context=t,this},withDebounce:function(n,i){if(t.isNaN(n))throw"Milliseconds must be a number";var s=this.callback;return this.callback=t.debounce(s,n,!!i),this},withDelay:function(n){if(t.isNaN(n))throw"Milliseconds must be a number";var i=this,s=this.callback;return this.callback=function(t,c){setTimeout(function(){s.call(i.context,t,c)},n)},this},withThrottle:function(n){if(t.isNaN(n))throw"Milliseconds must be a number";var i=this.callback;return this.callback=t.throttle(i,n),this},subscribe:function(t){return this.callback=t,this}};var o={cache:{},regex:{},compare:function(n,i){var s,c,e,r=this.cache[i]&&this.cache[i][n];return r!==undefined?r:((c=this.regex[n])||(s="^"+t.map(n.split("."),function(t){var n="";return e&&(n="#"!==e?"\\.\\b":"\\b"),n+="#"===t?"[\\s\\S]*":"*"===t?"[^.]+":t,e=t,n}).join("")+"$",c=this.regex[n]=RegExp(s)),this.cache[i]=this.cache[i]||{},this.cache[i][n]=r=c.test(i),r)},reset:function(){this.cache={},this.regex={}}},a=function(n,s){!n.inactive&&i.configuration.resolver.compare(n.topic,s.topic)&&t.all(n.constraints,function(t){return t.call(n.context,s.data,s)})&&"function"==typeof n.callback&&n.callback.call(n.context,s.data,s)},u=0,h=[],l=function(){for(;h.length;)h.shift().unsubscribe()},f={addWireTap:function(t){var n=this;return n.wireTaps.push(t),function(){var i=n.wireTaps.indexOf(t);-1!==i&&n.wireTaps.splice(i,1)}},publish:function(n){return++u,n.timeStamp=new Date,t.each(this.wireTaps,function(t){t(n.data,n)}),this.subscriptions[n.channel]&&t.each(this.subscriptions[n.channel],function(t){for(var i,s=0,c=t.length;c>s;)(i=t[s++])&&a(i,n)}),0===--u&&l(),n},reset:function(){this.subscriptions&&(t.each(this.subscriptions,function(n){t.each(n,function(t){for(;t.length;)t.pop().unsubscribe()})}),this.subscriptions={})},subscribe:function(t){var n,i=this.subscriptions[t.channel];return i||(i=this.subscriptions[t.channel]={}),n=this.subscriptions[t.channel][t.topic],n||(n=this.subscriptions[t.channel][t.topic]=[]),n.push(t),t},subscriptions:{},wireTaps:[],unsubscribe:function(t){if(u)return h.push(t),undefined;if(this.subscriptions[t.channel][t.topic])for(var n=this.subscriptions[t.channel][t.topic].length,i=0;n>i;){if(this.subscriptions[t.channel][t.topic][i]===t){this.subscriptions[t.channel][t.topic].splice(i,1);break}i+=1}}};if(i={configuration:{bus:f,resolver:o,DEFAULT_CHANNEL:"/",SYSTEM_CHANNEL:"postal"},ChannelDefinition:e,SubscriptionDefinition:r,channel:function(t){return new e(t)},subscribe:function(t){return new r(t.channel||i.configuration.DEFAULT_CHANNEL,t.topic,t.callback)},publish:function(t){return t.channel=t.channel||i.configuration.DEFAULT_CHANNEL,i.configuration.bus.publish(t)},addWireTap:function(t){return this.configuration.bus.addWireTap(t)},linkChannels:function(n,s){var c=[];return n=t.isArray(n)?n:[n],s=t.isArray(s)?s:[s],t.each(n,function(n){var e=n.topic||"#";t.each(s,function(s){var r=s.channel||i.configuration.DEFAULT_CHANNEL;c.push(i.subscribe({channel:n.channel||i.configuration.DEFAULT_CHANNEL,topic:e,callback:function(n,c){var e=t.clone(c);e.topic=t.isFunction(s.topic)?s.topic(c.topic):s.topic||c.topic,e.channel=r,e.data=n,i.publish(e)}}))})}),c},utils:{getSubscribersFor:function(){var t=arguments[0],n=arguments[1];return 1===arguments.length&&(t=arguments[0].channel||i.configuration.DEFAULT_CHANNEL,n=arguments[0].topic),i.configuration.bus.subscriptions[t]&&Object.prototype.hasOwnProperty.call(i.configuration.bus.subscriptions[t],n)?i.configuration.bus.subscriptions[t][n]:[]},reset:function(){i.configuration.bus.reset(),i.configuration.resolver.reset()}}},f.subscriptions[i.configuration.SYSTEM_CHANNEL]={},n&&n.hasOwnProperty("__postalReady__")&&t.isArray(n.__postalReady__))for(;n.__postalReady__.length;)n.__postalReady__.shift().onReady(i);return i}); \ No newline at end of file +(function(t,n){"object"==typeof module&&module.exports?module.exports=function(t){return t=t||require("underscore"),n(t)}:"function"==typeof define&&define.amd?define(["underscore"],function(i){return n(i,t)}):t.postal=n(t._,t)})(this,function(t,n){var i,e=function(t){var n=t.owner[t.prop];if("function"!=typeof n)throw Error("Strategies can only target methods.");var i=[],e=t.context||t.owner,r=function(){var t=0,r=function r(){var c,s=Array.prototype.slice.call(arguments,0),o=t;t+=1,i.length>o?(c=i[o],c.fn.apply(c.context||e,[r].concat(s))):n.apply(e,s)};r.apply(this,arguments)};return r.target=function(){return n},r.context=function(t){return 0===arguments.length?e:(e=t,undefined)},r.strategies=function(){return i},r.useStrategy=function(t){for(var n=0,e=!1;i.length>n;){if(i[n].name===t.name){i[n]=t,e=!0;break}n+=1}e||i.push(t)},r.reset=function(){i=[]},t.lazyInit?(n.useStrategy=function(){t.owner[t.prop]=r,r.useStrategy.apply(r,arguments)},n.context=function(){return t.owner[t.prop]=r,r.context.apply(r,arguments)},n):r},r={setTimeout:function(t){return{name:"setTimeout",fn:function(n,i,e){setTimeout(function(){n(i,e)},t)}}},after:function(n,i){var e=t.after(n,i);return{name:"after",fn:function(t,n,i){e(),t(n,i)}}},throttle:function(n){return{name:"throttle",fn:t.throttle(function(t,n,i){t(n,i)},n)}},debounce:function(n,i){return{name:"debounce",fn:t.debounce(function(t,n,i){t(n,i)},n,!!i)}},predicate:function(t){return{name:"predicate",fn:function(n,i,e){t.call(this,i,e)&&n.call(this,i,e)}}},distinct:function(t){t=t||{};var n=function(t){return t[0]},i=t.all?new s(n):new c(n);return{name:"distinct",fn:function(t,n,e){i(n)&&t(n,e)}}}},c=function(n){var i;return function(){var e=n(arguments),r=!1;return t.isString(e)?(r=e===i,i=e):(r=t.isEqual(e,i),i=t.clone(e)),!r}},s=function(n){var i=[];return function(){var e=n(arguments),r=!t.any(i,function(n){return t.isObject(e)||t.isArray(e)?t.isEqual(e,n):e===n});return r&&i.push(e),r}},o=function(t){this.channel=t||i.configuration.DEFAULT_CHANNEL};o.prototype.subscribe=function(){return 1===arguments.length?new a(this.channel,arguments[0].topic,arguments[0].callback):new a(this.channel,arguments[0],arguments[1])},o.prototype.publish=function(){var t=1===arguments.length?"[object String]"===Object.prototype.toString.call(arguments[0])?{topic:arguments[0]}:arguments[0]:{topic:arguments[0],data:arguments[1]};return t.channel=this.channel,i.configuration.bus.publish(t)};var a=function(t,n,e){this.channel=t,this.topic=n,this.subscribe(e),i.configuration.bus.publish({channel:i.configuration.SYSTEM_CHANNEL,topic:"subscription.created",data:{event:"subscription.created",channel:t,topic:n}}),i.configuration.bus.subscribe(this)};a.prototype={unsubscribe:function(){this.inactive||(this.inactive=!0,i.configuration.bus.unsubscribe(this),i.configuration.bus.publish({channel:i.configuration.SYSTEM_CHANNEL,topic:"subscription.removed",data:{event:"subscription.removed",channel:this.channel,topic:this.topic}}))},defer:function(){return this.callback.useStrategy(i.configuration.strategies.setTimeout(0)),this},disposeAfter:function(n){if(t.isNaN(n)||0>=n)throw"The value provided to disposeAfter (maxCalls) must be a number greater than zero.";var e=this;return e.callback.useStrategy(i.configuration.strategies.after(n,function(){e.unsubscribe.call(e)})),e},distinctUntilChanged:function(){return this.callback.useStrategy(i.configuration.strategies.distinct()),this},distinct:function(){return this.callback.useStrategy(i.configuration.strategies.distinct({all:!0})),this},once:function(){return this.disposeAfter(1),this},withConstraint:function(n){if(!t.isFunction(n))throw"Predicate constraint must be a function";return this.callback.useStrategy(i.configuration.strategies.predicate(n)),this},withContext:function(t){return this.callback.context(t),this},withDebounce:function(n,i){if(t.isNaN(n))throw"Milliseconds must be a number";var e=this.callback;return this.callback=t.debounce(e,n,!!i),this},withDelay:function(n){if(t.isNaN(n))throw"Milliseconds must be a number";return this.callback.useStrategy(i.configuration.strategies.setTimeout(n)),this},withThrottle:function(n){if(t.isNaN(n))throw"Milliseconds must be a number";return this.callback.useStrategy(i.configuration.strategies.throttle(n)),this},subscribe:function(t){return this.callback=t,this.callback=new e({owner:this,prop:"callback",context:this,lazyInit:!0}),this}};var u={cache:{},regex:{},compare:function(n,i){var e,r,c,s=this.cache[i]&&this.cache[i][n];return s!==undefined?s:((r=this.regex[n])||(e="^"+t.map(n.split("."),function(t){var n="";return c&&(n="#"!==c?"\\.\\b":"\\b"),n+="#"===t?"[\\s\\S]*":"*"===t?"[^.]+":t,c=t,n}).join("")+"$",r=this.regex[n]=RegExp(e)),this.cache[i]=this.cache[i]||{},this.cache[i][n]=s=r.test(i),s)},reset:function(){this.cache={},this.regex={}}},h=function(t,n){!t.inactive&&i.configuration.resolver.compare(t.topic,n.topic)&&t.callback.call(t.callback.context?t.callback.context():this,n.data,n)},f=0,l=[],p=function(){for(;l.length;)l.shift().unsubscribe()},b={addWireTap:function(t){var n=this;return n.wireTaps.push(t),function(){var i=n.wireTaps.indexOf(t);-1!==i&&n.wireTaps.splice(i,1)}},publish:function(n){return++f,n.timeStamp=new Date,t.each(this.wireTaps,function(t){t(n.data,n)}),this.subscriptions[n.channel]&&t.each(this.subscriptions[n.channel],function(t){for(var i,e=0,r=t.length;r>e;)(i=t[e++])&&h(i,n)}),0===--f&&p(),n},reset:function(){this.subscriptions&&(t.each(this.subscriptions,function(n){t.each(n,function(t){for(;t.length;)t.pop().unsubscribe()})}),this.subscriptions={})},subscribe:function(t){var n,i=this.subscriptions[t.channel];return i||(i=this.subscriptions[t.channel]={}),n=this.subscriptions[t.channel][t.topic],n||(n=this.subscriptions[t.channel][t.topic]=[]),n.push(t),t},subscriptions:{},wireTaps:[],unsubscribe:function(t){if(f)return l.push(t),undefined;if(this.subscriptions[t.channel][t.topic])for(var n=this.subscriptions[t.channel][t.topic].length,i=0;n>i;){if(this.subscriptions[t.channel][t.topic][i]===t){this.subscriptions[t.channel][t.topic].splice(i,1);break}i+=1}}};if(i={configuration:{bus:b,resolver:u,DEFAULT_CHANNEL:"/",SYSTEM_CHANNEL:"postal",strategies:r},ChannelDefinition:o,SubscriptionDefinition:a,channel:function(t){return new o(t)},subscribe:function(t){return new a(t.channel||i.configuration.DEFAULT_CHANNEL,t.topic,t.callback)},publish:function(t){return t.channel=t.channel||i.configuration.DEFAULT_CHANNEL,i.configuration.bus.publish(t)},addWireTap:function(t){return this.configuration.bus.addWireTap(t)},linkChannels:function(n,e){var r=[];return n=t.isArray(n)?n:[n],e=t.isArray(e)?e:[e],t.each(n,function(n){var c=n.topic||"#";t.each(e,function(e){var s=e.channel||i.configuration.DEFAULT_CHANNEL;r.push(i.subscribe({channel:n.channel||i.configuration.DEFAULT_CHANNEL,topic:c,callback:function(n,r){var c=t.clone(r);c.topic=t.isFunction(e.topic)?e.topic(r.topic):e.topic||r.topic,c.channel=s,c.data=n,i.publish(c)}}))})}),r},utils:{getSubscribersFor:function(){var t=arguments[0],n=arguments[1];return 1===arguments.length&&(t=arguments[0].channel||i.configuration.DEFAULT_CHANNEL,n=arguments[0].topic),i.configuration.bus.subscriptions[t]&&Object.prototype.hasOwnProperty.call(i.configuration.bus.subscriptions[t],n)?i.configuration.bus.subscriptions[t][n]:[]},reset:function(){i.configuration.bus.reset(),i.configuration.resolver.reset()}}},b.subscriptions[i.configuration.SYSTEM_CHANNEL]={},n&&n.hasOwnProperty("__postalReady__")&&t.isArray(n.__postalReady__))for(;n.__postalReady__.length;)n.__postalReady__.shift().onReady(i);return i}); \ No newline at end of file diff --git a/example/standard/js/postal.js b/example/standard/js/postal.js index 00dd3e8..dad99dd 100755 --- a/example/standard/js/postal.js +++ b/example/standard/js/postal.js @@ -25,10 +25,145 @@ var postal; + var Strategy = function( options ) { + var _target = options.owner[options.prop]; + if ( typeof _target !== "function" ) { + throw new Error( "Strategies can only target methods." ); + } + var _strategies = []; + var _context = options.context || options.owner; + var strategy = function() { + var idx = 0; + var next = function next() { + var args = Array.prototype.slice.call( arguments, 0 ); + var thisIdx = idx; + var strategy; + idx += 1; + if ( thisIdx < _strategies.length ) { + strategy = _strategies[thisIdx]; + strategy.fn.apply( strategy.context || _context, [next].concat( args ) ); + } else { + _target.apply( _context, args ); + } + }; + next.apply( this, arguments ); + }; + strategy.target = function() { + return _target; + }; + strategy.context = function( ctx ) { + if ( arguments.length === 0 ) { + return _context; + } else { + _context = ctx; + } + }; + strategy.strategies = function() { + return _strategies; + }; + strategy.useStrategy = function( strategy ) { + var idx = 0, + exists = false; + while ( idx < _strategies.length ) { + if ( _strategies[idx].name === strategy.name ) { + _strategies[idx] = strategy; + exists = true; + break; + } + idx += 1; + } + if ( !exists ) { + _strategies.push( strategy ); + } + }; + strategy.reset = function() { + _strategies = []; + }; + if ( options.lazyInit ) { + _target.useStrategy = function() { + options.owner[options.prop] = strategy; + strategy.useStrategy.apply( strategy, arguments ); + }; + _target.context = function() { + options.owner[options.prop] = strategy; + return strategy.context.apply( strategy, arguments ); + }; + return _target; + } else { + return strategy; + } + }; + /* global DistinctPredicate,ConsecutiveDistinctPredicate */ + var strats = { + setTimeout: function(ms) { + return { + name: "setTimeout", + fn: function (next, data, envelope) { + setTimeout(function () { + next(data, envelope); + }, ms); + } + }; + }, + after: function(maxCalls, callback) { + var dispose = _.after(maxCalls, callback); + return { + name: "after", + fn: function (next, data, envelope) { + dispose(); + next(data, envelope); + } + }; + }, + throttle : function(ms) { + return { + name: "throttle", + fn: _.throttle(function(next, data, envelope) { + next(data, envelope); + }, ms) + }; + }, + debounce: function(ms, immediate) { + return { + name: "debounce", + fn: _.debounce(function(next, data, envelope) { + next(data, envelope); + }, ms, !!immediate) + }; + }, + predicate: function(pred) { + return { + name: "predicate", + fn: function(next, data, envelope) { + if(pred.call(this, data, envelope)) { + next.call(this, data, envelope); + } + } + }; + }, + distinct : function(options) { + options = options || {}; + var accessor = function(args) { + return args[0]; + }; + var check = options.all ? + new DistinctPredicate(accessor) : + new ConsecutiveDistinctPredicate(accessor); + return { + name : "distinct", + fn : function(next, data, envelope) { + if(check(data)) { + next(data, envelope); + } + } + }; + } + }; /*jshint -W098 */ - var ConsecutiveDistinctPredicate = function () { + var ConsecutiveDistinctPredicate = function (argsAccessor) { var previous; - return function ( data ) { + return function () { + var data = argsAccessor(arguments); var eq = false; if ( _.isString( data ) ) { eq = data === previous; @@ -42,10 +177,11 @@ }; }; /*jshint -W098 */ - var DistinctPredicate = function () { + var DistinctPredicate = function (argsAccessor) { var previous = []; - return function ( data ) { + return function () { + var data = argsAccessor(arguments); var isDistinct = !_.any( previous, function ( p ) { if ( _.isObject( data ) || _.isArray( data ) ) { return _.isEqual( data, p ); @@ -55,7 +191,7 @@ if ( isDistinct ) { previous.push( data ); } - return isDistinct; + return isDistinct; }; }; /* global postal, SubscriptionDefinition */ @@ -83,9 +219,7 @@ var SubscriptionDefinition = function ( channel, topic, callback ) { this.channel = channel; this.topic = topic; - this.callback = callback; - this.constraints = []; - this.context = null; + this.subscribe(callback); postal.configuration.bus.publish( { channel : postal.configuration.SYSTEM_CHANNEL, topic : "subscription.created", @@ -116,13 +250,7 @@ }, defer : function () { - var self = this; - var fn = this.callback; - this.callback = function ( data, env ) { - setTimeout( function () { - fn.call( self.context, data, env ); - }, 0 ); - }; + this.callback.useStrategy(postal.configuration.strategies.setTimeout(0)); return this; }, @@ -130,26 +258,20 @@ if ( _.isNaN( maxCalls ) || maxCalls <= 0 ) { throw "The value provided to disposeAfter (maxCalls) must be a number greater than zero."; } - var self = this; - var fn = this.callback; - var dispose = _.after( maxCalls, _.bind( function () { - this.unsubscribe(); - }, this ) ); - - this.callback = function () { - fn.apply( self.context, arguments ); - dispose(); - }; - return this; + var self = this; + self.callback.useStrategy(postal.configuration.strategies.after(maxCalls, function() { + self.unsubscribe.call(self); + })); + return self; }, distinctUntilChanged : function () { - this.withConstraint( new ConsecutiveDistinctPredicate() ); + this.callback.useStrategy(postal.configuration.strategies.distinct()); return this; }, distinct : function () { - this.withConstraint( new DistinctPredicate() ); + this.callback.useStrategy(postal.configuration.strategies.distinct({ all: true })); return this; }, @@ -162,22 +284,12 @@ if ( !_.isFunction( predicate ) ) { throw "Predicate constraint must be a function"; } - this.constraints.push( predicate ); + this.callback.useStrategy(postal.configuration.strategies.predicate(predicate)); return this; }, - withConstraints : function ( predicates ) { - var self = this; - if ( _.isArray( predicates ) ) { - _.each( predicates, function ( predicate ) { - self.withConstraint( predicate ); - } ); - } - return self; - }, - withContext : function ( context ) { - this.context = context; + this.callback.context(context); return this; }, @@ -194,13 +306,7 @@ if ( _.isNaN( milliseconds ) ) { throw "Milliseconds must be a number"; } - var self = this; - var fn = this.callback; - this.callback = function ( data, env ) { - setTimeout( function () { - fn.call( self.context, data, env ); - }, milliseconds ); - }; + this.callback.useStrategy(postal.configuration.strategies.setTimeout(milliseconds)); return this; }, @@ -208,13 +314,18 @@ if ( _.isNaN( milliseconds ) ) { throw "Milliseconds must be a number"; } - var fn = this.callback; - this.callback = _.throttle( fn, milliseconds ); + this.callback.useStrategy(postal.configuration.strategies.throttle(milliseconds)); return this; }, subscribe : function ( callback ) { this.callback = callback; + this.callback = new Strategy({ + owner : this, + prop : "callback", + context : this, // TODO: is this the best option? + lazyInit : true + }); return this; } }; @@ -259,13 +370,7 @@ /* global postal */ var fireSub = function ( subDef, envelope ) { if ( !subDef.inactive && postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) { - if ( _.all( subDef.constraints, function ( constraint ) { - return constraint.call( subDef.context, envelope.data, envelope ); - } ) ) { - if ( typeof subDef.callback === "function" ) { - subDef.callback.call( subDef.context, envelope.data, envelope ); - } - } + subDef.callback.call( subDef.callback.context ? subDef.callback.context() : this, envelope.data, envelope ); } }; @@ -366,7 +471,8 @@ bus : localBus, resolver : bindingsResolver, DEFAULT_CHANNEL : "/", - SYSTEM_CHANNEL : "postal" + SYSTEM_CHANNEL : "postal", + strategies : strats }, ChannelDefinition : ChannelDefinition, diff --git a/example/standard/js/postal.min.js b/example/standard/js/postal.min.js index 1d18aba..7b20459 100755 --- a/example/standard/js/postal.min.js +++ b/example/standard/js/postal.min.js @@ -4,4 +4,4 @@ License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license) Version 0.8.9 */ -(function(t,n){"object"==typeof module&&module.exports?module.exports=function(t){return t=t||require("underscore"),n(t)}:"function"==typeof define&&define.amd?define(["underscore"],function(i){return n(i,t)}):t.postal=n(t._,t)})(this,function(t,n){var i,s=function(){var n;return function(i){var s=!1;return t.isString(i)?(s=i===n,n=i):(s=t.isEqual(i,n),n=t.clone(i)),!s}},c=function(){var n=[];return function(i){var s=!t.any(n,function(n){return t.isObject(i)||t.isArray(i)?t.isEqual(i,n):i===n});return s&&n.push(i),s}},e=function(t){this.channel=t||i.configuration.DEFAULT_CHANNEL};e.prototype.subscribe=function(){return 1===arguments.length?new r(this.channel,arguments[0].topic,arguments[0].callback):new r(this.channel,arguments[0],arguments[1])},e.prototype.publish=function(){var t=1===arguments.length?"[object String]"===Object.prototype.toString.call(arguments[0])?{topic:arguments[0]}:arguments[0]:{topic:arguments[0],data:arguments[1]};return t.channel=this.channel,i.configuration.bus.publish(t)};var r=function(t,n,s){this.channel=t,this.topic=n,this.callback=s,this.constraints=[],this.context=null,i.configuration.bus.publish({channel:i.configuration.SYSTEM_CHANNEL,topic:"subscription.created",data:{event:"subscription.created",channel:t,topic:n}}),i.configuration.bus.subscribe(this)};r.prototype={unsubscribe:function(){this.inactive||(this.inactive=!0,i.configuration.bus.unsubscribe(this),i.configuration.bus.publish({channel:i.configuration.SYSTEM_CHANNEL,topic:"subscription.removed",data:{event:"subscription.removed",channel:this.channel,topic:this.topic}}))},defer:function(){var t=this,n=this.callback;return this.callback=function(i,s){setTimeout(function(){n.call(t.context,i,s)},0)},this},disposeAfter:function(n){if(t.isNaN(n)||0>=n)throw"The value provided to disposeAfter (maxCalls) must be a number greater than zero.";var i=this,s=this.callback,c=t.after(n,t.bind(function(){this.unsubscribe()},this));return this.callback=function(){s.apply(i.context,arguments),c()},this},distinctUntilChanged:function(){return this.withConstraint(new s),this},distinct:function(){return this.withConstraint(new c),this},once:function(){return this.disposeAfter(1),this},withConstraint:function(n){if(!t.isFunction(n))throw"Predicate constraint must be a function";return this.constraints.push(n),this},withConstraints:function(n){var i=this;return t.isArray(n)&&t.each(n,function(t){i.withConstraint(t)}),i},withContext:function(t){return this.context=t,this},withDebounce:function(n,i){if(t.isNaN(n))throw"Milliseconds must be a number";var s=this.callback;return this.callback=t.debounce(s,n,!!i),this},withDelay:function(n){if(t.isNaN(n))throw"Milliseconds must be a number";var i=this,s=this.callback;return this.callback=function(t,c){setTimeout(function(){s.call(i.context,t,c)},n)},this},withThrottle:function(n){if(t.isNaN(n))throw"Milliseconds must be a number";var i=this.callback;return this.callback=t.throttle(i,n),this},subscribe:function(t){return this.callback=t,this}};var o={cache:{},regex:{},compare:function(n,i){var s,c,e,r=this.cache[i]&&this.cache[i][n];return r!==undefined?r:((c=this.regex[n])||(s="^"+t.map(n.split("."),function(t){var n="";return e&&(n="#"!==e?"\\.\\b":"\\b"),n+="#"===t?"[\\s\\S]*":"*"===t?"[^.]+":t,e=t,n}).join("")+"$",c=this.regex[n]=RegExp(s)),this.cache[i]=this.cache[i]||{},this.cache[i][n]=r=c.test(i),r)},reset:function(){this.cache={},this.regex={}}},a=function(n,s){!n.inactive&&i.configuration.resolver.compare(n.topic,s.topic)&&t.all(n.constraints,function(t){return t.call(n.context,s.data,s)})&&"function"==typeof n.callback&&n.callback.call(n.context,s.data,s)},u=0,h=[],l=function(){for(;h.length;)h.shift().unsubscribe()},f={addWireTap:function(t){var n=this;return n.wireTaps.push(t),function(){var i=n.wireTaps.indexOf(t);-1!==i&&n.wireTaps.splice(i,1)}},publish:function(n){return++u,n.timeStamp=new Date,t.each(this.wireTaps,function(t){t(n.data,n)}),this.subscriptions[n.channel]&&t.each(this.subscriptions[n.channel],function(t){for(var i,s=0,c=t.length;c>s;)(i=t[s++])&&a(i,n)}),0===--u&&l(),n},reset:function(){this.subscriptions&&(t.each(this.subscriptions,function(n){t.each(n,function(t){for(;t.length;)t.pop().unsubscribe()})}),this.subscriptions={})},subscribe:function(t){var n,i=this.subscriptions[t.channel];return i||(i=this.subscriptions[t.channel]={}),n=this.subscriptions[t.channel][t.topic],n||(n=this.subscriptions[t.channel][t.topic]=[]),n.push(t),t},subscriptions:{},wireTaps:[],unsubscribe:function(t){if(u)return h.push(t),undefined;if(this.subscriptions[t.channel][t.topic])for(var n=this.subscriptions[t.channel][t.topic].length,i=0;n>i;){if(this.subscriptions[t.channel][t.topic][i]===t){this.subscriptions[t.channel][t.topic].splice(i,1);break}i+=1}}};if(i={configuration:{bus:f,resolver:o,DEFAULT_CHANNEL:"/",SYSTEM_CHANNEL:"postal"},ChannelDefinition:e,SubscriptionDefinition:r,channel:function(t){return new e(t)},subscribe:function(t){return new r(t.channel||i.configuration.DEFAULT_CHANNEL,t.topic,t.callback)},publish:function(t){return t.channel=t.channel||i.configuration.DEFAULT_CHANNEL,i.configuration.bus.publish(t)},addWireTap:function(t){return this.configuration.bus.addWireTap(t)},linkChannels:function(n,s){var c=[];return n=t.isArray(n)?n:[n],s=t.isArray(s)?s:[s],t.each(n,function(n){var e=n.topic||"#";t.each(s,function(s){var r=s.channel||i.configuration.DEFAULT_CHANNEL;c.push(i.subscribe({channel:n.channel||i.configuration.DEFAULT_CHANNEL,topic:e,callback:function(n,c){var e=t.clone(c);e.topic=t.isFunction(s.topic)?s.topic(c.topic):s.topic||c.topic,e.channel=r,e.data=n,i.publish(e)}}))})}),c},utils:{getSubscribersFor:function(){var t=arguments[0],n=arguments[1];return 1===arguments.length&&(t=arguments[0].channel||i.configuration.DEFAULT_CHANNEL,n=arguments[0].topic),i.configuration.bus.subscriptions[t]&&Object.prototype.hasOwnProperty.call(i.configuration.bus.subscriptions[t],n)?i.configuration.bus.subscriptions[t][n]:[]},reset:function(){i.configuration.bus.reset(),i.configuration.resolver.reset()}}},f.subscriptions[i.configuration.SYSTEM_CHANNEL]={},n&&n.hasOwnProperty("__postalReady__")&&t.isArray(n.__postalReady__))for(;n.__postalReady__.length;)n.__postalReady__.shift().onReady(i);return i}); \ No newline at end of file +(function(t,n){"object"==typeof module&&module.exports?module.exports=function(t){return t=t||require("underscore"),n(t)}:"function"==typeof define&&define.amd?define(["underscore"],function(i){return n(i,t)}):t.postal=n(t._,t)})(this,function(t,n){var i,e=function(t){var n=t.owner[t.prop];if("function"!=typeof n)throw Error("Strategies can only target methods.");var i=[],e=t.context||t.owner,r=function(){var t=0,r=function r(){var c,s=Array.prototype.slice.call(arguments,0),o=t;t+=1,i.length>o?(c=i[o],c.fn.apply(c.context||e,[r].concat(s))):n.apply(e,s)};r.apply(this,arguments)};return r.target=function(){return n},r.context=function(t){return 0===arguments.length?e:(e=t,undefined)},r.strategies=function(){return i},r.useStrategy=function(t){for(var n=0,e=!1;i.length>n;){if(i[n].name===t.name){i[n]=t,e=!0;break}n+=1}e||i.push(t)},r.reset=function(){i=[]},t.lazyInit?(n.useStrategy=function(){t.owner[t.prop]=r,r.useStrategy.apply(r,arguments)},n.context=function(){return t.owner[t.prop]=r,r.context.apply(r,arguments)},n):r},r={setTimeout:function(t){return{name:"setTimeout",fn:function(n,i,e){setTimeout(function(){n(i,e)},t)}}},after:function(n,i){var e=t.after(n,i);return{name:"after",fn:function(t,n,i){e(),t(n,i)}}},throttle:function(n){return{name:"throttle",fn:t.throttle(function(t,n,i){t(n,i)},n)}},debounce:function(n,i){return{name:"debounce",fn:t.debounce(function(t,n,i){t(n,i)},n,!!i)}},predicate:function(t){return{name:"predicate",fn:function(n,i,e){t.call(this,i,e)&&n.call(this,i,e)}}},distinct:function(t){t=t||{};var n=function(t){return t[0]},i=t.all?new s(n):new c(n);return{name:"distinct",fn:function(t,n,e){i(n)&&t(n,e)}}}},c=function(n){var i;return function(){var e=n(arguments),r=!1;return t.isString(e)?(r=e===i,i=e):(r=t.isEqual(e,i),i=t.clone(e)),!r}},s=function(n){var i=[];return function(){var e=n(arguments),r=!t.any(i,function(n){return t.isObject(e)||t.isArray(e)?t.isEqual(e,n):e===n});return r&&i.push(e),r}},o=function(t){this.channel=t||i.configuration.DEFAULT_CHANNEL};o.prototype.subscribe=function(){return 1===arguments.length?new a(this.channel,arguments[0].topic,arguments[0].callback):new a(this.channel,arguments[0],arguments[1])},o.prototype.publish=function(){var t=1===arguments.length?"[object String]"===Object.prototype.toString.call(arguments[0])?{topic:arguments[0]}:arguments[0]:{topic:arguments[0],data:arguments[1]};return t.channel=this.channel,i.configuration.bus.publish(t)};var a=function(t,n,e){this.channel=t,this.topic=n,this.subscribe(e),i.configuration.bus.publish({channel:i.configuration.SYSTEM_CHANNEL,topic:"subscription.created",data:{event:"subscription.created",channel:t,topic:n}}),i.configuration.bus.subscribe(this)};a.prototype={unsubscribe:function(){this.inactive||(this.inactive=!0,i.configuration.bus.unsubscribe(this),i.configuration.bus.publish({channel:i.configuration.SYSTEM_CHANNEL,topic:"subscription.removed",data:{event:"subscription.removed",channel:this.channel,topic:this.topic}}))},defer:function(){return this.callback.useStrategy(i.configuration.strategies.setTimeout(0)),this},disposeAfter:function(n){if(t.isNaN(n)||0>=n)throw"The value provided to disposeAfter (maxCalls) must be a number greater than zero.";var e=this;return e.callback.useStrategy(i.configuration.strategies.after(n,function(){e.unsubscribe.call(e)})),e},distinctUntilChanged:function(){return this.callback.useStrategy(i.configuration.strategies.distinct()),this},distinct:function(){return this.callback.useStrategy(i.configuration.strategies.distinct({all:!0})),this},once:function(){return this.disposeAfter(1),this},withConstraint:function(n){if(!t.isFunction(n))throw"Predicate constraint must be a function";return this.callback.useStrategy(i.configuration.strategies.predicate(n)),this},withContext:function(t){return this.callback.context(t),this},withDebounce:function(n,i){if(t.isNaN(n))throw"Milliseconds must be a number";var e=this.callback;return this.callback=t.debounce(e,n,!!i),this},withDelay:function(n){if(t.isNaN(n))throw"Milliseconds must be a number";return this.callback.useStrategy(i.configuration.strategies.setTimeout(n)),this},withThrottle:function(n){if(t.isNaN(n))throw"Milliseconds must be a number";return this.callback.useStrategy(i.configuration.strategies.throttle(n)),this},subscribe:function(t){return this.callback=t,this.callback=new e({owner:this,prop:"callback",context:this,lazyInit:!0}),this}};var u={cache:{},regex:{},compare:function(n,i){var e,r,c,s=this.cache[i]&&this.cache[i][n];return s!==undefined?s:((r=this.regex[n])||(e="^"+t.map(n.split("."),function(t){var n="";return c&&(n="#"!==c?"\\.\\b":"\\b"),n+="#"===t?"[\\s\\S]*":"*"===t?"[^.]+":t,c=t,n}).join("")+"$",r=this.regex[n]=RegExp(e)),this.cache[i]=this.cache[i]||{},this.cache[i][n]=s=r.test(i),s)},reset:function(){this.cache={},this.regex={}}},h=function(t,n){!t.inactive&&i.configuration.resolver.compare(t.topic,n.topic)&&t.callback.call(t.callback.context?t.callback.context():this,n.data,n)},f=0,l=[],p=function(){for(;l.length;)l.shift().unsubscribe()},b={addWireTap:function(t){var n=this;return n.wireTaps.push(t),function(){var i=n.wireTaps.indexOf(t);-1!==i&&n.wireTaps.splice(i,1)}},publish:function(n){return++f,n.timeStamp=new Date,t.each(this.wireTaps,function(t){t(n.data,n)}),this.subscriptions[n.channel]&&t.each(this.subscriptions[n.channel],function(t){for(var i,e=0,r=t.length;r>e;)(i=t[e++])&&h(i,n)}),0===--f&&p(),n},reset:function(){this.subscriptions&&(t.each(this.subscriptions,function(n){t.each(n,function(t){for(;t.length;)t.pop().unsubscribe()})}),this.subscriptions={})},subscribe:function(t){var n,i=this.subscriptions[t.channel];return i||(i=this.subscriptions[t.channel]={}),n=this.subscriptions[t.channel][t.topic],n||(n=this.subscriptions[t.channel][t.topic]=[]),n.push(t),t},subscriptions:{},wireTaps:[],unsubscribe:function(t){if(f)return l.push(t),undefined;if(this.subscriptions[t.channel][t.topic])for(var n=this.subscriptions[t.channel][t.topic].length,i=0;n>i;){if(this.subscriptions[t.channel][t.topic][i]===t){this.subscriptions[t.channel][t.topic].splice(i,1);break}i+=1}}};if(i={configuration:{bus:b,resolver:u,DEFAULT_CHANNEL:"/",SYSTEM_CHANNEL:"postal",strategies:r},ChannelDefinition:o,SubscriptionDefinition:a,channel:function(t){return new o(t)},subscribe:function(t){return new a(t.channel||i.configuration.DEFAULT_CHANNEL,t.topic,t.callback)},publish:function(t){return t.channel=t.channel||i.configuration.DEFAULT_CHANNEL,i.configuration.bus.publish(t)},addWireTap:function(t){return this.configuration.bus.addWireTap(t)},linkChannels:function(n,e){var r=[];return n=t.isArray(n)?n:[n],e=t.isArray(e)?e:[e],t.each(n,function(n){var c=n.topic||"#";t.each(e,function(e){var s=e.channel||i.configuration.DEFAULT_CHANNEL;r.push(i.subscribe({channel:n.channel||i.configuration.DEFAULT_CHANNEL,topic:c,callback:function(n,r){var c=t.clone(r);c.topic=t.isFunction(e.topic)?e.topic(r.topic):e.topic||r.topic,c.channel=s,c.data=n,i.publish(c)}}))})}),r},utils:{getSubscribersFor:function(){var t=arguments[0],n=arguments[1];return 1===arguments.length&&(t=arguments[0].channel||i.configuration.DEFAULT_CHANNEL,n=arguments[0].topic),i.configuration.bus.subscriptions[t]&&Object.prototype.hasOwnProperty.call(i.configuration.bus.subscriptions[t],n)?i.configuration.bus.subscriptions[t][n]:[]},reset:function(){i.configuration.bus.reset(),i.configuration.resolver.reset()}}},b.subscriptions[i.configuration.SYSTEM_CHANNEL]={},n&&n.hasOwnProperty("__postalReady__")&&t.isArray(n.__postalReady__))for(;n.__postalReady__.length;)n.__postalReady__.shift().onReady(i);return i}); \ No newline at end of file diff --git a/lib/postal.js b/lib/postal.js index 00dd3e8..dad99dd 100755 --- a/lib/postal.js +++ b/lib/postal.js @@ -25,10 +25,145 @@ var postal; + var Strategy = function( options ) { + var _target = options.owner[options.prop]; + if ( typeof _target !== "function" ) { + throw new Error( "Strategies can only target methods." ); + } + var _strategies = []; + var _context = options.context || options.owner; + var strategy = function() { + var idx = 0; + var next = function next() { + var args = Array.prototype.slice.call( arguments, 0 ); + var thisIdx = idx; + var strategy; + idx += 1; + if ( thisIdx < _strategies.length ) { + strategy = _strategies[thisIdx]; + strategy.fn.apply( strategy.context || _context, [next].concat( args ) ); + } else { + _target.apply( _context, args ); + } + }; + next.apply( this, arguments ); + }; + strategy.target = function() { + return _target; + }; + strategy.context = function( ctx ) { + if ( arguments.length === 0 ) { + return _context; + } else { + _context = ctx; + } + }; + strategy.strategies = function() { + return _strategies; + }; + strategy.useStrategy = function( strategy ) { + var idx = 0, + exists = false; + while ( idx < _strategies.length ) { + if ( _strategies[idx].name === strategy.name ) { + _strategies[idx] = strategy; + exists = true; + break; + } + idx += 1; + } + if ( !exists ) { + _strategies.push( strategy ); + } + }; + strategy.reset = function() { + _strategies = []; + }; + if ( options.lazyInit ) { + _target.useStrategy = function() { + options.owner[options.prop] = strategy; + strategy.useStrategy.apply( strategy, arguments ); + }; + _target.context = function() { + options.owner[options.prop] = strategy; + return strategy.context.apply( strategy, arguments ); + }; + return _target; + } else { + return strategy; + } + }; + /* global DistinctPredicate,ConsecutiveDistinctPredicate */ + var strats = { + setTimeout: function(ms) { + return { + name: "setTimeout", + fn: function (next, data, envelope) { + setTimeout(function () { + next(data, envelope); + }, ms); + } + }; + }, + after: function(maxCalls, callback) { + var dispose = _.after(maxCalls, callback); + return { + name: "after", + fn: function (next, data, envelope) { + dispose(); + next(data, envelope); + } + }; + }, + throttle : function(ms) { + return { + name: "throttle", + fn: _.throttle(function(next, data, envelope) { + next(data, envelope); + }, ms) + }; + }, + debounce: function(ms, immediate) { + return { + name: "debounce", + fn: _.debounce(function(next, data, envelope) { + next(data, envelope); + }, ms, !!immediate) + }; + }, + predicate: function(pred) { + return { + name: "predicate", + fn: function(next, data, envelope) { + if(pred.call(this, data, envelope)) { + next.call(this, data, envelope); + } + } + }; + }, + distinct : function(options) { + options = options || {}; + var accessor = function(args) { + return args[0]; + }; + var check = options.all ? + new DistinctPredicate(accessor) : + new ConsecutiveDistinctPredicate(accessor); + return { + name : "distinct", + fn : function(next, data, envelope) { + if(check(data)) { + next(data, envelope); + } + } + }; + } + }; /*jshint -W098 */ - var ConsecutiveDistinctPredicate = function () { + var ConsecutiveDistinctPredicate = function (argsAccessor) { var previous; - return function ( data ) { + return function () { + var data = argsAccessor(arguments); var eq = false; if ( _.isString( data ) ) { eq = data === previous; @@ -42,10 +177,11 @@ }; }; /*jshint -W098 */ - var DistinctPredicate = function () { + var DistinctPredicate = function (argsAccessor) { var previous = []; - return function ( data ) { + return function () { + var data = argsAccessor(arguments); var isDistinct = !_.any( previous, function ( p ) { if ( _.isObject( data ) || _.isArray( data ) ) { return _.isEqual( data, p ); @@ -55,7 +191,7 @@ if ( isDistinct ) { previous.push( data ); } - return isDistinct; + return isDistinct; }; }; /* global postal, SubscriptionDefinition */ @@ -83,9 +219,7 @@ var SubscriptionDefinition = function ( channel, topic, callback ) { this.channel = channel; this.topic = topic; - this.callback = callback; - this.constraints = []; - this.context = null; + this.subscribe(callback); postal.configuration.bus.publish( { channel : postal.configuration.SYSTEM_CHANNEL, topic : "subscription.created", @@ -116,13 +250,7 @@ }, defer : function () { - var self = this; - var fn = this.callback; - this.callback = function ( data, env ) { - setTimeout( function () { - fn.call( self.context, data, env ); - }, 0 ); - }; + this.callback.useStrategy(postal.configuration.strategies.setTimeout(0)); return this; }, @@ -130,26 +258,20 @@ if ( _.isNaN( maxCalls ) || maxCalls <= 0 ) { throw "The value provided to disposeAfter (maxCalls) must be a number greater than zero."; } - var self = this; - var fn = this.callback; - var dispose = _.after( maxCalls, _.bind( function () { - this.unsubscribe(); - }, this ) ); - - this.callback = function () { - fn.apply( self.context, arguments ); - dispose(); - }; - return this; + var self = this; + self.callback.useStrategy(postal.configuration.strategies.after(maxCalls, function() { + self.unsubscribe.call(self); + })); + return self; }, distinctUntilChanged : function () { - this.withConstraint( new ConsecutiveDistinctPredicate() ); + this.callback.useStrategy(postal.configuration.strategies.distinct()); return this; }, distinct : function () { - this.withConstraint( new DistinctPredicate() ); + this.callback.useStrategy(postal.configuration.strategies.distinct({ all: true })); return this; }, @@ -162,22 +284,12 @@ if ( !_.isFunction( predicate ) ) { throw "Predicate constraint must be a function"; } - this.constraints.push( predicate ); + this.callback.useStrategy(postal.configuration.strategies.predicate(predicate)); return this; }, - withConstraints : function ( predicates ) { - var self = this; - if ( _.isArray( predicates ) ) { - _.each( predicates, function ( predicate ) { - self.withConstraint( predicate ); - } ); - } - return self; - }, - withContext : function ( context ) { - this.context = context; + this.callback.context(context); return this; }, @@ -194,13 +306,7 @@ if ( _.isNaN( milliseconds ) ) { throw "Milliseconds must be a number"; } - var self = this; - var fn = this.callback; - this.callback = function ( data, env ) { - setTimeout( function () { - fn.call( self.context, data, env ); - }, milliseconds ); - }; + this.callback.useStrategy(postal.configuration.strategies.setTimeout(milliseconds)); return this; }, @@ -208,13 +314,18 @@ if ( _.isNaN( milliseconds ) ) { throw "Milliseconds must be a number"; } - var fn = this.callback; - this.callback = _.throttle( fn, milliseconds ); + this.callback.useStrategy(postal.configuration.strategies.throttle(milliseconds)); return this; }, subscribe : function ( callback ) { this.callback = callback; + this.callback = new Strategy({ + owner : this, + prop : "callback", + context : this, // TODO: is this the best option? + lazyInit : true + }); return this; } }; @@ -259,13 +370,7 @@ /* global postal */ var fireSub = function ( subDef, envelope ) { if ( !subDef.inactive && postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) { - if ( _.all( subDef.constraints, function ( constraint ) { - return constraint.call( subDef.context, envelope.data, envelope ); - } ) ) { - if ( typeof subDef.callback === "function" ) { - subDef.callback.call( subDef.context, envelope.data, envelope ); - } - } + subDef.callback.call( subDef.callback.context ? subDef.callback.context() : this, envelope.data, envelope ); } }; @@ -366,7 +471,8 @@ bus : localBus, resolver : bindingsResolver, DEFAULT_CHANNEL : "/", - SYSTEM_CHANNEL : "postal" + SYSTEM_CHANNEL : "postal", + strategies : strats }, ChannelDefinition : ChannelDefinition, diff --git a/lib/postal.min.js b/lib/postal.min.js index 1d18aba..7b20459 100755 --- a/lib/postal.min.js +++ b/lib/postal.min.js @@ -4,4 +4,4 @@ License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license) Version 0.8.9 */ -(function(t,n){"object"==typeof module&&module.exports?module.exports=function(t){return t=t||require("underscore"),n(t)}:"function"==typeof define&&define.amd?define(["underscore"],function(i){return n(i,t)}):t.postal=n(t._,t)})(this,function(t,n){var i,s=function(){var n;return function(i){var s=!1;return t.isString(i)?(s=i===n,n=i):(s=t.isEqual(i,n),n=t.clone(i)),!s}},c=function(){var n=[];return function(i){var s=!t.any(n,function(n){return t.isObject(i)||t.isArray(i)?t.isEqual(i,n):i===n});return s&&n.push(i),s}},e=function(t){this.channel=t||i.configuration.DEFAULT_CHANNEL};e.prototype.subscribe=function(){return 1===arguments.length?new r(this.channel,arguments[0].topic,arguments[0].callback):new r(this.channel,arguments[0],arguments[1])},e.prototype.publish=function(){var t=1===arguments.length?"[object String]"===Object.prototype.toString.call(arguments[0])?{topic:arguments[0]}:arguments[0]:{topic:arguments[0],data:arguments[1]};return t.channel=this.channel,i.configuration.bus.publish(t)};var r=function(t,n,s){this.channel=t,this.topic=n,this.callback=s,this.constraints=[],this.context=null,i.configuration.bus.publish({channel:i.configuration.SYSTEM_CHANNEL,topic:"subscription.created",data:{event:"subscription.created",channel:t,topic:n}}),i.configuration.bus.subscribe(this)};r.prototype={unsubscribe:function(){this.inactive||(this.inactive=!0,i.configuration.bus.unsubscribe(this),i.configuration.bus.publish({channel:i.configuration.SYSTEM_CHANNEL,topic:"subscription.removed",data:{event:"subscription.removed",channel:this.channel,topic:this.topic}}))},defer:function(){var t=this,n=this.callback;return this.callback=function(i,s){setTimeout(function(){n.call(t.context,i,s)},0)},this},disposeAfter:function(n){if(t.isNaN(n)||0>=n)throw"The value provided to disposeAfter (maxCalls) must be a number greater than zero.";var i=this,s=this.callback,c=t.after(n,t.bind(function(){this.unsubscribe()},this));return this.callback=function(){s.apply(i.context,arguments),c()},this},distinctUntilChanged:function(){return this.withConstraint(new s),this},distinct:function(){return this.withConstraint(new c),this},once:function(){return this.disposeAfter(1),this},withConstraint:function(n){if(!t.isFunction(n))throw"Predicate constraint must be a function";return this.constraints.push(n),this},withConstraints:function(n){var i=this;return t.isArray(n)&&t.each(n,function(t){i.withConstraint(t)}),i},withContext:function(t){return this.context=t,this},withDebounce:function(n,i){if(t.isNaN(n))throw"Milliseconds must be a number";var s=this.callback;return this.callback=t.debounce(s,n,!!i),this},withDelay:function(n){if(t.isNaN(n))throw"Milliseconds must be a number";var i=this,s=this.callback;return this.callback=function(t,c){setTimeout(function(){s.call(i.context,t,c)},n)},this},withThrottle:function(n){if(t.isNaN(n))throw"Milliseconds must be a number";var i=this.callback;return this.callback=t.throttle(i,n),this},subscribe:function(t){return this.callback=t,this}};var o={cache:{},regex:{},compare:function(n,i){var s,c,e,r=this.cache[i]&&this.cache[i][n];return r!==undefined?r:((c=this.regex[n])||(s="^"+t.map(n.split("."),function(t){var n="";return e&&(n="#"!==e?"\\.\\b":"\\b"),n+="#"===t?"[\\s\\S]*":"*"===t?"[^.]+":t,e=t,n}).join("")+"$",c=this.regex[n]=RegExp(s)),this.cache[i]=this.cache[i]||{},this.cache[i][n]=r=c.test(i),r)},reset:function(){this.cache={},this.regex={}}},a=function(n,s){!n.inactive&&i.configuration.resolver.compare(n.topic,s.topic)&&t.all(n.constraints,function(t){return t.call(n.context,s.data,s)})&&"function"==typeof n.callback&&n.callback.call(n.context,s.data,s)},u=0,h=[],l=function(){for(;h.length;)h.shift().unsubscribe()},f={addWireTap:function(t){var n=this;return n.wireTaps.push(t),function(){var i=n.wireTaps.indexOf(t);-1!==i&&n.wireTaps.splice(i,1)}},publish:function(n){return++u,n.timeStamp=new Date,t.each(this.wireTaps,function(t){t(n.data,n)}),this.subscriptions[n.channel]&&t.each(this.subscriptions[n.channel],function(t){for(var i,s=0,c=t.length;c>s;)(i=t[s++])&&a(i,n)}),0===--u&&l(),n},reset:function(){this.subscriptions&&(t.each(this.subscriptions,function(n){t.each(n,function(t){for(;t.length;)t.pop().unsubscribe()})}),this.subscriptions={})},subscribe:function(t){var n,i=this.subscriptions[t.channel];return i||(i=this.subscriptions[t.channel]={}),n=this.subscriptions[t.channel][t.topic],n||(n=this.subscriptions[t.channel][t.topic]=[]),n.push(t),t},subscriptions:{},wireTaps:[],unsubscribe:function(t){if(u)return h.push(t),undefined;if(this.subscriptions[t.channel][t.topic])for(var n=this.subscriptions[t.channel][t.topic].length,i=0;n>i;){if(this.subscriptions[t.channel][t.topic][i]===t){this.subscriptions[t.channel][t.topic].splice(i,1);break}i+=1}}};if(i={configuration:{bus:f,resolver:o,DEFAULT_CHANNEL:"/",SYSTEM_CHANNEL:"postal"},ChannelDefinition:e,SubscriptionDefinition:r,channel:function(t){return new e(t)},subscribe:function(t){return new r(t.channel||i.configuration.DEFAULT_CHANNEL,t.topic,t.callback)},publish:function(t){return t.channel=t.channel||i.configuration.DEFAULT_CHANNEL,i.configuration.bus.publish(t)},addWireTap:function(t){return this.configuration.bus.addWireTap(t)},linkChannels:function(n,s){var c=[];return n=t.isArray(n)?n:[n],s=t.isArray(s)?s:[s],t.each(n,function(n){var e=n.topic||"#";t.each(s,function(s){var r=s.channel||i.configuration.DEFAULT_CHANNEL;c.push(i.subscribe({channel:n.channel||i.configuration.DEFAULT_CHANNEL,topic:e,callback:function(n,c){var e=t.clone(c);e.topic=t.isFunction(s.topic)?s.topic(c.topic):s.topic||c.topic,e.channel=r,e.data=n,i.publish(e)}}))})}),c},utils:{getSubscribersFor:function(){var t=arguments[0],n=arguments[1];return 1===arguments.length&&(t=arguments[0].channel||i.configuration.DEFAULT_CHANNEL,n=arguments[0].topic),i.configuration.bus.subscriptions[t]&&Object.prototype.hasOwnProperty.call(i.configuration.bus.subscriptions[t],n)?i.configuration.bus.subscriptions[t][n]:[]},reset:function(){i.configuration.bus.reset(),i.configuration.resolver.reset()}}},f.subscriptions[i.configuration.SYSTEM_CHANNEL]={},n&&n.hasOwnProperty("__postalReady__")&&t.isArray(n.__postalReady__))for(;n.__postalReady__.length;)n.__postalReady__.shift().onReady(i);return i}); \ No newline at end of file +(function(t,n){"object"==typeof module&&module.exports?module.exports=function(t){return t=t||require("underscore"),n(t)}:"function"==typeof define&&define.amd?define(["underscore"],function(i){return n(i,t)}):t.postal=n(t._,t)})(this,function(t,n){var i,e=function(t){var n=t.owner[t.prop];if("function"!=typeof n)throw Error("Strategies can only target methods.");var i=[],e=t.context||t.owner,r=function(){var t=0,r=function r(){var c,s=Array.prototype.slice.call(arguments,0),o=t;t+=1,i.length>o?(c=i[o],c.fn.apply(c.context||e,[r].concat(s))):n.apply(e,s)};r.apply(this,arguments)};return r.target=function(){return n},r.context=function(t){return 0===arguments.length?e:(e=t,undefined)},r.strategies=function(){return i},r.useStrategy=function(t){for(var n=0,e=!1;i.length>n;){if(i[n].name===t.name){i[n]=t,e=!0;break}n+=1}e||i.push(t)},r.reset=function(){i=[]},t.lazyInit?(n.useStrategy=function(){t.owner[t.prop]=r,r.useStrategy.apply(r,arguments)},n.context=function(){return t.owner[t.prop]=r,r.context.apply(r,arguments)},n):r},r={setTimeout:function(t){return{name:"setTimeout",fn:function(n,i,e){setTimeout(function(){n(i,e)},t)}}},after:function(n,i){var e=t.after(n,i);return{name:"after",fn:function(t,n,i){e(),t(n,i)}}},throttle:function(n){return{name:"throttle",fn:t.throttle(function(t,n,i){t(n,i)},n)}},debounce:function(n,i){return{name:"debounce",fn:t.debounce(function(t,n,i){t(n,i)},n,!!i)}},predicate:function(t){return{name:"predicate",fn:function(n,i,e){t.call(this,i,e)&&n.call(this,i,e)}}},distinct:function(t){t=t||{};var n=function(t){return t[0]},i=t.all?new s(n):new c(n);return{name:"distinct",fn:function(t,n,e){i(n)&&t(n,e)}}}},c=function(n){var i;return function(){var e=n(arguments),r=!1;return t.isString(e)?(r=e===i,i=e):(r=t.isEqual(e,i),i=t.clone(e)),!r}},s=function(n){var i=[];return function(){var e=n(arguments),r=!t.any(i,function(n){return t.isObject(e)||t.isArray(e)?t.isEqual(e,n):e===n});return r&&i.push(e),r}},o=function(t){this.channel=t||i.configuration.DEFAULT_CHANNEL};o.prototype.subscribe=function(){return 1===arguments.length?new a(this.channel,arguments[0].topic,arguments[0].callback):new a(this.channel,arguments[0],arguments[1])},o.prototype.publish=function(){var t=1===arguments.length?"[object String]"===Object.prototype.toString.call(arguments[0])?{topic:arguments[0]}:arguments[0]:{topic:arguments[0],data:arguments[1]};return t.channel=this.channel,i.configuration.bus.publish(t)};var a=function(t,n,e){this.channel=t,this.topic=n,this.subscribe(e),i.configuration.bus.publish({channel:i.configuration.SYSTEM_CHANNEL,topic:"subscription.created",data:{event:"subscription.created",channel:t,topic:n}}),i.configuration.bus.subscribe(this)};a.prototype={unsubscribe:function(){this.inactive||(this.inactive=!0,i.configuration.bus.unsubscribe(this),i.configuration.bus.publish({channel:i.configuration.SYSTEM_CHANNEL,topic:"subscription.removed",data:{event:"subscription.removed",channel:this.channel,topic:this.topic}}))},defer:function(){return this.callback.useStrategy(i.configuration.strategies.setTimeout(0)),this},disposeAfter:function(n){if(t.isNaN(n)||0>=n)throw"The value provided to disposeAfter (maxCalls) must be a number greater than zero.";var e=this;return e.callback.useStrategy(i.configuration.strategies.after(n,function(){e.unsubscribe.call(e)})),e},distinctUntilChanged:function(){return this.callback.useStrategy(i.configuration.strategies.distinct()),this},distinct:function(){return this.callback.useStrategy(i.configuration.strategies.distinct({all:!0})),this},once:function(){return this.disposeAfter(1),this},withConstraint:function(n){if(!t.isFunction(n))throw"Predicate constraint must be a function";return this.callback.useStrategy(i.configuration.strategies.predicate(n)),this},withContext:function(t){return this.callback.context(t),this},withDebounce:function(n,i){if(t.isNaN(n))throw"Milliseconds must be a number";var e=this.callback;return this.callback=t.debounce(e,n,!!i),this},withDelay:function(n){if(t.isNaN(n))throw"Milliseconds must be a number";return this.callback.useStrategy(i.configuration.strategies.setTimeout(n)),this},withThrottle:function(n){if(t.isNaN(n))throw"Milliseconds must be a number";return this.callback.useStrategy(i.configuration.strategies.throttle(n)),this},subscribe:function(t){return this.callback=t,this.callback=new e({owner:this,prop:"callback",context:this,lazyInit:!0}),this}};var u={cache:{},regex:{},compare:function(n,i){var e,r,c,s=this.cache[i]&&this.cache[i][n];return s!==undefined?s:((r=this.regex[n])||(e="^"+t.map(n.split("."),function(t){var n="";return c&&(n="#"!==c?"\\.\\b":"\\b"),n+="#"===t?"[\\s\\S]*":"*"===t?"[^.]+":t,c=t,n}).join("")+"$",r=this.regex[n]=RegExp(e)),this.cache[i]=this.cache[i]||{},this.cache[i][n]=s=r.test(i),s)},reset:function(){this.cache={},this.regex={}}},h=function(t,n){!t.inactive&&i.configuration.resolver.compare(t.topic,n.topic)&&t.callback.call(t.callback.context?t.callback.context():this,n.data,n)},f=0,l=[],p=function(){for(;l.length;)l.shift().unsubscribe()},b={addWireTap:function(t){var n=this;return n.wireTaps.push(t),function(){var i=n.wireTaps.indexOf(t);-1!==i&&n.wireTaps.splice(i,1)}},publish:function(n){return++f,n.timeStamp=new Date,t.each(this.wireTaps,function(t){t(n.data,n)}),this.subscriptions[n.channel]&&t.each(this.subscriptions[n.channel],function(t){for(var i,e=0,r=t.length;r>e;)(i=t[e++])&&h(i,n)}),0===--f&&p(),n},reset:function(){this.subscriptions&&(t.each(this.subscriptions,function(n){t.each(n,function(t){for(;t.length;)t.pop().unsubscribe()})}),this.subscriptions={})},subscribe:function(t){var n,i=this.subscriptions[t.channel];return i||(i=this.subscriptions[t.channel]={}),n=this.subscriptions[t.channel][t.topic],n||(n=this.subscriptions[t.channel][t.topic]=[]),n.push(t),t},subscriptions:{},wireTaps:[],unsubscribe:function(t){if(f)return l.push(t),undefined;if(this.subscriptions[t.channel][t.topic])for(var n=this.subscriptions[t.channel][t.topic].length,i=0;n>i;){if(this.subscriptions[t.channel][t.topic][i]===t){this.subscriptions[t.channel][t.topic].splice(i,1);break}i+=1}}};if(i={configuration:{bus:b,resolver:u,DEFAULT_CHANNEL:"/",SYSTEM_CHANNEL:"postal",strategies:r},ChannelDefinition:o,SubscriptionDefinition:a,channel:function(t){return new o(t)},subscribe:function(t){return new a(t.channel||i.configuration.DEFAULT_CHANNEL,t.topic,t.callback)},publish:function(t){return t.channel=t.channel||i.configuration.DEFAULT_CHANNEL,i.configuration.bus.publish(t)},addWireTap:function(t){return this.configuration.bus.addWireTap(t)},linkChannels:function(n,e){var r=[];return n=t.isArray(n)?n:[n],e=t.isArray(e)?e:[e],t.each(n,function(n){var c=n.topic||"#";t.each(e,function(e){var s=e.channel||i.configuration.DEFAULT_CHANNEL;r.push(i.subscribe({channel:n.channel||i.configuration.DEFAULT_CHANNEL,topic:c,callback:function(n,r){var c=t.clone(r);c.topic=t.isFunction(e.topic)?e.topic(r.topic):e.topic||r.topic,c.channel=s,c.data=n,i.publish(c)}}))})}),r},utils:{getSubscribersFor:function(){var t=arguments[0],n=arguments[1];return 1===arguments.length&&(t=arguments[0].channel||i.configuration.DEFAULT_CHANNEL,n=arguments[0].topic),i.configuration.bus.subscriptions[t]&&Object.prototype.hasOwnProperty.call(i.configuration.bus.subscriptions[t],n)?i.configuration.bus.subscriptions[t][n]:[]},reset:function(){i.configuration.bus.reset(),i.configuration.resolver.reset()}}},b.subscriptions[i.configuration.SYSTEM_CHANNEL]={},n&&n.hasOwnProperty("__postalReady__")&&t.isArray(n.__postalReady__))for(;n.__postalReady__.length;)n.__postalReady__.shift().onReady(i);return i}); \ No newline at end of file diff --git a/spec/ConsecutiveDistinctPredicate.spec.js b/spec/ConsecutiveDistinctPredicate.spec.js index dc51d41..cf22cb3 100644 --- a/spec/ConsecutiveDistinctPredicate.spec.js +++ b/spec/ConsecutiveDistinctPredicate.spec.js @@ -1,7 +1,10 @@ /* global describe, postal, it, after, before, expect, ConsecutiveDistinctPredicate */ describe( "ConsecutiveDistinctPredicate", function () { + var accessor = function(args) { + return args[0]; + }; describe( "When calling the function with the same data multiple times", function () { - var pred = new ConsecutiveDistinctPredicate(), + var pred = new ConsecutiveDistinctPredicate(accessor), data = { name : "Dr Who" }, results = []; results.push( pred( data ) ); @@ -19,7 +22,7 @@ describe( "ConsecutiveDistinctPredicate", function () { } ); } ); describe( "When calling the function with different data every time", function () { - var predA = new ConsecutiveDistinctPredicate(), + var predA = new ConsecutiveDistinctPredicate(accessor), data = { name : "Amelia" }, res = []; res.push( predA( data ) ); @@ -39,7 +42,7 @@ describe( "ConsecutiveDistinctPredicate", function () { } ); } ); describe( "When calling the function with different data every two calls", function () { - var predA = new ConsecutiveDistinctPredicate(), + var predA = new ConsecutiveDistinctPredicate(accessor), data = { name : "Amelia" }, res = []; res.push( predA( data ) ); diff --git a/spec/DistinctPredicate.spec.js b/spec/DistinctPredicate.spec.js index a840595..43515c9 100644 --- a/spec/DistinctPredicate.spec.js +++ b/spec/DistinctPredicate.spec.js @@ -1,8 +1,11 @@ /* global describe, postal, it, after, before, expect, DistinctPredicate */ describe( 'DistinctPredicate', function () { + var accessor = function(args) { + return args[0]; + }; describe( 'When calling the function with the same data object multiple times', function () { - var pred = new DistinctPredicate(), + var pred = new DistinctPredicate(accessor), results = []; results.push( pred( {career : 'ninja'} ) ); @@ -21,7 +24,7 @@ describe( 'DistinctPredicate', function () { } ); describe( 'When calling the function with the same primitive multiple times', function () { - var pred = new DistinctPredicate(), + var pred = new DistinctPredicate(accessor), results = []; results.push( pred( 'ninja' ) ); @@ -40,7 +43,7 @@ describe( 'DistinctPredicate', function () { } ); describe( 'When calling the function with the same array multiple times', function () { - var pred = new DistinctPredicate(), + var pred = new DistinctPredicate(accessor), results = []; results.push( pred( ['Jack Black', 'Kyle Gass'] ) ); @@ -61,7 +64,7 @@ describe( 'DistinctPredicate', function () { // ------------------------------------------ describe( 'When calling the function with different data object every time', function () { - var pred = new DistinctPredicate(), + var pred = new DistinctPredicate(accessor), results = []; results.push( pred( {codename : 'tinker'} ) ); @@ -78,7 +81,7 @@ describe( 'DistinctPredicate', function () { } ); describe( 'When calling the function with different primitive every time', function () { - var pred = new DistinctPredicate(), + var pred = new DistinctPredicate(accessor), results = []; results.push( pred( 100.5 ) ); @@ -95,7 +98,7 @@ describe( 'DistinctPredicate', function () { } ); describe( 'When calling the function with different array every time', function () { - var pred = new DistinctPredicate(), + var pred = new DistinctPredicate(accessor), results = []; results.push( pred( [] ) ); @@ -114,7 +117,7 @@ describe( 'DistinctPredicate', function () { // ------------------------------------------ describe( 'When calling the function with different data object between duplicates', function () { - var pred = new DistinctPredicate(), + var pred = new DistinctPredicate(accessor), results = []; results.push( pred( {game : 'Diablo 3'} ) ); @@ -141,7 +144,7 @@ describe( 'DistinctPredicate', function () { } ); describe( 'When calling the function with different primitive between duplicates', function () { - var pred = new DistinctPredicate(), + var pred = new DistinctPredicate(accessor), results = []; results.push( pred( 'Stan Marsh' ) ); @@ -168,7 +171,7 @@ describe( 'DistinctPredicate', function () { } ); describe( 'When calling the function with different array between duplicates', function () { - var pred = new DistinctPredicate(), + var pred = new DistinctPredicate(accessor), results = []; results.push( pred( [] ) ); diff --git a/spec/Postal.spec.js b/spec/Postal.spec.js index ca81e30..2cbf972 100644 --- a/spec/Postal.spec.js +++ b/spec/Postal.spec.js @@ -40,11 +40,8 @@ describe( "Postal", function () { it( "should have set subscription topic value", function () { expect( sub.topic ).to.be( "MyTopic" ); } ); - it( "should have defaulted the subscription constraints array", function () { - expect( sub.constraints.length ).to.be( 0 ); - } ); it( "should have defaulted the subscription context value", function () { - expect( sub.context ).to.be( null ); + expect( sub.callback.context() ).to.be( sub ); } ); it( "should have captured subscription creation event", function () { expect( caughtSubscribeEvent ).to.be.ok(); @@ -143,9 +140,9 @@ describe( "Postal", function () { } ); it( "should produce expected messages", function () { expect( results.length ).to.be( 3 ); - expect( results[0] ).to.be( "1 received message" ); - expect( results[1] ).to.be( "2 received message" ); - expect( results[2] ).to.be( "unsubscribed" ); + expect( results[0] ).to.be( "unsubscribed" ); + expect( results[1] ).to.be( "1 received message" ); + expect( results[2] ).to.be( "2 received message" ); } ); } ); } ); @@ -221,8 +218,7 @@ describe( "Postal", function () { channel = postal.channel( "MyChannel" ); subscription = channel.subscribe( "MyTopic", function ( data ) { subInvokedCnt++; - } ) - .distinctUntilChanged(); + }).distinctUntilChanged(); channel.publish( "MyTopic", "Testing123" ); channel.publish( "MyTopic", "Testing123" ); channel.publish( "MyTopic", "Testing123" ); @@ -234,9 +230,9 @@ describe( "Postal", function () { postal.utils.reset(); subInvokedCnt = 0; } ); - it( "should have a constraint on the subscription", function () { - expect( postal.configuration.bus.subscriptions.MyChannel.MyTopic[0].constraints.length ).to.be( 1 ); - } ); + it( "callback should be a strategy", function () { + expect( typeof postal.configuration.bus.subscriptions.MyChannel.MyTopic[0].callback.context ).to.be( "function" ); + } ); it( "subscription callback should be invoked once", function () { expect( subInvokedCnt ).to.be( 1 ); } ); @@ -247,10 +243,9 @@ describe( "Postal", function () { channel = postal.channel( "MyChannel" ); subscription = channel.subscribe( "MyTopic", function ( data ) { recvd = true; - } ) - .withConstraint( function () { - return true; - } ); + }).withConstraint( function () { + return true; + }); channel.publish( "MyTopic", "Testing123" ); } ); after( function () { @@ -258,7 +253,7 @@ describe( "Postal", function () { recvd = false; } ); it( "should have a constraint on the subscription", function () { - expect( postal.configuration.bus.subscriptions.MyChannel.MyTopic[0].constraints.length ).to.be( 1 ); + expect( subscription.callback.strategies()[0].name ).to.be( "predicate" ); } ); it( "should have invoked the subscription callback", function () { expect( recvd ).to.be.ok(); @@ -281,70 +276,41 @@ describe( "Postal", function () { recvd = false; } ); it( "should have a constraint on the subscription", function () { - expect( postal.configuration.bus.subscriptions.MyChannel.MyTopic[0].constraints.length ).to.be( 1 ); + expect( subscription.callback.strategies()[0].name ).to.be( "predicate" ); } ); it( "should not have invoked the subscription callback", function () { expect( recvd ).to.not.be.ok(); } ); } ); - describe( "When subscribing with multiple constraints returning true", function () { + describe( "When subscribing with multiple constraints", function () { var recvd = false; before( function () { channel = postal.channel( "MyChannel" ); subscription = channel.subscribe( "MyTopic", function ( data ) { recvd = true; } ) - .withConstraints( [function () { - return true; - }, - function () { - return true; - }, - function () { - return true; - }] ); + .withConstraint(function () { + return false; + }) + .withConstraint(function () { + return false; + }) + .withConstraint(function () { + return true; + }); channel.publish( "MyTopic", "Testing123" ); } ); after( function () { postal.utils.reset(); recvd = false; } ); - it( "should have a constraint on the subscription", function () { - expect( postal.configuration.bus.subscriptions.MyChannel.MyTopic[0].constraints.length ).to.be( 3 ); + it( "should overwrite constraint with last one passed in", function () { + expect( subscription.callback.strategies().length ).to.be( 1 ); } ); it( "should have invoked the callback", function () { expect( recvd ).to.be.ok(); } ); } ); - describe( "When subscribing with multiple constraints and one returning false", function () { - var recvd = false; - before( function () { - channel = postal.channel( "MyChannel" ); - subscription = channel.subscribe( "MyTopic", function ( data ) { - recvd = true; - } ) - .withConstraints( [function () { - return true; - }, - function () { - return false; - }, - function () { - return true; - }] ); - channel.publish( "MyTopic", "Testing123" ); - } ); - after( function () { - postal.utils.reset(); - recvd = false; - } ); - it( "should have a constraint on the subscription", function () { - expect( postal.configuration.bus.subscriptions.MyChannel.MyTopic[0].constraints.length ).to.be( 3 ); - } ); - it( "should not have invoked the callback", function () { - expect( recvd ).to.not.be.ok() - } ); - } ); describe( "When subscribing with the context being set", function () { var count = 0, obj = { @@ -610,11 +576,8 @@ describe( "Postal", function () { it( "should have set subscription topic value", function () { expect( sub.topic ).to.be( "MyTopic" ); } ); - it( "should have defaulted the subscription constraints array", function () { - expect( sub.constraints.length ).to.be( 0 ); - } ); - it( "should have defaulted the subscription context value", function () { - expect( sub.context ).to.be( null ); + it( "should have context method", function () { + expect( typeof sub.callback.context ).to.be( "function" ); } ); } ); describe( "When using global channel api", function () { @@ -683,8 +646,7 @@ describe( "Postal", function () { it( "should have created a subscription definition", function () { expect( sub.channel ).to.be( "MyChannel" ); expect( sub.topic ).to.be( "MyTopic" ); - expect( sub.constraints.length ).to.be( 0 ); - expect( sub.context ).to.be( null ); + expect( sub.context ).to.be( undefined ); } ); it( "should have created a resolver cache entry", function () { expect( _.isEmpty( resolver ) ).to.not.be.ok() diff --git a/spec/SubscriptionDefinition.spec.js b/spec/SubscriptionDefinition.spec.js index 8891079..0e40dd5 100644 --- a/spec/SubscriptionDefinition.spec.js +++ b/spec/SubscriptionDefinition.spec.js @@ -34,12 +34,6 @@ describe( "SubscriptionDefinition", function () { it( "should set the callback", function () { expect( sDef.callback ).to.be( NO_OP ); } ); - it( "should default the constraints", function () { - expect( sDef.constraints.length ).to.be( 0 ); - } ); - it( "should default the context", function () { - expect( sDef.context ).to.be( null ); - } ); it( "should fire the subscription.created message", function () { expect( caughtSubscribeEvent ).to.be( true ); } ); @@ -48,29 +42,18 @@ describe( "SubscriptionDefinition", function () { describe( "When setting distinctUntilChanged", function () { var sDefa = new SubscriptionDefinition( "TestChannel", "TestTopic", NO_OP ).distinctUntilChanged(); - it( "Should add a DistinctPredicate constraint to the configuration constraints", function () { - expect( sDefa.constraints.length ).to.be( 1 ); - } ); + it( "callback should be a strategy", function () { + expect( typeof sDefa.callback.context ).to.be( "function" ); + } ); } ); describe( "When adding a constraint", function () { var sDefb = new SubscriptionDefinition( "TestChannel", "TestTopic", NO_OP ).withConstraint( function () { } ); - it( "Should add a constraint", function () { - expect( sDefb.constraints.length ).to.be( 1 ); - } ); - } ); - - describe( "When adding multiple constraints", function () { - var sDefc = new SubscriptionDefinition( "TestChannel", "TestTopic", NO_OP ).withConstraints( [function () { - }, function () { - }, function () { - }] ); - - it( "Should add a constraint", function () { - expect( sDefc.constraints.length ).to.be( 3 ); - } ); + it( "callback should be a strategy", function () { + expect( typeof sDefb.callback.context ).to.be( "function" ); + } ); } ); describe( "When setting the context", function () { @@ -86,7 +69,7 @@ describe( "SubscriptionDefinition", function () { postal.publish( { channel : "TestChannel", topic : "TestTopic", data : "Oh, hai"} ) it( "Should set context", function () { - expect( sDefd.context ).to.be( obj ); + expect( sDefd.callback.context() ).to.be( obj ); } ); it( "Should apply context to predicate/constraint", function () { expect( name ).to.be( "Rose" ); diff --git a/spec/index.html b/spec/index.html index fd516ba..d6e618c 100644 --- a/spec/index.html +++ b/spec/index.html @@ -15,6 +15,8 @@ + + diff --git a/src/Api.js b/src/Api.js index dfdc4b6..4f1c447 100644 --- a/src/Api.js +++ b/src/Api.js @@ -5,7 +5,8 @@ postal = { bus : localBus, resolver : bindingsResolver, DEFAULT_CHANNEL : "/", - SYSTEM_CHANNEL : "postal" + SYSTEM_CHANNEL : "postal", + strategies : strats }, ChannelDefinition : ChannelDefinition, diff --git a/src/ConsecutiveDistinctPredicate.js b/src/ConsecutiveDistinctPredicate.js index e94ed48..fc6aab0 100644 --- a/src/ConsecutiveDistinctPredicate.js +++ b/src/ConsecutiveDistinctPredicate.js @@ -1,7 +1,8 @@ /*jshint -W098 */ -var ConsecutiveDistinctPredicate = function () { +var ConsecutiveDistinctPredicate = function (argsAccessor) { var previous; - return function ( data ) { + return function () { + var data = argsAccessor(arguments); var eq = false; if ( _.isString( data ) ) { eq = data === previous; diff --git a/src/DistinctPredicate.js b/src/DistinctPredicate.js index 16ad115..0303067 100644 --- a/src/DistinctPredicate.js +++ b/src/DistinctPredicate.js @@ -1,8 +1,9 @@ /*jshint -W098 */ -var DistinctPredicate = function () { +var DistinctPredicate = function (argsAccessor) { var previous = []; - return function ( data ) { + return function () { + var data = argsAccessor(arguments); var isDistinct = !_.any( previous, function ( p ) { if ( _.isObject( data ) || _.isArray( data ) ) { return _.isEqual( data, p ); @@ -12,6 +13,6 @@ var DistinctPredicate = function () { if ( isDistinct ) { previous.push( data ); } - return isDistinct; + return isDistinct; }; }; \ No newline at end of file diff --git a/src/LocalBus.js b/src/LocalBus.js index fa5e770..3a9927b 100644 --- a/src/LocalBus.js +++ b/src/LocalBus.js @@ -1,13 +1,7 @@ /* global postal */ var fireSub = function ( subDef, envelope ) { if ( !subDef.inactive && postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) { - if ( _.all( subDef.constraints, function ( constraint ) { - return constraint.call( subDef.context, envelope.data, envelope ); - } ) ) { - if ( typeof subDef.callback === "function" ) { - subDef.callback.call( subDef.context, envelope.data, envelope ); - } - } + subDef.callback.call( subDef.callback.context ? subDef.callback.context() : this, envelope.data, envelope ); } }; diff --git a/src/SubscriptionDefinition.js b/src/SubscriptionDefinition.js index b6874a5..531ba43 100644 --- a/src/SubscriptionDefinition.js +++ b/src/SubscriptionDefinition.js @@ -3,9 +3,7 @@ var SubscriptionDefinition = function ( channel, topic, callback ) { this.channel = channel; this.topic = topic; - this.callback = callback; - this.constraints = []; - this.context = null; + this.subscribe(callback); postal.configuration.bus.publish( { channel : postal.configuration.SYSTEM_CHANNEL, topic : "subscription.created", @@ -36,13 +34,7 @@ SubscriptionDefinition.prototype = { }, defer : function () { - var self = this; - var fn = this.callback; - this.callback = function ( data, env ) { - setTimeout( function () { - fn.call( self.context, data, env ); - }, 0 ); - }; + this.callback.useStrategy(postal.configuration.strategies.setTimeout(0)); return this; }, @@ -50,26 +42,20 @@ SubscriptionDefinition.prototype = { if ( _.isNaN( maxCalls ) || maxCalls <= 0 ) { throw "The value provided to disposeAfter (maxCalls) must be a number greater than zero."; } - var self = this; - var fn = this.callback; - var dispose = _.after( maxCalls, _.bind( function () { - this.unsubscribe(); - }, this ) ); - - this.callback = function () { - fn.apply( self.context, arguments ); - dispose(); - }; - return this; + var self = this; + self.callback.useStrategy(postal.configuration.strategies.after(maxCalls, function() { + self.unsubscribe.call(self); + })); + return self; }, distinctUntilChanged : function () { - this.withConstraint( new ConsecutiveDistinctPredicate() ); + this.callback.useStrategy(postal.configuration.strategies.distinct()); return this; }, distinct : function () { - this.withConstraint( new DistinctPredicate() ); + this.callback.useStrategy(postal.configuration.strategies.distinct({ all: true })); return this; }, @@ -82,22 +68,12 @@ SubscriptionDefinition.prototype = { if ( !_.isFunction( predicate ) ) { throw "Predicate constraint must be a function"; } - this.constraints.push( predicate ); + this.callback.useStrategy(postal.configuration.strategies.predicate(predicate)); return this; }, - withConstraints : function ( predicates ) { - var self = this; - if ( _.isArray( predicates ) ) { - _.each( predicates, function ( predicate ) { - self.withConstraint( predicate ); - } ); - } - return self; - }, - withContext : function ( context ) { - this.context = context; + this.callback.context(context); return this; }, @@ -114,13 +90,7 @@ SubscriptionDefinition.prototype = { if ( _.isNaN( milliseconds ) ) { throw "Milliseconds must be a number"; } - var self = this; - var fn = this.callback; - this.callback = function ( data, env ) { - setTimeout( function () { - fn.call( self.context, data, env ); - }, milliseconds ); - }; + this.callback.useStrategy(postal.configuration.strategies.setTimeout(milliseconds)); return this; }, @@ -128,13 +98,18 @@ SubscriptionDefinition.prototype = { if ( _.isNaN( milliseconds ) ) { throw "Milliseconds must be a number"; } - var fn = this.callback; - this.callback = _.throttle( fn, milliseconds ); + this.callback.useStrategy(postal.configuration.strategies.throttle(milliseconds)); return this; }, subscribe : function ( callback ) { this.callback = callback; + this.callback = new Strategy({ + owner : this, + prop : "callback", + context : this, // TODO: is this the best option? + lazyInit : true + }); return this; } }; \ No newline at end of file diff --git a/src/postal.js b/src/postal.js index 9bba05d..be44617 100644 --- a/src/postal.js +++ b/src/postal.js @@ -19,6 +19,8 @@ var postal; + //import("strategy.js"); + //import("strategies.js"); //import("ConsecutiveDistinctPredicate.js"); //import("DistinctPredicate.js"); //import("ChannelDefinition.js"); diff --git a/src/strategies.js b/src/strategies.js new file mode 100644 index 0000000..d2cc60f --- /dev/null +++ b/src/strategies.js @@ -0,0 +1,66 @@ +/* global DistinctPredicate,ConsecutiveDistinctPredicate */ +var strats = { + setTimeout: function(ms) { + return { + name: "setTimeout", + fn: function (next, data, envelope) { + setTimeout(function () { + next(data, envelope); + }, ms); + } + }; + }, + after: function(maxCalls, callback) { + var dispose = _.after(maxCalls, callback); + return { + name: "after", + fn: function (next, data, envelope) { + dispose(); + next(data, envelope); + } + }; + }, + throttle : function(ms) { + return { + name: "throttle", + fn: _.throttle(function(next, data, envelope) { + next(data, envelope); + }, ms) + }; + }, + debounce: function(ms, immediate) { + return { + name: "debounce", + fn: _.debounce(function(next, data, envelope) { + next(data, envelope); + }, ms, !!immediate) + }; + }, + predicate: function(pred) { + return { + name: "predicate", + fn: function(next, data, envelope) { + if(pred.call(this, data, envelope)) { + next.call(this, data, envelope); + } + } + }; + }, + distinct : function(options) { + options = options || {}; + var accessor = function(args) { + return args[0]; + }; + var check = options.all ? + new DistinctPredicate(accessor) : + new ConsecutiveDistinctPredicate(accessor); + return { + name : "distinct", + fn : function(next, data, envelope) { + if(check(data)) { + next(data, envelope); + } + } + }; + } +}; \ No newline at end of file diff --git a/src/strategy.js b/src/strategy.js new file mode 100644 index 0000000..b50bd26 --- /dev/null +++ b/src/strategy.js @@ -0,0 +1,68 @@ +var Strategy = function( options ) { + var _target = options.owner[options.prop]; + if ( typeof _target !== "function" ) { + throw new Error( "Strategies can only target methods." ); + } + var _strategies = []; + var _context = options.context || options.owner; + var strategy = function() { + var idx = 0; + var next = function next() { + var args = Array.prototype.slice.call( arguments, 0 ); + var thisIdx = idx; + var strategy; + idx += 1; + if ( thisIdx < _strategies.length ) { + strategy = _strategies[thisIdx]; + strategy.fn.apply( strategy.context || _context, [next].concat( args ) ); + } else { + _target.apply( _context, args ); + } + }; + next.apply( this, arguments ); + }; + strategy.target = function() { + return _target; + }; + strategy.context = function( ctx ) { + if ( arguments.length === 0 ) { + return _context; + } else { + _context = ctx; + } + }; + strategy.strategies = function() { + return _strategies; + }; + strategy.useStrategy = function( strategy ) { + var idx = 0, + exists = false; + while ( idx < _strategies.length ) { + if ( _strategies[idx].name === strategy.name ) { + _strategies[idx] = strategy; + exists = true; + break; + } + idx += 1; + } + if ( !exists ) { + _strategies.push( strategy ); + } + }; + strategy.reset = function() { + _strategies = []; + }; + if ( options.lazyInit ) { + _target.useStrategy = function() { + options.owner[options.prop] = strategy; + strategy.useStrategy.apply( strategy, arguments ); + }; + _target.context = function() { + options.owner[options.prop] = strategy; + return strategy.context.apply( strategy, arguments ); + }; + return _target; + } else { + return strategy; + } +}; \ No newline at end of file