Removed a *lot* of code. Postal's diet is in full force...

This commit is contained in:
Jim Cowart 2012-12-20 02:32:45 -05:00
parent 8530716096
commit 3acbb142e6
95 changed files with 448 additions and 19226 deletions

View file

@ -1,6 +1,6 @@
{
"name": "postal.js",
"version": "0.7.3",
"version": "0.8.0",
"main": ["lib/standard/postal.min.js", "lib/standard/postal.js"],
"dependencies": {
"underscore": "~1.3.0"

View file

@ -1,13 +1,13 @@
define( ['postal', 'postaldiags'], function ( postal, diags ) {
// The world's simplest subscription
var channel = postal.channel( "Name.Changed" );
// This gets you a handle to the default postal channel...
var channel = postal.channel();
// subscribe
var subscription = channel.subscribe( function ( data ) {
var subscription = channel.subscribe( "Name.Changed", function ( data ) {
$( "#example1" ).html( "Name: " + data.name );
} );
// And someone publishes a first name change:
channel.publish( { name : "Dr. Who" } );
channel.publish( "Name.Changed", { name : "Dr. Who" } );
subscription.unsubscribe();
@ -15,14 +15,11 @@ define( ['postal', 'postaldiags'], function ( postal, diags ) {
// The # symbol represents "one word" in a topic (i.e - the text between two periods of a topic).
// By subscribing to "#.Changed", the binding will match
// Name.Changed & Location.Changed but *not* for Changed.Companion
var hashChannel = postal.channel( "#.Changed" ),
chgSubscription = hashChannel.subscribe( function ( data ) {
$( '<li>' + data.type + " Changed: " + data.value + '</li>' ).appendTo( "#example2" );
} );
postal.channel( "Name.Changed" )
.publish( { type : "Name", value : "John Smith" } );
postal.channel( "Location.Changed" )
.publish( { type : "Location", value : "Early 20th Century England" } );
var chgSubscription = channel.subscribe( "#.Changed", function ( data ) {
$( '<li>' + data.type + " Changed: " + data.value + '</li>' ).appendTo( "#example2" );
} );
channel.publish( "Name.Changed", { type : "Name", value : "John Smith" } );
channel.publish( "Location.Changed", { type : "Location", value : "Early 20th Century England" } );
chgSubscription.unsubscribe();
@ -30,120 +27,85 @@ define( ['postal', 'postaldiags'], function ( postal, diags ) {
// The * symbol represents any number of characters/words in a topic string.
// By subscribing to "DrWho.*.Changed", the binding will match
// DrWho.NinthDoctor.Companion.Changed & DrWho.Location.Changed but *not* Changed
var starChannel = postal.channel( "DrWho.*.Changed" ),
starSubscription = starChannel.subscribe( function ( data ) {
$( '<li>' + data.type + " Changed: " + data.value + '</li>' ).appendTo( "#example3" );
} );
postal.channel( "DrWho.NinthDoctor.Companion.Changed" )
.publish( { type : "Companion Name", value : "Rose" } );
postal.channel( "DrWho.TenthDoctor.Companion.Changed" )
.publish( { type : "Companion Name", value : "Martha" } );
postal.channel( "DrWho.Eleventh.Companion.Changed" )
.publish( { type : "Companion Name", value : "Amy" } );
postal.channel( "DrWho.Location.Changed" )
.publish( { type : "Location", value : "The Library" } );
postal.channel( "TheMaster.DrumBeat.Changed" )
.publish( { type : "DrumBeat", value : "This won't trigger any subscriptions" } );
postal.channel( "Changed" )
.publish( { type : "Useless", value : "This won't trigger any subscriptions either" } );
var starSubscription = channel.subscribe( "DrWho.*.Changed", function ( data ) {
$( '<li>' + data.type + " Changed: " + data.value + '</li>' ).appendTo( "#example3" );
} );
channel.publish( "DrWho.NinthDoctor.Companion.Changed", { type : "Companion Name", value : "Rose" } );
channel.publish( "DrWho.TenthDoctor.Companion.Changed", { type : "Companion Name", value : "Martha" } );
channel.publish( "DrWho.Eleventh.Companion.Changed", { type : "Companion Name", value : "Amy" } );
channel.publish( "DrWho.Location.Changed", { type : "Location", value : "The Library" } );
channel.publish( "TheMaster.DrumBeat.Changed", { type : "DrumBeat", value : "This won't trigger any subscriptions" } );
channel.publish( "Changed", { type : "Useless", value : "This won't trigger any subscriptions either" } );
starSubscription.unsubscribe();
// Applying distinctUntilChanged to a subscription
var dupChannel = postal.channel( "WeepingAngel.*" ),
dupSubscription = dupChannel.subscribe(
function ( data ) {
$( '<li>' + data.value + '</li>' ).appendTo( "#example4" );
} ).distinctUntilChanged();
postal.channel( "WeepingAngel.DontBlink" )
.publish( { value : "Don't Blink" } );
postal.channel( "WeepingAngel.DontBlink" )
.publish( { value : "Don't Blink" } );
postal.channel( "WeepingAngel.DontEvenBlink" )
.publish( { value : "Don't Even Blink" } );
postal.channel( "WeepingAngel.DontBlink" )
.publish( { value : "Don't Close Your Eyes" } );
postal.channel( "WeepingAngel.DontBlink" )
.publish( { value : "Don't Blink" } );
postal.channel( "WeepingAngel.DontBlink" )
.publish( { value : "Don't Blink" } );
var dupSubscription = channel.subscribe( "WeepingAngel.*",
function ( data ) {
$( '<li>' + data.value + '</li>' ).appendTo( "#example4" );
} ).distinctUntilChanged();
channel.publish( "WeepingAngel.DontBlink", { value : "Don't Blink" } );
channel.publish( "WeepingAngel.DontBlink", { value : "Don't Blink" } );
channel.publish( "WeepingAngel.DontEvenBlink", { value : "Don't Even Blink" } );
channel.publish( "WeepingAngel.DontBlink", { value : "Don't Close Your Eyes" } );
channel.publish( "WeepingAngel.DontBlink", { value : "Don't Blink" } );
channel.publish( "WeepingAngel.DontBlink", { value : "Don't Blink" } );
dupSubscription.unsubscribe();
// Using disposeAfter(X) to remove subscription automagically after X number of receives
var daChannel = postal.channel( "Donna.Noble.*" ),
daSubscription = daChannel.subscribe(
function ( data ) {
$( '<li>' + data.value + '</li>' ).appendTo( "#example5" );
} ).disposeAfter( 2 );
postal.channel( "Donna.Noble.ScreamingAgain" )
.publish( { value : "Donna Noble has left the library." } );
postal.channel( "Donna.Noble.ScreamingAgain" )
.publish( { value : "Donna Noble has left the library." } );
postal.channel( "Donna.Noble.ScreamingAgain" )
.publish( { value : "Donna Noble has left the library." } );
postal.channel( "Donna.Noble.ScreamingAgain" )
.publish( { value : "Donna Noble has left the library." } );
postal.channel( "Donna.Noble.ScreamingAgain" )
.publish( { value : "Donna Noble has left the library." } );
var daSubscription = channel.subscribe( "Donna.Noble.*",
function ( data ) {
$( '<li>' + data.value + '</li>' ).appendTo( "#example5" );
} ).disposeAfter( 2 );
channel.publish( "Donna.Noble.ScreamingAgain", { value : "Donna Noble has left the library." } );
channel.publish( "Donna.Noble.ScreamingAgain", { value : "Donna Noble has left the library." } );
channel.publish( "Donna.Noble.ScreamingAgain", { value : "Donna Noble has left the library." } );
channel.publish( "Donna.Noble.ScreamingAgain", { value : "Donna Noble has left the library." } );
channel.publish( "Donna.Noble.ScreamingAgain", { value : "Donna Noble has left the library." } );
daSubscription.unsubscribe();
// Using withConstraint to apply a predicate to the subscription
var drIsInTheTardis = false,
wcChannel = postal.channel( "Tardis.Depart" ),
wcSubscription = wcChannel.subscribe(
wcSubscription = channel.subscribe( "Tardis.Depart",
function ( data ) {
$( '<li>' + data.value + '</li>' ).appendTo( "#example6" );
} ).withConstraint( function () {
return drIsInTheTardis;
} );
postal.channel( "Tardis.Depart" )
.publish( { value : "Time for time travel....fantastic!" } );
postal.channel( "Tardis.Depart" )
.publish( { value : "Time for time travel....fantastic!" } );
channel.publish( "Tardis.Depart", { value : "Time for time travel....fantastic!" } );
channel.publish( "Tardis.Depart", { value : "Time for time travel....fantastic!" } );
drIsInTheTardis = true;
postal.channel( "Tardis.Depart" )
.publish( { value : "Time for time travel....fantastic!" } );
channel.publish( "Tardis.Depart", { value : "Time for time travel....fantastic!" } );
wcSubscription.unsubscribe();
// Using withContext to set the "this" context
var ctxChannel = postal.channel( "Dalek.Meet.CyberMen" ),
ctxSubscription = ctxChannel.subscribe(
function ( data ) {
$( '<li>' + data.value + '</li>' ).appendTo( this );
} ).withContext( $( "#example7" ) );
postal.channel( "Dalek.Meet.CyberMen" )
.publish( { value : "Exterminate!" } );
postal.channel( "Dalek.Meet.CyberMen" )
.publish( { value : "Delete!" } );
var ctxSubscription = channel.subscribe( "Dalek.Meet.CyberMen",
function ( data ) {
$( '<li>' + data.value + '</li>' ).appendTo( this );
} ).withContext( $( "#example7" ) );
channel.publish( "Dalek.Meet.CyberMen", { value : "Exterminate!" } );
channel.publish( "Dalek.Meet.CyberMen", { value : "Delete!" } );
ctxSubscription.unsubscribe();
// Using withDelay() to delay the subscription evaluation
var wdChannel = postal.channel( "He.Will.Knock.Four.Times" ),
wdSubscription = wdChannel.subscribe(
function ( data ) {
$( '<li>' + data.value + '</li>' ).appendTo( $( "#example8" ) );
} ).withDelay( 5000 );
postal.channel( "He.Will.Knock.Four.Times" )
.publish( { value : "Knock!" } );
postal.channel( "He.Will.Knock.Four.Times" )
.publish( { value : "Knock!" } );
postal.channel( "He.Will.Knock.Four.Times" )
.publish( { value : "Knock!" } );
postal.channel( "He.Will.Knock.Four.Times" )
.publish( { value : "Knock!" } );
var wdSubscription = channel.subscribe( "He.Will.Knock.Four.Times",
function ( data ) {
$( '<li>' + data.value + '</li>' ).appendTo( $( "#example8" ) );
} ).withDelay( 5000 );
channel.publish( "He.Will.Knock.Four.Times", { value : "Knock!" } );
channel.publish( "He.Will.Knock.Four.Times", { value : "Knock!" } );
channel.publish( "He.Will.Knock.Four.Times", { value : "Knock!" } );
channel.publish( "He.Will.Knock.Four.Times", { value : "Knock!" } );
wdSubscription.unsubscribe();
// Using distinct() to ignore duplicate messages
var revealChannel = postal.channel('detect.cylon'),
revealSubscription = revealChannel.subscribe(function (who) {
$('<li></li>').text(who.name).appendTo($('#example9'));
}).distinct();
postal.channel('detect.cylon').publish({name: 'Boomer'});
postal.channel('detect.cylon').publish({name: 'Saul Tigh'});
postal.channel('detect.cylon').publish({name: 'William Adama'});
postal.channel('detect.cylon').publish({name: 'Helo'});
//ignored!
postal.channel('detect.cylon').publish({name: 'Boomer'});
postal.channel('detect.cylon').publish({name: 'Felix Gaeta'});
//ignored!
postal.channel('detect.cylon').publish({name: 'William Adama'});
var revealSubscription = channel.subscribe( 'detect.cylon',function ( who ) {
$( '<li></li>' ).text( who.name ).appendTo( $( '#example9' ) );
} ).distinct();
channel.publish( 'detect.cylon', {name : 'Boomer'} );
channel.publish( 'detect.cylon', {name : 'Saul Tigh'} );
channel.publish( 'detect.cylon', {name : 'William Adama'} );
channel.publish( 'detect.cylon', {name : 'Helo'} );
channel.publish( 'detect.cylon', {name : 'Boomer'} ); //ignored!
channel.publish( 'detect.cylon', {name : 'Felix Gaeta'} );
channel.publish( 'detect.cylon', {name : 'William Adama'} );//ignored!
} );

View file

@ -54,44 +54,22 @@
return isDistinct;
};
};
var ChannelDefinition = function ( channelName, defaultTopic ) {
var ChannelDefinition = function ( channelName ) {
this.channel = channelName || DEFAULT_CHANNEL;
this._topic = defaultTopic || "";
};
ChannelDefinition.prototype = {
subscribe : function () {
var len = arguments.length;
if ( len === 1 ) {
return new SubscriptionDefinition( this.channel, this._topic, arguments[0] );
}
else if ( len === 2 ) {
return new SubscriptionDefinition( this.channel, arguments[0], arguments[1] );
}
return arguments.length === 1 ?
new SubscriptionDefinition( this.channel, arguments[0].topic, arguments[0].callback ) :
new SubscriptionDefinition( this.channel, arguments[0], arguments[1] );
},
publish : function ( obj ) {
var _obj = obj || {};
var envelope = {
channel : this.channel,
topic : this._topic,
data : _obj
};
// If this is an envelope....
if ( _obj.topic && _obj.data ) {
envelope = _obj;
envelope.channel = envelope.channel || this.channel;
}
envelope.timeStamp = new Date();
publish : function () {
var envelope = arguments.length === 1 ? arguments[0] : { topic: arguments[0], data: arguments[1] };
envelope.channel = this.channel;
postal.configuration.bus.publish( envelope );
return envelope;
},
topic : function ( topic ) {
if ( topic === this._topic ) {
return this;
}
return new ChannelDefinition( this.channel, topic );
}
};
@ -302,6 +280,7 @@
},
publish : function ( envelope ) {
envelope.timeStamp = new Date();
_.each( this.wireTaps, function ( tap ) {
tap( envelope.data, envelope );
} );
@ -371,55 +350,7 @@
}
};
var publishPicker = {
"1" : function ( envelope ) {
if ( !envelope ) {
throw new Error( "publishing from the 'global' postal.publish call requires a valid envelope." );
}
envelope.channel = envelope.channel || DEFAULT_CHANNEL;
envelope.timeStamp = new Date();
postal.configuration.bus.publish( envelope );
return envelope;
},
"2" : function ( topic, data ) {
var envelope = { channel : DEFAULT_CHANNEL, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
},
"3" : function ( channel, topic, data ) {
var envelope = { channel : channel, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
}
},
channelPicker = {
"1" : function ( chn ) {
var channel = chn, topic, options = {};
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
}
else {
channel = chn.channel || DEFAULT_CHANNEL;
topic = chn.topic;
options = chn.options || options;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"2" : function ( chn, tpc ) {
var channel = chn, topic = tpc, options = {};
if ( Object.prototype.toString.call( tpc ) === "[object Object]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
options = tpc;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"3" : function ( channel, topic, options ) {
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
}
},
sessionInfo = {};
var sessionInfo = {};
// save some setup time, albeit tiny
localBus.subscriptions[SYSTEM_CHANNEL] = {};
@ -434,29 +365,22 @@
SYSTEM_CHANNEL : SYSTEM_CHANNEL
},
channelTypes : {
local : ChannelDefinition
},
ChannelDefinition : ChannelDefinition,
channel : function () {
var len = arguments.length;
if ( channelPicker[len] ) {
return channelPicker[len].apply( this, arguments );
}
SubscriptionDefinition: SubscriptionDefinition,
channel : function ( channelName ) {
return new ChannelDefinition( channelName );
},
subscribe : function ( options ) {
var callback = options.callback,
topic = options.topic,
channel = options.channel || DEFAULT_CHANNEL;
return new SubscriptionDefinition( channel, topic, callback );
return new SubscriptionDefinition( options.channel || DEFAULT_CHANNEL, options.topic, options.callback );
},
publish : function () {
var len = arguments.length;
if ( publishPicker[len] ) {
return publishPicker[len].apply( this, arguments );
}
publish : function ( envelope ) {
envelope.channel = envelope.channel || DEFAULT_CHANNEL;
postal.configuration.bus.publish( envelope );
return envelope;
},
addWireTap : function ( callback ) {

File diff suppressed because one or more lines are too long

View file

@ -1,9 +1,9 @@
require.config( {
paths : {
underscore : 'libs/underscore/underscore-min',
postal : 'libs/postal/postal',
underscore : 'libs/underscore/underscore-min',
postal : 'libs/postal/postal',
postaldiags : 'libs/postal/postal.diagnostics',
jquery : 'libs/jquery/jquery-min'
jquery : 'libs/jquery/jquery-min'
}
} );

View file

@ -1,49 +0,0 @@
# postal.js sample app with browser and node.js components
## Explanation
As much as I want to convince you that this sample app was written with you in mind, I'd be lying if I said so. The browser examples in this repo are currently "bare bones", with no real usage examples beyond calling the API in browser js and then calling it a day. I wanted to demonstrate node.js usage of postal as well, and that gave birth to this example project.
## How to run
### Install depedencies first!
Open a terminal/console to the example/node directory and run `npm install`.
### Now, to run...
Open a terminal/console to the example/node directory and run `npm start`. Then open a browser to http://localhost:8002. Once you enter a search term in the client UI, it will trigger the server side twitter search module to start searching, etc.
## The Good, the Bad & the Caveats
###The Good:
* You'll see postal.js used in node.js and in the browser (#winning?)
* This is way beyond a hello world example
* The browser and node.js postal instances are being *bridged* by a websocket connection (geek brownie points, FTW)
* I demonstrate a general usage pattern (message endpoint) I've fallen into with the node.js side of things, which I describe as "Local message bus at boundaries, events internal."
* Note that the node/messaging/ folder contains "bus-adapter.js" and "collector-adapter.js"
* These modules exist to adapt other bus-agnostics modules to postal.js
* The "bus agnostic" modules (for ex. - anything in the "collectors" directory) can work just fine without a message bus in place, but any subscribers would have to have a direct reference to the module, causing the application to be more tightly coupled.
* The adapters wrap the standard "events" that a module can produce and act as a bridge to then publish those events onto the message bus. This enables other clients to subscribe to said events without needing to know anything explicit about the event producer beyond the structure of the message (the message *is* the contract).
###The Bad:
* I have (had?) high hopes to refactor this into better organized code, but alas, I have a wife, two kids, friends, other OSS projects and about 200 books on my amazon wish list. So, while the code is readable, it's more verbose than it should be.
* *IF* I get to refactor anything, I would target things like:
* Less verbose app-level FSM on the node.js side
* Tie postal.js into the Backbone.Sync process so that models could transparently get updates from the server w/o having to know about subscriptions for updates
* More comments - especially around the bus-adapter/collector adpaters in the node.js code
###The Caveats:
* The web socket bridge in use in this project (postal.socket) is *highly experimental*! A real version is in the works, but this project served as a test bed for ideas I had floating around in my brain.
* The actual functionality of this example app is truly silly and arbitrary:
* clients connect and the first one to enter a search term owns the search until they disconnect or give control to another client
* search terms are used against twitter's "REST-ish" API (not the stream)
* as the server-side search agent gets tweet results, it publishes them local to node.js for the stat collectors to aggregate and produce stats
* stat collectors publish messages to which browser clients have subscribed (browser clients have a "socket" channel type thanks to the experimental postal.socket plugin that subscribes not only locally, but pushes the subscription to the other end of the socket on the server side, causing messages on the server for that susbcription to be pushed over the socket, back to the client.)
* If a client that doesn't own the search submits a search request, the owner can go to the "Search Requests" link on the top navbar and click on the term they want to allow. This gives control to the requester.
* Beyond simple playing in the browser and repl, I've not performed any major testing of this app - good grief, man, it's a sample app I've written while mostly half asleep :-)
More real-world sample applications using postal will be included in this repository as I build ones I'm at liberty to share.
Thanks a ton for reading this if you made it this far. *You* are exactly why I write this stuff!

View file

@ -1,51 +0,0 @@
body {
font-family: Tahoma, Arial;
font-size: 9pt;
}
fieldset {
width: 285px;
float: left;
}
.images {
height: 48px;
width: 48px;
border: .5pt solid black;
}
.scrollableDiv {
overflow-y: auto;
overflow-x: hidden;
height: 570px;
}
.top-bar {
position: relative;
left: 0px;
top: 0px;
margin-bottom: 25px;
}
.nav-menu {
float: left;
margin: 0px;
margin-right: 30px;
}
.nav-menu li {
display: inline-block;
margin-right: 10px;
}
.search {
float: left;
}
.search input[type='text'] {
margin-left: 5px;
}
#stats {
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<base href="/">
<title>Twitter Hash Tag Stats Demo</title>
<link rel="stylesheet" type="text/css" href="css/style.css">
<script src="/socket.io/socket.io.js"></script>
<script data-main="js/main" src="js/lib/require-jquery.js"></script>
</head>
<body>
</body>
</html>

View file

@ -1,55 +0,0 @@
define( [
'jquery',
'backbone',
'bus',
'infrastructure/router',
'infrastructure/view-manager',
'views/container',
'views/menu',
'views/tweet-count',
'views/mention-count',
'views/mentioner-count',
'views/hash-tag-count',
'views/profanity-percentage',
'views/search-requests',
'views/wiretap',
], function ( $, Backbone, bus, Router, ViewManager, ContainerView, MenuView, TweetCountView,
MentionCountView, MentionerCountView, HashTagCountView, ProfanityPercentage,
SearchRequestView, WiretapView ) {
var app = {
bus : bus,
router : new Router()
};
// Set up UI concerns
app.viewManager = new ViewManager();
app.bus.viewManager.subscribe( "ui.show", function ( data, env ) {
app.viewManager.UI[ data.name ].activate( data.context );
} );
app.viewManager.registerViews( [
{ name : "container", ctor : ContainerView },
{ name : "menu", ctor : MenuView },
{ name : "tweetCount", ctor : TweetCountView },
{ name : "mentionCount", ctor : MentionCountView },
{ name : "mentionerCount", ctor : MentionerCountView },
{ name : "hashTagCount", ctor : HashTagCountView },
{ name : "profanityPercentage", ctor : ProfanityPercentage },
{ name : "searchRequests", ctor : SearchRequestView },
{ name : "wiretap", ctor : WiretapView }
] );
app.viewManager.defineUIs( [
{ name : "homeUI", dependencies : [ "container", "menu", "tweetCount", "mentionCount", "mentionerCount", "hashTagCount", "profanityPercentage" ] },
{ name : "searchRequestUI", dependencies : [ "container", "menu", "searchRequests" ] },
{ name : "wireTapLogUI", dependencies : [ "container", "menu", "wiretap" ] }
] );
$( function () {
Backbone.history.start( {
pushState : true,
root : $( "base" ).attr( "href" )
} );
} );
return app;
} );

View file

@ -1,11 +0,0 @@
define( [
'postal'
], function ( postal ) {
return {
router : postal.channel( "router", "*" ),
viewManager : postal.channel( "viewmanager", "*" ),
data : postal.channel( "data", "*" ),
app : postal.channel( "statsApp", "*", { type : "websocket" } ),
stats : postal.channel( "stats", "*", { type : "websocket" } )
}
} );

View file

@ -1,298 +0,0 @@
/*
postal.socket
Author: Jim Cowart
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.1.0
*/
(function ( root, doc, factory ) {
if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define( [ "underscore", "machina", "postal" ], function ( _, machina, postal ) {
return factory( _, machina, postal, root, doc );
} );
} else {
// Browser globals
factory( root._, root.machina, root.postal, root, doc );
}
}( this, document, function ( _, machina, postal, global, document, undefined ) {
/*
adding a socket namespace to postal
which provides the following members:
1.) config - provides values used to manage the socket connection
2.) goOffline() - tells the manager to close the connection intentionally
3.) goOnline() - tells the manager to try to connect.
4.) manifest - an array of objects describing the subscriptions that have been
set up on the remote end.
5.) publish() - takes a valid postal envelope and pushes it through the socket
to be published to the server instance of postal.
6.) socketMgr - the FSM managing the socket connection
7.) socketNamespace - exposed currently for debugging only
8.) subscribe() - passes an object through the socket to the server
which contains data necessary to set up a remote subscription. The
options passed to the socket would look similar to this:
{
"channel":"SomeChannel",
"topic":"my.topic",
"correlationId":"2073383865318591267"
}
The "correlationId" is used on the remote side to apply a constraint to
the subscription, enabling just this specific client to be targeted on
and otherwise public channel.
9.) unsubscribe() - passes an object through the socket to the server
which contains data necessary to remove a remote subscription. The options
passed would look similar to the example above in #8.
*/
postal.connections = postal.connections || {};
var postalSocket = postal.connections.socket = (function () {
var socketNamespace,
fsm = new machina.Fsm( {
retryFn : undefined,
session : undefined,
wireUpSocketEvents : function () {
var self = this;
_.each( [ "connect", "connecting", "connect_failed", "disconnect", "reconnect", "reconnect_failed",
"reconnecting", "postal.socket.remote", "postal.socket.identified", "postal.socket.migration" ],
function ( evnt ) {
socketNamespace.on( evnt, function ( data ) {
self.handle( evnt, data );
} );
} );
},
states : {
uninitialized : {
tryConnect : function () {
this.transition( "initializing" );
}
},
initializing : {
_onEnter : function () {
socketNamespace = io.connect( postalSocket.config.url, { "auto connect" : false } );
this.wireUpSocketEvents();
this.transition( "probing" )
},
socketTransmit : function () {
this.deferUntilTransition( "online" );
}
},
probing : {
_onEnter : function () {
clearTimeout( this.retryFn );
if ( !socketNamespace.socket.connecting && !socketNamespace.socket.reconnecting ) {
socketNamespace.socket.connect();
}
else {
this.transition( "settingSessionInfo" );
}
},
connect : function () {
this.transition( "settingSessionInfo" );
},
connect_failed : function () {
this.transition( "disconnected" );
},
maxAttempts : function () {
this.transition( "offline" );
},
"postal.socket.remote" : function () {
this.deferUntilTransition( "online" );
},
reconnect : function () {
this.transition( "settingSessionInfo" );
},
reconnect_failed : function () {
this.transition( "disconnected" );
},
socketTransmit : function () {
this.deferUntilTransition( "online" );
}
},
settingSessionInfo : {
_onEnter : function () {
var self = this;
postal.utils.setSessionId( socketNamespace.socket.sessionid, function ( session ) {
self.session = session;
self.transition( "identifying" );
} );
}
},
identifying : {
_onEnter : function () {
var self = this;
self.retryFn = setTimeout( function () {
self.handle( "timeout.identifying" );
}, postalSocket.config.reconnectInterval );
self.handle( "client.identifier" );
},
"client.identifier" : function () {
clearTimeout( this.retryFn );
socketNamespace.emit( "postal.clientId", { id : this.session.id, lastId : this.session.lastId } );
},
connect_failed : function () {
this.transition( "disconnected" );
},
disconnect : function () {
this.transition( "probing" );
},
"postal.socket.identified" : function ( data ) {
this.transition( "online" );
},
"postal.socket.migration" : function () {
_.each( postal.connections.socket.manifest, function ( sub ) {
fsm.handle( "socketTransmit", "postal.subscribe", sub );
} );
socketNamespace.emit( "postal.migrationComplete", {} );
},
"postal.socket.remote" : function () {
this.deferUntilTransition( "online" );
},
reconnect_failed : function () {
this.transition( "disconnected" );
},
socketTransmit : function ( evntName, envelope ) {
if ( evntName === "postal.subscribe" ) {
// we risk mutating the message here, so extend
// and add the correlationId to the extended copy
var socketEnv = _.extend( {}, envelope );
socketEnv.correlationId = this.session.id;
socketNamespace.emit( evntName, socketEnv );
}
else {
this.deferUntilTransition( "online" );
}
},
"timeout.identifying" : function () {
this.transition( "probing" );
}
},
online : {
disconnect : function () {
this.transition( "probing" );
},
goOffline : function () {
this.transition( "offline" );
},
"postal.socket.remote" : function ( envelope ) {
postal.publish( envelope );
},
socketTransmit : function ( evntName, envelope ) {
// we risk mutating the message here, so extend
// and add the correlationId to the extended copy
var socketEnv = _.extend( {}, envelope );
socketEnv.correlationId = this.session.id;
socketNamespace.emit( evntName, socketEnv );
}
},
offline : {
_onEnter : function () {
socketNamespace.socket.disconnect();
},
socketTransmit : function () {
this.deferUntilTransition( "online" );
},
"tryConnect" : function () {
this.transition( "probing" );
}
},
disconnected : {
_onEnter : function () {
var self = this;
self.retryFn = setTimeout( function () {
self.transition( "probing" );
}, postalSocket.config.reconnectInterval );
},
connecting : function () {
this.transition( "probing" );
},
reconnecting : function () {
this.transition( "probing" );
},
socketTransmit : function () {
this.deferUntilTransition( "online" );
}
}
}
} );
postal.subscribe( {
channel : "postal",
topic : "sessionId.changed",
callback : function () {
fsm.handle( "postal.session.changed" );
}
} );
return {
config : {
url : window.location.origin,
reconnectInterval : 4000
},
goOffline : function () {
fsm.handle( "goOffline" );
},
goOnline : function () {
fsm.handle( "tryConnect" );
},
manifest : [],
publish : function ( envelope ) {
fsm.handle( "socketTransmit", "postal.publish", envelope );
},
subscribe : function ( options ) {
options.channel = options.channel || postal.configuration.DEFAULT_CHANNEL;
options.topic = options.topic || "*";
if ( !_.any( this.manifest, function ( item ) {
return item.channel === options.channel && item.topic === options.topic;
} ) ) {
this.manifest.push( options );
fsm.handle( "socketTransmit", "postal.subscribe", options );
}
},
socketMgr : fsm,
socketNamespace : socketNamespace,
unsubscribe : function ( options ) {
options.channel = options.channel || postal.configuration.DEFAULT_CHANNEL;
options.topic = options.topic || "*";
if ( !postal.getSubscribersFor( options.channel, options.topic ).length ) {
fsm.handle( "socketTransmit", "postal.unsubscribe", options );
}
}
}
})();
postal.connections.socket.goOnline();
var SocketChannel = postal.channelTypes.websocket = function ( channelName, defaultTopic ) {
var channel = postal.channel( channelName, defaultTopic ),
localSubscribe = channel.subscribe,
localPublish = channel.publish,
localTopic = channel.topic;
channel.publish = function () {
postalSocket.publish( localPublish.apply( channel, arguments ) );
};
channel.subscribe = function () {
var sub = localSubscribe.apply( channel, arguments ),
origUnsubscribe;
origUnsubscribe = sub.unsubscribe;
sub.unsubscribe = function () {
origUnsubscribe.call( sub );
postalSocket.unsubscribe( { channel : sub.channel, topic : sub.topic } );
};
postalSocket.subscribe( { channel : sub.channel, topic : sub.topic } );
return sub;
};
channel.topic = function ( topic ) {
if ( topic === channel._topic ) {
return this;
}
return new SocketChannel( this.channel, topic );
};
return channel;
};
} ));

View file

@ -1,53 +0,0 @@
define( [
'jquery',
'backbone',
'bus'
], function ( $, Backbone, bus ) {
return Backbone.Router.extend( {
routes : {
"" : "home",
"requests" : "requests",
"wiretap" : "wiretap",
"*anything" : "redirect"
},
initialize : function () {
var self = this;
_.bindAll( self );
$( document ).delegate( "a.ps-nav", "click", function ( e ) {
e.preventDefault();
self.navigate( $( this ).attr( 'href' ), { trigger : true } );
} );
bus.router.publish( "initialized" );
},
activateUI : function ( uiName, context ) {
bus.viewManager.publish( {
topic : "ui.show",
data : {
name : uiName,
context : context
}
} );
},
home : function () {
this.activateUI( "homeUI" );
},
requests : function () {
this.activateUI( "searchRequestUI" );
},
wiretap : function () {
this.activateUI( "wireTapLogUI" );
},
redirect : function () {
this.navigate( "/", { trigger : true } );
}
} );
} );

View file

@ -1,121 +0,0 @@
define( [
'underscore',
'bus'
], function ( _, bus ) {
var ViewManager = function () {
this.views = {}; // holds the views that are registered with the manager
this.UI = {}; // holds the UI configurations that are defined
this.priorContext = undefined; // holds the name of the last UI configuration
};
//----------------------------------------------------------------------------
// registerView - registers a view with the manager, under the name provided.
// The handle to the constructor function (second arg) is used to create an
// instance of the view the first time getInstance is called. The rendered
// and visible booleans are used by the ViewManager to track state of the view.
// The getInstance call can take an options object. If options.forceNew = true,
// then the ViewManager will create a new instance of the view. If options.args
// exists, it will be passed into the constructor function of the view.
//----------------------------------------------------------------------------
ViewManager.prototype.registerView = function ( name, viewCtor ) {
this.views[name] = {
rendered : false,
visible : false,
getInstance : (function () {
var _instance;
return function ( options ) {
var _options = options || {};
if ( !_instance || _options.forceNew ) {
_instance = new viewCtor( _options.args || {} );
}
return _instance;
}
})()
}
};
ViewManager.prototype.registerViews = function ( views ) {
_.each( views, function ( view ) {
this.registerView( view.name, view.ctor );
}, this );
};
//----------------------------------------------------------------------------
// defineUI - defines a UI configuration, which is effectively a named group
// of views that need to be stood up, in order. The first argument is the UI
// name, second arg is the array of view names (in the order they need to be
// instantiated/rendered/shown)
//----------------------------------------------------------------------------
ViewManager.prototype.defineUI = function ( name, dependencies, options ) {
var self = this;
self.UI[ name ] = {
options : options || {},
dependencies : dependencies,
activate : function ( data ) {
data = data || {};
data.priorContext = self.priorContext;
data.targetContext = name;
if ( !this.options.noHide ) {
// hide anything visible that's not in the dependencies for this UI configuration
var shouldHide = _.reduce( self.views, function ( memo, val, key ) {
if ( val.visible && !_.include( this.dependencies, key ) ) {
memo.push( key );
}
return memo;
}, [], this );
_.each( shouldHide, function ( viewName ) {
var instance = self.views[ viewName ].getInstance();
if ( instance.hide ) {
instance.hide();
}
self.views[ viewName ].visible = false;
} );
}
// set up, render & show the dependencies for this UI configuration
_.each( this.dependencies, function ( viewName ) {
var instance = self.views[viewName].getInstance( data );
if ( !self.views[viewName].rendered ) {
instance.render( data );
self.views[viewName].rendered = true;
}
if ( !self.views[viewName].visible ) {
if ( instance.show ) {
instance.show( data );
}
self.views[viewName].visible = true;
}
if ( instance.update ) {
instance.update( data );
}
} );
self.priorContext = name;
}
};
};
ViewManager.prototype.defineUIs = function ( uis ) {
_.each( uis, function ( ui ) {
this.defineUI( ui.name, ui.dependencies, ui.options );
}, this );
};
ViewManager.prototype.addViewToUI = function ( uiName, viewName, viewCtor ) {
var uis = _.isArray( uiName ) ? uiName : [ uiName ];
if ( !this.views[ viewName ] ) {
this.registerView( viewName, viewCtor );
}
_.each( uis, function ( ui ) {
if ( this.UI[ ui ] ) {
this.UI[ ui ].dependencies.push( viewName );
}
}, this );
};
return ViewManager;
} );

View file

@ -1,360 +0,0 @@
/*!
* AmplifyJS 1.0.0 - Core, Store, Request
*
* Copyright 2011 appendTo LLC. (http://appendto.com/team)
* Dual licensed under the MIT or GPL licenses.
* http://appendto.com/open-source-licenses
*
* http://amplifyjs.com
*/
(function ( a, b, c ) {
typeof define == "function" && define.amd ? define( ["jquery"], function ( d ) {
return c( d, a, b )
} ) : c( a.jQuery, a, b )
})( this, document, function ( a, b, c, d ) {
return function ( a, b ) {
var c = [].slice, d = {}, e = a.amplify = {publish : function ( a ) {
var b = c.call( arguments, 1 ), e, f, g, h = 0, i;
if ( !d[a] ) {
return!0;
}
e = d[a].slice();
for ( g = e.length; h < g; h++ ) {
f = e[h], i = f.callback.apply( f.context, b );
if ( i === !1 ) {
break
}
}
return i !== !1
}, subscribe : function ( a, b, c, e ) {
arguments.length === 3 && typeof c == "number" && (e = c, c = b, b = null), arguments.length === 2 && (c = b, b = null), e = e || 10;
var f = 0, g = a.split( /\s/ ), h = g.length, i;
for ( ; f < h; f++ ) {
a = g[f], i = !1, d[a] || (d[a] = []);
var j = d[a].length - 1, k = {callback : c, context : b, priority : e};
for ( ; j >= 0; j-- ) {
if ( d[a][j].priority <= e ) {
d[a].splice( j + 1, 0, k ), i = !0;
break
}
}
i || d[a].unshift( k )
}
return c
}, unsubscribe : function ( a, b ) {
if ( !d[a] ) {
return;
}
var c = d[a].length, e = 0;
for ( ; e < c; e++ ) {
if ( d[a][e].callback === b ) {
d[a].splice( e, 1 );
break
}
}
}}
}( this ), function ( a, b ) {
function f( a, c ) {
d.addType( a, function ( f, g, h ) {
var i, j, k, l, m = g, n = (new Date).getTime();
if ( !f ) {
m = {}, l = [], k = 0;
try {
f = c.length;
while ( f = c.key( k++ ) ) {
e.test( f ) && (j = JSON.parse( c.getItem( f ) ), j.expires && j.expires <= n ? l.push( f ) : m[f.replace( e, "" )] = j.data);
}
while ( f = l.pop() ) {
c.removeItem( f )
}
} catch ( o ) {
}
return m
}
f = "__amplify__" + f;
if ( g === b ) {
i = c.getItem( f ), j = i ? JSON.parse( i ) : {expires : -1};
if ( !(j.expires && j.expires <= n) ) {
return j.data;
}
c.removeItem( f )
} else if ( g === null ) {
c.removeItem( f );
} else {
j = JSON.stringify( {data : g, expires : h.expires ? n + h.expires : null} );
try {
c.setItem( f, j )
} catch ( o ) {
d[a]();
try {
c.setItem( f, j )
} catch ( o ) {
throw d.error()
}
}
}
return m
} )
}
var d = a.store = function ( a, b, c, e ) {
var e = d.type;
return c && c.type && c.type in d.types && (e = c.type), d.types[e]( a, b, c || {} )
};
d.types = {}, d.type = null, d.addType = function ( a, b ) {
d.type || (d.type = a), d.types[a] = b, d[a] = function ( b, c, e ) {
return e = e || {}, e.type = a, d( b, c, e )
}
}, d.error = function () {
return"amplify.store quota exceeded"
};
var e = /^__amplify__/;
for ( var g in{localStorage : 1, sessionStorage : 1} ) {
try {
window[g].getItem && f( g, window[g] )
} catch ( h ) {
}
}
if ( !d.types.localStorage && window.globalStorage ) {
try {
f( "globalStorage", window.globalStorage[window.location.hostname] ), d.type === "sessionStorage" && (d.type = "globalStorage")
} catch ( h ) {
}
}
(function () {
if ( d.types.localStorage ) {
return;
}
var a = c.createElement( "div" ), e = "amplify";
a.style.display = "none", c.getElementsByTagName( "head" )[0].appendChild( a );
try {
a.addBehavior( "#default#userdata" ), a.load( e )
} catch ( f ) {
a.parentNode.removeChild( a );
return
}
d.addType( "userData", function ( c, f, g ) {
a.load( e );
var h, i, j, k, l, m = f, n = (new Date).getTime();
if ( !c ) {
m = {}, l = [], k = 0;
while ( h = a.XMLDocument.documentElement.attributes[k++] ) {
i = JSON.parse( h.value ), i.expires && i.expires <= n ? l.push( h.name ) : m[h.name] = i.data;
}
while ( c = l.pop() ) {
a.removeAttribute( c );
}
return a.save( e ), m
}
c = c.replace( /[^-._0-9A-Za-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u37f-\u1fff\u200c-\u200d\u203f\u2040\u2070-\u218f]/g, "-" ), c = c.replace( /^-/, "_-" );
if ( f === b ) {
h = a.getAttribute( c ), i = h ? JSON.parse( h ) : {expires : -1};
if ( !(i.expires && i.expires <= n) ) {
return i.data;
}
a.removeAttribute( c )
} else {
f === null ? a.removeAttribute( c ) : (j = a.getAttribute( c ), i = JSON.stringify( {data : f, expires : g.expires ? n + g.expires : null} ), a.setAttribute( c, i ));
}
try {
a.save( e )
} catch ( o ) {
j === null ? a.removeAttribute( c ) : a.setAttribute( c, j ), d.userData();
try {
a.setAttribute( c, i ), a.save( e )
} catch ( o ) {
throw j === null ? a.removeAttribute( c ) : a.setAttribute( c, j ), d.error()
}
}
return m
} )
})(), function () {
function e( a ) {
return a === b ? b : JSON.parse( JSON.stringify( a ) )
}
var a = {}, c = {};
d.addType( "memory", function ( d, f, g ) {
return d ? f === b ? e( a[d] ) : (c[d] && (clearTimeout( c[d] ), delete c[d]), f === null ? (delete a[d], null) : (a[d] = f, g.expires && (c[d] = setTimeout( function () {
delete a[d], delete c[d]
}, g.expires )), f)) : e( a )
} )
}()
}( this.amplify = this.amplify || {} ), function ( a, b ) {
function c() {
}
function d( a ) {
return{}.toString.call( a ) === "[object Function]"
}
function e( a ) {
var b = !1;
return setTimeout( function () {
b = !0
}, 1 ), function () {
var c = this, d = arguments;
b ? a.apply( c, d ) : setTimeout( function () {
a.apply( c, d )
}, 1 )
}
}
a.request = function ( b, f, g ) {
var h = b || {};
typeof h == "string" && (d( f ) && (g = f, f = {}), h = {resourceId : b, data : f || {}, success : g});
var i = {abort : c}, j = a.request.resources[h.resourceId], k = h.success || c, l = h.error || c;
h.success = e( function ( b, c ) {
c = c || "success", a.publish( "request.success", h, b, c ), a.publish( "request.complete", h, b, c ), k( b, c )
} ), h.error = e( function ( b, c ) {
c = c || "error", a.publish( "request.error", h, b, c ), a.publish( "request.complete", h, b, c ), l( b, c )
} );
if ( !j ) {
throw h.resourceId ? "amplify.request: unknown resourceId: " + h.resourceId : "amplify.request: no resourceId provided";
}
if ( !a.publish( "request.before", h ) ) {
h.error( null, "abort" );
return
}
return a.request.resources[h.resourceId]( h, i ), i
}, a.request.types = {}, a.request.resources = {}, a.request.define = function ( b, c, d ) {
if ( typeof c == "string" ) {
if ( !(c in a.request.types) ) {
throw"amplify.request.define: unknown type: " + c;
}
d.resourceId = b, a.request.resources[b] = a.request.types[c]( d )
} else {
a.request.resources[b] = c
}
}
}( amplify ), function ( a, b, c ) {
var d = ["status", "statusText", "responseText", "responseXML", "readyState"], e = /\{([^\}]+)\}/g;
a.request.types.ajax = function ( e ) {
return e = b.extend( {type : "GET"}, e ), function ( f, g ) {
function n( a, e ) {
b.each( d, function ( a, b ) {
try {
m[b] = h[b]
} catch ( c ) {
}
} ), /OK$/.test( m.statusText ) && (m.statusText = "success"), a === c && (a = null), l && (e = "abort"), /timeout|error|abort/.test( e ) ? m.error( a, e ) : m.success( a, e ), n = b.noop
}
var h, i = e.url, j = g.abort, k = b.extend( !0, {}, e, {data : f.data} ), l = !1, m = {readyState : 0, setRequestHeader : function ( a, b ) {
return h.setRequestHeader( a, b )
}, getAllResponseHeaders : function () {
return h.getAllResponseHeaders()
}, getResponseHeader : function ( a ) {
return h.getResponseHeader( a )
}, overrideMimeType : function ( a ) {
return h.overrideMideType( a )
}, abort : function () {
l = !0;
try {
h.abort()
} catch ( a ) {
}
n( null, "abort" )
}, success : function ( a, b ) {
f.success( a, b )
}, error : function ( a, b ) {
f.error( a, b )
}};
a.publish( "request.ajax.preprocess", e, f, k, m ), b.extend( k, {success : function ( a, b ) {
n( a, b )
}, error : function ( a, b ) {
n( null, b )
}, beforeSend : function ( b, c ) {
h = b, k = c;
var d = e.beforeSend ? e.beforeSend.call( this, m, k ) : !0;
return d && a.publish( "request.before.ajax", e, f, k, m )
}} ), b.ajax( k ), g.abort = function () {
m.abort(), j.call( this )
}
}
}, a.subscribe( "request.ajax.preprocess", function ( a, c, d ) {
var f = [], g = d.data;
if ( typeof g == "string" ) {
return;
}
g = b.extend( !0, {}, a.data, g ), d.url = d.url.replace( e, function ( a, b ) {
if ( b in g ) {
return f.push( b ), g[b]
}
} ), b.each( f, function ( a, b ) {
delete g[b]
} ), d.data = g
} ), a.subscribe( "request.ajax.preprocess", function ( a, c, d ) {
var e = d.data, f = a.dataMap;
if ( !f || typeof e == "string" ) {
return;
}
b.isFunction( f ) ? d.data = f( e ) : (b.each( a.dataMap, function ( a, b ) {
a in e && (e[b] = e[a], delete e[a])
} ), d.data = e)
} );
var f = a.request.cache = {_key : function ( a, b, c ) {
function g() {
return c.charCodeAt( e++ ) << 24 | c.charCodeAt( e++ ) << 16 | c.charCodeAt( e++ ) << 8 | c.charCodeAt( e++ ) << 0
}
c = b + c;
var d = c.length, e = 0, f = g();
while ( e < d ) {
f ^= g();
}
return"request-" + a + "-" + f
}, _default : function () {
var a = {};
return function ( b, c, d, e ) {
var g = f._key( c.resourceId, d.url, d.data ), h = b.cache;
if ( g in a ) {
return e.success( a[g] ), !1;
}
var i = e.success;
e.success = function ( b ) {
a[g] = b, typeof h == "number" && setTimeout( function () {
delete a[g]
}, h ), i.apply( this, arguments )
}
}
}()};
a.store && (b.each( a.store.types, function ( b ) {
f[b] = function ( c, d, e, g ) {
var h = f._key( d.resourceId, e.url, e.data ), i = a.store[b]( h );
if ( i ) {
return e.success( i ), !1;
}
var j = g.success;
g.success = function ( d ) {
a.store[b]( h, d, {expires : c.cache.expires} ), j.apply( this, arguments )
}
}
} ), f.persist = f[a.store.type]), a.subscribe( "request.before.ajax", function ( a ) {
var b = a.cache;
if ( b ) {
return b = b.type || b, f[b in f ? b : "_default"].apply( this, arguments )
}
} ), a.request.decoders = {jsend : function ( a, b, c, d, e ) {
a.status === "success" ? d( a.data ) : a.status === "fail" ? e( a.data, "fail" ) : a.status === "error" && (delete a.status, e( a, "error" ))
}}, a.subscribe( "request.before.ajax", function ( c, d, e, f ) {
function j( a, b ) {
g( a, b )
}
function k( a, b ) {
h( a, b )
}
var g = f.success, h = f.error, i = b.isFunction( c.decoder ) ? c.decoder : c.decoder in a.request.decoders ? a.request.decoders[c.decoder] : a.request.decoders._default;
if ( !i ) {
return;
}
f.success = function ( a, b ) {
i( a, b, f, j, k )
}, f.error = function ( a, b ) {
i( a, b, f, j, k )
}
} )
}( amplify, jQuery ), amplify
} )

View file

@ -1,710 +0,0 @@
// Backbone.js 0.9.2
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
(function ( h, g ) {
typeof exports !== "undefined" ? g( h, exports, require( "underscore" ) ) : typeof define === "function" && define.amd ? define( ["underscore", "jquery", "exports"], function ( f, i, p ) {
h.Backbone = g( h, p, f, i )
} ) : h.Backbone = g( h, {}, h._, h.jQuery || h.Zepto || h.ender )
})( this, function ( h, g, f, i ) {
var p = h.Backbone, y = Array.prototype.slice, z = Array.prototype.splice;
g.VERSION = "0.9.2";
g.setDomLibrary = function ( a ) {
i = a
};
g.noConflict = function () {
h.Backbone = p;
return g
};
g.emulateHTTP = false;
g.emulateJSON = false;
var q = /\s+/, l = g.Events =
{on : function ( a, b, c ) {
var d, e, f, g, j;
if ( !b ) {
return this;
}
a = a.split( q );
for ( d = this._callbacks || (this._callbacks = {}); e = a.shift(); ) {
f = (j = d[e]) ? j.tail : {}, f.next = g = {}, f.context = c, f.callback = b, d[e] = {tail : g, next : j ? j.next : f};
}
return this
}, off : function ( a, b, c ) {
var d, e, k, g, j, h;
if ( e = this._callbacks ) {
if ( !a && !b && !c ) {
return delete this._callbacks, this;
}
for ( a = a ? a.split( q ) : f.keys( e ); d = a.shift(); ) {
if ( k = e[d], delete e[d], k && (b || c) ) {
for ( g = k.tail; (k = k.next) !== g; ) {
if ( j = k.callback, h = k.context, b && j !== b || c && h !== c ) {
this.on( d, j, h );
}
}
}
}
return this
}
},
trigger : function ( a ) {
var b, c, d, e, f, g;
if ( !(d = this._callbacks) ) {
return this;
}
f = d.all;
a = a.split( q );
for ( g = y.call( arguments, 1 ); b = a.shift(); ) {
if ( c = d[b] ) {
for ( e = c.tail; (c = c.next) !== e; ) {
c.callback.apply( c.context || this, g );
}
}
if ( c = f ) {
e = c.tail;
for ( b = [b].concat( g ); (c = c.next) !== e; ) {
c.callback.apply( c.context || this, b )
}
}
}
return this
}};
l.bind = l.on;
l.unbind = l.off;
var o = g.Model = function ( a, b ) {
var c;
a || (a = {});
b && b.parse && (a = this.parse( a ));
if ( c = n( this, "defaults" ) ) {
a = f.extend( {}, c, a );
}
if ( b && b.collection ) {
this.collection = b.collection;
}
this.attributes = {};
this._escapedAttributes = {};
this.cid = f.uniqueId( "c" );
this.changed = {};
this._silent = {};
this._pending = {};
this.set( a, {silent : true} );
this.changed = {};
this._silent = {};
this._pending = {};
this._previousAttributes = f.clone( this.attributes );
this.initialize.apply( this, arguments )
};
f.extend( o.prototype, l, {changed : null, _silent : null, _pending : null, idAttribute : "id", initialize : function () {
}, toJSON : function () {
return f.clone( this.attributes )
}, get : function ( a ) {
return this.attributes[a]
}, escape : function ( a ) {
var b;
if ( b = this._escapedAttributes[a] ) {
return b;
}
b = this.get( a );
return this._escapedAttributes[a] = f.escape( b == null ? "" : "" + b )
}, has : function ( a ) {
return this.get( a ) != null
}, set : function ( a, b, c ) {
var d, e;
f.isObject( a ) || a == null ? (d = a, c = b) : (d = {}, d[a] = b);
c || (c = {});
if ( !d ) {
return this;
}
if ( d instanceof o ) {
d = d.attributes;
}
if ( c.unset ) {
for ( e in d ) {
d[e] = void 0;
}
}
if ( !this._validate( d, c ) ) {
return false;
}
if ( this.idAttribute in d ) {
this.id = d[this.idAttribute];
}
var b = c.changes = {}, g = this.attributes, h = this._escapedAttributes, j = this._previousAttributes ||
{};
for ( e in d ) {
a = d[e];
if ( !f.isEqual( g[e], a ) || c.unset && f.has( g, e ) ) {
delete h[e], (c.silent ? this._silent : b)[e] = true;
}
c.unset ? delete g[e] : g[e] = a;
!f.isEqual( j[e], a ) || f.has( g, e ) != f.has( j, e ) ? (this.changed[e] = a, c.silent || (this._pending[e] = true)) : (delete this.changed[e], delete this._pending[e])
}
c.silent || this.change( c );
return this
}, unset : function ( a, b ) {
(b || (b = {})).unset = true;
return this.set( a, null, b )
}, clear : function ( a ) {
(a || (a = {})).unset = true;
return this.set( f.clone( this.attributes ), a )
}, fetch : function ( a ) {
var a =
a ? f.clone( a ) : {}, b = this, c = a.success;
a.success = function ( d, e, f ) {
if ( !b.set( b.parse( d, f ), a ) ) {
return false;
}
c && c( b, d )
};
a.error = g.wrapError( a.error, b, a );
return(this.sync || g.sync).call( this, "read", this, a )
}, save : function ( a, b, c ) {
var d, e;
f.isObject( a ) || a == null ? (d = a, c = b) : (d = {}, d[a] = b);
c = c ? f.clone( c ) : {};
if ( c.wait ) {
if ( !this._validate( d, c ) ) {
return false;
}
e = f.clone( this.attributes )
}
a = f.extend( {}, c, {silent : true} );
if ( d && !this.set( d, c.wait ? a : c ) ) {
return false;
}
var k = this, h = c.success;
c.success = function ( a, b, e ) {
b = k.parse( a, e );
c.wait && (delete c.wait, b = f.extend( d || {}, b ));
if ( !k.set( b, c ) ) {
return false;
}
h ? h( k, a ) : k.trigger( "sync", k, a, c )
};
c.error = g.wrapError( c.error, k, c );
b = this.isNew() ? "create" : "update";
b = (this.sync || g.sync).call( this, b, this, c );
c.wait && this.set( e, a );
return b
}, destroy : function ( a ) {
var a = a ? f.clone( a ) : {}, b = this, c = a.success, d = function () {
b.trigger( "destroy", b, b.collection, a )
};
if ( this.isNew() ) {
return d(), false;
}
a.success = function ( e ) {
a.wait && d();
c ? c( b, e ) : b.trigger( "sync", b, e, a )
};
a.error = g.wrapError( a.error, b, a );
var e = (this.sync ||
g.sync).call( this, "delete", this, a );
a.wait || d();
return e
}, url : function () {
var a = n( this, "urlRoot" ) || n( this.collection, "url" ) || t();
return this.isNew() ? a : a + (a.charAt( a.length - 1 ) == "/" ? "" : "/") + encodeURIComponent( this.id )
}, parse : function ( a ) {
return a
}, clone : function () {
return new this.constructor( this.attributes )
}, isNew : function () {
return this.id == null
}, change : function ( a ) {
a || (a = {});
var b = this._changing;
this._changing = true;
for ( var c in this._silent ) {
this._pending[c] = true;
}
var d = f.extend( {}, a.changes, this._silent );
this._silent = {};
for ( c in d ) {
this.trigger( "change:" + c, this, this.get( c ), a );
}
if ( b ) {
return this;
}
for ( ; !f.isEmpty( this._pending ); ) {
this._pending = {};
this.trigger( "change", this, a );
for ( c in this.changed ) {
!this._pending[c] && !this._silent[c] && delete this.changed[c];
}
this._previousAttributes = f.clone( this.attributes )
}
this._changing = false;
return this
}, hasChanged : function ( a ) {
return!arguments.length ? !f.isEmpty( this.changed ) : f.has( this.changed, a )
}, changedAttributes : function ( a ) {
if ( !a ) {
return this.hasChanged() ? f.clone( this.changed ) :
false;
}
var b, c = false, d = this._previousAttributes, e;
for ( e in a ) {
if ( !f.isEqual( d[e], b = a[e] ) ) {
(c || (c = {}))[e] = b;
}
}
return c
}, previous : function ( a ) {
return!arguments.length || !this._previousAttributes ? null : this._previousAttributes[a]
}, previousAttributes : function () {
return f.clone( this._previousAttributes )
}, isValid : function () {
return!this.validate( this.attributes )
}, _validate : function ( a, b ) {
if ( b.silent || !this.validate ) {
return true;
}
var a = f.extend( {}, this.attributes, a ), c = this.validate( a, b );
if ( !c ) {
return true;
}
b && b.error ?
b.error( this, c, b ) : this.trigger( "error", this, c, b );
return false
}} );
var r = g.Collection = function ( a, b ) {
b || (b = {});
if ( b.model ) {
this.model = b.model;
}
if ( b.comparator ) {
this.comparator = b.comparator;
}
this._reset();
this.initialize.apply( this, arguments );
a && this.reset( a, {silent : true, parse : b.parse} )
};
f.extend( r.prototype, l, {model : o, initialize : function () {
}, toJSON : function ( a ) {
return this.map( function ( b ) {
return b.toJSON( a )
} )
}, add : function ( a, b ) {
var c, d, e, g, h, j = {}, i = {}, l = [];
b || (b = {});
a = f.isArray( a ) ? a.slice() : [a];
for ( c = 0, d =
a.length; c < d; c++ ) {
if ( !(e = a[c] = this._prepareModel( a[c], b )) ) {
throw Error( "Can't add an invalid model to a collection" );
}
g = e.cid;
h = e.id;
j[g] || this._byCid[g] || h != null && (i[h] || this._byId[h]) ? l.push( c ) : j[g] = i[h] = e
}
for ( c = l.length; c--; ) {
a.splice( l[c], 1 );
}
for ( c = 0, d = a.length; c < d; c++ ) {
(e = a[c]).on( "all", this._onModelEvent, this ), this._byCid[e.cid] = e, e.id != null && (this._byId[e.id] = e);
}
this.length += d;
z.apply( this.models, [b.at != null ? b.at : this.models.length, 0].concat( a ) );
this.comparator && this.sort( {silent : true} );
if ( b.silent ) {
return this;
}
for ( c = 0, d = this.models.length; c < d; c++ ) {
if ( j[(e = this.models[c]).cid] ) {
b.index = c, e.trigger( "add", e, this, b );
}
}
return this
}, remove : function ( a, b ) {
var c, d, e, g;
b || (b = {});
a = f.isArray( a ) ? a.slice() : [a];
for ( c = 0, d = a.length; c < d; c++ ) {
if ( g = this.getByCid( a[c] ) || this.get( a[c] ) ) {
delete this._byId[g.id];
delete this._byCid[g.cid];
e = this.indexOf( g );
this.models.splice( e, 1 );
this.length--;
if ( !b.silent ) {
b.index = e, g.trigger( "remove", g, this, b );
}
this._removeReference( g )
}
}
return this
}, push : function ( a, b ) {
a = this._prepareModel( a, b );
this.add( a,
b );
return a
}, pop : function ( a ) {
var b = this.at( this.length - 1 );
this.remove( b, a );
return b
}, unshift : function ( a, b ) {
a = this._prepareModel( a, b );
this.add( a, f.extend( {at : 0}, b ) );
return a
}, shift : function ( a ) {
var b = this.at( 0 );
this.remove( b, a );
return b
}, get : function ( a ) {
return a == null ? void 0 : this._byId[a.id != null ? a.id : a]
}, getByCid : function ( a ) {
return a && this._byCid[a.cid || a]
}, at : function ( a ) {
return this.models[a]
}, where : function ( a ) {
return f.isEmpty( a ) ? [] : this.filter( function ( b ) {
for ( var c in a ) {
if ( a[c] !== b.get( c ) ) {
return false;
}
}
return true
} )
}, sort : function ( a ) {
a || (a = {});
if ( !this.comparator ) {
throw Error( "Cannot sort a set without a comparator" );
}
var b = f.bind( this.comparator, this );
this.comparator.length == 1 ? this.models = this.sortBy( b ) : this.models.sort( b );
a.silent || this.trigger( "reset", this, a );
return this
}, pluck : function ( a ) {
return f.map( this.models, function ( b ) {
return b.get( a )
} )
}, reset : function ( a, b ) {
a || (a = []);
b || (b = {});
for ( var c = 0, d = this.models.length; c < d; c++ ) {
this._removeReference( this.models[c] );
}
this._reset();
this.add( a, f.extend( {silent : true},
b ) );
b.silent || this.trigger( "reset", this, b );
return this
}, fetch : function ( a ) {
a = a ? f.clone( a ) : {};
if ( a.parse === void 0 ) {
a.parse = true;
}
var b = this, c = a.success;
a.success = function ( d, e, f ) {
b[a.add ? "add" : "reset"]( b.parse( d, f ), a );
c && c( b, d )
};
a.error = g.wrapError( a.error, b, a );
return(this.sync || g.sync).call( this, "read", this, a )
}, create : function ( a, b ) {
var c = this, b = b ? f.clone( b ) : {}, a = this._prepareModel( a, b );
if ( !a ) {
return false;
}
b.wait || c.add( a, b );
var d = b.success;
b.success = function ( e, f ) {
b.wait && c.add( e, b );
d ? d( e, f ) : e.trigger( "sync",
a, f, b )
};
a.save( null, b );
return a
}, parse : function ( a ) {
return a
}, chain : function () {
return f( this.models ).chain()
}, _reset : function () {
this.length = 0;
this.models = [];
this._byId = {};
this._byCid = {}
}, _prepareModel : function ( a, b ) {
b || (b = {});
if ( a instanceof o ) {
if ( !a.collection ) {
a.collection = this
}
} else {
var c;
b.collection = this;
a = new this.model( a, b );
a._validate( a.attributes, b ) || (a = false)
}
return a
}, _removeReference : function ( a ) {
this == a.collection && delete a.collection;
a.off( "all", this._onModelEvent, this )
}, _onModelEvent : function ( a, b, c, d ) {
(a == "add" || a == "remove") && c != this || (a == "destroy" && this.remove( b, d ), b && a === "change:" + b.idAttribute && (delete this._byId[b.previous( b.idAttribute )], this._byId[b.id] = b), this.trigger.apply( this, arguments ))
}} );
f.each( "forEach,each,map,reduce,reduceRight,find,detect,filter,select,reject,every,all,some,any,include,contains,invoke,max,min,sortBy,sortedIndex,toArray,size,first,initial,rest,last,without,indexOf,shuffle,lastIndexOf,isEmpty,groupBy".split( "," ), function ( a ) {
r.prototype[a] = function () {
return f[a].apply( f,
[this.models].concat( f.toArray( arguments ) ) )
}
} );
var u = g.Router = function ( a ) {
a || (a = {});
if ( a.routes ) {
this.routes = a.routes;
}
this._bindRoutes();
this.initialize.apply( this, arguments )
}, A = /:\w+/g, B = /\*\w+/g, C = /[-[\]{}()+?.,\\^$|#\s]/g;
f.extend( u.prototype, l, {initialize : function () {
}, route : function ( a, b, c ) {
g.history || (g.history = new m);
f.isRegExp( a ) || (a = this._routeToRegExp( a ));
c || (c = this[b]);
g.history.route( a, f.bind( function ( d ) {
d = this._extractParameters( a, d );
c && c.apply( this, d );
this.trigger.apply( this, ["route:" +
b].concat( d ) );
g.history.trigger( "route", this, b, d )
}, this ) );
return this
}, navigate : function ( a, b ) {
g.history.navigate( a, b )
}, _bindRoutes : function () {
if ( this.routes ) {
var a = [], b;
for ( b in this.routes ) {
a.unshift( [b, this.routes[b]] );
}
b = 0;
for ( var c = a.length; b < c; b++ ) {
this.route( a[b][0], a[b][1], this[a[b][1]] )
}
}
}, _routeToRegExp : function ( a ) {
a = a.replace( C, "\\$&" ).replace( A, "([^/]+)" ).replace( B, "(.*?)" );
return RegExp( "^" + a + "$" )
}, _extractParameters : function ( a, b ) {
return a.exec( b ).slice( 1 )
}} );
var m = g.History = function () {
this.handlers =
[];
f.bindAll( this, "checkUrl" )
}, s = /^[#\/]/, D = /msie [\w.]+/;
m.started = false;
f.extend( m.prototype, l, {interval : 50, getHash : function ( a ) {
return(a = (a ? a.location : window.location).href.match( /#(.*)$/ )) ? a[1] : ""
}, getFragment : function ( a, b ) {
if ( a == null ) {
if ( this._hasPushState || b ) {
var a = window.location.pathname, c = window.location.search;
c && (a += c)
} else {
a = this.getHash();
}
}
a.indexOf( this.options.root ) || (a = a.substr( this.options.root.length ));
return a.replace( s, "" )
}, start : function ( a ) {
if ( m.started ) {
throw Error( "Backbone.history has already been started" );
}
m.started = true;
this.options = f.extend( {}, {root : "/"}, this.options, a );
this._wantsHashChange = this.options.hashChange !== false;
this._wantsPushState = !!this.options.pushState;
this._hasPushState = !(!this.options.pushState || !window.history || !window.history.pushState);
var a = this.getFragment(), b = document.documentMode;
if ( b = D.exec( navigator.userAgent.toLowerCase() ) && (!b || b <= 7) ) {
this.iframe = i( '<iframe src="javascript:0" tabindex="-1" />' ).hide().appendTo( "body" )[0].contentWindow, this.navigate( a );
}
if ( this._hasPushState ) {
i( window ).bind( "popstate",
this.checkUrl );
} else if ( this._wantsHashChange && "onhashchange"in window && !b ) {
i( window ).bind( "hashchange", this.checkUrl );
} else if ( this._wantsHashChange ) {
this._checkUrlInterval = setInterval( this.checkUrl, this.interval );
}
this.fragment = a;
a = window.location;
b = a.pathname == this.options.root;
if ( this._wantsHashChange && this._wantsPushState && !this._hasPushState && !b ) {
return this.fragment = this.getFragment( null, true ), window.location.replace( this.options.root + "#" + this.fragment ), true;
} else if ( this._wantsPushState && this._hasPushState &&
b && a.hash ) {
this.fragment = this.getHash().replace( s, "" ), window.history.replaceState( {}, document.title, a.protocol + "//" + a.host + this.options.root + this.fragment );
}
if ( !this.options.silent ) {
return this.loadUrl()
}
}, stop : function () {
i( window ).unbind( "popstate", this.checkUrl ).unbind( "hashchange", this.checkUrl );
clearInterval( this._checkUrlInterval );
m.started = false
}, route : function ( a, b ) {
this.handlers.unshift( {route : a, callback : b} )
}, checkUrl : function () {
var a = this.getFragment();
a == this.fragment && this.iframe && (a = this.getFragment( this.getHash( this.iframe ) ));
if ( a == this.fragment ) {
return false;
}
this.iframe && this.navigate( a );
this.loadUrl() || this.loadUrl( this.getHash() )
}, loadUrl : function ( a ) {
var b = this.fragment = this.getFragment( a );
return f.any( this.handlers, function ( a ) {
if ( a.route.test( b ) ) {
return a.callback( b ), true
}
} )
}, navigate : function ( a, b ) {
if ( !m.started ) {
return false;
}
if ( !b || b === true ) {
b = {trigger : b};
}
var c = (a || "").replace( s, "" );
if ( this.fragment != c ) {
this._hasPushState ? (c.indexOf( this.options.root ) != 0 && (c = this.options.root + c), this.fragment = c, window.history[b.replace ?
"replaceState" : "pushState"]( {}, document.title, c )) : this._wantsHashChange ? (this.fragment = c, this._updateHash( window.location, c, b.replace ), this.iframe && c != this.getFragment( this.getHash( this.iframe ) ) && (b.replace || this.iframe.document.open().close(), this._updateHash( this.iframe.location, c, b.replace ))) : window.location.assign( this.options.root + a ), b.trigger && this.loadUrl( a )
}
}, _updateHash : function ( a, b, c ) {
c ? a.replace( a.toString().replace( /(javascript:|#).*$/, "" ) + "#" + b ) : a.hash = b
}} );
var v = g.View = function ( a ) {
this.cid =
f.uniqueId( "view" );
this._configure( a || {} );
this._ensureElement();
this.initialize.apply( this, arguments );
this.delegateEvents()
}, E = /^(\S+)\s*(.*)$/, w = "model,collection,el,id,attributes,className,tagName".split( "," );
f.extend( v.prototype, l, {tagName : "div", $ : function ( a ) {
return this.$el.find( a )
}, initialize : function () {
}, render : function () {
return this
}, remove : function () {
this.$el.remove();
return this
}, make : function ( a, b, c ) {
a = document.createElement( a );
b && i( a ).attr( b );
c != null && i( a ).html( c );
return a
}, setElement : function ( a, b ) {
this.$el && this.undelegateEvents();
this.$el = a instanceof i ? a : i( a );
this.el = this.$el[0];
b !== false && this.delegateEvents();
return this
}, delegateEvents : function ( a ) {
if ( a || (a = n( this, "events" )) ) {
this.undelegateEvents();
for ( var b in a ) {
var c = a[b];
f.isFunction( c ) || (c = this[a[b]]);
if ( !c ) {
throw Error( 'Method "' + a[b] + '" does not exist' );
}
var d = b.match( E ), e = d[1], d = d[2], c = f.bind( c, this );
e += ".delegateEvents" + this.cid;
d === "" ? this.$el.bind( e, c ) : this.$el.delegate( d, e, c )
}
}
}, undelegateEvents : function () {
this.$el.unbind( ".delegateEvents" +
this.cid )
}, _configure : function ( a ) {
this.options && (a = f.extend( {}, this.options, a ));
for ( var b = 0, c = w.length; b < c; b++ ) {
var d = w[b];
a[d] && (this[d] = a[d])
}
this.options = a
}, _ensureElement : function () {
if ( this.el ) {
this.setElement( this.el, false );
} else {
var a = n( this, "attributes" ) || {};
if ( this.id ) {
a.id = this.id;
}
if ( this.className ) {
a["class"] = this.className;
}
this.setElement( this.make( this.tagName, a ), false )
}
}} );
o.extend = r.extend = u.extend = v.extend = function ( a, b ) {
var c = F( this, a, b );
c.extend = this.extend;
return c
};
var G = {create : "POST",
update : "PUT", "delete" : "DELETE", read : "GET"};
g.sync = function ( a, b, c ) {
var d = G[a];
c || (c = {});
var e = {type : d, dataType : "json"};
if ( !c.url ) {
e.url = n( b, "url" ) || t();
}
if ( !c.data && b && (a == "create" || a == "update") ) {
e.contentType = "application/json", e.data = JSON.stringify( b.toJSON() );
}
if ( g.emulateJSON ) {
e.contentType = "application/x-www-form-urlencoded", e.data = e.data ? {model : e.data} : {};
}
if ( g.emulateHTTP && (d === "PUT" || d === "DELETE") ) {
if ( g.emulateJSON ) {
e.data._method = d;
}
e.type = "POST";
e.beforeSend = function ( a ) {
a.setRequestHeader( "X-HTTP-Method-Override",
d )
}
}
if ( e.type !== "GET" && !g.emulateJSON ) {
e.processData = false;
}
return i.ajax( f.extend( e, c ) )
};
g.wrapError = function ( a, b, c ) {
return function ( d, e ) {
e = d === b ? e : d;
a ? a( b, e, c ) : b.trigger( "error", b, e, c )
}
};
var x = function () {
}, F = function ( a, b, c ) {
var d;
d = b && b.hasOwnProperty( "constructor" ) ? b.constructor : function () {
a.apply( this, arguments )
};
f.extend( d, a );
x.prototype = a.prototype;
d.prototype = new x;
b && f.extend( d.prototype, b );
c && f.extend( d, c );
d.prototype.constructor = d;
d.__super__ = a.prototype;
return d
}, n = function ( a, b ) {
return!a ||
!a[b] ? null : f.isFunction( a[b] ) ? a[b]() : a[b]
}, t = function () {
throw Error( 'A "url" property or function must be specified' );
};
return g
} );

View file

@ -1,212 +0,0 @@
/*
machina.js
Author: Jim Cowart
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.1.0
*/
(function ( root, doc, factory ) {
if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define( ["underscore"], function ( _ ) {
return factory( _, root, doc );
} );
} else {
// Browser globals
factory( root._, root, doc );
}
}( this, document, function ( _, global, document, undefined ) {
var slice = [].slice,
NEXT_TRANSITION = "transition",
NEXT_HANDLER = "handler",
transformEventListToObject = function ( eventList ) {
var obj = {};
_.each( eventList, function ( evntName ) {
obj[evntName] = [];
} );
return obj;
},
parseEventListeners = function ( evnts ) {
var obj = evnts;
if ( _.isArray( evnts ) ) {
obj = transformEventListToObject( evnts );
}
return obj;
};
var utils = {
makeFsmNamespace : (function () {
var machinaCount = 0;
return function () {
return "fsm." + machinaCount++;
};
})(),
getDefaultOptions : function () {
return {
initialState : "uninitialized",
eventListeners : {
"*" : []
},
states : {},
eventQueue : [],
namespace : utils.makeFsmNamespace()
};
}
};
var Fsm = function ( options ) {
var opt, initialState, defaults = utils.getDefaultOptions();
if ( options ) {
if ( options.eventListeners ) {
options.eventListeners = parseEventListeners( options.eventListeners );
}
if ( options.messaging ) {
options.messaging = _.extend( {}, defaults.messaging, options.messaging );
}
}
opt = _.extend( defaults, options || {} );
initialState = opt.initialState;
delete opt.initialState;
_.extend( this, opt );
this.state = undefined;
this._priorAction = "";
this._currentAction = "";
if ( initialState ) {
this.transition( initialState );
}
machina.fireEvent( "newFsm", this );
};
Fsm.prototype.fireEvent = function ( eventName ) {
var args = arguments;
_.each( this.eventListeners["*"], function ( callback ) {
try {
callback.apply( this, slice.call( args, 0 ) );
} catch ( exception ) {
if ( console && typeof console.log !== "undefined" ) {
console.log( exception.toString() );
}
}
} );
if ( this.eventListeners[eventName] ) {
_.each( this.eventListeners[eventName], function ( callback ) {
try {
callback.apply( this, slice.call( args, 1 ) );
} catch ( exception ) {
if ( console && typeof console.log !== "undefined" ) {
console.log( exception.toString() );
}
}
} );
}
};
Fsm.prototype.handle = function ( msgType ) {
// vars to avoid a "this." fest
var states = this.states, current = this.state, args = slice.call( arguments, 0 ), handlerName;
this.currentActionArgs = args;
if ( states[current] && (states[current][msgType] || states[current]["*"]) ) {
handlerName = states[current][msgType] ? msgType : "*";
this._currentAction = current + "." + handlerName;
this.fireEvent.apply( this, ["Handling"].concat( args ) );
states[current][handlerName].apply( this, args.slice( 1 ) );
this.fireEvent.apply( this, ["Handled"].concat( args ) );
this._priorAction = this._currentAction;
this._currentAction = "";
this.processQueue( NEXT_HANDLER );
}
else {
this.fireEvent.apply( this, ["NoHandler"].concat( args ) );
}
this.currentActionArgs = undefined;
};
Fsm.prototype.transition = function ( newState ) {
if ( this.states[newState] ) {
var oldState = this.state;
this.state = newState;
if ( this.states[newState]._onEnter ) {
this.states[newState]._onEnter.call( this );
}
this.fireEvent.apply( this, ["Transitioned", oldState, this.state ] );
this.processQueue( NEXT_TRANSITION );
return;
}
this.fireEvent.apply( this, ["InvalidState", this.state, newState ] );
};
Fsm.prototype.processQueue = function ( type ) {
var filterFn = type === NEXT_TRANSITION ?
function ( item ) {
return item.type === NEXT_TRANSITION && ((!item.untilState) || (item.untilState === this.state));
} :
function ( item ) {
return item.type === NEXT_HANDLER;
},
toProcess = _.filter( this.eventQueue, filterFn, this );
this.eventQueue = _.difference( this.eventQueue, toProcess );
_.each( toProcess, function ( item, index ) {
this.handle.apply( this, item.args );
}, this );
};
Fsm.prototype.deferUntilTransition = function ( stateName ) {
if ( this.currentActionArgs ) {
var queued = { type : NEXT_TRANSITION, untilState : stateName, args : this.currentActionArgs };
this.eventQueue.push( queued );
this.fireEvent.apply( this, [ "Deferred", this.state, queued ] );
}
};
Fsm.prototype.deferUntilNextHandler = function () {
if ( this.currentActionArgs ) {
var queued = { type : NEXT_TRANSITION, args : this.currentActionArgs };
this.eventQueue.push( queued );
this.fireEvent.apply( this, [ "Deferred", this.state, queued ] );
}
};
Fsm.prototype.on = function ( eventName, callback ) {
if ( !this.eventListeners[eventName] ) {
this.eventListeners[eventName] = [];
}
this.eventListeners[eventName].push( callback );
};
Fsm.prototype.off = function ( eventName, callback ) {
if ( this.eventListeners[eventName] ) {
this.eventListeners[eventName] = _.without( this.eventListeners[eventName], callback );
}
};
var machina = {
Fsm : Fsm,
bus : undefined,
utils : utils,
on : function ( eventName, callback ) {
if ( !this.eventListeners[eventName] ) {
this.eventListeners[eventName] = [];
}
this.eventListeners[eventName].push( callback );
},
off : function ( eventName, callback ) {
if ( this.eventListeners[eventName] ) {
this.eventListeners[eventName] = _.without( this.eventListeners[eventName], callback );
}
},
fireEvent : function ( eventName ) {
var i = 0, len, args = arguments, listeners = this.eventListeners[eventName];
if ( listeners && listeners.length ) {
_.each( listeners, function ( callback ) {
callback.apply( null, slice.call( args, 1 ) );
} );
}
},
eventListeners : {
newFsm : []
}
};
global.machina = machina;
return machina;
} ));

View file

@ -1,89 +0,0 @@
// This is the amd module version of postal.diagnostics.js
// If you need the standard lib version, go to http://github.com/ifandelse/postal.js
define( [ "postal", "underscore" ], function ( postal, _, undefined ) {
var filters = [],
applyFilter = function ( filter, env ) {
var match = 0, possible = 0;
_.each( filter, function ( item, key ) {
if ( env[key] ) {
possible++;
if ( _.isRegExp( item ) && item.test( env[key] ) ) {
match++;
}
else if ( _.isObject( env[key] ) && !_.isArray( env[key] ) ) {
if ( applyFilter( item, env[key] ) ) {
match++;
}
}
else {
if ( _.isEqual( env[key], item ) ) {
match++;
}
}
}
} );
return match === possible;
};
// this returns a callback that, if invoked, removes the wireTap
var wireTap = postal.addWireTap( function ( data, envelope ) {
if ( !filters.length || _.any( filters, function ( filter ) {
return applyFilter( filter, envelope );
} ) ) {
if ( !JSON ) {
throw "This browser or environment does not provide JSON support";
}
try {
console.log( JSON.stringify( envelope ) );
}
catch ( exception ) {
try {
var env = _.extend( {}, envelope );
delete env.data;
console.log( JSON.stringify( env ) + "\n\t" + "JSON.stringify Error: " + exception.message );
}
catch ( ex ) {
console.log( "Unable to parse data to JSON: " + exception );
}
}
}
} );
postal.diagnostics = postal.diagnostics || {};
postal.diagnostics.console = {
clearFilters : function () {
filters = [];
},
removeFilter : function ( filter ) {
filters = _.filter( filters, function ( item ) {
return !_.isEqual( item, filter );
} );
},
addFilter : function ( constraint ) {
if ( !_.isArray( constraint ) ) {
constraint = [ constraint ];
}
_.each( constraint, function ( item ) {
if ( filters.length === 0 || !_.any( filters, function ( filter ) {
return _.isEqual( filter, item );
} ) ) {
filters.push( item );
}
} );
},
getCurrentFilters : function () {
return filters;
},
removeWireTap : function () {
if ( wireTap ) {
wireTap();
}
}
};
} );

View file

@ -1,527 +0,0 @@
/*
postal
Author: Jim Cowart (http://freshbrewedcode.com/jimcowart)
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.7.3
*/
(function ( root, factory ) {
if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define( ["underscore"], function ( _ ) {
return factory( _, root );
} );
} else {
// Browser globals
factory( root._, root );
}
}( this, function ( _, global, undefined ) {
var DEFAULT_CHANNEL = "/",
DEFAULT_PRIORITY = 50,
DEFAULT_DISPOSEAFTER = 0,
SYSTEM_CHANNEL = "postal",
NO_OP = function () {
};
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 ChannelDefinition = function ( channelName, defaultTopic ) {
this.channel = channelName || DEFAULT_CHANNEL;
this._topic = defaultTopic || "";
};
ChannelDefinition.prototype = {
subscribe : function () {
var len = arguments.length;
if ( len === 1 ) {
return new SubscriptionDefinition( this.channel, this._topic, arguments[0] );
}
else if ( len === 2 ) {
return new SubscriptionDefinition( this.channel, arguments[0], arguments[1] );
}
},
publish : function ( obj ) {
var _obj = obj || {};
var envelope = {
channel : this.channel,
topic : this._topic,
data : _obj
};
// If this is an envelope....
if ( _obj.topic && _obj.data ) {
envelope = _obj;
envelope.channel = envelope.channel || this.channel;
}
envelope.timeStamp = new Date();
postal.configuration.bus.publish( envelope );
return envelope;
},
topic : function ( topic ) {
if ( topic === this._topic ) {
return this;
}
return new ChannelDefinition( this.channel, topic );
}
};
var SubscriptionDefinition = function ( channel, topic, callback ) {
this.channel = channel;
this.topic = topic;
this.callback = callback;
this.priority = DEFAULT_PRIORITY;
this.constraints = new Array( 0 );
this.maxCalls = DEFAULT_DISPOSEAFTER;
this.onHandled = NO_OP;
this.context = null;
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.created",
timeStamp : new Date(),
data : {
event : "subscription.created",
channel : channel,
topic : topic
}
} );
postal.configuration.bus.subscribe( this );
};
SubscriptionDefinition.prototype = {
unsubscribe : function () {
postal.configuration.bus.unsubscribe( this );
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.removed",
timeStamp : new Date(),
data : {
event : "subscription.removed",
channel : this.channel,
topic : this.topic
}
} );
},
defer : function () {
var fn = this.callback;
this.callback = function ( data ) {
setTimeout( fn, 0, data );
};
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 fn = this.onHandled;
var dispose = _.after( maxCalls, _.bind( function () {
this.unsubscribe( this );
}, this ) );
this.onHandled = function () {
fn.apply( this.context, arguments );
dispose();
};
return this;
},
distinctUntilChanged : function () {
this.withConstraint( new ConsecutiveDistinctPredicate() );
return this;
},
distinct : function () {
this.withConstraint( new DistinctPredicate() );
return this;
},
once: function() {
this.disposeAfter(1);
},
withConstraint : function ( predicate ) {
if ( !_.isFunction( predicate ) ) {
throw "Predicate constraint must be a function";
}
this.constraints.push( 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;
return this;
},
withDebounce : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = _.debounce( fn, milliseconds );
return this;
},
withDelay : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = function ( data ) {
setTimeout( function () {
fn( data );
}, milliseconds );
};
return this;
},
withPriority : function ( priority ) {
if ( _.isNaN( priority ) ) {
throw "Priority must be a number";
}
this.priority = priority;
postal.configuration.bus.changePriority( this );
return this;
},
withThrottle : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = _.throttle( fn, milliseconds );
return this;
},
subscribe : function ( callback ) {
this.callback = callback;
return this;
}
};
var bindingsResolver = {
cache : { },
compare : function ( binding, topic ) {
if ( this.cache[topic] && this.cache[topic][binding] ) {
return true;
}
var pattern = ("^" + binding.replace( /\./g, "\\." ) // escape actual periods
.replace( /\*/g, "[A-Z,a-z,0-9]*" ) // asterisks match any alpha-numeric 'word'
.replace( /#/g, ".*" ) + "$") // hash matches 'n' # of words (+ optional on start/end of topic)
.replace( "\\..*$", "(\\..*)*$" ) // fix end of topic matching on hash wildcards
.replace( "^.*\\.", "^(.*\\.)*" ); // fix beginning of topic matching on hash wildcards
var rgx = new RegExp( pattern );
var result = rgx.test( topic );
if ( result ) {
if ( !this.cache[topic] ) {
this.cache[topic] = {};
}
this.cache[topic][binding] = true;
}
return result;
},
reset : function () {
this.cache = {};
}
};
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 );
}
};
},
changePriority : function ( subDef ) {
var idx, found;
if ( this.subscriptions[subDef.channel] && this.subscriptions[subDef.channel][subDef.topic] ) {
this.subscriptions[subDef.channel][subDef.topic] = _.without( this.subscriptions[subDef.channel][subDef.topic], subDef );
idx = this.subscriptions[subDef.channel][subDef.topic].length - 1;
for ( ; idx >= 0; idx-- ) {
if ( this.subscriptions[subDef.channel][subDef.topic][idx].priority <= subDef.priority ) {
this.subscriptions[subDef.channel][subDef.topic].splice( idx + 1, 0, subDef );
found = true;
break;
}
}
if ( !found ) {
this.subscriptions[subDef.channel][subDef.topic].unshift( subDef );
}
}
},
publish : function ( envelope ) {
_.each( this.wireTaps, function ( tap ) {
tap( envelope.data, envelope );
} );
if ( this.subscriptions[envelope.channel] ) {
_.each( this.subscriptions[envelope.channel], function ( topic ) {
// TODO: research faster ways to handle this than _.clone
_.each( _.clone( topic ), function ( subDef ) {
if ( 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.onHandled();
}
}
}
} );
} );
}
},
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 idx, found, fn, 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] = new Array( 0 );
}
subs.push( subDef );
return subDef;
},
subscriptions : {},
wireTaps : new Array( 0 ),
unsubscribe : function ( config ) {
if ( this.subscriptions[config.channel][config.topic] ) {
var len = this.subscriptions[config.channel][config.topic].length,
idx = 0;
for ( ; idx < len; idx++ ) {
if ( this.subscriptions[config.channel][config.topic][idx] === config ) {
this.subscriptions[config.channel][config.topic].splice( idx, 1 );
break;
}
}
}
}
};
var publishPicker = {
"1" : function ( envelope ) {
if ( !envelope ) {
throw new Error( "publishing from the 'global' postal.publish call requires a valid envelope." );
}
envelope.channel = envelope.channel || DEFAULT_CHANNEL;
envelope.timeStamp = new Date();
postal.configuration.bus.publish( envelope );
return envelope;
},
"2" : function ( topic, data ) {
var envelope = { channel : DEFAULT_CHANNEL, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
},
"3" : function ( channel, topic, data ) {
var envelope = { channel : channel, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
}
},
channelPicker = {
"1" : function ( chn ) {
var channel = chn, topic, options = {};
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
}
else {
channel = chn.channel || DEFAULT_CHANNEL;
topic = chn.topic;
options = chn.options || options;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"2" : function ( chn, tpc ) {
var channel = chn, topic = tpc, options = {};
if ( Object.prototype.toString.call( tpc ) === "[object Object]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
options = tpc;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"3" : function ( channel, topic, options ) {
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
}
},
sessionInfo = {};
// save some setup time, albeit tiny
localBus.subscriptions[SYSTEM_CHANNEL] = {};
var postal = {
configuration : {
bus : localBus,
resolver : bindingsResolver,
DEFAULT_CHANNEL : DEFAULT_CHANNEL,
DEFAULT_PRIORITY : DEFAULT_PRIORITY,
DEFAULT_DISPOSEAFTER : DEFAULT_DISPOSEAFTER,
SYSTEM_CHANNEL : SYSTEM_CHANNEL
},
channelTypes : {
local : ChannelDefinition
},
channel : function () {
var len = arguments.length;
if ( channelPicker[len] ) {
return channelPicker[len].apply( this, arguments );
}
},
subscribe : function ( options ) {
var callback = options.callback,
topic = options.topic,
channel = options.channel || DEFAULT_CHANNEL;
return new SubscriptionDefinition( channel, topic, callback );
},
publish : function () {
var len = arguments.length;
if ( publishPicker[len] ) {
return publishPicker[len].apply( this, arguments );
}
},
addWireTap : function ( callback ) {
return this.configuration.bus.addWireTap( callback );
},
linkChannels : function ( sources, destinations ) {
var result = [];
if ( !_.isArray( sources ) ) {
sources = [sources];
}
if ( !_.isArray( destinations ) ) {
destinations = [destinations];
}
_.each( sources, function ( source ) {
var sourceTopic = source.topic || "#";
_.each( destinations, function ( destination ) {
var destChannel = destination.channel || DEFAULT_CHANNEL;
result.push(
postal.subscribe( {
channel : source.channel || DEFAULT_CHANNEL,
topic : source.topic || "#",
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;
},
utils : {
getSubscribersFor : function () {
var channel = arguments[ 0 ],
tpc = arguments[ 1 ],
result = [];
if ( arguments.length === 1 ) {
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = postal.configuration.DEFAULT_CHANNEL;
tpc = arguments[ 0 ];
}
else {
channel = arguments[ 0 ].channel || postal.configuration.DEFAULT_CHANNEL;
tpc = arguments[ 0 ].topic;
}
}
if ( postal.configuration.bus.subscriptions[ channel ] &&
postal.configuration.bus.subscriptions[ channel ].hasOwnProperty( tpc ) ) {
result = postal.configuration.bus.subscriptions[ channel ][ tpc ];
}
return result;
},
reset : function () {
postal.configuration.bus.reset();
postal.configuration.resolver.reset();
}
}
};
global.postal = postal;
return postal;
} ));

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -1,117 +0,0 @@
/*
RequireJS text 1.0.2 Copyright (c) 2010-2011, The Dojo Foundation All Rights Reserved.
Available via the MIT or new BSD license.
see: http://github.com/jrburke/requirejs for details
*/
(function () {
var k = ["Msxml2.XMLHTTP", "Microsoft.XMLHTTP", "Msxml2.XMLHTTP.4.0"], n = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, o = /<body[^>]*>\s*([\s\S]+)\s*<\/body>/im, i = typeof location !== "undefined" && location.href, p = i && location.protocol && location.protocol.replace( /\:/, "" ), q = i && location.hostname, r = i && (location.port || void 0), j = [];
define( function () {
var g, h, l;
typeof window !== "undefined" && window.navigator && window.document ? h = function ( a, c ) {
var b = g.createXhr();
b.open( "GET", a, !0 );
b.onreadystatechange =
function () {
b.readyState === 4 && c( b.responseText )
};
b.send( null )
} : typeof process !== "undefined" && process.versions && process.versions.node ? (l = require.nodeRequire( "fs" ), h = function ( a, c ) {
c( l.readFileSync( a, "utf8" ) )
}) : typeof Packages !== "undefined" && (h = function ( a, c ) {
var b = new java.io.File( a ), e = java.lang.System.getProperty( "line.separator" ), b = new java.io.BufferedReader( new java.io.InputStreamReader( new java.io.FileInputStream( b ), "utf-8" ) ), d, f, g = "";
try {
d = new java.lang.StringBuffer;
(f = b.readLine()) && f.length() &&
f.charAt( 0 ) === 65279 && (f = f.substring( 1 ));
for ( d.append( f ); (f = b.readLine()) !== null; ) {
d.append( e ), d.append( f );
}
g = String( d.toString() )
} finally {
b.close()
}
c( g )
});
return g = {version : "1.0.2", strip : function ( a ) {
if ( a ) {
var a = a.replace( n, "" ), c = a.match( o );
c && (a = c[1])
} else {
a = "";
}
return a
}, jsEscape : function ( a ) {
return a.replace( /(['\\])/g, "\\$1" ).replace( /[\f]/g, "\\f" ).replace( /[\b]/g, "\\b" ).replace( /[\n]/g, "\\n" ).replace( /[\t]/g, "\\t" ).replace( /[\r]/g, "\\r" )
}, createXhr : function () {
var a, c, b;
if ( typeof XMLHttpRequest !==
"undefined" ) {
return new XMLHttpRequest;
} else {
for ( c = 0; c < 3; c++ ) {
b = k[c];
try {
a = new ActiveXObject( b )
} catch ( e ) {
}
if ( a ) {
k = [b];
break
}
}
}
if ( !a ) {
throw Error( "createXhr(): XMLHttpRequest not available" );
}
return a
}, get : h, parseName : function ( a ) {
var c = !1, b = a.indexOf( "." ), e = a.substring( 0, b ), a = a.substring( b + 1, a.length ), b = a.indexOf( "!" );
b !== -1 && (c = a.substring( b + 1, a.length ), c = c === "strip", a = a.substring( 0, b ));
return{moduleName : e, ext : a, strip : c}
}, xdRegExp : /^((\w+)\:)?\/\/([^\/\\]+)/, useXhr : function ( a, c, b, e ) {
var d = g.xdRegExp.exec( a ),
f;
if ( !d ) {
return!0;
}
a = d[2];
d = d[3];
d = d.split( ":" );
f = d[1];
d = d[0];
return(!a || a === c) && (!d || d === b) && (!f && !d || f === e)
}, finishLoad : function ( a, c, b, e, d ) {
b = c ? g.strip( b ) : b;
d.isBuild && (j[a] = b);
e( b )
}, load : function ( a, c, b, e ) {
if ( e.isBuild && !e.inlineText ) {
b();
} else {
var d = g.parseName( a ), f = d.moduleName + "." + d.ext, m = c.toUrl( f ), h = e && e.text && e.text.useXhr || g.useXhr;
!i || h( m, p, q, r ) ? g.get( m, function ( c ) {
g.finishLoad( a, d.strip, c, b, e )
} ) : c( [f], function ( a ) {
g.finishLoad( d.moduleName + "." + d.ext, d.strip, a, b, e )
} )
}
}, write : function ( a, c, b ) {
if ( c in
j ) {
var e = g.jsEscape( j[c] );
b.asModule( a + "!" + c, "define(function () { return '" + e + "';});\n" )
}
}, writeFile : function ( a, c, b, e, d ) {
var c = g.parseName( c ), f = c.moduleName + "." + c.ext, h = b.toUrl( c.moduleName + "." + c.ext ) + ".js";
g.load( f, b, function () {
var b = function ( a ) {
return e( h, a )
};
b.asModule = function ( a, b ) {
return e.asModule( a, h, b )
};
g.write( a, f, b, d )
}, d )
}}
} )
})();

View file

@ -1,717 +0,0 @@
// Underscore.js 1.3.1
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function () {
function q( a, c, d ) {
if ( a === c ) {
return a !== 0 || 1 / a == 1 / c;
}
if ( a == null || c == null ) {
return a === c;
}
if ( a._chain ) {
a = a._wrapped;
}
if ( c._chain ) {
c = c._wrapped;
}
if ( a.isEqual && b.isFunction( a.isEqual ) ) {
return a.isEqual( c );
}
if ( c.isEqual && b.isFunction( c.isEqual ) ) {
return c.isEqual( a );
}
var e = l.call( a );
if ( e != l.call( c ) ) {
return false;
}
switch ( e ) {
case "[object String]":
return a == String( c );
case "[object Number]":
return a != +a ? c != +c : a == 0 ? 1 / a == 1 / c : a == +c;
case "[object Date]":
case "[object Boolean]":
return+a == +c;
case "[object RegExp]":
return a.source ==
c.source && a.global == c.global && a.multiline == c.multiline && a.ignoreCase == c.ignoreCase
}
if ( typeof a != "object" || typeof c != "object" ) {
return false;
}
for ( var f = d.length; f--; ) {
if ( d[f] == a ) {
return true;
}
}
d.push( a );
var f = 0, g = true;
if ( e == "[object Array]" ) {
if ( f = a.length, g = f == c.length ) {
for ( ; f--; ) {
if ( !(g = f in a == f in c && q( a[f], c[f], d )) ) {
break
}
}
}
} else {
if ( "constructor"in a != "constructor"in c || a.constructor != c.constructor ) {
return false;
}
for ( var h in a ) {
if ( b.has( a, h ) && (f++, !(g = b.has( c, h ) && q( a[h], c[h], d ))) ) {
break;
}
}
if ( g ) {
for ( h in c ) {
if ( b.has( c,
h ) && !f-- ) {
break;
}
}
g = !f
}
}
d.pop();
return g
}
var r = this, G = r._, n = {}, k = Array.prototype, o = Object.prototype, i = k.slice, H = k.unshift, l = o.toString, I = o.hasOwnProperty, w = k.forEach, x = k.map, y = k.reduce, z = k.reduceRight, A = k.filter, B = k.every, C = k.some, p = k.indexOf, D = k.lastIndexOf, o = Array.isArray, J = Object.keys, s = Function.prototype.bind, b = function ( a ) {
return new m( a )
};
if ( typeof exports !== "undefined" ) {
if ( typeof module !== "undefined" && module.exports ) {
exports = module.exports = b;
}
exports._ = b
} else {
r._ = b;
}
b.VERSION = "1.3.1";
var j = b.each =
b.forEach = function ( a, c, d ) {
if ( a != null ) {
if ( w && a.forEach === w ) {
a.forEach( c, d );
} else if ( a.length === +a.length ) {
for ( var e = 0, f = a.length; e < f; e++ ) {
if ( e in a && c.call( d, a[e], e, a ) === n ) {
break
}
}
} else {
for ( e in a ) {
if ( b.has( a, e ) && c.call( d, a[e], e, a ) === n ) {
break
}
}
}
}
};
b.map = b.collect = function ( a, c, b ) {
var e = [];
if ( a == null ) {
return e;
}
if ( x && a.map === x ) {
return a.map( c, b );
}
j( a, function ( a, g, h ) {
e[e.length] = c.call( b, a, g, h )
} );
if ( a.length === +a.length ) {
e.length = a.length;
}
return e
};
b.reduce = b.foldl = b.inject = function ( a, c, d, e ) {
var f = arguments.length > 2;
a ==
null && (a = []);
if ( y && a.reduce === y ) {
return e && (c = b.bind( c, e )), f ? a.reduce( c, d ) : a.reduce( c );
}
j( a, function ( a, b, i ) {
f ? d = c.call( e, d, a, b, i ) : (d = a, f = true)
} );
if ( !f ) {
throw new TypeError( "Reduce of empty array with no initial value" );
}
return d
};
b.reduceRight = b.foldr = function ( a, c, d, e ) {
var f = arguments.length > 2;
a == null && (a = []);
if ( z && a.reduceRight === z ) {
return e && (c = b.bind( c, e )), f ? a.reduceRight( c, d ) : a.reduceRight( c );
}
var g = b.toArray( a ).reverse();
e && !f && (c = b.bind( c, e ));
return f ? b.reduce( g, c, d, e ) : b.reduce( g, c )
};
b.find = b.detect =
function ( a, c, b ) {
var e;
E( a, function ( a, g, h ) {
if ( c.call( b, a, g, h ) ) {
return e = a, true
}
} );
return e
};
b.filter = b.select = function ( a, c, b ) {
var e = [];
if ( a == null ) {
return e;
}
if ( A && a.filter === A ) {
return a.filter( c, b );
}
j( a, function ( a, g, h ) {
c.call( b, a, g, h ) && (e[e.length] = a)
} );
return e
};
b.reject = function ( a, c, b ) {
var e = [];
if ( a == null ) {
return e;
}
j( a, function ( a, g, h ) {
c.call( b, a, g, h ) || (e[e.length] = a)
} );
return e
};
b.every = b.all = function ( a, c, b ) {
var e = true;
if ( a == null ) {
return e;
}
if ( B && a.every === B ) {
return a.every( c, b );
}
j( a, function ( a, g, h ) {
if ( !(e =
e && c.call( b, a, g, h )) ) {
return n
}
} );
return e
};
var E = b.some = b.any = function ( a, c, d ) {
c || (c = b.identity);
var e = false;
if ( a == null ) {
return e;
}
if ( C && a.some === C ) {
return a.some( c, d );
}
j( a, function ( a, b, h ) {
if ( e || (e = c.call( d, a, b, h )) ) {
return n
}
} );
return!!e
};
b.include = b.contains = function ( a, c ) {
var b = false;
if ( a == null ) {
return b;
}
return p && a.indexOf === p ? a.indexOf( c ) != -1 : b = E( a, function ( a ) {
return a === c
} )
};
b.invoke = function ( a, c ) {
var d = i.call( arguments, 2 );
return b.map( a, function ( a ) {
return(b.isFunction( c ) ? c || a : a[c]).apply( a, d )
} )
};
b.pluck =
function ( a, c ) {
return b.map( a, function ( a ) {
return a[c]
} )
};
b.max = function ( a, c, d ) {
if ( !c && b.isArray( a ) ) {
return Math.max.apply( Math, a );
}
if ( !c && b.isEmpty( a ) ) {
return-Infinity;
}
var e = {computed : -Infinity};
j( a, function ( a, b, h ) {
b = c ? c.call( d, a, b, h ) : a;
b >= e.computed && (e = {value : a, computed : b})
} );
return e.value
};
b.min = function ( a, c, d ) {
if ( !c && b.isArray( a ) ) {
return Math.min.apply( Math, a );
}
if ( !c && b.isEmpty( a ) ) {
return Infinity;
}
var e = {computed : Infinity};
j( a, function ( a, b, h ) {
b = c ? c.call( d, a, b, h ) : a;
b < e.computed && (e = {value : a, computed : b})
} );
return e.value
};
b.shuffle = function ( a ) {
var b = [], d;
j( a, function ( a, f ) {
f == 0 ? b[0] = a : (d = Math.floor( Math.random() * (f + 1) ), b[f] = b[d], b[d] = a)
} );
return b
};
b.sortBy = function ( a, c, d ) {
return b.pluck( b.map( a,
function ( a, b, g ) {
return{value : a, criteria : c.call( d, a, b, g )}
} ).sort( function ( a, b ) {
var c = a.criteria, d = b.criteria;
return c < d ? -1 : c > d ? 1 : 0
} ), "value" )
};
b.groupBy = function ( a, c ) {
var d = {}, e = b.isFunction( c ) ? c : function ( a ) {
return a[c]
};
j( a, function ( a, b ) {
var c = e( a, b );
(d[c] || (d[c] = [])).push( a )
} );
return d
};
b.sortedIndex = function ( a, c, d ) {
d || (d = b.identity);
for ( var e = 0, f = a.length; e < f; ) {
var g = e + f >> 1;
d( a[g] ) < d( c ) ? e = g + 1 : f = g
}
return e
};
b.toArray = function ( a ) {
return!a ? [] : a.toArray ? a.toArray() : b.isArray( a ) ? i.call( a ) : b.isArguments( a ) ? i.call( a ) : b.values( a )
};
b.size = function ( a ) {
return b.toArray( a ).length
};
b.first = b.head = function ( a, b, d ) {
return b != null && !d ? i.call( a, 0, b ) : a[0]
};
b.initial = function ( a, b, d ) {
return i.call( a, 0, a.length - (b == null || d ? 1 : b) )
};
b.last = function ( a, b, d ) {
return b != null && !d ? i.call( a, Math.max( a.length - b, 0 ) ) : a[a.length - 1]
};
b.rest =
b.tail = function ( a, b, d ) {
return i.call( a, b == null || d ? 1 : b )
};
b.compact = function ( a ) {
return b.filter( a, function ( a ) {
return!!a
} )
};
b.flatten = function ( a, c ) {
return b.reduce( a, function ( a, e ) {
if ( b.isArray( e ) ) {
return a.concat( c ? e : b.flatten( e ) );
}
a[a.length] = e;
return a
}, [] )
};
b.without = function ( a ) {
return b.difference( a, i.call( arguments, 1 ) )
};
b.uniq = b.unique = function ( a, c, d ) {
var d = d ? b.map( a, d ) : a, e = [];
b.reduce( d, function ( d, g, h ) {
if ( 0 == h || (c === true ? b.last( d ) != g : !b.include( d, g )) ) {
d[d.length] = g, e[e.length] = a[h];
}
return d
}, [] );
return e
};
b.union = function () {
return b.uniq( b.flatten( arguments, true ) )
};
b.intersection = b.intersect = function ( a ) {
var c = i.call( arguments, 1 );
return b.filter( b.uniq( a ), function ( a ) {
return b.every( c, function ( c ) {
return b.indexOf( c, a ) >= 0
} )
} )
};
b.difference = function ( a ) {
var c = b.flatten( i.call( arguments, 1 ) );
return b.filter( a, function ( a ) {
return!b.include( c, a )
} )
};
b.zip = function () {
for ( var a = i.call( arguments ), c = b.max( b.pluck( a, "length" ) ), d = Array( c ), e = 0; e < c; e++ ) {
d[e] = b.pluck( a, "" + e );
}
return d
};
b.indexOf = function ( a, c, d ) {
if ( a == null ) {
return-1;
}
var e;
if ( d ) {
return d = b.sortedIndex( a, c ), a[d] === c ? d : -1;
}
if ( p && a.indexOf === p ) {
return a.indexOf( c );
}
for ( d = 0, e = a.length; d < e; d++ ) {
if ( d in a && a[d] === c ) {
return d;
}
}
return-1
};
b.lastIndexOf = function ( a, b ) {
if ( a == null ) {
return-1;
}
if ( D && a.lastIndexOf === D ) {
return a.lastIndexOf( b );
}
for ( var d = a.length; d--; ) {
if ( d in a && a[d] === b ) {
return d;
}
}
return-1
};
b.range = function ( a, b, d ) {
arguments.length <= 1 && (b = a || 0, a = 0);
for ( var d = arguments[2] || 1, e = Math.max( Math.ceil( (b - a) / d ), 0 ), f = 0, g = Array( e ); f < e; ) {
g[f++] = a, a += d;
}
return g
};
var F = function () {
};
b.bind = function ( a, c ) {
var d, e;
if ( a.bind === s && s ) {
return s.apply( a, i.call( arguments, 1 ) );
}
if ( !b.isFunction( a ) ) {
throw new TypeError;
}
e = i.call( arguments, 2 );
return d = function () {
if ( !(this instanceof d) ) {
return a.apply( c, e.concat( i.call( arguments ) ) );
}
F.prototype = a.prototype;
var b = new F, g = a.apply( b, e.concat( i.call( arguments ) ) );
return Object( g ) === g ? g : b
}
};
b.bindAll = function ( a ) {
var c = i.call( arguments, 1 );
c.length == 0 && (c = b.functions( a ));
j( c, function ( c ) {
a[c] = b.bind( a[c], a )
} );
return a
};
b.memoize = function ( a, c ) {
var d = {};
c || (c = b.identity);
return function () {
var e = c.apply( this, arguments );
return b.has( d, e ) ? d[e] : d[e] = a.apply( this, arguments )
}
};
b.delay = function ( a, b ) {
var d = i.call( arguments, 2 );
return setTimeout( function () {
return a.apply( a, d )
}, b )
};
b.defer = function ( a ) {
return b.delay.apply( b, [a, 1].concat( i.call( arguments, 1 ) ) )
};
b.throttle = function ( a, c ) {
var d, e, f, g, h, i = b.debounce( function () {
h = g = false
}, c );
return function () {
d = this;
e = arguments;
var b;
f || (f = setTimeout( function () {
f = null;
h && a.apply( d, e );
i()
}, c ));
g ? h = true :
a.apply( d, e );
i();
g = true
}
};
b.debounce = function ( a, b ) {
var d;
return function () {
var e = this, f = arguments;
clearTimeout( d );
d = setTimeout( function () {
d = null;
a.apply( e, f )
}, b )
}
};
b.once = function ( a ) {
var b = false, d;
return function () {
if ( b ) {
return d;
}
b = true;
return d = a.apply( this, arguments )
}
};
b.wrap = function ( a, b ) {
return function () {
var d = [a].concat( i.call( arguments, 0 ) );
return b.apply( this, d )
}
};
b.compose = function () {
var a = arguments;
return function () {
for ( var b = arguments, d = a.length - 1; d >= 0; d-- ) {
b = [a[d].apply( this, b )];
}
return b[0]
}
};
b.after = function ( a, b ) {
return a <= 0 ? b() : function () {
if ( --a < 1 ) {
return b.apply( this, arguments )
}
}
};
b.keys = J || function ( a ) {
if ( a !== Object( a ) ) {
throw new TypeError( "Invalid object" );
}
var c = [], d;
for ( d in a ) {
b.has( a, d ) && (c[c.length] = d);
}
return c
};
b.values = function ( a ) {
return b.map( a, b.identity )
};
b.functions = b.methods = function ( a ) {
var c = [], d;
for ( d in a ) {
b.isFunction( a[d] ) && c.push( d );
}
return c.sort()
};
b.extend = function ( a ) {
j( i.call( arguments, 1 ), function ( b ) {
for ( var d in b ) {
a[d] = b[d]
}
} );
return a
};
b.defaults = function ( a ) {
j( i.call( arguments,
1 ), function ( b ) {
for ( var d in b ) {
a[d] == null && (a[d] = b[d])
}
} );
return a
};
b.clone = function ( a ) {
return!b.isObject( a ) ? a : b.isArray( a ) ? a.slice() : b.extend( {}, a )
};
b.tap = function ( a, b ) {
b( a );
return a
};
b.isEqual = function ( a, b ) {
return q( a, b, [] )
};
b.isEmpty = function ( a ) {
if ( b.isArray( a ) || b.isString( a ) ) {
return a.length === 0;
}
for ( var c in a ) {
if ( b.has( a, c ) ) {
return false;
}
}
return true
};
b.isElement = function ( a ) {
return!!(a && a.nodeType == 1)
};
b.isArray = o || function ( a ) {
return l.call( a ) == "[object Array]"
};
b.isObject = function ( a ) {
return a === Object( a )
};
b.isArguments = function ( a ) {
return l.call( a ) == "[object Arguments]"
};
if ( !b.isArguments( arguments ) ) {
b.isArguments = function ( a ) {
return!(!a || !b.has( a, "callee" ))
};
}
b.isFunction = function ( a ) {
return l.call( a ) == "[object Function]"
};
b.isString = function ( a ) {
return l.call( a ) == "[object String]"
};
b.isNumber = function ( a ) {
return l.call( a ) == "[object Number]"
};
b.isNaN = function ( a ) {
return a !== a
};
b.isBoolean = function ( a ) {
return a === true || a === false || l.call( a ) == "[object Boolean]"
};
b.isDate = function ( a ) {
return l.call( a ) == "[object Date]"
};
b.isRegExp = function ( a ) {
return l.call( a ) == "[object RegExp]"
};
b.isNull = function ( a ) {
return a === null
};
b.isUndefined = function ( a ) {
return a === void 0
};
b.has = function ( a, b ) {
return I.call( a, b )
};
b.noConflict = function () {
r._ = G;
return this
};
b.identity = function ( a ) {
return a
};
b.times = function ( a, b, d ) {
for ( var e = 0; e < a; e++ ) {
b.call( d, e )
}
};
b.escape = function ( a ) {
return("" + a).replace( /&/g, "&amp;" ).replace( /</g, "&lt;" ).replace( />/g, "&gt;" ).replace( /"/g, "&quot;" ).replace( /'/g, "&#x27;" ).replace( /\//g, "&#x2F;" )
};
b.mixin = function ( a ) {
j( b.functions( a ),
function ( c ) {
K( c, b[c] = a[c] )
} )
};
var L = 0;
b.uniqueId = function ( a ) {
var b = L++;
return a ? a + b : b
};
b.templateSettings = {evaluate : /<%([\s\S]+?)%>/g, interpolate : /<%=([\s\S]+?)%>/g, escape : /<%-([\s\S]+?)%>/g};
var t = /.^/, u = function ( a ) {
return a.replace( /\\\\/g, "\\" ).replace( /\\'/g, "'" )
};
b.template = function ( a, c ) {
var d = b.templateSettings, d = "var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('" + a.replace( /\\/g, "\\\\" ).replace( /'/g, "\\'" ).replace( d.escape || t,
function ( a, b ) {
return"',_.escape(" +
u( b ) + "),'"
} ).replace( d.interpolate || t,
function ( a, b ) {
return"'," + u( b ) + ",'"
} ).replace( d.evaluate || t,
function ( a, b ) {
return"');" + u( b ).replace( /[\r\n\t]/g, " " ) + ";__p.push('"
} ).replace( /\r/g, "\\r" ).replace( /\n/g, "\\n" ).replace( /\t/g, "\\t" ) + "');}return __p.join('');", e = new Function( "obj", "_", d );
return c ? e( c, b ) : function ( a ) {
return e.call( this, a, b )
}
};
b.chain = function ( a ) {
return b( a ).chain()
};
var m = function ( a ) {
this._wrapped = a
};
b.prototype = m.prototype;
var v = function ( a, c ) {
return c ? b( a ).chain() : a
}, K = function ( a, c ) {
m.prototype[a] =
function () {
var a = i.call( arguments );
H.call( a, this._wrapped );
return v( c.apply( b, a ), this._chain )
}
};
b.mixin( b );
j( "pop,push,reverse,shift,sort,splice,unshift".split( "," ), function ( a ) {
var b = k[a];
m.prototype[a] = function () {
var d = this._wrapped;
b.apply( d, arguments );
var e = d.length;
(a == "shift" || a == "splice") && e === 0 && delete d[0];
return v( d, this._chain )
}
} );
j( ["concat", "join", "slice"], function ( a ) {
var b = k[a];
m.prototype[a] = function () {
return v( b.apply( this._wrapped, arguments ), this._chain )
}
} );
m.prototype.chain = function () {
this._chain =
true;
return this
};
m.prototype.value = function () {
return this._wrapped
};
typeof define === "function" && define.amd && define( "underscore", function () {
return b
} )
}).call( this );

View file

@ -1,58 +0,0 @@
var socket;
require.config( {
paths : {
'text' : 'lib/requirejs-text-1.0.2',
'backbone' : 'lib/backbone',
'underscore' : 'lib/underscore',
'machina' : 'lib/machina',
'postal' : 'lib/postal',
'amplify' : 'lib/amplify',
'bus' : 'infrastructure/bus'
},
baseUrl : 'js'
} );
require( [ 'backbone', 'jquery', 'underscore', 'amplify', 'machina', 'postal', 'lib/postal.diagnostics', 'infrastructure/postal.socket-client' ],
function ( Backbone, $, _, amplify, machina, postal ) {
// Customizing Postal with some experimental functionality....
var sessionInfo = {};
postal.configuration.getSessionIdAction = function ( callback ) {
callback( sessionInfo );
};
postal.configuration.setSessionIdAction = function ( info, callback ) {
sessionInfo = info;
callback( sessionInfo );
};
postal.utils.getSessionId = function ( callback ) {
postal.configuration.getSessionIdAction.call( this, callback );
};
postal.utils.setSessionId = function ( value, callback ) {
postal.utils.getSessionId( function ( info ) {
// get the session info to move id to last id
info.lastId = info.id;
info.id = value;
// invoke the callback the user provided to handle storing session
postal.configuration.setSessionIdAction( info, function ( session ) {
callback( session );
// publish postal event msg about the change
postal.publish( {
channel : postal.configuration.SYSTEM_CHANNEL,
topic : "sessionId.changed",
data : session
} );
} );
} );
};
// for debugging purposes ONLY for now:
window.postal = postal;
require( [ 'infrastructure/app' ], function ( app ) {
window.app = app;
} );
} );

View file

@ -1,60 +0,0 @@
define( [
'backbone',
'bus'
],
function ( Backbone, bus ) {
"use strict";
return Backbone.Model.extend( {
defaults : {
sessionId : "",
searchOwnership : "",
searchTerm : "",
requests : false
},
initialize : function () {
this.subscriptions = [
bus.app.subscribe( "search.info", this.setCurrentSearch ).withContext( this ),
bus.app.subscribe( "search.new.ask", this.updateReqCount ).withContext( this ),
bus.app.subscribe( "search.requests", this.updateReqCount ).withContext( this )
];
bus.app.publish( {
topic : "get.search.info",
data : {}
} );
},
dispose : function () {
_.each( this.subscriptions, function ( subscription ) {
subscription.unsubscribe();
} );
this.clear( { silent : true } );
},
setCurrentSearch : function ( data, env ) {
var self = this;
self.set( "searchTerm", data.searchTerm );
postal.utils.getSessionId(
function ( session ) {
self.set( "sessionId", session.id, { silent : true } );
self.set( "searchOwnership",
(session.id === data.id)
? "You own the search."
: "You do not own the search."
);
}
);
},
updateReqCount : function ( data, env ) {
if ( (_.isArray( data ) && data.length) || data.searchTerm ) {
this.set( "requests", true );
}
else {
this.set( "requests", false );
}
this.change( "requests" );
}
} );
} );

View file

@ -1,70 +0,0 @@
define( [
'backbone',
'bus'
],
function ( Backbone, bus ) {
"use strict";
return Backbone.Model.extend( {
defaults : {
requests : [],
ownerId : undefined,
sessionId : undefined
},
initialize : function () {
_.bindAll( this );
this.setSessionId();
this.subscriptions = [
bus.app.subscribe( "search.info", this.setOwner ).withContext( this ),
bus.app.subscribe( "search.requests", this.updateRequests ).withContext( this ),
bus.app.subscribe( "search.new.ask", this.addRequest ).withContext( this ),
bus.app.subscribe( "search.init", this.askForUpdate ).withContext( this )
];
this.askForUpdate();
},
askForUpdate : function () {
bus.app.publish( {
topic : "get.search.requests",
data : {}
} );
bus.app.publish( {
topic : "get.search.info",
data : {}
} );
},
dispose : function () {
_.each( this.subscriptions, function ( subscription ) {
subscription.unsubscribe();
} );
this.clear( { silent : true } );
},
setOwner : function ( data, env ) {
this.set( "ownerId", data.id );
this.setSessionId();
},
setSessionId : function () {
var self = this;
postal.utils.getSessionId(
function ( session ) {
self.set( "sessionId", session.id );
}
);
},
updateRequests : function ( data, env ) {
var reqs = _.sortBy( data, function ( item ) {
return item.searchTerm;
});
this.set( "requests", reqs );
},
addRequest : function ( data, env ) {
this.askForUpdate();
}
} );
} );

View file

@ -1,33 +0,0 @@
define( [
'backbone'
],
function ( Backbone ) {
"use strict";
return function( defaults, subscriptions ) {
var model = new (Backbone.Model.extend( {
initialize : function () {
_.bindAll( this );
this.subscriptions = [];
},
dispose : function () {
if( this.subscriptions ) {
_.each( this.subscriptions, function ( subscription ) {
subscription.unsubscribe();
} );
}
this.clear( { silent : true } );
}
}))( defaults );
_.each( subscriptions, function( sub ){
sub.withContext( model );
});
model.subscriptions = subscriptions ;
return model;
};
} );

View file

@ -1,40 +0,0 @@
define( [
'backbone',
'postal'
],
function ( Backbone, postal ) {
"use strict";
var RawMessage = Backbone.Model.extend({
defaults: {
text: ""
}
});
return Backbone.Collection.extend( {
model: RawMessage,
initialize : function () {
var self = this;
postal.addWireTap(function( data, envelope ){
var text = "";
try {
text = JSON.stringify( envelope );
}
catch ( exception ) {
try {
var env = _.extend( {}, envelope );
delete env.data;
text = JSON.stringify( env ) + "\n\t" + "JSON.stringify Error: " + exception.message;
}
catch ( ex ) {
text = "Unable to parse data to JSON: " + exception;
}
}
self.add({ text: text });
});
}
} );
} );

View file

@ -1,21 +0,0 @@
define( [
'jquery',
'views/managed-view',
'text!views/templates/container.html'
],
function ( $, ManagedView, template ) {
// Using ECMAScript 5 strict mode during development. By default r.js will ignore that.
"use strict";
return ManagedView.extend( {
el : "body",
initialize : function () {
ManagedView.prototype.initialize.call(this, template);
},
render : function () {
this.$el.html( this.template );
}
} );
} );

View file

@ -1,43 +0,0 @@
define( [
'jquery',
'views/managed-view',
'text!views/templates/hash-tag-count.html',
'models/stat-model',
'bus'
],
function ( $, ManagedView, template, HashTagCountModel, bus ) {
"use strict";
return ManagedView.extend({
tagName : "div",
initialize : function () {
ManagedView.prototype.initialize.call(this, template);
this.model = new HashTagCountModel({ hashTags : [] }, [
bus.stats.subscribe( "hash-tag-count", function ( data, env ) {
if ( data.hashTags && data.hashTags.length ) {
this.set( "hashTags", _.sortBy( data.hashTags, function ( item ) {
return item.count * -1;
} ) );
}
} ),
bus.app.subscribe( "search.init", function () {
this.set( "hashTags", [] );
} )
]);
bus.app.subscribe( "search.info", this.setCurrentSearch );
this.model.bind( "change", this.render );
this.inDom = false;
bus.stats.publish( { topic : "hash-tag-count.getLatest", data : {} } );
},
render : function () {
// TODO: Capture scroll position and restore after render...
this.$el.html( this.template( this.model.toJSON() ) );
if ( !this.inDom ) {
this.$el.appendTo( "#stats" );
this.inDom = true;
}
}
});
} );

View file

@ -1,30 +0,0 @@
define( [
'underscore',
'jquery',
'backbone'
],
function ( _, $, Backbone ) {
"use strict";
return Backbone.View.extend( {
initialize : function ( template ) {
_.bindAll( this );
if( template ) {
this.template = _.template( template );
}
},
render : function () {
this.$el.html( this.template( this.model.toJSON() ) );
},
show : function () {
this.$el.show();
},
hide : function () {
this.$el.hide();
}
} );
} );

View file

@ -1,43 +0,0 @@
define( [
'jquery',
'views/managed-view',
'text!views/templates/mention-count.html',
'models/stat-model',
'bus'
],
function ( $, ManagedView, template, MentionCountModel, bus ) {
"use strict";
return ManagedView.extend( {
tagName : "div",
initialize : function () {
ManagedView.prototype.initialize.call(this, template);
this.model = new MentionCountModel({ mentions : [] },[
bus.stats.subscribe( "mention-count", function ( data, env ) {
if ( data.mentions && data.mentions.length ) {
this.set( "mentions", _.sortBy( data.mentions, function ( item ) {
return item.count * -1;
} ) );
}
} ),
bus.app.subscribe( "search.init", function () {
this.set( "mentions", [] );
} )
]);
bus.app.subscribe( "search.info", this.setCurrentSearch );
this.model.bind( "change", this.render );
this.inDom = false;
bus.stats.publish( { topic : "mention-count.getLatest", data : {} } );
},
render : function () {
// TODO: Capture scroll position and restore after render...
this.$el.html( this.template( this.model.toJSON() ) );
if ( !this.inDom ) {
this.$el.appendTo( "#stats" );
this.inDom = true;
}
}
} );
} );

View file

@ -1,43 +0,0 @@
define( [
'jquery',
'views/managed-view',
'text!views/templates/mentioner-count.html',
'models/stat-model',
'bus'
],
function ( $, ManagedView, template, MentionerCountModel, bus ) {
"use strict";
return ManagedView.extend( {
tagName : "div",
initialize : function () {
ManagedView.prototype.initialize.call(this, template);
this.model = new MentionerCountModel({ mentioners: [] }, [
bus.stats.subscribe( "mentioner-count", function ( data, env ) {
if ( data.mentioners && data.mentioners.length ) {
this.set( "mentioners", _.sortBy( data.mentioners, function ( item ) {
return item.count * -1;
} ) );
}
} ),
bus.app.subscribe( "search.init", function () {
this.set( "mentioners", [] );
} )
]);
bus.app.subscribe( "search.info", this.setCurrentSearch );
this.model.bind( "change", this.render );
this.inDom = false;
bus.stats.publish( { topic : "mentioner-count.getLatest", data : {} } );
},
render : function () {
// TODO: Capture scroll position and restore after render...
this.$el.html( this.template( this.model.toJSON() ) );
if ( !this.inDom ) {
this.$el.appendTo( "#stats" );
this.inDom = true;
}
}
} );
} );

View file

@ -1,58 +0,0 @@
define( [
'jquery',
'views/managed-view',
'text!views/templates/menu.html',
'bus',
'models/menu-model'
],
function ( $, ManagedView, template, bus, MenuModel ) {
"use strict";
return ManagedView.extend( {
el : "#menu",
events : {
"click #btnSearch" : "updateSearch"
},
initialize : function () {
ManagedView.prototype.initialize.call(this, template);
this.model = new MenuModel();
this.model.bind( "change", this.updateView );
},
updateSearch : function () {
var searchTerm = this.$el.find( '#searchTerm' ).val();
if ( searchTerm ) {
bus.app.publish( {
topic : "search.new.request",
data : {
searchTerm : searchTerm
}
} );
}
},
updateView : function () {
var $ownership = this.$el.find( "#search-ownership" ),
mdlOwnership = this.model.get( "searchOwnership" ),
dispOwnership = $ownership.text();
if( dispOwnership && mdlOwnership !== dispOwnership ) {
$ownership.text( this.model.get( "searchOwnership" ) )
.animate({
"font-size": "14pt",
"margin-left" : "+=15"
}, 500, function() {
$ownership.stop().animate({
"font-size" : "12pt",
"margin-left" : "-=15"
}, 400);
});
} else {
$ownership.text( this.model.get( "searchOwnership" ) );
}
this.$el.find( "#currentSearch" ).text( this.model.get( "searchTerm" ) );
this.$el.find( "#request-indicator" ).text( this.model.get( "requests" ) ? " *" : "" );
}
} );
} );

View file

@ -1,52 +0,0 @@
define( [
'jquery',
'views/managed-view',
'text!views/templates/profanity-percentage.html',
'models/stat-model',
'bus'
],
function ( $, ManagedView, template, ProfanityPercentageModel, bus ) {
"use strict";
return ManagedView.extend( {
tagName : "div",
initialize : function () {
ManagedView.prototype.initialize.call(this, template);
this.model = new ProfanityPercentageModel({
percentage : "",
clean : "",
explicit : "",
total : ""
},[
bus.stats.subscribe( "profanity-percentage", function ( data, env ) {
this.set( "percentage", data.percentage, { silent : true } );
this.set( "clean", data.clean, { silent : true } );
this.set( "explicit", data.explicit, { silent : true } );
this.set( "total", data.clean + data.explicit, { silent : true } );
this.change();
} ),
bus.app.subscribe( "search.init", function () {
this.set( "percentage", "", { silent : true } );
this.set( "clean", "", { silent : true } );
this.set( "explicit", "", { silent : true } );
this.set( "total", "", { silent : true } );
this.change();
} )
]);
bus.app.subscribe( "search.info", this.setCurrentSearch );
this.model.bind( "change", this.render );
this.inDom = false;
bus.stats.publish( { topic : "profanity-percentage.getLatest", data : {} } );
},
render : function () {
// TODO: Capture scroll position and restore after render...
this.$el.html( this.template( this.model.toJSON() ) );
if ( !this.inDom ) {
this.$el.appendTo( "#stats" );
this.inDom = true;
}
}
} );
} );

View file

@ -1,36 +0,0 @@
define( [
'jquery',
'views/managed-view',
'text!views/templates/search-requests.html',
'models/search-requests-model',
'bus'
],
function ( $, ManagedView, template, SearchRequestsModel, bus ) {
"use strict";
return ManagedView.extend( {
el : "#requests",
events : {
"click a.req-allow" : "allowSearch"
},
initialize : function () {
ManagedView.prototype.initialize.call(this, template);
this.model = new SearchRequestsModel();
this.model.bind( "change", this.render );
},
allowSearch : function ( e ) {
var idx = $( e.currentTarget ).attr( 'href' ),
search = this.model.get( "requests" )[idx];
if ( search ) {
bus.app.publish( {
topic : "search.new.confirm",
data : search
} )
}
e.preventDefault();
}
} );
} );

View file

@ -1,7 +0,0 @@
<div id="menu"></div>
<div id="requests"></div>
<div id="stats"></div>
<div id="wiretap"></div>

View file

@ -1,15 +0,0 @@
<fieldset>
<legend>
<div class="title">Associated Hash Tags:</div>
</legend>
<div class="scrollableDiv">
<table class="hashTagTable" cellspacing="0">
<% _.each( hashTags, function( item ) { %>
<tr>
<td><span><%= item.hashTag %></span></td>
<td><span><%= item.count %></span></td>
</tr>
<% }) %>
</table>
</div>
</fieldset>

View file

@ -1,14 +0,0 @@
<fieldset>
<legend><span>Top Mentioned:</span></legend>
<div class="scrollableDiv">
<table cellspacing="0">
<% _.each( mentions, function( mention ) { %>
<tr>
<td><img class="images" src="<%= mention.image %>"/></td>
<td><span><%= mention.user %></span></td>
<td><span><%= mention.count %></span></td>
</tr>
<% }) %>
</table>
</div>
</fieldset>

View file

@ -1,14 +0,0 @@
<fieldset>
<legend><span>Top Mention-ers:</span></legend>
<div class="scrollableDiv">
<table cellspacing="0">
<% _.each( mentioners, function( mentioner ) { %>
<tr>
<td><img class="images" src="<%= mentioner.image %>"/></td>
<td><span><%= mentioner.user %></span></td>
<td><span><%= mentioner.count %></span></td>
</tr>
<% }) %>
</table>
</div>
</fieldset>

View file

@ -1,15 +0,0 @@
<div class="top-bar">
<ul class="nav-menu">
<li><a class="ps-nav" href="">Home</a></li>
<li><a class="ps-nav" href="requests">Requested Searches</a><span id="request-indicator"></span></li>
<li><a class="ps-nav" href="wiretap">Show Wiretap</a></li>
</ul>
<div class="search">Searching Twitter for: <span id="currentSearch"><%= searchTerm %></span></div>
<div class="search">
<input type="text" id="searchTerm">
<input id="btnSearch" type="button" value="Search">
</div>
<div class="search" id="search-ownership"><%= searchOwnership %></div>
<div style="clear:both"></div>
</div>

View file

@ -1,17 +0,0 @@
<fieldset>
<legend><span>Profanity Rate:</span></legend>
<div>
<div>
<span>Total:</span>
<span><%= total %></span>
</div>
<div>
<span>Explicit:</span>
<span><%= explicit %></span>
</div>
<div>
<span>Profanity Rate:</span>
<span><%= percentage %>%</span>
</div>
</div>
</fieldset>

View file

@ -1,18 +0,0 @@
<div>
<% if( requests.length > 0 ) { %>
<div>
<h4>Search Requests <% if( sessionId === ownerId ) { %>(click one to give control to the requester)<% } %></h4>
</div>
<% _.each( requests, function ( request, idx ) { %>
<div>(Client ID: <%= request.correlationId %>) -
<% if( sessionId === ownerId ) { %>
<a class="req-allow" href="<%= idx %>"><%= request.searchTerm %></a>
<% } else { %>
<%= request.searchTerm %>
<% } %>
</div>
<% }) %>
<% } else { %>
<div><em>No other searches have been requested at this time.</em></div>
<% } %>
</div>

View file

@ -1,14 +0,0 @@
<fieldset>
<legend><span>Top Tweeters:</span></legend>
<div class="scrollableDiv">
<table cellspacing="0">
<% _.each( tweeters, function( tweeter ) { %>
<tr>
<td><img class="images" src="<%= tweeter.image %>"/></td>
<td><span><%= tweeter.user %></span></td>
<td><span><%= tweeter.count %></span></td>
</tr>
<% }) %>
</table>
</div>
</fieldset>

View file

@ -1,13 +0,0 @@
<h3>A Somewhat Pointless Feed of the postal.js Wiretap:</h3>
<div class="scrollableDiv">
<% if( messages.length ) { %>
<% _.each( messages, function( msg ) { %>
<div>
<%= msg.text %>
</div>
<hr>
<% }) %>
<% } else { %>
<em>Waiting on more data to come through the wiretap...</em>
<% } %>
</div>

View file

@ -1,45 +0,0 @@
define( [
'jquery',
'views/managed-view',
'text!views/templates/tweet-count.html',
'models/stat-model',
'bus'
],
function ( $, ManagedView, template, TweetCountModel, bus ) {
"use strict";
return ManagedView.extend( {
tagName : "div",
initialize : function () {
ManagedView.prototype.initialize.call(this, template);
this.model = new TweetCountModel({
tweeters : []
}, [
bus.stats.subscribe( "tweet-count", function ( data, env ) {
if ( data.tweeters && data.tweeters.length ) {
this.set( "tweeters", _.sortBy( data.tweeters, function ( item ) {
return item.count * -1;
} ) );
}
} ) ,
bus.app.subscribe( "search.init", function () {
this.set( "tweeters", [] );
} )
]);
bus.app.subscribe( "search.info", this.setCurrentSearch );
this.model.bind( "change", this.render );
this.inDom = false;
bus.stats.publish( { topic : "tweet-count.getLatest", data : {} } );
},
render : function () {
// TODO: Capture scroll position and restore after render...
this.$el.html( this.template( this.model.toJSON() ) );
if ( !this.inDom ) {
this.$el.appendTo( "#stats" );
this.inDom = true;
}
}
} );
} );

View file

@ -1,25 +0,0 @@
define( [
'jquery',
'views/managed-view',
'text!views/templates/wiretap.html',
'models/wiretap-model'
],
function ( $, ManagedView, template, WiretapModel ) {
"use strict";
return ManagedView.extend( {
el : "#wiretap",
initialize : function () {
ManagedView.prototype.initialize.call(this, template);
this.model = new WiretapModel();
this.model.bind( "add", this.render );
},
render : function () {
this.$el.html( this.template( { messages: this.model.toJSON() } ) );
// Yes, I know - quick, dirty and ugly :-)
this.$el.find(".scrollableDiv").animate({ scrollTop: this.$el.find(".scrollableDiv")[0].scrollHeight }, "fast");
}
} );
} );

View file

@ -1,63 +0,0 @@
var _ = require( 'underscore' ),
_scanner = function ( regExp, text, callback ) {
var match;
while ( text.search( regExp ) !== -1 ) {
match = regExp.exec( text );
if ( match && match[1] ) {
callback( match[1].toLowerCase() );
}
text = text.replace( regExp, "" );
}
},
HashTagCount = function ( namespace ) {
this.namespace = namespace;
this.events = {};
this.hashTags = { list : [], registry : {} };
this.lastStats = undefined;
};
HashTagCount.prototype = {
init : function () {
this.hashTags = { list : [], registry : {} };
this.lastStats = undefined;
},
on : function ( eventName, callback ) {
if ( !this.events[ eventName ] ) {
this.events[ eventName ] = [];
}
this.events[ eventName ].push( callback );
return function () {
this.events[ eventName ] = _.without( this.events[ eventName ], callback );
}.bind( this );
},
raiseEvent : function ( eventName, data ) {
if ( this.events[ eventName ] ) {
this.events[ eventName ].forEach( function ( callback ) {
callback( data );
} );
}
},
processNewTweets : function ( tweets ) {
tweets.forEach( function ( tweet ) {
this.processOtherHashTags( tweet.text );
}, this );
this.lastStats = { type : "HashTagCount", hashTags : this.hashTags.list };
this.raiseEvent( this.namespace, this.lastStats );
},
processOtherHashTags : function ( text ) {
_scanner( /#(\w*)/i, text, function ( hash ) {
if ( !this.hashTags.registry[ hash ] ) {
var obj = { hashTag : hash, count : 0 };
this.hashTags.registry[ hash ] = obj;
this.hashTags.list.push( obj );
}
this.hashTags.registry[ hash ].count++;
}.bind( this ) );
}
};
module.exports = HashTagCount;

View file

@ -1,73 +0,0 @@
var _ = require( 'underscore' ),
_scanner = function ( regExp, text, callback ) {
var match;
while ( text.search( regExp ) !== -1 ) {
match = regExp.exec( text );
if ( match && match[1] ) {
callback( match[1].toLowerCase() );
}
text = text.replace( regExp, "" );
}
},
MentionCount = function ( namespace ) {
this.namespace = namespace;
this.events = {};
this.mentions = { list : [], registry : {} };
this.userImageMap = {};
this.lastStats = undefined;
};
MentionCount.prototype = {
init : function () {
this.mentions = { list : [], registry : {} };
this.lastStats = undefined;
},
on : function ( eventName, callback ) {
if ( !this.events[ eventName ] ) {
this.events[ eventName ] = [];
}
this.events[ eventName ].push( callback );
return function () {
this.events[ eventName ] = _.without( this.events[ eventName ], callback );
}.bind( this );
},
raiseEvent : function ( eventName, data ) {
if ( this.events[ eventName ] ) {
this.events[ eventName ].forEach( function ( callback ) {
callback( data );
} );
}
},
processNewTweets : function ( tweets ) {
tweets.forEach( function ( tweet ) {
this.userImageMap[ tweet.from_user ] = tweet.profile_image_url;
this.processMentions( tweet );
}, this );
this.tryToMatchProfileImages();
this.lastStats = { type : "MentionCount", mentions : this.mentions.list };
this.raiseEvent( this.namespace, this.lastStats );
},
tryToMatchProfileImages : function () {
_.each( this.mentions.registry, function ( v, k ) {
if ( this.userImageMap[ k ] ) {
v.image = this.userImageMap[ k ];
}
}, this );
},
processMentions : function ( tweet ) {
_scanner( /@(\w*)/i, tweet.text, function ( mentioned ) {
if ( !this.mentions.registry[mentioned] ) {
var obj = { user : mentioned, count : 0, image : "images/default_profile_1_normal.png" };
this.mentions.registry[mentioned] = obj;
this.mentions.list.push( obj );
}
this.mentions.registry[mentioned].count++;
}.bind( this ) );
}
};
module.exports = MentionCount;

View file

@ -1,62 +0,0 @@
var _ = require( 'underscore' ),
_scanner = function ( regExp, text, callback ) {
var match;
while ( text.search( regExp ) !== -1 ) {
match = regExp.exec( text );
if ( match && match[1] ) {
callback( match[1].toLowerCase() );
}
text = text.replace( regExp, "" );
}
},
MentionerCount = function ( namespace ) {
this.namespace = namespace;
this.events = {};
this.mentioners = { list : [], registry : {} };
this.lastStats = undefined;
};
MentionerCount.prototype = {
init : function () {
this.mentioners = { list : [], registry : {} };
this.lastStats = undefined;
},
on : function ( eventName, callback ) {
if ( !this.events[ eventName ] ) {
this.events[ eventName ] = [];
}
this.events[ eventName ].push( callback );
return function () {
this.events[ eventName ] = _.without( this.events[ eventName ], callback );
}.bind( this );
},
raiseEvent : function ( eventName, data ) {
if ( this.events[ eventName ] ) {
this.events[ eventName ].forEach( function ( callback ) {
callback( data );
} );
}
},
processNewTweets : function ( tweets ) {
tweets.forEach( function ( tweet ) {
this.processMentioners( tweet );
}, this );
this.lastStats = { type : "MentionerCount", mentioners : this.mentioners.list };
this.raiseEvent( this.namespace, this.lastStats );
},
processMentioners : function ( tweet ) {
_scanner( /@(\w*)/i, tweet.text, function ( mentioned ) {
if ( !this.mentioners.registry[tweet.from_user] ) {
var obj = { user : tweet.from_user, count : 0, image : tweet.profile_image_url };
this.mentioners.registry[tweet.from_user] = obj;
this.mentioners.list.push( obj );
}
this.mentioners.registry[tweet.from_user].count++;
}.bind( this ) );
}
};
module.exports = MentionerCount;

View file

@ -1,108 +0,0 @@
// quick hack array - not going to go regex crazy for now
var _badWords = [
/\bfuck\b/i,
/\bfucks\b/i,
/\bfucking\b/i,
/\bfucked\b/i,
/\bfucker\b/i,
/\bfuckers\b/i,
/\bass\b/i,
/\basses\b/i,
/\basshole\b/i,
/\bassholes\b/i,
/\bshit\b/i,
/\bshitted\b/i,
/\bshitter\b/i,
/\bshitters\b/i,
/\bshitting\b/i,
/\bshithead\b/i,
/\bcunt\b/i,
/\bcunts\b/i,
/\bpussy\b/i,
/\bdick\b/i,
/\bdicks\b/i,
/\bdickhead\b/i,
/\bdickheads\b/i,
/\bdamn\b/i,
/\bdamned\b/i,
/\bdamning\b/i,
/\bcock\b/i,
/\bpenis\b/i,
/\bfag\b/i,
/\bbitch\b/i,
/\bfaggot\b/,
/\bpiss\b/,
/\bpissing\b/
],
_ = require( 'underscore' ),
ProfanityPercentage = function ( namespace ) {
this.namespace = namespace;
this.events = {};
this.profanityStats = { clean : 0, explicit : 0 };
this.lastStats = undefined;
};
ProfanityPercentage.prototype = {
init : function () {
this.profanityStats = { clean : 0, explicit : 0 };
this.lastStats = undefined;
},
on : function ( eventName, callback ) {
if ( !this.events[ eventName ] ) {
this.events[ eventName ] = [];
}
this.events[ eventName ].push( callback );
return function () {
this.events[ eventName ] = _.without( this.events[ eventName ], callback );
}.bind( this );
},
raiseEvent : function ( eventName, data ) {
if ( this.events[ eventName ] ) {
this.events[ eventName ].forEach( function ( callback ) {
callback( data );
} );
}
},
hasProfanity : function ( text ) {
for ( var i = 0; i < _badWords.length; i++ ) {
if ( text.search( _badWords[i] ) !== -1 ) {
return true;
}
}
return false;
},
getPercentage : function () {
var total = (this.profanityStats.clean + this.profanityStats.explicit);
if ( total === 0 ) {
return 0;
}
else {
return ((this.profanityStats.explicit / total) * 100).toFixed( 2 );
}
},
processNewTweets : function ( tweets ) {
tweets.forEach( function ( tweet ) {
this.profanitize( tweet );
}, this );
this.lastStats = {
type : "ProfanityPercentage",
percentage : this.getPercentage(),
clean : this.profanityStats.clean,
explicit : this.profanityStats.explicit
};
this.raiseEvent( this.namespace, this.lastStats );
},
profanitize : function ( tweet ) {
if ( this.hasProfanity( tweet.text ) ) {
this.profanityStats.explicit++;
}
else {
this.profanityStats.clean++;
}
}
};
module.exports = ProfanityPercentage;

View file

@ -1,52 +0,0 @@
var _ = require( 'underscore' ),
TweetCount = function ( namespace ) {
this.namespace = namespace;
this.events = {};
this.tweeters = { list : [], registry : {} };
this.lastStats = undefined;
};
TweetCount.prototype = {
init : function () {
this.tweeters = { list : [], registry : {} };
this.lastStats = undefined;
},
on : function ( eventName, callback ) {
if ( !this.events[ eventName ] ) {
this.events[ eventName ] = [];
}
this.events[ eventName ].push( callback );
return function () {
this.events[ eventName ] = _.without( this.events[ eventName ], callback );
}.bind( this );
},
raiseEvent : function ( eventName, data ) {
if ( this.events[ eventName ] ) {
this.events[ eventName ].forEach( function ( callback ) {
callback.call( this, data );
} );
}
},
processNewTweets : function ( tweets ) {
tweets.forEach( function ( tweet ) {
this.processTweetCount( tweet );
}, this );
this.lastStats = { type : "TweetCount", tweeters : this.tweeters.list };
this.raiseEvent( this.namespace, this.lastStats );
},
processTweetCount : function ( tweet ) {
if ( tweet.from_user ) {
if ( !this.tweeters.registry[tweet.from_user] ) {
var obj = {user : tweet.from_user, count : 0, image : tweet.profile_image_url};
this.tweeters.registry[tweet.from_user] = obj;
this.tweeters.list.push( obj );
}
this.tweeters.registry[tweet.from_user].count++;
}
}
};
module.exports = TweetCount;

View file

@ -1,215 +0,0 @@
var _ = require( "underscore" ),
express = require( 'express' ),
app = express.createServer(),
postal = require( "./messaging/postal.js" ),
PostalSocketHost = require( './messaging/postal.socket-host.js' ),
sockets = require( './messaging/socket-io-provider.js' ),
TwitterSearch = require( './twitter-search.js' ),
collectors = require( './stat-collectors.js' ),
BusAdapter = require( './messaging/bus-adapter.js' ),
machina = require( './machina.js' );
postal.addWireTap( function ( data, envelope ) {
if ( envelope.channel === "stats" /*|| envelope.channel === "twittersearch"*/ ) {
var env = _.extend( {}, envelope );
delete env.data;
console.log( JSON.stringify( env ) );
}
else if ( _.include( ["postal.socket", "postal", "app", "app.events"], envelope.channel ) ) {
console.log( JSON.stringify( envelope ) );
}
} );
// wire machina FSMs into postal automagically
require( './messaging/machina.postal.js' )( postal, machina );
var TwitterSocketStats = function ( port, refreshinterval ) {
// Stand up our express app
app.use( "/", express.static( __dirname + '/client' ) );
app.listen( port );
var searchChannel = postal.channel( "twittersearch", "#" ),
statsChannel = postal.channel( "stats", "#" ),
appChannel = postal.channel( "statsApp", "#" ),
fsm;
postal.linkChannels( { channel : "postal.socket", topic : "client.migrated"}, { channel : "statsApp", topic : "client.migrated" } );
fsm = new machina.Fsm( {
namespace : "statsApp",
currentSearch : {
id : null,
searchTerm : "{ Search Not Active }"
},
searchRequests : {},
express : app,
appChannel : appChannel,
searchChannel : searchChannel,
statsChannel : statsChannel,
searchAgent : new BusAdapter( new TwitterSearch( refreshinterval ), searchChannel, searchChannel ),
requestedSearches : [],
stats : collectors.load( searchChannel, statsChannel ),
postal : postal,
bridge : new PostalSocketHost( postal, sockets.getSocketProvider( postal, app ) ),
getAvailableStats : function ( clientId ) {
this.appChannel.publish( {
topic : "available.topics",
correlationId : clientId,
data : {
topics : _.toArray( this.stats ).map( function ( stat ) {
return { channel : "stats", topic : stat.namespace };
} )
}
} );
},
setSearch : function ( correlationId, searchTerm ) {
this.currentSearch = {
id : correlationId,
searchTerm : searchTerm
};
this.searchAgent.search( searchTerm );
this.removeSearchRequest( correlationId, searchTerm );
this.appChannel.publish( {
topic : "search.info",
data : this.currentSearch
} );
},
getSearchInfo : function ( env ) {
this.appChannel.publish( {
topic : "search.info",
correlationId : env.correlationId,
data : this.currentSearch
} );
},
getSearchRequests : function ( env ) {
env || (env = {});
this.appChannel.publish( {
topic : "search.requests",
correlationId : env.correlationId,
data : this.requestedSearches
} );
},
addSearchRequest : function ( correlationId, searchTerm ) {
if ( !_.any( this.searchRequests, function ( item ) {
return item.correlationId === request.correlationId &&
item.searchTerm === request.searchTerm;
} ) ) {
this.requestedSearches.push( { correlationId : correlationId, searchTerm : searchTerm } );
}
},
removeSearchRequest : function ( correlationId, searchTerm ) {
if ( _.any( this.requestedSearches, function ( item ) {
return item.correlationId === correlationId && item.searchTerm === searchTerm;
} ) ) {
this.requestedSearches = _.filter( this.requestedSearches, function ( item ) {
return item.correlationId !== correlationId && item.searchTerm !== searchTerm;
} );
this.getSearchRequests();
}
},
states : {
uninitialized : {
start : function () {
this.transition( "notSearching" );
},
"*" : function () {
this.deferUntilTransition();
}
},
notSearching : {
"search.new.request" : function ( data, envelope ) {
this.setSearch( envelope.correlationId, data.searchTerm );
this.transition( "searching" );
},
"get.search.info" : function ( data, env ) {
this.getSearchInfo( env );
},
"get.available" : function ( data, envelope ) {
this.getAvailableStats( envelope.correlationId );
}
},
searching : {
"search.new.request" : function ( data, envelope ) {
if ( envelope.correlationId === this.currentSearch.id ) {
this.setSearch( envelope.correlationId, data.searchTerm );
}
else {
this.addSearchRequest( envelope.correlationId, data.searchTerm );
this.appChannel.publish( {
topic : "search.new.ask",
data : {
correlationId : envelope.correlationId,
searchTerm : data.searchTerm
}
} );
}
},
"search.new.confirm" : function ( data, envelope ) {
if ( envelope.correlationId === this.currentSearch.id ) {
this.setSearch( data.correlationId, data.searchTerm );
}
},
"get.search.info" : function ( data, env ) {
this.getSearchInfo( env );
},
"get.search.requests" : function ( data, env ) {
this.getSearchRequests( env );
},
"get.available" : function ( data, envelope ) {
this.getAvailableStats( envelope.correlationId );
},
"client.migrated" : function ( data, envelope ) {
if ( data.lastSessionId === this.currentSearch.id ) {
this.currentSearch.id = data.sessionId;
}
},
"client.disconnect" : function ( data ) {
if ( this.currentSearch.id === data.sessionId ) {
if( this.requestedSearches.length ) {
var newSearch = this.requestedSearches.shift();
this.setSearch( newSearch.correlationId, newSearch.searchTerm );
} else {
this.transition("notSearching");
}
}
}
}
}
} );
postal.subscribe( {
channel : "postal.socket",
topic : "client.disconnect",
callback: function( data, env ) {
fsm.handle(env.topic, data );
}
} );
return fsm;
};
var x = module.exports = new TwitterSocketStats( 8002, 7000 );
x.on( "*", function ( evnt, data ) {
console.log( "FSM Event: " + evnt + " - " + JSON.stringify( [].slice.call( arguments, 1 ) ) );
} );
x.handle( "start" );

View file

@ -1,199 +0,0 @@
var _ = require( 'underscore' );
/*
machina.js
Author: Jim Cowart
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.1.0
*/
var slice = [].slice,
NEXT_TRANSITION = "transition",
NEXT_HANDLER = "handler",
transformEventListToObject = function ( eventList ) {
var obj = {};
_.each( eventList, function ( evntName ) {
obj[evntName] = [];
} );
return obj;
},
parseEventListeners = function ( evnts ) {
var obj = evnts;
if ( _.isArray( evnts ) ) {
obj = transformEventListToObject( evnts );
}
return obj;
},
utils = {
makeFsmNamespace : (function () {
var machinaCount = 0;
return function () {
return "fsm." + machinaCount++;
};
})(),
getDefaultOptions : function () {
return {
initialState : "uninitialized",
eventListeners : {
"*" : []
},
states : {},
eventQueue : [],
namespace : utils.makeFsmNamespace()
};
}
},
Fsm = function ( options ) {
var opt, initialState, defaults = utils.getDefaultOptions();
if ( options ) {
if ( options.eventListeners ) {
options.eventListeners = parseEventListeners( options.eventListeners );
}
if ( options.messaging ) {
options.messaging = _.extend( {}, defaults.messaging, options.messaging );
}
}
opt = _.extend( defaults, options || {} );
initialState = opt.initialState;
delete opt.initialState;
_.extend( this, opt );
this.state = undefined;
this._priorAction = "";
this._currentAction = "";
if ( initialState ) {
this.transition( initialState );
}
machina.fireEvent( "newFsm", this );
};
Fsm.prototype.fireEvent = function ( eventName ) {
var args = arguments;
_.each( this.eventListeners["*"], function ( callback ) {
try {
callback.apply( this, slice.call( args, 0 ) );
} catch ( exception ) {
if ( console && typeof console.log !== "undefined" ) {
console.log( exception.toString() );
}
}
} );
if ( this.eventListeners[eventName] ) {
_.each( this.eventListeners[eventName], function ( callback ) {
try {
callback.apply( this, slice.call( args, 1 ) );
} catch ( exception ) {
if ( console && typeof console.log !== "undefined" ) {
console.log( exception.toString() );
}
}
} );
}
};
Fsm.prototype.handle = function ( msgType ) {
// vars to avoid a "this." fest
var states = this.states, current = this.state, args = slice.call( arguments, 0 ), handlerName;
this.currentActionArgs = args;
if ( states[current] && (states[current][msgType] || states[current]["*"]) ) {
handlerName = states[current][msgType] ? msgType : "*";
this._currentAction = current + "." + handlerName;
this.fireEvent.apply( this, ["Handling"].concat( args ) );
states[current][handlerName].apply( this, args.slice( 1 ) );
this.fireEvent.apply( this, ["Handled"].concat( args ) );
this._priorAction = this._currentAction;
this._currentAction = "";
this.processQueue( NEXT_HANDLER );
}
else {
this.fireEvent.apply( this, ["NoHandler"].concat( args ) );
}
this.currentActionArgs = undefined;
};
Fsm.prototype.transition = function ( newState ) {
if ( this.states[newState] ) {
var oldState = this.state;
this.state = newState;
if ( this.states[newState]._onEnter ) {
this.states[newState]._onEnter.call( this );
}
this.fireEvent.apply( this, ["Transitioned", oldState, this.state ] );
this.processQueue( NEXT_TRANSITION );
return;
}
this.fireEvent.apply( this, ["InvalidState", this.state, newState ] );
};
Fsm.prototype.processQueue = function ( type ) {
var filterFn = type === NEXT_TRANSITION ?
function ( item ) {
return item.type === NEXT_TRANSITION && ((!item.untilState) || (item.untilState === this.state));
} :
function ( item ) {
return item.type === NEXT_HANDLER;
},
toProcess = _.filter( this.eventQueue, filterFn, this );
this.eventQueue = _.difference( this.eventQueue, toProcess );
_.each( toProcess, function ( item, index ) {
this.handle.apply( this, item.args );
}, this );
};
Fsm.prototype.deferUntilTransition = function ( stateName ) {
if ( this.currentActionArgs ) {
var queued = { type : NEXT_TRANSITION, untilState : stateName, args : this.currentActionArgs };
this.eventQueue.push( queued );
this.fireEvent.apply( this, [ "Deferred", this.state, queued ] );
}
};
Fsm.prototype.deferUntilNextHandler = function () {
if ( this.currentActionArgs ) {
var queued = { type : NEXT_TRANSITION, args : this.currentActionArgs };
this.eventQueue.push( queued );
this.fireEvent.apply( this, [ "Deferred", this.state, queued ] );
}
};
Fsm.prototype.on = function ( eventName, callback ) {
if ( !this.eventListeners[eventName] ) {
this.eventListeners[eventName] = [];
}
this.eventListeners[eventName].push( callback );
};
Fsm.prototype.off = function ( eventName, callback ) {
if ( this.eventListeners[eventName] ) {
this.eventListeners[eventName] = _.without( this.eventListeners[eventName], callback );
}
};
var machina = {
Fsm : Fsm,
bus : undefined,
utils : utils,
on : function ( eventName, callback ) {
if ( !this.eventListeners[eventName] ) {
this.eventListeners[eventName] = [];
}
this.eventListeners[eventName].push( callback );
},
off : function ( eventName, callback ) {
if ( this.eventListeners[eventName] ) {
this.eventListeners[eventName] = _.without( this.eventListeners[eventName], callback );
}
},
fireEvent : function ( eventName ) {
var i = 0, len, args = arguments, listeners = this.eventListeners[eventName];
if ( listeners && listeners.length ) {
_.each( listeners, function ( callback ) {
callback.apply( null, slice.call( args, 1 ) );
} );
}
},
eventListeners : {
newFsm : []
}
};
module.exports = machina;

View file

@ -1,20 +0,0 @@
module.exports = function ( target, searchChannel, statsChannel ) {
target.bus = {
subscriptions : [],
publishers : [
target.on( "newTweets", function ( data ) {
statsChannel.publish( { topic : "newTweets", data : data } );
} ),
target.on( "search.current", function ( data ) {
searchChannel.publish( { topic : "search.current", data : data } );
} ),
target.on( "search.nodata", function ( data ) {
searchChannel.publish( { topic : "search.nodata", data : data } );
} ),
target.on( "search.init", function ( data ) {
searchChannel.publish( { topic : "search.init", data : data } );
} )
]
};
return target;
};

View file

@ -1,23 +0,0 @@
module.exports = function ( target, searchChannel, statChannel ) {
target.bus = {
subscriptions : [
searchChannel.subscribe( "search.init", target.init ).withContext( target ),
searchChannel.subscribe( "newTweets", target.processNewTweets ).withContext( target ),
statChannel.subscribe( target.namespace + ".getLatest",
function ( data, env ) {
console.log( "GET LATEST FOR: " + target.namespace );
statChannel.publish( {
topic : target.namespace,
data : target.lastStats,
correlationId : env.correlationId
} )
} ).withContext( target )
],
publishers : [
target.on( target.namespace, function ( data ) {
statChannel.publish( { topic : target.namespace, data : data } );
} )
]
};
return target;
};

View file

@ -1,45 +0,0 @@
/*
machina.postal.js
Author: Jim Cowart
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.1.0
*/
module.exports = function ( postal, machina ) {
var bus = machina.bus = {
channels : {},
config : {
handlerChannelSuffix : "",
eventChannelSuffix : ".events"
},
wireHandlersToBus : function ( fsm, handlerChannel ) {
bus.channels[handlerChannel]._subscriptions.push(
bus.channels[handlerChannel].subscribe( "#", function ( data, envelope ) {
fsm.handle.call( fsm, envelope.topic, data, envelope );
} )
);
},
wireEventsToBus : function ( fsm, eventChannel ) {
var publisher = bus.channels[eventChannel].eventPublisher = function () {
try {
bus.channels[eventChannel].publish( { topic : arguments[0], data : arguments[1] || {} } );
} catch ( exception ) {
if ( console && typeof console.log !== "undefined" ) {
console.log( exception.toString() );
}
}
};
fsm.on( "*", publisher );
},
wireUp : function ( fsm ) {
var handlerChannel = fsm.namespace + bus.config.handlerChannelSuffix,
eventChannel = fsm.namespace + bus.config.eventChannelSuffix;
bus.channels[handlerChannel] = postal.channel( { channel : handlerChannel } );
bus.channels[eventChannel] = postal.channel( { channel : eventChannel } );
bus.channels[handlerChannel]._subscriptions = [];
bus.wireHandlersToBus( fsm, handlerChannel );
bus.wireEventsToBus( fsm, eventChannel );
}
};
machina.on( "newFsm", bus.wireUp );
};

View file

@ -1,85 +0,0 @@
module.exports = function ( _, postal ) {
var filters = [],
applyFilter = function ( filter, env ) {
var match = 0, possible = 0;
_.each( filter, function ( item, key ) {
if ( env[key] ) {
possible++;
if ( _.isRegExp( item ) && item.test( env[key] ) ) {
match++;
}
else if ( _.isObject( env[key] ) && !_.isArray( env[key] ) ) {
if ( applyFilter( item, env[key] ) ) {
match++;
}
}
else {
if ( _.isEqual( env[key], item ) ) {
match++;
}
}
}
} );
return match === possible;
};
// this returns a callback that, if invoked, removes the wireTap
var wireTap = postal.addWireTap( function ( data, envelope ) {
if ( !filters.length || _.any( filters, function ( filter ) {
return applyFilter( filter, envelope );
} ) ) {
if ( !JSON ) {
throw "This browser or environment does not provide JSON support";
}
try {
console.log( JSON.stringify( envelope ) );
}
catch ( exception ) {
try {
var env = _.extend( {}, envelope );
delete env.data;
console.log( JSON.stringify( env ) + "\n\t" + "JSON.stringify Error: " + exception.message );
}
catch ( ex ) {
console.log( "Unable to parse data to JSON: " + exception );
}
}
}
} );
postal.diagnostics = postal.diagnostics || {};
postal.diagnostics.console = {
clearFilters : function () {
filters = [];
},
removeFilter : function ( filter ) {
filters = _.filter( filters, function ( item ) {
return !_.isEqual( item, filter );
} );
},
addFilter : function ( constraint ) {
if ( !_.isArray( constraint ) ) {
constraint = [ constraint ];
}
_.each( constraint, function ( item ) {
if ( filters.length === 0 || !_.any( filters, function ( filter ) {
return _.isEqual( filter, item );
} ) ) {
filters.push( item );
}
} );
},
getCurrentFilters : function () {
return filters;
},
removeWireTap : function () {
if ( wireTap ) {
wireTap();
}
}
};
};

View file

@ -1,517 +0,0 @@
/*
postal
Author: Jim Cowart (http://freshbrewedcode.com/jimcowart)
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.7.3
*/
// This is the node.js version of postal.js
// If you need the web client lib version, go to http://github.com/ifandelse/postal.js
var _ = require( 'underscore' );
var DEFAULT_CHANNEL = "/",
DEFAULT_PRIORITY = 50,
DEFAULT_DISPOSEAFTER = 0,
SYSTEM_CHANNEL = "postal",
NO_OP = function () {
};
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 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 ChannelDefinition = function ( channelName, defaultTopic ) {
this.channel = channelName || DEFAULT_CHANNEL;
this._topic = defaultTopic || "";
};
ChannelDefinition.prototype = {
subscribe : function () {
var len = arguments.length;
if ( len === 1 ) {
return new SubscriptionDefinition( this.channel, this._topic, arguments[0] );
}
else if ( len === 2 ) {
return new SubscriptionDefinition( this.channel, arguments[0], arguments[1] );
}
},
publish : function ( obj ) {
var _obj = obj || {};
var envelope = {
channel : this.channel,
topic : this._topic,
data : _obj
};
// If this is an envelope....
if ( _obj.topic && _obj.data ) {
envelope = _obj;
envelope.channel = envelope.channel || this.channel;
}
envelope.timeStamp = new Date();
postal.configuration.bus.publish( envelope );
return envelope;
},
topic : function ( topic ) {
if ( topic === this._topic ) {
return this;
}
return new ChannelDefinition( this.channel, topic );
}
};
var SubscriptionDefinition = function ( channel, topic, callback ) {
this.channel = channel;
this.topic = topic;
this.callback = callback;
this.priority = DEFAULT_PRIORITY;
this.constraints = new Array( 0 );
this.maxCalls = DEFAULT_DISPOSEAFTER;
this.onHandled = NO_OP;
this.context = null;
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.created",
timeStamp : new Date(),
data : {
event : "subscription.created",
channel : channel,
topic : topic
}
} );
postal.configuration.bus.subscribe( this );
};
SubscriptionDefinition.prototype = {
unsubscribe : function () {
postal.configuration.bus.unsubscribe( this );
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.removed",
timeStamp : new Date(),
data : {
event : "subscription.removed",
channel : this.channel,
topic : this.topic
}
} );
},
defer : function () {
var fn = this.callback;
this.callback = function ( data ) {
setTimeout( fn, 0, data );
};
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 fn = this.onHandled;
var dispose = _.after( maxCalls, _.bind( function () {
this.unsubscribe( this );
}, this ) );
this.onHandled = function () {
fn.apply( this.context, arguments );
dispose();
};
return this;
},
distinctUntilChanged : function () {
this.withConstraint( new ConsecutiveDistinctPredicate() );
return this;
},
distinct : function () {
this.withConstraint( new DistinctPredicate() );
return this;
},
once: function() {
this.disposeAfter(1);
},
withConstraint : function ( predicate ) {
if ( !_.isFunction( predicate ) ) {
throw "Predicate constraint must be a function";
}
this.constraints.push( 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;
return this;
},
withDebounce : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = _.debounce( fn, milliseconds );
return this;
},
withDelay : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = function ( data ) {
setTimeout( function () {
fn( data );
}, milliseconds );
};
return this;
},
withPriority : function ( priority ) {
if ( _.isNaN( priority ) ) {
throw "Priority must be a number";
}
this.priority = priority;
postal.configuration.bus.changePriority( this );
return this;
},
withThrottle : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = _.throttle( fn, milliseconds );
return this;
},
subscribe : function ( callback ) {
this.callback = callback;
return this;
}
};
var bindingsResolver = {
cache : { },
compare : function ( binding, topic ) {
if ( this.cache[topic] && this.cache[topic][binding] ) {
return true;
}
var pattern = ("^" + binding.replace( /\./g, "\\." ) // escape actual periods
.replace( /\*/g, "[A-Z,a-z,0-9]*" ) // asterisks match any alpha-numeric 'word'
.replace( /#/g, ".*" ) + "$") // hash matches 'n' # of words (+ optional on start/end of topic)
.replace( "\\..*$", "(\\..*)*$" ) // fix end of topic matching on hash wildcards
.replace( "^.*\\.", "^(.*\\.)*" ); // fix beginning of topic matching on hash wildcards
var rgx = new RegExp( pattern );
var result = rgx.test( topic );
if ( result ) {
if ( !this.cache[topic] ) {
this.cache[topic] = {};
}
this.cache[topic][binding] = true;
}
return result;
},
reset : function () {
this.cache = {};
}
};
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 );
}
};
},
changePriority : function ( subDef ) {
var idx, found;
if ( this.subscriptions[subDef.channel] && this.subscriptions[subDef.channel][subDef.topic] ) {
this.subscriptions[subDef.channel][subDef.topic] = _.without( this.subscriptions[subDef.channel][subDef.topic], subDef );
idx = this.subscriptions[subDef.channel][subDef.topic].length - 1;
for ( ; idx >= 0; idx-- ) {
if ( this.subscriptions[subDef.channel][subDef.topic][idx].priority <= subDef.priority ) {
this.subscriptions[subDef.channel][subDef.topic].splice( idx + 1, 0, subDef );
found = true;
break;
}
}
if ( !found ) {
this.subscriptions[subDef.channel][subDef.topic].unshift( subDef );
}
}
},
publish : function ( envelope ) {
_.each( this.wireTaps, function ( tap ) {
tap( envelope.data, envelope );
} );
if ( this.subscriptions[envelope.channel] ) {
_.each( this.subscriptions[envelope.channel], function ( topic ) {
// TODO: research faster ways to handle this than _.clone
_.each( _.clone( topic ), function ( subDef ) {
if ( 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.onHandled();
}
}
}
} );
} );
}
},
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 idx, found, fn, 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] = new Array( 0 );
}
subs.push( subDef );
return subDef;
},
subscriptions : {},
wireTaps : new Array( 0 ),
unsubscribe : function ( config ) {
if ( this.subscriptions[config.channel][config.topic] ) {
var len = this.subscriptions[config.channel][config.topic].length,
idx = 0;
for ( ; idx < len; idx++ ) {
if ( this.subscriptions[config.channel][config.topic][idx] === config ) {
this.subscriptions[config.channel][config.topic].splice( idx, 1 );
break;
}
}
}
}
};
var publishPicker = {
"1" : function ( envelope ) {
if ( !envelope ) {
throw new Error( "publishing from the 'global' postal.publish call requires a valid envelope." );
}
envelope.channel = envelope.channel || DEFAULT_CHANNEL;
envelope.timeStamp = new Date();
postal.configuration.bus.publish( envelope );
return envelope;
},
"2" : function ( topic, data ) {
var envelope = { channel : DEFAULT_CHANNEL, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
},
"3" : function ( channel, topic, data ) {
var envelope = { channel : channel, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
}
},
channelPicker = {
"1" : function ( chn ) {
var channel = chn, topic, options = {};
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
}
else {
channel = chn.channel || DEFAULT_CHANNEL;
topic = chn.topic;
options = chn.options || options;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"2" : function ( chn, tpc ) {
var channel = chn, topic = tpc, options = {};
if ( Object.prototype.toString.call( tpc ) === "[object Object]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
options = tpc;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"3" : function ( channel, topic, options ) {
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
}
},
sessionInfo = {};
// save some setup time, albeit tiny
localBus.subscriptions[SYSTEM_CHANNEL] = {};
var postal = {
configuration : {
bus : localBus,
resolver : bindingsResolver,
DEFAULT_CHANNEL : DEFAULT_CHANNEL,
DEFAULT_PRIORITY : DEFAULT_PRIORITY,
DEFAULT_DISPOSEAFTER : DEFAULT_DISPOSEAFTER,
SYSTEM_CHANNEL : SYSTEM_CHANNEL
},
channelTypes : {
local : ChannelDefinition
},
channel : function () {
var len = arguments.length;
if ( channelPicker[len] ) {
return channelPicker[len].apply( this, arguments );
}
},
subscribe : function ( options ) {
var callback = options.callback,
topic = options.topic,
channel = options.channel || DEFAULT_CHANNEL;
return new SubscriptionDefinition( channel, topic, callback );
},
publish : function () {
var len = arguments.length;
if ( publishPicker[len] ) {
return publishPicker[len].apply( this, arguments );
}
},
addWireTap : function ( callback ) {
return this.configuration.bus.addWireTap( callback );
},
linkChannels : function ( sources, destinations ) {
var result = [];
if ( !_.isArray( sources ) ) {
sources = [sources];
}
if ( !_.isArray( destinations ) ) {
destinations = [destinations];
}
_.each( sources, function ( source ) {
var sourceTopic = source.topic || "#";
_.each( destinations, function ( destination ) {
var destChannel = destination.channel || DEFAULT_CHANNEL;
result.push(
postal.subscribe( {
channel : source.channel || DEFAULT_CHANNEL,
topic : source.topic || "#",
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;
},
utils : {
getSubscribersFor : function () {
var channel = arguments[ 0 ],
tpc = arguments[ 1 ],
result = [];
if ( arguments.length === 1 ) {
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = postal.configuration.DEFAULT_CHANNEL;
tpc = arguments[ 0 ];
}
else {
channel = arguments[ 0 ].channel || postal.configuration.DEFAULT_CHANNEL;
tpc = arguments[ 0 ].topic;
}
}
if ( postal.configuration.bus.subscriptions[ channel ] &&
postal.configuration.bus.subscriptions[ channel ].hasOwnProperty( tpc ) ) {
result = postal.configuration.bus.subscriptions[ channel ][ tpc ];
}
return result;
},
reset : function () {
postal.configuration.bus.reset();
postal.configuration.resolver.reset();
}
}
};
module.exports = postal;

View file

@ -1,249 +0,0 @@
var _ = require( 'underscore' ),
machina = require( '../machina.js' );
/*
A RemoteClientProxy instance is a finite state machine that manages
how a socket is used, and how it is (or isn't) allowed to interact
with the server-side instance of postal, depending on the state
related to the connection and the client on the other end of the socket.
*/
var RemoteClientProxy = function ( postal, socketClient, bridge ) {
// helper function to handle when a socket client wants
// to set up a subscription to server-side messages
var subscribe = function ( data ) {
var self = this;
if ( !self.subscriptions[ data.channel ] ) {
self.subscriptions[ data.channel ] = {};
}
if ( !self.subscriptions[ data.channel ][ data.topic ] ) {
self.subscriptions[ data.channel ][ data.topic ] =
postal.subscribe( {
channel : data.channel,
topic : data.topic,
callback : function ( d, e ) {
self.handle( "socketTransmit", d, e );
}
} ).withConstraint( function ( data, env ) {
if ( !env.correlationId ) {
return true;
}
return env.correlationId === self.sessionId;
} );
}
},
// The actual FSM managing the socketClient
fsm = new machina.Fsm( {
// handle to the socket client wrapper
socket : socketClient,
// object used to store (and lookup) subscriptions owned by this connection/client
subscriptions : {},
// method to allow a RemoteClientProxy to hand off state to another RemoteClientProxy
// as part of a migration
getClientState : function () {
return {
queuedMsgs : _.filter( this.eventQueue, function ( item ) {
return item.args[0] === "socketTransmit";
} )
};
},
sessionId : null,
states : {
// we start here after an explicit transition from uninitialized
// the only message we are interested in is "clientId" - in which
// the client tells us who they are, and we determine if we need
// to move through a migration first, or go directly to 'connected'
connecting : {
"*" : function () {
this.deferUntilTransition();
},
clientId : function ( session ) {
console.log( "SESSION STUFF: " + JSON.stringify( session ) );
this.sessionId = session.id;
if ( bridge.enableMigration && session.lastId && session.lastId !== session.id ) {
this.lastSessionId = session.lastId;
this.transition( "migrating" );
}
else {
this.transition( "connected" );
}
}
},
// we're going to get any relevant state from the old RemoteClientProxy
// and apply it to this instance. This includes the publishing of
// messages that got queued up at the old RemoteClientProxy.
migrating : {
// We tell the client to handle it's migration work (if any) first.
_onEnter : function () {
console.log( "TELLING CLIENT TO MIGRATE" )
this.socket.migrateClientSubscriptions();
},
// Once the client has completed its migration tasks, it will
// publish a message that triggers this handler. This is where we
// transfer queued messages from the old RemoteClientProxy, etc.
migrationComplete : function ( data ) {
if ( bridge.clientMap[ this.lastSessionId ] ) {
console.log( "LAST SESSION ID EXISTS" )
var priorState = bridge.clientMap[ this.lastSessionId ].getClientState();
_.each( priorState.queuedMsgs, function ( item ) {
this.eventQueue.unshift( item );
}, this );
}
postal.publish( {
channel : "postal.socket",
topic : "client.migrated",
data : {
sessionId : this.socket.id,
lastSessionId : this.lastSessionId
}
} );
this.transition( "connected" );
},
subscribe : subscribe,
"*" : function () {
this.deferUntilTransition();
}
},
// We're online and this is how to handle it....
connected : {
_onEnter : function () {
bridge.clientMap[ this.sessionId ] = this;
this.socket.confirmClientIdentified( this.sessionId );
},
disconnect : function () {
this.transition( "disconnected" );
},
subscribe : subscribe,
unsubscribe : function ( data ) {
if ( this.subscriptions[ data.channel ] && this.subscriptions[ data.channel ][ data.topic ] ) {
this.subscriptions[ data.channel ] && this.subscriptions[ data.channel ][ data.topic ].unsubscribe();
}
},
publish : function ( envelope ) {
postal.publish( envelope );
},
socketTransmit : function ( data, envelope ) {
this.socket.publish.call( this.socket, data, envelope );
}
},
disconnected : {
_onEnter : function () {
postal.publish( {
channel : "postal.socket",
topic : "client.disconnect",
data : {
sessionId : this.sessionId
}
} );
},
"*" : function () {
this.deferUntilTransition();
},
socketTransmit : function () {
this.deferUntilTransition( "connected" );
}
}
}
} );
// These are all the events from our Socket Client wrapper to which we need to listen.
_.each( [ "disconnect", "subscribe", "unsubscribe", "publish", "clientId", "migrationComplete" ], function ( evnt ) {
socketClient.on( evnt, function ( data ) {
fsm.handle( evnt, data );
} );
} );
// This is currently here for debugging purposes only
fsm.on( "*", function ( evnt, data ) {
var args = [].slice.call( arguments, 1 );
if ( evnt === "Deferred" || args[0] === "socketTransmit" ) {
console.log( "Socket FSM: " + evnt + " - " + JSON.stringify( args[0] ) );
}
else {
console.log( "Socket FSM: " + evnt + " - " + JSON.stringify( args ) );
}
} );
// every RemoteClientProxy gets a private subscription that can talk to the client
// apart from any application-level channels. This is a "utility" conduit.
fsm.subscriptions._direct = {
_private : postal.subscribe( {
channel : socketClient.id,
topic : "#",
callback : function ( d, e ) {
fsm.handle( "socketTransmit", d, e );
}
} )
};
// we explicitly start the FSM now that we've wired everything up, etc.
fsm.transition( "connecting" );
return fsm;
};
var PostalSocketHost = function ( postal, socketProvider ) {
var self = this;
// if enableMigration === true, then clients are allowed to migrate
// from an older session id to a new one. This can happen when the
// client's connection is lost, but the old session id is kept around
// in memory or local storage, etc. When the client reconnects, it
// will transmit the current and previous (if applicable) session ids.
// If this is set to true, then the migration will take place - which
// includes transferring existing subscriptions, as well as the delivery
// of messages sent to the old session id proxy after the client lost
// connection.
self.enableMigration = true;
self.channel = postal.channel( "postal.socket", "client.connect" );
// array of clients - confirmed or not
self.clients = [];
// once a client has been marked as "identified", they are added to
// this object, with the session id as the key.
self.clientMap = {};
// how to clean up a client
self.removeClient = function ( id ) {
if ( self.clientMap[ id ] ) {
_.each( self.clientMap[ id ].subscriptions, function ( channel ) {
_.each( channel, function ( sub ) {
console.log( "unsubscribing: " + sub.channel + " - " + sub.topic );
sub.unsubscribe();
} );
} );
delete self.clientMap[ id ];
}
self.clients = _.filter( self.clients, function ( item ) {
return item.sessionId !== id;
} );
};
self.provider = socketProvider;
// when our provider indicates a new socket has connected, we pass
// the socket wrapper to an fsm (RemoteClientProxy) to be managed by it.
self.provider.on( "connect", function ( socket ) {
self.clients.push( new RemoteClientProxy( postal, socket, self ) );
} );
// when a client disconnects, if enableMigration is true, we don't delete
// the old session until it has been used in a migration
self.channel.subscribe( "client.disconnect", function ( data, envelope ) {
if ( !self.enableMigration ) {
self.removeClient( data.sessionId );
}
} );
// if a session has been used in a migration and is no longer needed, remove it
self.channel.subscribe( "client.migrated", function ( data, envelope ) {
self.removeClient( data.lastSessionId );
} );
};
module.exports = PostalSocketHost;

View file

@ -1,113 +0,0 @@
var _ = require( 'underscore' ),
EventEmitter = require( 'events' ).EventEmitter,
util = require( 'util' );
module.exports = {
getSocketProvider : function ( postal, app, options ) {
/*
Socket client wrappers must provide the following:
1.) an "id" property which returns the session identifier for the connection
2.) EventEmitter's "on" method call (to subscribe callbacks to events)
3.) publish the following events (consumed by the postal socket host:
a.) disconnect - triggered when the socket disconnects.
b.) publish - triggered when the client at the other end of the socket
publishes a message. The callback(s) for this event should receive
a valid postal.js message envelope, and should ideally include a
correlationId that matches the client wrapper id.
{
channel: "someChannel",
topic: "some.topic",
data: {
greeting: "Oh, hai!"
},
timeStamp: "2012-04-17T07:11:14.731Z",
correlationId: "195371348624696856"
}
c.) unsubscribe - triggered when the client at the other end of the socket
specified that they want to unsubscribe a server-side subscription tied
to the socket. The callback(s) for this event should receive an object
that contains the channel name and topic to unsubscribe:
{ channel: "someChannel", topic: "some.topic" }
d.) subscribe - triggered when the client at the other end of the socket
specifies that they want to subscribe on the server-side, and have messages
forwarded over the socket down the client. The callback(s) for this event
should receive an object that contains the channel name and topic to which
the client wants to subscribe:
{ channel: "someChannel", topic: "some.topic" }
e.) clientId - triggered when the client sends a message containing the current
and previous (if one exists) session id. The callback(s) for this event
should receive an object similar to the following:
{ sessionId: "5072013211555684761", lastSessionId: "15075244651115973710" }
f.) migrationComplete - triggered when the client is done migrating any state from
an old session id to the current one. The callbacks do not need an arg provided.
*/
var SocketIoClient = function ( socket ) {
var self = this;
EventEmitter.call( self );
self.socket = socket;
Object.defineProperty( self, "id", {
get : function () {
return self.socket.id;
},
enumerable : true
} );
// wire up what to do when this socket disconnects
self.socket.on( 'disconnect', function () {
self.emit( "disconnect", self );
} );
// set up event hooks for postal specific events
_.each( [ "publish", "unsubscribe", "subscribe", "clientId", "migrationComplete" ], function ( evnt ) {
self.socket.on( "postal." + evnt, function ( data ) {
self.emit( evnt, data );
} );
} );
};
util.inherits( SocketIoClient, EventEmitter );
SocketIoClient.prototype.publish = function ( data, envelope ) {
this.socket.emit( "postal.socket.remote", envelope );
};
SocketIoClient.prototype.confirmClientIdentified = function ( uid ) {
this.socket.emit( "postal.socket.identified", { uid : uid } );
};
SocketIoClient.prototype.migrateClientSubscriptions = function () {
this.socket.emit( "postal.socket.migration", {} );
};
/*
The Socket Provider is a wrapper around the framework being
used to provide websocket functionality to postal.socket.
The implementation below wraps socket.io.
*/
var SocketIoProvider = function ( webApp, opt ) {
var opt = opt || {},
io = require( 'socket.io' ).listen( webApp ),
self = this;
io.set( 'log level', opt.ioLogLevel || 1 );
EventEmitter.call( self );
self.events = {
"connect" : []
};
// We listen for when socket.io detects a new socket connection.
// Then we take that socket instance and wrap it in our SocketIoClient
// wrapper. The PostalSocketHost receives an instance of this
// SocketIoProvider object and subscribes to the "connect" event.
io.sockets.on( "connection", function ( socket ) {
var _socket = new SocketIoClient( socket );
self.emit( "connect", _socket );
} );
};
util.inherits( SocketIoProvider, EventEmitter );
return new SocketIoProvider( app, options );
}
};

View file

@ -1,22 +0,0 @@
{
"author": "Jim Cowart <jim@ifandelse.com>",
"name": "postal.js-node.js-example",
"version": "0.1.0",
"repository": {
"type": "git",
"url": "git://github.com/ifandelse/postal.js.git"
},
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express" : "2.5.11",
"socket.io" : "0.9.10",
"underscore" : "1.3.3"
},
"optionalDependencies": {},
"engines": {
"node": "0.6.14"
}
}

View file

@ -1,19 +0,0 @@
var fs = require( 'fs' ),
path = require( 'path' ),
Adapter = require( './messaging/collector-adapter.js' ),
collectorPath = './collectors';
module.exports = {
load : function ( searchChannel, statsChannel ) {
return fs.readdirSync( collectorPath ).reduce( function ( accum, collector ) {
try {
var instance = new ( require( path.join( path.resolve( collectorPath ), collector ) ) )( collector.replace( /.js\b/, "" ) );
accum[ collector ] = new Adapter( instance, searchChannel, statsChannel );
}
catch ( ex ) {
console.log( "Unable to load '" + collector + "' - " + ex );
}
return accum;
}, {} );
}
};

View file

@ -1,129 +0,0 @@
var query = require( 'querystring' ),
http = require( 'http' );
var client = http.createClient( 80, "search.twitter.com" );
var TwitterSearch = function ( refreshInterval ) {
this.events = {};
this.timeoutFn = undefined;
this.searchPath = "/search.json";
this.refreshInterval = refreshInterval || 5000;
this.searchRegistry = {};
this.tweetRegistry = {};
this.defaultSearch = {};
this.nextSearch = undefined;
};
TwitterSearch.prototype = {
search : function ( searchTerm ) {
clearTimeout( this.timeoutFn );
this.raiseEvent( "search.init", searchTerm );
this.defaultSearch = {
q : searchTerm,
result_type : "recent",
rpp : 100
};
this.nextSearch = undefined;
this.searchRegistry = {};
this.tweetRegistry = {};
this.runSearch();
},
on : function ( eventName, callback ) {
if ( !this.events[ eventName ] ) {
this.events[ eventName ] = [];
}
this.events[ eventName ].push( callback );
return function () {
this.events[ eventName ] = _.without( this.events[ eventName ], callback );
}.bind( this );
},
raiseEvent : function ( eventName, data ) {
if ( this.events[ eventName ] ) {
this.events[ eventName ].forEach( function ( callback ) {
callback( data );
} );
}
},
buildUrl : function () {
var srchQry;
if ( this.nextSearch ) {
srchQry = this.nextSearch;
}
else {
if ( !this.defaultSearch.since_id ) {
delete this.defaultSearch.since_id;
}
srchQry = "?" + query.stringify( this.defaultSearch );
}
return this.searchPath + srchQry;
},
runSearch : function () {
var url = this.buildUrl();
if ( !this.searchRegistry[url] ) {
this.searchRegistry[url] = true;
this.raiseEvent( "search.current", { url : url } );
var request = client.request( "GET", url, {"host" : "search.twitter.com"} );
request.addListener( "response", function ( response ) {
var body = "";
response.addListener( "data", function ( data ) {
body += data;
} );
response.addListener( "end", function () {
this.onSearched( JSON.parse( body ) );
}.bind( this ) );
this.timeoutFn = setTimeout( this.runSearch.bind( this ), this.refreshInterval );
}.bind( this ) );
request.end();
}
else {
this.raiseEvent( "search.nodata", { url : url } );
this.timeoutFn = setTimeout( this.runSearch.bind( this ), this.refreshInterval );
}
},
onSearched : function ( payload ) {
// sift out errors
if ( !payload.error ) {
if ( payload.results.length > 0 ) {
this.processTweets( payload.results );
}
// do we have a next page option? If so, let's use it
if ( payload.next_page ) {
this.nextSearch = payload.next_page;
}
if ( payload.max_id ) {
this.defaultSearch.since_id = payload.max_id;
}
}
else {
this.nextSearch = undefined;
}
},
processTweets : function ( tweets ) {
var newTweets = tweets.filter( function ( t ) {
return !this.tweetRegistry[t];
}, this );
newTweets.forEach( function ( tweet ) {
if ( !this.tweetRegistry[tweet.id] ) {
this.tweetRegistry[tweet.id] = true;
}
// deal with the images that consistently fail from twitter...
if ( tweet.profile_image_url === "http://twitter.com/images/default_profile_normal.png" ||
tweet.profile_image_url === "http://static.twitter.com/images/default_profile_normal.png" ) {
tweet.profile_image_url = "templates/images/default_profile_1_normal.png";
}
}, this );
this.raiseEvent( "newTweets", newTweets );
}
};
module.exports = TwitterSearch;

View file

@ -1,12 +1,12 @@
$( function () {
// The world's simplest subscription
var channel = postal.channel( "Name.Changed" );
// This gets you a handle to the default postal channel...
var channel = postal.channel();
// subscribe
var subscription = channel.subscribe( function ( data ) {
var subscription = channel.subscribe( "Name.Changed", function ( data ) {
$( "#example1" ).html( "Name: " + data.name );
} );
// And someone publishes a first name change:
channel.publish( { name : "Dr. Who" } );
channel.publish( "Name.Changed", { name : "Dr. Who" } );
subscription.unsubscribe();
@ -14,14 +14,11 @@ $( function () {
// The # symbol represents "one word" in a topic (i.e - the text between two periods of a topic).
// By subscribing to "#.Changed", the binding will match
// Name.Changed & Location.Changed but *not* for Changed.Companion
var hashChannel = postal.channel( "#.Changed" ),
chgSubscription = hashChannel.subscribe( function ( data ) {
$( '<li>' + data.type + " Changed: " + data.value + '</li>' ).appendTo( "#example2" );
} );
postal.channel( "Name.Changed" )
.publish( { type : "Name", value : "John Smith" } );
postal.channel( "Location.Changed" )
.publish( { type : "Location", value : "Early 20th Century England" } );
var chgSubscription = channel.subscribe( "#.Changed", function ( data ) {
$( '<li>' + data.type + " Changed: " + data.value + '</li>' ).appendTo( "#example2" );
} );
channel.publish( "Name.Changed", { type : "Name", value : "John Smith" } );
channel.publish( "Location.Changed", { type : "Location", value : "Early 20th Century England" } );
chgSubscription.unsubscribe();
@ -29,120 +26,85 @@ $( function () {
// The * symbol represents any number of characters/words in a topic string.
// By subscribing to "DrWho.*.Changed", the binding will match
// DrWho.NinthDoctor.Companion.Changed & DrWho.Location.Changed but *not* Changed
var starChannel = postal.channel( "DrWho.*.Changed" ),
starSubscription = starChannel.subscribe( function ( data ) {
$( '<li>' + data.type + " Changed: " + data.value + '</li>' ).appendTo( "#example3" );
} );
postal.channel( "DrWho.NinthDoctor.Companion.Changed" )
.publish( { type : "Companion Name", value : "Rose" } );
postal.channel( "DrWho.TenthDoctor.Companion.Changed" )
.publish( { type : "Companion Name", value : "Martha" } );
postal.channel( "DrWho.Eleventh.Companion.Changed" )
.publish( { type : "Companion Name", value : "Amy" } );
postal.channel( "DrWho.Location.Changed" )
.publish( { type : "Location", value : "The Library" } );
postal.channel( "TheMaster.DrumBeat.Changed" )
.publish( { type : "DrumBeat", value : "This won't trigger any subscriptions" } );
postal.channel( "Changed" )
.publish( { type : "Useless", value : "This won't trigger any subscriptions either" } );
var starSubscription = channel.subscribe( "DrWho.*.Changed", function ( data ) {
$( '<li>' + data.type + " Changed: " + data.value + '</li>' ).appendTo( "#example3" );
} );
channel.publish( "DrWho.NinthDoctor.Companion.Changed", { type : "Companion Name", value : "Rose" } );
channel.publish( "DrWho.TenthDoctor.Companion.Changed", { type : "Companion Name", value : "Martha" } );
channel.publish( "DrWho.Eleventh.Companion.Changed", { type : "Companion Name", value : "Amy" } );
channel.publish( "DrWho.Location.Changed", { type : "Location", value : "The Library" } );
channel.publish( "TheMaster.DrumBeat.Changed", { type : "DrumBeat", value : "This won't trigger any subscriptions" } );
channel.publish( "Changed", { type : "Useless", value : "This won't trigger any subscriptions either" } );
starSubscription.unsubscribe();
// Applying distinctUntilChanged to a subscription
var dupChannel = postal.channel( "WeepingAngel.*" ),
dupSubscription = dupChannel.subscribe(
function ( data ) {
$( '<li>' + data.value + '</li>' ).appendTo( "#example4" );
} ).distinctUntilChanged();
postal.channel( "WeepingAngel.DontBlink" )
.publish( { value : "Don't Blink" } );
postal.channel( "WeepingAngel.DontBlink" )
.publish( { value : "Don't Blink" } );
postal.channel( "WeepingAngel.DontEvenBlink" )
.publish( { value : "Don't Even Blink" } );
postal.channel( "WeepingAngel.DontBlink" )
.publish( { value : "Don't Close Your Eyes" } );
postal.channel( "WeepingAngel.DontBlink" )
.publish( { value : "Don't Blink" } );
postal.channel( "WeepingAngel.DontBlink" )
.publish( { value : "Don't Blink" } );
var dupSubscription = channel.subscribe( "WeepingAngel.*",
function ( data ) {
$( '<li>' + data.value + '</li>' ).appendTo( "#example4" );
} ).distinctUntilChanged();
channel.publish( "WeepingAngel.DontBlink", { value : "Don't Blink" } );
channel.publish( "WeepingAngel.DontBlink", { value : "Don't Blink" } );
channel.publish( "WeepingAngel.DontEvenBlink", { value : "Don't Even Blink" } );
channel.publish( "WeepingAngel.DontBlink", { value : "Don't Close Your Eyes" } );
channel.publish( "WeepingAngel.DontBlink", { value : "Don't Blink" } );
channel.publish( "WeepingAngel.DontBlink", { value : "Don't Blink" } );
dupSubscription.unsubscribe();
// Using disposeAfter(X) to remove subscription automagically after X number of receives
var daChannel = postal.channel( "Donna.Noble.*" ),
daSubscription = daChannel.subscribe(
function ( data ) {
$( '<li>' + data.value + '</li>' ).appendTo( "#example5" );
} ).disposeAfter( 2 );
postal.channel( "Donna.Noble.ScreamingAgain" )
.publish( { value : "Donna Noble has left the library." } );
postal.channel( "Donna.Noble.ScreamingAgain" )
.publish( { value : "Donna Noble has left the library." } );
postal.channel( "Donna.Noble.ScreamingAgain" )
.publish( { value : "Donna Noble has left the library." } );
postal.channel( "Donna.Noble.ScreamingAgain" )
.publish( { value : "Donna Noble has left the library." } );
postal.channel( "Donna.Noble.ScreamingAgain" )
.publish( { value : "Donna Noble has left the library." } );
var daSubscription = channel.subscribe( "Donna.Noble.*",
function ( data ) {
$( '<li>' + data.value + '</li>' ).appendTo( "#example5" );
} ).disposeAfter( 2 );
channel.publish( "Donna.Noble.ScreamingAgain", { value : "Donna Noble has left the library." } );
channel.publish( "Donna.Noble.ScreamingAgain", { value : "Donna Noble has left the library." } );
channel.publish( "Donna.Noble.ScreamingAgain", { value : "Donna Noble has left the library." } );
channel.publish( "Donna.Noble.ScreamingAgain", { value : "Donna Noble has left the library." } );
channel.publish( "Donna.Noble.ScreamingAgain", { value : "Donna Noble has left the library." } );
daSubscription.unsubscribe();
// Using withConstraint to apply a predicate to the subscription
var drIsInTheTardis = false,
wcChannel = postal.channel( "Tardis.Depart" ),
wcSubscription = wcChannel.subscribe(
wcSubscription = channel.subscribe( "Tardis.Depart",
function ( data ) {
$( '<li>' + data.value + '</li>' ).appendTo( "#example6" );
} ).withConstraint( function () {
return drIsInTheTardis;
} );
postal.channel( "Tardis.Depart" )
.publish( { value : "Time for time travel....fantastic!" } );
postal.channel( "Tardis.Depart" )
.publish( { value : "Time for time travel....fantastic!" } );
channel.publish( "Tardis.Depart", { value : "Time for time travel....fantastic!" } );
channel.publish( "Tardis.Depart", { value : "Time for time travel....fantastic!" } );
drIsInTheTardis = true;
postal.channel( "Tardis.Depart" )
.publish( { value : "Time for time travel....fantastic!" } );
channel.publish( "Tardis.Depart", { value : "Time for time travel....fantastic!" } );
wcSubscription.unsubscribe();
// Using withContext to set the "this" context
var ctxChannel = postal.channel( "Dalek.Meet.CyberMen" ),
ctxSubscription = ctxChannel.subscribe(
function ( data ) {
$( '<li>' + data.value + '</li>' ).appendTo( this );
} ).withContext( $( "#example7" ) );
postal.channel( "Dalek.Meet.CyberMen" )
.publish( { value : "Exterminate!" } );
postal.channel( "Dalek.Meet.CyberMen" )
.publish( { value : "Delete!" } );
var ctxSubscription = channel.subscribe( "Dalek.Meet.CyberMen",
function ( data ) {
$( '<li>' + data.value + '</li>' ).appendTo( this );
} ).withContext( $( "#example7" ) );
channel.publish( "Dalek.Meet.CyberMen", { value : "Exterminate!" } );
channel.publish( "Dalek.Meet.CyberMen", { value : "Delete!" } );
ctxSubscription.unsubscribe();
// Using withDelay() to delay the subscription evaluation
var wdChannel = postal.channel( "He.Will.Knock.Four.Times" ),
wdSubscription = wdChannel.subscribe(
function ( data ) {
$( '<li>' + data.value + '</li>' ).appendTo( $( "#example8" ) );
} ).withDelay( 5000 );
postal.channel( "He.Will.Knock.Four.Times" )
.publish( { value : "Knock!" } );
postal.channel( "He.Will.Knock.Four.Times" )
.publish( { value : "Knock!" } );
postal.channel( "He.Will.Knock.Four.Times" )
.publish( { value : "Knock!" } );
postal.channel( "He.Will.Knock.Four.Times" )
.publish( { value : "Knock!" } );
var wdSubscription = channel.subscribe( "He.Will.Knock.Four.Times",
function ( data ) {
$( '<li>' + data.value + '</li>' ).appendTo( $( "#example8" ) );
} ).withDelay( 5000 );
channel.publish( "He.Will.Knock.Four.Times", { value : "Knock!" } );
channel.publish( "He.Will.Knock.Four.Times", { value : "Knock!" } );
channel.publish( "He.Will.Knock.Four.Times", { value : "Knock!" } );
channel.publish( "He.Will.Knock.Four.Times", { value : "Knock!" } );
wdSubscription.unsubscribe();
// Using distinct() to ignore duplicate messages
var revealChannel = postal.channel('detect.cylon'),
revealSubscription = revealChannel.subscribe(function (who) {
$('<li></li>').text(who.name).appendTo($('#example9'));
}).distinct();
postal.channel('detect.cylon').publish({name: 'Boomer'});
postal.channel('detect.cylon').publish({name: 'Saul Tigh'});
postal.channel('detect.cylon').publish({name: 'William Adama'});
postal.channel('detect.cylon').publish({name: 'Helo'});
//ignored!
postal.channel('detect.cylon').publish({name: 'Boomer'});
postal.channel('detect.cylon').publish({name: 'Felix Gaeta'});
//ignored!
postal.channel('detect.cylon').publish({name: 'William Adama'});
var revealSubscription = channel.subscribe( 'detect.cylon',function ( who ) {
$( '<li></li>' ).text( who.name ).appendTo( $( '#example9' ) );
} ).distinct();
channel.publish( 'detect.cylon', {name : 'Boomer'} );
channel.publish( 'detect.cylon', {name : 'Saul Tigh'} );
channel.publish( 'detect.cylon', {name : 'William Adama'} );
channel.publish( 'detect.cylon', {name : 'Helo'} );
channel.publish( 'detect.cylon', {name : 'Boomer'} ); //ignored!
channel.publish( 'detect.cylon', {name : 'Felix Gaeta'} );
channel.publish( 'detect.cylon', {name : 'William Adama'} );//ignored!
} );

View file

@ -54,44 +54,22 @@
return isDistinct;
};
};
var ChannelDefinition = function ( channelName, defaultTopic ) {
var ChannelDefinition = function ( channelName ) {
this.channel = channelName || DEFAULT_CHANNEL;
this._topic = defaultTopic || "";
};
ChannelDefinition.prototype = {
subscribe : function () {
var len = arguments.length;
if ( len === 1 ) {
return new SubscriptionDefinition( this.channel, this._topic, arguments[0] );
}
else if ( len === 2 ) {
return new SubscriptionDefinition( this.channel, arguments[0], arguments[1] );
}
return arguments.length === 1 ?
new SubscriptionDefinition( this.channel, arguments[0].topic, arguments[0].callback ) :
new SubscriptionDefinition( this.channel, arguments[0], arguments[1] );
},
publish : function ( obj ) {
var _obj = obj || {};
var envelope = {
channel : this.channel,
topic : this._topic,
data : _obj
};
// If this is an envelope....
if ( _obj.topic && _obj.data ) {
envelope = _obj;
envelope.channel = envelope.channel || this.channel;
}
envelope.timeStamp = new Date();
publish : function () {
var envelope = arguments.length === 1 ? arguments[0] : { topic: arguments[0], data: arguments[1] };
envelope.channel = this.channel;
postal.configuration.bus.publish( envelope );
return envelope;
},
topic : function ( topic ) {
if ( topic === this._topic ) {
return this;
}
return new ChannelDefinition( this.channel, topic );
}
};
@ -302,6 +280,7 @@
},
publish : function ( envelope ) {
envelope.timeStamp = new Date();
_.each( this.wireTaps, function ( tap ) {
tap( envelope.data, envelope );
} );
@ -371,55 +350,7 @@
}
};
var publishPicker = {
"1" : function ( envelope ) {
if ( !envelope ) {
throw new Error( "publishing from the 'global' postal.publish call requires a valid envelope." );
}
envelope.channel = envelope.channel || DEFAULT_CHANNEL;
envelope.timeStamp = new Date();
postal.configuration.bus.publish( envelope );
return envelope;
},
"2" : function ( topic, data ) {
var envelope = { channel : DEFAULT_CHANNEL, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
},
"3" : function ( channel, topic, data ) {
var envelope = { channel : channel, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
}
},
channelPicker = {
"1" : function ( chn ) {
var channel = chn, topic, options = {};
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
}
else {
channel = chn.channel || DEFAULT_CHANNEL;
topic = chn.topic;
options = chn.options || options;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"2" : function ( chn, tpc ) {
var channel = chn, topic = tpc, options = {};
if ( Object.prototype.toString.call( tpc ) === "[object Object]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
options = tpc;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"3" : function ( channel, topic, options ) {
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
}
},
sessionInfo = {};
var sessionInfo = {};
// save some setup time, albeit tiny
localBus.subscriptions[SYSTEM_CHANNEL] = {};
@ -434,29 +365,22 @@
SYSTEM_CHANNEL : SYSTEM_CHANNEL
},
channelTypes : {
local : ChannelDefinition
},
ChannelDefinition : ChannelDefinition,
channel : function () {
var len = arguments.length;
if ( channelPicker[len] ) {
return channelPicker[len].apply( this, arguments );
}
SubscriptionDefinition: SubscriptionDefinition,
channel : function ( channelName ) {
return new ChannelDefinition( channelName );
},
subscribe : function ( options ) {
var callback = options.callback,
topic = options.topic,
channel = options.channel || DEFAULT_CHANNEL;
return new SubscriptionDefinition( channel, topic, callback );
return new SubscriptionDefinition( options.channel || DEFAULT_CHANNEL, options.topic, options.callback );
},
publish : function () {
var len = arguments.length;
if ( publishPicker[len] ) {
return publishPicker[len].apply( this, arguments );
}
publish : function ( envelope ) {
envelope.channel = envelope.channel || DEFAULT_CHANNEL;
postal.configuration.bus.publish( envelope );
return envelope;
},
addWireTap : function ( callback ) {

File diff suppressed because one or more lines are too long

View file

@ -1,53 +0,0 @@
/*
postal
Author: Jim Cowart (http://freshbrewedcode.com/jimcowart)
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.7.3
*/
/*
postal
Author: Jim Cowart (http://freshbrewedcode.com/jimcowart)
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.7.3
*/
(function ( root, doc, factory ) {
if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define( ["postal"], function ( postal ) {
return factory( postal, root, doc );
} );
} else {
// Browser globals
factory( root.postal, root, doc );
}
}( this, document, function ( postal, global, document, undefined ) {
var classicBindingsResolver = {
cache : { },
compare : function ( binding, topic ) {
if ( this.cache[topic] && this.cache[topic][binding] ) {
return true;
}
// binding.replace(/\./g,"\\.") // escape actual periods
// .replace(/\*/g, ".*") // asterisks match any value
// .replace(/#/g, "[A-Z,a-z,0-9]*"); // hash matches any alpha-numeric 'word'
var rgx = new RegExp( "^" + binding.replace( /\./g, "\\." ).replace( /\*/g, ".*" ).replace( /#/g, "[A-Z,a-z,0-9]*" ) + "$" ),
result = rgx.test( topic );
if ( result ) {
if ( !this.cache[topic] ) {
this.cache[topic] = {};
}
this.cache[topic][binding] = true;
}
return result;
},
reset : function () {
this.cache = {};
}
};
postal.configuration.resolver = classicBindingsResolver;
} ));

View file

@ -1,7 +0,0 @@
/*
postal
Author: Jim Cowart (http://freshbrewedcode.com/jimcowart)
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.7.3
*/
(function(e,t,n){typeof define=="function"&&define.amd?define(["postal"],function(r){return n(r,e,t)}):n(e.postal,e,t)})(this,document,function(e,t,n,r){var i={cache:{},compare:function(e,t){if(this.cache[t]&&this.cache[t][e])return!0;var n=new RegExp("^"+e.replace(/\./g,"\\.").replace(/\*/g,".*").replace(/#/g,"[A-Z,a-z,0-9]*")+"$"),r=n.test(t);return r&&(this.cache[t]||(this.cache[t]={}),this.cache[t][e]=!0),r},reset:function(){this.cache={}}};e.configuration.resolver=i})

File diff suppressed because one or more lines are too long

6
lib/classic-resolver.js Normal file
View file

@ -0,0 +1,6 @@
/*
postal
Author: Jim Cowart (http://freshbrewedcode.com/jimcowart)
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.8.0
*/

6
lib/classic-resolver.min.js vendored Normal file
View file

@ -0,0 +1,6 @@
/*
postal
Author: Jim Cowart (http://freshbrewedcode.com/jimcowart)
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.8.0
*/

View file

@ -1,45 +0,0 @@
/*
postal
Author: Jim Cowart (http://freshbrewedcode.com/jimcowart)
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.7.3
*/
/*
postal
Author: Jim Cowart (http://freshbrewedcode.com/jimcowart)
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.7.3
*/
var classicBindingsResolver = {
cache : { },
compare : function ( binding, topic ) {
if ( this.cache[topic] && this.cache[topic][binding] ) {
return true;
}
// binding.replace(/\./g,"\\.") // escape actual periods
// .replace(/\*/g, ".*") // asterisks match any value
// .replace(/#/g, "[A-Z,a-z,0-9]*"); // hash matches any alpha-numeric 'word'
var rgx = new RegExp( "^" + binding.replace( /\./g, "\\." ).replace( /\*/g, ".*" ).replace( /#/g, "[A-Z,a-z,0-9]*" ) + "$" ),
result = rgx.test( topic );
if ( result ) {
if ( !this.cache[topic] ) {
this.cache[topic] = {};
}
this.cache[topic][binding] = true;
}
return result;
},
reset : function () {
this.cache = {};
}
};
module.exports = {
configure: function(postal) {
postal.configuration.resolver = classicBindingsResolver;
return postal;
}
};

View file

@ -1,517 +0,0 @@
/*
postal
Author: Jim Cowart (http://freshbrewedcode.com/jimcowart)
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.7.3
*/
// This is the node.js version of postal.js
// If you need the web client lib version, go to http://github.com/ifandelse/postal.js
var _ = require( 'underscore' );
var DEFAULT_CHANNEL = "/",
DEFAULT_PRIORITY = 50,
DEFAULT_DISPOSEAFTER = 0,
SYSTEM_CHANNEL = "postal",
NO_OP = function () {
};
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 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 ChannelDefinition = function ( channelName, defaultTopic ) {
this.channel = channelName || DEFAULT_CHANNEL;
this._topic = defaultTopic || "";
};
ChannelDefinition.prototype = {
subscribe : function () {
var len = arguments.length;
if ( len === 1 ) {
return new SubscriptionDefinition( this.channel, this._topic, arguments[0] );
}
else if ( len === 2 ) {
return new SubscriptionDefinition( this.channel, arguments[0], arguments[1] );
}
},
publish : function ( obj ) {
var _obj = obj || {};
var envelope = {
channel : this.channel,
topic : this._topic,
data : _obj
};
// If this is an envelope....
if ( _obj.topic && _obj.data ) {
envelope = _obj;
envelope.channel = envelope.channel || this.channel;
}
envelope.timeStamp = new Date();
postal.configuration.bus.publish( envelope );
return envelope;
},
topic : function ( topic ) {
if ( topic === this._topic ) {
return this;
}
return new ChannelDefinition( this.channel, topic );
}
};
var SubscriptionDefinition = function ( channel, topic, callback ) {
this.channel = channel;
this.topic = topic;
this.callback = callback;
this.priority = DEFAULT_PRIORITY;
this.constraints = new Array( 0 );
this.maxCalls = DEFAULT_DISPOSEAFTER;
this.onHandled = NO_OP;
this.context = null;
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.created",
timeStamp : new Date(),
data : {
event : "subscription.created",
channel : channel,
topic : topic
}
} );
postal.configuration.bus.subscribe( this );
};
SubscriptionDefinition.prototype = {
unsubscribe : function () {
postal.configuration.bus.unsubscribe( this );
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.removed",
timeStamp : new Date(),
data : {
event : "subscription.removed",
channel : this.channel,
topic : this.topic
}
} );
},
defer : function () {
var fn = this.callback;
this.callback = function ( data ) {
setTimeout( fn, 0, data );
};
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 fn = this.onHandled;
var dispose = _.after( maxCalls, _.bind( function () {
this.unsubscribe( this );
}, this ) );
this.onHandled = function () {
fn.apply( this.context, arguments );
dispose();
};
return this;
},
distinctUntilChanged : function () {
this.withConstraint( new ConsecutiveDistinctPredicate() );
return this;
},
distinct : function () {
this.withConstraint( new DistinctPredicate() );
return this;
},
once: function() {
this.disposeAfter(1);
},
withConstraint : function ( predicate ) {
if ( !_.isFunction( predicate ) ) {
throw "Predicate constraint must be a function";
}
this.constraints.push( 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;
return this;
},
withDebounce : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = _.debounce( fn, milliseconds );
return this;
},
withDelay : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = function ( data ) {
setTimeout( function () {
fn( data );
}, milliseconds );
};
return this;
},
withPriority : function ( priority ) {
if ( _.isNaN( priority ) ) {
throw "Priority must be a number";
}
this.priority = priority;
postal.configuration.bus.changePriority( this );
return this;
},
withThrottle : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = _.throttle( fn, milliseconds );
return this;
},
subscribe : function ( callback ) {
this.callback = callback;
return this;
}
};
var bindingsResolver = {
cache : { },
compare : function ( binding, topic ) {
if ( this.cache[topic] && this.cache[topic][binding] ) {
return true;
}
var pattern = ("^" + binding.replace( /\./g, "\\." ) // escape actual periods
.replace( /\*/g, "[A-Z,a-z,0-9]*" ) // asterisks match any alpha-numeric 'word'
.replace( /#/g, ".*" ) + "$") // hash matches 'n' # of words (+ optional on start/end of topic)
.replace( "\\..*$", "(\\..*)*$" ) // fix end of topic matching on hash wildcards
.replace( "^.*\\.", "^(.*\\.)*" ); // fix beginning of topic matching on hash wildcards
var rgx = new RegExp( pattern );
var result = rgx.test( topic );
if ( result ) {
if ( !this.cache[topic] ) {
this.cache[topic] = {};
}
this.cache[topic][binding] = true;
}
return result;
},
reset : function () {
this.cache = {};
}
};
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 );
}
};
},
changePriority : function ( subDef ) {
var idx, found;
if ( this.subscriptions[subDef.channel] && this.subscriptions[subDef.channel][subDef.topic] ) {
this.subscriptions[subDef.channel][subDef.topic] = _.without( this.subscriptions[subDef.channel][subDef.topic], subDef );
idx = this.subscriptions[subDef.channel][subDef.topic].length - 1;
for ( ; idx >= 0; idx-- ) {
if ( this.subscriptions[subDef.channel][subDef.topic][idx].priority <= subDef.priority ) {
this.subscriptions[subDef.channel][subDef.topic].splice( idx + 1, 0, subDef );
found = true;
break;
}
}
if ( !found ) {
this.subscriptions[subDef.channel][subDef.topic].unshift( subDef );
}
}
},
publish : function ( envelope ) {
_.each( this.wireTaps, function ( tap ) {
tap( envelope.data, envelope );
} );
if ( this.subscriptions[envelope.channel] ) {
_.each( this.subscriptions[envelope.channel], function ( topic ) {
// TODO: research faster ways to handle this than _.clone
_.each( _.clone( topic ), function ( subDef ) {
if ( 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.onHandled();
}
}
}
} );
} );
}
},
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 idx, found, fn, 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] = new Array( 0 );
}
subs.push( subDef );
return subDef;
},
subscriptions : {},
wireTaps : new Array( 0 ),
unsubscribe : function ( config ) {
if ( this.subscriptions[config.channel][config.topic] ) {
var len = this.subscriptions[config.channel][config.topic].length,
idx = 0;
for ( ; idx < len; idx++ ) {
if ( this.subscriptions[config.channel][config.topic][idx] === config ) {
this.subscriptions[config.channel][config.topic].splice( idx, 1 );
break;
}
}
}
}
};
var publishPicker = {
"1" : function ( envelope ) {
if ( !envelope ) {
throw new Error( "publishing from the 'global' postal.publish call requires a valid envelope." );
}
envelope.channel = envelope.channel || DEFAULT_CHANNEL;
envelope.timeStamp = new Date();
postal.configuration.bus.publish( envelope );
return envelope;
},
"2" : function ( topic, data ) {
var envelope = { channel : DEFAULT_CHANNEL, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
},
"3" : function ( channel, topic, data ) {
var envelope = { channel : channel, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
}
},
channelPicker = {
"1" : function ( chn ) {
var channel = chn, topic, options = {};
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
}
else {
channel = chn.channel || DEFAULT_CHANNEL;
topic = chn.topic;
options = chn.options || options;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"2" : function ( chn, tpc ) {
var channel = chn, topic = tpc, options = {};
if ( Object.prototype.toString.call( tpc ) === "[object Object]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
options = tpc;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"3" : function ( channel, topic, options ) {
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
}
},
sessionInfo = {};
// save some setup time, albeit tiny
localBus.subscriptions[SYSTEM_CHANNEL] = {};
var postal = {
configuration : {
bus : localBus,
resolver : bindingsResolver,
DEFAULT_CHANNEL : DEFAULT_CHANNEL,
DEFAULT_PRIORITY : DEFAULT_PRIORITY,
DEFAULT_DISPOSEAFTER : DEFAULT_DISPOSEAFTER,
SYSTEM_CHANNEL : SYSTEM_CHANNEL
},
channelTypes : {
local : ChannelDefinition
},
channel : function () {
var len = arguments.length;
if ( channelPicker[len] ) {
return channelPicker[len].apply( this, arguments );
}
},
subscribe : function ( options ) {
var callback = options.callback,
topic = options.topic,
channel = options.channel || DEFAULT_CHANNEL;
return new SubscriptionDefinition( channel, topic, callback );
},
publish : function () {
var len = arguments.length;
if ( publishPicker[len] ) {
return publishPicker[len].apply( this, arguments );
}
},
addWireTap : function ( callback ) {
return this.configuration.bus.addWireTap( callback );
},
linkChannels : function ( sources, destinations ) {
var result = [];
if ( !_.isArray( sources ) ) {
sources = [sources];
}
if ( !_.isArray( destinations ) ) {
destinations = [destinations];
}
_.each( sources, function ( source ) {
var sourceTopic = source.topic || "#";
_.each( destinations, function ( destination ) {
var destChannel = destination.channel || DEFAULT_CHANNEL;
result.push(
postal.subscribe( {
channel : source.channel || DEFAULT_CHANNEL,
topic : source.topic || "#",
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;
},
utils : {
getSubscribersFor : function () {
var channel = arguments[ 0 ],
tpc = arguments[ 1 ],
result = [];
if ( arguments.length === 1 ) {
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = postal.configuration.DEFAULT_CHANNEL;
tpc = arguments[ 0 ];
}
else {
channel = arguments[ 0 ].channel || postal.configuration.DEFAULT_CHANNEL;
tpc = arguments[ 0 ].topic;
}
}
if ( postal.configuration.bus.subscriptions[ channel ] &&
postal.configuration.bus.subscriptions[ channel ].hasOwnProperty( tpc ) ) {
result = postal.configuration.bus.subscriptions[ channel ][ tpc ];
}
return result;
},
reset : function () {
postal.configuration.bus.reset();
postal.configuration.resolver.reset();
}
}
};
module.exports = postal;

View file

@ -2,27 +2,29 @@
postal
Author: Jim Cowart (http://freshbrewedcode.com/jimcowart)
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.7.3
Version 0.8.0
*/
(function ( root, factory ) {
if ( typeof define === "function" && define.amd ) {
if ( typeof module === "object" && module.exports ) {
// Node, or CommonJS-Like environments
module.exports = function ( _ ) {
_ = _ || require( "underscore" );
return factory( _ );
}
} else if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define( ["underscore"], function ( _ ) {
define( ["."], function ( _ ) {
return factory( _, root );
} );
} else {
// Browser globals
factory( root._, root );
root.postal = factory( root._, root );
}
}( this, function ( _, global, undefined ) {
var DEFAULT_CHANNEL = "/",
DEFAULT_PRIORITY = 50,
DEFAULT_DISPOSEAFTER = 0,
SYSTEM_CHANNEL = "postal",
NO_OP = function () {
};
SYSTEM_CHANNEL = "postal";
var ConsecutiveDistinctPredicate = function () {
var previous;
return function ( data ) {
@ -39,84 +41,52 @@
};
};
var DistinctPredicate = function () {
var previous = [];
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;
};
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 ChannelDefinition = function ( channelName, defaultTopic ) {
var ChannelDefinition = function ( channelName ) {
this.channel = channelName || DEFAULT_CHANNEL;
this._topic = defaultTopic || "";
};
ChannelDefinition.prototype = {
subscribe : function () {
var len = arguments.length;
if ( len === 1 ) {
return new SubscriptionDefinition( this.channel, this._topic, arguments[0] );
}
else if ( len === 2 ) {
return new SubscriptionDefinition( this.channel, arguments[0], arguments[1] );
}
},
publish : function ( obj ) {
var _obj = obj || {};
var envelope = {
channel : this.channel,
topic : this._topic,
data : _obj
};
// If this is an envelope....
if ( _obj.topic && _obj.data ) {
envelope = _obj;
envelope.channel = envelope.channel || this.channel;
}
envelope.timeStamp = new Date();
postal.configuration.bus.publish( envelope );
return envelope;
},
topic : function ( topic ) {
if ( topic === this._topic ) {
return this;
}
return new ChannelDefinition( this.channel, topic );
}
ChannelDefinition.prototype.subscribe = function () {
return 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 ? arguments[0] : { topic : arguments[0], data : arguments[1] };
envelope.channel = this.channel;
return postal.configuration.bus.publish( envelope );
};
var SubscriptionDefinition = function ( channel, topic, callback ) {
this.channel = channel;
this.topic = topic;
this.callback = callback;
this.priority = DEFAULT_PRIORITY;
this.constraints = new Array( 0 );
this.maxCalls = DEFAULT_DISPOSEAFTER;
this.onHandled = NO_OP;
this.constraints = [];
this.context = null;
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.created",
timeStamp : new Date(),
data : {
event : "subscription.created",
channel : channel,
topic : topic
}
} );
postal.configuration.bus.subscribe( this );
};
SubscriptionDefinition.prototype = {
@ -125,7 +95,6 @@
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.removed",
timeStamp : new Date(),
data : {
event : "subscription.removed",
channel : this.channel,
@ -146,20 +115,19 @@
if ( _.isNaN( maxCalls ) || maxCalls <= 0 ) {
throw "The value provided to disposeAfter (maxCalls) must be a number greater than zero.";
}
var fn = this.onHandled;
var fn = this.callback;
var dispose = _.after( maxCalls, _.bind( function () {
this.unsubscribe( this );
this.unsubscribe();
}, this ) );
this.onHandled = function () {
this.callback = function () {
fn.apply( this.context, arguments );
dispose();
};
return this;
},
distinctUntilChanged : function () {
distinctUntilChanged : function () {
this.withConstraint( new ConsecutiveDistinctPredicate() );
return this;
},
@ -169,9 +137,9 @@
return this;
},
once: function() {
this.disposeAfter(1);
},
once : function () {
this.disposeAfter( 1 );
},
withConstraint : function ( predicate ) {
if ( !_.isFunction( predicate ) ) {
@ -218,15 +186,6 @@
return this;
},
withPriority : function ( priority ) {
if ( _.isNaN( priority ) ) {
throw "Priority must be a number";
}
this.priority = priority;
postal.configuration.bus.changePriority( this );
return this;
},
withThrottle : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
@ -241,7 +200,6 @@
return this;
}
};
var bindingsResolver = {
cache : { },
@ -250,10 +208,10 @@
return true;
}
var pattern = ("^" + binding.replace( /\./g, "\\." ) // escape actual periods
.replace( /\*/g, "[A-Z,a-z,0-9]*" ) // asterisks match any alpha-numeric 'word'
.replace( /#/g, ".*" ) + "$") // hash matches 'n' # of words (+ optional on start/end of topic)
.replace( "\\..*$", "(\\..*)*$" ) // fix end of topic matching on hash wildcards
.replace( "^.*\\.", "^(.*\\.)*" ); // fix beginning of topic matching on hash wildcards
.replace( /\*/g, "[A-Z,a-z,0-9]*" ) // asterisks match any alpha-numeric 'word'
.replace( /#/g, ".*" ) + "$") // hash matches 'n' # of words (+ optional on start/end of topic)
.replace( "\\..*$", "(\\..*)*$" ) // fix end of topic matching on hash wildcards
.replace( "^.*\\.", "^(.*\\.)*" ); // fix beginning of topic matching on hash wildcards
var rgx = new RegExp( pattern );
var result = rgx.test( topic );
if ( result ) {
@ -269,9 +227,7 @@
this.cache = {};
}
};
var localBus = {
addWireTap : function ( callback ) {
var self = this;
self.wireTaps.push( callback );
@ -283,47 +239,28 @@
};
},
changePriority : function ( subDef ) {
var idx, found;
if ( this.subscriptions[subDef.channel] && this.subscriptions[subDef.channel][subDef.topic] ) {
this.subscriptions[subDef.channel][subDef.topic] = _.without( this.subscriptions[subDef.channel][subDef.topic], subDef );
idx = this.subscriptions[subDef.channel][subDef.topic].length - 1;
for ( ; idx >= 0; idx-- ) {
if ( this.subscriptions[subDef.channel][subDef.topic][idx].priority <= subDef.priority ) {
this.subscriptions[subDef.channel][subDef.topic].splice( idx + 1, 0, subDef );
found = true;
break;
}
}
if ( !found ) {
this.subscriptions[subDef.channel][subDef.topic].unshift( subDef );
}
}
},
publish : function ( envelope ) {
envelope.timeStamp = new Date();
_.each( this.wireTaps, function ( tap ) {
tap( envelope.data, envelope );
} );
if ( this.subscriptions[envelope.channel] ) {
_.each( this.subscriptions[envelope.channel], function ( topic ) {
// TODO: research faster ways to handle this than _.clone
_.each( _.clone( topic ), function ( subDef ) {
if ( postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) {
if ( _.all( subDef.constraints, function ( constraint ) {
return constraint.call(subDef.context, envelope.data, envelope );
return constraint.call( subDef.context, envelope.data, envelope );
} ) ) {
if ( typeof subDef.callback === 'function' ) {
subDef.callback.call( subDef.context, envelope.data, envelope );
subDef.onHandled();
}
}
}
} );
} );
}
return envelope;
},
reset : function () {
@ -341,13 +278,12 @@
subscribe : function ( subDef ) {
var idx, found, fn, 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] = new Array( 0 );
subs = this.subscriptions[subDef.channel][subDef.topic] = [];
}
subs.push( subDef );
return subDef;
@ -355,7 +291,7 @@
subscriptions : {},
wireTaps : new Array( 0 ),
wireTaps : [],
unsubscribe : function ( config ) {
if ( this.subscriptions[config.channel][config.topic] ) {
@ -370,57 +306,6 @@
}
}
};
var publishPicker = {
"1" : function ( envelope ) {
if ( !envelope ) {
throw new Error( "publishing from the 'global' postal.publish call requires a valid envelope." );
}
envelope.channel = envelope.channel || DEFAULT_CHANNEL;
envelope.timeStamp = new Date();
postal.configuration.bus.publish( envelope );
return envelope;
},
"2" : function ( topic, data ) {
var envelope = { channel : DEFAULT_CHANNEL, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
},
"3" : function ( channel, topic, data ) {
var envelope = { channel : channel, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
}
},
channelPicker = {
"1" : function ( chn ) {
var channel = chn, topic, options = {};
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
}
else {
channel = chn.channel || DEFAULT_CHANNEL;
topic = chn.topic;
options = chn.options || options;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"2" : function ( chn, tpc ) {
var channel = chn, topic = tpc, options = {};
if ( Object.prototype.toString.call( tpc ) === "[object Object]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
options = tpc;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"3" : function ( channel, topic, options ) {
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
}
},
sessionInfo = {};
// save some setup time, albeit tiny
localBus.subscriptions[SYSTEM_CHANNEL] = {};
@ -429,34 +314,24 @@
bus : localBus,
resolver : bindingsResolver,
DEFAULT_CHANNEL : DEFAULT_CHANNEL,
DEFAULT_PRIORITY : DEFAULT_PRIORITY,
DEFAULT_DISPOSEAFTER : DEFAULT_DISPOSEAFTER,
SYSTEM_CHANNEL : SYSTEM_CHANNEL
},
channelTypes : {
local : ChannelDefinition
},
ChannelDefinition : ChannelDefinition,
channel : function () {
var len = arguments.length;
if ( channelPicker[len] ) {
return channelPicker[len].apply( this, arguments );
}
SubscriptionDefinition : SubscriptionDefinition,
channel : function ( channelName ) {
return new ChannelDefinition( channelName );
},
subscribe : function ( options ) {
var callback = options.callback,
topic = options.topic,
channel = options.channel || DEFAULT_CHANNEL;
return new SubscriptionDefinition( channel, topic, callback );
return new SubscriptionDefinition( options.channel || DEFAULT_CHANNEL, options.topic, options.callback );
},
publish : function () {
var len = arguments.length;
if ( publishPicker[len] ) {
return publishPicker[len].apply( this, arguments );
}
publish : function ( envelope ) {
envelope.channel = envelope.channel || DEFAULT_CHANNEL;
return postal.configuration.bus.publish( envelope );
},
addWireTap : function ( callback ) {
@ -464,13 +339,9 @@
},
linkChannels : function ( sources, destinations ) {
var result = [];
if ( !_.isArray( sources ) ) {
sources = [sources];
}
if ( !_.isArray( destinations ) ) {
destinations = [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 ) {
@ -509,7 +380,7 @@
}
}
if ( postal.configuration.bus.subscriptions[ channel ] &&
postal.configuration.bus.subscriptions[ channel ].hasOwnProperty( tpc ) ) {
postal.configuration.bus.subscriptions[ channel ].hasOwnProperty( tpc ) ) {
result = postal.configuration.bus.subscriptions[ channel ][ tpc ];
}
return result;
@ -522,6 +393,5 @@
}
};
global.postal = postal;
return postal;
} ));

7
lib/postal.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
{
"name" : "postal",
"description" : "Pub/Sub library providing wildcard subscriptions, complex message handling, etc. Works server and client-side.",
"version" : "0.7.3",
"version" : "0.8.0",
"homepage" : "http://github.com/ifandelse/postal.js",
"repository" : {
"type" : "git",

View file

@ -1,29 +1,16 @@
describe( "ChannelDefinition", function () {
describe( "When initializing a channel definition", function () {
var chDef = new ChannelDefinition( "TestChannel", "TestTopic" );
var chDef = new ChannelDefinition( "TestChannel" );
it( "should set channel to TestChannel", function () {
expect( chDef.channel ).to.be( "TestChannel" );
} );
it( "should set topic to TestTopic", function () {
expect( chDef._topic ).to.be( "TestTopic" );
} );
} );
describe( "When calling subscribe", function () {
var ch = new ChannelDefinition( "TestChannel", "TestTopic" ),
sub = ch.subscribe( function () {
var ch = new ChannelDefinition( "TestChannel" ),
sub = ch.subscribe( "TestTopic", function () {
} );
it( "subscription should be instance of SubscriptionDefinition", function () {
expect( sub instanceof SubscriptionDefinition ).to.be.ok();
} );
} );
describe( "When calling topic", function () {
var ch = new ChannelDefinition( "TestChannel", "TestTopic" ),
ch2 = ch.topic( "TestTopic2" );
it( "new channel should be of type ChannelDefinition", function () {
expect( ch2 instanceof ChannelDefinition ).to.be.ok();
} );
it( "new channel should have topic of TestTopic2", function () {
expect( ch2._topic ).to.be( "TestTopic2" );
} );
} );
} );

View file

@ -20,9 +20,7 @@ describe( "Postal", function () {
}
}
} );
subscription = postal.channel( { channel : "MyChannel", topic : "MyTopic" } )
.subscribe( function () {
} );
subscription = postal.channel( "MyChannel" ).subscribe( "MyTopic" , function () {} );
sub = postal.configuration.bus.subscriptions.MyChannel.MyTopic[0];
} );
after( function () {
@ -41,15 +39,9 @@ describe( "Postal", function () {
it( "should have set subscription topic value", function () {
expect( sub.topic ).to.be( "MyTopic" );
} );
it( "should have set subscription priority value", function () {
expect( sub.priority ).to.be( 50 );
} );
it( "should have defaulted the subscription constraints array", function () {
expect( sub.constraints.length ).to.be( 0 );
} );
it( "should have defaulted the subscription disposeAfter value", function () {
expect( sub.maxCalls ).to.be( 0 );
} );
it( "should have defaulted the subscription context value", function () {
expect( sub.context ).to.be(null);
} );
@ -75,9 +67,7 @@ describe( "Postal", function () {
;
}
} );
subscription = postal.channel( { channel : "MyChannel", topic : "MyTopic" } )
.subscribe( function () {
} );
subscription = postal.channel( "MyChannel" ).subscribe( "MyTopic" , function () {} );
subExistsBefore = postal.configuration.bus.subscriptions.MyChannel.MyTopic[0] !== undefined;
subscription.unsubscribe();
subExistsAfter = postal.configuration.bus.subscriptions.MyChannel.MyTopic.length !== 0;
@ -100,14 +90,14 @@ describe( "Postal", function () {
var msgReceivedCnt = 0,
msgData;
before( function () {
channel = postal.channel( { channel : "MyChannel", topic : "MyTopic" } );
subscription = channel.subscribe( function ( data ) {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic", function ( data ) {
msgReceivedCnt++;
msgData = data;
} );
channel.publish( "Testing123" );
channel.publish( "MyTopic", "Testing123" );
subscription.unsubscribe();
channel.publish( "Testing123" );
channel.publish( "MyTopic", "Testing123" );
} );
after( function () {
postal.utils.reset();
@ -119,35 +109,20 @@ describe( "Postal", function () {
expect( msgData ).to.be( "Testing123" );
} );
} );
describe( "When subscribing multiple subscribers with different priority", function () {
var s1, s2, r1 = [];
before( function () {
s1 = postal.subscribe( { channel : "MyChannel", topic : "MyTopic", callback: function() { r1.push("lower"); } } ).withPriority(200);
s2 = postal.subscribe( { channel : "MyChannel", topic : "MyTopic", callback: function() { r1.push("higher"); } } ).withPriority(1);
postal.publish( { channel: "MyChannel", topic: "MyTopic", data: "Oh, Hai!" } );
} );
after( function () {
postal.utils.reset();
} );
it( "should invoke higher priority subscription first", function () {
expect(r1[0] ).to.be("higher");
expect(r1[1] ).to.be("lower");
} );
} );
describe( "When subscribing with a disposeAfter of 5", function () {
var msgReceivedCnt = 0;
before( function () {
channel = postal.channel( { channel : "MyChannel", topic : "MyTopic" } );
subscription = channel.subscribe( function ( data ) {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic", function ( data ) {
msgReceivedCnt++;
} )
.disposeAfter( 5 );
channel.publish( "Testing123" );
channel.publish( "Testing123" );
channel.publish( "Testing123" );
channel.publish( "Testing123" );
channel.publish( "Testing123" );
channel.publish( "Testing123" );
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();
@ -159,16 +134,16 @@ describe( "Postal", function () {
describe( "When subscribing with once()", function () {
var msgReceivedCnt = 0;
before( function () {
channel = postal.channel( { channel : "MyChannel", topic : "MyTopic" } );
subscription = channel.subscribe( function ( data ) {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic", function ( data ) {
msgReceivedCnt++;
} ).once();
channel.publish( "Testing123" );
channel.publish( "Testing123" );
channel.publish( "Testing123" );
channel.publish( "Testing123" );
channel.publish( "Testing123" );
channel.publish( "Testing123" );
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();
@ -180,17 +155,17 @@ describe( "Postal", function () {
describe( "When subscribing and ignoring duplicates", function () {
var subInvokedCnt = 0;
before( function () {
channel = postal.channel( { channel : "MyChannel", topic : "MyTopic" } );
subscription = channel.subscribe( function ( data ) {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic", function ( data ) {
subInvokedCnt++;
} )
.distinctUntilChanged();
channel.publish( "Testing123" );
channel.publish( "Testing123" );
channel.publish( "Testing123" );
channel.publish( "Testing123" );
channel.publish( "Testing123" );
channel.publish( "Testing123" );
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();
@ -206,14 +181,14 @@ describe( "Postal", function () {
describe( "When subscribing with one constraint returning true", function () {
var recvd = false;
before( function () {
channel = postal.channel( { channel : "MyChannel", topic : "MyTopic" } );
subscription = channel.subscribe( function ( data ) {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic", function ( data ) {
recvd = true;
} )
.withConstraint( function () {
return true;
} );
channel.publish( "Testing123" );
channel.publish( "MyTopic", "Testing123" );
} );
after( function () {
postal.utils.reset();
@ -229,14 +204,14 @@ describe( "Postal", function () {
describe( "When subscribing with one constraint returning false", function () {
var recvd = false;
before( function () {
channel = postal.channel( { channel : "MyChannel", topic : "MyTopic" } );
subscription = channel.subscribe( function ( data ) {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic", function ( data ) {
recvd = true;
} )
.withConstraint( function () {
return false;
} );
channel.publish( "Testing123" );
channel.publish( "MyTopic", "Testing123" );
} );
after( function () {
postal.utils.reset();
@ -252,8 +227,8 @@ describe( "Postal", function () {
describe( "When subscribing with multiple constraints returning true", function () {
var recvd = false;
before( function () {
channel = postal.channel( { channel : "MyChannel", topic : "MyTopic" } );
subscription = channel.subscribe( function ( data ) {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic", function ( data ) {
recvd = true;
} )
.withConstraints( [function () {
@ -265,7 +240,7 @@ describe( "Postal", function () {
function () {
return true;
}] );
channel.publish( "Testing123" );
channel.publish( "MyTopic", "Testing123" );
} );
after( function () {
postal.utils.reset();
@ -281,8 +256,8 @@ describe( "Postal", function () {
describe( "When subscribing with multiple constraints and one returning false", function () {
var recvd = false;
before( function () {
channel = postal.channel( { channel : "MyChannel", topic : "MyTopic" } );
subscription = channel.subscribe( function ( data ) {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic", function ( data ) {
recvd = true;
} )
.withConstraints( [function () {
@ -294,7 +269,7 @@ describe( "Postal", function () {
function () {
return true;
}] );
channel.publish( "Testing123" );
channel.publish( "MyTopic", "Testing123" );
} );
after( function () {
postal.utils.reset();
@ -315,12 +290,12 @@ describe( "Postal", function () {
}
};
before( function () {
channel = postal.channel( { channel : "MyChannel", topic : "MyTopic" } );
subscription = channel.subscribe( function ( data ) {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic", function ( data ) {
this.increment();
} )
.withContext( obj );
channel.publish( "Testing123" );
channel.publish( "MyTopic", "Testing123" );
} );
after( function () {
postal.utils.reset();
@ -332,49 +307,49 @@ describe( "Postal", function () {
describe( "When subscribing with defer", function () {
var results = [];
before( function () {
channel = postal.channel( { channel : "MyChannel", topic : "MyTopic" } );
channel = postal.channel( "MyChannel" );
} );
after( function () {
postal.utils.reset();
} );
it( "should have met expected results", function (done) {
subscription = channel.subscribe(
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( "Testing123" );
channel.publish( "MyTopic", "Testing123" );
results.push( "first" );
} );
} );
describe( "When subscribing with delay", function () {
var results = [];
before( function () {
channel = postal.channel( { channel : "MyChannel", topic : "MyTopic" } );
channel = postal.channel( "MyChannel" );
} );
after( function () {
postal.utils.reset();
} );
it( "should have met expected results", function (done) {
subscription = channel.subscribe(
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( "Testing123" );
channel.publish( "MyTopic", "Testing123" );
results.push( "first" );
} );
} );
describe( "When subscribing with debounce", function () {
var results = [], debouncedChannel;
before( function () {
debouncedChannel = postal.channel( { channel : "DebouncedChannel", topic : "MyTopic" } );
subscription = debouncedChannel.subscribe(
debouncedChannel = postal.channel( "MyChannel" );
subscription = debouncedChannel.subscribe( "MyTopic",
function ( data ) {
results.push( data );
} ).withDebounce( 800 );
@ -383,21 +358,21 @@ describe( "Postal", function () {
postal.utils.reset();
} );
it( "should have only invoked debounced callback once", function (done) {
debouncedChannel.publish( 1 ); // starts the two second clock on debounce
debouncedChannel.publish( "MyTopic", 1 ); // starts the two second clock on debounce
setTimeout( function () {
debouncedChannel.publish( 2 );
debouncedChannel.publish( "MyTopic", 2 );
}, 20 ); // should not invoke callback
setTimeout( function () {
debouncedChannel.publish( 3 );
debouncedChannel.publish( "MyTopic", 3 );
}, 80 ); // should not invoke callback
setTimeout( function () {
debouncedChannel.publish( 4 );
debouncedChannel.publish( "MyTopic", 4 );
}, 250 ); // should not invoke callback
setTimeout( function () {
debouncedChannel.publish( 5 );
debouncedChannel.publish( "MyTopic", 5 );
}, 500 ); // should not invoke callback
setTimeout( function () {
debouncedChannel.publish( 6 );
debouncedChannel.publish( "MyTopic", 6 );
}, 1000 ); // should invoke callback
setTimeout( function () {
expect( results[0] ).to.be( 6 );
@ -409,8 +384,8 @@ describe( "Postal", function () {
describe( "When subscribing with throttle", function () {
var results = [], throttledChannel;
before( function () {
throttledChannel = postal.channel( { channel : "ThrottledChannel", topic : "MyTopic" } );
subscription = throttledChannel.subscribe(
throttledChannel = postal.channel( "MyChannel" );
subscription = throttledChannel.subscribe( "MyTopic",
function ( data ) {
results.push( data );
} ).withThrottle( 500 );
@ -419,13 +394,13 @@ describe( "Postal", function () {
postal.utils.reset();
} );
it( "should have only invoked throttled callback twice", function (done) {
throttledChannel.publish( 1 ); // starts the two second clock on debounce
throttledChannel.publish( "MyTopic", 1 ); // starts the two second clock on debounce
setTimeout( function () {
throttledChannel.publish( 800 );
throttledChannel.publish( "MyTopic", 800 );
}, 800 ); // should invoke callback
for ( var i = 0; i < 20; i++ ) {
(function ( x ) {
throttledChannel.publish( x );
throttledChannel.publish( "MyTopic", x );
})( i );
}
setTimeout( function () {
@ -439,15 +414,13 @@ describe( "Postal", function () {
describe( "When subscribing with a hierarchical binding, no wildcards", function () {
var count = 0, channelB, channelC;
before( function () {
channel = postal.channel( { channel : "MyChannel", topic : "MyTopic.MiddleTopic.SubTopic" } );
channelB = postal.channel( { channel : "MyChannel", topic : "MyTopic.MiddleTopic" } );
channelC = postal.channel( { channel : "MyChannel", topic : "MyTopic.MiddleTopic.SubTopic.YetAnother" } );
subscription = channel.subscribe( function ( data ) {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic.MiddleTopic", function ( data ) {
count++;
} );
channel.publish( "Testing123" );
channelB.publish( "Testing123" );
channelC.publish( "Testing123" );
channel.publish( "MyTopic.MiddleTopic.SubTopic", "Testing123" );
channel.publish( "MyTopic.MiddleTopic", "Testing123" );
channel.publish( "MyTopic.MiddleTopic.SubTopic.YetAnother", "Testing123" );
} );
after( function () {
postal.utils.reset();
@ -461,19 +434,14 @@ describe( "Postal", function () {
var count, channelB, channelC, channelD, channelE;
before( function () {
count = 0;
channel = postal.channel( { channel : "MyChannel", topic : "MyTopic.#.SubTopic" } );
channelB = postal.channel( { channel : "MyChannel", topic : "MyTopic.MiddleTopic" } );
channelC = postal.channel( { channel : "MyChannel", topic : "MyTopic.MiddleTopic.SubTopic" } );
channelD = postal.channel( { channel : "MyChannel", topic : "MyTopic.MiddleTopic.SubMiddle.SubTopic" } );
channelE = postal.channel( { channel : "MyChannel", topic : "MyTopic.MiddleTopic.SubTopic.YetAnother" } );
subscription = channel.subscribe( function ( data, env ) {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic.#.SubTopic", function ( data, env ) {
count++;
console.log("TOPIC: " + env.topic );
} );
channelC.publish( {channel : "MyChannel", topic : "MyTopic.MiddleTopic.SubTopic", data : "Testing123"} );
channelB.publish( {channel : "MyChannel", topic : "MyTopic.MiddleTopic", data : "Testing123"} );
channelD.publish( {channel : "MyChannel", topic : "MyTopic.MiddleTopic.SubMiddle.SubTopic", data : "Testing123"} );
channelD.publish( {channel : "MyChannel", topic : "MyTopic.MiddleTopic.SubTopic.YetAnother", data : "Testing123"} );
channel.publish( "MyTopic.MiddleTopic.SubTopic", "Testing123" );
channel.publish( "MyTopic.MiddleTopic", "Testing123" );
channel.publish( "MyTopic.MiddleTopic.SubMiddle.SubTopic", "Testing123" );
channel.publish( "MyTopic.MiddleTopic.SubTopic.YetAnother", "Testing123" );
} );
after( function () {
postal.utils.reset();
@ -486,17 +454,14 @@ describe( "Postal", function () {
describe( "When subscribing with a hierarchical binding, using *", function () {
var count = 0, channelB, channelC, channelD;
before( function () {
channel = postal.channel( { channel : "MyChannel", topic : "MyTopic.MiddleTopic.*" } );
channelB = postal.channel( { channel : "MyChannel", topic : "MyTopic.MiddleTopic" } );
channelC = postal.channel( { channel : "MyChannel", topic : "MyTopic.MiddleTopic.SubTopic" } );
channelD = postal.channel( { channel : "MyChannel", topic : "MyTopic.MiddleTopic.SubTopic.YetAnother" } );
subscription = channel.subscribe( function ( data ) {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic.MiddleTopic.*", function ( data ) {
count++;
} );
channelC.publish( "Testing123" );
channelB.publish( "Testing123" );
channelD.publish( "Testing123" );
channel.publish( "MyTopic.MiddleTopic", "Testing123" );
channel.publish( "MyTopic.MiddleTopic.SubTopic", "Testing123" );
channel.publish( "MyTopic.MiddleTopic.SubTopic.YetAnother", "Testing123" );
} );
after( function () {
postal.utils.reset();
@ -509,19 +474,15 @@ describe( "Postal", function () {
describe( "When subscribing with a hierarchical binding, using # and *", function () {
var count = 0, channelB, channelC, channelD, channelE;
before( function () {
channel = postal.channel( { channel : "MyChannel", topic : "MyTopic.#.*" } );
channelB = postal.channel( { channel : "MyChannel", topic : "MyTopic.MiddleTopic" } );
channelC = postal.channel( { channel : "MyChannel", topic : "MyTopic.MiddleTopic.SubTopic" } );
channelD = postal.channel( { channel : "MyChannel", topic : "MyTopic.MiddleTopic.SubTopic.YetAnother" } );
channelE = postal.channel( { channel : "MyChannel", topic : "OtherTopic.MiddleTopic.SubTopic.YetAnother" } );
subscription = channel.subscribe( function ( data ) {
channel = postal.channel( "MyChannel" );
subscription = channel.subscribe( "MyTopic.#.*", function ( data ) {
count++;
} );
channelC.publish( {channel : "MyChannel", topic : "MyTopic.MiddleTopic.SubTopic", data : "Testing123"} );
channelB.publish( {channel : "MyChannel", topic : "MyTopic.MiddleTopic", data : "Testing123"} );
channelD.publish( {channel : "MyChannel", topic : "MyTopic.MiddleTopic.SubTopic.YetAnother", data : "Testing123"} );
channelE.publish( {channel : "MyChannel", topic : "OtherTopic.MiddleTopic.SubTopic.YetAnother", data : "Testing123"} );
channel.publish( "MyTopic.MiddleTopic.SubTopic", "Testing123" );
channel.publish( "MyTopic.MiddleTopic", "Testing123" );
channel.publish( "MyTopic.MiddleTopic.SubTopic.YetAnother", "Testing123" );
channel.publish( "OtherTopic.MiddleTopic.SubTopic.YetAnother", "Testing123" );
} );
after( function () {
postal.utils.reset();
@ -540,9 +501,9 @@ describe( "Postal", function () {
msgReceivedCnt++;
msgData = data;
} );
postal.publish( "MyGlobalChannel", "MyTopic", "Testing123" );
postal.publish( { channel: "MyGlobalChannel", topic: "MyTopic", data: "Testing123" } );
subscription.unsubscribe();
postal.publish( "MyGlobalChannel", "MyTopic", "Testing123" );
postal.publish( { channel: "MyGlobalChannel", topic: "MyTopic", data: "Testing123" } );
} );
after( function () {
postal.utils.reset();
@ -586,15 +547,9 @@ describe( "Postal", function () {
it( "should have set subscription topic value", function () {
expect( sub.topic ).to.be( "MyTopic" );
} );
it( "should have set subscription priority value", function () {
expect( sub.priority ).to.be( 50 );
} );
it( "should have defaulted the subscription constraints array", function () {
expect( sub.constraints.length ).to.be( 0 );
} );
it( "should have defaulted the subscription disposeAfter value", function () {
expect( sub.maxCalls ).to.be( 0 );
} );
it( "should have defaulted the subscription context value", function () {
expect( sub.context ).to.be(null);
} );
@ -653,9 +608,9 @@ describe( "Postal", function () {
var resolver;
before( function () {
postal.utils.reset();
subscription = postal.channel( { channel : "MyChannel", topic : "MyTopic" } ).subscribe( function () {
subscription = postal.channel( "MyChannel" ).subscribe( "MyTopic", function () {
} );
postal.channel( { channel : "MyChannel", topic : "MyTopic" } ).publish( "Oh Hai!" );
postal.channel( "MyChannel" ).publish( "MyTopic", "Oh Hai!" );
sub = postal.configuration.bus.subscriptions.MyChannel.MyTopic[0];
resolver = postal.configuration.resolver.cache["MyTopic"];
postal.utils.reset();
@ -665,9 +620,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.priority ).to.be( 50 );
expect( sub.constraints.length ).to.be( 0 );
expect( sub.maxCalls ).to.be( 0 );
expect( sub.context ).to.be(null);
} );
it( "should have created a resolver cache entry", function () {
@ -685,12 +638,12 @@ describe( "Postal", function () {
var subs = [], i;
before( function () {
i = 10;
var ch1 = postal.channel( { channel : "MyChannel", topic : "MyTopic" } ),
ch2 = postal.channel( { channel : "MyChannel2", topic : "MyTopic2" } );
var ch1 = postal.channel( "MyChannel" ),
ch2 = postal.channel( "MyChannel2" );
while ( i ) {
subs.push( ch1.subscribe( function () {
subs.push( ch1.subscribe( "MyTopic", function () {
} ) );
subs.push( ch2.subscribe( function () {
subs.push( ch2.subscribe( "MyTopic2", function () {
} ) );
i--;
}

View file

@ -1,3 +1,4 @@
var NO_OP = function() {};
describe( "SubscriptionDefinition", function () {
describe( "When initializing SubscriptionDefinition", function () {
var sDef,
@ -32,18 +33,9 @@ describe( "SubscriptionDefinition", function () {
it( "should set the callback", function () {
expect( sDef.callback ).to.be( NO_OP );
} );
it( "should default the priority", function () {
expect( sDef.priority ).to.be( 50 );
} );
it( "should default the constraints", function () {
expect( sDef.constraints.length ).to.be( 0 );
} );
it( "should default the maxCalls", function () {
expect( sDef.maxCalls ).to.be( 0 );
} );
it( "should default the onHandled callback", function () {
expect( sDef.onHandled ).to.be( NO_OP );
} );
it( "should default the context", function () {
expect( sDef.context ).to.be( null );
} );
@ -100,14 +92,6 @@ describe( "SubscriptionDefinition", function () {
} );
} );
describe( "When setting priority", function () {
var sDefe = new SubscriptionDefinition( "TestChannel", "TestTopic", NO_OP ).withPriority( 10 );
it( "Should set priority", function () {
expect( sDefe.priority ).to.be( 10 );
} );
} );
describe( "When calling subscribe to set the callback", function () {
var sDefe = new SubscriptionDefinition( "TestChannel", "TestTopic", NO_OP ),
fn = function () {

View file

@ -10,9 +10,9 @@ describe("Linked Channels", function(){
destData.push( data );
destEnv.push( env );
}} );
postal.publish( "sourceChannel", "Oh.Hai.There", { data : "I'm in yer bus, linkin' to yer subscriptionz..."} );
postal.publish( { channel: "sourceChannel", topic: "Oh.Hai.There", data: "I'm in yer bus, linkin' to yer subscriptionz..." } );
linkages[0].unsubscribe();
postal.publish( "sourceChannel", "Oh.Hai.There", { data : "I'm in yer bus, linkin' to yer subscriptionz..."} );
postal.publish( { channel: "sourceChannel", topic: "Oh.Hai.There", data : "I'm in yer bus, linkin' to yer subscriptionz..."} );
} );
after( function () {
postal.utils.reset();
@ -22,7 +22,7 @@ describe("Linked Channels", function(){
expect( destEnv.length ).to.be( 1 );
} );
it( "linked subscription data should match expected results", function () {
expect( destData[0].data ).to.be( "I'm in yer bus, linkin' to yer subscriptionz..." );
expect( destData[0] ).to.be( "I'm in yer bus, linkin' to yer subscriptionz..." );
} );
it( "linked subscription envelope should match expected results", function () {
expect( destEnv[0].channel ).to.be( "destinationChannel" );
@ -39,9 +39,9 @@ describe("Linked Channels", function(){
destData.push( data );
destEnv.push( env );
}} );
postal.publish( "sourceChannel", "Oh.Hai.There", { data : "I'm in yer bus, linkin' to yer subscriptionz..."} );
postal.publish( { channel: "sourceChannel", topic: "Oh.Hai.There", data : "I'm in yer bus, linkin' to yer subscriptionz..."} );
linkages[0].unsubscribe();
postal.publish( "sourceChannel", "Oh.Hai.There", { data : "I'm in yer bus, linkin' to yer subscriptionz..."} );
postal.publish( { channel: "sourceChannel", topic: "Oh.Hai.There", data : "I'm in yer bus, linkin' to yer subscriptionz..."} );
} );
after( function () {
postal.utils.reset();
@ -51,7 +51,7 @@ describe("Linked Channels", function(){
expect( destEnv.length ).to.be( 1 );
} );
it( "linked subscription data should match expected results", function () {
expect( destData[0].data ).to.be( "I'm in yer bus, linkin' to yer subscriptionz..." );
expect( destData[0] ).to.be( "I'm in yer bus, linkin' to yer subscriptionz..." );
} );
it( "linked subscription envelope should match expected results", function () {
expect( destEnv[0].channel ).to.be( "destinationChannel" );
@ -70,9 +70,9 @@ describe("Linked Channels", function(){
destData.push( data );
destEnv.push( env );
}} );
postal.publish( "sourceChannel", "Oh.Hai.There", { data : "I'm in yer bus, linkin' to yer subscriptionz..."} );
postal.publish( { channel: "sourceChannel", topic: "Oh.Hai.There", data : "I'm in yer bus, linkin' to yer subscriptionz..."} );
linkages[0].unsubscribe();
postal.publish( "sourceChannel", "Oh.Hai.There", { data : "I'm in yer bus, linkin' to yer subscriptionz..."} );
postal.publish( { channel: "sourceChannel", topic: "Oh.Hai.There", data : "I'm in yer bus, linkin' to yer subscriptionz..."} );
} );
after( function () {
postal.utils.reset();
@ -82,7 +82,7 @@ describe("Linked Channels", function(){
expect( destEnv.length ).to.be( 1 );
} );
it( "linked subscription data should match expected results", function () {
expect( destData[0].data ).to.be( "I'm in yer bus, linkin' to yer subscriptionz..." );
expect( destData[0] ).to.be( "I'm in yer bus, linkin' to yer subscriptionz..." );
} );
it( "linked subscription envelope should match expected results", function () {
expect( destEnv[0].channel ).to.be( "destinationChannel" );
@ -107,7 +107,7 @@ describe("Linked Channels", function(){
]);
postal.subscribe( { channel : "destinationChannel", topic : "NewTopic.Oh.Hai", callback : callback} );
postal.subscribe( { channel : "destinationChannel", topic : "NewTopic.Oh.Hai.There", callback : callback } );
postal.publish( "sourceChannel", "Oh.Hai.There", { data : "I'm in yer bus, linkin' to yer subscriptionz..."} );
postal.publish( { channel: "sourceChannel", topic: "Oh.Hai.There", data : "I'm in yer bus, linkin' to yer subscriptionz..."} );
} );
after( function () {
postal.utils.reset();

View file

@ -6,10 +6,10 @@ var bindingsResolver = {
return true;
}
var pattern = ("^" + binding.replace( /\./g, "\\." ) // escape actual periods
.replace( /\*/g, "[A-Z,a-z,0-9]*" ) // asterisks match any alpha-numeric 'word'
.replace( /#/g, ".*" ) + "$") // hash matches 'n' # of words (+ optional on start/end of topic)
.replace( "\\..*$", "(\\..*)*$" ) // fix end of topic matching on hash wildcards
.replace( "^.*\\.", "^(.*\\.)*" ); // fix beginning of topic matching on hash wildcards
.replace( /\*/g, "[A-Z,a-z,0-9]*" ) // asterisks match any alpha-numeric 'word'
.replace( /#/g, ".*" ) + "$") // hash matches 'n' # of words (+ optional on start/end of topic)
.replace( "\\..*$", "(\\..*)*$" ) // fix end of topic matching on hash wildcards
.replace( "^.*\\.", "^(.*\\.)*" ); // fix beginning of topic matching on hash wildcards
var rgx = new RegExp( pattern );
var result = rgx.test( topic );
if ( result ) {
@ -24,4 +24,4 @@ var bindingsResolver = {
reset : function () {
this.cache = {};
}
};
};

View file

@ -1,5 +1,3 @@
var sessionInfo = {};
// save some setup time, albeit tiny
localBus.subscriptions[SYSTEM_CHANNEL] = {};
@ -8,17 +6,15 @@ var postal = {
bus : localBus,
resolver : bindingsResolver,
DEFAULT_CHANNEL : DEFAULT_CHANNEL,
DEFAULT_PRIORITY : DEFAULT_PRIORITY,
DEFAULT_DISPOSEAFTER : DEFAULT_DISPOSEAFTER,
SYSTEM_CHANNEL : SYSTEM_CHANNEL
},
ChannelDefinition : ChannelDefinition,
ChannelDefinition : ChannelDefinition,
SubscriptionDefinition: SubscriptionDefinition,
SubscriptionDefinition : SubscriptionDefinition,
channel : function ( channelName ) {
return new ChannelDefinition( channelName );
return new ChannelDefinition( channelName );
},
subscribe : function ( options ) {
@ -26,9 +22,8 @@ var postal = {
},
publish : function ( envelope ) {
envelope.channel = envelope.channel || DEFAULT_CHANNEL;
postal.configuration.bus.publish( envelope );
return envelope;
envelope.channel = envelope.channel || DEFAULT_CHANNEL;
return postal.configuration.bus.publish( envelope );
},
addWireTap : function ( callback ) {
@ -36,13 +31,9 @@ var postal = {
},
linkChannels : function ( sources, destinations ) {
var result = [];
if ( !_.isArray( sources ) ) {
sources = [sources];
}
if ( !_.isArray( destinations ) ) {
destinations = [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 ) {
@ -81,7 +72,7 @@ var postal = {
}
}
if ( postal.configuration.bus.subscriptions[ channel ] &&
postal.configuration.bus.subscriptions[ channel ].hasOwnProperty( tpc ) ) {
postal.configuration.bus.subscriptions[ channel ].hasOwnProperty( tpc ) ) {
result = postal.configuration.bus.subscriptions[ channel ][ tpc ];
}
return result;

View file

@ -1,25 +0,0 @@
var classicBindingsResolver = {
cache : { },
compare : function ( binding, topic ) {
if ( this.cache[topic] && this.cache[topic][binding] ) {
return true;
}
// binding.replace(/\./g,"\\.") // escape actual periods
// .replace(/\*/g, ".*") // asterisks match any value
// .replace(/#/g, "[A-Z,a-z,0-9]*"); // hash matches any alpha-numeric 'word'
var rgx = new RegExp( "^" + binding.replace( /\./g, "\\." ).replace( /\*/g, ".*" ).replace( /#/g, "[A-Z,a-z,0-9]*" ) + "$" ),
result = rgx.test( topic );
if ( result ) {
if ( !this.cache[topic] ) {
this.cache[topic] = {};
}
this.cache[topic][binding] = true;
}
return result;
},
reset : function () {
this.cache = {};
}
};

View file

@ -2,17 +2,14 @@ var ChannelDefinition = function ( channelName ) {
this.channel = channelName || DEFAULT_CHANNEL;
};
ChannelDefinition.prototype = {
subscribe : function () {
return arguments.length === 1 ?
new SubscriptionDefinition( this.channel, arguments[0].topic, arguments[0].callback ) :
new SubscriptionDefinition( this.channel, arguments[0], arguments[1] );
},
publish : function () {
var envelope = arguments.length === 1 ? arguments[0] : { topic: arguments[0], data: arguments[1] };
envelope.channel = this.channel;
postal.configuration.bus.publish( envelope );
return envelope;
}
ChannelDefinition.prototype.subscribe = function () {
return 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 ? arguments[0] : { topic : arguments[0], data : arguments[1] };
envelope.channel = this.channel;
return postal.configuration.bus.publish( envelope );
};

View file

@ -1,6 +1,3 @@
var DEFAULT_CHANNEL = "/",
DEFAULT_PRIORITY = 50,
DEFAULT_DISPOSEAFTER = 0,
SYSTEM_CHANNEL = "postal",
NO_OP = function () {
};
SYSTEM_CHANNEL = "postal";

View file

@ -1,16 +1,16 @@
var DistinctPredicate = function () {
var previous = [];
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;
};
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

@ -1,5 +1,4 @@
var localBus = {
addWireTap : function ( callback ) {
var self = this;
self.wireTaps.push( callback );
@ -11,48 +10,28 @@ var localBus = {
};
},
changePriority : function ( subDef ) {
var idx, found;
if ( this.subscriptions[subDef.channel] && this.subscriptions[subDef.channel][subDef.topic] ) {
this.subscriptions[subDef.channel][subDef.topic] = _.without( this.subscriptions[subDef.channel][subDef.topic], subDef );
idx = this.subscriptions[subDef.channel][subDef.topic].length - 1;
for ( ; idx >= 0; idx-- ) {
if ( this.subscriptions[subDef.channel][subDef.topic][idx].priority <= subDef.priority ) {
this.subscriptions[subDef.channel][subDef.topic].splice( idx + 1, 0, subDef );
found = true;
break;
}
}
if ( !found ) {
this.subscriptions[subDef.channel][subDef.topic].unshift( subDef );
}
}
},
publish : function ( envelope ) {
envelope.timeStamp = new Date();
envelope.timeStamp = new Date();
_.each( this.wireTaps, function ( tap ) {
tap( envelope.data, envelope );
} );
if ( this.subscriptions[envelope.channel] ) {
_.each( this.subscriptions[envelope.channel], function ( topic ) {
// TODO: research faster ways to handle this than _.clone
_.each( _.clone( topic ), function ( subDef ) {
if ( postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) {
if ( _.all( subDef.constraints, function ( constraint ) {
return constraint.call(subDef.context, envelope.data, envelope );
return constraint.call( subDef.context, envelope.data, envelope );
} ) ) {
if ( typeof subDef.callback === 'function' ) {
subDef.callback.call( subDef.context, envelope.data, envelope );
subDef.onHandled();
}
}
}
} );
} );
}
return envelope;
},
reset : function () {
@ -70,13 +49,12 @@ var localBus = {
subscribe : function ( subDef ) {
var idx, found, fn, 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] = new Array( 0 );
subs = this.subscriptions[subDef.channel][subDef.topic] = [];
}
subs.push( subDef );
return subDef;
@ -84,7 +62,7 @@ var localBus = {
subscriptions : {},
wireTaps : new Array( 0 ),
wireTaps : [],
unsubscribe : function ( config ) {
if ( this.subscriptions[config.channel][config.topic] ) {
@ -98,4 +76,4 @@ var localBus = {
}
}
}
};
};

View file

@ -2,24 +2,18 @@ var SubscriptionDefinition = function ( channel, topic, callback ) {
this.channel = channel;
this.topic = topic;
this.callback = callback;
this.priority = DEFAULT_PRIORITY;
this.constraints = new Array( 0 );
this.maxCalls = DEFAULT_DISPOSEAFTER;
this.onHandled = NO_OP;
this.constraints = [];
this.context = null;
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.created",
timeStamp : new Date(),
data : {
event : "subscription.created",
channel : channel,
topic : topic
}
} );
postal.configuration.bus.subscribe( this );
};
SubscriptionDefinition.prototype = {
@ -28,7 +22,6 @@ SubscriptionDefinition.prototype = {
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.removed",
timeStamp : new Date(),
data : {
event : "subscription.removed",
channel : this.channel,
@ -49,20 +42,19 @@ SubscriptionDefinition.prototype = {
if ( _.isNaN( maxCalls ) || maxCalls <= 0 ) {
throw "The value provided to disposeAfter (maxCalls) must be a number greater than zero.";
}
var fn = this.onHandled;
var fn = this.callback;
var dispose = _.after( maxCalls, _.bind( function () {
this.unsubscribe( this );
this.unsubscribe();
}, this ) );
this.onHandled = function () {
this.callback = function () {
fn.apply( this.context, arguments );
dispose();
};
return this;
},
distinctUntilChanged : function () {
distinctUntilChanged : function () {
this.withConstraint( new ConsecutiveDistinctPredicate() );
return this;
},
@ -72,9 +64,9 @@ SubscriptionDefinition.prototype = {
return this;
},
once: function() {
this.disposeAfter(1);
},
once : function () {
this.disposeAfter( 1 );
},
withConstraint : function ( predicate ) {
if ( !_.isFunction( predicate ) ) {
@ -121,15 +113,6 @@ SubscriptionDefinition.prototype = {
return this;
},
withPriority : function ( priority ) {
if ( _.isNaN( priority ) ) {
throw "Priority must be a number";
}
this.priority = priority;
postal.configuration.bus.changePriority( this );
return this;
},
withThrottle : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
@ -143,4 +126,4 @@ SubscriptionDefinition.prototype = {
this.callback = callback;
return this;
}
};
};

View file

@ -1,16 +0,0 @@
(function ( root, doc, factory ) {
if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define( ["postal"], function ( postal ) {
return factory( postal, root, doc );
} );
} else {
// Browser globals
factory( root.postal, root, doc );
}
}( this, document, function ( postal, global, document, undefined ) {
//import("../BindingsResolver.js");
postal.configuration.resolver = classicBindingsResolver;
} ));

View file

@ -1,24 +0,0 @@
(function ( root, factory ) {
if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define( ["underscore"], function ( _ ) {
return factory( _, root );
} );
} else {
// Browser globals
factory( root._, root );
}
}( this, function ( _, global, undefined ) {
//import("../Constants.js");
//import("../ConsecutiveDistinctPredicate.js");
//import("../DistinctPredicate.js");
//import("../ChannelDefinition.js");
//import("../SubscriptionDefinition.js");
//import("../AmqpBindingsResolver.js");
//import("../LocalBus.js");
//import("../Api.js");
global.postal = postal;
return postal;
} ));

View file

@ -1,8 +0,0 @@
//import("../BindingsResolver.js");
module.exports = {
configure: function(postal) {
postal.configuration.resolver = classicBindingsResolver;
return postal;
}
};

View file

@ -1,14 +0,0 @@
// This is the node.js version of postal.js
// If you need the web client lib version, go to http://github.com/ifandelse/postal.js
var _ = require( 'underscore' );
//import("../Constants.js");
//import("../DistinctPredicate.js");
//import("../ConsecutiveDistinctPredicate.js");
//import("../ChannelDefinition.js");
//import("../SubscriptionDefinition.js");
//import("../AmqpBindingsResolver.js");
//import("../LocalBus.js");
//import("../Api.js");
module.exports = postal;

29
src/postal.js Normal file
View file

@ -0,0 +1,29 @@
(function ( root, factory ) {
if ( typeof module === "object" && module.exports ) {
// Node, or CommonJS-Like environments
module.exports = function ( _ ) {
_ = _ || require( "underscore" );
return factory( _ );
}
} else if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define( ["."], function ( _ ) {
return factory( _, root );
} );
} else {
// Browser globals
root.postal = factory( root._, root );
}
}( this, function ( _, global, undefined ) {
//import("Constants.js");
//import("ConsecutiveDistinctPredicate.js");
//import("DistinctPredicate.js");
//import("ChannelDefinition.js");
//import("SubscriptionDefinition.js");
//import("AmqpBindingsResolver.js");
//import("LocalBus.js");
//import("Api.js");
return postal;
} ));