Understanding the usage of parameters passed inside $.each in plugin code - javascript

I am new to jQuery and just learning new stuff. I was just reading through Chris Coyer's article and came across the following code :
$.fn.faq = function(options) {
return this.each(function(i, el) {
var base = el,
$base = $(el);
console.log(options);
base.init = function() {
// Do initialization stuff
$base
.find("dd")
.hide()
.end()
.find("dt")
.click(function() {
var ans = $(this).next();
if (ans.is(":visible")) {
base.closeQ(ans);
} else {
base.openQ(ans);
}
})
};
base.openQ = function(ans) {
// Open panel
ans.show();
// Do callback
options.qOpen.call();
};
base.closeQ = function(ans) {
// Open panel
ans.hide();
// Do callback
options.qClose.call();
};
base.init();
});
};
$("dl").faq({
qOpen: myQuestionOpenCallback,
qClose: myQuestionCloseCallback
});
function myQuestionOpenCallback() {
alert("answer opened!");
}
function myQuestionCloseCallback() {
alert("answer closed!");
}
Now I didn't quite understand this part of the code:
return this.each(function(i, el) {
The second line in the code, what exactly is i and el? I don't see anywhere these parameters being passed into the each function.
I asked a senior colleague of mine and got the following answer:
Many plugins start that way. Since most plugins are chainable, they
have to return this. And they also have to loop through the elements
from the selector,
return this.each(function(i, el) {
does them both. A loop, then the return.
but I still didn't quite understand.
The JS Fiddle can be found here.

Inside a jQuery plugin, this refers to the jQuery object representing what you called the plugin on. For example, in the case of this faq plugin, if I call $('#someDiv').faq({ ... });, this will be the same as $('#someDiv') inside the plugin function.
So because it is a jQuery object representing a selection of DOM nodes, you can call the jQuery method .each() on it, which takes a function that gets given two parameters when it is called for each DOM node in the selection:
The index (0, 1, 2 and so on)
The DOM node itself
.each() also returns the thing it was called on, so you end up returning the $('#someDiv') object from the plugin. That's great, because then we can call some other jQuery method on it straight afterwards ("chaining"). e.g. $('#someDiv').faq({ ... }).hide(); to hide it immediately.

https://api.jquery.com/jquery.each/
i : index of the element.
el : the DOM element (not a jQuery object).

Related

Functions in javascript

I am new to javascript and I was messing around with it. I was checking out jquery and I wanted to see if I could create something to do the same things. This is my js file
//returns a dom
var $ = function(selector) {
return document.querySelector(selector);
};
//adds something to the dom
var append = function(value) {
this.innerHTML += value;
};
//clears dom
var empty = function() {
while (this.lastChild) this.removeChild(this.lastChild);
};
When I call $('#test') I get the dom with id test. When I call $('#test').append('hi there') it works. However when I try $('#test').empty() I get a Uncaught TypeError: $(...).empty is not a function Any idea why? If I am doing something comletely wrong please let me know. Thanks
Your functions aren't added to the prototype chain of DOM elements, (that wouldn't be a good idea), so they can't be called as methods of DOM elements.
append works, because the DOM node already had a method called append, and it has nothing to do with the function you stored in a variable called append.
jQuery works by creating a container object that holds a collection of DOM elements and has it's own methods. EG:
var $ = function(selector) {
var objects = document.querySelectorAll(selector);
return {
append: function ( ) {
// do something to `objects` here
},
empty: function ( ) {
},
};
};

jQuery plugin return this.each

I am bulding a plugin let's call it ptest and I want to be able to call it with:
$(".myClassName").ptest();
Since I am using attributes from the element on which the plugin is called, lets say data-attribute I now know that returning this.each(...); is a must.
Here is my code:
(function($){
var op;
$.fn.ptest = function(options) {
op = $.extend({
target: null,
attribute: null
}, options);
return this.each(function(){
op.target = $(this);
op.attribute = op.target.attr("data-attribute");
bind();
});
};
function bind(){
op.target.find('.clickable').bind('click',log);
}
function log(){
console.log(op.attribute);
}
}(jQuery));
I know that by having op as a global variable it will always retain the last value for the attribute and the target. How can I make the op variable retain the correct value for each element of .myClassName while being able to access each op from log or bind functions?
I sense i need to declare the functions and the variable in a different way, but how?
I have looked at a lot of different questions and tutorials, here are some:
http://devheart.org/articles/tutorial-creating-a-jquery-plugin/
jQuery plugin development - return this.each issue
jQuery Plugin Return this.each and add function property to each object?
https://learn.jquery.com/plugins/ (of course)
If bind and log really need access to the specific element in the loop, then you need to define them within the each callback, and make op local to that callback:
(function($){
$.fn.ptest = function(options) {
return this.each(function(){
var op = $.extend({
target: $(this)
}, options);
op.attribute = op.target.attr("data-attribute");
bind();
function bind(){
op.target.find('.clickable').bind('click',log);
}
function log(){
console.log(op.attribute);
}
});
};
}(jQuery));
But depending on how you're using bind and log, there may be other options available.

how does $(this) work when nothing is selected

I just got into caching my jquery objects but not sure how to properly do it when using (this).
Also the only time I know how to use (this) is when its inside a click object or function so like:
$(".trigger").click(function () {
if ($(this).hasClass('toggle')) {
$(this).closest("td").next().find(".clueless > div").slideUp();
$(this).removeClass('toggle');
} else {
$(this).closest("td").next().find(".clueless > div").slideDown();
$(this).addClass('toggle');
}
});
so if I wanted to cache the $(this).closest("td").next().find(".clueless > div") the only thing I could think would be:
var $something = $(".trigger").find(this).closest("td").next().find(".clueless > div");
I will not completely re-iterate how the this keyword works, but there's an exhaustive explanation here.
In JS when the default this behaviour is not altered
Keeping things simple, to know the object to which the this keyword refers to you can simply look at the left-side of the . in a function invocation.
For example, in myObj.someFunction(), the this keyword within someFunction will point to myObj (that is unless the function has been bound using Function.prototype.bind).
If the function is not invoked on an object, such as someFunction(), then this will point to the global object which is window in browsers.
This is also the case within anonymous functions that are passed around, except for addEventListener, which will make sure that the this value within the handler is the object to which the handler was attached.
E.g.
setTimeout(function () { this; /*window*/ }, 10);
document.addEventListener('click', function (e) {
e.target; //the clicked DOM element
this; //the document
});
When this is altered by the API
Using Function.prototype.call or Function.prototype.apply, it is possible to specify the object to which this will point to during a function execution.
Some libraries (e.g. jQuery) are taking advantage of that feature to make this point to an object that is more intuitive, rather than the global object.
E.g.
$('#someEl').on('click', function (e) {
this; //the DOM element that was clicked (not the jQuery wrapper)
});
When this is altered in such way by the library, there is no other way than looking at the library's documentation to see what this will be.
We can read from jQuery event docs that:
In addition to the event object, the event handling function also has
access to the DOM element that the handler was bound to via the
keyword this.
Rewriting your function
Now, here's how you could re-write your function:
$(".trigger").click(function () {
var $this = $(this).toggleClass('toggle'),
$elementToSlide = $this.closest("td").next().find(".clueless > div"),
isToggled = !$this.hasClass('toggle'),
slideBehavior = isToggled? 'slideUp' : 'slideDown';
$elementToSlide[slideBehavior]();
});
Not sure exactly what you're trying to achieve but you can cache $(this) whenever it has context.
$(".trigger").click(function () {
var $trigger = $(this);
if ($trigger.hasClass('toggle')) {
$trigger.closest("td").next().find(".clueless > div").slideUp();
$trigger.removeClass('toggle');
} else {
$trigger.closest("td").next().find(".clueless > div").slideDown();
$trigger.addClass('toggle');
}
});
If you want to cache $(this).closest("td").next().find(".clueless > div") then
$(".trigger").click(function () {
var el = $(this).closest("td").next().find(".clueless > div");
if ($(this).hasClass('toggle')) {
el.slideUp();
$(this).removeClass('toggle');
} else {
el.slideDown();
$(this).addClass('toggle');
}
});
I assume this is what you are going for, it would make sense to cache $(this) since you are using it multiple times
var $this = $(this);
would do it

jQuery plugin instance variables

I'm beginning with jQuery plugins, apologies for the newbie question. My objective is to have a single plugin instantiated twice, where each instance has its own variables values. However, they seem to share the namespace.
For example, given the following plugin:
(function ( $ ) {
var x = false;
$.fn.test = function() {
alert(x);
if ( !x )
x = true;
return this;
};
}( jQuery ));
that is invoked from the following divs:
$( "div1" ).test();
$( "div2" ).test();
The alert displays first false, then true, when the objective is to have to sets of variables where the alert would display false twice.
is this possible?
There is some confusion in your question. Your plugin is a simple function. You don't "instantiate" a function by calling it. So you don't "instantiate" your plugin either.
You can instantiate things in your function, and persist them somewhere.
Since the outer scope runs only once, in your original solution you only get one instance of variable x.
If you create it inside the function, a new instance gets created every time you call it.
I assume you want to create an instance for every element you call this plugin on. A good solution would be to attach your data to the DOM element you initiate your plugin on, like:
(function ( $ ) {
$.fn.test = function() {
var vars = this.data('testPlugin');
if (!vars) {
vars = {
x: false,
something: 'else'
};
this.data('testPlugin', vars);
}
alert(vars.x);
vars.x = !vars.x;
return this;
};
}( jQuery ));
Try this fiddle.
You should put
var x = false;
inside $.fn.test function, otherwise the x variable is the same for all test() functions, and set to true after first call.
You can read more here about javascript variable scoping.
Actually, this is much easier than the previous answers. The context of this in your plugin is the jQuery object for the DOM element you're receiving based on the selector you provided. To gain uniqueness, simply iterate over each element, like so:
(function($) {
$.fn.test = function() {
return this.each(function() {
var x = false;
alert(x);
if (!x) {
x = true;
}
});
}
}(jQuery));
$("div1").test(); //false
$("div2").test(); // false
Here's a JSFiddle to confirm: http://jsfiddle.net/Z6j7f/

Is it a good practice to store jquery plugin configuration in data?

I want to create jQuery plugin with config (for example plugin myplugin).
Than call $(elem).myplugin(config); After that I want to call methods from this plugin like $(elem).myplugin().method() with already stored config.
My offer is something like that:
(function($) {
$.fn.myplugin = function(options) {
var $this = $(this);
var getOptions = function() {
return $this.data('myplugin');
};
var initOptions = function(opt) {
$this.data('myplugin', opt);
};
var setOption = function(key, value) {
$this.data('myplugin')[key] = value;
}
var updateBorderWidth = function() {
$this.css('border-width',
getOptions().borderWidth * getOptions().coeficient);
};
var init = function(opt) {
initOptions(opt);
updateBorderWidth();
}
function changeBorder(width) {
setOption('borderWidth', width)
updateBorderWidth();
}
if(options) {
init(options);
}
return {
changeBorder : changeBorder
};
}
})(jQuery);
And usage:
$(function() {
var item1 = $('#test1').myplugin({ coeficient: 1, borderWidth: 1 });
var item1 = $('#test2').myplugin({ coeficient: 2, borderWidth: 1 });
$('#btn').click(updateBorder);
});
function updateBorder() {
$('#test1').myplugin().changeBorder($('#inpt').val());
$('#test2').myplugin().changeBorder($('#inpt').val());
}
Example: http://jsfiddle.net/inser/zQumX/4/
My question: is it a good practice to do that?
May be it's incorrect approach. Can you offer better solution?
Edit:
After searching for threads on jQuery plugin template I found these Boilerplate templates (updated) which are more versatile and extensive designs than what I've offered below. Ultimately what you choose all depends on what your needs are. The Boilerplate templates cover more use cases than my offering, but each has its own benefits and caveats depending on the requirements.
Typically jQuery plugins either return a jQuery object when a value is passed to them as in:
.wrap(html) // returns a jQuery object
or they return a value when no parameter is passed in
.width() // returns a value
.height() // also returns a value
To read your example calling convention:
$('#test1').myplugin().changeBorder($('#inpt').val());
it would appear, to any developer who uses jQuery, as though two separate plugins are being utilized in tandem, first .myplugin() which one would assume will return a jQuery object with some default DOM maniplulation performed on #test1, then followed by .changeBorder($('#inpt').val()) which may also return a jQuery object but in the case of your example the whole line is not assigned to a variable so any return value is not used - again it looks like a DOM manipulation. But your design does not follow the standard calling convention that I've described, so there may be some confusion to anyone looking at your code as to what it actually does if they are not familiar with your plugin.
I have, in the past, considered a similar problem and use case to the one you are describing and I like the idea of having a convenient convention for calling separate functions associated with a plugin. The choice is totally up to you - it is your plugin and you will need to decide based on who will be using it, but the way that I have settled on is to simply pass the name of the function and it's parameters either as a separate .myplugin(name, parameters) or in an object as .myplugin(object).
I typically do it like so:
(function($) {
$.fn.myplugin = function(fn, o) { // both fn and o are [optional]
return this.each(function(){ // each() allows you to keep internal data separate for each DOM object that's being manipulated in case the jQuery object (from the original selector that generated this jQuery) is being referenced for later use
var $this = $(this); // in case $this is referenced in the short cuts
// short cut methods
if(fn==="method1") {
if ($this.data("method1")) // if not initialized method invocation fails
$this.data("method1")() // the () invokes the method passing user options
} else if(fn==="method2") {
if ($this.data("method2"))
$this.data("method2")()
} else if(fn==="method3") {
if ($this.data("method3"))
$this.data("method3")(o) // passing the user options to the method
} else if(fn==="destroy") {
if ($this.data("destroy"))
$this.data("destroy")()
}
// continue with initial configuration
var _data1,
_data2,
_default = { // contains all default parameters for any functions that may be called
param1: "value #1",
param2: "value #2",
},
_options = {
param1: (o===undefined) ? _default.param1 : (o.param1===undefined) ? _default.param1 : o.param1,
param2: (o===undefined) ? _default.param2 : (o.param2===undefined) ? _default.param2 : o.param2,
}
method1 = function(){
// do something that requires no parameters
return;
},
method2 = function(){
// do some other thing that requires no parameters
return;
},
method3 = function(){
// does something with param1
// _options can be reset from the user options parameter - (o) - from within any of these methods as is done above
return;
},
initialize = function(){
// may or may not use data1, data2, param1 and param2
$this
.data("method1", method1)
.data("method2", method2)
.data("method3", method3)
.data("destroy", destroy);
},
destroy = function(){
// be sure to unbind any events that were bound in initialize(), then:
$this
.removeData("method1", method1)
.removeData("method2", method2)
.removeData("method3", method3)
.removeData("destroy", destroy);
}
initialize();
}) // end of each()
} // end of function
})(jQuery);
And the usage:
var $test = $('#test').myplugin(false, {param1: 'first value', param2: 'second value'}); // initializes the object
$test.myplugin('method3', {param1: 'some new value', param2: 'second new value'}); // change some values (method invocation with params)
or you could just say:
$('#test').myplugin(); // assume defaults and initialize the selector
Passing parameters to javascript via data attributes is a great pattern, as it effectively decouples the Javascript code and the server-side code. It also does not have a negative effect on the testability of the Javascript code, which is a side-effect of a lot of other approaches to the problem.
I'd go as far as to say it is the best way for server-side code to communicate with client-side code in a web application.

Categories