I have this line of code in my JavaScript:
for (var row in $('#alertsbody > tr')) {
. . .
}
I need to test that the ID of the row matches a value. However,
if (row.attr('id') == myRowId) {
. . .
}
Generates a "TypeError: row.attr is not a function" error.
What's the right way to get the ID of the row?
Do not use a for..in loop to iterate over the elements of a jQuery collection. for..in iterates over all properties of an object, and a jQuery collection has a hell of a lot of properties, only some of which are actual elements of the collection. Use jQuery's provided .each() function -> http://api.jquery.com/each/
A sidenote, the variable row in for(row in something) will contain property names, not the values of the properties.
jQuery selector returns an array of elements wrapped by a jQuery object. Using your code, row would represent a property of the object, which would work for the numeric properties; but would break for all the other properties (see below for why this is bad).
Again, the following should not be used, but using your code, you would implement the in looping construct by caching your selector:
var $tr = $('#alertsbody > tr');
for (var row in $tr) {
// perform any property validation here
if (isNaN(row))
continue; // try next property
// id validation
if ($tr[row].id == myRowId)
. . .
}
Warning / Better Alternative
This is a bad idea. This is because you should not loop through the object in this manner, instead you should iterate over the jQuery object using .each().
$tr.each(function(){
if(this.id == myRowId)
...
})
Alternative
Some alternatives are to skip the loop entirely and select the rows by id eg($('#myRowId') or $('tr#myRowId')though, tr is not necessary as there should only be one id per HTML page) and perform some action. Or, if you already have some looping construct in place or need an alternative checking mechanism, you can use the .is() function:
var domElement = document.getElementById('myRowId');
var $jqElement = $('#myRowId');
$tr.each(function(){
var $this = $(this);
if( $this.is('#myRowId') )
...
if( $this.is( domElement ) )
...
if( $this.is( $jqElement ) )
});
As you're writing it, you could use the each function to iterate, and then JQuery wrap each element before checking the id:
$("#alertsbody > tr").each(function(i, tr) {
if ($(tr).attr('id') == myRowId) {
//do something
}
});
However, given what you're trying to do, wouldn't it make more sense to just get the element with the specific id?
$(myRowId)
The only time you'd need to iterate multiple elements would be to select by a non-unique attribute such as class, and even then you could incorporate that into the JQuery selector:
$("#alertsbody > tr."+myRowClass)
Related
jQuery collections are array-like Objects with a length property and methods from Array.prototype like splice, sort, and push. I'm aware that those methods are not chainable like typical jQuery methods, but the methods do work as expected.
Are there any reasons not to manipulate jQuery collections using those Array.prototype methods such as browser compatibility or issues with other jQuery methods?
Example
In a loop, add elements to a jQuery collection for later manipulation.
var $divs = $(); // Create empty jQuery collection.
for (i = 0; i < 8; i++) {
var div = document.createElement('div');
div.innerHTML = i+1;
$divs.push(div); // Add newly created div to collection
}
$divs.addClass('red').appendTo(document.body);
View on Codepen
Using push here instead of $divs = $divs.add(div); is more succinct and seems more efficient. If jsperf were working right now, I'm sure it would show better performance with push.
Here is .add from the jQuery source:
add: function( selector, context ) {
return this.pushStack(
jQuery.uniqueSort(
jQuery.merge( this.get(), jQuery( selector, context ) )
)
);
},
In this function 3 more methods are called... .merge, .uniqueSort (which is actually SizzleJS), and .pushStack...
In addition to just .push, jQuery will:
Merge your input if it's an array of selectors. (.merge)
Remove any duplicate entries. (.uniqueSort)
Accept flexible input with DOM elements (.pushStack)
So, to conclude that, if you're worried about input, .add is good to use, since jQuery makes it flexible (regular DOM or jQuery objects work), but it also removes duplicate entries and can take in multiple items with an array.
You can pass an Array into jQuery, this means you can make full use of a native Array
// build
var j, el, ly = [];
for (j = 0; j < 3; ++j) {
el = document.createElement('span');
ly.push(el);
}
// wrap
var jq = $(ly); // jQuery object [<span>, <span>, <span>]
Adding more elements can be done with pushStack and another Array
jq.pushStack([document.createElement('div')]);
jq; // [<span>, <span>, <span>, <div>]
Or more generally using add which accepts any input the normal jQuery constructor takes
jq.add(document.createElement('hr'));
jq; // [<span>, <span>, <span>, <div>, <hr>]
Having a table like:
<table id="table_1">
<tr><td>1</td><td>foo</td></tr>
<tr><td>2</td><td>foo</td></tr>
<tr><td>3</td><td>foo</td></tr>
<tr><td>4</td><td>foo</td></tr>
</table>
I want to get the first td of each table row.
Then i want to iterate through the findings and compare the text value contained in the td with a value i have.
So far, i can get the first td of each tr by using the following :
var test = $("#table_1").find('td:first-child');
Then i get the number of tds : console.log(test.length);
but when i try to get the text of a td i get an error : console.log(test[1].text());
Error:
Uncaught TypeError: test[1].text is not a function
Obviously i do something wrong. Is my way of thinking wrong? How can i fix it?
test is jquery object of all first child elements. You should be using .eq(0) or .first() to target first element in the collection:
console.log(test.eq(0).text());
or
console.log(test.first().text());
Update: To get all the texts of first-child td elements in array
var allfirsttdtext = test.map(function(){
return $(this).text();
}).get();
Working Demo
test[1] will return underlying DOM element and .text() is a jQuery function thus you are getting the error.
I think, You need to use .eq()
Reduce the set of matched elements to the one at the specified index.
Code
test.eq(0).text()
Note: that the supplied index is zero-based, and refers to the position of the element within the jQuery object, not within the DOM tree.
OR, Use textContent
test[1].textContent
use textContent in JavaScript bcoz u are converting jquery object into javascript object
console.log(test.eq(0).text());
or
console.log(test[0].textContent);
You have to surround by jQuery as the resulted array is a Dom Elements:
for(i = 0; i < test.length; i++){
console.log($(test[i]).text())
}
Try this:
var v = 'compare';
$('#table_1 td').each(function () {
if ($(this).text() == v)
console.log($(this).text()+' is equal with the variable '+v);
})
console.log(test[1].text());
test -> is an array of htmlDomElement
when you say test[1] -> it gives you a htmlElement not a jQuery object of that element, so you can not use .text() which is jQuery function.
Wrap it with $(test[1]).text() to make it jQuery object to use text() function on it.
Clicking on an element:
$('.my_list').click(function(){
var selected_object = $(this);
$('.my_list').each(function(){
var current_object = $(this);
if( selected_object == current_object ) alert('FOUND IT !');
});
});
I don't know why, but I don't get the alert message "FOUND IT !".
You can use the jQuery.is function:
Check the current matched set of elements against a selector, element,
or jQuery object and return true if at least one of these elements
matches the given arguments.
if (selected_object.is(current_object)) {
...
}
An alternate solution is to use jQuery.get function to get the raw elements and compare them using == or === operator:
if (selected_object.get(0) == current_object.get(0)) {
...
}
jsFiddle demo
There's good answer provided... but it's important to understand, why you directly can't compare selectors in jQuery.
jQuery selectors return data structures which will never be equal in the sense of reference equality. So the only way to figure this out is to get DOM reference from the jQuery object and to compare DOM elements.
The simplest comparison of DOM reference for the above example would be:
selected_object.[0] == current_object.[0]
I have a loop:
for (index = 0; index < total_groups; index += 1) {
groups[index].list_item = $(list_item_snippet);
// Closure to bind the index for event handling
(function (new_index) {
groups[index].list_item.find('.listing-group-title')
.html(groups[index].Group.Name)
.click(function(e){
fns.manageActiveGroup(new_index, groups);
return false;
});
})(index);
// Append to DOM
mkp.$group_listing.append(groups[index].list_item);
};
I would rather not call append() each time the loop fires.
I know that I could use a String and concatenate the markup with each loop iteration and append the string to mkp.$group_listing at the end, however this flattens the object and the bindings are lost (I am not relying on IDs).
Is there a way to perhaps add my objects to an array and append them all in one go at the bottom without flatening to HTML?
Assumptions:
$(list_item_snippet) contains some HTML defining a list item (and includes an element with class .listing-group-title).
groups is a block of JSON defining a 'group' in my script
The closure works perfectly
Edit:
Found that I can use the following syntax to append multiple elements:
mkp.$group_listing.append(groups[0].list_item, groups[1].list_item );
But i obviously need to automate it - it's not an array it's just optional additional function parameters so I'm not sure how to do this.
To append an array of elements to a selector you can use this:
$.fn.append.apply($sel, myArray);
In your case, since it's actually the .list_item property of each array element that you need you can use $.map to extract those first:
$.fn.append.apply(mkp.$group_listing, $.map(groups, function(value) {
return value.list_item;
}));
Instead of bind it the way you've done, if you bind it using on() like below,
$(document).on('click', '.listing-group-title', function() {
// click handler code here
});
You can flatten the HTML and append it in one statement and it'll still work.
Note: For better efficiency, replace document in the above statement to a selector matching the closest parent of .listing-group-title
Yes. Use the jQuery add method to add all your items to a jQuery object. Then append that one object.
http://api.jquery.com/add/
EDIT: Example:
var arr = $();
for (index = 0; index < total_groups; index += 1) {
groups[index].list_item = $(list_item_snippet);
// Closure to bind the index for event handling
(function (new_index) {
...
})(index);
// Add to jQuery object.
arr.add(groups[index].list_item));
};
mkp.$group_listing.append(arr);
I am trying to attach an onChange callback to all the input elements under the div #dim. It selects all 3 input elements, but returns an exception:
Uncaught TypeError: Object 0 has no method 'change'
It may be because x may not be a jQuery object. How would I make this work?
function registercb() {
var sel = $("div.dim > input");
for (x in sel) {
x.change(function() {
dosomething();
});
}
}
You can simply do:
function registercb() {
$("div.dim > input").change(dosomething);
}
A few things to watch for:
Inside that iteration (don't use this, see the next point) x is the DOM element, not a jQuery object which has the .change() method, you would need to wrap it in a jQuery object like $(x), but again that isn't the correct solution here.
Don't use a for(...in...) loop to iterate an object (a jQuery object is array-like), that type of loop is for enumeration.
Most jQuery functions (almost all) run on more than one element, so just run it on the set to affect all elements, .change() is one of these.
In the cases you do need to loop, check out .each(), as it'll make your life much easier, don't use this here, it's only an example of what it would look like:
Example:
function registercb() {
$("div.dim > input").each(function() {
$(this).change(dosomething);
});
}
You don't have to loop over the elements. You can think of a jQuery object as holding a collection. When you do:
var sel = $("div.dim > input");
It means that sel has all the input elements in it, so then when you run a method like change() it will affect all of the elements in the collection. Thus you can do something like this:
function registercb() {
$("div.dim > input").change(function(){
dosomething();
});
}
Bonus knowledge: Now your problem is that when you were doing for( x in sel ) you are getting a lot of stuff on the jQuery object itself that you don't want. If you run the following code in chrome you'll see it outputting a lot unexpected stuff:
for( x in sel ){
console.log( x );
}
Instead jQuery has the each that lets you loop over the things you want:
sel.each(function(index, item){
console.log(item);
});
You can even use it on other things, which is really handy!
$([1,2,3]).each(function( index item ){
console.log( item ); // 1,2,3
})
Assuming your 'dim' div has an ID rather than a class of dim, you can simply do this:
$("#dim > input").change(function() { dosomething(); });
Working example.
In the text you refer to #dim whereas in the code you're refering to .dim - # selects by ID and . selects by class, so if your div is in the format <div id="dim"> then you won't find any matched elements with div.dim as your selector.