Suppose I get a list of items like $(".box"). Is it possible to get an indexed jQuery object
like
var $boxes = $(".box"),
$box2 = $boxes[1]
currently I do something like
var $boxes = $(".box");
$boxes.each(function(i, box) {
var $box = $(box); // <-- is this a good idea?
// do something with $box
});
I wonder tho if the line var $box = $(box) is such a good idea? I am actually running that in a setInterval()
like
var $boxes = $(".box");
setInterval(function() {
$boxes.each(function(i, box) {
var $box = $(box); // <-- is this a good idea?
// do something with $box
});
}, 1000);
I wonder if its bad for performance since I am initializing a variable for each item in $boxes per 1s in this example. If I can access the element directly from the jQuery "array" or whatever $boxes is, it maybe better?
It's not entirely clear what your question is, but jQuery objects are already array-like, you can use the [] operators on them. What you get back is the raw DOM object at that index, so:
var $boxes = $(".box"),
box2 = $boxes[1], // `box2` is a raw DOM object
$box2 = $(box2); // `$box2` is a jQuery wrapper around the second box
Regarding this code:
var $boxes = $(".box");
setInterval(function() {
$boxes.each(function(i, box) {
var $box = $(box); // <-- is this a good idea?
// do something with $box
});
}, 1000);
It's fine to do that provided you really need to do it (e.g., if you really need a jQuery wrapper around that specific entry). It is making the browser do work each time the interval timer fires (because $() isn't free, though it's not expensive either), so if the list is short, you could trade that CPU time for memory use by pre-creating the jQuery wrappers on the elements:
var $wrapped = [];
$(".box").each(function() {
$wrapped.push($(this));
});
setInterval(function() {
$.each($wrapped, function(i, $box) {
// do something with $box
});
}, 1000);
You can iterate though the jQuery elements as per your example, nothing wrong with that. Creating a local variable for each element var $box = $(box); is a good idea.
You can also access the elements of the jQuery object with the eq method, e.g:
var $boxes = $(".box"),
$box2 = $boxes.eq(1);
That way you don't need to pass the element through the $ constructor.
If you want a faster and more efficient way of looping through the elements while also having a jQuery wrapper for each one, check out Ben Alman's "each2" plugin:
http://benalman.com/projects/jquery-misc-plugins/#each2
Then you could replace this code:
$boxes.each(function(i, box) {
var $box = $(box);
// do something with $box
});
With this:
$boxes.each2(function(i, $box) {
// do something with $box
});
On a side-note,
var $boxes = $(".box");
setInterval(function() {
$boxes.each(function(i, box) {
var $box = $(box);
// do something with $box
});
}, 1000);
is equivalent to
var $boxes = $(".box");
setInterval(function() {
$boxes.each(function() {
var $box = $(this);
// do something with $box
});
}, 1000);
'i' in the callback function of each is the index. But I wouldn't recommend it. Simply call 'this' inside the each function for the current element.
var $box = this;
If you only need i.e. box 3:
$('.box:eq(3)').doStuff();
Related
many people said that looping function inside each() is bad practices.
it will cause terrible perfomance...
just like version one...
Version one :
$(".next").each(function() {
var el = $(this),
setNo = 0,
onNo = function() {
setNo = 1;
};
onNo(); // setNo will become 1
)};
so, i have to move function outside each(). then i'm confused on replacing local variable at each()
Version two :
var onNo = function() {
var setNo = 1;
return setNo; // replace it
};
$(".next").each(function() {
var el = $(this),
setNo = 0;
onNo(); // it's not replacing setNo local variable. how to fix this?
)};
how to fix this?
do you guys have more efficient design pattern, i'm really confused at javascript design pattern.
thanks...
Are you looking for something like this?
function onNo ($el) {
// do some calculation with $el
var setNo = 1;
return setNo;
}
$(".next").each(function() {
var $el = $(this)
var setNo = 0
setNo = onNo($el)
)}
I agree with Juhana's comment that the performance issues are not significant enough to worry about. However, to answer your question:
You can't set a local variable in another function like that. However, you can pass variables to that function.
var onNo = function(setNo) {
return setNo; // returns 0
};
$(".next").each(function() {
var el = $(this),
setNo = 0;
onNo(setNo);
)};
I'm trying to reuse an object I created to dynamically create more than one slider on a page.
My idea was to create an array and push my slider object there as often as needed, so I could access it by id. Unfortunatelly it doesn't work. Hope someone can point me in the right direction ...
So what I have is this;
var slider = {
"init":function(slide_it){
this.parent = $(slide_it);
/Count Elements and create a navigation depending on the count etc./
},
"otherstuff":{...}
}
In my (document).ready function I create an array and fill it up with different slider objects, add Ids to an accordion and call the init function:
var slide_array = [];
var accordion_sections = $('#accordion > div').length;
for(var i = 0; i < accordion_sections; i++){
slide_array.push(slider);
$('#accordion').children('div').eq(i).attr('id', 'slide_it_'+ i);
slide_array[i].init($('#slide_it_' + i).find('.slider'));
}
Then I have a button with class="next" and I call a function within the slider
$('.next').click(function(){
slide_array[0].otherstuff();
});
My plan is to get the parent of .next and its id so that I can use slide_array[parentID].otherstuff();
But ... it's not working propperly when I call the init function inside the for loop more then once.
More weird, some functions calls seem to work, other have no effect.
What am I doing wrong?
You can use Object.create.
var s1 = Object.create(slider),
s2 = Object.create(slider);
s1.init(...);
s2.init(...);
If you return this from init your will be able to chain like:
var s1 = Object.create(slider).init(...);
However at this point I would just ditch the object literal and use constructors, since this is what you need.
function Slider(slide_it) {
this.parent = $(slide_it);
}
Slider.prototype = {
constructor: Slider,
otherStuff: function () {}
};
var s1 = new Slider(...),
s2 = new Slider(...);
Write a function to return the object:
function slider() {
return {
"init":function(slide_it){
this.parent = $(slide_it);
/Count Elements and create a navigation depending on the count etc./
},
"otherstuff":{...}
};
}
Then:
slide_array.push( slider() );
That'll give you a separate object every time. In you're version, you're filling the array with references to the same single object.
Why not just turn that into a jQuery plugin ?
jQuery.fn.slider = function(options) {
return this.each(function() {
var sliderElem = $(this),
settings = $.extend({
speed : 3000,
something : 'other thing'
}, options);
otherStuff(sliderElem);
});
function otherStuff(elem) {
}
}
$('#accordion > div').slider();
No iteration or jumping through hoops, just call it on the collection and it creates a new slider for each element ?
How can we clone jQuery elements with them data?
Using .data("dummy", "test") I set "test" data to "dummy" key of selected elements. Using .data("dummy") it returns "test". After cloning .data("dummy") returns undefined.
How can I avoid this?
$(".save").on("click", function () {
var dummy = $(this).data("dummy");
if (dummy) {
return alert(dummy);
}
$(this).data("dummy", "I am a button");
var $clone = $(this).clone();
$(this).after($clone);
});
JSFIDDLE
You were just missing 1 parameter...
http://jsfiddle.net/DEKFn/2/
Notice the use of true in the clone(). That determines whether to copy data and events when cloning the element, as per the docs..
http://api.jquery.com/clone/
$(".save").on("click", function () {
var dummy = $(this).data("dummy");
if (dummy) {
return alert(dummy);
}
$(this).data("dummy", "I am a button");
var $clone = $(this).clone(true);
$(this).after($clone);
});
You've also asked how to copy just the data - not the events. This isn't possible, but you can remove all the event handlers like this...
var $clone = $(this).clone(true);
var $clone.off();
clone takes an argument withDataAndEvents so do:
var $clone = $(this).clone(true);
to get only data just turn off the events.
var $clone = $(this).clone(true).off();
Fiddle
The data is attached to the element as a simple javascript object. Simply duplicate the data and assign it to the clone.
$(".save").on("click", function () {
var dummy = $(this).data("dummy");
if (dummy) {
return alert(dummy);
}
var $this = $(this);
$this.data("dummy", "I am a button");
var $clone = $this.clone();
var $data = $.extend(true,{},$this.data());
$clone.data($data);
$(this).after($clone);
});
More on .extend() http://api.jquery.com/jQuery.extend/
Fiddle here
http://jsfiddle.net/9Q7EM/
EDIT:
Not sure why this was downvoted. You incur some overhead copying both the data and the events as the original answer suggested. Both are valid.
I'm trying to use the objects within my jQuery code.
I've nearly this:
var opts = {
ul: $(this).find('.carousel'),
li: ul.find('li')
}
li property gives an error Cannot call method 'find' of undefined
How can it be fixed?
It doesn't matter what your selector is, you can't access a property of an object that you are declaring, while you are declaring it.
Why would ul be declared? You're making a property ul on your opts object.
Ugly:
What you might want is:
var opts = {
ul: $(this).find('.carousel')
}
opts.li = opts.ul.find('li');
But if you don't actually need references to both groups then:
Cleaner:
var opts = {
li: $(this).find('.carousel li')
}
is just as good.
Cleanest:
You could also do:
var $carousel = $(this).find('.carousel');
var options = {
carousel: $carousel,
carouselItems: $carousel.find('li')
}
Godawful, but you asked for it:
var CarouselOptions = (function () {
var options = function (carousel) {
this.carousel = $(carousel);
this.carouselItems = this.carousel.find('li');
};
options.prototype.myOptionsFunction = function () {
// Don't know what you want your object to do, but you wanted a prototype...
};
return options;
})();
var opts = new CarouselOptions($(this).find('.carousel'));
Also
(Be careful with what your this is, presumably you have more than one .carousel element on the page, and here you want the one that is within the target of an event.)
Your error message is essentially saying that $(this) is undefined (or in other words, jQuery couldn't find this element). Because you don't have any code other than the single object you are trying to set, I don't know what the actual value of this is.
What I would do is ensure that this is set to an element of some sort. A simple console.log(this) should handle that. If this isn't an HTML element, then that's your problem. Either ensure you are inside of a jQuery event function like this:
$('#id').click(function() {
this === document.getElementById('id'); // true
});`
Or you can just drop the $(this):
var opts = {};
opts.ul = $('.carousel'),
opts.li = opts.ul.find('li')
var that = $(this);
var opts = {
ul: that.find('.carousel'),
li: ul.find('li')
}
I'm writing simple slider for my website. This slider contains list items. I want to use OOP approach.
My actual code:
var miniSlider = function(objId)
{
this.obj = $("#" + objId);
this.obj.settings = [];
this.obj.settings['items'] = $('ul li', this.obj).length;
this.pagerNext = this.obj.find("i.next");
this.pagerPrev = this.obj.find("i.prev");
this.pagerNext.on("click", function() {
alert(this.obj.settings['items']);
});
};
I can invoke a few other sliders (yes, that's why I introduced a class):
miniSlider("mini-slider");
The problem is that when I'm in jQuery this.pagerNext.on("click", function() { }); this is no longer my object but - it's become a clicked element. How can I access this.obj.settings after click in a well done way (and with multi sliders support)?
EDIT:
Here is a full code created with a cooperation with SOF community :)
var MiniSlider = function(objId)
{
this.obj = $("#" + objId);
this.obj.settings = {
items: $("ul li", this.obj).length,
autoChangeTime: 8000
};
this.obj.activeElement = null;
this.pagerNext = this.obj.find("i.next");
this.pagerPrev = this.obj.find("i.prev");
var self = this;
this.pagerNext.on("click", function() {
self.obj.activeElement = $('li.active', self.obj);
if(self.obj.settings.items > 0)
{
if(self.obj.activeElement.is(':last-child'))
{
$('li.active', self.obj).removeClass('active');
$('li', self.obj).first().addClass('active');
}
else
{
self.obj.activeElement.next().addClass('active').prev().removeClass('active');
}
}
});
this.pagerPrev.on("click", function()
{
self.obj.activeElement = $('li.active', self.obj);
if(self.obj.settings.items > 0)
{
if(self.obj.activeElement.is(':first-child'))
{
self.obj.activeElement.removeClass('active');
$('li', self.obj).last().addClass('active');
}
else
{
self.obj.activeElement.prev().addClass('active').next().removeClass('active');
}
}
});
this.obj.parent().on('mouseenter mouseleave', function(e) {
if (e.type == 'mouseenter')
{
$(this).addClass('stop');
}
else
{
$(this).removeClass('stop');
}
});
setInterval(function() {
if(self.obj.settings.items > 0 && !self.obj.parent().hasClass("stop"))
{
self.pagerNext.click();
}
}, this.obj.settings.autoChangeTime);
};
and invoke:
new MiniSlider("mini-slider");
Alex gave you the solution to the this problem in your callback, but there is another problem in your code.
You are calling the miniSlider() function without a new operator:
miniSlider("mini-slider");
That means that inside the function, this is not a unique object, but is actually the window object!
You need to use the new operator to create an individual object for each call:
new miniSlider("mini-slider");
But you should also change the name of this function to follow the JavaScript convention that constructors begin with a capital letter. Call it MiniSlider and use it like so:
new MiniSlider("mini-slider");
If you follow this convention (which most experienced JavaScript programmers do), it will help you remember when to use new. If the function begins with a capital letter, it's a constructor and you need to use new with it. Otherwise, you don't.
If you'd like to be able to use your constructor without new, that is also possible with a bit more code, e.g.:
function MiniSlider( objId ) {
if( this == window ) return new MiniSlider( objId );
// the rest of your constructor code goes here
}
But generally people don't bother with that and just use the initial capital letter on the constructor as a reminder to use new.
Also, as a suggestion, I like to use a meaningful name when I save this in a variable, and then I use that name consistently instead of using this at all. Doing it this way it might look like:
var miniSlider = function(objId) {
var slider = this;
slider.obj = $("#" + objId);
slider.obj.settings = [];
slider.obj.settings['items'] = $('ul li', slider.obj).length;
slider.pagerNext = slider.obj.find("i.next");
slider.pagerPrev = slider.obj.find("i.prev");
slider.pagerNext.on("click", function() {
alert(slider.obj.settings['items']);
});
};
Why do I prefer that approach over using this in most places and another variable like self where you need it? This way I don't have to remember which to use: I can always use slider in the code instead of this. (Of course you could use self or any other name; I just like to have a more meaningful name if I'm going to the trouble of making up a name at all.)
Another minor problem in the code is in these two statements:
slider.obj.settings = [];
slider.obj.settings['items'] = $('ul li', slider.obj).length;
You shouldn't use an Array when you are going to be giving it named properties like this. Use an Object instead. Arrays should only be used when you have numeric indexes like 0, 1, 2, etc. And with an object literal you can set the property at the same time:
slider.obj.settings = {
items: $('ul li', slider.obj).length
};
Also, when you use that property:
alert(slider.obj.settings['items']);
you can write it more simply as:
alert(slider.obj.settings.items);
Either way it does the same thing.
Save a reference to this in a local variable, and use that variable instead of this in the nested function.
var self = this;
this.pagerNext.on("click", function() {
alert(self.obj.settings['items']);
});