Reputation: 5870
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);
Upvotes: 5
Views: 8736
Reputation: 769
Here's my take on it:
I have an object within the closure that is used to create instance objects. The instance objects are attached to the element node using jQuery's data() method. These instance objects have public methods that you can call as needed.
(function($)
{
// This is used to create AutoComplete object that are attatched to each element that is matched
// when the plugin is invoked
var AutoCompleteEx = function(options, acx) {
// PRIVATE VARIABLES
var timerID;
var input;
//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
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");
}
});
// PUBLIC METHODS
this.setTimerID = function(id) {
timerID = id;
};
this.getTimerID = function() {
return timerID;
};
};
//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)
// creating a new AutoCompleteEx object and attach to the element's data, if not already attached
if (!acx.data('autoCompleteEx')) {
acx.data('autoCompleteEx', new AutoCompleteEx(options, acx));
}
}); //End Each JQ Element
}; //End autoCompleteEx()
//Default Settings
$.fn.autoCompleteEx.defaults =
{
minChars: 2,
delay: 300,
maxItems: 1
};
//End Of Closure
})(jQuery);
You can call the methods like this:
$("div#someDiv").autoCompleteEx();
$("div#someDiv").data('autoCompleteEx').setTimerID(123);
var timerId = $("div").data('autoCompleteEx').getTimerID();
console.log(timerId); // outputs '123'
And if you are instantiating more than one:
$("div.someDiv").autoCompleteEx();
$("div.someDiv").eq(0).data('autoCompleteEx').setTimerID(123);
$("div.someDiv").eq(1).data('autoCompleteEx').setTimerID(124);
var firstTimerId = $("div").eq(0).data('autoCompleteEx').getTimerID();
var secondTimerId = $("div").eq(1).data('autoCompleteEx').getTimerID();
console.log(firstTimerId); // outputs '123'
console.log(secondTimerId); // outputs '124'
Upvotes: 1
Reputation:
Instance manipulation! That's what you want, right? Good old fashion, real time instance manipulation. That's what I wanted too. I Googled the same question and couldn't get a good answer anywhere (like the ones above) so I figured it out. I don't like my solution because it seems like a round about way to get access to instance methods and strange for a jquery consumer but jQuery is freakin weird in the first place but lovely. I wrote a simple plugin to fade images but then once I needed to do more with it, I wanted to expose methods to a live instance to get this result here -> example, I did the following:
var instanceAccessor = {};
var pluginOptions = {'accessor':instanceAccessor}
$('div').myPlugin(pluginOptions);
Then inside the plugin, I add on methods to this passed in object 'accessor' since its an object. I expose the methods inside the plugin like this:
if (pluginOptions.accessor != null && typeof(pluginOptions.accessor) === 'object') {
pluginOptions.accessor.exposedMethod = function (someParam) {
// call some private function here and access private data here
};
}
Then, the consumer of this plugin can call the instance method or methods any time during runtime like we used to do before jquery made this strange:
instanceAccessor.exposedMethod('somevalue');
You can look for "dumb cross fade" on jquery plugin search to find my dumb plugin and see the code for yourself.
Upvotes: 0
Reputation: 106342
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));
}
};
Upvotes: 7
Reputation: 38652
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);
Upvotes: 4
Reputation: 8784
Use something like this:
acx.data("acx-somename", datavalue);
Then you can later retrieve it with:
var datavalue = acx.data("acx-somename");
Upvotes: 1
Reputation: 4769
Take a look at the .data functionality of jQuery. It lets you store key/value pairs on any object.
Upvotes: 1