Last active
January 1, 2016 19:09
-
-
Save eschwartz/8188988 to your computer and use it in GitHub Desktop.
WireJS Plugin: ListenTo Facet
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define([ | |
'vendor/underscore', | |
'when' | |
], function(_, when) { | |
/** | |
* Helper class for | |
* the listenTo facet. | |
* | |
* @param proxy | |
* @param wire | |
* | |
* @constructor | |
*/ | |
var ListenToFacet = function(proxy, wire) { | |
this.listener_ = proxy.target; | |
this.spec_ = proxy.options; | |
this.wire_ = wire; | |
}; | |
/** | |
* Bind event listeners, as | |
* defined in the facet spec. | |
* | |
* @returns {when.promise} A promise to resolve all listeners. | |
*/ | |
ListenToFacet.prototype.connect = function() { | |
var connectionPromises = []; | |
_.each(this.spec_, function(eventSpec, targetRef) { | |
connectionPromises.push(this.connectTarget(eventSpec, targetRef)) | |
}, this); | |
return when.all(connectionPromises); | |
}; | |
/** | |
* Bind listeners to a target object. | |
* | |
* @param {Object} eventSpec | |
* As: { | |
* eventName: 'opt_transform | handler', | |
* ... | |
* } | |
* | |
* @param {string} targetRef Reference to target object | |
* @returns {when.promise} Promise to bind target events. | |
*/ | |
ListenToFacet.prototype.connectTarget = function(eventSpec, targetRef) { | |
var passThrough = function(args) { return args; } | |
var whenTargetResolved = this.wire_.resolveRef(targetRef); | |
var connectionPromises = []; | |
_.each(eventSpec, function(handlerSpec, topic) { | |
var whenTransformerResolved; | |
var whenConnected; | |
var handler; | |
var parts = handlerSpec.split('|'); | |
// HandlerSpec: 'transformer | handler' | |
// --> Resolve transformer | |
if (parts.length === 2) { | |
whenTransformerResolved = this.resolveTransformer(parts[0].trim()); | |
handler = this.listener_[parts[1].trim()]; | |
} | |
// HandlerSpec: 'handler' | |
// --> Use a pass-through transformer | |
else { | |
whenTransformerResolved = when(passThrough); | |
handler = this.listener_[handlerSpec]; | |
} | |
// Transformer and target are resolved | |
whenConnected = when.join(whenTargetResolved, whenTransformerResolved). | |
then(_.bind(function(refs) { | |
var target = refs[0]; | |
var transformer = refs[1]; | |
this.listenTo(target, topic, handler, transformer); | |
}, this)); | |
connectionPromises.push(whenConnected); | |
}, this); | |
return when.all(connectionPromises); | |
}; | |
/** | |
* @param {string} transformerSpec As '[opt_ns].transformer' | |
* @return {when} Promise to resolve with transformer function. | |
*/ | |
ListenToFacet.prototype.resolveTransformer = function(transformerSpec) { | |
var specParts = transformerSpec.split('.'); | |
var nsRef, transformerRef; | |
return when.promise(_.bind(function(resolve, reject) { | |
// Spec: 'namespace.transformer' | |
// --> Resolve namespace reference | |
if (specParts.length === 2) { | |
nsRef = specParts[0].trim(); | |
transformerRef = specParts[1].trim(); | |
this.wire_.resolveRef(nsRef).then(function(ns) { | |
resolve(ns[transformerRef]); | |
}); | |
} | |
// Spec: 'transformer' | |
// --> Resolve transformer reference | |
else { | |
transformerRef = transformerSpec; | |
this.wire_.resolveRef(transformerRef).then(resolve, reject); | |
} | |
}, this)); | |
}; | |
/** | |
* Listen to a topic emitted by a target, | |
* using a handler and a transformer. | |
* | |
* @param {Backbone.Events} target | |
* @param {string} topic | |
* @param {Function} handler | |
* @param {Function} transformer | |
*/ | |
ListenToFacet.prototype.listenTo = function(target, topic, handler, transformer) { | |
this.listener_.listenTo(target, topic, _.bind(function(var_params) { | |
var args = _.argsToArray(arguments); | |
var tranformedParams = transformer.apply(transformer, args); | |
handler.call(this.listener_, tranformedParams); | |
}, this)) | |
}; | |
/** | |
* String#trim method shim | |
* | |
* From: | |
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim | |
*/ | |
if (!String.prototype.trim) { | |
String.prototype.trim = function () { | |
return this.replace(/^\s+|\s+$/g, ''); | |
}; | |
} | |
return function() { | |
return { | |
facets: { | |
/** | |
* ListenTo Facet. | |
* | |
* Binds a listener object to events thrown | |
* by a target object. | |
* | |
* Both objects must mixin Backbone.Events: this facet | |
* uses the Backbone.Events#listenTo / #stopListeningTo | |
* methods to bind events. | |
* | |
* Parameters attached to events can be optionally | |
* transformed by transformer methods. Transformer methods | |
* receive an array of event parameters, and return a | |
* transformed array of arguments to pass on to | |
* the event handler. | |
* | |
* Example: | |
* // Transformer | |
* define('shout', function() { | |
* return function(talker, words) { | |
* return words.toUpperCase(); | |
* } | |
* }); | |
* | |
* wire({ | |
* shout: { module: 'shout' }, | |
* talker: { create: 'talker' }, | |
* listener: { | |
* create: 'listener', | |
* listenTo: { | |
* talker: { | |
* whisper: 'shout | listen' | |
* } | |
* } | |
* } | |
* }); | |
* | |
* talker.trigger('whisper', talker, 'hello there'); | |
* // listener called with: 'HELLO THERE' | |
* | |
* | |
* Transformers can also be referenced from | |
* within an object. | |
* | |
* Example: | |
* define('transformers', { | |
* shout: function(args) { ... } | |
* }); | |
* | |
* wire({ | |
* // ... | |
* transformers: { module: 'transformers' }, | |
* listener: { | |
* listenTo: { | |
* talker: { | |
* whisper: 'transformers.shout | listen' | |
* } | |
* } | |
* } | |
* }); | |
* | |
* | |
* Transform can be specified as: | |
* 'methodName' // A method defined in the spec. | |
* 'otherObj.methodName' // A method of otherObj (where otherObj is defined in the spec) | |
*/ | |
listenTo: { | |
ready: function(resolver, proxy, wire) { | |
var facet = new ListenToFacet(proxy, wire); | |
facet.connect().then(function() { | |
resolver.resolve(); | |
}, resolver.reject); | |
}, | |
destroy: function(resolver, proxy, wire) { | |
// This is crude, but it works. | |
proxy.target.stopListening(); | |
resolver.resolve(); | |
} | |
} | |
} | |
}; | |
}; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define([ | |
'vendor/underscore', | |
'testUtils', | |
'wire', | |
'vendor/backbone' | |
], function(_, testUtil, wire, Backbone) { | |
var plugins = ['application/plugin/events']; | |
var Listener = function() { | |
this.listen = jasmine.createSpy('Listener#listen'); | |
this.listenClosely = jasmine.createSpy('Listener#listenHard'); | |
}; | |
_.extend(Listener.prototype, Backbone.Events); | |
define('listener', function() { | |
return Listener; | |
}); | |
var Talker = function() { | |
}; | |
_.extend(Talker.prototype, Backbone.Events); | |
define('talker', function() { | |
return Talker; | |
}); | |
var throwUncatchable = function(e) { | |
_.defer(function() { | |
throw e; | |
}) | |
}; | |
var loudspeaker = jasmine.createSpy('loudspeaker'). | |
andCallFake(function(talker, words) { | |
return words.toUpperCase(); | |
}); | |
define('loudspeaker', function() { | |
return loudspeaker; | |
}); | |
define('transformers', { | |
loudspeaker: loudspeaker, | |
muffler: function(talk, words) { | |
return words.toLowerCase() | |
} | |
}); | |
describe('The wire listenTo facet', function() { | |
describe('listenTo facet', function () { | |
it('should listen to multiple events from multiple emitters', function () { | |
wire({ | |
talkerA: { | |
create: 'talker' | |
}, | |
talkerB: { | |
create: 'talker' | |
}, | |
listener: { | |
create: 'listener', | |
listenTo: { | |
talkerA: { | |
talk: 'listen', | |
whisper: 'listenClosely' | |
}, | |
talkerB: { | |
talk: 'listen', | |
whisper: 'listenClosely' | |
} | |
} | |
}, | |
plugins: plugins | |
}).then(function (ctx) { | |
ctx.talkerA.trigger('talk', 'hello you'); | |
expect(ctx.listener.listen).toHaveBeenCalledWith('hello you'); | |
ctx.talkerA.trigger('whisper', 'hey guy'); | |
expect(ctx.listener.listenClosely).toHaveBeenCalledWith('hey guy'); | |
testUtil.setFlag(); | |
}).otherwise(throwUncatchable); | |
waitsFor(testUtil.checkFlag, 1000, 'Wire to complete'); | |
}); | |
it('should transform event data', function () { | |
wire({ | |
talker: { | |
create: 'talker' | |
}, | |
loudspeaker: { module: 'loudspeaker' }, | |
listener: { | |
create: 'listener', | |
listenTo: { | |
talker: { | |
whisper: 'loudspeaker | listen' | |
} | |
} | |
}, | |
plugins: plugins | |
}).then(function (ctx) { | |
ctx.talker.trigger('whisper', ctx.talker, 'hey guy'); | |
expect(ctx.listener.listen).toHaveBeenCalledWith('HEY GUY'); | |
testUtil.setFlag(); | |
}).otherwise(throwUncatchable); | |
waitsFor(testUtil.checkFlag, 1000, 'Wire to complete'); | |
}); | |
it('should find a transformer within a namespace', function () { | |
wire({ | |
talker: { | |
create: 'talker' | |
}, | |
transformers: { module: 'transformers' }, | |
listener: { | |
create: 'listener', | |
listenTo: { | |
talker: { | |
whisper: 'transformers.loudspeaker | listen', | |
shout: 'transformers.muffler | listen' | |
} | |
} | |
}, | |
plugins: plugins | |
}).then(function (ctx) { | |
ctx.talker.trigger('whisper', ctx.talker, 'hey guy'); | |
expect(ctx.listener.listen).toHaveBeenCalledWith('HEY GUY'); | |
ctx.talker.trigger('shout', ctx.talker, 'YO DUDE'); | |
expect(ctx.listener.listen).toHaveBeenCalledWith('yo dude'); | |
testUtil.setFlag(); | |
}).otherwise(throwUncatchable); | |
waitsFor(testUtil.checkFlag, 1000, 'Wire to complete'); | |
}); | |
it('should clear all listeners when the context is destroyed', function() { | |
spyOn(Listener.prototype, 'stopListening'); | |
wire({ | |
talkerA: { | |
create: 'talker' | |
}, | |
talkerB: { | |
create: 'talker' | |
}, | |
listener: { | |
create: 'listener', | |
listenTo: { | |
talkerA: { | |
talk: 'listen', | |
whisper: 'listenClosely' | |
}, | |
talkerB: { | |
talk: 'listen', | |
whisper: 'listenClosely' | |
} | |
} | |
}, | |
plugins: plugins | |
}).then(function(ctx) { | |
return ctx.destroy(); | |
}). | |
then(function() { | |
expect(Listener.prototype.stopListening).toHaveBeenCalled(); | |
testUtil.setFlag(); | |
}). | |
otherwise(throwUncatchable); | |
waitsFor(testUtil.checkFlag, 1000, 'Wire to complete'); | |
}); | |
}); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment