Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to have member variables and public methods in a jQuery plugin?

Tags:

jquery

I'm trying to create a jQuery plugin that will create something like an autoCompleteBox but with custom features. How do I store member variables for each matching jQuery element?

For example I'll need to store a timerID for each. I'd also like to store references to some of the DOM elements that make up the control.

I'd like to be able to make a public method that works something like:

$("#myCtrl").autoCompleteEx.addItem("1");

But in the implementation of addItem() how can I access the member variables for that particular object like its timerID or whatever?

Below is what I have so far...

Thanks for any help or suggestions!

(function($)
{     
    //Attach this new method to jQuery   
    $.fn.autoCompleteEx = function(options)
    {    
        //Merge Given Options W/ Defaults, But Don't Alter Either
        var opts = $.extend({}, $.fn.autoCompleteEx.defaults, options);


        //Iterate over the current set of matched elements   
        return this.each(function() 
        {
            var acx = $(this); //Get JQuery Version Of Element (Should Be Div)

            //Give Div Correct Class & Add <ul> w/ input item to it
            acx.addClass("autoCompleteEx"); 
            acx.html("<ul><li class=\"input\"><input type=\"text\"/></li></ul>");

            //Grab Input As JQ Object
            var input = $("input", acx);

            //Wireup Div
            acx.click(function()
            {
                input.focus().val( input.val() );
            });


            //Wireup Input
            input.keydown(function(e)
            {
                var kc = e.keyCode;
                if(kc == 13)   //Enter
                {

                }
                else if(kc == 27)  //Esc
                {

                }
                else
                {
                    //Resize TextArea To Input
                    var width = 50 + (_txtArea.val().length*10);
                    _txtArea.css("width", width+"px");    
                }
            });

        });   //End Each JQ Element

    }; //End autoCompleteEx()

    //Private Functions
    function junk()
    {

    };

    //Public Functions
    $.fn.autoCompleteEx.addItem = function(id,txt)
    {
        var x = this;
        var y = 0;
    };

    //Default Settings
    $.fn.autoCompleteEx.defaults =
    {
        minChars:  2,
        delay:     300,
        maxItems:  1
    };

    //End Of Closure
})(jQuery); 
like image 273
user169867 Avatar asked Oct 07 '09 19:10

user169867


4 Answers

I've found that the jQuery UI way of handling this seems to work out the best. You create your 'extra methods' as a string argument to your plugin:

$('#elem').autoCompleteEx('addItem', '1');

Then the 'this' context is preserved, and you can do something along these lines:

function addItem() {
  // this should be == jquery object when this get called
}

$.fn.autoCompleteEx = function(options) {
   if (options === 'addItem') {
     return addItem.apply(this, Array.prototype.splice.call(arguments, 1));
   }
};
like image 160
gnarf Avatar answered Nov 10 '22 15:11

gnarf


Here is a template that I am experimenting with when building more complex widget plugins:

(function($){

  // configuration, private helper functions and variables
  var _defaultConfig = {
        /* ... config ... */
      },
      _version = 1;


  // the main controller constructor
  $.myplugin = function ( elm, config ) {

    // if this contructor wasn't newed, then new it...
    if ( this === window ) { return new $.myplugin( elm, config || {} ); }

    // store the basics
    this.item = $( elm );
    this.config = new $.myplugin.config( config );

    // don't rerun the plugin if it is already present
    if ( this.item.data( 'myplugin' ) ) { return; }

    // register this controlset with the element
    this.item.data( 'myplugin', this );

    // ... more init ...

  };
  $.myplugin.version = _version;
  $.myplugin.config = function ( c ) { $.extend( this, $.myplugin.config, c ); };
  $.myplugin.config.prototype = _defaultConfig;
  $.myplugin.prototype = {

    /* ... "public" methods ... */

  };

  // expose as a selector plugin
  $.fn.myplugin = function ( config ) {
    return this.each(function(){
      new $.myplugin( this, config );
    });
  };

})(jQuery);

I put the default config and version at the top simply because it the most likely thing anyone reading the code is seeking. Most of the time you just want to examine the settings block.

This will expose "myplugin" in two places, as a constructor for the widget's "controller" on $ and as a collection method on $.fn. As you can see $.fn method doesn't really do anything except instanciate new controllers.

The config is a prototypally inherited object where the default is the prototype. This give extended flexibility with asigining values as you may assign the "next" defaults into $.myplugin.config, or alter every running plugin's default with $.myplugin.config.prototype. This does require you to allways assign into these with $.extend or you will break the system. More code could counter that, but I prefer to know what I'm doing. :-)

The instance of the controller is binds itself to the element through jQuery's data() method, and in fact uses it to test that it isn't run twice on the same element (although you might want to allow reconfiguring it).

This gives you the following interface to the controller:

// init:
$( 'div#myid' ).myplugin();

// call extraMethod on the controller:
$( 'div#myid' ).data('myplugin').extraMethod();

The biggest flaw on this approach is that it is a bit of a pain to maintain the "this" context with every event assignment. Until context for events arrives in jQuery this needs to be done with a liberal amount of closures.

Here is a rough example of how an (incomplete and useless) plugin might look:

(function($){

  // configuration, private helper functions and variables
  var _defaultConfig = {
        openOnHover: true,
        closeButton: '<a href="#">Close</a>',
        popup: '<div class="wrapper"></div>'
      },
      _version = 1;

  // the main controller constructor
  $.myplugin = function ( elm, config ) {

    // if this contructor wasn't newed, then new it...
    if ( this === window ) { return new $.myplugin( elm, config || {} ); }
    this.item = $( elm );
    this.config = new $.myplugin.config( config );
    if ( this.item.data( 'myplugin' ) ) { return; }
    this.item.data( 'myplugin', this );

    // register some events
    var ev = 'click' + ( this.config.openOnHover ) ? ' hover' : '';
    this.item.bind(ev, function (e) {
      $( this ).data( 'myplugin' ).openPopup();
    });

  };
  $.myplugin.version = _version;
  $.myplugin.config = function ( c ) { $.extend( this, $.myplugin.config, c ); };
  $.myplugin.config.prototype = _defaultConfig;
  $.myplugin.prototype = {

    openPopup: function () {
      var C = this.config;
      this.pop = $( C.popup ).insertAfter( this.item );
      this.pop.text( 'This says nothing' );
      var self = this;
      $( C.closeButton )
          .appendTo( pop )
          .bind('click', function () {
            self.closePopup();  // closure keeps context
            return false;
          });
      return this;  // chaining
    },

    closePopup: function () {
      this.pop.remove();
      this.pop = null;
      return this;  // chaining
    }

  };

  // expose as a selector plugin
  $.fn.myplugin = function ( config ) {
    return this.each(function(){
      new $.myplugin( this, config );
    });
  };

})(jQuery);
like image 4
Borgar Avatar answered Nov 10 '22 14:11

Borgar


Take a look at the .data functionality of jQuery. It lets you store key/value pairs on any object.

like image 1
Corey Downie Avatar answered Nov 10 '22 15:11

Corey Downie


Use something like this:

acx.data("acx-somename", datavalue);

Then you can later retrieve it with:

var datavalue = acx.data("acx-somename");
like image 1
Anthony Mills Avatar answered Nov 10 '22 16:11

Anthony Mills