5

I've been looking over the internet for examples of implementing the observer pattern in jquery.

I would like to have it like this

observer1.observe(subject);
observer2.observe(subject);

Define some custom event callbacks for the observers

observer1.bind('customEvent', function(contextData) {
  //Some code
});
observer1.bind('anotherCustomEvent', function(contextData) {
  //Some code  
});
observer2.bind('customEvent', function(contextData) {
  //Some code  
});

The following line would then trigger the customEvent callbacks of both observers

subject.trigger('customEvent', contextData);

while the following would fire anotherCustomEvent on observer1 only, since observer2 doesn't have that custom event binded

subject.trigger('anotherCustomEvent', contextData);

Guides on the internet is more general:

$( document ).on( "topicName" , function () {
  //..perform some behaviour
});

$( document ).trigger( "topicName" );

(Example from http://addyosmani.com/resources/essentialjsdesignpatterns/book/#observerpatternjquery) I can't see how the above code can be used to accomplish what I'm looking for.

Either i would have to make it like this (if I keep it like the above example):

$(document).on("customEvent", function () {
  observer1.trigger("customEvent");
  observer2.trigger("customEvent");
});

$(subject).click(function() { 
  $(document).trigger("customEvent");
});

or a little bit better:

$(subject).click(function() { 
  observer1.trigger("customEvent");
  observer2.trigger("customEvent");
});

Either way I'm stuck with having to edit the subject-click-callback or document-customEvent-callback instead of telling the observer to subscribe to the subject.

Have I misunderstood observer pattern or is there a way to achieve what I'm looking for?

http://addyosmani.com/resources/essentialjsdesignpatterns/book/#observerpatternjavascript mention Publish/Subscribe Pattern a bit further down in that chapter. That could be a way for me, but I'm missing the code behind the example.

Anders
  • 499
  • 1
  • 5
  • 18
  • 1
    I don't understand where you're stuck. Your `bind`/`trigger` (or `on`/`trigger`) examples are actually using jQuery's implementation of the pattern (publisher/subscriber variation). – bfavaretto Sep 25 '12 at 20:24
  • What I tried to show was that it looks like the subject decides who is observer rather then the observer choosing its subjects. Is that a bit more clear? – Anders Sep 25 '12 at 21:39
  • Here is how I see it: the subject triggers the event (i.e., dispatches it), and the observers catch it and call the appropriate event handler. So, when an observer subscribes to a particular event, it's choosing its subjects: the elements who trigger that event. – bfavaretto Sep 25 '12 at 23:08
  • I totally agree. I just can't see how this is achieved when you have to tell the subject which elements to trigger by selectors instead of the subject having a list that the observer can register to – Anders Sep 25 '12 at 23:29

3 Answers3

17

From your comment:

I just can't see how this is achieved when you have to tell the subject which elements to trigger by selectors instead of the subject having a list that the observer can register to

Please correct me if I'm wrong, but you seem to be misunderstanding how the pattern was implemented in jQuery. You don't "tell the subject which elements to trigger", and the subject doesn't have "a list that the observer can register to" either. It works like this:

  • The subject/publisher emits/triggers certain events (on certain circumstances you define).
  • The observer/subscriber listens to certain events. It keeps a list of the events it subscribed to.
  • That's all based on DOM events, so it's limited by the DOM Event Model.

For example, consider the following HTML:

<div id="div1">div 1</div>
<div id="div2">div 2</div>

Let's make the inner divs trigger a custom event called 'custom'. You define when that should happen, on this example this will happen when they are clicked:

$('div').on('click', function(e) {
    $(this).trigger('custom');
});

Now let's make the document element subscribe to that custom event:

$(document).on('custom', function(e) {
    console.log('document is handling custom event triggered by ' + e.target.id);
});

When the custom event is triggered by one of the divs, the observer/subscriber is notified and a message is logged to the console.

The example uses document as the observer for a reason: events bubble up the DOM tree, and can only be caught by elements that are ancestors of the one that triggered it. Since document is the root of the DOM tree, it can see all events. If #div1 was our observer, it would only see events triggered by #div1 itself, but not the ones triggered by #div2.

Maybe that limitation is what confused you?


There are ways to circumvent that limitation, but usually, if you want to do something to #div1 based upon an event triggered by #div2, you just do it from the callback you have setup on the document element (or the closest common ancestor to both divs). Anyway, it seems you really want an alternative, so here is one in the form of a jQuery plugin:

$.fn.observe = function(eventName, callback) {
    return this.each(function(){
        var el = this;
        $(document).on(eventName, function(){
            callback.apply(el, arguments);
        })
    });
}

You can use it like this:

$('#div1').observe('custom', function(e) {
    // do something
});

Live example: http://jsfiddle.net/JwJsP/1

bfavaretto
  • 71,580
  • 16
  • 111
  • 150
  • So how is an element that I want to observe #div1 triggered? Must the document custom callback define which are observers then and trigger them in its turn? – Anders Sep 27 '12 at 18:09
  • 1
    Added an alternative, but usually you just do whatever you need to do from the handler on `document`. – bfavaretto Sep 28 '12 at 00:44
0

Might be not what you are looking for, however observers can subscribe for listening events:

        $(document).on('customEvent', '.iObserver', function() {
            console.log('i am observer');
        });

        // add observer
        $('ul li#Observer').addClass('iObserver');

        //remove observer
        $('ul li#Observer').removeClass('iObserver');
Felix
  • 830
  • 10
  • 17
  • That is one way. I had a simplified version of that earlier. I didn't use document but rather triggered the class directly like this $('.observer').trigger('customevent') – Anders Sep 26 '12 at 10:18
-1

If you're still looking for some implementation using the events engine of jQuery you can try this code:

//The implementation   
(function($) {

    var o = $({});

    $.each({
        trigger: 'publish',
        on: 'subscribe',
        off: 'unsubscribe'
    }, function(key, val) {
        jQuery[val] = function() {
            o[key].apply(o, arguments);
        };
    });

})(jQuery);


// The way to use it is this:
var somedata = [1, 2, 3, 5, 4, 7, 8];
$.publish('myEvent', somedata); // with data
$.publish('myEvent'); // without data

$.subscribe('myEvent', function(event, data) {
    // handle the event
});
sabotero
  • 4,265
  • 3
  • 28
  • 43