Working towards a basic and full build option. Added separate strategies-only build, etc.

This commit is contained in:
ifandelse 2014-01-27 01:52:22 -05:00
parent c34650abc5
commit b1e259f9f0
21 changed files with 1500 additions and 723 deletions

View file

@ -35,6 +35,28 @@ gulp.task("combine", function() {
.pipe(header(banner, { pkg : pkg }))
.pipe(rename("postal.min.js"))
.pipe(gulp.dest("./lib/"));
gulp.src(["./src/postal.basic.js"])
.pipe(header(banner, { pkg : pkg }))
.pipe(fileImports())
.pipe(hintNot())
.pipe(beautify({indentSize: 4}))
.pipe(gulp.dest("./lib/basic/"))
.pipe(uglify({ compress: { negate_iife: false }}))
.pipe(header(banner, { pkg : pkg }))
.pipe(rename("postal.basic.min.js"))
.pipe(gulp.dest("./lib/basic/"));
gulp.src(["./src/postal.strategies.js"])
.pipe(header(banner, { pkg : pkg }))
.pipe(fileImports())
.pipe(hintNot())
.pipe(beautify({indentSize: 4}))
.pipe(gulp.dest("./lib/strategies-add-on/"))
.pipe(uglify({ compress: { negate_iife: false }}))
.pipe(header(banner, { pkg : pkg }))
.pipe(rename("postal.strategies.min.js"))
.pipe(gulp.dest("./lib/strategies-add-on/"));
});
gulp.task("default", function() {
@ -62,7 +84,7 @@ var createServer = function(port) {
var servers;
gulp.task("server", function(){
gulp.run("report");
gulp.run("combine", "report");
if(!servers) {
servers = createServer(port);
}

417
lib/basic/postal.basic.js Normal file
View file

@ -0,0 +1,417 @@
/**
* postal - Pub/Sub library providing wildcard subscriptions, complex message handling, etc. Works server and client-side.
* Author: Jim Cowart (http://freshbrewedcode.com/jimcowart)
* Version: v0.8.11
* Url: http://github.com/postaljs/postal.js
* License(s): MIT, GPL
*/
(function (root, factory) {
if (typeof module === "object" && module.exports) {
// Node, or CommonJS-Like environments
module.exports = factory(require("underscore"), this);
} else if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module.
define(["underscore"], function (_) {
return factory(_, root);
});
} else {
// Browser globals
root.postal = factory(root._, root);
}
}(this, function (_, global, undefined) {
var postal;
var prevPostal = global.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;
}
};
var ChannelDefinition = function (channelName) {
this.channel = channelName || postal.configuration.DEFAULT_CHANNEL;
};
ChannelDefinition.prototype.subscribe = function () {
return postal.subscribe(arguments.length === 1 ? new SubscriptionDefinition(this.channel, arguments[0].topic, arguments[0].callback) : new SubscriptionDefinition(this.channel, arguments[0], arguments[1]));
};
ChannelDefinition.prototype.publish = function () {
var envelope = arguments.length === 1 ? (Object.prototype.toString.call(arguments[0]) === "[object String]" ? {
topic: arguments[0]
} : arguments[0]) : {
topic: arguments[0],
data: arguments[1]
};
envelope.channel = this.channel;
return postal.configuration.bus.publish(envelope);
};
var SubscriptionDefinition = function (channel, topic, callback) {
if (arguments.length !== 3) {
throw new Error("You must provide a channel, topic and callback when creating a SubscriptionDefinition instance.");
}
if (topic.length === 0) {
throw new Error("Topics cannot be empty");
}
this.channel = channel;
this.topic = topic;
this.subscribe(callback);
};
SubscriptionDefinition.prototype = {
unsubscribe: function () {
if (!this.inactive) {
this.inactive = true;
postal.unsubscribe(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;
},
withContext: function (context) {
this.callback.context(context);
return this;
}
};
var bindingsResolver = {
cache: {},
regex: {},
compare: function (binding, topic) {
var pattern, rgx, prevSegment, result = (this.cache[topic] && this.cache[topic][binding]);
if (typeof result !== "undefined") {
return result;
}
if (!(rgx = this.regex[binding])) {
pattern = "^" + _.map(binding.split("."), function (segment) {
var res = "";
if ( !! prevSegment) {
res = prevSegment !== "#" ? "\\.\\b" : "\\b";
}
if (segment === "#") {
res += "[\\s\\S]*";
} else if (segment === "*") {
res += "[^.]+";
} else {
res += segment;
}
prevSegment = segment;
return res;
}).join("") + "$";
rgx = this.regex[binding] = new RegExp(pattern);
}
this.cache[topic] = this.cache[topic] || {};
this.cache[topic][binding] = result = rgx.test(topic);
return result;
},
reset: function () {
this.cache = {};
this.regex = {};
}
};
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);
}
}
}
};
var pubInProgress = 0;
var unSubQueue = [];
var clearUnSubQueue = function () {
while (unSubQueue.length) {
localBus.unsubscribe(unSubQueue.shift());
}
};
var localBus = {
addWireTap: function (callback) {
var self = this;
self.wireTaps.push(callback);
return function () {
var idx = self.wireTaps.indexOf(callback);
if (idx !== -1) {
self.wireTaps.splice(idx, 1);
}
};
},
publish: function (envelope) {
++pubInProgress;
envelope.timeStamp = new Date();
_.each(this.wireTaps, function (tap) {
tap(envelope.data, envelope);
});
if (this.subscriptions[envelope.channel]) {
_.each(this.subscriptions[envelope.channel], function (subscribers) {
var idx = 0,
len = subscribers.length,
subDef;
while (idx < len) {
if (subDef = subscribers[idx++]) {
fireSub(subDef, envelope);
}
}
});
}
if (--pubInProgress === 0) {
clearUnSubQueue();
}
return envelope;
},
reset: function () {
if (this.subscriptions) {
_.each(this.subscriptions, function (channel) {
_.each(channel, function (topic) {
while (topic.length) {
topic.pop().unsubscribe();
}
});
});
this.subscriptions = {};
}
},
subscribe: function (subDef) {
var channel = this.subscriptions[subDef.channel],
subs;
if (!channel) {
channel = this.subscriptions[subDef.channel] = {};
}
subs = this.subscriptions[subDef.channel][subDef.topic];
if (!subs) {
subs = this.subscriptions[subDef.channel][subDef.topic] = [];
}
subs.push(subDef);
return subDef;
},
subscriptions: {},
wireTaps: [],
unsubscribe: function (config) {
if (pubInProgress) {
unSubQueue.push(config);
return;
}
if (this.subscriptions[config.channel] && this.subscriptions[config.channel][config.topic]) {
var len = this.subscriptions[config.channel][config.topic].length,
idx = 0;
while (idx < len) {
if (this.subscriptions[config.channel][config.topic][idx] === config) {
this.subscriptions[config.channel][config.topic].splice(idx, 1);
break;
}
idx += 1;
}
}
}
};
postal = {
configuration: {
bus: localBus,
resolver: bindingsResolver,
DEFAULT_CHANNEL: "/",
SYSTEM_CHANNEL: "postal"
},
ChannelDefinition: ChannelDefinition,
SubscriptionDefinition: SubscriptionDefinition,
channel: function (channelName) {
return new ChannelDefinition(channelName);
},
subscribe: function (options) {
var subDef = new SubscriptionDefinition(options.channel || postal.configuration.DEFAULT_CHANNEL, options.topic, options.callback);
postal.configuration.bus.publish({
channel: postal.configuration.SYSTEM_CHANNEL,
topic: "subscription.created",
data: {
event: "subscription.created",
channel: subDef.channel,
topic: subDef.topic
}
});
return postal.configuration.bus.subscribe(subDef);
},
publish: function (envelope) {
envelope.channel = envelope.channel || postal.configuration.DEFAULT_CHANNEL;
return postal.configuration.bus.publish(envelope);
},
unsubscribe: function (subdef) {
postal.configuration.bus.unsubscribe(subdef);
postal.configuration.bus.publish({
channel: postal.configuration.SYSTEM_CHANNEL,
topic: "subscription.removed",
data: {
event: "subscription.removed",
channel: subdef.channel,
topic: subdef.topic
}
});
},
addWireTap: function (callback) {
return this.configuration.bus.addWireTap(callback);
},
linkChannels: function (sources, destinations) {
var result = [];
sources = !_.isArray(sources) ? [sources] : sources;
destinations = !_.isArray(destinations) ? [destinations] : destinations;
_.each(sources, function (source) {
var sourceTopic = source.topic || "#";
_.each(destinations, function (destination) {
var destChannel = destination.channel || postal.configuration.DEFAULT_CHANNEL;
result.push(
postal.subscribe({
channel: source.channel || postal.configuration.DEFAULT_CHANNEL,
topic: sourceTopic,
callback: function (data, env) {
var newEnv = _.clone(env);
newEnv.topic = _.isFunction(destination.topic) ? destination.topic(env.topic) : destination.topic || env.topic;
newEnv.channel = destChannel;
newEnv.data = data;
postal.publish(newEnv);
}
}));
});
});
return result;
},
noConflict: function () {
if (typeof window === "undefined") {
throw new Error("noConflict can only be used in browser clients which aren't using AMD modules");
}
global.postal = prevPostal;
return this;
},
utils: {
getSubscribersFor: function () {
var channel = arguments[0],
tpc = arguments[1];
if (arguments.length === 1) {
channel = arguments[0].channel || postal.configuration.DEFAULT_CHANNEL;
tpc = arguments[0].topic;
}
if (postal.configuration.bus.subscriptions[channel] && Object.prototype.hasOwnProperty.call(postal.configuration.bus.subscriptions[channel], tpc)) {
return postal.configuration.bus.subscriptions[channel][tpc];
}
return [];
},
reset: function () {
postal.configuration.bus.reset();
postal.configuration.resolver.reset();
}
}
};
localBus.subscriptions[postal.configuration.SYSTEM_CHANNEL] = {};
if (global && Object.prototype.hasOwnProperty.call(global, "__postalReady__") && _.isArray(global.__postalReady__)) {
while (global.__postalReady__.length) {
global.__postalReady__.shift().onReady(postal);
}
}
return postal;
}));

8
lib/basic/postal.basic.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -24,6 +24,48 @@
var postal;
var prevPostal = global.postal;
var SubscriptionDefinition = function (channel, topic, callback) {
if (arguments.length !== 3) {
throw new Error("You must provide a channel, topic and callback when creating a SubscriptionDefinition instance.");
}
if (topic.length === 0) {
throw new Error("Topics cannot be empty");
}
this.channel = channel;
this.topic = topic;
this.subscribe(callback);
};
SubscriptionDefinition.prototype = {
unsubscribe: function () {
if (!this.inactive) {
this.inactive = true;
postal.unsubscribe(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;
},
withContext: function (context) {
this.callback.context(context);
return this;
}
};
var Strategy = function (options) {
var _target = options.owner[options.prop];
if (typeof _target !== "function") {
@ -93,10 +135,46 @@
}
};
var ConsecutiveDistinctPredicate = function () {
var previous;
return function (data) {
var eq = false;
if (_.isString(data)) {
eq = data === previous;
previous = data;
}
else {
eq = _.isEqual(data, previous);
previous = _.clone(data);
}
return !eq;
};
};
var DistinctPredicate = function () {
var previous = [];
return function (data) {
var isDistinct = !_.any(previous, function (p) {
if (_.isObject(data) || _.isArray(data)) {
return _.isEqual(data, p);
}
return data === p;
});
if (isDistinct) {
previous.push(data);
}
return isDistinct;
};
};
var strats = {
setTimeout: function (ms) {
withDelay: function (ms) {
if (_.isNaN(ms)) {
throw "Milliseconds must be a number";
}
return {
name: "setTimeout",
name: "withDelay",
fn: function (next, data, envelope) {
setTimeout(function () {
next(data, envelope);
@ -104,25 +182,37 @@
}
};
},
after: function (maxCalls, callback) {
defer: function () {
return this.withDelay(0);
},
stopAfter: function (maxCalls, callback) {
if (_.isNaN(maxCalls) || maxCalls <= 0) {
throw "The value provided to disposeAfter (maxCalls) must be a number greater than zero.";
}
var dispose = _.after(maxCalls, callback);
return {
name: "after",
name: "stopAfter",
fn: function (next, data, envelope) {
dispose();
next(data, envelope);
}
};
},
throttle: function (ms) {
withThrottle: function (ms) {
if (_.isNaN(ms)) {
throw "Milliseconds must be a number";
}
return {
name: "throttle",
name: "withThrottle",
fn: _.throttle(function (next, data, envelope) {
next(data, envelope);
}, ms)
};
},
debounce: function (ms, immediate) {
withDebounce: function (ms, immediate) {
if (_.isNaN(ms)) {
throw "Milliseconds must be a number";
}
return {
name: "debounce",
fn: _.debounce(function (next, data, envelope) {
@ -130,9 +220,12 @@
}, ms, !! immediate)
};
},
predicate: function (pred) {
withConstraint: function (pred) {
if (!_.isFunction(pred)) {
throw "Predicate constraint must be a function";
}
return {
name: "predicate",
name: "withConstraint",
fn: function (next, data, envelope) {
if (pred.call(this, data, envelope)) {
next.call(this, data, envelope);
@ -157,37 +250,54 @@
}
};
var ConsecutiveDistinctPredicate = function () {
var previous;
return function (data) {
var eq = false;
if (_.isString(data)) {
eq = data === previous;
previous = data;
}
else {
eq = _.isEqual(data, previous);
previous = _.clone(data);
}
return !eq;
};
SubscriptionDefinition.prototype.defer = function () {
this.callback.useStrategy(strats.defer());
return this;
};
var DistinctPredicate = function () {
var previous = [];
SubscriptionDefinition.prototype.disposeAfter = function (maxCalls) {
var self = this;
self.callback.useStrategy(strats.stopAfter(maxCalls, function () {
self.unsubscribe.call(self);
}));
return self;
};
return function (data) {
var isDistinct = !_.any(previous, function (p) {
if (_.isObject(data) || _.isArray(data)) {
return _.isEqual(data, p);
}
return data === p;
});
if (isDistinct) {
previous.push(data);
}
return isDistinct;
};
SubscriptionDefinition.prototype.distinctUntilChanged = function () {
this.callback.useStrategy(strats.distinct());
return this;
};
SubscriptionDefinition.prototype.distinct = function () {
this.callback.useStrategy(strats.distinct({
all: true
}));
return this;
};
SubscriptionDefinition.prototype.once = function () {
this.disposeAfter(1);
return this;
};
SubscriptionDefinition.prototype.withConstraint = function (predicate) {
this.callback.useStrategy(strats.withConstraint(predicate));
return this;
};
SubscriptionDefinition.prototype.withDebounce = function (milliseconds, immediate) {
this.callback.useStrategy(strats.withDebounce(milliseconds, immediate));
return this;
};
SubscriptionDefinition.prototype.withDelay = function (milliseconds) {
this.callback.useStrategy(strats.withDelay(milliseconds));
return this;
};
SubscriptionDefinition.prototype.withThrottle = function (milliseconds) {
this.callback.useStrategy(strats.withThrottle(milliseconds));
return this;
};
var ChannelDefinition = function (channelName) {
@ -209,110 +319,6 @@
return postal.configuration.bus.publish(envelope);
};
var SubscriptionDefinition = function (channel, topic, callback) {
if (arguments.length !== 3) {
throw new Error("You must provide a channel, topic and callback when creating a SubscriptionDefinition instance.");
}
if (topic.length === 0) {
throw new Error("Topics cannot be empty");
}
this.channel = channel;
this.topic = topic;
this.subscribe(callback);
};
SubscriptionDefinition.prototype = {
unsubscribe: function () {
if (!this.inactive) {
this.inactive = true;
postal.unsubscribe(this);
}
},
defer: function () {
this.callback.useStrategy(postal.configuration.strategies.setTimeout(0));
return this;
},
disposeAfter: function (maxCalls) {
if (_.isNaN(maxCalls) || maxCalls <= 0) {
throw "The value provided to disposeAfter (maxCalls) must be a number greater than zero.";
}
var self = this;
self.callback.useStrategy(postal.configuration.strategies.after(maxCalls, function () {
self.unsubscribe.call(self);
}));
return self;
},
distinctUntilChanged: function () {
this.callback.useStrategy(postal.configuration.strategies.distinct());
return this;
},
distinct: function () {
this.callback.useStrategy(postal.configuration.strategies.distinct({
all: true
}));
return this;
},
once: function () {
this.disposeAfter(1);
return this;
},
withConstraint: function (predicate) {
if (!_.isFunction(predicate)) {
throw "Predicate constraint must be a function";
}
this.callback.useStrategy(postal.configuration.strategies.predicate(predicate));
return this;
},
withContext: function (context) {
this.callback.context(context);
return this;
},
withDebounce: function (milliseconds, immediate) {
if (_.isNaN(milliseconds)) {
throw "Milliseconds must be a number";
}
this.callback.useStrategy(postal.configuration.strategies.debounce(milliseconds, immediate));
return this;
},
withDelay: function (milliseconds) {
if (_.isNaN(milliseconds)) {
throw "Milliseconds must be a number";
}
this.callback.useStrategy(postal.configuration.strategies.setTimeout(milliseconds));
return this;
},
withThrottle: function (milliseconds) {
if (_.isNaN(milliseconds)) {
throw "Milliseconds must be a number";
}
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;
}
};
var bindingsResolver = {
cache: {},
regex: {},
@ -463,8 +469,7 @@
bus: localBus,
resolver: bindingsResolver,
DEFAULT_CHANNEL: "/",
SYSTEM_CHANNEL: "postal",
strategies: strats
SYSTEM_CHANNEL: "postal"
},
ChannelDefinition: ChannelDefinition,

2
lib/postal.min.js vendored

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,195 @@
/**
* postal - Pub/Sub library providing wildcard subscriptions, complex message handling, etc. Works server and client-side.
* Author: Jim Cowart (http://freshbrewedcode.com/jimcowart)
* Version: v0.8.11
* Url: http://github.com/postaljs/postal.js
* License(s): MIT, GPL
*/
(function (root, factory) {
if (typeof module === "object" && module.exports) {
// Node, or CommonJS-Like environments
module.exports = function (postal) {
return factory(postal, this);
};
} else if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module.
define(["postal"], function (postal) {
return factory(postal, root);
});
} else {
// Browser globals
root.postal = factory(root.postal, root);
}
}(this, function (postal, global, undefined) {
(function (SubscriptionDefinition) {
var ConsecutiveDistinctPredicate = function () {
var previous;
return function (data) {
var eq = false;
if (_.isString(data)) {
eq = data === previous;
previous = data;
}
else {
eq = _.isEqual(data, previous);
previous = _.clone(data);
}
return !eq;
};
};
var DistinctPredicate = function () {
var previous = [];
return function (data) {
var isDistinct = !_.any(previous, function (p) {
if (_.isObject(data) || _.isArray(data)) {
return _.isEqual(data, p);
}
return data === p;
});
if (isDistinct) {
previous.push(data);
}
return isDistinct;
};
};
var strats = {
withDelay: function (ms) {
if (_.isNaN(ms)) {
throw "Milliseconds must be a number";
}
return {
name: "withDelay",
fn: function (next, data, envelope) {
setTimeout(function () {
next(data, envelope);
}, ms);
}
};
},
defer: function () {
return this.withDelay(0);
},
stopAfter: function (maxCalls, callback) {
if (_.isNaN(maxCalls) || maxCalls <= 0) {
throw "The value provided to disposeAfter (maxCalls) must be a number greater than zero.";
}
var dispose = _.after(maxCalls, callback);
return {
name: "stopAfter",
fn: function (next, data, envelope) {
dispose();
next(data, envelope);
}
};
},
withThrottle: function (ms) {
if (_.isNaN(ms)) {
throw "Milliseconds must be a number";
}
return {
name: "withThrottle",
fn: _.throttle(function (next, data, envelope) {
next(data, envelope);
}, ms)
};
},
withDebounce: function (ms, immediate) {
if (_.isNaN(ms)) {
throw "Milliseconds must be a number";
}
return {
name: "debounce",
fn: _.debounce(function (next, data, envelope) {
next(data, envelope);
}, ms, !! immediate)
};
},
withConstraint: function (pred) {
if (!_.isFunction(pred)) {
throw "Predicate constraint must be a function";
}
return {
name: "withConstraint",
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);
}
}
};
}
};
SubscriptionDefinition.prototype.defer = function () {
this.callback.useStrategy(strats.defer());
return this;
};
SubscriptionDefinition.prototype.disposeAfter = function (maxCalls) {
var self = this;
self.callback.useStrategy(strats.stopAfter(maxCalls, function () {
self.unsubscribe.call(self);
}));
return self;
};
SubscriptionDefinition.prototype.distinctUntilChanged = function () {
this.callback.useStrategy(strats.distinct());
return this;
};
SubscriptionDefinition.prototype.distinct = function () {
this.callback.useStrategy(strats.distinct({
all: true
}));
return this;
};
SubscriptionDefinition.prototype.once = function () {
this.disposeAfter(1);
return this;
};
SubscriptionDefinition.prototype.withConstraint = function (predicate) {
this.callback.useStrategy(strats.withConstraint(predicate));
return this;
};
SubscriptionDefinition.prototype.withDebounce = function (milliseconds, immediate) {
this.callback.useStrategy(strats.withDebounce(milliseconds, immediate));
return this;
};
SubscriptionDefinition.prototype.withDelay = function (milliseconds) {
this.callback.useStrategy(strats.withDelay(milliseconds));
return this;
};
SubscriptionDefinition.prototype.withThrottle = function (milliseconds) {
this.callback.useStrategy(strats.withThrottle(milliseconds));
return this;
};
}(postal.SubscriptionDefinition));
return postal;
}));

View file

@ -0,0 +1,8 @@
/**
* postal - Pub/Sub library providing wildcard subscriptions, complex message handling, etc. Works server and client-side.
* Author: Jim Cowart (http://freshbrewedcode.com/jimcowart)
* Version: v0.8.11
* Url: http://github.com/postaljs/postal.js
* License(s): MIT, GPL
*/
(function(t,n){"object"==typeof module&&module.exports?module.exports=function(t){return n(t,this)}:"function"==typeof define&&define.amd?define(["postal"],function(e){return n(e,t)}):t.postal=n(t.postal,t)})(this,function(t){return function(t){var n=function(){var t;return function(n){var e=!1;return _.isString(n)?(e=n===t,t=n):(e=_.isEqual(n,t),t=_.clone(n)),!e}},e=function(){var t=[];return function(n){var e=!_.any(t,function(t){return _.isObject(n)||_.isArray(n)?_.isEqual(n,t):n===t});return e&&t.push(n),e}},i={withDelay:function(t){if(_.isNaN(t))throw"Milliseconds must be a number";return{name:"withDelay",fn:function(n,e,i){setTimeout(function(){n(e,i)},t)}}},defer:function(){return this.withDelay(0)},stopAfter:function(t,n){if(_.isNaN(t)||0>=t)throw"The value provided to disposeAfter (maxCalls) must be a number greater than zero.";var e=_.after(t,n);return{name:"stopAfter",fn:function(t,n,i){e(),t(n,i)}}},withThrottle:function(t){if(_.isNaN(t))throw"Milliseconds must be a number";return{name:"withThrottle",fn:_.throttle(function(t,n,e){t(n,e)},t)}},withDebounce:function(t,n){if(_.isNaN(t))throw"Milliseconds must be a number";return{name:"debounce",fn:_.debounce(function(t,n,e){t(n,e)},t,!!n)}},withConstraint:function(t){if(!_.isFunction(t))throw"Predicate constraint must be a function";return{name:"withConstraint",fn:function(n,e,i){t.call(this,e,i)&&n.call(this,e,i)}}},distinct:function(t){t=t||{};var i=function(t){return t[0]},r=t.all?new e(i):new n(i);return{name:"distinct",fn:function(t,n,e){r(n)&&t(n,e)}}}};t.prototype.defer=function(){return this.callback.useStrategy(i.defer()),this},t.prototype.disposeAfter=function(t){var n=this;return n.callback.useStrategy(i.stopAfter(t,function(){n.unsubscribe.call(n)})),n},t.prototype.distinctUntilChanged=function(){return this.callback.useStrategy(i.distinct()),this},t.prototype.distinct=function(){return this.callback.useStrategy(i.distinct({all:!0})),this},t.prototype.once=function(){return this.disposeAfter(1),this},t.prototype.withConstraint=function(t){return this.callback.useStrategy(i.withConstraint(t)),this},t.prototype.withDebounce=function(t,n){return this.callback.useStrategy(i.withDebounce(t,n)),this},t.prototype.withDelay=function(t){return this.callback.useStrategy(i.withDelay(t)),this},t.prototype.withThrottle=function(t){return this.callback.useStrategy(i.withThrottle(t)),this}}(t.SubscriptionDefinition),t});

34
spec/basic.html Normal file
View file

@ -0,0 +1,34 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<link rel="stylesheet" href="../bower/mocha/mocha.css" />
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
</head>
<body>
<div id="mocha"></div>
<script type="text/javascript">
// initial postal value for a noConflict test....
window.postal = { foo: "bar" };
</script>
<script type="text/javascript" src="../bower/jquery/jquery.min.js"></script>
<script type="text/javascript" src="../bower/underscore/underscore-min.js"></script>
<script type="text/javascript" src="../bower/expect/expect.js"></script>
<script type="text/javascript" src="../bower/mocha/mocha.js"></script>
<script type="text/javascript">
mocha.setup({ ui: 'bdd', timeout: 60000 });
</script>
<script type="text/javascript" src="../lib/postal.js"></script>
<script type="text/javascript" src="../lib/strategies-add-on/postal.strategies.js"></script>
<script type="text/javascript" src="postaljs.spec.js"></script>
<script type="text/javascript" src="utils.spec.js"></script>
<script type="text/javascript" src="linkedChannels.spec.js"></script>
<script type="text/javascript" src="bindingsResolver.spec.js"></script>
<script type="text/javascript" src="channelDefinition.spec.js"></script>
<script type="text/javascript" src="subscriptionDefinition.spec.js"></script>
<script type="text/javascript">
mocha.run();
</script>
</body>
</html>

View file

@ -24,6 +24,8 @@
<script type="text/javascript" src="bindingsResolver.spec.js"></script>
<script type="text/javascript" src="channelDefinition.spec.js"></script>
<script type="text/javascript" src="subscriptionDefinition.spec.js"></script>
<script type="text/javascript" src="subDef.strategies.spec.js"></script>
<script type="text/javascript" src="postaljs.strategies.spec.js"></script>
<script type="text/javascript">
mocha.run();
</script>

View file

@ -70,76 +70,6 @@
expect( caughtSubscribeEvent ).to.be.ok();
} );
} );
describe( "When subscribing and ignoring duplicates", function () {
var subInvokedCnt = 0;
before( function () {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic", function ( data ) {
subInvokedCnt++;
} )
.distinctUntilChanged();
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
} );
after( function () {
postal.utils.reset();
subInvokedCnt = 0;
} );
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 );
} );
} );
describe( "When subscribing with a disposeAfter of 5", function () {
var msgReceivedCnt = 0, subExistsAfter, systemSubscription;
before( function () {
caughtUnsubscribeEvent = false;
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic", function () {
msgReceivedCnt++;
}).disposeAfter( 5 );
systemSubscription = postal.subscribe( {
channel : "postal",
topic : "subscription.*",
callback : function ( data, env ) {
if ( data.event &&
data.event === "subscription.removed" &&
data.channel === "MyChannel" &&
data.topic === "MyTopic" ) {
caughtUnsubscribeEvent = true;
}
}
} );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
subExistsAfter = postal.configuration.bus.subscriptions.MyChannel.MyTopic.length !== 0;
} );
after( function () {
postal.utils.reset();
} );
it( "subscription callback should be invoked 5 times", function () {
expect( msgReceivedCnt ).to.be( 5 );
} );
it( "subscription should not exist after unsubscribe", function () {
expect( subExistsAfter ).to.not.be.ok();
} );
it( "should have captured unsubscription creation event", function () {
expect( caughtUnsubscribeEvent ).to.be.ok();
} );
it( "postal.getSubscribersFor('MyChannel', 'MyTopic') should not return any subscriptions", function () {
expect( postal.utils.getSubscribersFor("MyChannel", "MyTopic").length ).to.be(0);
} );
} );
describe( "When subscribing with a hierarchical binding, no wildcards", function () {
var count = 0, channelB, channelC;
before( function () {
@ -221,179 +151,6 @@
expect( count ).to.be( 3 );
} );
} );
describe( "When subscribing with debounce", function () {
var results = [], debouncedChannel;
before( function () {
debouncedChannel = postal.channel( "DebounceChannel" );
subscription = debouncedChannel.subscribe( "MyTopic",
function ( data ) {
results.push( data );
} ).withDebounce( 800 );
} );
after( function () {
postal.utils.reset();
} );
it( "should have only invoked debounced callback once", function ( done ) {
debouncedChannel.publish( "MyTopic", 1 ); // starts the two second clock on debounce
setTimeout( function () {
debouncedChannel.publish( "MyTopic", 2 );
}, 20 ); // should not invoke callback
setTimeout( function () {
debouncedChannel.publish( "MyTopic", 3 );
}, 80 ); // should not invoke callback
setTimeout( function () {
debouncedChannel.publish( "MyTopic", 4 );
}, 250 ); // should not invoke callback
setTimeout( function () {
debouncedChannel.publish( "MyTopic", 5 );
}, 500 ); // should not invoke callback
setTimeout( function () {
debouncedChannel.publish( "MyTopic", 6 );
}, 1000 ); // should invoke callback
setTimeout( function () {
expect( results[0] ).to.be( 6 );
expect( results.length ).to.be( 1 );
done();
}, 1900 );
} );
} );
describe( "When subscribing with defer", function () {
var results = [];
before( function () {
channel = postal.channel( "DeferChannel" );
} );
after( function () {
postal.utils.reset();
} );
it( "should have met expected results", function ( done ) {
subscription = channel.subscribe( "MyTopic",
function ( data ) {
results.push( "second" );
expect( results[0] ).to.be( "first" );
expect( results[1] ).to.be( "second" );
done();
} ).defer();
channel.publish( "MyTopic", "Testing123" );
results.push( "first" );
} );
} );
describe( "When subscribing with delay", function () {
var results = [];
before( function () {
channel = postal.channel( "DelayChannel" );
} );
after( function () {
postal.utils.reset();
} );
it( "should have met expected results", function ( done ) {
subscription = channel.subscribe( "MyTopic",
function ( data ) {
results.push( "second" );
expect( results[0] ).to.be( "first" );
expect( results[1] ).to.be( "second" );
done();
} ).withDelay( 500 );
channel.publish( "MyTopic", "Testing123" );
results.push( "first" );
} );
} );
describe( "When subscribing with once()", function () {
var msgReceivedCnt = 0;
before( function () {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic",function ( data ) {
msgReceivedCnt++;
} ).once();
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
} );
after( function () {
postal.utils.reset();
} );
it( "subscription callback should be invoked 1 time", function () {
expect( msgReceivedCnt ).to.be( 1 );
} );
} );
describe( "When subscribing with multiple constraints returning true", function () {
var recvd = false;
before( function () {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic", function ( data ) {
recvd = 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 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 one constraint returning false", function () {
var recvd = false;
before( function () {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic", function ( data ) {
recvd = true;
} )
.withConstraint( function () {
return false;
} );
channel.publish( "MyTopic", "Testing123" );
} );
after( function () {
postal.utils.reset();
recvd = false;
} );
it( "should have a constraint on the subscription", function () {
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 one constraint returning true", function () {
var recvd = false;
before( function () {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic", function ( data ) {
recvd = true;
} )
.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( subscription.callback.strategies()[0].name ).to.be( "predicate" );
} );
it( "should have invoked the subscription callback", function () {
expect( recvd ).to.be.ok();
} );
} );
describe( "When subscribing with the context being set", function () {
var count = 0,
obj = {
@ -416,36 +173,6 @@
expect( count ).to.be( 1 );
} );
} );
describe( "When subscribing with throttle", function () {
var results = [], throttledChannel;
before( function () {
throttledChannel = postal.channel( "ThrottleChannel" );
subscription = throttledChannel.subscribe( "MyTopic",
function ( data ) {
results.push( data );
} ).withThrottle( 500 );
} );
after( function () {
postal.utils.reset();
} );
it( "should have only invoked throttled callback twice", function ( done ) {
throttledChannel.publish( "MyTopic", 1 ); // starts the two second clock on debounce
setTimeout( function () {
throttledChannel.publish( "MyTopic", 800 );
}, 800 ); // should invoke callback
for ( var i = 0; i < 20; i++ ) {
(function ( x ) {
throttledChannel.publish( "MyTopic", x );
})( i );
}
setTimeout( function () {
expect( results[0] ).to.be( 1 );
expect( results[2] ).to.be( 800 );
expect( results.length ).to.be( 3 );
done();
}, 1500 );
} );
} );
describe( "When using global subscribe api", function () {
before( function () {
subscription = postal.subscribe( {

View file

@ -0,0 +1,288 @@
/* global describe, postal, it, after, before, expect */
(function(global) {
var postal = typeof window === "undefined" ? require("../lib/postal.js") : window.postal;
var expect = typeof window === "undefined" ? require("expect.js") : window.expect;
var _ = typeof window === "undefined" ? require("underscore") : window._;
var subscription;
var sub;
var channel;
var caughtSubscribeEvent = false;
var caughtUnsubscribeEvent = false;
describe("Subscription Creation - Strategies", function(){
describe( "When subscribing and ignoring duplicates", function () {
var subInvokedCnt = 0;
before( function () {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic", function ( data ) {
subInvokedCnt++;
} )
.distinctUntilChanged();
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
} );
after( function () {
postal.utils.reset();
subInvokedCnt = 0;
} );
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 );
} );
} );
describe( "When subscribing with a disposeAfter of 5", function () {
var msgReceivedCnt = 0, subExistsAfter, systemSubscription;
before( function () {
caughtUnsubscribeEvent = false;
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic", function () {
msgReceivedCnt++;
}).disposeAfter( 5 );
systemSubscription = postal.subscribe( {
channel : "postal",
topic : "subscription.*",
callback : function ( data, env ) {
if ( data.event &&
data.event === "subscription.removed" &&
data.channel === "MyChannel" &&
data.topic === "MyTopic" ) {
caughtUnsubscribeEvent = true;
}
}
} );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
subExistsAfter = postal.configuration.bus.subscriptions.MyChannel.MyTopic.length !== 0;
} );
after( function () {
postal.utils.reset();
} );
it( "subscription callback should be invoked 5 times", function () {
expect( msgReceivedCnt ).to.be( 5 );
} );
it( "subscription should not exist after unsubscribe", function () {
expect( subExistsAfter ).to.not.be.ok();
} );
it( "should have captured unsubscription creation event", function () {
expect( caughtUnsubscribeEvent ).to.be.ok();
} );
it( "postal.getSubscribersFor('MyChannel', 'MyTopic') should not return any subscriptions", function () {
expect( postal.utils.getSubscribersFor("MyChannel", "MyTopic").length ).to.be(0);
} );
} );
describe( "When subscribing with debounce", function () {
var results = [], debouncedChannel;
before( function () {
debouncedChannel = postal.channel( "DebounceChannel" );
subscription = debouncedChannel.subscribe( "MyTopic",
function ( data ) {
results.push( data );
} ).withDebounce( 800 );
} );
after( function () {
postal.utils.reset();
} );
it( "should have only invoked debounced callback once", function ( done ) {
debouncedChannel.publish( "MyTopic", 1 ); // starts the two second clock on debounce
setTimeout( function () {
debouncedChannel.publish( "MyTopic", 2 );
}, 20 ); // should not invoke callback
setTimeout( function () {
debouncedChannel.publish( "MyTopic", 3 );
}, 80 ); // should not invoke callback
setTimeout( function () {
debouncedChannel.publish( "MyTopic", 4 );
}, 250 ); // should not invoke callback
setTimeout( function () {
debouncedChannel.publish( "MyTopic", 5 );
}, 500 ); // should not invoke callback
setTimeout( function () {
debouncedChannel.publish( "MyTopic", 6 );
}, 1000 ); // should invoke callback
setTimeout( function () {
expect( results[0] ).to.be( 6 );
expect( results.length ).to.be( 1 );
done();
}, 1900 );
} );
} );
describe( "When subscribing with defer", function () {
var results = [];
before( function () {
channel = postal.channel( "DeferChannel" );
} );
after( function () {
postal.utils.reset();
} );
it( "should have met expected results", function ( done ) {
subscription = channel.subscribe( "MyTopic",
function ( data ) {
results.push( "second" );
expect( results[0] ).to.be( "first" );
expect( results[1] ).to.be( "second" );
done();
} ).defer();
channel.publish( "MyTopic", "Testing123" );
results.push( "first" );
} );
} );
describe( "When subscribing with delay", function () {
var results = [];
before( function () {
channel = postal.channel( "DelayChannel" );
} );
after( function () {
postal.utils.reset();
} );
it( "should have met expected results", function ( done ) {
subscription = channel.subscribe( "MyTopic",
function ( data ) {
results.push( "second" );
expect( results[0] ).to.be( "first" );
expect( results[1] ).to.be( "second" );
done();
} ).withDelay( 500 );
channel.publish( "MyTopic", "Testing123" );
results.push( "first" );
} );
} );
describe( "When subscribing with once()", function () {
var msgReceivedCnt = 0;
before( function () {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic",function ( data ) {
msgReceivedCnt++;
} ).once();
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
channel.publish( "MyTopic", "Testing123" );
} );
after( function () {
postal.utils.reset();
} );
it( "subscription callback should be invoked 1 time", function () {
expect( msgReceivedCnt ).to.be( 1 );
} );
} );
describe( "When subscribing with multiple constraints returning true", function () {
var recvd = false;
before( function () {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic", function ( data ) {
recvd = 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 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 one constraint returning false", function () {
var recvd = false;
before( function () {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic", function ( data ) {
recvd = true;
} )
.withConstraint( function () {
return false;
} );
channel.publish( "MyTopic", "Testing123" );
} );
after( function () {
postal.utils.reset();
recvd = false;
} );
it( "should have a constraint on the subscription", function () {
expect( subscription.callback.strategies()[0].name ).to.be( "withConstraint" );
} );
it( "should not have invoked the subscription callback", function () {
expect( recvd ).to.not.be.ok();
} );
} );
describe( "When subscribing with one constraint returning true", function () {
var recvd = false;
before( function () {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic", function ( data ) {
recvd = true;
} )
.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( subscription.callback.strategies()[0].name ).to.be( "withConstraint" );
} );
it( "should have invoked the subscription callback", function () {
expect( recvd ).to.be.ok();
} );
} );
describe( "When subscribing with throttle", function () {
var results = [], throttledChannel;
before( function () {
throttledChannel = postal.channel( "ThrottleChannel" );
subscription = throttledChannel.subscribe( "MyTopic",
function ( data ) {
results.push( data );
} ).withThrottle( 500 );
} );
after( function () {
postal.utils.reset();
} );
it( "should have only invoked throttled callback twice", function ( done ) {
throttledChannel.publish( "MyTopic", 1 ); // starts the two second clock on debounce
setTimeout( function () {
throttledChannel.publish( "MyTopic", 800 );
}, 800 ); // should invoke callback
for ( var i = 0; i < 20; i++ ) {
(function ( x ) {
throttledChannel.publish( "MyTopic", x );
})( i );
}
setTimeout( function () {
expect( results[0] ).to.be( 1 );
expect( results[2] ).to.be( 800 );
expect( results.length ).to.be( 3 );
done();
}, 1500 );
} );
} );
});
}(this));

View file

@ -0,0 +1,197 @@
/* global describe, postal, it, after, before, expect */
(function() {
var postal = typeof window === "undefined" ? require("../lib/postal.js") : window.postal;
var expect = typeof window === "undefined" ? require("expect.js") : window.expect;
var NO_OP = function () {};
var SubscriptionDefinition = postal.SubscriptionDefinition;
describe( "SubscriptionDefinition - Strategies", function () {
describe( "When setting distinctUntilChanged", function () {
var sDefa = new SubscriptionDefinition( "TestChannel", "TestTopic", NO_OP ).distinctUntilChanged();
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( "callback should be a strategy", function () {
expect( typeof sDefb.callback.context ).to.be( "function" );
});
} );
describe( "When deferring the callback", function () {
var results = [], sDefe;
it( "Should defer the callback", function ( done ) {
sDefe = new SubscriptionDefinition( "TestChannel", "TestTopic", function ( data, env ) {
results.push( data );
expect( results[0] ).to.be( "first" );
expect( results[1] ).to.be( "second" );
expect( env.topic ).to.be( "TestTopic" );
done();
} ).defer();
sDefe.callback( "second", { topic : "TestTopic" } );
results.push( "first" );
} );
it( "Should keep the context intact", function ( done ) {
var context = {
key : 1234
};
sDefe = new SubscriptionDefinition( "TestChannel", "TestTopic", function ( data, env ) {
expect( this ).to.be( context );
done();
} ).withContext(context).defer();
sDefe.callback.call( sDefe.context, "stuff", { topic : "TestTopic" } );
} );
it( "Should keep the context intact when modified later", function ( done ) {
var context = {
key : 1234
};
sDefe = new SubscriptionDefinition( "TestChannel", "TestTopic", function ( data, env ) {
expect( this ).to.be( context );
done();
} ).defer().withContext(context);
sDefe.callback.call( sDefe.context, "stuff", { topic : "TestTopic" } );
} );
} );
describe( "When throttling the callback", function () {
it( "should have only invoked throttled callback twice", function ( done ) {
var results = [], timeout1, timeout2;
var sDefe = new SubscriptionDefinition( "ThrottleTest", "TestTopic", function ( data ) {
results.push( data );
} ).withThrottle( 1200, { leading: true } );
sDefe.callback( 1 ); // starts the clock on throttle
timeout1 = setTimeout( function () {
sDefe.callback( 700 );
}, 700 ); // should invoke callback
for ( var i = 0; i < 20; i++ ) {
(function ( x ) {
sDefe.callback( x );
})( i );
}
timeout2 = setTimeout( function () {
expect( results[0] ).to.be( 1 );
expect( results.length ).to.be( 2 );
done();
}, 1500 );
} );
it( "Should keep the context intact", function( done ) {
var results = [], timeout1, timeout2;
var sDefe = new SubscriptionDefinition( "ThrottleTest", "TestTopic", function ( data ) {
results.push( data );
} ).withThrottle( 1000, { leading: true } );
var context = {
key : 'abcd'
};
sDefe = new SubscriptionDefinition( "ThrottleTest", "TestTopic", function( data, env ) {
expect( this ).to.be( context );
done();
} ).withContext( context ).withThrottle( 500 );
sDefe.callback.call( sDefe.context, 1 );
} );
} );
describe( "When delaying the callback", function () {
var results = [], sDefe;
it( "Should delay the callback", function ( done ) {
sDefe = new SubscriptionDefinition( "DelayTest", "TestTopic", function ( data, env ) {
results.push( data );
expect( results[0] ).to.be( "first" );
expect( results[1] ).to.be( "second" );
expect( env.topic ).to.be( "TestTopic" );
done();
} ).withDelay( 200 );
sDefe.callback( "second", { topic : "TestTopic" } );
results.push( "first" );
} );
it( "Should keep the context intact", function ( done ) {
var context = {
key : 1234
};
sDefe = new SubscriptionDefinition( "DelayTest", "TestTopic", function ( data, env ) {
expect( this ).to.be( context );
done();
} ).withContext(context).withDelay( 200 );
sDefe.callback.call( sDefe.context, "stuff", { topic : "TestTopic" } );
} );
} );
describe( "When debouncing the callback", function () {
var results = [],
sDefe = new SubscriptionDefinition( "DebounceTest", "TestTopic", function ( data ) {
results.push( data );
} ).withDebounce( 800 );
it( "should have only invoked debounced callback once", function ( done ) {
sDefe.callback( 1 ); // starts the two second clock on debounce
setTimeout( function () {
sDefe.callback( 2 );
}, 20 ); // should not invoke callback
setTimeout( function () {
sDefe.callback( 3 );
}, 80 ); // should not invoke callback
setTimeout( function () {
sDefe.callback( 6 );
}, 800 ); // should invoke callback
setTimeout( function () {
expect( results[0] ).to.be( 6 );
expect( results.length ).to.be( 1 );
done();
}, 1800 );
} );
it( "Should keep the context intact", function ( done ) {
var context = {
key : 5678
};
var timeout;
sDefe = new SubscriptionDefinition( "DebounceTest", "TestTopic", function ( data, env ) {
expect( this ).to.be( context );
clearTimeout(timeout);
done();
} ).withContext(context).withDebounce( 100 );
sDefe.callback.call( sDefe.context, 1 );
timeout = setTimeout( function () {
sDefe.callback.call( sDefe.context, 2 );
}, 200 ); // should invoke callback
});
} );
describe( "When self disposing", function () {
var context = {
key : 'abcd'
};
it( "Should be inactive", function () {
var sDefe = new SubscriptionDefinition( "DisposeTest", "TestTopic", function ( data, env ) {
} ).withContext(context).disposeAfter( 1 );
sDefe.callback.call( sDefe.context, "stuff", { topic : "TestTopic" } );
expect( sDefe.inactive ).to.be( true );
} );
it( "Should keep the context intact", function ( done ) {
var sDefe = new SubscriptionDefinition( "DisposeTest", "TestTopic", function ( data, env ) {
expect( this ).to.be( context );
done();
} ).withContext(context).disposeAfter( 200 );
sDefe.callback.call( sDefe.context, "stuff", { topic : "TestTopic" } );
} );
} );
} );
}());

View file

@ -27,23 +27,6 @@
} );
} );
describe( "When setting distinctUntilChanged", function () {
var sDefa = new SubscriptionDefinition( "TestChannel", "TestTopic", NO_OP ).distinctUntilChanged();
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( "callback should be a strategy", function () {
expect( typeof sDefb.callback.context ).to.be( "function" );
});
} );
describe( "When setting the context", function () {
var obj = { name : "Rose" },
name,
@ -72,176 +55,5 @@
expect( sDefe.callback ).to.be( fn );
} );
} );
describe( "When deferring the callback", function () {
var results = [], sDefe;
it( "Should defer the callback", function ( done ) {
sDefe = new SubscriptionDefinition( "TestChannel", "TestTopic", function ( data, env ) {
results.push( data );
expect( results[0] ).to.be( "first" );
expect( results[1] ).to.be( "second" );
expect( env.topic ).to.be( "TestTopic" );
done();
} ).defer();
sDefe.callback( "second", { topic : "TestTopic" } );
results.push( "first" );
} );
it( "Should keep the context intact", function ( done ) {
var context = {
key : 1234
};
sDefe = new SubscriptionDefinition( "TestChannel", "TestTopic", function ( data, env ) {
expect( this ).to.be( context );
done();
} ).withContext(context).defer();
sDefe.callback.call( sDefe.context, "stuff", { topic : "TestTopic" } );
} );
it( "Should keep the context intact when modified later", function ( done ) {
var context = {
key : 1234
};
sDefe = new SubscriptionDefinition( "TestChannel", "TestTopic", function ( data, env ) {
expect( this ).to.be( context );
done();
} ).defer().withContext(context);
sDefe.callback.call( sDefe.context, "stuff", { topic : "TestTopic" } );
} );
} );
describe( "When throttling the callback", function () {
it( "should have only invoked throttled callback twice", function ( done ) {
var results = [], timeout1, timeout2;
var sDefe = new SubscriptionDefinition( "ThrottleTest", "TestTopic", function ( data ) {
results.push( data );
} ).withThrottle( 1200, { leading: true } );
sDefe.callback( 1 ); // starts the clock on throttle
timeout1 = setTimeout( function () {
sDefe.callback( 700 );
}, 700 ); // should invoke callback
for ( var i = 0; i < 20; i++ ) {
(function ( x ) {
sDefe.callback( x );
})( i );
}
timeout2 = setTimeout( function () {
expect( results[0] ).to.be( 1 );
expect( results.length ).to.be( 2 );
done();
}, 1500 );
} );
it( "Should keep the context intact", function( done ) {
var results = [], timeout1, timeout2;
var sDefe = new SubscriptionDefinition( "ThrottleTest", "TestTopic", function ( data ) {
results.push( data );
} ).withThrottle( 1000, { leading: true } );
var context = {
key : 'abcd'
};
sDefe = new SubscriptionDefinition( "ThrottleTest", "TestTopic", function( data, env ) {
expect( this ).to.be( context );
done();
} ).withContext( context ).withThrottle( 500 );
sDefe.callback.call( sDefe.context, 1 );
} );
} );
describe( "When delaying the callback", function () {
var results = [], sDefe;
it( "Should delay the callback", function ( done ) {
sDefe = new SubscriptionDefinition( "DelayTest", "TestTopic", function ( data, env ) {
results.push( data );
expect( results[0] ).to.be( "first" );
expect( results[1] ).to.be( "second" );
expect( env.topic ).to.be( "TestTopic" );
done();
} ).withDelay( 200 );
sDefe.callback( "second", { topic : "TestTopic" } );
results.push( "first" );
} );
it( "Should keep the context intact", function ( done ) {
var context = {
key : 1234
};
sDefe = new SubscriptionDefinition( "DelayTest", "TestTopic", function ( data, env ) {
expect( this ).to.be( context );
done();
} ).withContext(context).withDelay( 200 );
sDefe.callback.call( sDefe.context, "stuff", { topic : "TestTopic" } );
} );
} );
describe( "When debouncing the callback", function () {
var results = [],
sDefe = new SubscriptionDefinition( "DebounceTest", "TestTopic", function ( data ) {
results.push( data );
} ).withDebounce( 800 );
it( "should have only invoked debounced callback once", function ( done ) {
sDefe.callback( 1 ); // starts the two second clock on debounce
setTimeout( function () {
sDefe.callback( 2 );
}, 20 ); // should not invoke callback
setTimeout( function () {
sDefe.callback( 3 );
}, 80 ); // should not invoke callback
setTimeout( function () {
sDefe.callback( 6 );
}, 800 ); // should invoke callback
setTimeout( function () {
expect( results[0] ).to.be( 6 );
expect( results.length ).to.be( 1 );
done();
}, 1800 );
} );
it( "Should keep the context intact", function ( done ) {
var context = {
key : 5678
};
var timeout;
sDefe = new SubscriptionDefinition( "DebounceTest", "TestTopic", function ( data, env ) {
expect( this ).to.be( context );
clearTimeout(timeout);
done();
} ).withContext(context).withDebounce( 100 );
sDefe.callback.call( sDefe.context, 1 );
timeout = setTimeout( function () {
sDefe.callback.call( sDefe.context, 2 );
}, 200 ); // should invoke callback
});
} );
describe( "When self disposing", function () {
var context = {
key : 'abcd'
};
it( "Should be inactive", function () {
var sDefe = new SubscriptionDefinition( "DisposeTest", "TestTopic", function ( data, env ) {
} ).withContext(context).disposeAfter( 1 );
sDefe.callback.call( sDefe.context, "stuff", { topic : "TestTopic" } );
expect( sDefe.inactive ).to.be( true );
} );
it( "Should keep the context intact", function ( done ) {
var sDefe = new SubscriptionDefinition( "DisposeTest", "TestTopic", function ( data, env ) {
expect( this ).to.be( context );
done();
} ).withContext(context).disposeAfter( 200 );
sDefe.callback.call( sDefe.context, "stuff", { topic : "TestTopic" } );
} );
} );
} );
}());

View file

@ -5,8 +5,7 @@ postal = {
bus : localBus,
resolver : bindingsResolver,
DEFAULT_CHANNEL : "/",
SYSTEM_CHANNEL : "postal",
strategies : strats
SYSTEM_CHANNEL : "postal"
},
ChannelDefinition : ChannelDefinition,

View file

@ -1,16 +0,0 @@
/*jshint -W098 */
var ConsecutiveDistinctPredicate = function () {
var previous;
return function ( data ) {
var eq = false;
if ( _.isString( data ) ) {
eq = data === previous;
previous = data;
}
else {
eq = _.isEqual( data, previous );
previous = _.clone( data );
}
return !eq;
};
};

View file

@ -1,17 +0,0 @@
/*jshint -W098 */
var DistinctPredicate = function () {
var previous = [];
return function ( data ) {
var isDistinct = !_.any( previous, function ( p ) {
if ( _.isObject( data ) || _.isArray( data ) ) {
return _.isEqual( data, p );
}
return data === p;
} );
if ( isDistinct ) {
previous.push( data );
}
return isDistinct;
};
};

View file

@ -20,74 +20,6 @@ SubscriptionDefinition.prototype = {
}
},
defer : function () {
this.callback.useStrategy(postal.configuration.strategies.setTimeout(0));
return this;
},
disposeAfter : function ( maxCalls ) {
if ( _.isNaN( maxCalls ) || maxCalls <= 0 ) {
throw "The value provided to disposeAfter (maxCalls) must be a number greater than zero.";
}
var self = this;
self.callback.useStrategy(postal.configuration.strategies.after(maxCalls, function() {
self.unsubscribe.call(self);
}));
return self;
},
distinctUntilChanged : function () {
this.callback.useStrategy(postal.configuration.strategies.distinct());
return this;
},
distinct : function () {
this.callback.useStrategy(postal.configuration.strategies.distinct({ all: true }));
return this;
},
once : function () {
this.disposeAfter( 1 );
return this;
},
withConstraint : function ( predicate ) {
if ( !_.isFunction( predicate ) ) {
throw "Predicate constraint must be a function";
}
this.callback.useStrategy(postal.configuration.strategies.predicate(predicate));
return this;
},
withContext : function ( context ) {
this.callback.context(context);
return this;
},
withDebounce : function ( milliseconds, immediate ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
this.callback.useStrategy(postal.configuration.strategies.debounce(milliseconds, immediate));
return this;
},
withDelay : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
this.callback.useStrategy(postal.configuration.strategies.setTimeout(milliseconds));
return this;
},
withThrottle : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
this.callback.useStrategy(postal.configuration.strategies.throttle(milliseconds));
return this;
},
subscribe : function ( callback ) {
this.callback = callback;
this.callback = new Strategy({
@ -97,5 +29,12 @@ SubscriptionDefinition.prototype = {
lazyInit : true
});
return this;
},
withContext : function ( context ) {
this.callback.context(context);
return this;
}
};
};

36
src/postal.basic.js Normal file
View file

@ -0,0 +1,36 @@
/*jshint -W098 */
(function ( root, factory ) {
if ( typeof module === "object" && module.exports ) {
// Node, or CommonJS-Like environments
module.exports = factory( require( "underscore" ), this );
} else if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define( ["underscore"], function ( _ ) {
return factory( _, root );
} );
} else {
// Browser globals
root.postal = factory( root._, root );
}
}( this, function ( _, global, undefined ) {
var postal;
var prevPostal = global.postal;
//import("strategy.js");
//import("ChannelDefinition.js");
//import("SubscriptionDefinition.js");
//import("AmqpBindingsResolver.js");
//import("LocalBus.js");
//import("Api.js");
/*jshint -W106 */
if ( global && Object.prototype.hasOwnProperty.call( global, "__postalReady__" ) && _.isArray( global.__postalReady__ ) ) {
while(global.__postalReady__.length) {
global.__postalReady__.shift().onReady(postal);
}
}
/*jshint +W106 */
return postal;
} ));

View file

@ -17,12 +17,10 @@
var postal;
var prevPostal = global.postal;
//import("SubscriptionDefinition.js");
//import("strategy.js");
//import("strategies.js");
//import("ConsecutiveDistinctPredicate.js");
//import("DistinctPredicate.js");
//import("ChannelDefinition.js");
//import("SubscriptionDefinition.js");
//import("AmqpBindingsResolver.js");
//import("LocalBus.js");
//import("Api.js");

24
src/postal.strategies.js Normal file
View file

@ -0,0 +1,24 @@
/*jshint -W098 */
(function ( root, factory ) {
if ( typeof module === "object" && module.exports ) {
// Node, or CommonJS-Like environments
module.exports = function(postal) {
return factory( postal, this );
};
} else if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define( ["postal"], function ( postal ) {
return factory( postal, root );
} );
} else {
// Browser globals
root.postal = factory( root.postal, root );
}
}( this, function ( postal, global, undefined ) {
(function(SubscriptionDefinition) {
//import("strategies.js");
}(postal.SubscriptionDefinition));
return postal;
} ));

View file

@ -1,8 +1,44 @@
/* global DistinctPredicate,ConsecutiveDistinctPredicate */
/* global DistinctPredicate,ConsecutiveDistinctPredicate,SubscriptionDefinition,postal */
/*jshint -W098 */
var ConsecutiveDistinctPredicate = function () {
var previous;
return function ( data ) {
var eq = false;
if ( _.isString( data ) ) {
eq = data === previous;
previous = data;
}
else {
eq = _.isEqual( data, previous );
previous = _.clone( data );
}
return !eq;
};
};
var DistinctPredicate = function () {
var previous = [];
return function ( data ) {
var isDistinct = !_.any( previous, function ( p ) {
if ( _.isObject( data ) || _.isArray( data ) ) {
return _.isEqual( data, p );
}
return data === p;
} );
if ( isDistinct ) {
previous.push( data );
}
return isDistinct;
};
};
var strats = {
setTimeout: function(ms) {
withDelay: function(ms) {
if ( _.isNaN( ms ) ) {
throw "Milliseconds must be a number";
}
return {
name: "setTimeout",
name: "withDelay",
fn: function (next, data, envelope) {
setTimeout(function () {
next(data, envelope);
@ -10,25 +46,37 @@ var strats = {
}
};
},
after: function(maxCalls, callback) {
defer: function() {
return this.withDelay(0);
},
stopAfter: function(maxCalls, callback) {
if ( _.isNaN( maxCalls ) || maxCalls <= 0 ) {
throw "The value provided to disposeAfter (maxCalls) must be a number greater than zero.";
}
var dispose = _.after(maxCalls, callback);
return {
name: "after",
name: "stopAfter",
fn: function (next, data, envelope) {
dispose();
next(data, envelope);
}
};
},
throttle : function(ms) {
withThrottle : function(ms) {
if ( _.isNaN( ms ) ) {
throw "Milliseconds must be a number";
}
return {
name: "throttle",
name: "withThrottle",
fn: _.throttle(function(next, data, envelope) {
next(data, envelope);
}, ms)
};
},
debounce: function(ms, immediate) {
withDebounce: function(ms, immediate) {
if ( _.isNaN( ms ) ) {
throw "Milliseconds must be a number";
}
return {
name: "debounce",
fn: _.debounce(function(next, data, envelope) {
@ -36,9 +84,12 @@ var strats = {
}, ms, !!immediate)
};
},
predicate: function(pred) {
withConstraint: function(pred) {
if ( !_.isFunction( pred ) ) {
throw "Predicate constraint must be a function";
}
return {
name: "predicate",
name: "withConstraint",
fn: function(next, data, envelope) {
if(pred.call(this, data, envelope)) {
next.call(this, data, envelope);
@ -63,4 +114,52 @@ var strats = {
}
};
}
};
SubscriptionDefinition.prototype.defer = function () {
this.callback.useStrategy(strats.defer());
return this;
};
SubscriptionDefinition.prototype.disposeAfter = function ( maxCalls ) {
var self = this;
self.callback.useStrategy(strats.stopAfter(maxCalls, function() {
self.unsubscribe.call(self);
}));
return self;
};
SubscriptionDefinition.prototype.distinctUntilChanged = function () {
this.callback.useStrategy(strats.distinct());
return this;
};
SubscriptionDefinition.prototype.distinct = function () {
this.callback.useStrategy(strats.distinct({ all : true }));
return this;
};
SubscriptionDefinition.prototype.once = function () {
this.disposeAfter(1);
return this;
};
SubscriptionDefinition.prototype.withConstraint = function ( predicate ) {
this.callback.useStrategy(strats.withConstraint(predicate));
return this;
};
SubscriptionDefinition.prototype.withDebounce = function ( milliseconds, immediate ) {
this.callback.useStrategy(strats.withDebounce(milliseconds, immediate));
return this;
};
SubscriptionDefinition.prototype.withDelay = function ( milliseconds ) {
this.callback.useStrategy(strats.withDelay(milliseconds));
return this;
};
SubscriptionDefinition.prototype.withThrottle = function ( milliseconds ) {
this.callback.useStrategy(strats.withThrottle(milliseconds));
return this;
};