Manipulating a jquery collection - javascript

I have a JQuery collection created with:
var children = $('span[data-Parent="7"]');
Now I would like to manipulate the css class of the items in that collection. I've tried:
for (var i = 0; i < children.length; i++) {
var $child = children[i];
if ($child.hasClass("red")) {
$child.addClass("green");
}
But get errors stating that the object doesn't have a method hasClass. What is the proper way to get the elements of the collection to be document elements so they can be manipulated?

You must wrap the DOM element as a jQuery object. And iterating over elements using jQuery is usually done with the provided each function :
$('span[data-Parent="7"]').each(function(){
if ($(this).hasClass('red')) {
$(this).addClass('green');
}
});
Note that the whole code can be reduced to
$('span[data-Parent="7"].red').addClass('green');

I'd say use $.each:
$.each(children, function(i, child) {
if ($(child).hasClass("red")) {
$(child).addClass("green");
}
});

Just cycle through each of the children and run the check in the .each loop.
var children = $('span[data-Parent="7"]');
children.each(function(index)
{
var self = $(this);
if (self.hasClass("red"))
{
self.addClass("green");
}
});

Change your code to:
var $child = $(children[i]);
//the rest of the code

Related

forEach is not a function when trying to loop through Firebase result set:

I am trying to port some of my Firebase database calls to an IOT board that does not have jQuery, just good old JavaScript.
In my code I originally had a jQuery $.each(tripData, function(index, element)
... loop to iterate through my results.
I have switched this to:
var tripsRef;
tripsRef = firebase.database().ref('trips/');
tripsRef.orderByChild('timestamp').limitToLast(100).on('value', function (response) {
var tripData = response.val();
tripData.forEach(function (index, element) {
if (element.status >= 0) {
var trip = new Object();
trip.id = index;
trip.launch = element.launch;
trip.status = element.status;
}
});
... but, I am getting the following error:
forEach is not a function
I am not sure how to resolve this.
for(let index in tripData){
element = trimpData[index];
}
not realy foreach, but works exactly like it
but you also can use map functions
You should really figure out if your response is an Array or Object.
$.each() iterates over arrays AND objects, thats why it works.
you should use for...in statement if you really want to iterate over this object tripData.
for(let prop in tripData)
{
if (tripData.hasOwnProperty(index))
{
item = tripData[prop];
// do stuff
}
}
lear about for...in statement here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in
While the answer by #TypedSource will iterate over the resulting children, the order in which it iterates is undetermined - and very likely not going to be by timestamp.
A snapshot that you get as the result for a query contains three pieces of information for each child: its key, its value, and its position relative to the other children. When you call .val() on the snapshot, you lose the relative ordering.
To maintain the order, use the built-in forEach() method of the snapshot:
var tripsRef;
tripsRef = firebase.database().ref('trips/');
tripsRef.orderByChild('timestamp').limitToLast(100).on('value', function (response) {
var index = 0;
response.forEach(function (child) {
var element = child.val();
if (element.status >= 0) {
var trip = new Object();
trip.id = index;
trip.launch = element.launch;
trip.status = element.status;
}
index++;
});
});
Array.of(response.val()).forEach should work if it is just an array-like object missing its iterator

Cleaning up memory leaks

I am using jQuery to clone elements, then I save a reference to an element within that clone. And much later remove the clone. Here is a basic example:
HTML
<div> <span></span> </div>
Script
var i, $clone, $span,
$saved = $('span'),
$orig = $('div');
for (i = 0; i < 100; i++) {
$clone = $orig.clone().appendTo('body');
$span = $clone.find('span');
$saved = $saved.add($span);
$clone.remove();
}
console.log( 'leaking = ', $saved.length);
The console log outputs a length of 101.
I need to clean up the $saved jQuery object and remove references to elements no longer attached to the DOM. So I wrote this basic function to clean it all up.
var cleanUpLeaks = function ($el) {
var el, remove,
index = $el.length - 1;
while (index >= 0) {
el = $el[index];
remove = true;
while (el) {
el = el.parentNode;
if (el && el.nodeName === 'HTML') {
remove = false;
break;
}
}
if (remove) {
$el.splice(index, 1);
}
index--;
}
return $el;
};
console.log( 'cleaned up = ', cleanUpLeaks( $saved ).length );
This time the console outputs 1.
So now my questions are:
How could I have prevented the memory leak in the first place?
And if that isn't possible, should I be using .splice() in the cleanUpLeaks function to remove the reference? Or would it be better to set that reference to null as is recommended? Because when I do set it to null, $saved remains at a length of 101.
Demo: http://jsfiddle.net/Mottie/6q2hjazg/
To elaborate, I save a reference to the span in $saved. There are other functions that use this value for styling and such. This is a very basic example; and no, I do not immediately remove the clone after appending it to the body, it was done here to show how the memory leak is occurring.
The better solution here is to stop saving dynamic DOM elements in a persistent jQuery variable. If your page is regularly removing content from the DOM, then saving these in a persistent jQuery object just sets you up for having to deal with memory leaks, rather than changing the design to a design that does not have to save references to DOM elements at all.
If instead, you just tag interesting elements with a particular class name that is not used elsewhere in the document, you can generate the desired list of elements at any time with a simple jQuery selector query and you will have no issues at all with leaks because you aren't ever retaining DOM references in persistent variables.
One possible solution is that you take a leaf out of AngularJS's book and monkey-patch jQuery to fire an event when an element is removed. Then you can add a handler for that event and restore the state of $saved to what it was before you added the $span.
First, monkey patch jQuery (taken from AngularJS source):
// All nodes removed from the DOM via various jQuery APIs like .remove()
// are passed through jQuery.cleanData. Monkey-patch this method to fire
// the $destroy event on all removed nodes.
var originalCleanData = jQuery.cleanData;
var skipDestroyOnNextJQueryCleanData;
jQuery.cleanData = function (elems) {
var events;
if (!skipDestroyOnNextJQueryCleanData) {
for (var i = 0, elem;
(elem = elems[i]) != null; i++) {
events = jQuery._data(elem, "events");
if (events && events.$destroy) {
jQuery(elem).triggerHandler('$destroy');
}
}
} else {
skipDestroyOnNextJQueryCleanData = false;
}
originalCleanData(elems);
};
Next, add in your $destroy event handler and restore the captured original state of $saved.
var i, $clone, $span,
$saved = $('span'),
$orig = $('div');
for (i = 0; i < 100; i++) {
(function ($originalSaved) {
$clone = $orig.clone().appendTo('body');
$span = $clone.find('span');
$clone.on('$destroy', function () {
$saved = $originalSaved;
$originalSaved = null;
});
$saved = $saved.add($span);
$clone.remove();
})($saved);
}
console.log('original length = ', $saved.length); // => 1
Here is a jsFiddle with this working. In my testing in Chrome, this doesn't introduce additional leaks.

Set attribute on multiple selectors

I'm trying the following, which doesn't work, but am wondering if there is something similar?
Basically, I'm trying to set the same attribute on multiple elements:
document.querySelectorAll("#id1, #id2, #id3").setAttribute('onclick','return false;');
I'm using vanilla Javascript, no library.
querySelectorAll() will return an array-like node list that you need to loop through:
var elems = document.querySelectorAll("#id1, #id2, #id3");
for(var i = 0; i < elems.length; i ++){
elems[i].setAttribute('onclick', 'return false');
}
JSFiddle
There are several approaches for this, the most basic one is to iterate the set and add click handlers (In your case, you want an addEventHandler not an attribute!)
var elements = document.querySelectorAll("#id1, #id2, #id3");
[].forEach.call(elements, function addClickHandler(el) {
el.addEventListener('click', function() { return false; });
};
Perhaps a better approach if you have many elements is to set one event handler on the document, and see if it matches a query selector:
var elements = [].slice.call(document.querySelectorAll("#id1, #id2, #id3")); //Turn into an array
document.addEventHandler('click', function(e) {
if (elements.indexOf(e.target) !== -1) { //Element in list was clicked!
return false;
}
};

addEventListener for array elements

With this code I'm trying to iterate over a Array which is derived from a json Array.
There is a SVG inside the page.
When I click on a country, the name should be submitted to a URL.
Unfortunately I get the following error at the line indicated with -->error
Uncaught TypeError: Cannot call method 'addEventListener' of null
How can I overcome this error?? And why is it occurring?
The array is like this:
{"Countries":["india","switzerland","usa","alaska","germany","austria","netherlands","france","italy","spain","poland","hungary","czech","romania","russia","china","mexico","brazil","britain","thailand","sweden","uae","new_zealand_north_island","new_zealand_south_island","egypt"]}
var mySVG = document.getElementById("VectorMap");
var svgDoc;
mySVG.addEventListener("load", function () {
svgDoc = mySVG.contentDocument;
$.getJSON("http://www.someurl.com/jsonArray",
function (data) {
$.each(data, function (index, item) {
var i=0;
for (tot=item.length; i < tot; i++) {
var someElement = svgDoc.getElementById(item[i]);
//--->error
someElement.addEventListener("mousedown", function () {
$("#info").html(ajax_load).load("http://www.someurl.com/returnData"+"?country="+text);
}, false); //add behaviour
}
});
});
}, false);
You're not checking whether the element exists before attempting to attach the event handler; if the element doesn't exist, getElementById() would return null. This code would have that check.
var someElement;
for (var i = 0, tot = item.length; i < tot; i++) {
someElement = svgDoc.getElementById(item[i]);
if (someElement) {
someElement.addEventListener("mousedown", function () {
$("#info")
.html(ajax_load)
.load("http://www.someurl.com/returnData"+"?country="+text);
}, false); //add behaviour
}
}
The exception is happening because in the page there is no element with the id given. In such case, getElementById returns null: you can check by your web console / debugger in the browser, what is this id that doesn't exists, and if it is supposed to be there – and therefore you have an error or typo.
Anyway, you could take advantages of jQuery. Because in jQuery you can pass a list of ids, and the listener will be attached only to those elements that are actually in the page, without throwing exception. So in your case, instead of:
var i=0;
for (tot=item.length; i < tot; i++) {
var someElement = svgDoc.getElementById(item[i]);
//--->error
someElement.addEventListener("mousedown", function () {
$("#info").html(ajax_load).load("http://www.someurl.com/returnData"+"?country="+text);
}, false); //add behaviour
}
You can simply have:
$('#' + item.join(', #')).on('mousedown', function() {
$("#info").html(ajax_load).load("http://www.someurl.com/returnData"+"?country="+text);
});
So basically, assuming item is an array (and therefore I would call it items) with plain ids, like ['a', 'b', 'c'], with '#' + item.join(', #') you will obtain "#a, #b, #c" as string to pass to jQuery: if any on those elements doesn't exists in the page, the mousedown listener simply won't be attached, without raise any error.
Note: not sure where this text variable came from, I just put there because in your original example.

jQuery - why can't I bind events to elements in a loop?

Here is my code:
var b = $(slipStream.conf.mainVis).find('p#prev');
b.click(function() {
slipStream.slideLeft();
return false;
});
b = $(slipStream.conf.mainVis).find('p#next');
b.click(function() {
slipStream.slideRight();
return false;
});
b = $(slipStream.conf.controls).find('li img');
console.log(b);
for (var l in b) {
l.click(function() {
var visIndex = l.index();
console.log(visIndex);
});
};
The first two bindings go through, no problem. But I can't loop through a collection and bind something to each member? (the console is telling me that "l.click is not a function.") Is this a limitation of jQuery or is my code off? This seems like it would be the way to do it, though...
When you enumerate over a jQuery object, the values being enumerated are actual DOM nodes and not jQuery wrappers. Therefore, they don't have a click method but you can wrap them again to get all the usual functionality.
Of course this is not necessary because you can simply attach a wrapper directly from your initial jQuery instance:
$(slipStream.conf.controls).find('li img').click(function() {
var visIndex = $(this).index();
console.log(visIndex);
});
This is the classic "loop variables don't work properly in callbacks" bug.
Your variable l no longer has the originally supplied value by the time the callback is invoked - it has whatever final value was assigned in the last pass through the loop.
[FWIW, l isn't actually a jQuery object, so you have to wrap it - $(l) to use it with jQuery]
The usual fix to the loop bug is to create an additional closure that returns a function bound to the current value:
for (var l in b) { // NB: don't use `for ... in ...` on array-like objects!
var make_cb = function(n) {
return function() {
var visIndex = $(n).index();
console.log(visIndex);
}
}
$(l).click(make_cb(l));
};
Fortunately, you don't need a loop at all - you can have jQuery automatically add the callback to every element by itself:
b = $(slipStream.conf.controls).find('li img');
b.click(function() {
var visIndex = $(this).index();
console.log(visIndex);
});
Could it be that the problem is the forloop. .click is part of the jQuery, so you must be sure that it's called on element that is wrapper with jQuery.
$.each(b, function (index, element) {
$(element).click(function() {
});
};
With each() you can iterate through a set of jQuery objects:
$(slipStream.conf.controls).find('li img').each(function(){
$(this).click(function() {
var visIndex = $(this).index();
console.log(visIndex);
});
});
$(this) will match the currently indexed object from the collection.

Categories

Resources