Skip to content

Namespaces in jQuery

jQuery encourages using namespaces for methods in the $ namespace, like $.foo.bar() rather than $.bar(). This works for $ because methods don't expect this to refer to anything specific, and the way javascript works is to assign this to the last-named object, so in $.foo.bar(), this refers to $.foo.

This idea fails for plugins, however, since plugins expect this to refer to the jQuery object that started the chain. If I define $.fn.bar = function(){}, then when $(...).bar() is called, this refers to $(...), just as we want. But if I define $.fn.foo.bar = function(){}, then when $(...).foo.bar() is called, this refers to $(...).foo, which is an object that knows nothing about jQuery. There's no way to make an object reference return something else.

But all is not lost. We can define a function that returns an object, and that function can use this to set the returned object to be just like a jQuery object, but with the desired namespaced methods in it. The inefficient way to do that is to copy the new methods into the jQuery object, but if we can manipulate the prototype chain directly (as we can in Firefox) we can add our new methods to the chain without copying.

So a namespacing plugin would be:


(function($){
if ({}.__proto__){
	// mozilla  & webkit expose the prototype chain directly
	$.namespace = function(name){
		$.fn[name] = function namespace() { // insert this function in the prototype chain
			this.__proto__ = arguments.callee;
			return this;
		};
		$.fn[name].__proto__ = $.fn;
	};
	$.fn.$ = function(){
		this.__proto__ = $.fn;
		return this;
	};	
}else{
	// every other browser; need to copy methods
	$.namespace = function(name){
		$.fn[name] = function namespace() { return this.extend(arguments.callee); };
	};
	$.fn.$ = function() { // slow but restores the default namespace
		var len = this.length;
		this.extend($.fn);
		this.length = len; // $.fn has length = 0, which messes everything up
		return this;
	};
}
})(jQuery);

And you could use it like:


	$.namespace('danny');
	$.namespace('danny2');
	$.fn.danny.foo = function() {return this.css('color', 'green')};
	$.fn.danny2.foo = function(x){alert(x); return this; };
	// now we have two different methods "foo"
	$('p').danny().foo(); // colors paragraphs green
	$('p').danny2().foo('Hello, world'); // alerts 'Hello, world'
	$('p').danny().foo().danny2().foo('Hello, world'); // chaining works
	$.fn.danny.add = function(a,b) { alert(a+b); return this;}; // defines a function with the same name as a real jQuery one
	$('p').danny().add(1,2).$().add('div'); // the $() plugin restores the real jQuery namespace to a chain

The namespacing is per-chain only; $('p').danny() does not affect any subsequent statements. Plugins that call pushStack will reset the namespacing, but in general the namespace function should be called right before the method, so that should not be an issue.

This is inefficient, obviously, adding an extra function call and possible a lot of copying with extend, but for most code that is insignificant.

{ 12 } Comments

  1. Larry Gordon | January 23, 2009 at 5:03 pm | Permalink

    I was trying to get your $.namespace method to work, and I get an error in jQuery stating that “fn.call is not a function”
    for the following
    $.namespace(‘foo’);
    $.fn.foo.bar = function(){
    alert($(this));
    };
    // where I call a div with id=’someid’
    $(‘#someid’).foo.bar();

    Can you help with this?

  2. Danny | January 26, 2009 at 3:26 pm | Permalink

    @Larry Gordon:
    You need to do $(‘#someid’).foo().bar()
    foo can’t be used as an object, since it would force “this” in bar() to be the foo object rather than the original jQuery object. Only an actual function call
    that executes foo = function() { … return this; } will pass on the jQuery object. You need to call foo with foo().
    I hope that’s understandable, but it is the key point that makes namespacing (and a lot of javascript) hard to figure out.

    Danny

  3. Anonymous | March 11, 2009 at 6:10 pm | Permalink

    I reworked your code a bit to allow for chained namespacing (like in java). Here is the code:

    (function($){
    if ({}.__proto__){
        // mozilla  & webkit expose the prototype chain directly
        $.namespace = function(n){
            var names=n.split('.');
            var f=$.fn;
            for(var i=0;i<names.length;i++) {
                var name=names[i];
                if(!f[name]) {
                    f[name] = function namespace() { // insert this function in the prototype chain
                        this.__proto__ = arguments.callee;
                        return this;
                    };
                    f[name].__proto__ = f;
                }
                f=f[name];
            }
        };
        $.fn.$ = function(){
            this.__proto__ = $.fn;
            return this;
        };
    }else{
        // every other browser; need to copy methods
        $.namespace = function(n){
            var names=n.split('.');
            var f=$.fn;
            for(var i=0;i<names.length;i++) {
                var name=names[i];
                if(!f[name]) {
                    f[name] = function namespace() { return this.extend(arguments.callee); };
                }
                f=f[name];
            }
        };
        $.fn.$ = function() { // slow but restores the default namespace
            var len = this.length;
            this.extend($.fn);
            this.length = len; // $.fn has length = 0, which messes everything up
            return this;
        };
    }
    })(jQuery);

    It allows you to do stuff like so:

    $.namespace('danny');
    $.fn.danny.foo = function() {alert('color=green'); return this;};
    $.namespace('danny.tools');
    $.fn.danny.tools.foo = function(){alert('color=blue'); return this;};
    $('p').danny().foo();
    $('p').danny().tools().foo();

    Creating the “danny” namespace first is just there to prove that any existing packages aren’t overriden when they are chained.

  4. Danny | March 12, 2009 at 10:08 am | Permalink

    @anonymous:
    Looks cool. I’ve been using jQuery UI widgets as the basis for a few of my plugins, and I’d like to get them to use plugin namespaces, and this would allow something like $(selector).ui().draggable().option(whatever), rather than $.draggable(‘option’, whatever).
    –Danny

  5. Anthony Rodriguez | March 12, 2009 at 10:36 am | Permalink

    @danny

    Coming from a java background, I did feel that the $.draggable(?option?, whatever) syntax was a little awkward. I just started working with jQuery and I wanted to find the “jQuery way” to namespace as I have been doing in plain old javascript up until now. Great blog. Keep up the good work.

  6. Jeff | July 20, 2009 at 7:52 am | Permalink

    @Danny
    I actually like the syntax that results from this code:
    http://projects.pro.br/gsaraiva/jquerynamespace/

    $(‘p’).danny.foo()
    $(‘p’).danny2.foo()
    $(‘p’).danny.foo().danny2.foo()

    as opposed to

    $(‘p’).danny().foo()
    $(‘p’).danny2().foo()
    $(‘p’).danny().foo().danny2().foo()

    However I think the code lacks elegance and flexibility relative to yours. A merger of the two would be my ideal.

  7. Danny | July 20, 2009 at 11:49 am | Permalink

    I agree that the syntax is nice without the extra parentheses but it’s the only way to actually return an object that depends on its context (i.e. pass “this” to it). Saraiva works around that by creating, effectively, a global variable (jQuery.fn.curReturn) that stores the last jQuery object created. I think that will get you into trouble with any complex manipulations as one $() overwrites the previous one.

  8. Jeff | July 20, 2009 at 1:01 pm | Permalink

    @Danny
    I agree. As much I like the syntax it produces I don’t believe I trust it enough to use it.
    FYI, I’ve come across a couple other attempts to do jQuery namespacing.

    Ariel Flesler:
    http://flesler.blogspot.com/2008/04/jquerymodularize.html

    John Resig:
    http://dev.jquery.com/~john/plugins/space/

    Flesler’s code produces results similar to yours. Resig’s code produces results similar to Saraiva’s, though chaining doesn’t seem to work.

    Thanks and keep up the good work.

  9. Danny | July 21, 2009 at 2:24 am | Permalink

    @Jeff:
    Yes, Ariel Flesler’s code is very similar to mine under the hood, though he doesn’t use __proto__
    Danny

  10. Teo | October 30, 2009 at 7:37 am | Permalink

    Thanks! I am new to JavaScript and jQuery but this proved to be very useful for creating a small framework of plugins for my company.

  11. Shabeer Naha | November 2, 2009 at 7:12 am | Permalink

    Isnt it much easier if it were done like this :

    (function($) {
        $.fn.danny = function (){
            var jq = this;
            return {
                foo: function(){
                    return jq.each(function(){});
                },
    
                bar: function(){
                    return jq.each(function(){});
               }
           }
        };
    })(jQuery);
    
    (function($) {
        $.fn.danny2 = function (){
            var jq = this;
            return {
                foo: function(){
                    return jq.each(function(){});
                },
    
                bar: function(){
                    return jq.each(function(){});
               }
           }
        };
    })(jQuery);
    
    $('p').danny().foo();
    $('p').danny2().foo();
    
    $('p').danny().bar();
    $('p').danny2().bar();
  12. Danny | November 2, 2009 at 10:12 am | Permalink

    @Shabeer Naha:
    Your code works, but the idea of namespacing is a little different. After changing the namespace, all the methods that are not overridden still exist, and the new methods continue to be visible in the chain. Thus:

    $('p').danny().text('Hello').foo().bar().fadeOut();

    Your code would require

    $('p').text('Hello').danny().foo().danny().bar().fadeOut();

    Not a huge difference, but a conceptual one.
    –Danny

{ 1 } Trackback

  1. […] Namespaces in jQuery Ein Tutorial ?ber das Namespacing in jQuery. […]

Post a Comment

Your email is never published nor shared. Required fields are marked *