I am trying to use jQuery's .data() tool in a jQuery plugin but I'm getting some odd results.
Here is a cut down version of my plugin with the relevant code:
(function( $ ){
var methods = {
init : function( options ) {
// Do some stuff
this.data('name', 'New Component');
return this;
},
getStateData : function () {
// Function returns all data for save
var state = new Object;
state.name = this.data('name');
// snip ... Add some other bits to state
// ...
return state;
},
setStateData: function (args) {
var key = args[0];
var value = args[1];
// snip
this.data(key, value);
}
};
$.fn.component7Segment = function(method) {
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.designComponent' );
}
};
})( jQuery );
If I call $(instance-of-plugin).component7Segment('getStateData'); the plugin returns the correct 'name' however if I call setStateData to change the name it has not changed the 'name' value when I next call getStateData.
Doing console.log(this); in each function looks slightly different so I started trying:
$.data($('#object-id'), 'name', value);
This still doesn't have the desired effect.
Can anybody see what I am missing?
Assuming you're calling setStateData like this:
$(instance).component7Segment("setStateData", "name", "Foo");
the setStateData function should be changed to look like this:
setStateData: function () {
var key = arguments[0];
var value = arguments[1];
// snip
this.data(key, value);
}
Note the removal of the args parameter and the use of the special arguments array-like object instead.
Example: http://jsfiddle.net/andrewwhitaker/ShFHC/
Your original problem was most likely that you were calling setStateData with parameters name and "foo". args[0] and args[1] was actually accessing the characters in name at that position instead of the arguments passed to the function.
Related
I use totango for some usage tracking. Now, while we try to rework the way we track, I want to send trackings into 2 different totango accounts, for a transition period.
I've managed to split the objects into window.totango_old and window.totango_beta.
Now instead of replacing all the old usages of window.totango, I was wondering whether I can make window.totango simply apply any arbitrary method I use on it onto the 2 different objects specified above.
I've tried figuring out the usage with .apply(), but I can't fully grasp how it would work in my case.
I want to avoid doing this:
window.totango = function() {
return {
track: function(event, value) {
window.totango_old.track(event, value);
window.totango_beta.track(event, value);
}
}
}
Because it means I have to map the usable functions one by one. Is there a "catch-all" way that would pass any method I call on an object, and let me get its name and arguments, to pass on to different objects dynamically?
I tried running a test, like so:
window.test2 = function() {
return {
testFunc: function(a, b) {
console.log([a, b]);
}
}
};
window.test = function() {
this.apply(window.test2, arguments)
// also tried: window.test2.apply(window.test2, arguments)
};
window.test.testFunc("1", "2");
But I received the following exception:
Uncaught TypeError: undefined is not a function
You could use something like this:
window.callFunction = function( method, args ) {
var res = { };
//Set the default values
method = method || 'track';
args = args || [ ];
//in case we want the result of the functions
res.old = window.totango_old[ method ].apply( window.totango_old, args );
res.beta = window.totango_beta[ method ].apply( window.totango_beta, args );
return res;
}
Because totango_old is an object, you can use the name of the method as the index, then call apply on the returned function and pass your arguments. The first parameter of apply is the value of "this" in that context. Depending on how the module is set up it is important to have the right value in the first parameter. The second parameter passed to apply is the arguments to pass to the function.
You could possibly do something like this
function TotangoFill () {
}
TotangoFill.prototype = {
callFunction: function ( method, args ) {
var res = { };
//Set the default values
args = args || [ ];
//in case we want the result of the functions
res.old = window.totango_old[ method ].apply( window.totango_old, args );
res.beta = window.totango_beta[ method ].apply( window.totango_beta, args );
return res;
},
track: function( event, value ) {
// arguments is an array of all the arguments passed to this function
return this.callFunction( 'track', arguments );
},
anotherMethod: function( ) {
//You don't need to define the parameters, just pass them on
return this.callFunction( 'anotherMethod', arguments );
},
customEvent: function( value ) {
//Add another layer of abstraction
return this.track( 'customEvent', value );
}
}
window.totango = new TotangoFill( )
You have a problem with your attempt to wrap totango which explains the error in your test, and can be easily resolved without changing your invocations.
Specifically, you need to actually invoke the function that you assigned to window.totango such that totango contains the returned object, and not the function itself, i.e.:
window.totango = (function() {
return {
track: function(event, value) {
window.totango_old.track(event, value);
return window.totango_beta.track(event, value);
}
}
})(); // invocation
i write this plugin and now i want add method to this plugin such as this
$.createMessage().removeMessage()
how can i do it?
my code is
$(function () {
$.extend({
createtext: function (options) {
var setting = {
holder: "",
text: "",
}
if (options != null) {
$.extend(setting, options)
}
var $this = $(setting.holder)
$this.find("div#CreatetextHolder").remove()
$this.append("<div id='CreatetextHolder'><span></span><p class='Createtext'>" + setting.text + "</p></div>")
$this.find("div#CreatetextHolder").fadeIn('slow')
}
})
})
thank you for your help
$(selector).createMessage().removeMessage() would require you to write two plugins - one for 'create' and the other for 'remove'.
It's far better to do everything in one plugin and you can do so by targeting the syntax ...
$(selector).createMessage('remove');
Then it's a matter of testing options in the plugin code, and branching accordingly.
Currently you test if (options != null) assuming options to be a javascript plain object and that the only action is initianisation.
But with my suggestion to allow $.createMessage('remove'), you need to perform more extensive testing/branching depending on what parameter(s) are actually passed.
For example:
$(function () {
$.extend({
createtext: function ( method, options ) {
var settings = {
holder: "",
text: ""
};
var methods = {
'init': function(options) {
var _settings = $.extend({}, settings, options);//this leaves `settings` unaffected and available for reuse in future inits.
//initialize here
},
'remove': function() {
//uninitialize here
}
}
// These tests allow `init' to be passed explicitly,
// or assumed if an options object is the only argument.
// Otherwise, a method such as 'remove' may be passed,
// with or without further parameters.
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || !method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist in jQuery.createtext');
}
}
});
});
I'm trying to make a jQuery plugin for custom checkboxes and radio buttons.
(function($)
{
$.fn.checkboxRadio = function(options)
{
var defaults = some;
...
return this.each(function()
{
var button = $(this);
...
});
}
})(jQuery);
It can be used now by $('input').checkboxRadio(options);
How do I add a method check without changing current scope, to make a possible usage of something like $('input').checkboxRadio('check')?
How to handle a custom method and get its name inside my plugin?
Here is the official jquery plugin guide.
The part about wrapping functions is found here ("Plugin Methods") (the example is a would-be tooltip plugin) :
(function( $ ){
var methods = {
init : function(options) { ... },
show : function() { ... },
hide : function() { ... },
update : function(content) { ... }
};
$.fn.tooltip = function( method ) {
// Method calling logic
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.tooltip' );
}
};
})(jQuery);
[update] explaining the methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 )) line in the guide :
If you call $(selector).tooltip('update', 'hello') from your javascript code, you want to end up calling the update method, passing 'hello' as the content argument, with this set to $(selector) for the duration of the call.
That is what this line takes care of :
if method == 'update', methods[method] is the update method,
arguments will be equal to ['update', 'hello'], you have to drop the first element to get the arguments you want to pass to your method ; this is exactly what Array.prototype.slice.call(arguments, 1) does,
myFunc.apply(obj, argsArray) calls the function myFunc, passing argsArray as the arguments, and setting this to obj for the duration of the call.
So inside your methods, you can call this.each(...) to iterate over all of the selector's items, e.g. :
update: function(content) {
this.each(function(){ $(this).data('tooltip.content', content); });
return this;
}
You can connect plugin methods like this:
(function($) {
$.fn.checkboxRadio = function(options) {
var defaults = {
check: 'check'
}
return this.each(function() {
var o = options;
var _this = $(this);
if( o.check === 'check' ) {
_this.attr('checked','checked');
}else if ( o.check === 'uncheck' ) {
_this.removeAttr('checked');
}
});
}
})(jQuery);
and user document should be like what you want: $('input').checkboxRadio({check:'check'});
I'm trying to create my first jQuery plugin, reading jQuery plugin authoring and one of the things it really emphasizes is to use methods instead of namespaces. I'm also trying to use the data from that guide to bounce variables between methods but it's not working. I need to create some data, pass it to another method to update it and send it somewhere else.
(function($) {
var methods = {
init : function(settings) {
return this.each(function(){
var x = 3;
var object = $(this);
// Bind variables to object
$.data(object, 'x', x);
$(object).bbslider('infoParse');
var y = $.data(object,'y');
alert(y);
}); // End object loop
}, // End init
infoParse : function() {
var object = $(this);
var x = $.data(object,'x');
alert(x);
var y = 10;
$.data(object,'y',y);
}, // End infoParse
}; // End method
$.fn.bbslider = function(method) {
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.bbslider' );
}
}; // End slider
})(jQuery);
Here's a jsFiddle to show what I mean: http://jsfiddle.net/wRxsX/3/
How do I pass variables between methods?
see if this http://jsfiddle.net/wRxsX/6/ is ok ?
element.data(k,v)
$.data should use on element
http://api.jquery.com/data/
Store arbitrary data associated with the matched elements or return the value at the named data store for the first element in the set of matched elements.
So I'm trying to learn how to implement method collection for a plugin based on this example: http://docs.jquery.com/Plugins/Authoring
What I cannot understand is how options that are extended with defaults for the plugin get sent to the individual methods.
I'm pretty sure any original options get sent to the method here:
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
So how can you extend these arguments with defaults? The example doesn't really define how to do this...
var methods = {
init : function( options ) {
return this.each(function(){
$(window).bind('resize.tooltip', methods.reposition);
});
}
}
Also, here is the code from the example plugin authoring page:
(function( $ ){
var methods = {
init : function( options ) {
return this.each(function(){
$(window).bind('resize.tooltip', methods.reposition);
});
},
destroy : function( ) {
return this.each(function(){
$(window).unbind('.tooltip');
})
},
reposition : function( ) { // ... },
show : function( ) { // ... },
hide : function( ) { // ... },
update : function( content ) { // ...}
};
$.fn.tooltip = function( method ) {
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.tooltip' );
}
};
})( jQuery );
Looks like my previous answer was closer to the mark than I previously thought.
Yes, this line is passing on the arguments:
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
Using .apply(), you can call a method, change its context (this value), and give it a collection of arguments instead of individual ones. Handy if you don't know how may arguments need to be passed on.
So you can break the above line down like this:
// reference the arguments passed to the plugin
var args = arguments;
// eliminate the first one, since we know that's just the name of the method
args = Array.prototype.slice.call( arguments, 1 )
// call the method that was passed,
// passing along the Array of the arguments (minus the first one),
return methods[method].apply( this, args);
// Note that it is being called from the context of "this" which is the jQuery object
So if the plugin was called like this:
$('.someElem').tooltip('destroy', 'someArg', 'anotherArg' );
The code will locate the "destroy" method, slice "destroy" off of the Arguments object, and call "destroy" passing along the Array of remaining arguments.
you use $.extend(existingObject || {} (new object), oneObject, secondObject, nthObject)
var mydefaults = { myDefaultArg : "foo" }
var inputOptions = { myDefaultArg : "Somethin else" }
var options = $.extend({}, mydefaults, inputOptions);
options.myDefaultArg == "Somethin else";
To access data, or to save them,you can use the data method
so if you are in the plugin, "this" is the jquery element element, you can now save data into it.
$(this).data("pluginname.somedataname", data);
and retriev it
var data = $(this).data("pluginname.somedataname");