When would I use JQuery.Callbacks?
Asked Answered
C

6

41

I was looking through new stuff added to jQuery 1.7 and I saw they now have jQuery.Callbacks() http://api.jquery.com/jQuery.Callbacks/.

The documentation shows you how to use jQuery.callbacks() but not any applicable examples of when I would want to use them.

It seems you can add/remove callbacks from a callbacks list and you can do jQuery.callbacks().fire(args), but this just fires off ALL of the callbacks in that list. Maybe I am missing something but this doesn't seem very useful.

In my head when I first saw this new functionality I thought you would be able to use it with key/value pairs. Which would then provide a simple way to manage callback functions in a single place in your application. Something like

$.callbacks.add("foo", myFunction);

and then for example if I wanted to call that callback at the end of my function I could do something like

$.callbacks().fire("foo", args);

However it doesn't look like you can fire off specific callbacks, you can only fire off all of them with the given arguments or none of them.

The closest thing I saw was being given the ability to give the .fire() function a context to set the "this" property

.fireWith(context, args)

but this doesn't really help much either.

  1. Am I misunderstanding the documentation?

  2. If this is the desired functionality what are some applicable examples where this is useful.

Christianson answered 9/11, 2011 at 20:6 Comment(6)
You first need to make a callbacks object by doing something like var callbacks = $.Callbacks();. Then you can add methods using callbacks.add(myFunction); (you don't name them, you just add functions to a list). They are then fired (in order) by callbacks.fire(args). fireWith is used to set a context (this), but not select which callback to fire. fireWith also requires the arguments be an array, for example: callbacks.fireWith(document, "foo");.Nedanedda
@Rocket I fixed the context error I had in my explanation. I understand you can call all of the callbacks at once with fire and give them all of the same arguments but this does not seem helpful. When would I want to do something like this in my application? Why isn't there functionality to fire off certain callbacks? What problem does this new functionality solve?Christianson
It says right at the top of the documentation: "The $.Callbacks() function is internally used ...." In other words, you--as a developer who isn't working on the jQuery Core--will really have no practical use for it, but they're documenting it anyway to avoid questions like the one you have just asked.Kostman
Yes, it does say it is used internally... it also says "It can be used as a similar base to define functionality for new components." Which you could confirm by firing up jsFiddle and doing alert($.Callbacks) which would show that it is exposed for you-as a developer to use. Which still makes my question valid. If it is exposed through their API for me to use. I would like to know practical uses.Christianson
It seems this 'callback' chain is similar to making a queue of functions and having them each call the next one after they're done. Except the functions stay in the queue, and you can set the context.Nedanedda
deferred then and deferred doneEmprise
A
16

To expand on @Rockets answer a bit and clear up some confusion:

The reason that one might need to use jQuery's $.Callbacks is multifaceted:

  1. The user has a lot of code in one function and wants to split it up
  2. They take that information and send it through the jQuery callback function which then allows them to have split their code into better manageable pieces with which to work with.
    So (for example) if you look at @Rocket's code:

    var clickCallbacks = $.Callbacks();
    
    clickCallbacks.add(function() { //one one function piece
        //parse and do something on the scope of `this`
        var c = parseInt(this.text(), 10);
        this.text(c + 1);
    });
    clickCallbacks.add(function(id) { //add a second non-related function piece
        //do something with the arguments that were passed
        $('span', '#last').text(id);
    });
    
    $('.click').click(function() {
        var $ele = $(this).next('div').find('[id^="clickCount"]');
        clickCallbacks.fireWith($ele, [this.id]); //do two separate but related things.
    });
    
  3. What you can now have is multiple callback batches of function which you can now call whenever you deem it be necessary without the need to make so many changes throughout out your code.
Andry answered 9/11, 2011 at 21:40 Comment(8)
@Rocket anytime ^_^ thank you for explaining it somewhat to me :-DAndry
Ahhh ok. The "multiple callback batches" is the KEY I think I was missing. So does $.Callbacks create an instance of a Callbacks list? So I could have callbacks1 and callbacks2 which would then contain different but related callback functions? So I could then call callbacks1.fire() in myFunction1 and callbacks2.fire() in myFunction2?Christianson
@Christianson that is my impression of what it does. You should test to make sure. Basically what $.Callbacks() does is return an object which you could then use to fit your needs.Andry
@Keith.Abramo: Yeah, you can have different callback lists. var c1 = $.Callbacks(), c2 = $.Callbacks();. Each call to $.Callbacks() creates a new object.Nedanedda
Awesome. This clears it up great. Here is a jsFiddle for those who come across this in the future and want verification jsfiddle.net/UX5Ln/1Christianson
@Christianson No problem ^_^, but ahhhh! try not to use numeric IDs (that is not valid DOM) :-PAndry
Noted :). I only did that because it was the first thing that came to mind for testing.Christianson
+1 This is a perfect example @Neal. I wish this example was in the doc. Simple, straight to the point. Well done.Woolpack
N
12

I can see callbacks being useful when you are updating different DOM elements using the same method(s).

Here is a cheesy example: http://jsfiddle.net/UX5Ln/

var clickCallbacks = $.Callbacks();

clickCallbacks.add(function() {
    var c = parseInt(this.text(), 10);
    this.text(c + 1);
});
clickCallbacks.add(function(id) {
    $('span', '#last').text(id);
});

$('.click').click(function() {
    var $ele = $(this).next('div').find('[id^="clickCount"]');
    clickCallbacks.fireWith($ele, [this.id]);
});

It updates the click counter and the 'last clicked' when you click on something.

Nedanedda answered 9/11, 2011 at 21:22 Comment(4)
Whoa. how does that 2nd callback function work? (I have not looked into 1.7 much yet)Andry
I'm using fireWith which is like calling apply. jQuery calls the callbacks with my context (a jQuery object), and an argument array (containing one string). Both callback functions are called. The first ignores the argument(s) and uses this (which is a jQuery object). The second doesn't use this, but uses the argument(s) passed to it.Nedanedda
It took me a while to understand how $.Callbacks worked. I had to read the docs multiple times.Nedanedda
Roket -- this answer is very very useful, and it helped contribute to my answer, but your fiddle could become dead at any moment and this answer will become useless. Could you add some code (or all code) from your fiddle to your answer so it will be useful in the future when/if the fiddle goes dead?Andry
M
8

An (almost) out of the box jQuery Pub/Sub system

This is IMHO the most interesting application, and since it was not clearly stated in the answers (although some do make allusion to the usage), I am adding it to this relatively old post.

NB: The usage is clearly indicated, with an example, in the jQuery docs, but I guess it was added after the question was posted.

Pub/sub, aka the observer pattern, is a pattern which promotes loose coupling and single responsibility in an application. Rather than having objects calling directly the methods of other objects, objects instead subscribe to a specific task or activity and are notified when it occurs. For a more detailed explanation of the benefits of using the Pub/Sub pattern, you can check Why would one use the Publish/Subscribe pattern (in JS/jQuery)?.

Sure, this was possible with custom events using trigger, .on() and .off(), but I find jQuery.Callbacks to be a lot more suitable to the task, producing cleaner code.

Here is the example snippet from the jQuery documentation:

var topics = {};
 
jQuery.Topic = function( id ) {
    var callbacks,
        method,
        topic = id && topics[ id ];
 
    if ( !topic ) {
        callbacks = jQuery.Callbacks();
        topic = {
            publish: callbacks.fire,
            subscribe: callbacks.add,
            unsubscribe: callbacks.remove
        };
        if ( id ) {
            topics[ id ] = topic;
        }
    }
    return topic;
};

And a usage example:

// Subscribers
$.Topic( "mailArrived" ).subscribe( fn1 );
$.Topic( "mailArrived" ).subscribe( fn2 );
$.Topic( "mailSent" ).subscribe( fn1 );
 
// Publisher
$.Topic( "mailArrived" ).publish( "hello world!" );
$.Topic( "mailSent" ).publish( "woo! mail!" );
 
// Here, "hello world!" gets pushed to fn1 and fn2
// when the "mailArrived" notification is published
// with "woo! mail!" also being pushed to fn1 when
// the "mailSent" notification is published.
 
/*
output:
hello world!
fn2 says: hello world!
woo! mail!
*/
Monohydric answered 29/8, 2013 at 15:56 Comment(0)
B
3

It seems that $.Callbacks began as an implementation detail: a means to manage lists of functions and to call all the functions in a given list with the same arguments. A little like C#'s multicast delegates, with additional features, like the flags you can pass to customize the list's behavior.

A good example might be that jQuery uses $.Callbacks internally to implement its ready event. bindReady() initializes a callback list:

readyList = jQuery.Callbacks( "once memory" );

Note the once and memory flags, that ensure the callback list will only be called once, and that functions added after the list has been called will be called immediately.

Then, ready() adds the specified handler to that list:

ready: function( fn ) {
    // Attach the listeners
    jQuery.bindReady();

    // Add the callback
    readyList.add( fn );

    return this;
}

Finally, the callback list is fired when the DOM is ready:

readyList.fireWith( document, [ jQuery ] );

All the ready handlers are called in the context of the same document with the same reference to the global jQuery object. They can only be called this once, and additional handlers passed to ready() will be called immediately from then on, all of this courtesy of $.Callbacks.

Billetdoux answered 9/11, 2011 at 21:36 Comment(0)
B
2

I don't see any specific mention of setting context, but since you can pass an arbitrary number of arguments, that would potentially be useful. You could also make your own convention to pass a flag as a first argument and have listeners return false immediately if they aren't meant to handle the remaining argument list.

I've encountered cases where something like this might have been useful, but have used bind() and trigger() with custom events instead. Imagine some message handling system (a web based chat room or e-mail client) where you're polling a service for new messages. One function might be setting a number in a span or displaying a growl when something happens. Another might be updating a grid. With triggers you would have to trigger the event for each listener and "unroll" the passed arguments from eventData, with callbacks it's just one fire and your listeners are simple javascript functions with a simple argument list.

Callbacks isn't exactly revolutionary, but it'll make for less and cleaner code.

Barabarabarabas answered 9/11, 2011 at 20:47 Comment(2)
I understand how you could use this functionality as long as you only had ONE place where ALL callbacks needed to be called. But that is almost never the case in a web application. Most of the time you will have several functions which required callbacks, or conditional situations where you might want to call one callback over another. This does not seem possible with this architecture. It looks like the only thing you gain is a way to call one .fire() line instead of a couple calls to your callbacks. Also your callbacks all need to take the same arguments.Christianson
The reference to the context is in the .fireWith() function. Sorry I mistyped that but I fixed it.Christianson
T
0

I'm working on an app with a lot of business logic and at least 11 external services. It really helps keep things straight if you can write your own flow control classes and behaviors using something like Callbacks instead of trying to force your will on the Deferred implementation.

Ton answered 9/11, 2011 at 21:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.