javascript iteration within if statement - javascript

In this JSFiddle (with the problem code commented out) the first click in an empty cell sets a value in a hidden input and sets the bgcolor of the cell to green. A click in a second empty table cell sets the value of another hidden input and changes the second cell bgcolor to red.
Now, based on feedback from another SO question I have tried to implement a check by looping through an array (All the commented out code) of all td's in the table to see if onclick, any cell already has a bgcolor set to green/red respectively and if true, set the bgcolor to empty/blank to allow for the NEW cell selection to get the bgcolor , so there should always be only 1 green block and 1 red block. Can someone explain to me how I am implementing the loop and check wrong and not getting the expected result.
the array looping works here -jsfiddle when not part of the existing code. But when I add it to code where it's needed, it does not work.
HTLM
<div id="starget"></div>
<div id="etarget"></div>
<table width="100%" id="test">
<thead>
<tr>
<th>Tech</th>
<th>0800</th>
<th>0900</th>
<th>1000</th>
<th>1100</th>
<th>1200</th>
</tr>
</thead>
<tr>
<td>Bill</td>
<td>XXX</td>
<td onclick="res(0900,this);"></td>
<td>XXX</td>
<td>XXX</td>
<td onclick="res(1200,this);"></td>
</tr>
</table>
SCRIPT
var x = 0;
var click = 0;
/* tdElements = document.getElementsByTagName("td"); */
/* I have tried the tdelements array inside and outside of the function */
function res(zz,el) {
if (click == 0) {
/* for(var key in tdElements) {
if (tdElements[key].style.backgroundColor=="green") {
tdElements[key].style.backgroundColor="";
}
} */
document.getElementById('starget').innerHTML=zz;
click = 1;
el.style.backgroundColor='green';
}
else {
/* for(var key in tdElements) {
if (tdElements[key].style.backgroundColor=="red") {
tdElements[key].style.backgroundColor="";
}
} */
document.getElementById('etarget').innerHTML=zz;
click = 0;
el.style.backgroundColor='red';
}
}

If you call .getElementsByTagName, you are not getting back an array! You are getting back a live HTMLCollection element, which contains some other items that you cannot ignore when using a for.. in loop. Heres where a for loop wil come in handy as your live HTMLCollection contains a length you can use!
/* HTMLCollections come with a length attribute we can use
* as every item is numbered, but in a object-style key-value pair */
for(var i = 0; i < tdElements.length; i++){
/* This was tripping up your code - the `item` function to fetch an
* element at the index defined in the HTMLCollection*/
tdElements.item(i).style.backgroundColor = "green"
}
This will turn all the backgrounds green. Now you will have to amend your code, but thats how it works. More info here: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection

What you really want to be doing is handling this with a class, rather than a style. The reason is that you can very easily pull up all of the elements that have a specific class using getElementsByClassName (note: in IE 9 and later).
Without deviating from your approach TOO much (I did make it a little more efficient :) ), your code would need to change to:
CSS:
.firstClick {background-color: green;}
.secondClick {background-color: red;}
Script:
var click = 0;
function res(zz,el) {
var currentlyClickedEls, currClass, divTarget;
if (click === 0) {
currClass = "firstClick";
divTarget = "starget";
click = 1;
}
else {
currClass = "secondClick";
divTarget = "etarget";
click = 0;
}
// get all the elements with the appropriate class
currentlyClickedEls = document.getElementsByClassName(currClass);
// remove that class from those elements
while (currentlyClickedEls.length > 0) {
currentlyClickedEls[0].className = "";
}
// add the class to the clicked element
el.className = currClass;
document.getElementById(divTarget).innerHTML = zz;
}
With this approach, on the click, the first thing that you would do is find all of the elements with the class firstClick or secondClick (based on the value of click) and remove the class. Since there should only ever be one, at the most, that makes for a VERY short loop to go through (but would also remove the class from more than one element, if that were to happen somehow).
EDIT:
So, you wouldn't have noticed it in your code, since you don't ever have more than one instance of each of the two classes, but, as somethingthere pointed out, we are dealing with a LIVE collection of DOM elements, so, as you remove the classes from the elements (the element that you removed them from, falls out of the collection.
For example, on the third click of your table, there will be one element in the collection that is returned by document.getElementsByClassName(currClass); = HTMLCollection[td.firstClick] (in the console). But, once you've removed that class from the element, it no longer matches the criteria, so the collection becomes empty (i.e., HTMLCollection[]).
So, while it works for your situation (because it stops on i = 1 because that is still "not less than" the new length of the collection (which is now 0).
However, in other situations, that might not be the case . . . for example, if there were 2 elements returned, after the first one had its class removed, i would change to 1, but the length would also drop to 1 and the loop would stop, without processing the second element.
So the approach really needs to be changed to handle that behavior correctly. I've done that by changing these lines:
for (i = 0; i < currentlyClickedEls.length; i++) {
currentlyClickedEls[i].className = "";
. . . to these lines:
while (currentlyClickedEls.length > 0) {
currentlyClickedEls[0].className = "";
This way, the element that has it's class removed is always the first one in the collection and the while loop checks to make sure that there is still an element in the collection, before it attempts to remove the class.
So, long story, short, when dealing with an HTML Collection, as somethinghere pointed out, you are dealing with a live collection of DOM elements, so, if you make a change that would affect an individual elements inclusion in that Collection, it WILL update the Collection accordingly and you need to be able to account for that in your code.

Related

Parse a JS array and add a style for matching values using .addClass()

I'm confused as I don't believe I've done anything out of the ordinary here, that I haven't done before.
I'm looking to implement a feature similar to Excel's "Highlight Duplicate Values" action.
I have a function that takes tab separated values and parses them into a list of claims. It's all hacky, but the "list" (or table) is a series of divs that contains various elements such as a button that holds the claim ID. Once the list is created and all claims are visible, I parse it to find what buttons hold duplicate values, and apply a class to said button to highlight it using .addClass().
The problem is that I cannot seem to get the button to highlight. Now I've used functions like this before without any issue, including making a row highlight when clicked (and dim the others), and so on, but I'm confused what seems to be wrong here.
The parsing code is as follows:
function parseForDuplicates() { // Look for duplicates to highlight
var importedListLength = $("div.column.colClaim").length; // Get length of claims in list
var claimsToReview = []; // create an array for below...
for (let a=0; a < softParsedList.length; a++) { // Add the claim IDs to the claimsToReview array
claimsToReview.push(softParsedList[a][0]);
}
for (let i=0; i < importedListLength; i++) { // Compare values...
var currentCheckedClaimBtn = $(`div.column.colClaim:nth-child(${i+1}) button`); // Get the button element for current line
if (i > 0) {
var previousCheckedClaimBtn = $(`div.column.colClaim:nth-child(${i}) button`); // Get the button element for previous line only if we're further along the list
} else {
var previousCheckedClaimBtn = "none"; // set to "none" if we're at the beginning.
}
if (i < importedListLength) {
var nextCheckedClaimBtn = $(`div.column.colClaim:nth-child(${i+2}) button`); // Get button element from next line
} else if (i == importedListLength) {
var nextCheckedClaimBtn = "none"; // set to "none" if we're at the end
}
var currentCheckedClaim = $(`div.column.colClaim:nth-child(${i+1}) button`).val(); // Get claim ID from current line
if (i > 0) {
var previousCheckedClaim = $(`div.column.colClaim:nth-child(${i}) button`).val(); // Get claim ID from previous line
} else {
var previousCheckedClaim = "none"; // set to "none" if we're at the beginning
}
if (i < importedListLength) {
var nextCheckedClaim = $(`div.column.colClaim:nth-child(${i+2}) button`).val(); // Get claim ID from next line
} else if (i == importedListLength) {
var nextCheckedClaim = "none"; // set to "none" if we're at the end
}
//console.log(`Previous Claim: ${previousCheckedClaim}`);
//console.log(`Current Claim: ${currentCheckedClaim}`);
//console.log(`Next Claim: ${nextCheckedClaim}`);
if (currentCheckedClaim == nextCheckedClaim) { // If the current claim matches next claim
currentCheckedClaimBtn.addClass('duplicateClaim'); // Highlight current button
nextCheckedClaimBtn.addClass('duplicateClaim'); // Highlight next button
nextCheckedClaimBtn.addClass('duplicateClaim'); // Highlight next button
console.log(`${currentCheckedClaim} is a duplicate!`);
}
if (currentCheckedClaim == previousCheckedClaim) { // If the current claim matches previous claim
currentCheckedClaimBtn.addClass('duplicateClaim'); // Highlight current button
previousCheckedClaimBtn.addClass('duplicateClaim'); // Highlight previous button
console.log(`${currentCheckedClaim} is a duplicate!`);
}
}
}
The CSS in question...
.duplicateClaim {
border-color: #BB9955 !important;
background-color: #773311 !important;
color: #FFFFFF !important;
}
I've tried with and without !important and that doesn't change anything
When I open up DevTools I can see that the styles get applied appropriately, however in the Styles sidebar, the style is nowhere to be found! I can add it manually to the stylesheet, and at that point, the buttons highlight as intended!
I've gone over the seemingly trivial things: I've definitely saved the CSS file. The HTML file points to the correct CSS file (it never changed to begin with), and there are no conflicting styles that I'm aware of. I use two stylesheets (one named style2.css and the other named external.css and there are no conflicts between them. I tried disabling external.css - no change. I also made sure I didn't somehow open the wrong css (from production or a backup) and just have been editing CSS that was never referenced by changing a DIFFERENT rule - the body - to have a background color that's #FFFFFF and it works. I've also tried inserting it directly into the HTML... that doesn't work. Moving .duplicateClaim to the TOP of the CSS file? Nope. Maybe I just fat fingered it all? Nope. Spelling is correct. I did a copy/paste just in case my eyes have betrayed my brain - nah, they're still cooperating with me... for now. And what about adding a class that's actually an ID?
Yeah, no, that period has not magically turned into a hash. Most definitely has remained a period, and hasn't given me an issue. Oh oh! But what about applying the style DIRECTLY too the element? Nah, I assure you I've tried and nothing wants to play nice.
And lastly, I've tried generating each line WITH the class already entered! That doesn't work either. For whatever reason, chrome just seems to eat that one rule for lunch, and it just doesn't exist. I feel like there is SOMETHING dumb that I'm just not looking at and I'm just dealing with being really, really tired, but I need some kind of sanity check here. Or, maybe I really am going crazy...
Of course, again, let me emphasize that when looking at the source in the developer tools, the class most definitely gets added to the elements that I specify in the code - the JS appears to work, regardless of whether or not it's messy and triggers you (however it's not the first time something appears to work but is still wrong. It just seems like the class ceases to exist the moment the page is loaded. And if I manually add it in the developer tools by clicking the New Style Rule button (the plus next to the :hov and .cls buttons)? It applies to the correct buttons without issue.

How to determine the img source using Jquery/Javascript on pageload?

I've gone through many SO threads, I can't seem to find a working solution.
All I'm trying to do is when the page loads, the site pushes all elements with the ".home" class into the array arr. Then, the script parses through each element in the array and tries to match it with a string. For example, right now all I have is a check to see if the element has the words "Boston" in it, in which case I want to make the image source for ".homeimage" the linked imgur link. I'm aware it's not wise to host images on imgur for these reasons, I'm just trying to check if it works. Below this test I have some redundant code I was practicing with that I found in a SO thread, changing the color of text to gray. I figured changing attributes is the same.
my html code:
<td colspan = "3"width=400px class = "home"><b><%= game.home %></b></td>
<td colspan = "3"><img style="width:150px;height:128px;" class = "homeimage"></td>
my javascript/jquery code:
<script>
var arr=[];
$(document).ready( function(){
$(".home").each(function(){ arr.push($(this));});
for(i = 0; i < arr.length; i++){
if(arr[i].indexOf "Boston" != -1){
$('.homeimage img').attr("src","http://i.imgur.com/s5WKBjy.png");
}
}
$.each(arr,function(key,val){
val.css('color','gray')}); //something redundant i was testing out
});
</script>
additional questions:
When I have multiple image with the .homeimage class, and multiple checks to determine the image source, will it make all of the images in the .homeimage class that src at the end? So whatever the last image that gets checked is the image src for all of the images with the ".homeimage" class? I don't want that. How can I uniquely make each image? Make a custom id instead of a class for each div? Also, does this script have to be below the html in question? Or does that not matter
Thanks for the future advice you all.
// I don't quite understand what you want to do.
// Since you type too much, and make no highlights.
// but here are somethings I found:
var arr = []; // this array is going to contain all tags (like td) with class '.home'
if(arr[i].innerHTML.indexOf("Boston") != -1) { } // indexOf() won't work on DOM element
// then arr[0] must be a DOM element, so why you call .indexOf("Boston") on it?
// next, $('.homeimage img') all return DOM element with class 'homeimage' or with tagName 'img'
$('img.homeimage'); // this may what you want to do.
// Alright, I try to give you an answer.
// this oImgUrl works as a map from some ((String))-->((img url))
var oImgUrl = {
'Boston': 'http://another.imageurl.com/boston.png',
'NewYork': 'http://another.imageurl.com/newyork.png'
};
// I take your "arr" unchanged
// this will test every element in arr
// if carry String like 'Boston' or 'NewYork'
// then find the img tag (img.homeimage) in it.
// then apply URL string to img tag
for (var i=0, length=arr.length; i < length; i++) {
if(arr[i].innerHTML.indexOf("Boston") != -1) {
arr[i].find('img.homeimage').attr('src', oImgUrl['Boston']);
continue;
}
if(arr[i].innerHTML.indexOf("New York") != -1) {
arr[i].find('img.homeimage').attr('src', oImgUrl['NewYork']);
continue;
}
}
example html:
<td class='home'>Welcome to Boston!<img class='homeimage'></td>
<td class='home'>Welcome to New York!<img class='homeimage'></td>
answers:
Question 1: Custom ID?
JavaScript will find these two td.home and add them into arr.
then, apply different image url to img tag
according to innerHTML of the td tag.
when doing this, you don't need to set each img tag an unique ID.
Question 2: Script place below html?
No, you don't have to.
You hold all thses script in docuement ready function
so, they will only work when HTML DOM is ready.
in another words, no matter where you place this script,
they will be invoked after Every Tag is ready.

Can't understand Javascript eventhandler with for loop code

I'm trying to learn JavaScript and I saw a code to change the css style of a web page depending on the button you press.
I can't understand why or how a for loop indicate witch button was press. Here is the javascript part:
var buttons = document.getElementsByTagName("button");
var len = buttons.length;
for (i = 0; i < len; i++) {
buttons[i].onclick = function() {
var className = this.innerHTML.toLowerCase();
document.body.className = className;
};
}
http://jsfiddle.net/qp9jwwq6/
I looked on the net and w3 school but they don't explain that code with a for loop. Can someone explain it to me?
Thank you
Lets break it down.
First we need to have access to the DOM element on the page, so we do that by using a method on the document itself which will return the element we want to manipulate.
var buttons = document.getElementsByTagName("button");
The buttons var will be a list of ALL the buttons on the page. We want to do something with all of them, so first we cache the length of the list, i.e, count how many buttons we have.
var len = buttons.length;
Then we basically say: set i to 0, and step it up one until its equal to the number of buttons we have.
for (i = 0; i < len; i++) {
Now, to access one button from the list, we need to use the brackets notation. So buttons[0] is the first element, buttons[1] is the second, etc. Since i starts at 0, we put i in the brackets so that on each iteration it will access the next button in the list.
buttons[i].onclick = function() {
var className = this.innerHTML.toLowerCase();
document.body.className = className;
};
}
This is equivalent of doing:
buttons[0].onclick = function() {
var className = this.innerHTML.toLowerCase();
document.body.className = className;
};
buttons[1].onclick = function() {
var className = this.innerHTML.toLowerCase();
document.body.className = className;
};
buttons[2].onclick = function() {
var className = this.innerHTML.toLowerCase();
document.body.className = className;
};
// etc.
But of course that is super inefficient, and we may not know how many buttons the page has. So we get all the buttons there, find out how many there are, then go through each button and assign an event handler to it along with a new class.
Now, looking at the onclick handler itself we can see that it first finds the HTML within the button being clicked, turns it into lowercase letters, and assigns it to a variable:
var className = this.innerHTML.toLowerCase();
By using this we're ensuring that each button will know to get it's own innerHTML when clicked. We're not tracking which button is which, we're just telling each button to check it's own content.
Then what it does is change the class of the body HTML element to whatever it is that we just parsed
document.body.className = className;
So say you have something like
<button>success</button>
<button>failure</button>
<button>warning</button>
Clicking the first button would set the <body> element's class to success, clicking the second would set it to failure, and the third would set it to warning.
First line saves all buttons in a variable called buttons. This is actually an array since there can be several buttons on the page. Then you iterate through each button and define a function which should be executed onclick. Lets say you have 2 buttons then it will be buttons[0] and buttons[1] which get the function.
Firstly, speaking generally, the underlying basis for this code is a little wonky and unusual and non-robust, so don't anticipate that you're on the brink of learning any powerful insight into JavaScript or code design.
On to the answer:
The for-loop does not "indicate" which button was pressed. Rather, it loops through every button element on the page and assigns the exact same function definition to the onclick attribute of each element. The code that ends up running when a particular button element is clicked (here I'm talking about the function body) assigns a CSS class to the body element by assigning to document.body.className.
Your question is asking how the function knows which class name to assign to document.body.className. The function grabs the class name from the innerHTML of the button element, which is accessible as this.innerHTML (because in an event handler, this is a reference to the element on which the triggering event occurred). The HTML <button> element is a little bit special, in that, although it is generally a simple-looking button, it is also a non-leaf node, meaning it contains its own HTML. You can put a lot of things in there, but in this example, they just have a plain text node which consists of exactly (or nearly exactly) the class name (Normal for one and Changed for the other). That's how the function can get a CSS class name that is specific to that button; it grabs it from the text inside the clicked <button> element.
I said "nearly exactly" back there because there's actually a letter-case difference between the button text and the actual CSS classes they've defined in the CSS rules (which are normal and changed). That's why they have to lower the letter-case of the extracted button text (toLowerCase()) before assigning the class name. CSS classes are case-sensitive (see Are CSS selectors case-sensitive?).
As I said, this is unusual code. It is rather inadvisable to create a mapping (especially an inexact mapping!) between plain HTML text and code metadata (CSS classes in this case).

Can someone help me apply the js 'this' keyword to an iterated jq select expression

Here's my latest version of this code. The 1st line iterates through the rows of a table skipping over the 1st row. For each row I test to see if the (class='dim') state of one of 8 tag elements (index j) on that row matches the corresponding !'dim' state of a set of 8 filters that the user can toggle. The idea is to set any rows where the 'active' filter / 'dim' tag states line up, to the 'hide' class, so they can disappear from the table that the user sees. The CSS for disappearing it is: .hide {display:none;}
It's that last 'if' statement that's killing me. I've tried dozens of versions but I always get some form of syntax error, undefined variable, etc. In that line of code here I've removed my latest set of +, ', " characters to better show clearly what I'm trying to do.
I don't just want something that works to replace this code. And I'm not interested in the shortest trickiest way to do it. I'd like to see some simple obvious code that I could easily understand a year from now so I can solve problems like this myself. Thanks in advance.
var thisRow = $('tbody tr:gt(0)').each(function() {
for (var i=0,j=4;i<8;i++,j++) {
if (!$('.butt').eq(i).hasClass('dim')) {
if (thisRow.nth-child(j)).hasClass('dim')) $(this).addClass('hide');
else $(this).removeClass('hide');
}
}
}
Above this line is the question as I first asked it. Below this is the complete function in case anyone else might find this useful. Thanks to Mr. Pavlikov for the lesson!
function filterTbl() { //Hide rows that don't satisfy all active filters
var butts=$('.butt'); //Each filter button has class 'butt'
$('tbody tr:gt(0)').each(function() { //iterate each table row except the 1st
var thisRow = $(this); //the row being examined
var chilluns = thisRow.children(); //all td's in the row being examined
for (var i=0,j=4;i<8;i++,j++) {
if (!butts.eq(i).hasClass('dim')) { //If this filter is active
//and If the corresponding tag is not active (is 'dimmed'), then hide this row
if (chilluns.eq(j).hasClass('dim')) thisRow.addClass('hide');
else thisRow.removeClass('hide'); //else unhide this row
}
}
});
}
First of all you should be getting thisRow variable like this (not like you are currently doing)
$('tbody tr:gt(0)').each(function() {
var thisRow = $(this);
}
And what does nth-child stand for? Use nth-child selector correctly or use siblings at least if you are willing to compare one row with other rows. I didn't quite understand what are you trying to do, but hope this helps.
And some usefull tips. You do not need to find $('.butt') EVERY TIME in the loop, just find it once before your each loop:
var butts = $('.butt');
So now you will be able to replace
$('.butt').eq(i)
with
butts.eq(i)
This is significant speedup.
Also if n-th child you are trying to find is something that is inside thisRow, find children and do .eq() on them.

Javascript showing tr when the list item has a specific value

I've to following question. I've a multiple listitem and i want show the three hidden tr i've set to display 'hidden' when the list item has a specific value.
the form elements have the following set up
<tr>
<td><label>bla</label></td>
<td><input id='bla'></td>
</tr>
i use the following javascript.
function checkValue(){
var verhuurdVan = document.getElementById('verhuurd_van_datepicker_field').parentNode.parentNode.style.display='none';
var verhuurdTot = document.getElementById('verhuurd_tot_datepicker_field').parentNode.parentNode.style.display='none';
var beschikbaar = document.getElementById('beschikbaar_verhuur_1').parentNode.parentNode.style.display='none';
var list = document.getElementById('productstatus_id_ListOn');
for(var i=0; i<list.options.length; i++){
if(list.options[i].value == '1'){
here the table rows has to be set to display block;
}
}
Ignore the HTML id name's because this is parsed by the CMS i use.
thanks in advance.
Without knowing the exact layout of your DOM it's not possible to give the exact lines of code required. However, it seems quite clear from the code block you posted that the elements are being hidden by lines 3-5.
Consequently, within your inner if block, all you have to do to make the elements reappear is to set the style of the corresponding elements back to 'block', e.g.:
document.getElementById('verhuurd_tot_datepicker_field').parentNode.parentNode.style.display='block';
Which of the three nodes you actually activate is up to you to decide, but that's how you would go about it.

Categories

Resources