jQuery: The Write Less, Do More JavaScript Library

Object-oriented plugins?

View: New views
7 Messages — Rating Filter:   Alert me  

Object-oriented plugins?

by Hector Virgen :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Does anyone know of a good example of an object-oriented plugin for jQuery?

The reason I ask is that the jQuery convention is to return this at the end of the plugin to allow for chaining. While that's great for simple plugins, some of my advanced plugins instantiate object that I would like to access at various points in my script.

From the looks of it, jQuery's "tabs" plugin seems to use an object stored "somewhere" (perhaps in the element). But to invoke methods on that object, you have to call the plugin again and pass it a string as its first parameter:

http://docs.jquery.com/UI/Tabs/tabs#.22enable.22index
jQuery#tabs( "add", url, label, [index] )

To be honest, this seems a little awkward for me. This means I would have to write my plugin to check the first parameter to see if it's a string or an object, and if it's a string I then would have to make a switch to call the approriate method based on the string value.

I would much rather be able to access the tabs instance directly:

var tabs = $('#tabbed').tabs(); // tabs is instance of jQueryTabs
tabs.add(url, label); // accessing instance directly

But if tabs acted like that, it would break chaining.

$('#tabbed').tabs().hide(); // This would error

Is there another way to create object-oriented plugins, while still following the jQuery conventions?

Thanks!

-Hector

Re: Object-oriented plugins?

by Balazs Endresz :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


> To be honest, this seems a little awkward for me. This means I would have to
> write my plugin to check the first parameter to see if it's a string or an
> object, and if it's a string I then would have to make a switch to call the
> approriate method based on the string value.

You don't need a switch, you can call a method like this:
object['method_name']();
and also you can store the instance with $.data

This is what UI's widget factory was built for:
http://docs.jquery.com/UI/Developer_Guide

As for UI widgets you can call the method directly with $.data:
$("#tabbed").tabs();
$("#tabbed").data('tabs').add('test.html', 'new tab', 4);

But I'm not sure if this would be the encouraged way of using plugins,
actually I haven't heard about any convention you could follow when
writing an OO plugin. This questions comes up from time to time and it
seems it's up to you to pick a pattern.

On Nov 23, 4:03 am, "Hector Virgen" <djvir...@...> wrote:

> Does anyone know of a good example of an object-oriented plugin for jQuery?
>
> The reason I ask is that the jQuery convention is to return *this* at the
> end of the plugin to allow for chaining. While that's great for simple
> plugins, some of my advanced plugins instantiate object that I would like to
> access at various points in my script.
>
> From the looks of it, jQuery's "tabs" plugin seems to use an object stored
> "somewhere" (perhaps in the element). But to invoke methods on that object,
> you have to call the plugin again and pass it a string as its first
> parameter:
>
> http://docs.jquery.com/UI/Tabs/tabs#.22enable.22index
> jQuery#tabs( "add", url, label, [index] )
>
> To be honest, this seems a little awkward for me. This means I would have to
> write my plugin to check the first parameter to see if it's a string or an
> object, and if it's a string I then would have to make a switch to call the
> approriate method based on the string value.
>
> I would much rather be able to access the tabs instance directly:
>
> var tabs = $('#tabbed').tabs(); // tabs is instance of jQueryTabs
> tabs.add(url, label); // accessing instance directly
>
> But if tabs acted like that, it would break chaining.
>
> $('#tabbed').tabs().hide(); // This would error
>
> Is there another way to create object-oriented plugins, while still
> following the jQuery conventions?
>
> Thanks!
>
> -Hector

Re: Object-oriented plugins?

by Scott González :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


Providing the method name as the first parameter is a bit awkward,
perhaps looking at the alternatives would help:

Add namespaces to jQuery.  This isn't very jQuery-like.  Example: $
(el).tabs.add(url, label).show();

Add a new jQuery method for every plugin instance method.  This
pollutes the jQuery namespace, so this should only be done when it
really makes sense.  Example: $(el).addTab(url, label);

Use events.  You can bind custom events in a namespace and then have
users interact with your plugin by triggering those events.  Example: $
(el).trigger('add.tabs', url, label);

There may be other approaches as well.

The jQuery UI approach allows plugins to expose as many methods as
they want while only using one method in the jQuery namespace.

Re: Object-oriented plugins?

by Hector Virgen :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

I've been thinking about this over the weekend and came up with a way to write class-based plugins while still following the jQuery convention. Maybe someone else has done this before but I couldn't find any documentation on this subject.

The idea is to extend the base jQuery object with the javascript class, and then extend jQuery.fn with a simple method that does nothing but instantiate the class and return "this". 

The nice thing about this approach is that you can still make chainable plugins without polluting the jQuery.fn namespace with a ton of methods. 

$('#mydiv').myplugin().show();

But if you need access to the object, you can use the "new" construct.

var myplugin = new $.MyPlugin($('#mydiv'));
myplugin.doSomething();

Here is an example plugin that just alerts some text when the selected elements are clicked. But, if you create the plugin object manually with the "new" construct, you can change the message or invoke the alert without the click:

(function($){
var Alerter = function(selector, options)
{
var obj = this;
this.settings = {
message: 'no message'
};
$.extend(this.settings, options);
selector.click(function()
{
obj.alert.call(obj);
});
}
Alerter.prototype.alert = function()
{
alert('Alerter said: ' + this.settings.message);
}
Alerter.prototype.message = function(message)
{
this.settings.message = message;
return this;
}
$.extend({
Alerter: Alerter
});
$.fn.extend({
alerter: function(options)
{
var alerter = new $.Alerter(this, options);
return this;
}
});
})(jQuery);


// Usage as jQuery plugin
$('#mydiv').alerter({
message: 'foo'
});

// Usage as object
var alerter = new $.Alerter($('#anotherdiv'), {
message: 'bar'
});

// As an object, you can call methods on it easily
alerter.message('new message');
alerter.alert(); // alerts "Alerter said: new message"

Any thoughts on this approach?

-Hector


On Sun, Nov 23, 2008 at 5:15 AM, Scott González <scott.gonzalez@...> wrote:

Providing the method name as the first parameter is a bit awkward,
perhaps looking at the alternatives would help:

Add namespaces to jQuery.  This isn't very jQuery-like.  Example: $
(el).tabs.add(url, label).show();

Add a new jQuery method for every plugin instance method.  This
pollutes the jQuery namespace, so this should only be done when it
really makes sense.  Example: $(el).addTab(url, label);

Use events.  You can bind custom events in a namespace and then have
users interact with your plugin by triggering those events.  Example: $
(el).trigger('add.tabs', url, label);

There may be other approaches as well.

The jQuery UI approach allows plugins to expose as many methods as
they want while only using one method in the jQuery namespace.


Re: Object-oriented plugins?

by Balazs Endresz :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


I personally use the same approach in the Translate plugin (maybe I
should have told you that before :), but with that you don't need the
'new' keyword: $.translate() returns a new object (it's a bit similar
to $.ajax or jQuery itself, you don't need 'new' there either).
http://code.google.com/p/jquery-translate/

With Translate you can only control the behaviour through the options
when using the jQuery method (though the object is available in
callback functions), but  in your case I think you can still consider
letting the first variable call a method:

$.fn.alerter = function(a){
 if(typeof a!="string") {
  var alerter = new $.Alerter(this, a);
  $(this).data("alerter", alerter); //store the instance
 } else {
  var instance = $(this).data("alerter"); //get the instance
  instance[a].apply(instance, $.makeArray(arguments).slice(1) ); //
call method
 }
 return this;
}

I don't want to push this but it's really not a lot of code for a
shorthand, as you don't need to deal with the instances in your code
beacause they're handled by the plugin and you can call methods
without breaking a chain, but it really depends on what you want the
plugin to do.

Some other discussions related to this topic if you're interested:
http://groups.google.com/group/jquery-en/browse_thread/thread/9dc9be1cc298cbdd
http://groups.google.com/group/jquery-dev/browse_thread/thread/48400f696b85a7cb
http://groups.google.com/group/jquery-dev/browse_thread/thread/b2f784b7575456dc/0cd276379f8a2f7d?show_docid=0cd276379f8a2f7d
http://groups.google.com/group/jquery-dev/browse_thread/thread/6c02b9b939c96bdb/5f244e161b9c1346?show_docid=5f244e161b9c1346

On Nov 26, 7:16 pm, "Hector Virgen" <djvir...@...> wrote:

> I've been thinking about this over the weekend and came up with a way to
> write class-based plugins while still following the jQuery convention. Maybe
> someone else has done this before but I couldn't find any documentation on
> this subject.
> The idea is to extend the base jQuery object with the javascript class, and
> then extend jQuery.fn with a simple method that does nothing but instantiate
> the class and return "this".
>
> The nice thing about this approach is that you can still make chainable
> plugins without polluting the jQuery.fn namespace with a ton of methods.
>
> $('#mydiv').myplugin().show();
>
> But if you need access to the object, you can use the "new" construct.
>
> var myplugin = new $.MyPlugin($('#mydiv'));
> myplugin.doSomething();
>
> Here is an example plugin that just alerts some text when the selected
> elements are clicked. But, if you create the plugin object manually with the
> "new" construct, you can change the message or invoke the alert without the
> click:
>
> (function($){
> var Alerter = function(selector, options)
> {
> var obj = this;
>  this.settings = {
> message: 'no message'};
>
>  $.extend(this.settings, options);
>  selector.click(function()
> {
> obj.alert.call(obj);});
> }
>
>  Alerter.prototype.alert = function()
> {
> alert('Alerter said: ' + this.settings.message);}
>
>  Alerter.prototype.message = function(message)
> {
> this.settings.message = message;
> return this;}
>
>  $.extend({
> Alerter: Alerter});
>
>  $.fn.extend({
> alerter: function(options)
> {
> var alerter = new $.Alerter(this, options);
> return this;
>
> }
> });
> })(jQuery);
>
> // Usage as jQuery plugin
> $('#mydiv').alerter({
> message: 'foo'
>
> });
>
> // Usage as object
> var alerter = new $.Alerter($('#anotherdiv'), {
> message: 'bar'
>
> });
>
> // As an object, you can call methods on it easily
> alerter.message('new message');
> alerter.alert(); // alerts "Alerter said: new message"
>
> Any thoughts on this approach?
>
> -Hector
>
> On Sun, Nov 23, 2008 at 5:15 AM, Scott González <scott.gonza...@...>wrote:
>
>
>
> > Providing the method name as the first parameter is a bit awkward,
> > perhaps looking at the alternatives would help:
>
> > Add namespaces to jQuery.  This isn't very jQuery-like.  Example: $
> > (el).tabs.add(url, label).show();
>
> > Add a new jQuery method for every plugin instance method.  This
> > pollutes the jQuery namespace, so this should only be done when it
> > really makes sense.  Example: $(el).addTab(url, label);
>
> > Use events.  You can bind custom events in a namespace and then have
> > users interact with your plugin by triggering those events.  Example: $
> > (el).trigger('add.tabs', url, label);
>
> > There may be other approaches as well.
>
> > The jQuery UI approach allows plugins to expose as many methods as
> > they want while only using one method in the jQuery namespace.

Re: Object-oriented plugins?

by ajpiano :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


I've been writing a lot of jQuery plugins for internal use that use an
OO and prototypal approach.  I have a basic class that i use for all
plugins that provides some basic methods (like storing the instance in
the .data() of the element and fires the internal methods of the
subclass, and then i attach a basic jQuery plugin functionality to
start it all up.  I've found it rather enjoyable to work with for
creating and working with objects with a lot of methods...and it keeps
the jQuery namespace clean

        app.Toolbox = function(tool)  {
                function Toolbox() {}
                Toolbox.prototype = $.extend({
                        flag:function() {
                                app.flags[tool.name] = true;
                        },
                        unflag:function() {
                                delete app.flags[tool.name];
                        },
                        initTool:function() {
                                if (tool.flags) {
                                        if (app.flags[tool.name]) {
                                                alert("You can only launch one "+tool.description+" at a
time.");
                                                return false;
                                        } else {
                                                this.flag();
                                        }
                                }
                                tool.$el.data(tool.name,this);
                                return this.init();
                        },
                        takeDown:function() {
                                tool.destroy.call(this);
                                tool.$el.removeData(tool.name);
                                tool.flags && this.unflag();
                        }
                },tool);
                return new Toolbox().initTool();
        };

        jQuery.fn.columnMasterEditor = function() {
                return this.each(function() {
                        var $a = $("<a href='#' class='masterEditLink' title='Update entire
column...'/>");
                        $a
                        .tooltip(app.defaults.tooltip)
                        .prependTo(this)
                        .bind("click.columnMasterEditor",function(e) {
                                new app.Toolbox.ColumnMasterEditor($a,e);
                                return false;
                        });
                });
        };

this is just a pattern i have developed for myself but if anyone has
feedback on it please do share...


--adam



On Nov 26, 3:41 pm, Balazs Endresz <balazs.endr...@...> wrote:

> I personally use the same approach in the Translate plugin (maybe I
> should have told you that before :), but with that you don't need the
> 'new' keyword: $.translate() returns a new object (it's a bit similar
> to $.ajax or jQuery itself, you don't need 'new' there either).http://code.google.com/p/jquery-translate/
>
> With Translate you can only control the behaviour through the options
> when using the jQuery method (though the object is available in
> callback functions), but  in your case I think you can still consider
> letting the first variable call a method:
>
> $.fn.alerter = function(a){
>  if(typeof a!="string") {
>   var alerter = new $.Alerter(this, a);
>   $(this).data("alerter", alerter); //store the instance
>  } else {
>   var instance = $(this).data("alerter"); //get the instance
>   instance[a].apply(instance, $.makeArray(arguments).slice(1) ); //
> call method
>  }
>  return this;
>
> }
>
> I don't want to push this but it's really not a lot of code for a
> shorthand, as you don't need to deal with the instances in your code
> beacause they're handled by the plugin and you can call methods
> without breaking a chain, but it really depends on what you want the
> plugin to do.
>
> Some other discussions related to this topic if you're interested:http://groups.google.com/group/jquery-en/browse_thread/thread/9dc9be1...http://groups.google.com/group/jquery-dev/browse_thread/thread/48400f...http://groups.google.com/group/jquery-dev/browse_thread/thread/b2f784...http://groups.google.com/group/jquery-dev/browse_thread/thread/6c02b9...
>
> On Nov 26, 7:16 pm, "Hector Virgen" <djvir...@...> wrote:
>
> > I've been thinking about this over the weekend and came up with a way to
> > write class-based plugins while still following the jQuery convention. Maybe
> > someone else has done this before but I couldn't find any documentation on
> > this subject.
> > The idea is to extend the base jQuery object with the javascript class, and
> > then extend jQuery.fn with a simple method that does nothing but instantiate
> > the class and return "this".
>
> > The nice thing about this approach is that you can still make chainable
> > plugins without polluting the jQuery.fn namespace with a ton of methods.
>
> > $('#mydiv').myplugin().show();
>
> > But if you need access to the object, you can use the "new" construct.
>
> > var myplugin = new $.MyPlugin($('#mydiv'));
> > myplugin.doSomething();
>
> > Here is an example plugin that just alerts some text when the selected
> > elements are clicked. But, if you create the plugin object manually with the
> > "new" construct, you can change the message or invoke the alert without the
> > click:
>
> > (function($){
> > var Alerter = function(selector, options)
> > {
> > var obj = this;
> >  this.settings = {
> > message: 'no message'};
>
> >  $.extend(this.settings, options);
> >  selector.click(function()
> > {
> > obj.alert.call(obj);});
> > }
>
> >  Alerter.prototype.alert = function()
> > {
> > alert('Alerter said: ' + this.settings.message);}
>
> >  Alerter.prototype.message = function(message)
> > {
> > this.settings.message = message;
> > return this;}
>
> >  $.extend({
> > Alerter: Alerter});
>
> >  $.fn.extend({
> > alerter: function(options)
> > {
> > var alerter = new $.Alerter(this, options);
> > return this;
>
> > }
> > });
> > })(jQuery);
>
> > // Usage as jQuery plugin
> > $('#mydiv').alerter({
> > message: 'foo'
>
> > });
>
> > // Usage as object
> > var alerter = new $.Alerter($('#anotherdiv'), {
> > message: 'bar'
>
> > });
>
> > // As an object, you can call methods on it easily
> > alerter.message('new message');
> > alerter.alert(); // alerts "Alerter said: new message"
>
> > Any thoughts on this approach?
>
> > -Hector
>
> > On Sun, Nov 23, 2008 at 5:15 AM, Scott González <scott.gonza...@...>wrote:
>
> > > Providing the method name as the first parameter is a bit awkward,
> > > perhaps looking at the alternatives would help:
>
> > > Add namespaces to jQuery.  This isn't very jQuery-like.  Example: $
> > > (el).tabs.add(url, label).show();
>
> > > Add a new jQuery method for every plugin instance method.  This
> > > pollutes the jQuery namespace, so this should only be done when it
> > > really makes sense.  Example: $(el).addTab(url, label);
>
> > > Use events.  You can bind custom events in a namespace and then have
> > > users interact with your plugin by triggering those events.  Example: $
> > > (el).trigger('add.tabs', url, label);
>
> > > There may be other approaches as well.
>
> > > The jQuery UI approach allows plugins to expose as many methods as
> > > they want while only using one method in the jQuery namespace.

Re: Object-oriented plugins?

by carpii :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


On Nov 26, 10:27 pm, ajpiano <ajpi...@...> wrote:
>
> this is just a pattern i have developed for myself but if anyone has
> feedback on it please do share...
>

Can you show an example of how such a class would be used?

Im struggling to understand the benefit of it, although Im sure there
is one

Thanks