lib/messenger/m_source.js
'use strict';
var Mixin = require('ml-mixin')
, MessengerAPI = require('./m_api')
, _ = require('protojs')
, check = require('ml-check')
, Match = check.Match;
/**
* `milo.classes.MessageSource`
* An abstract class (subclass of [Mixin](../abstract/mixin.js.html)) for connecting [Messenger](./index.js.html) to external sources of messages (like DOM events) and defining higher level messages.
* An instance of MessageSource can either be passed to Messenger constructor or later using `_setMessageSource` method of Messenger. Once set, MessageSource of Messenger cannot be changed.
*/
var MessageSource = _.createSubclass(Mixin, 'MessageSource', true);
module.exports = MessageSource;
/**
* ####MessageSource instance methods####
*
* - [init](#init) - initializes messageSource - called by Mixin superclass
* - [setMessenger](#setMessenger) - connects Messenger to MessageSource, is called from `init` or `_setMessageSource` methods of [Messenger](./index.js.html).
* - [onSubscriberAdded](#onSubscriberAdded) - called by Messenger to notify when the first subscriber for an internal message was added, so MessageSource can subscribe to source
* - [onSubscriberRemoved](#onSubscriberRemoved) - called by Messenger to notify when the last subscriber for an internal message was removed, so MessageSource can unsubscribe from source
* - [dispatchMessage](#dispatchMessage) - dispatches source message. MessageSource subclass should implement mechanism when on actual source message this method is called.
*
* Methods below should be implemented in subclass:
*
* - [trigger](#trigger) - triggers messages on the source (an optional method)
* - [addSourceSubscriber](#addSourceSubscriber) - adds listener/subscriber to external message
* - [removeSourceSubscriber](#removeSourceSubscriber) - removes listener/subscriber from external message
*/
_.extendProto(MessageSource, {
init: init,
destroy: MessageSource$destroy,
setMessenger: setMessenger,
onSubscriberAdded: onSubscriberAdded,
onSubscriberRemoved: onSubscriberRemoved,
dispatchMessage: dispatchMessage,
postMessage: postMessage,
_prepareMessengerAPI: _prepareMessengerAPI,
// Methods below must be implemented in subclass
trigger: toBeImplemented,
addSourceSubscriber: toBeImplemented,
removeSourceSubscriber: toBeImplemented
});
/**
* MessageSource instance method.
* Called by Mixin constructor.
* MessageSource constructor should be passed the same parameters as this method signature.
* If an instance of [MessengerAPI](./m_api.js.html) is passed as the third parameter, it extends MessageSource functionality to allow it to define new messages, to filter messages based on their data and to change message data. See [MessengerAPI](./m_api.js.html).
*
* @param {Object} hostObject Optional object that stores the MessageSource on one of its properties. It is used to proxy methods of MessageSource.
* @param {Object[String]} proxyMethods Optional map of method names; key - proxy method name, value - MessageSource's method name.
* @param {MessengerAPI} messengerAPI Optional instance of MessengerAPI.
*/
function init(hostObject, proxyMethods, messengerAPI) {
this._prepareMessengerAPI(messengerAPI);
}
/**
* Destroys message source
*/
function MessageSource$destroy() {
if (this.messengerAPI)
this.messengerAPI.destroy();
}
/**
* MessageSource instance method.
* Sets reference to Messenger instance.
*
* @param {Messenger} messenger reference to Messenger instance linked to this MessageSource
*/
function setMessenger(messenger) {
_.defineProperty(this, 'messenger', messenger);
}
/**
* MessageSource instance method.
* Prepares [MessengerAPI](./m_api.js.html) passed to constructor by proxying its methods to itself or if MessengerAPI wasn't passed defines two methods to avoid checking their availability every time the message is dispatched.
*
* @private
* @param {MessengerAPI} messengerAPI Optional instance of MessengerAPI
*/
function _prepareMessengerAPI(messengerAPI) {
check(messengerAPI, Match.Optional(MessengerAPI));
if (! messengerAPI)
messengerAPI = new MessengerAPI;
_.defineProperty(this, 'messengerAPI', messengerAPI);
}
/**
* MessageSource instance method.
* Subscribes to external source using `addSourceSubscriber` method that should be implemented in subclass.
* This method is called by [Messenger](./index.js.html) when the first subscriber to the `message` is added.
* Delegates to supplied or default [MessengerAPI](./m_api.js.html) for translation of `message` to `sourceMessage`. `MessageAPI.prototype.addInternalMessage` will return undefined if this `sourceMessage` was already subscribed to to prevent duplicate subscription.
*
* @param {String} message internal Messenger message that has to be subscribed to at the external source of messages.
*/
function onSubscriberAdded(message) {
var newSourceMessage = this.messengerAPI.addInternalMessage(message);
if (typeof newSourceMessage != 'undefined')
this.addSourceSubscriber(newSourceMessage);
}
/**
* MessageSource instance method.
* Unsubscribes from external source using `removeSourceSubscriber` method that should be implemented in subclass.
* This method is called by [Messenger](./index.js.html) when the last subscriber to the `message` is removed.
* Delegates to supplied or default [MessengerAPI](./m_api.js.html) for translation of `message` to `sourceMessage`. `MessageAPI.prototype.removeInternalMessage` will return undefined if this `sourceMessage` was not yet subscribed to to prevent unsubscription without previous subscription.
*
* @param {String} message internal Messenger message that has to be unsubscribed from at the external source of messages.
*/
function onSubscriberRemoved(message) {
var removedSourceMessage = this.messengerAPI.removeInternalMessage(message);
if (typeof removedSourceMessage != 'undefined')
this.removeSourceSubscriber(removedSourceMessage);
}
/**
* MessageSource instance method.
* Dispatches sourceMessage to Messenger.
* Mechanism that calls this method when the source message is received should be implemented by subclass (see [DOMEventsSource](../components/msg_src/dom_events.js.html) for example).
* Delegates to supplied or default [MessengerAPI](./m_api.js.html) to create internal message data (`createInternalData`) and to filter the message based on its data and/or message (`filterSourceMessage`).
* Base MessengerAPI class implements these two methods in a trivial way (`createInternalData` simply returns external data, `filterSourceMessage` returns `true`), they are meant to be implemented by subclass.
*
* @param {String} sourceMessage source message received from external source
* @param {Object} sourceData data received from external source
*/
function dispatchMessage(sourceMessage, sourceData) {
var api = this.messengerAPI
, internalMessages = api.getInternalMessages(sourceMessage);
if (internalMessages)
internalMessages.forEach(function (message) {
var internalData = api.createInternalData(sourceMessage, message, sourceData);
var shouldDispatch = api.filterSourceMessage(sourceMessage, message, internalData);
if (shouldDispatch)
this.postMessage(message, internalData);
}, this);
}
/**
* Posts message on the messenger. This method is separated so specific message sources can make message dispatch synchronous by using `postMessageSync`
*
* @param {String} message
* @param {Object} data
*/
function postMessage(message, data) {
this.messenger.postMessage(message, data);
}
function toBeImplemented() {
throw new Error('calling the method of an absctract class');
}