Git Product home page Git Product logo

mediator.js's Introduction

Mediator.js

Build Status

Version 0.11.0

A light utility class to help implement the Mediator pattern for easy eventing

Mediator is a simple class that allows you to register, unregister, and call subscriber methods to help event-based, asyncronous programming. Its purpose is to make the usage of WebSockets, Ajax calls, DOM events, or any other asynchronous operations easy to maintain and test.

Mediator has no dependencies on any other libraries.

1.12kb, minifed and gzipped

Why?

My specific use case: bind elements easily for WebSocket callbacks. But, you may find usage in it for all kinds of things: as an event management system, to decouple calls between javascript functions, Ajax request callbacks, and more. There's an excellent online book that talks about Mediators more in detail by Addy Osmani.

Usage

Using in Node

The package is in NPM as mediator-js. Include it in your project like so:

var Mediator = require("mediator-js").Mediator,
    mediator = new Mediator();

mediator.subscribe("wat", function(){ console.log(arguments); });
mediator.publish("wat", 7, "hi", { one: 1 });

Using in the Browser

Mediator.js is compatible with browser module-loading solutions, including but not limited to Browserify, Almond.js, Require.js, and others.

Note: if using AMD / Almond module loading, use the NPM package name: require("mediator-js").Mediator

<script src="/js/Mediator.min.js"></script>

<script>
  var Mediator = require("mediator-js").Mediator,
      mediator = new Mediator();

  mediator.subscribe("wat", function(){ console.log(arguments); });
  mediator.publish("wat", 7, "hi", { one: 1 });
</script>

API

You can register events with the mediator two ways using channels. You can add a predicate to perform more complex matching. Instantiate a new mediator, and then you can being subscribing, removing, and publishing.

To use it in the browser, include mediator.min.js from the root here, or the unminified version at lib/mediator.js.

Subscription signature: var mediator = new Mediator();

mediator.subscribe(channel, callback, <options>, <context>);
mediator.publish(channel, <data, data, ... >)
mediator.remove(channel, <identifier>)

Additionally,

  • subscribe: is alias for on and bind
  • publish: is alias for trigger and emit
  • off: is an alias for remove
  • once: can be used to subscribe to an event that should only be fired once.

Subscriber signature:

function(<data, data ...>, channel);

The channel is always returned as the last argument to subscriber functions.

Mediator.subscribe options (all are optional; default is empty):

{
  predicate: function(*args){ ... }
  priority: 0|1|... 
  calls: 1|2|...
}

Predicates return a boolean and are run using whatever args are passed in by the publishing class. If the boolean is true, the subscriber is run.

Priority marks the order in which a subscriber is called.

calls allows you to specify how many times the subscriber is called before it is automatically removed. This is decremented each time it is called until it reaches 0 and is removed. If it has a predicate and the predicate does not match, calls is not decremented.

A Subscriber object is returned when calling Mediator.subscribe. It allows you to update options on a given subscriber, or to reference it by an id for easy removal later.

{
  id, // guid
  fn, // function
  options, // options
  context, // context for fn to be called within
  channel, // provides a pointer back to its channel
  update(options){ ...} // update the subscriber ({ fn, options, context })
}

Examples:

var mediator = new Mediator();

// Alert data when the "message" channel is published to
// Subscribe returns a "Subscriber" object
mediator.subscribe("message", function(data){ alert(data); });
mediator.publish("message", "Hello, world");

// Alert the "message" property of the object called when the predicate function returns true (The "From" property is equal to "Jack")
var predicate = function(data){ return data.From === "Jack" };
mediator.subscribe("channel", function(data){ alert(data.Message); }, { predicate: predicate });
mediator.publish("channel", { Message: "Hey!", From: "Jack" }); //alerts
mediator.publish("channel", { Message: "Hey!", From: "Audrey" }); //doesn't alert

You can remove events by passing in a channel, or a channel and the function to remove or subscriber id. If you only pass in a channel, all subscribers are removed.

// removes all methods bound directly to a channel, but not subchannels
mediator.remove("channel");

// unregisters *only* MethodFN, a named function, from "channel"
mediator.remove("channel", MethodFN);

You can call the registered functions with the Publish method, which accepts an args array:

mediator.publish("channel", "argument", "another one", { etc: true });

You can namespace your subscribing / removing / publishing as such:

mediator.subscribe("application:chat:receiveMessage", function(data){ ... });

// will call parents of the application:chat:receiveMessage namespace
// (that is, next it will call all subscribers of application:chat, and then
// application). It will not recursively call subchannels - only direct subscribers.
mediator.publish("application:chat:receiveMessage", "Jack Lawson", "Hey");

You can update Subscriber priority:

var sub = mediator.subscribe("application:chat", function(data){ ... });
var sub2 = mediator.subscribe("application:chat", function(data){ ... });

// have sub2 executed first
mediator.getChannel("application:chat").setPriority(sub2.id, 0);

You can update Subscriber callback, context, and/or options:

sub.update({ fn: ..., context: { }, options: { ... });

You can stop the chain of execution by calling channel.stopPropagation():

// for example, let's not post the message if the from and to are the same
mediator.subscribe("application:chat", function(data, channel){
  alert("Don't send messages to yourself!");
  channel.stopPropagation();
}, options: {
  predicate: function(data){ return data.From == data.To },
  priority: 0
});

Changelog

Version 0.11.0

Version 0.10.1

  • Accepted a PR that fixed some AMD require issues, and allowed upwards- recursing events to fire on events even where the most-specific event is not fired (a listener on "application:chat:message" will fire on an event like "application:chat:message:jack", even if there's no matching listenener on "jack".) #45

Version 0.9.8

  • Accepted a ton of PRs from tbusser that fixed some issues and improved performance.

Version 0.9.7

  • Fixed bug where subscribers that failed predicates were decrementing calls.

Version 0.9.6

  • Fixed AMD-style export; export constructor, not instance

Version 0.9.5

  • Fixed issue with requring from node

Version 0.9.4

  • Fixed issue with auto-removing subscribers after a maximum amount of calls

Version 0.9.3

  • Make AMD name match npm package name mediator-js. (Previously used Mediator.js.)

Version 0.9.1

  • Fixed AMD / define syntax
  • Exposed Mediator.version

Version 0.9.0

  • Reversed order of recursion: now calls parents instead of children channels
  • Lowercase methods
  • Aliases: on and bind are aliased to subscribe, and trigger and emit are bound to publish. off is an alias for remove.
  • Moved tests to mocha from jasmine
  • Supports AMD, requirejs, and browser loading
  • Lots of cleanup around extra variables, and jslinted
  • Published to NPM under "mediator-js"
  • Added travis-ci build

Version 0.6.1

  • Cleaned up some typos
  • Save pointer to channel within subscription
  • Save namespace in channel
  • Fixed bugs in SetPriority

Version 0.6.0

  • Added ability to stop the chain of calls using c.stopPropagation()

Version 0.5.0

  • Added ability to access and update subscribing objects
    • Subscribers now have a unique ID and can be queried by id or by function
    • Subscriber class can have its function, context, or options updated
    • Subscriber priority can be updated post-addition
    • Channels made public by Mediator.GetChannel
    • Added a little performance test

Version 0.4.2

  • Added Priority to calls, allowing you to set callback index

Version 0.4.1

  • Minor internal updates

Version 0.4.0

  • Predicate no longer acts as a channel and is moved to an options object at the end of the subcription call.
  • Signatures changed; context moved to the end of subscriptions
  • Namespacing for subscription binding

License

This class and its accompanying README and are MIT licensed.

In Closing

Have fun, and please submit suggestions and improvements! You can leave any issues here, or contact me at (@ajacksified).

mediator.js's People

Contributors

ajacksified avatar tbusser avatar msemenistyi avatar twidi avatar brandonhamilton avatar carlostf avatar andrei-cacio avatar aldaviva avatar yanhkim avatar passy avatar richierunner avatar toby-farley avatar pedroetb avatar

Stargazers

Abhishiv Saxena avatar Pengoose avatar Paul Russo avatar dexter dev avatar Hulkee avatar InSeong-So avatar Cat  avatar  avatar Yasin ATEŞ avatar Abbas Hussain avatar Christian avatar Christopher Kloß avatar Wellington Carlos Massola avatar  avatar ko-kamenashi avatar The Idea Dood avatar YPanda avatar Gabriel Fernandes avatar  avatar Scott Falkingham avatar Chandu avatar Mehmet Kamil Morçay avatar InternetGuy avatar SumoSudo avatar polybius avatar Roland Leitenberger avatar Sergey Fedorov avatar Uk avatar pengjielee avatar Consuella Moore avatar  avatar Kyle Fong avatar Reyn avatar Michael Spiegel avatar Gabriel Jiménez avatar Vijay avatar Vlad Tyshkevich avatar DCArt avatar Alexander Tipugin avatar  avatar Andrew Bell avatar GUAN avatar Mini Tang avatar ieatnoodles avatar Muhammad Ghazali avatar betgar avatar 弈天 avatar  avatar Helton Carlos de Souza avatar Sean Brown avatar hoangitk avatar Alaa Al-Maliki avatar Tianzuo.Zhou avatar yuanzm avatar Alfeu S avatar LEI avatar YGZX avatar esteban avatar 小辉辉 avatar Gianluca Esposito avatar  avatar Lukasz Ostrowski avatar liuyongjia avatar  avatar Laumond Arnaud avatar Eder Eduardo avatar Avinash  avatar Nicolas Quiceno B avatar Kevin Weber avatar Patrick Huang avatar Widada avatar EM avatar Paul avatar  avatar Nathan Hood avatar Su avatar Habib MAALEM avatar g-hound avatar yangyi fu avatar Gurpreet Singh avatar John Austin avatar Jerfferson Ferreira avatar Mario Tilli avatar yinjiaji avatar venoral avatar qian.li avatar SimBoo avatar Alexandr Andreev avatar Joel Cordeiro avatar Dzulqarnain Nasir avatar Cruz Boon avatar Eric Kim avatar 梵高先生 avatar Sirwan Afifi avatar SuperWalle avatar Stefanie Strosin avatar Zach Frank avatar Leo Furze-Waddock avatar John Larson avatar Gastón Ambrogi avatar

Watchers

Viktor Karagyaurov avatar Deni avatar ND avatar  avatar Mehdi Lefebvre avatar Evaldo Barbosa avatar James Cloos avatar liuguanyu avatar Robert Yao avatar Michael Anthony avatar  avatar  avatar Luciano avatar  avatar  avatar venoral avatar Paul avatar OviKosta avatar

mediator.js's Issues

Infinite mediator.once

Consider the code:

mediator.once("channel",function(){console.log(1);mediator.publish("channel");});
mediator.publish("channel");

This works infinitely though "once" is written instead of "subscribe"

multiple once for the same subscriber

It seems that if I subscribe twice to the same channel with the same subscriber with the "once" method, then if I "publish", the channel will be executed twice for the subscriber !

I thought "once" would have meant :

  • one subscription by subscriber, avoiding multiple subscriptions for the same subscriber.
  • when published once, then unsubscribed (that part is ok).

Example :

mediator.once("test", function () { print("Hello world!"); });
mediator.once("test", function () { print("Hello world!"); });
mediator.publish("test");

...seem to print :

"Hello world!" "Hello world!"

Priority not working?

I've got a Chaplin app I'm working on, and I'm having issues using the priority on subscriptions.... Here's a little code:

// from my "LayoutView":
mediator.subscribe('log:toggle', this.sizeViews, { priority: 9 });

// from my "LogView":
mediator.subscribe('log:toggle', this.toggleMe, { priority: 1 });

I have a console.log() in both handlers, and no matter what, the one from my Layout view is called first... I tried it at priority 0, 2 and 9... I want the one from LeftSidebar to fire first...

What am I doing wrong?

I may end up just adding a publish to the LogView stating when it's done, and the subscribe to that in my LayoutView, but that feels like the easy way out. =)

Behaviour for publishing to non-existent channel has changed

PR #31 introduced a change in behaviour which might break existing implementations of the Mediator. The change to getChannel() on the Mediator object has had an unexpected consequence.

When you have a channel named root:sub1 and you post a message to root:sub1:subA the message will bubble through the hierarchy as expected. The problem is that subscribers will no longer be able to tell the message was originally posted to root:sub1:subA.

Before the PR this worked because the publish method create the non-existing root:sub1:subA and call the subscribers with this newly created Channel object. Due to the change the subscribers will now get called with a Channel which has a namespace root:sub as this is the most specific channel we were able to find.

I am not sure if this is going to cause any problems for anyone already using the Mediator from before PR #31 was merged. One possible solution could be to push the namespace the publish method was called with into the arguments array like this:

function publish(channelName) {
  ...
  var args = Array.prototype.slice.call(arguments, 1);
  args.push(channel);
  args.push(channelName);
  channel.publish(args);
  ...
}

Mediator across modules

Can I use Mediator to publish and subscribe across different files loaded with require in node.js and browserify?

Question about removing subscriptions

I'm using Mediator to allow multiple backbone views to subscribe to a single channel. Each of these views do something different with the data, and may be removed from the DOM at any point.

Upon removal, I am attempting to unsubscribe (via Remove) as part of the view's destruction. Calling remove while destroying one view removes all of the other view's subscriptions to the channel. I'm assuming I'm doing something wrong, or at least not properly referencing the callback I'm attempting to remove?

Is there a way to unsubscribe only one "listener" from a given channel?

Thread of execution depends on event's listeners.

If we have a few of subscribers and one of those throws an exception, our thread of execution well be interrupted.

For example:

var hub = new Mediator();
hub.on('test', function func1() { console.log(1); });
hub.on('test', function func2() { throw new Error('test error'); });
hub.on('test', function func3() { console.log(3); });

hub.trigger('test');
console.log(4);

Actual result:

1
throwing exception

Expected result:

1
3
4
and throwing exception with stacktrace

Is there a way to wait until all listeners have completed (async) tasks?

Hi,

I am using MediatorJS with requireJS modules. After a page change all my listeners will be informed and they will execute some tasks. Some tasks are async. This has the effect that this will not work constantly:

                    showLoader();

                    // Publish page change
                    mediator_.publish(
                        config_.mediator.channel.afterContentNavigation,
                        {
                            "content": $frameDoc,
                            "page": page
                        }
                    );

                    hideLoader();

As you can imagine if a components listens to the afterContentNavigation it will be triggered sure, but the hideLoader() function will may be called before the async task is completed.

Is there a way to handle such things with Mediator-JS?

can i get last publish value

I want to get the last value of 'mediator.publish()'.
like this:
mediator.publish("message", "Hello, world");
and then ,i can get 'Hello,world' by :mediator.getlastpuslish('message')

Default registration with module "mediator-js" causing problem with requirejs

I am using require-js to load mediator.js as a dependency. When I register using any name in requirejs config, the module does not load as it gets referenced with a default name "mediator-js" as can be seen in the mediator.js source.
Can this default behavior be removed ?, so that we may reference it using any other name other than "mediator-js" , I was trying to use "mediatorjs" instead in my requirejs config.

requirejs config
requirejs.config({
baseUrl: './assets/js/',
paths: {
"jquery": 'jquery-1.12.4.min',
"jqueryui": 'jquery-ui.min',
"jquerylayout":'jquery.layout-1.4.0',
"mediator-js":'mediator/mediator' // trying to use "mediatorjs" does not link it to the module.
}
});

Channels are not called recursively if the first one is not matched

If I listen to 'x:y' and I emit to 'x:y:z', my listener won't trigger. However, if I add a listener for 'x:y:z', then both will trigger. Is this intended? I would expect all 'parent' channels to be visited always, regardless of any gaps in the listeners.
What is the purpose of the namespace check in Mediator.publish? It seems to be the cause.

Best regards

Question: constructor style

What does this for ? i dont understand
can you explain it to me?
inside the Subscriber function :
if (!(this instanceof Subscriber)) { return new Subscriber(fn, options, context); }

Consider supporting Meteor package format

In Meteor, packages are exported by assigning to a global (by omitting the var keyword)*.

Please consider adding it at the top of your library. That way, it wouldn't need to be repackaged for Meteor each time you release a new version.

The standard method looks like this:

if (typeof Package !== 'undefined') {
  // Meteor
  Mediator = factory();
} else if (typeof define === 'function' && define.amd) {
  // AMD
  define('mediator-js', [], function() {
    global.Mediator = factory();
    return global.Mediator;
  });
} else if (typeof exports !== 'undefined') {
  // Node/CommonJS
  exports.Mediator = factory();
} else {
  // Browser global
  global.Mediator = factory();
}

The Package check must go on top to prevent the Node style export further down.

Thanks for considering this.

(* Behind the scenes, Meteor creates the variable in a private scope, wraps the library code in an IIFE, and finally exports the var through its own means)

requirejs always brings up mediator as undefined

I am having problems loading mediator with requirejs (2.1.15) - when using it as a requirement for some modules

define(["mediator-js"], function(mediator_) {
    console.log(mediator_);
});

returns undefined to the browser console. What am I doing wrong?

requirejs not working, undefined after define call in other modules

I am trying to use this (v 0.9.0) with requirejs v 2.1.2, and ran into the following issue. When loading mediator like so:

define(['mediator'], function(Mediator) {
  console.log(Mediator);
});

Mediator is undefined. Looking at the amd section, I noticed it was done differently than some other modules I'm using (mustache to be exact). I changed mediator to call requirejs properly. Here's a diff:

22c22
<   } else if(typeof root.define === 'function' && root.define.amd) {

---
>   } else if(typeof define === 'function' && define.amd) {
24,27c24
<     root.define([], function() {
<       // Export to global too, for backward compatiblity
<       root.Mediator = factory();
<     });

---
>     define(factory());

New code looks like this:

  } else if(typeof define === 'function' && define.amd) {
    // AMD
    define(factory());
  }

I'm now getting the Mediator object properly. I'm not sure if you still need to check "root.define" and "root.define.amd", but the define function should be global at this point anyways if require is there. I've tested it with this:

  } else if(typeof root.define === 'function' && root.define.amd) {
    // AMD
    root.define(factory());
  }

and it worked fine as well. I think the problem is either (or both) calling define with two arguments, the first being an empty array, and also not returning the pointer to the factory function itself as the second argument.

Can't wait to give your module a try now that I've got it loading.

EDIT Changed the define(factory) calls to define(factory()), since the return is a function to execute on define.

Default priority? How to set a listener to be called at the end?

Hi,

unfortunately I didn't found any deeper documentation about priority. The documentation says that there is a priority property and you can set a integer value but there is no example or use case.

However, I would like to begin with one use case. I have one function that should be called as the last event listener since it depends on settings the previous listeners will set. Therefore it would be good to know what the default priority is? If it would be 1 I hopefully could set priority to 0 and the function would be called at the end.

However, could somebody @ajacksified tell me how to manage such situations with Mediator.js?

Support chaining

Have you through of adding chaining to subscribe, once and remove methods?

E.g.

Mediator
     .subscribe('channe:one', doOne)
     .subscribe('channel:two', doTwo)
     .subscribe('channe:three', doThree)

I notice subscribe and once return the subscriber. What is the use case for this?

If this would be a positive change I'd be happy to submit a pull.

Using Mediator with React ?

Hi! Is there any way to use this with React ?

I am new to React so I am not sure how to make it available "globally" so that unrelated React components can communicate between them.

bowerInstall - mediator-js was not injected in your file

Hi there,

I am using your mighty Mediator in one of my projects.
It's an Angular project generated with yeoman.
I am using bower for all my dependencies and added mediator-js (v.0.9.6) in my bower.json.

When running grunt bowerInstall i get the following error:

"mediator-js was not injected in your file"

The detector looks for mediator-js.js in dependencies root folder but can not find it. (It simply does not exist)

Can you please have a look at this?

Thank you!

Consider using anonymous define

When using mediator.js in Dojo (it loads modules with require.js) we must use the same name you defined ("mediator-js") to instantiate it.

I think it will be better not giving it any name, so it's up to the user define it with a custom name.

Can't unsubscribe to a channel during a publish

If i subscribe to a channel many times but i want a callback to be called only once, a way to do this is to call unsubscribe inside the callback.

But doing this updates (logically) the _callbacks array and the loop in Publish fails because the length is checked only at the loop's start.

Is there an another/recommended way to do this ? (run a callback only once)

Thanks in advance.

Difference in removing all / single subscriber

I was looking at the removeSubscriber method of the Channel object and I noticed something that I can't explain.

When removing a specific subscriber you set the channel property of the subscriber to null before removing it from the array of subscribers. When all the subscribers get cleared due to the id param being null the array is simply cleared and the channel property on all the subscribers don't get reset to null.

Is there a specific reason for this or is it an oversight?

Feature Request: Subscribe to multiple channels

Feature Request: It should be possible to subscribe to multiple channels, e.g. separated by a ,. A example would look like: mediator.subscribe("channelOne, channelTwo", function(){}); like in the jQuery syntax.

How to extend Mediator ?

I try to extend Mediator with a new method, but this code make an error with "this." :

Mediator.prototype = {

    subscribeBis: function(channelName){

        var myId = this.subscribe(channelName, function(data){
            console.log(myId);
        });
    }
};

var myMediator = new Mediator();
myMediator.subscribeBis("test");

Don't overwrite constructor

Thanks for a great library.

I'm using it in a bit of a convoluted way and I'd like to have access to the Mediator constructor via the instances' prototype – would you consider either not doing Mediator.prototype = {...} or doing a reassignment afterwards?

Like this: Mediator.prototype.constructor = Mediator

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.