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.
50+ jQuery Tutorials und mehr f?r Einsteiger und Fortgeschrittene says:
[…] Namespaces in jQuery Ein Tutorial ?ber das Namespacing in jQuery. […]
January 6, 2009, 1:28 amLarry Gordon says:
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?
January 23, 2009, 5:03 pmDanny says:
@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
January 26, 2009, 3:26 pmAnonymous says:
I reworked your code a bit to allow for chained namespacing (like in java). Here is the code:
It allows you to do stuff like so:
Creating the “danny” namespace first is just there to prove that any existing packages aren’t overriden when they are chained.
March 11, 2009, 6:10 pmDanny says:
@anonymous:
March 12, 2009, 10:08 amLooks 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
Anthony Rodriguez says:
@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.
March 12, 2009, 10:36 amJeff says:
@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.
July 20, 2009, 7:52 amDanny says:
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 “
July 20, 2009, 11:49 amthis
” 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.Jeff says:
@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.
July 20, 2009, 1:01 pmDanny says:
@Jeff:
July 21, 2009, 2:24 amYes, Ariel Flesler’s code is very similar to mine under the hood, though he doesn’t use __proto__
Danny
Teo says:
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.
October 30, 2009, 7:37 amShabeer Naha says:
Isnt it much easier if it were done like this :
November 2, 2009, 7:12 amDanny says:
@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:
Your code would require
Not a huge difference, but a conceptual one.
November 2, 2009, 10:12 am–Danny