views:

172

answers:

3

Hi I'm trying to author a jQuery plugin and I need to have methods accessible to elements after they are initialized as that kind of object, e.g.:

$('.list').list({some options}); //This initializes .list as a list

//now I want it to have certain methods like:
$('.list').find('List item'); //does some logic that I need

I tried with

$.fn.list = function (options) {
    return this.each(function() {
        // some code here
        this.find = function(test) {
            //function logic
        }
    }
}

and several other different attempts, I just can't figure out how to do it.

EDIT:

I'll try to explain this better.

I'm trying to turn a table into a list, basically like a list on a computer with column headers and sortable items and everything inbetween. You initiate the table with a command like

$(this).list({
    data: [{id: 1, name:'My First List Item', date:'2010/06/26'}, {id:2, name:'Second', date:'2010/05/20'}]
});

.list will make the <tbody> sortable and do a few other initial tasks, then add the following methods to the element:
.findItem(condition) will allow you to find a certain item by a condition (like findItem('name == "Second"')
.list(condition) will list all items that match a given condition
.sort(key) will sort all items by a given key
etc.

What's the best way to go about doing this?

A: 

this.each isn't needed. This should do:

$.fn.list = function (options) {
    this.find = function(test) {
        //function logic
    };
    return this;
};

Note that you'd be overwriting jQuery's native find method, and doing so isn't recommended.


Also, for what it's worth, I don't think this is a good idea. jQuery instances are assumed to only have methods inherited from jQuery's prototype object, and as such I feel what you want to do would not be consistent with the generally accepted jQuery-plugin behaviour -- i.e. return the this object (the jQuery instance) unchanged.

J-P
While I agree *usually*, this isn't always the case, a major example I can think of is the validate plugin...it all depends what your needs are.
Nick Craver
I tried doing just that (changed the .find to .findItem) and ended up with the error $('.list-box').findItem is not a function
Rob
Sorry, I forgot to add that you've got to `return this;`...
J-P
+2  A: 

If you want these methods to be available on any jQuery object, you will have to add each one of them to jQuery's prototype. The reason is every time you call $(".list") a fresh new object is created, and any methods you attached to a previous such object will get lost.

Assign each method to jQuery's prototype as:

jQuery.fn.extend({
    list: function() { .. },
    findItem: function() { .. },
    sort: function() { .. }
});

The list method here is special as it can be invoked on two occasions. First, when initializing the list, and second when finding particular items by a condition. You would have to differentiate between these two cases somehow - either by argument type, or some other parameter.

You can also use the data API to throw an exception if these methods are called for an object that has not been initialized with the list plugin. When ('xyz').list({ .. }) is first called, store some state variable in the data cache for that object. When any of the other methods - "list", "findItem", or "sort" are later invoked, check if the object contains that state variable in its data cache.

A better approach would be to namespace your plugin so that list() will return the extended object. The three extended methods can be called on its return value. The interface would be like:

$('selector').list({ ... });
$('selector').list().findOne(..);
$('selector').list().findAll(..);
$('selector').list().sort();

Or save a reference to the returned object the first time, and call methods on it directly.

var myList = $('selector').list({ ... });
myList.findOne(..);
myList.findAll(..);
myList.sort();
Anurag
Thanks for the info, that was actually very helpful I finally realize why my methods weren't existing. Couldn't I do `$list = $('.list').list({options});`and use $list from that point on? Anyway, I found another solution, I'm going to post it as an answer. +1 though thanks
Rob
@Rob - You can do that, as long as inside the `list` method, you extend the jQuery object with all the functions you need, and from this point onwards reference `$list` to call those functions. That said, I think your approach is better.
Anurag
+2  A: 

I found this solution here: http://www.virgentech.com/blog/2009/10/building-object-oriented-jquery-plugin.html

This seems to do exactly what I need.

(function($) {
    var TaskList = function(element, options)
    {
        var $elem = $(element);
        var options = $.extend({
            tasks: [],
            folders: []
        }, options || {});

        this.changed = false;
        this.selected = {};

        $elem.sortable({
            revert: true,
            opacity: 0.5
        });

        this.findTask = function(test, look) {
            var results = [];

            for (var i = 0,l = options.tasks.length; i < l; i++)
            {
                var t = options['tasks'][i];
                if (eval(test))
                {
                    results.push(options.tasks[i]);
                }
            }
            return results;
        }

        var debug = function(msg) {
            if (window.console) {
                console.log(msg);
            }
        }
    }

    $.fn.taskList = function(options)
    {
        return this.each(function() {
            var element = $(this);

            if (element.data('taskList')) { return; }

            var taskList = new TaskList(this, options);

            element.data('taskList', taskList);

        });
    }

})(jQuery);

Then I have

    $('.task-list-table').taskList({
        tasks: eval('(<?php echo mysql_real_escape_string(json_encode($tasks)); ?>)'),
        folders: eval('(<?php echo mysql_real_escape_string(json_encode($folders)); ?>)')
    });
    var taskList = $('.task-list-table').data('taskList');

and I can use taskList.findTask(condition);

And since the constructor has $elem I can also edit the jQuery instance for methods like list(condition) etc. This works perfectly.

Rob
+1 - I like this approach.
Anurag