milo.Messenger
A generic Messenger class that is used for all kinds of messaging in milo. It is subclassed from Mixin and it proxies its methods to the host object for convenience.
All facets and components have messenger attached to them. Messenger class interoperates with MessageSource class that connects the messenger to some external source of messages (e.g., DOM events) and MessengerAPI class that allows to define higher level messages than messages that exist on the source.
Messenger class is used internally in milo and can be used together with any objects/classes in the application.
milo also defines a global messenger milo.mail that dispatches domready
event and can be used for any application wide messaging.
To initialize your app after DOM is ready use:
milo.mail.on('domready', function() {
// application starts
});
or the following shorter form of the same:
milo(function() {
// application starts
});
var Messenger = _.createSubclass(Mixin, 'Messenger');
var messagesSplitRegExp = Messenger.messagesSplitRegExp = /\s*(?:\,|\s)\s*/;
"Private" methods
_.extendProto(Messenger, {
init: init, // called by Mixin (superclass)
destroy: Messenger$destroy,
on: Messenger$on,
once: Messenger$once,
onceSync: Messenger$onceSync,
onSync: Messenger$onSync,
onAsync: Messenger$onAsync,
onMessage: Messenger$on, // deprecated
off: Messenger$off,
offMessage: Messenger$off, // deprecated
onMessages: onMessages,
offMessages: offMessages,
offAll: Messenger$offAll,
postMessage: postMessage,
postMessageSync: postMessageSync,
getSubscribers: getSubscribers,
getMessageSource: getMessageSource,
_chooseSubscribersHash: _chooseSubscribersHash,
_registerSubscriber: _registerSubscriber,
_removeSubscriber: _removeSubscriber,
_removeAllSubscribers: _removeAllSubscribers,
_callPatternSubscribers: _callPatternSubscribers,
_callSubscribers: _callSubscribers,
_callSubscriber: _callSubscriber,
_setMessageSource: _setMessageSource
});
A default map of proxy methods used by ComponentFacet and Component classes to pass to Messenger when it is instantiated.
This map is for convenience only, it is NOT used internally by Messenger, a host class should pass it for methods to be proxied this way.
Messenger.defaultMethods = {
on: 'on',
onSync: 'onSync',
once: 'once',
onceSync: 'onceSync',
off: 'off',
onMessages: 'onMessages',
offMessages: 'offMessages',
postMessage: 'postMessage',
postMessageSync: 'postMessageSync',
getSubscribers: 'getSubscribers'
};
module.exports = Messenger;
Messenger.subscriptions = [];
Option name | Type | Description |
---|---|---|
hostObject | Object | Optional object that stores the messenger on one of its properties. It is used to proxy methods of messenger and also as a context for subscribers when they are called by the Messenger. See |
proxyMethods | Object | Optional map of method names; key - proxy method name, value - messenger's method name. |
messageSource | MessageSource | Optional messageSource linked to the messenger. If messageSource is supplied, the reference to the messenger will stored on its 'messenger' property |
Messenger instance method
Initializes Messenger. Method is called by Mixin class constructor.
See on method, Messenger class above and MessageSource class.
function init(hostObject, proxyMethods, messageSource) {
// hostObject and proxyMethods are used in Mixin and checked there
if (messageSource)
this._setMessageSource(messageSource);
_initializeSubscribers.call(this);
}
function _initializeSubscribers() {
_.defineProperties(this, {
_messageSubscribers: {},
_patternMessageSubscribers: {},
}, _.CONF);
}
Destroys messenger. Maybe needs to unsubscribe all subscribers
function Messenger$destroy() {
this.offAll();
var messageSource = this.getMessageSource();
if (messageSource)
messageSource.destroy();
}
Option name | Type | Description |
---|---|---|
messages | Message types that should envoke the subscriber. If string is passed, it can be a sigle message or multiple message types separated by whitespace with optional commas. |
|
subscriber | Function,Object | Message subscriber - a function that will be called when the message is dispatched on the messenger (usually via proxied postMessage method of host object). If hostObject was supplied to Messenger constructor, hostObject will be the context (the value of this) for the subscriber envocation. |
return | Boolean |
Messenger instance method.
Registers a subscriber function for a certain message(s).
This method returns true
if the subscription was successful. It can be unsuccessful if the passed subscriber has already been subscribed to this message type - double subscription never happens and it is safe to subscribe again - no error or warning is thrown or logged.
Subscriber is passed two parameters: message
(string) and data
(object). Data object is supplied when message is dispatched, Messenger itself adds nothing to it. For example, events facet sends actual DOM event when it posts message.
Usage:
// subscribes onMouseUpDown to two DOM events on component via events facet.
myComp.events.on('mousedown mouseup', onMouseUpDown);
function onMouseUpDown(eventType, event) {
// ...
}
myComp.data.on(/.+/, function(msg, data) {
logger.debug(msg, data);
}); // subscribes anonymous function to all non-empty messages on data facet
// it will not be possible to unsubscribe anonymous subscriber separately,
// but myComp.data.off(/.+/) will unsubscribe it
If messenger has MessageSource attached to it, MessageSource will be notified when the first subscriber for a given message is added, so it can subscribe to the source.
Components and facets change this method name to on
when they proxy it.
See postMessage.
function Messenger$on(messages, subscriber) {
return _Messenger_onWithOptions.call(this, messages, subscriber);
}
function Messenger$once(messages, subscriber) {
return _Messenger_onWithOptions.call(this, messages, subscriber, { dispatchTimes: 1 });
}
function Messenger$onceSync(messages, subscriber) {
return _Messenger_onWithOptions.call(this, messages, subscriber, { dispatchTimes: 1, sync: true });
}
function Messenger$onSync(messages, subscriber) {
return _Messenger_onWithOptions.call(this, messages, subscriber, { sync: true });
}
function Messenger$onAsync(messages, subscriber) {
return _Messenger_onWithOptions.call(this, messages, subscriber, { sync: false });
}
function _Messenger_onWithOptions(messages, subscriber, options) {
check(messages, Match.OneOf(String, [String], RegExp));
check(subscriber, Match.OneOf(Function, {
subscriber: Function,
context: Match.Any,
options: Match.Optional(Object),
}));
if (typeof subscriber == 'function') {
subscriber = {
subscriber: subscriber,
context: this._hostObject,
};
}
if (options) {
subscriber.options = subscriber.options || {};
_.extend(subscriber.options, options);
}
return _Messenger_on.call(this, messages, subscriber);
}
function _Messenger_on(messages, subscriber) {
_.defineProperty(subscriber, '__messages', messages);
return _eachMessage.call(this, '_registerSubscriber', messages, subscriber);
}
function _eachMessage(methodName, messages, subscriber) {
if (typeof messages == 'string')
messages = messages.split(messagesSplitRegExp);
var subscribersHash = this._chooseSubscribersHash(messages);
if (messages instanceof RegExp)
return this[methodName](subscribersHash, messages, subscriber);
else {
var changed = false;
messages.forEach(function(message) {
var subscriptionChanged = this[methodName](subscribersHash, message, subscriber);
changed = changed || subscriptionChanged;
}, this);
return changed;
}
}
Option name | Type | Description |
---|---|---|
list | list of subscribers |
|
subscriber | Function,Object | subscriber function or object with properties |
Finds subscriber index in the list
function _indexOfSubscriber(list, subscriber) {
var self = this;
return _.findIndex(list, function(subscr){
return subscriber.subscriber == subscr.subscriber
&& subscriber.context == subscr.context
});
}
Option name | Type | Description |
---|---|---|
messageSubscribers | Map of message subscribers to be added |
|
return |
Messenger instance method.
Subscribes to multiple messages passed as map together with subscribers.
Usage:
myComp.events.onMessages({
'mousedown': onMouseDown,
'mouseup': onMouseUp
});
function onMouseDown(eventType, event) {}
function onMouseUp(eventType, event) {}
Returns map with the same keys (message types) and boolean values indicating whether particular subscriber was added.
It is NOT possible to add pattern subscriber using this method, as although you can use RegExp as the key, JavaScript will automatically convert it to string.
function onMessages(messageSubscribers) {
check(messageSubscribers, Match.ObjectHash(Match.OneOf(Function, { subscriber: Function, context: Match.Any })));
var notYetRegisteredMap = _.mapKeys(messageSubscribers, function(subscriber, messages) {
return this.on(messages, subscriber);
}, this);
return notYetRegisteredMap;
}
Option name | Type | Description |
---|---|---|
messages | Message types that a subscriber should be removed for. If string is passed, it can be a sigle message or multiple message types separated by whitespace with optional commas. |
|
subscriber | Function | Message subscriber - Optional function that will be removed from the list of subscribers for the message(s). If subscriber is not supplied, all subscribers will be removed from this message(s). |
return | Boolean |
Messenger instance method.
Removes a subscriber for message(s). Removes all subscribers for the message if subscriber isn't passed.
This method returns true
if the subscriber was registered. No error or warning is thrown or logged if you remove subscriber that was not registered.
Components and facets change this method name to off
when they proxy it.
Usage:
// unsubscribes onMouseUpDown from two DOM events.
myComp.events.off('mousedown mouseup', onMouseUpDown);
If messenger has MessageSource attached to it, MessageSource will be notified when the last subscriber for a given message is removed and there is no more subscribers for this message.
function Messenger$off(messages, subscriber) {
check(messages, Match.OneOf(String, [String], RegExp));
check(subscriber, Match.Optional(Match.OneOf(Function, {
subscriber: Function,
context: Match.Any,
options: Match.Optional(Object),
// __messages: Match.Optional(Match.OneOf(String, [String], RegExp))
})));
return _Messenger_off.call(this, messages, subscriber);
}
function _Messenger_off(messages, subscriber) {
return _eachMessage.call(this, '_removeSubscriber', messages, subscriber);
}
Option name | Type | Description |
---|---|---|
messageSubscribers | Map of message subscribers to be removed |
|
return |
Messenger instance method.
Unsubscribes from multiple messages passed as map together with subscribers.
Returns map with the same keys (message types) and boolean values indicating whether particular subscriber was removed.
If a subscriber for one of the messages is not supplied, all subscribers for this message will be removed.
Usage:
myComp.events.offMessages({
'mousedown': onMouseDown,
'mouseup': onMouseUp,
'click': undefined // all subscribers to this message will be removed
});
It is NOT possible to remove pattern subscriber(s) using this method, as although you can use RegExp as the key, JavaScript will automatically convert it to string.
function offMessages(messageSubscribers) {
check(messageSubscribers, Match.ObjectHash(Match.Optional(Match.OneOf(Function, { subscriber: Function, context: Match.Any }))));
var subscriberRemovedMap = _.mapKeys(messageSubscribers, function(subscriber, messages) {
return this.off(messages, subscriber);
}, this);
return subscriberRemovedMap;
}
Unsubscribes all subscribers
function Messenger$offAll() {
_offAllSubscribers.call(this, this._patternMessageSubscribers);
_offAllSubscribers.call(this, this._messageSubscribers);
}
function _offAllSubscribers(subscribersHash) {
_.eachKey(subscribersHash, function(subscribers, message) {
this._removeAllSubscribers(subscribersHash, message);
}, this);
}
// TODO - send event to messageSource
Option name | Type | Description |
---|---|---|
message | String,RegExp | message to be dispatched If the message is a string, the subscribers registered with exactly this message will be called and also pattern subscribers registered with the pattern that matches the dispatched message. |
data | Any | data that will be passed to the subscriber as the second parameter. Messenger does not modify this data in any way. |
callback | Function | optional callback to pass to subscriber |
_synchronous | Boolean | if true passed, subscribers will be envoked synchronously apart from those that have |
Messenger instance method.
Dispatches the message calling all subscribers registered for this message and, if the message is a string, calling all pattern subscribers when message matches the pattern.
Each subscriber is passed the same parameters that are passed to theis method.
The context of the subscriber envocation is set to the host object (this._hostObject
) that was passed to the messenger constructor.
Subscribers are called in the next tick ("asynchronously") apart from those that were subscribed with onSync
(or that have options.sync == true
).
function postMessage(message, data, callback, _synchronous) {
check(message, Match.OneOf(String, RegExp));
check(callback, Match.Optional(Function));
var subscribersHash = this._chooseSubscribersHash(message);
var msgSubscribers = subscribersHash[message];
this._callSubscribers(message, data, callback, msgSubscribers, _synchronous);
if (typeof message == 'string')
this._callPatternSubscribers(message, data, callback, msgSubscribers, _synchronous);
}
Option name | Type | Description |
---|---|---|
message | String,RegExp | |
data | Any | |
callback | Function |
Same as postMessage apart from envoking subscribers synchronously, apart from those subscribed with onAsync
(or with options.sync == false
).
function postMessageSync(message, data, callback) {
this.postMessage(message, data, callback, true);
}
Option name | Type | Description |
---|---|---|
message | String,RegExp | Message to get subscribers for. If the message is RegExp, only pattern subscribers registered with exactly this pattern will be returned. |
includePatternSubscribers | Boolean | Optional false to prevent inclusion of patter subscribers, by default they are included. |
return | Array |
Messenger instance method.
Returns the array of subscribers that would be called if the message were dispatched.
If includePatternSubscribers === false
, pattern subscribers with matching patters will not be included (by default they are included).
If there are no subscribers to the message, undefined
will be returned, not an empty array, so it is safe to use the result in boolean tests.
function getSubscribers(message, includePatternSubscribers) {
check(message, Match.OneOf(String, RegExp));
var subscribersHash = this._chooseSubscribersHash(message);
var msgSubscribers = subscribersHash[message]
? [].concat(subscribersHash[message])
: [];
// pattern subscribers are incuded by default
if (includePatternSubscribers !== false && typeof message == 'string') {
_.eachKey(this._patternMessageSubscribers,
function(patternSubscribers) {
var pattern = patternSubscribers.pattern;
if (patternSubscribers && patternSubscribers.length
&& pattern.test(message))
_.appendArray(msgSubscribers, patternSubscribers);
}
);
}
// return undefined if there are no subscribers
return msgSubscribers.length
? msgSubscribers
: undefined;
}
Option name | Type | Description |
---|---|---|
messageSource | MessageSource | an instance of MessageSource class to attach to this messenger (and to have this messenger attached to it too) |
Messenger instance method
Sets MessageSource for the messenger also setting the reference to the messenger in the MessageSource.
MessageSource can be passed to message constructor; this method allows to set it at a later time. For example, the subclasses of ComponentFacet use this method to set different MessageSource'es in the messenger that is created by ComponentFacet.
Currently the method is implemented in such way that it can be called only once - MessageSource cannot be changed after this method is called.
function _setMessageSource(messageSource) {
check(messageSource, MessageSource);
_.defineProperty(this, '_messageSource', messageSource);
messageSource.messenger = this;
}
Messenger instance method
Returns messenger MessageSource
function getMessageSource() {
return this._messageSource
}