/*jslint bitwise: true, nomen: true, plusplus: true, white: true */
/*!
* Mediator Library v0.9.9
*
*/
(function (global, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
/*jslint bitwise: true, nomen: true, plusplus: true, white: true */
/*!
* Mediator Library v0.9.9
*
*/
(function (global, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
AMD
define(function () {
return factory();
});
} else if (typeof exports !== 'undefined') {
Node/CommonJS
exports.Mediator = factory();
} else {
Browser global
global.Mediator = factory();
}
}(this, function () {
'use strict';
Subscribers are instances of Mediator Channel registrations. We generate an object instance so that it can be updated later on without having to unregister and re-register. Subscribers are constructed with a function to be called, options object, and context.
class Subscriber {
constructor(fn, options, context = {}, channel = null) {
this.fn = fn;
this.channel = channel;
this.context = context;
this.options = options;
};
Mediator.update on a subscriber instance can update its function,context, or options object. It takes in an object and looks for fn, context, or options keys.
update(options = {}) {
Object.assign(this, options);
if (this.channel) {
this.setPriority(this.priority);
}
};
set options(options) {
this.update(options);
};
set context(context) {
this.setHook(context);
this._context = context;
};
get context() {
return this._context;
};
setHook(context) {
let channel = this.channel;
if (channel) {
channel.hook(this, context);
}
}
_reduceCalls() {
Check if the subscriber has options and if this include the calls options
if (this.calls !== undefined) {
Decrease the number of calls left by one
this.calls--;
Once the number of calls left reaches zero or less we need to remove the subscriber
if (this.calls < 1) {
this.remove();
}
}
};
return event remove method
remove() {
let channel = this.channel;
if (channel) {
channel.removeSubscriber(this);
}
};
Dynamic setPriority method
setPriority(priority) {
let channel = this.channel;
if (channel) {
channel.setPriority(this, priority);
}
};
run(data) {
if (!this.channel.stopped
&& !(typeof this.predicate === "function"
&& !this.predicate.apply(this.context, data))) {
Check if the callback should be called
this._reduceCalls();
Execute function.
this.fn.apply(this.context, data)
}
};
}
class Channel {
constructor(namespace, parent, context, hook) {
this.namespace = namespace || "";
this._subscribers = [];
this._channels = new Map();
this._parent = parent;
this.stopped = false;
this.context = context;
this.hook = hook;
};
A Mediator channel holds a list of sub-channels and subscribers to be fired when Mediator.publish is called on the Mediator instance. It also contains some methods to manipulate its lists of data; only setPriority and StopPropagation are meant to be used. The other methods should be accessed through the Mediator instance.
addSubscriber(fn, options, context = this.context) {
return new Subscriber(fn, options, context, this);
};
The channel instance is passed as an argument to the mediator subscriber, and further subscriber propagation can be called with channel.StopPropagation().
stopPropagation() {
this.stopped = true;
};
Channel.setPriority is useful in updating the order in which Subscribers are called, and takes an identifier (subscriber id or named function) and an array index. It will not search recursively through subchannels.
setPriority(subscriber, priority) {
let subscribers = this._subscribers,
index = subscribers.indexOf(subscriber);
if (index !== -1) {
subscribers.splice(subscribers.indexOf(subscriber), 1);
}
if (priority !== undefined && priority < this._subscribers.length) {
subscribers.splice(priority, 0, subscriber);
} else {
subscribers.push(subscriber);
}
};
hasChannel(channel) {
return this._channels.has(channel);
};
getChannel(channel) {
return this._channels.get(channel);
};
setChannel(namespace, readOnly) {
if (!this.hasChannel(namespace) && !readOnly) {
let channel = new Channel((this.namespace ? this.namespace + ':' : '') + namespace, this, this.context, this.hook);
this._channels.set(namespace, channel);
return channel;
} else {
return this.getChannel(namespace)
}
};
returnChannel(channels, readOnly) {
if (channels && channels.length > 0) {
let channel = channels.shift(),
returnChannel = this.setChannel(channel, readOnly);
if (returnChannel && channels.length > 0) {
return returnChannel.returnChannel(channels, readOnly);
} else {
return returnChannel;
}
}
};
removeSubscriber(subscriber) {
let subscribers = this._subscribers,
index = subscribers.indexOf(subscriber);
If we don’t pass in an value, we’re clearing all
if (!subscriber) {
subscribers.splice(0, subscribers.length);
} else if (index !== -1) {
subscribers.splice(index, 1);
}
if (this._subscribers.length === 0 && this._parent) {
this._parent.removeChannel(this);
}
};
removeChannel(channel) {
if (channel === this.getChannel(channel.namespace)) {
this._channels.delete(channel.namespace);
}
};
clear() {
this._channels.forEach(channel=>channel.clear());
this.removeSubscriber();
};
This will publish arbitrary arguments to a subscriber and then to parent channels.
publish(data) {
slice method are cloning array, means default array can remove handlers
this._subscribers.slice().forEach(subscriber=>subscriber.run(data));
if (this._parent) {
this._parent.publish(data);
}
this.stopped = false;
};
}
class Mediator {
constructor(context = {}, hook = ()=> {
}) {
if (!(this instanceof Mediator)) {
return new Mediator(context, hook);
}
this.channel = new Channel('', false, context, hook);
}
A Mediator instance is the interface through which events are registered and removed from publish channels.
Returns a channel instance based on namespace, for example application:chat:message:received. If readOnly is true we will refrain from creating non existing channels.
getChannel(namespace, readOnly) {
let namespaceHierarchy = namespace.split(':');
if (namespaceHierarchy.length > 0) {
return this.channel.returnChannel(namespaceHierarchy, readOnly);
}
};
Pass in a channel namespace, function to be called, options, and context to call the function in to Subscribe. It will create a channel if one does not exist. Options can include a predicate to determine if it should be called (based on the data published to it) and a priority index.
subscribe(channelName, fn, options = {}, context) {
if (channelName && channelName !== '') {
let channel = this.getChannel(channelName, false);
return channel.addSubscriber(fn, options, context);
} else {
throw Error('Namespace should be provided!');
}
};
Pass in a channel namespace, function to be called, options, and context to call the function in to Subscribe. It will create a channel if one does not exist. Options can include a predicate to determine if it should be called (based on the data published to it) and a priority index.
once(channelName, fn, options = {}, context) {
options.calls = 1;
return this.subscribe(channelName, fn, options, context);
};
Publishes arbitrary data to a given channel namespace. Channels are called recursively downwards; a post to application:chat will post to application:chat:receive and application:chat:derp:test:beta:bananas. Called using Mediator.publish(“application:chat”, [ args ]);
publish(channelName, ...args) {
if (channelName && channelName !== '') {
let channel = this.getChannel(channelName, true);
if (channel && channel.namespace === channelName) {
args.push(channel)
channel.publish(args);
}
}
};
clear() {
this.channel.clear();
};
}
Alias some common names for easy interop
Mediator.prototype.on = Mediator.prototype.subscribe;
Mediator.prototype.trigger = Mediator.prototype.publish;
Finally, expose it all.
Mediator.version = "0.9.9";
return Mediator;
}));