I just finished an aspect of my project that required looping through the rows of an HTML table and depending on the class of the td, do something with the text. I approached the issue with two different methods and now I'm wondering which is considered best coding practice. Also, is there an advantage to using one method over the other?
Method 1:
$('#table tr').each(function(){
var something = $(this).find('[class*=someClass]').html();
//Do something with 'something'
});
Method 2:
var x = 0;
$('#table tr').each(function(){
var something = $(this).find('.someClass' + x).html();
//Do something with 'something'
x++;
});
This may be more opinion-based, but personally, I would do this:
$('#table tr').each(function(i){
var something = $(this).find('.someClass' + i).html();
//Do something with 'something'
});
The index of each element is passed as an argument.
Edit:
To expand on what Karl-AndréGagnon said, the two '.someClass' selectors will behave differently. In the first example, they will select all elements with 'someClass', regardless of any numerical suffix. The second example will only select the classes with the specified numerical suffix.
If you are doing the same thing to each '.someClass' element, regardless of the number on the class, you may not need an "each" at all, and could just start with a selector such as:
'#table tr [class*=someClass]'
Related
On the project I'm working on, I've been writing a little JavaScript object. One of its behaviors involve removing any children in a series of classes, as such:
function Foo () {
var $a = $('.a'),
$b = $('.b'),
// ...etc...
$n = $('.n');
$a.remove();
$b.remove();
// ...etc again...
$n.remove();
}
While there are some ways to revise this to be more easily maintainable (putting the selectors into a single array springs instantly to mind), I'm wondering if there's any elegant way to perform the .remove() operation on a series of selectors, as such:
function FooPrime() {
var selectors = [
'.a',
'.b',
// ...
'.n'
];
// Perform remove on all given selectors?
$(selectors).remove();
}
Thus, Question: What are some ways I could perform a single jQuery operation on a number of selectors?
EDIT: here is a JSFiddle to show a cut-down version of the problem context.
You can separate the selectors with commas:
$(selectors.join(',')).remove();
The comma has that purpose in straight ordinary CSS selector syntax.
Thanks for showing your DOM, you should avoid making big lists of classes to select when you can add multiple classes to elements and create a specific class for the elements you want to target... or target via association to other elements. This would be a more clean and efficient way to do it.
By association
Basically for the selector I just have this:
$("#test-callout").find("div").not(".callout-main").remove();
Fiddle
This assumes that you do not have any other div's besides .callout-main and the target div in test-callout. If you do you can modify the selector chain a bit to compensate.
By adding another class
Your arrow creation code was like this:
function calculateArrow() {
var arrowClass;
if(pub.isLeft) {
arrowClass = 'callout-arrow-left';
} else {
arrowClass = 'callout-arrow-right';
}
pub.$callout.append('<div class="' + arrowClass + '"></div>');
}
Modify it to be like this:
function calculateArrow() {
$arrow = $("<div>")
.addClass("callout-arrow")
.addClass(pub.isLeft ? "callout-arrow-left" : "callout-arrow-right");
pub.$callout.append($arrow);
}
Then your selector can be simply:
$("#test-callout").find(".callout-arrow").remove();
Fiddle
If you are interested in a complete refactor - I reduced your CalloutObject from 53 to 17 lines and it still works the same.
Fiddle
I have a table I'm trying to derive a JSON object from using the below code
var tbl = $('#myTable tr:has(td)').map(function(i,v){
var $td = $('td', this);
return{
id:$td.eq(0).text(),
column1:$td.eq(1).text(),
column2:$td.eq(2).text()
}
}).get();
This works perfect except from one thing, I sometime have a textbox or checkbox inside a td whose value I need to retrieve. I've Google'd and searched StackOverflow but I could not find any that worked in this situation
I have also tried the follow with no luck
id:$td.eq(0).val()
id:$td.eq(0).childern().val()
any suggestions or advice would be greatly appreciated
You'll probably want to find the input if its something like a textbox:
$td.eq(0).find('input').val()
for a checkbox, use the pseudo-selector :checkbox and determine its checked property
$td.eq(0).find(':checkbox').prop('checked')
If you need value and text then use something like this:
id:$td.eq(0).find(':checkbox').attr('value') + $td.eq(0).text();
If you need value only then:
id:$td.eq(0).find(':checkbox').attr('value');
By the way, jQuery team recommended (check comments below) using .find() instead of $(target, context);
In your example:
var $td = $('td', this);
should look like:
var $td = $(this).find('td');
I'm pretty new to js/jquery. For each checkbox with the ID of check$ (where $ is a sequential number), I want to toggle the class "agree" of the surrounding span that uses the same check$ (but as a class). I don't want to have to hard-code the list of matching checkboxes, as this may vary.
Here's my code. This function works as expected:
agree = function (checkbox, span) {
$(checkbox).change(function(){
$(span).toggleClass('agree');
});
};
This is what I'm trying to pass to the above function, which does not work:
$(function() {
var elemid = 'check',
checks = Array($('[id^='+elemid+']').length);
console.log(checks);
for (i=0; i < checks; i++) {
agree('#'+elemid+checks[i], "."+elemid+checks[i]);
}
});
console.log(checks) returns [undefined × 4]. The number of elements is correct, but I don't know why it's undefined, or whether that is even significant.
The following code works as expected, but as I say, I'd rather not have to specify every matched element:
$(function() {
var checks = ["check1", "check2", "check3", "check4"];
for (i=0; i < checks.length; i++) {
agree('#'+checks[i], "."+checks[i]);
}
});
Thanks.
Edit: Thanks to Jack, I was overlooking the most simple method. I added the same class to all checkboxes and spans, and solved the problem with this:
$('input.check').change(function(){
$(this).closest('span.check').toggleClass('agree');
});
I might be totally missing something, but I'm pretty sure you are just trying to attach a change handler to each checkbox. In this case you can give them all the same class. I'm also guessing at your html structure for the span.
For reference:
http://api.jquery.com/closest/
http://docs.jquery.com/Tutorials:How_jQuery_Works
$('.yourcheckboxclass').change(function(){ //grab all elements with this class and attach this change handler
$(this).closest('span').toggleClass('agree');
});
The reason that the array is full of undefined values, is that you are just getting the number of items in the jQuery object, and create an array with that size. The jQuery object is discarded.
Put the jQuery object in the variable instead:
var elemid = 'check', checks = $('[id^='+elemid+']');
checks.each(function(){
agree(this, "."+elemid+checks[i]);
});
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.
I'm using jquery's .each() to iterate over a group of li's. I need a total of all the li's matched. Is the only way to create a count variable outside the .each() and increment this inside the .each()? It doesn't seem very elegant.
var count;
$('#accordion li').each(function() {
++count;
});
Two options:
$('#accordion li').size(); // the jQuery way
$('#accordion li').length; // the Javascript way, which jQuery uses
Since jQuery calls length under the hood, it's faster to use that instead of the size() call.
Well, I just saw this question, and you already accepted an answer, but I'm going to leave one anyway.
The point of the question seems to be concerned with incrementing a counter.
The fact is that jQuery's .each() method takes care of this for you. The first parameter for .each() is an incrementing counter, so you don't need to do it yourself.
$('#accordian li').each(function(index) {
// index has the count of the current iteration
console.log( index );
});
So as you can see, there is an elegant solution built in for you.
$('#accordion li').length;
Ok so the best way to do this is as follows: firstly map the wrapped set to a variable so you never have to do the sizzle dom lookup again:
var $myListItems = $('#accordian li');
Note: my preference is to put $ at the beginning of any vars that are a jQuery wrapped set, hence $myListItems as opposed to myListItems
Then set a var outside the each function that has the length of the wrapped set:
var myLength = $myListItems.length;
Note the lack of a $ here as the var is not a jQuery object.
Now run the each function with a variable for the index.
$myListItems.each(function(index){
// myLength has the length
// index has the current, 0 based index.
});
You now have what you've asked for in the OP with only one look up and so need to fumble in the each function with having to know the contents of the wrapped set just to know the length of it. the beauty of this over a counter is that on every iteration of the each you already know the length.
You can try this
const count = $('#accordion li').each(function() {
// Do something
}).length;
It works because .each() returns a jQuery object which has a length property.
One of the big issues, which I have not found a fix for yet, is if you have two sets of the same thing you will iterate through.
For instance, 2 table heads that you are iterating through number of column headers. If you use length on ( $item.children().find('thead tr th').each... ) for your each clause and you have 2 tables it will double the amount of your length that you are trying to walk through potentially [5 column headers in each table will return length of 10].
You could do both on a id name and have those different but if adding dynamically then this can become a headache.
The only way I found once inside the foreach is to use $(this):
$item.children().find('thead tr th').each(function(i, th) {
// I am working with a table but div could be used, or something else.
// I have 2 tables with 5 column headers, the below len return
var len = $(this).closest('table').find('thead tr th').length;
if (i < len) {
// ...your code here
}
});