jQuery / javascript argument handling question - javascript

First of all I don't know how to phrase the question "title", sorry if I am confusing everyone with the title here.
Anyway, I saw this code at jQuery http://docs.jquery.com/Plugins/Authoring
(function( $ ){
var methods = {
init : function( options ) {
return this.each(function(){
var $this = $(this),
data = $this.data('tooltip'),
tooltip = $('<div />', {
text : $this.attr('title')
});
// If the plugin hasn't been initialized yet
if ( ! data ) {
/*
Do more setup stuff here
*/
$(this).data('tooltip', {
target : $this,
tooltip : tooltip
});
}
});
},
destroy : function( ) {
return this.each(function(){
var $this = $(this),
data = $this.data('tooltip');
// Namespacing FTW
$(window).unbind('.tooltip');
data.tooltip.remove();
$this.removeData('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 );
My question being is that I cannot understand why do we need this if statement?
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
}
Or in other words, in what scenario that we will pass in argument like "methods[method]" base on the example?
Thanks!

That if statement will check if you are trying to call one of the methods available to the plugin. In the case of you example you have these methods:
init, destroy, reposition, show, hide, and update
So you can do a call like :
$.tooltip('init', { arg1: true, arg2: 'a value' });
Then your code knows where to send the arguments because this if statement will be true:
if(methods['init'])

You see at the beginning that the code defines an object methods.
The function $.fn.tooltip = function( method ) accepts an argument with name method (no s at the end).
The function will execute one of the methods defined in methods, but it can only do it, if this method is also available. Hence the if(methods[method]).
The expression will be true if method is e.g. show, hide, update, etc, i.e. if the methods object has a property with the name contained in method.
Therefore the expression will be false for foo or bar. If the if statement would not be there, the code would try to call method['foo'], which does not exist and you would get an error:
TypeError: object is not a function
Is this what you wanted to know?

Your code snippet isn't complete and it doesn't contain a demo to show how it's called, so it's hard to give a definite answer.
However, here's what I think from what the code looks like:
The if statement is necessary because the tooltip function will be called with arguments such as init, destroy, show, hide, update, which refer to the functions defined in the methods hash. You probably call tooltip with init to initialize the tooltip, hide to hide it, show to show it etc. If you don't pass an argument at all, it defaults to the init method and initializes the tooltip (second branch of the if).

First of all, the piece of code declares an hashmap named methods which contains some functions.
Then, the second part declares a function named tooltip which takes a parameter named method. This parameter is the name of the function we want to call, this name is the index of this function in the methods array.
So, when you do $('#whatever').tooltip('destroy'); it will look in the methods array for the function referenced with the destroy key.

Related

Override jQuery functions simply by extending?

This is related to, but not a duplicate of, another SO Q&A Override jQuery functions.
It is clear from the answer to the above question that the pattern to override a jQuery function is:
(function($){
// store original reference to the method
var _old = $.fn.method;
$.fn.method = function(arg1,arg2){
if ( ... condition ... ) {
return ....
} else { // do the default
return _old.apply(this,arguments);
}
};
})(jQuery);
But why!?
I've been able to override a jQuery function simply by defining a function of the same name as the function to be overridden, within $.extend or $.fn.extend.
Consider this:
// random example showing jquery function overriding
$.fn.extend({
hide: function() {
$(this).css({"color":"red"});
}
});
$("#test").hide(); // this will actually paint the #test element red!
jsFiddle
I'd like to understand why _old.apply(this,arguments) would be the preferred way to override a jQuery function, as listed here and here.
From glancing at references provided at original post, summary of pattern could be to keep both "old" and "new" methods available ?
Edit, updated
Sorry, I don't get this. As far as I see, the reference to the overridden method is saved in a local variable in a closure is
unquestionably lost outside the closure. Can you explain how the "old"
method is still available? –SNag
I'd like to understand why _old.apply(this,arguments) would be the
preferred way to override a jQuery function, as listed here and
here.
Utilizing pattern at 1st link , above , if interpret pieces correctly, appear arguments test within if statement of jquery method within "self-executing anonymous function" determine return value of "old" or "new" (newly included; override) jquery method ?
i.e.g., try
html
<div>abc</div>
js
// See http://www.paulirish.com/2010/duck-punching-with-jquery/ , at
// `First we start off with a self-executing anonymous function,
// that makes a happy closure while remapping jQuery to $:`
// `closure` start
(function ($) {
// jquery `.css()`
var _oldcss = $.fn.css;
// jquery `.hide()`
var _oldhide = $.fn.hide;
// "new" `.css()`
$.fn.css = function (prop, value) {
// "new" `.css()` `test`
if (/^background-?color$/i.test(prop)
&& value.toLowerCase() === 'burnt sienna') {
return _oldcss.call(this, prop, '#EA7E5D');
} else {
return _oldcss.apply(this, arguments);
}
};
// "new" `.hide()`
$.fn.hide = function (prop, value) {
// "new" `.hide()` `test`
if (/color/i.test(prop) && /[a-f]|[0-9]/i.test(value)) {
return $.fn.css.call(this, prop, value);
} else {
return _oldhide.apply(this, arguments);
}
};
})(jQuery);
// `closure` stop
// and using it...
// "new" `.css()`
jQuery(document.body).css('backgroundColor', 'burnt sienna');
// "old" `.css()`
$("div").css("color", "yellow");
// "old" `.hide()`
$("div").hide(7500, function () {
// "old" `.css()`
$(document.body)
.css({
"transition": "background 2s",
"background": "#edd452"
})
.find($("div")).show(2500)
// "new" `.hide()`
.hide("color", "red")
});
jsfiddle http://jsfiddle.net/guest271314/5bEe4/
(function($){
// store original reference to the method
// stored locally
var _old = $.fn.method;
$.fn.method = function(arg1,arg2){
if ( ... condition ... ) {
return ....
} else { // do the default
// call apply method, in order to pass the this context.
return _old.apply(this,arguments);
}
};
})(jQuery);
Here in the above code, we are calling an anonymous function, in which we are declaring a local variable _old. When this anonymous function execute, it save the _old method reference and form a closure.
Now, when we call the new method, i.e,
$.fn.method = function(arg1,arg2){
if ( ... condition ... ) {
return ....
} else { // do the default
return _old.apply(this,arguments);
}
};
we also have an access to _old method, since its scope exists in the current context. And then, we can use it inside the new method.
Here we are calling _old method with the help of apply, because we want to have the same this context for that as well.
With this approach, we can easily override the jQuery method by preserving its original functionality.

Understanding jQuery return object

I'm trying to understand how jQuery creates the return object when searching for DOM elements. I've gone through the source, but I'm not completely sure I understand, and was hoping somebody here could give me some insight.
From what I can gather reading the source, when querying for a jQuery DOM, jQuery finds matching DOM elements, and then adds the matched DOM element as an object using the index of the element as the key for the new object.
if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
for ( match in context ) {
// Properties of context are called as methods if possible
if ( jQuery.isFunction( this[ match ] ) ) {
this[ match ]( context[ match ] );
// ...and otherwise set as attributes
} else {
this.attr( match, context[ match ] );
}
}
}
return this;
Returning this, is returning the entire jQuery object which includes all the methods. Have I got it right to this point?
Now, it appears all the functions like css,find,ajax,hide,etc. are in the jQuery.fn object.
Somehow (and I think this is where I'm not seeing it), these functions are called, not on the DOM element itself, but through the access.js
https://github.com/jquery/jquery/blob/master/src/core/access.js
var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
using css as an example, we have
jQuery.extend({
css: function( elem, name, extra, styles ) {...
jQuery.fn.extend({
css: function( name, value ) {
return access( this, function( elem, name, value ) {
var styles, len,
map = {},
i = 0;
if ( jQuery.isArray( name ) ) {
styles = getStyles( elem );
len = name.length;
for ( ; i < len; i++ ) {
map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
}
return map;
}
return value !== undefined ?
jQuery.style( elem, name, value ) :
jQuery.css( elem, name );
}, name, value, arguments.length > 1 );
What I think I'm missing is how did we get from calling $('div').css(...) to that calling the jQuery.fn.extend.css method, and from there, the access method being called with a different signature to the access method initialized in the core jQuery?
Also, if we're constantly replacing the jQuery[0],jQuery[1], how is it that I can have:
var divs = $('div');
var spans = $('span');
Maintaining two different set of document tags if they are both returning the same jQuery object? I thought the object would be updated.
Am I completely misunderstanding how this is all working?
From what I can gather reading the source, when querying for a jQuery
DOM, jQuery finds matching DOM elements, and then adds the matched DOM
element as an object using the index of the element as the key for the
new object.
Yes. jQuery instances are basically array-like objects.
if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
for ( match in context ) {
// Properties of context are called as methods if possible
if ( jQuery.isFunction( this[ match ] ) ) {
this[ match ]( context[ match ] );
// ...and otherwise set as attributes
} else {
this.attr( match, context[ match ] );
}
}
}
return this;
But that's not what happens in this cited section of the code. What you see here is the code for handling the jQuery(html, attributes) signature - when the second argument is an object and the first is standalone html tag, then call the respective methods or set the attributes on the new collection (this).
Returning this, is returning the entire jQuery object which includes
all the methods. Now, it appears all the functions like css,find,ajax,hide,etc. are in
the jQuery.fn object.
Yes. The objects that are returned by the jQuery constructor do inherit these methods from the $.fn prototype object.
Somehow (and I think this is where I'm not seeing it), these functions
are called, not on the DOM element itself, but through the access.js
https://github.com/jquery/jquery/blob/master/src/core/access.js
access is just an internal helper function. All the jQuery methods are called on jQuery instances.
using css as an example, we have
jQuery.extend({
css: function( elem, name, extra, styles ) {...
jQuery.css() is just a "static", internal helper function for getting computed css values. Nothing you'd ever directly use yourself.
jQuery.fn.extend({
css: function( name, value ) {
return access( this, function( elem, name, value ) {
…
}, name, value, arguments.length > 1 );
}
What I think I'm missing is how did we get from calling
$('div').css(...) to that calling the jQuery.fn.extend.css method
There is no jQuery.fn.extend.css method. That call to jQuery.fn.extend() does define the jQuery.fn.css method. And that's just the method you call - it's prototypically inherited by $('div').
and from there, the access method being called with a different signature
to the access method initialized in the core jQuery?
No, why do you think that?
// the signature:
access = function( elems, fn, key, value, chainable, emptyGet, raw )
// the call:
access( this, // array-like collection
function(…){…}, // callback
name, // string
value, // whatever
arguments.length > 1 // boolean whether it's a getter
// undefined, implicit
// undefined, implicit
)
Also, if we're constantly replacing the jQuery[0],jQuery[1]
No, we aren't? Where did you see that?
how is it that I can have: var divs = $('div'); var spans = $('span');
Maintaining two different set of document tags if they are both
returning the same jQuery object?
They aren't. Both calls do create new jQuery instances.
I thought the object would be updated.
No, jQuery instances are quite immutable.

jQuery widget function not getting called

Update:
Sorry for the lengthy stupid question. It was a problem with my selector. Ignore post!
I have a widget that is having public functions. that I normally am able to call like:
$("#mygrid").myGrid( "MyFunction", args );
Problem:
But in some cases the function is not getting called.
Here are some trivia
My widget is derived from another (base)widget I created.
The same function works when called for the base widget
The function is not overridden in the child widget.
The same function works for another child widget of the same parent
with the same piece of code I mentioned earlier except the name of
the widget and the DOM element.
Now, here are some important information.
A similar function in the problem widget works and when I stepped inside the call, I found a difference in both the calls in the following function in jquery-ui.js:
$.widget.bridge = function( name, object ) {
var fullName = object.prototype.widgetFullName || name;
$.fn[ name ] = function( options ) {
var isMethodCall = typeof options === "string",
args = slice.call( arguments, 1 ),
returnValue = this;
// allow multiple hashes to be passed on init
options = !isMethodCall && args.length ?
$.widget.extend.apply( null, [ options ].concat(args) ) :
options;
if ( isMethodCall ) {
this.each(function() {
var methodValue,
// Rest of the code.
Here, For my function that is not getting called, it won't enter in that this.each. when I checked the this pointer, there was a difference.
For the function that is getting called, the content of this is like the following:
0: div#myGrid
context: document
length: 1
selector: "#mygrid"
__proto__: Object[0]
But for the function that is NOT getting called, the first item and the length attribute was missing.
context: document
selector: "#mygrid"
__proto__: Object[0]
What Could be the problem?

Passing parameters to a jQuery closure not working on Multisuggest plugin

I have a question of which someone might find this much simpler than I do, but alas, I don't have much experience with custom jQuery plugins.
The previous developer at my place of work left me with a lot of left-over plugins that don't seem to work very well, most which I've been able to fix but this which has been bugging me for a while.
It is a custom Multiple Suggestion plugin (called multisuggest) written in jQuery, and it has a set of functions that it uses internally (*e.g. setValue to set the value of the box, or lookup to update the search)*
It seems he's tried to call these plugin functions from an external script (this exteranl script specifically imports newly created suggestions into the multisuggest via user input and sets the value) like this:
this.$input.multisuggest('setValue', data.address.id, address);
This seems to call the function as it should, except the second and third parameters don't seem to be passed to the function (setValue receives nothing), and I don't understand how I can get it to pass these. It says it is undefined when I log it in the console. The functions are set out like this (I've only including the one I'm using and an internal function from multisuggest called select that actually works):
MultiSuggest.prototype = $.extend(MultiSuggest, _superproto, {
constructor : MultiSuggest,
select: function () { // When selecting an address from the suggestions
var active, display, val;
active = this.$menu.find('.active');
display = active.attr('data-display');
val = active.attr('data-value');
this.setValue(display, val, false); // This works, however when I do it as shown in the above example from an external script, it doesn't. This is because it doesn't receive the arguments.
},
setValue : function(display, value, newAddress) { // Setting the textbox value
console.log(display); // This returns undefined
console.log(value); // This returns undefined
if (display && display !== "" &&
value && value !== "") {
this.$element.val(this.updater(display)).change();
this.$hiddenInput.val(value);
this.$element.addClass("msuggest-selected");
}
if(newAddress === false){
return this.hide();
}
},
});
Why does it listen to the function, but not the values passed to it? Do I need to include an extra line of code somewhere to define these arguments?
Anyone with jQuery experience would be of great help! This is bottlenecking progress on a current project. Thanks for your time!
EDIT:
I've missed out the code of how the arguments are trying to be passed from the external script to the internal function of the plugin. Here is the plugin definition with how the external call is handled, can anyone see a problem with this?
$.fn.multisuggest = function(option) {
return this.each(function() {
var $this = $(this), data = $this.data('multisuggest'), options = typeof option === 'object' && option;
if (!data) {
$this.data('multisuggest', ( data = new MultiSuggest(this, options)));
} else if (typeof(option) === 'string') {
var method = data[option];
var parameters = Array.prototype.slice.call(arguments, 1);
method.apply(this, parameters);
}
});
};
The "usual" plugin supervisor looks like this :
// *****************************
// ***** Start: Supervisor *****
$.fn.multisuggest = 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 in jQuery.' + pluginName );
}
};
// ***** Fin: Supervisor *****
// ***************************
All the looping through this should be inside the method functions, not in the supervisor.
I'm a little worried that new MultiSuggest(...) appears in the current supervisor. That sort of thing is totally unconventional. The original author clearly had something in mind.
You need to extend the jQuery plugin function which is attached to $.fn['multisuggest'], that function is probably only taking and passing one parameter.

JQuery plugins: Function using $(this) inside the plugin's options

Hello everyone.
I am trying to develop a Jquery plugin following the steps I found in http://docs.jquery.com/Plugins/Authoring and I seem to have problems reaching the caller object (the “this” variable) inside the options passed to the plugin. It is a plugin that I just want to use to make a button have a “blink” effect.
I would like to be able to pass the functions to execute in “show/hide” (or link blink-on, blink-off, if you prefer) as an option for the plugin. Let's say the user wants to achieve the “blinking” effect by hiding/showing the whole button every 1000 milliseconds. Then I would like the options to be something like:
$("#bttnOk").myBlinker ({
blinkHide: function(){$(this).hide();},
blinkShow: function(){ $(this).show();},
interval:1000
});
// … //
// And to make it actually blink:
$("#bttnOk").myBlinker ("blink");
Or let's say that the user wants to move the button up and down applying an inline css sytle every 200ms. Then the options would something like:
$("#bttnOk").myBlinker ({
blinkHide: function(){$(this).css(“margin-top: 10px”);},
blinkShow: function(){ $(this).css(“margin-top: 0px”);},
interval:200
});
The problem is that I seem to lose the reference to “$(this)” when I am inside the options. When the plugin reaches the blinkHide/blinkShow functions, “this” is the whole DOM window, not the button $(“#bttnOk”) my “myBlinker” plugin is attached to.
This is the first Jquery plugin I'm trying to write so I'm not even sure if there's a way to achieve what I'm trying to do.
My plugin code follows the following structure:
(function($){
var defaultOptions = {
interval: 500
}
var methods = {
init : function( options ) {
return this.each(function(){
this.options = {}
$.extend(this.options, defaultOptions, options);
var $this = $(this);
var data = $this.data('myBlinker');
// If the plugin hasn't been initialized yet
if ( ! data ) {
$this.data('myBlinker', {
on : true
});
}
});
},
destroy : function( ) { // Some code here},
blink: function ( ){
console.log("Blinking!. This: " + this);
var current = 0;
var button=this.get(0);
setInterval(function() {
if (current == 0){
button.options["blinkShow"].call(this);
current=1;
} else {
button.options["blinkHide"].call(this);
current=0;
}
}, button.options["interval"]);
}
};
$.fn. myBlinker = 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.myBlinker ' );
return null;
}
};
})(jQuery);
Any idea, correction, link or tip will be appreciated.
Thank you.
Within the setInterval function, this is the global object, not the current element DOMElement like in the blink function.
A solution to that is to save a reference of this and use this saved reference in the setInterval:
blink: function ( ){
// save a reference of 'this'
var that = this;
setInterval(function() {
// use the saved reference instead of 'this'
button.options["blinkShow"].call(that);
}, button.options["interval"]);
}
DEMO

Categories

Resources