jQuery selecting direct "parents", not similar - javascript

I got a fixed structure of a table like this one:
<table>
<tr><td class="lvl-0">1</td><tr>
<tr><td class="lvl-1">2</td><tr>
<tr><td class="lvl-2">3</td><tr>
<tr><td class="lvl-2">4</td><tr>
<tr><td class="lvl-2">5</td><tr>
<tr><td class="lvl-1">6</td><tr>
<tr><td class="lvl-2">7</td><tr>
<tr><td class="lvl-2 selected">8</td><tr>
<tr><td class="lvl-2">9</td><tr>
</table>
I want to select every lvl parent.
That means I want to select every lvl previously from the selected. Except similar. That means in this example it would be 6 and 1
I tried it with this:
var ss = [];
for(var l = lvl; l <= 5; l++){
ss.push('td.lvl-'+l);
}
var ul = jQuery('table').find(ss.join(',')).closest('tr');
var pa = jQuery('.selected').closest('tr').prevAll('tr').not(ul);
But it also select number 2
// EDIT
I have a jsfiddle
http://jsfiddle.net/g7yhwojg/3/ selected should be:
14
13
10
1

I got it on my own.
var lvls = [];
pa = pa.filter(function(){
var pc = jQuery(this).find('td').attr('class');
if(lvls.indexOf(pc) == -1){
console.log(pc);
lvls.push(pc);
return true;
}
return false;
});
http://jsfiddle.net/g7yhwojg/11/
I looped through all the upper elemnts and took everytime only the first.

While you've already posted your own answer, I thought I'd offer an alternative, in case you're interested:
// caching the selected element (selecting by its id):
var selected = $('#selected'),
// caching the regular expression, in case it might be
// be needed again later; this matches a serious of
// one or more (+) numbers (\d) at the end of the
// string ($):
levelRegex = /\d+$/,
// finding the numbers at the end of the selected
// element's class attribute, and using parseInt()
// to convert that to a number in base-10:
selectedLevel = parseInt(selected.attr('class').match(levelRegex), 10),
// caching the selected element's closest ancestor
// <tr> element:
selectedParent = selected.closest('tr'),
// caching the <tr> elements before the selectedParent:
rowsBefore = selectedParent.prevAll(),
// using map() to iterate over those elements and
// if their child <td> element has a class equal to
// 'lvl-' + (selectedLevel - 1)
// we first decrement selectedLevel, and then
// return the text of the current <tr> element;
// decrementing here means we can only ever
// retrieve the first instance of an element
// with a 'lower' level:
pseudoParents = rowsBefore.map(function (i) {
if ($(this).find('td').hasClass('lvl-' + (selectedLevel - 1))) {
--selectedLevel;
return this.textContent;
}
// converting the 'map' into a native Array:
}).get();
console.log(pseudoParents);
// ["14", "13", "10", "1"]
var selected = $('#selected'),
levelRegex = /\d+$/,
selectedLevel = parseInt(selected.attr('class').match(levelRegex), 10),
selectedParent = selected.closest('tr'),
rowsBefore = selectedParent.prevAll(),
pseudoParents = rowsBefore.map(function(i) {
if ($(this).find('td').hasClass('lvl-' + (selectedLevel - 1))) {
--selectedLevel;
return this.textContent.trim();
}
}).get();
// snippet logs to the bottom of the result panel:
snippet.log(pseudoParents);
// logs to the console (obviously):
console.log(pseudoParents);
// ["14", "13", "10", "1"]
.lvl-0 {
padding-left: 10px;
}
.lvl-1 {
padding-left: 30px;
}
.lvl-2 {
padding-left: 50px;
}
.lvl-3 {
padding-left: 70px;
}
.lvl-4 {
padding-left: 90px;
}
.lvl-5 {
padding-left: 110px;
}
body {
color: #ffffff;
background: #000000;
}
<!-- Provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tr>
<td class="lvl-0">1</td>
</tr>
<tr>
<td class="lvl-1">2</td>
</tr>
<tr>
<td class="lvl-2">3</td>
</tr>
<tr>
<td class="lvl-2">4</td>
</tr>
<tr>
<td class="lvl-2">5</td>
</tr>
<tr>
<td class="lvl-1">6</td>
</tr>
<tr>
<td class="lvl-2">7</td>
</tr>
<tr>
<td class="lvl-2">8</td>
</tr>
<tr>
<td class="lvl-2">9</td>
</tr>
<tr>
<td class="lvl-1">10</td>
</tr>
<tr>
<td class="lvl-2">11</td>
</tr>
<tr>
<td class="lvl-2">12</td>
</tr>
<tr>
<td class="lvl-2">13</td>
</tr>
<tr>
<td class="lvl-3">14</td>
</tr>
<tr>
<td class="lvl-4">15</td>
</tr>
<tr>
<td class="lvl-4" id="selected">16</td>
</tr>
<tr>
<td class="lvl-3">17</td>
</tr>
<tr>
<td class="lvl-1">18</td>
</tr>
<tr>
<td class="lvl-2">19</td>
</tr>
<tr>
<td class="lvl-2">20</td>
</tr>
<tr>
<td class="lvl-2">21</td>
</tr>
</table>
External JS Fiddle demo, for experimentation and development.
Note that in both the Stack Snippet, and the JS Fiddle, I've corrected the HTML to close the <tr> elements, otherwise the erroneous <tr> tags at the end of each line of the <table> was creating a new, empty, <tr> for no reason.
References:
JavaScript:
JavaScript Regular Expressions Guide (MDN).
Node.textContent.
parseInt().
String.prototype.match().
String.prototype.trim().
jQuery:
attr().
closest().
get().
map().
prevAll().

Related

How to loop through a table and get the td elements to follow a condition

I just want make so it the tr hides when the td does not follow the requirements, tried with jQuery and JavaScript, don't know what's wrong.
$(document).ready(function(){
$("td").each(function() {
var id = $(this).attr("price_search");
if (id > value4 && id < value5) {
$(this).hide;
}
else {
$(this).hide;
}
});
});
You can do this.
Hope this will help you.
$(document).ready(function() {
var value4 = 2;
var value5 = 4;
$("td").each(function() {
var id = $(this).attr("price_search");
if (id > value4 && id < value5) {
$(this).hide();
} else {
$(this).show();
}
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table>
<tr>
<td price_search="3">10</td>
<td price_search="2">20</td>
<td price_search="3">30</td>
</tr>
</table>
I am going to go out on a limb here and make broad assumptions on content not in the question.
Your .hide; is invalid syntax
You are missing value for two variables value4 and value4 which frankly are not well named variables at all. I will make an assumption that those are better named and that they come from somewhere during the page rendering.
I make an assumption that you have something you want to filter/hide by those upper/lower price points.
I make the assumption the attribute might contain values that need to be parsed (not a number as they are)
var lowerPricePoint = .45;
var upperPricePoint = 5.25;
$(function() {
$("td").filter('[price_search]').each(function() {
// parse out a price from perhaps formatted values
let price = Number.parseFloat($(this).attr("price_search").replace(/\$|,/g, ''));
// toggle visibility of the row
$(this).closest('tr').toggle(price > lowerPricePoint && price < upperPricePoint);
});
});
td {
border: solid black 1px;
padding: 0.4em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<table>
<tr>
<td>Wear it</td>
<td price_search="123.13">Shoes</td>
</tr>
<tr>
<td>Drive it</td>
<td price_search="$23,123.13">Car</td>
</tr>
<tr>
<td>Drink it</td>
<td price_search="3.13">Beet Juice</td>
</tr>
<tr>
<td>Eat it</td>
<td price_search="12.13">Can of expensive corn</td>
</tr>
<tr>
<td>Cheap</td>
<td price_search="35">Radish</td>
</tr>
<tr>
<td>Use it</td>
<td price_search="1.45">Paper towel</td>
</tr>
<tr>
<td>Plain</td>
<td price_search="$1.87">Butter</td>
</tr>
<tr>
<td>Herb</td>
<td price_search="$2.45">Butter</td>
</tr>
<tr>
<td>Cheap</td>
<td price_search="15">Gum</td>
</tr>
</table>

td element not parsed to int

I have a table with id #tab1.
For each row, I want to calculate the value of column Points / Matches and to put it in the column Coeficiency, but my code doesn't work.
The numbers aren't parsed to int. I would always like to know if
elem[4].innerHTML(z); is ok to set coeficiency.
Average();
function Average() {
var table = document.getElementById('tab1'),
rows = table.getElementsByTagName('tbody')[1].getElementsByTagName('tr');
//console.log(rows.length);
for (var i = 0; i < rows.length; i++) {
elem = rows[i].getElementsByClassName("columns");
var x = parseInt(elem[2]);
var y = parseInt(elem[3]);
// console.log(x+y," ");
console.log(x, " ", y);
var z = y / x;
elem[4].innerHTML(z);
}
<div id="mytable">
<table id="tab1">
<tr class="rows">
<th class="columns">#</th>
<th class="columns">Team</th>
<th class="columns">Matches</th>
<th class="columns">Points</th>
<th class="columns">Coeficiency</th>
</tr>
<tbody>
<tr class="rows">
<td class="columns">1</td>
<td class="columns">Baetasii</td>
<td class="columns">3</td>
<td class="columns">9</td>
<td class="columns">100%</td>
</tr>
<tr class="rows">
<td class="columns">2</td>
<td class="columns">Carcotasii</td>
<td class="columns">2</td>
<td class="columns">5</td>
<td class="columns">100%</td>
</tr>
</tbody>
</table>
</div>
Okay, so a few pointers having looked over your code, first of all innerHTML is not a function, it's a simple property, you can just reassign it, however, I suggest using textContent due to the fact that using innerHTML, you can allow for XSS to occur.
I mean I know XSS probably isn't an issue in this specific scenario, however I thought it my be of value mentioning that.
Also, as I mentioned in the comments above, using parseInt, you need to pass it a string rather than an object which is what you were originally doing. Using functions such as getElementsByClassName or querySelectorAll, you'll have an array-like object, such as a HTMLCollection which contains a number of objects, usually Elements or Nodes.
Average();
function Average() {
var table = document.getElementById('tab1'),
rows = table.getElementsByTagName('tbody')[1].getElementsByTagName('tr');
//console.log(rows.length);
for (var i = 0; i < rows.length; i++) {
elem = rows[i].getElementsByClassName("columns");
var x = parseInt(elem[2].textContent);
var y = parseInt(elem[3].textContent);
// console.log(x+y," ");
console.log(x, " ", y);
var z = y / x;
elem[4].textContent = z;
}
}
<div id="mytable">
<table id="tab1">
<tr class="rows">
<th class="columns">#</th>
<th class="columns">Team</th>
<th class="columns">Matches</th>
<th class="columns">Points</th>
<th class="columns">Coeficiency</th>
</tr>
<tbody>
<tr class="rows">
<td class="columns">1</td>
<td class="columns">Baetasii</td>
<td class="columns">3</td>
<td class="columns">9</td>
<td class="columns">100%</td>
</tr>
<tr class="rows">
<td class="columns">2</td>
<td class="columns">Carcotasii</td>
<td class="columns">2</td>
<td class="columns">5</td>
<td class="columns">100%</td>
</tr>
</tbody>
</table>
</div>
Edit
I thought I'd also include a neater version, it does near enough the same logic stuff, it's more or less just more modern JavaScript syntax, using a more 'functional-style'. Originally I basically copied the exact same style that you provided for the sake of simplicity, but I thought that there's a few issues with that. An example being how you've used a capital letter for the Average, personally I only use a capital letter at the start of a name if it's a class, this is a personal choice however, feel free to disagree or stick to what you know!
I personally prefer using more modern syntax as personally I think is easier to read, it's more clear and concise, generally it looks like less code to read through.
// States if an array like object is empty or not.
const isEmpty = a => a.length > 0;
// Returns the text content of a html object.
const txt = td => td == null ? null : td.textContent;
// Simply updates the UI.
const render = tds => v => tds[4].textContent = parseFloat(v).toFixed(2);
// Works out whether or not to fire update or do nothing.
const compute = tds => isEmpty(tds) ? render(tds)(txt(tds[3]) / txt(tds[2])) : null;
// Gets the average for each tr.
const avg = trs => trs.forEach(tr => compute(tr.querySelectorAll("td")));
// Fire the avg function.
const update = () => avg(document.querySelectorAll("#tab1 tbody tr"));
// Render tr tag.
const renderTr = i => n => m => p => `<tr>
<td>${i}</td><td>${n}</td><td>${m}</td><td>${p}</td><td></td>
</tr>`;
// Add a table row.
const append = () => {
const tbl = document.getElementById("tab1");
const i = document.querySelectorAll("#tab1 tbody tr").length,
n = '_____',
m = Math.floor(Math.random() * 10) + 1,
p = Math.floor(Math.random() * 10) + 1;
// Safe-ish because what's being entered is controlled 100%.
// But generally try not to use innerHTML.
tbl.innerHTML += renderTr(i)(n)(m)(p);
update();
};
// Allow for auto add.
document.getElementById("add").onclick = append;
update(); // Initial run.
<div id="mytable">
<table id="tab1">
<tr class="rows">
<th class="columns">#</th>
<th class="columns">Team</th>
<th class="columns">Matches</th>
<th class="columns">Points</th>
<th class="columns">Coeficiency</th>
</tr>
<tbody>
<tr class="rows">
<td class="columns">1</td>
<td class="columns">Baetasii</td>
<td class="columns">3</td>
<td class="columns">9</td>
<td class="columns">100%</td>
</tr>
<tr class="rows">
<td class="columns">2</td>
<td class="columns">Carcotasii</td>
<td class="columns">2</td>
<td class="columns">5</td>
<td class="columns">100%</td>
</tr>
</tbody>
</table>
</div>
<button id="add">Add Row</button>
Using Object#values Array#forEach #getElementsByTagName
The main issue is that you needed to retrieve the text value with innerText.
You also don't need the redundant class names.
const table = document.getElementById("table");
const rows = table.querySelectorAll("tbody > tr");
Object.values(rows).forEach(row => {
const tds = row.getElementsByTagName('td');
if (tds.length === 5) {
const x = parseInt(tds[2].innerText),
y = parseInt(tds[3].innerText);
const z = y / x;
tds[4].innerText = `${z}`;
}
});
<table id="table">
<tr>
<th>#</th>
<th>Team</th>
<th>Matches</th>
<th>Points</th>
<th>Coeficiency</th>
</tr>
<tbody>
<tr>
<td>1</td>
<td>Baetasii</td>
<td>3</td>
<td>9</td>
<td>100%</td>
</tr>
<tr>
<td>2</td>
<td>Carcotasii</td>
<td>2</td>
<td>5</td>
<td>100%</td>
</tr>
</tbody>
</table>
getElementsByClassName returns an array-like object of all child elements which have all of the given class names.
Since we have a collection of DOM elements, elem[2] it's a DOM element and you should access its textContent property.
Also, you're using innerHTML property in a wrong way. Just replace
elem[4].innerHTML(z);
to
elem[4].innerHTML = z;
Average();
function Average() {
var table = document.getElementById('tab1'),
rows = table.getElementsByTagName('tbody')[1].getElementsByTagName('tr');
for (var i = 0; i < rows.length; i++) {
elem = rows[i].getElementsByClassName("columns");
var x = parseInt(elem[2].textContent);
var y = parseInt(elem[3].textContent);
console.log(x, " ", y);
var z = y / x;
elem[4].innerHTML = z;
}
}
<div id="mytable">
<table id="tab1">
<tr class="rows">
<th class="columns">#</th>
<th class="columns">Team</th>
<th class="columns">Matches</ht>
<th class="columns">Points</th>
<th class="columns">Coeficiency</th>
<tbody>
<tr class="rows">
<td class="columns">1</td>
<td class="columns">Baetasii</td>
<td class="columns">3</td>
<td class="columns">9</td>
<td class="columns">100%</td>
</tr>
<tr class="rows">
<td class="columns">2</td>
<td class="columns">Carcotasii</td>
<td class="columns">2</td>
<td class="columns">5</td>
<td class="columns">100%</td>
</tr>
</tbody>
</table>
</div>

JQuery sum rows with unique id elements

I'm looking for a way to display the sum of all the input elements (quantity and amount) respectively in the last 2 cells of each line.
Every element in this table has a unique id. Each input element has an id that contains information about the row it appears in :
var trRows = ['tr2', 'tr3', 'tr4', 'tr5', 'tr6', 'tr7', 'tr8', 'tr9', 'tr10', 'tr11', 'tr12'];
trRows.forEach(function(trRow) {
var $sumQuantity = 0;
var $sumAmount = 0;
$(this).find('#tr' + trRow + 'input[id *= "qty"]').each(function() {
$sumQuantity += +$(this).text() || 0;
});
$(this).find('#tr' + trRow + 'input[id *= "amount"]').each(function() {
$sumAmount += +$(this).text() || 0;
});
$('#sumQtyTR' + trRows, this).html($sumQuantity);
$('#sumAmountTR' + trRows, this).html($sumAmount);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tr id='tr2'>
<td id='td2_65'><span id='span_i2qty_65'><input id='input_i2qty_65' class='editbox'></span>
</td>
<td id='td2_65'><span id='span_i2amount_65'><input id='input_i2amount_65' class='editbox'></span>
</td>
<td id='td2_66'><span id='span_i2qty_66'><input id='input_i2qty_66' class='editbox'></span>
</td>
<td id='td2_66'><span id='span_i2amount_66'><input id='input_i2amount_66' class='editbox'></span>
</td>
<td id='sumQtyTR2'></td>
<td id='sumAmountTR2'></td>
</tr>
<tr id='tr3'>
<td id='td3_65'><span id='span_i3qty_65'><input id='input_i3qty_65' class='editbox'></span>
</td>
<td id='td3_65'><span id='span_i3amount_65'><input id='input_i3amount_65' class='editbox'></span>
</td>
<td id='td3_66'><span id='span_i3qty_66'><input id='input_i3qty_66' class='editbox'></span>
</td>
<td id='td3_66'><span id='span_i3amount_66'><input id='input_i3amount_66' class='editbox'></span>
</td>
<td id='sumQtyTR3'></td>
<td id='sumAmountTR3'></td>
</tr>
<!-- More rows -->
</table>
I can't figure out why sums are not displayed. Selectors ? Thanks
Recommendations
Within your code there are a few points of interest that may be worth consideration to reduce the clutter:
IDs are intended to be unique. There should never be a time where an ID is used in more than one place in an HTML document. In places where you think two or more elements might share an id, you should use a class instead
Unless you are using jQuery elsewhere, you should know that jQuery is not necessary. It does provide several advantages (including cross-browser compliance), but you should consider using native JavaScript and a packager that will add the necessary polyfills
Examples
The examples below are intended to be close in structure/design to the original source code in the OP for learning purposes; however, it should be noted that there are many ways to optimize to improve performance and readability.
One major point worth mentioning is in the logic flow. The structure in these examples are inefficient, since every time an input is updated it is looping over and recalculating sums for all of the rows. This could be improved by only calculating and updating the sum of the altered row. Additionally, holding an array of interested rows (e.g., row_ids) could be better improved with more semantic HTML and the use of classes (if needing to be selective).
There are some other major changes made to both the jQuery and ES6+ example below. Most notably is the onChange event handler. The code in the OP iterated over each ID immediately on page load, but never again. There needed to be a hook into some event that would be called when the input box was modified; thus the onChange or onBlur event is necessary in order to re-calculate the values.
jQuery Example
Given these two recommendations, I've made some substantial changes to your HTML and JavaScript, which include but are not limited to:
removed unnecessary IDs and added respective classes
use reduce for sum calculation
jQuery(document).ready(function($) {
let row_ids = ['tr2', 'tr3', 'tr4', 'tr5', 'tr6', 'tr7', 'tr8', 'tr9', 'tr10', 'tr11', 'tr12'];
function changeEventHandler() {
$.each(row_ids, function(ndx, row_id) {
const $row = $('#' + row_id)
if (!$row.length)
return;
const quantity = $row.find('input.quantity').get().reduce(function(acc, input) {
acc += parseFloat(input.value) || 0;
return acc
}, 0)
const amount = $row.find('input.amount').get().reduce(function(acc, input) {
acc += parseFloat(input.value) || 0;
return acc
}, 0)
$row.find('.sum.quantity').text(quantity)
$row.find('.sum.amount').text(amount)
});
}
$(document).on('change', 'input', changeEventHandler)
})
.sum { background: #eee; }
th,td {
vertical-align: bottom;
white-space: nowrap;
text-align: center;
}
table { width: 100%; }
input { width: 40%; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Fill in each input with a number (watch sums auto-update):
<table>
<thead>
<tr>
<th>QTY 1</th>
<th>AMT 1</th>
<th>QTY 2</th>
<th>AMT 2</th>
<th>SUM QTY</th>
<th>SUM AMT</th>
</tr>
</thead>
<tbody>
<tr id='tr2'>
<!-- td: use class and not id -->
<!-- remove unnecessary classes -->
<td class="item_65"><span><input class='quantity'></span></td>
<td class="item_65"><span><input class='amount'></span></td>
<td class="item_66"><span><input class='quantity'></span></td>
<td class="item_66"><span><input class='amount'></span></td>
<td class="sum quantity"></td>
<td class="sum amount"></td>
</tr>
<tr id='tr3'>
<td class="item_65"><span><input class='quantity'></span></td>
<td class="item_65"><span><input class='amount'></span></td>
<td class="item_66"><span><input class='quantity'></span></td>
<td class="item_66"><span><input class='amount'></span></td>
<td class="sum quantity"></td>
<td class="sum amount"></td>
</tr>
<!-- More rows -->
</tbody>
</table>
ES6+ Example
Given the two recommendations, I've made some substantial changes to your HTML and JavaScript, which include but are not limited to:
removed unnecessary IDs and added respective classes
removed unnecessary jQuery code (used native event handling)
use reduce for sum calculation
use arrow functions for simplified syntax
Note: as mentioned prior, the code may not work in all browsers (all versions and variations), but should work in most of the newer browsers. The snippet will work in most (if not all) because it is configured to use babel
function changeEventHandler() {
let row_ids = ['tr2', 'tr3', 'tr4', 'tr5', 'tr6', 'tr7', 'tr8', 'tr9', 'tr10', 'tr11', 'tr12'];
// could iterate over the rows:
// document.querySelectorAll(selector)
// where selector is 'tr' or '[id^="tr"]'
row_ids.forEach(row_id => {
const row = document.querySelector(`#${row_id}`)
if (row == null)
return;
const qty_el = row.querySelectorAll('input.quantity')
const quantity = [...qty_el].reduce((acc, cv) => acc += parseFloat(cv.value) || 0, 0)
const amt_el = row.querySelectorAll('input.amount')
const amount = [...amt_el].reduce((acc, cv) => acc += parseFloat(cv.value) || 0, 0)
row.querySelector('.sum.quantity').textContent = quantity
row.querySelector('.sum.amount').textContent = amount
});
}
window.setTimeout(function() {
document.querySelectorAll('input').forEach(() => this.onchange = changeEventHandler)
},0);
.sum { background: #eee; }
th,td {
vertical-align: bottom;
white-space: nowrap;
text-align: center;
}
table { width: 100%; }
input { width: 40%; }
Fill in each input with a number (watch sums auto-update):
<table>
<thead>
<tr>
<th>QTY 1</th>
<th>AMT 1</th>
<th>QTY 2</th>
<th>AMT 2</th>
<th>SUM QTY</th>
<th>SUM AMT</th>
</tr>
</thead>
<tbody>
<tr id='tr2'>
<!-- td: use class and not id -->
<!-- remove unnecessary classes -->
<td class="item_65"><span><input class='quantity'></span></td>
<td class="item_65"><span><input class='amount'></span></td>
<td class="item_66"><span><input class='quantity'></span></td>
<td class="item_66"><span><input class='amount'></span></td>
<td class="sum quantity"></td>
<td class="sum amount"></td>
</tr>
<tr id='tr3'>
<td class="item_65"><span><input class='quantity'></span></td>
<td class="item_65"><span><input class='amount'></span></td>
<td class="item_66"><span><input class='quantity'></span></td>
<td class="item_66"><span><input class='amount'></span></td>
<td class="sum quantity"></td>
<td class="sum amount"></td>
</tr>
<!-- More rows -->
</tbody>
</table>

JQuery: Identify duplicate values in a table textbox column and highlight the textboxes

I'm using JQuery and I'm sure this is pretty simple stuff but I was unable to find a solution. I have an employee table with "Number" column which is editable(text box). I want to find the duplicates in the "Number" column and highlight those textboxes. For example in the table below I want to highlight all textboxes with values 10 and 20. Also when a edit is done and there are no longer duplicates, remove the highlight.
Here's the JSFiddle
Any Ideas?
<table id="employeeTable">
<tr>
<th>Id</th>
<th>Name</th>
<th>Number</th>
</tr>
<tr>
<td>1</td>
<td>John</td>
<td>10</td>
</tr>
<tr>
<td>2</td>
<td>Sally</td>
<td>20</td>
</tr>
<tr>
<td>3</td>
<td>Mary</td>
<td>10</td>
</tr>
<tr>
<td>4</td>
<td>Sam</td>
<td>30</td>
</tr>
<tr>
<td>5</td>
<td>Chris</td>
<td>20</td>
</tr>
</table>
There are different possibilities, basically you'll have to test if the value of an array exists more than one time, for example like this.
Update:
Using the value selector works fine in the initial state, but it seems that when a value is changed by direct user input or by calling .val(), the HTML attribute value is not changed (only the native JS .value). Therefore - to use the value selector in this context, the html value attribute is always updated with the JS .value.
function highlightDuplicates() {
// loop over all input fields in table
$('#employeeTable').find('input').each(function() {
// check if there is another one with the same value
if ($('#employeeTable').find('input[value="' + $(this).val() + '"]').size() > 1) {
// highlight this
$(this).addClass('duplicate');
} else {
// otherwise remove
$(this).removeClass('duplicate');
}
});
}
$().ready(function() {
// initial test
highlightDuplicates();
// fix for newer jQuery versions!
// since you can select by value, but not by current val
$('#employeeTable').find('input').bind('input',function() {
$(this).attr('value',this.value)
});
// bind test on any change event
$('#employeeTable').find('input').on('input',highlightDuplicates);
});
Updated fiddle is here.
I guess this is what you are exactly looking for:
Working : Demo
1) First for loop for taking all input values into an array inpValArr[]
2) Second for loop for sorting and finding out the duplicate ones.
3) Third for loop for adding class .highLight to duplicate ones.
Now all this is in a function: inputCheck() which is called on DOM Ready and after you edit the text field.
inputCheck();
$("#employeeTable input").bind("change paste keyup", function() {
inputCheck();
});
function inputCheck() {
var totalInp = $("#employeeTable input").length;
var inpValArr = [];
for (var j = 0; j < totalInp; j++) {
var inpVal = $("#employeeTable input:eq(" + j + ")").val();
inpValArr.push(inpVal);
}
var sorted_arr = inpValArr.sort();
var results = [];
for (var i = 0; i < inpValArr.length - 1; i++) {
if (sorted_arr[i + 1] == sorted_arr[i]) {
results.push(sorted_arr[i]);
}
}
$('#employeeTable input').removeClass('highLight');
for (var k = 0; k < totalInp; k++) {
$('#employeeTable :input[value="' + results[k] + '"]').addClass('highLight');
}
}
#employeeTable th,
#employeeTable td {
padding: 0.8em;
border: 1px solid;
}
#employeeTable th {
background-color: #6699FF;
font-weight: bold;
}
.highLight {
background: red;
}
<table id="employeeTable">
<tr>
<th>Id</th>
<th>Name</th>
<th>Number</th>
</tr>
<tr>
<td>1</td>
<td>John</td>
<td>
<input type="text" value="10" />
</td>
</tr>
<tr>
<td>2</td>
<td>Sally</td>
<td>
<input type="text" value="20" />
</td>
</tr>
<tr>
<td>3</td>
<td>Mary</td>
<td>
<input type="text" value="10" />
</td>
</tr>
<tr>
<td>4</td>
<td>Sam</td>
<td>
<input type="text" value="30" />
</td>
</tr>
<tr>
<td>5</td>
<td>Chris</td>
<td>
<input type="text" value="20" />
</td>
</tr>
</table>
You could easily give a class such as 'hasInput' to all td with inputs and then try a .each on all of them and check for value if they are 10 or 20 and then add a class to make them styled as you wish.
html:
<table id="employeeTable">
<tr>
<th>Id</th>
<th>Name</th>
<th>Number</th>
</tr>
<tr>
<td>1</td>
<td>John</td>
<td class="hasInput"><input type="text" value = "10"/></td>
</tr>
<tr>
<td>2</td>
<td>Sally</td>
<td class="hasInput"><input type="text" value = "20"/></td>
</tr>
<tr>
<td>3</td>
<td>Mary</td>
<td class="hasInput"><input type="text" value = "10"/></td>
</tr>
<tr>
<td>4</td>
<td>Sam</td>
<td class="hasInput"><input type="text" value = "30"/></td>
</tr>
<tr>
<td>5</td>
<td>Chris</td>
<td class="hasInput"><input type="text" value = "20"/></td>
</tr>
css:
#employeeTable th, #employeeTable td{
padding:0.8em;
border: 1px solid;
}
#employeeTable th{
background-color:#6699FF;
font-weight:bold;
}
.colored {
background-color: red;
}
js:
$('.hasInput > input').each(function() {
if ($(this).val() == 10 || $(this).val() == 20) {
$(this).addClass('colored');
}
});
DEMO
This would work:
var dupes=[], values=[];;
$('.yellow').removeClass('yellow');
$('#employeeTable td:nth-child(3) input').each(function(){
if($.inArray($(this).val(),values) == -1){
values.push($(this).val());
}
else{
dupes.push($(this).val());
}
});
$('#employeeTable td:nth-child(3) input').filter(function(){return $.inArray(this.value,dupes) == -1 ? false : true }).addClass('yellow');
#employeeTable th, #employeeTable td{
padding:0.8em;
border: 1px solid;
}
#employeeTable th{
background-color:#6699FF;
font-weight:bold;
}
.yellow{
background-color:yellow;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="employeeTable">
<tr>
<th>Id</th>
<th>Name</th>
<th>Number</th>
</tr>
<tr>
<td>1</td>
<td>John</td>
<td><input type="text" value = "10"/></td>
</tr>
<tr>
<td>2</td>
<td>Sally</td>
<td><input type="text" value = "20"/></td>
</tr>
<tr>
<td>3</td>
<td>Mary</td>
<td><input type="text" value = "10"/></td>
</tr>
<tr>
<td>4</td>
<td>Sam</td>
<td><input type="text" value = "30"/></td>
</tr>
<tr>
<td>5</td>
<td>Chris</td>
<td><input type="text" value = "20"/></td>
</tr>
</table>
Expanding on the answer provided by #axel.michel using .count() selector of Linq.js. I decided to go this route because I couldn't get the JQuery selector to work correctly provided in his answer. And I really like the Linq.js and find myself loving it more each time i implement a use of it.
var allTextBoxes = $().find('input:text');
// loop over all input fields on page
$(allTextBoxes)
.each(function() {
// select any other text boxes that have the same value as this one
if (Enumerable.from(allTextBoxes).count("$.value == '" + $(this).val() + "'") > 1) {
// If more than 1 have the same value than highlight this textbox and display an error message
$(this).addClass('duplicate');
$('#custom-field-validator').html('Custom fields must have unique names.');
valid = false;
} else {
// otherwise remove
$(this).removeClass('duplicate');
}
});
This is working fine without needing to worry about the value selector and syncing the value attributes.

background color using html n css

I'm trying to loop through a table to get each TD value. If the value is below a specific number then I'll do something.
</head>
<body>
<table id="tableData" name="tableData">
<tr>
<td>abc</td>
<td>5</td>
<td>abcd</td>
</tr>
<tr>
<td>aaaa</td>
<td>15</td>
<td>bbbb</td>
</tr>
<tr>
<td>ccc</td>
<td>25</td>
<td>dddd</td>
</tr>
</table>
</body>
above is my code .. i need to change the background colors of the 2nd column as below . if the value of the 2nd column element is <= 10 then the color is green , from 11-20 its yellow and above 21 its red.
I have given the sample code here. actually in real , the table is derived from the database , iy may have any nomber of rows. so i need to color the column as the page gets loaded.
Any help is appreciated, thanks.
The following modified plain JavaScript will colour the <td> elements as required:
function checkForm() {
var tds = document.querySelectorAll('td[id]');
for (var j = 0; j < tds.length; j++) {
var i = tds[j].innerHTML;
if(i < 10){
tds[j].style.backgroundColor = 'green';
} else if(i >= 11 && i <= 20){
tds[j].style.backgroundColor = 'yellow';
} else if(i > 20){
tds[j].style.backgroundColor = 'red';
}
}
}
but you will need to modify the HTML to give the <td>s unique ID values, for example
<body onload="checkForm();">
<table id="tableData" name="tableData">
<tr>
<td>abc</td>
<td id="a">5</td>
<td>abcd</td>
</tr>
<tr>
<td>aaaa</td>
<td id="b">15</td>
<td>bbbb</td>
</tr>
<tr>
<td>ccc</td>
<td id="c">25</td>
<td>dddd</td>
</tr>
</table>
</body>
If it is always the middle cell that needs colour you could remove the ids completely and rely on the fact that is is "always the middle cell". For example use the following selector instead:
var tds = document.querySelectorAll('td:nth-child(2)');
The only limitation is that querySelectorAll is that it is not supported by IE<9. All other browsers support it.
Since the cell that requires a background-color is always the 2nd cell, you can use the CSS nth-child selector as the argument to in querySelectorAll() to "select the 2nd <td> child element of the parent", which in this case is the <tr>.
So td:nth-child(2) finds the the <td>two</td> element for the following HTML:
<tr>
<td>one</td>
<td>two</td>
<td>three</td>
</tr>
See some examples of how :nth-child works.
Demo of id-less solution (for if the cell to colour is always the middle one).
Since OP is stuck with IE8 and IE8 does not support :nth-child an alternative adjacent sibling combinator can be used to target the 2nd child with the caveats that there must only be 3 <td> and the 3rd must not contain any numbers.
Update:
Based on the actual requirements of needing to work in IE8 and add background-color to the 6th column, here is a simpler (to read) and more cross-browser compatible jQuery solution:
jsBin demo (so it works on IE8)
HTML
Remove the onload="checkForm(); from <body>
<table id="tableData" name="tableData">
<tr>
<td></td><td></td><td></td><td></td>
<td>abc</td>
<td>5</td>
<td>abcd</td>
</tr>
<tr>
<td></td><td></td><td></td><td></td>
<td>aaaa</td>
<td>15</td>
<td>bbbb</td>
</tr>
<tr>
<td></td><td></td><td></td><td></td>
<td>ccc</td>
<td>25</td>
<td>dddd</td>
</tr>
</table>
JavaScript
$(function(){
var tds = $('td:nth-child(6)');
for (var j = 0; j < tds.length; j++) {
var i = tds[j].innerHTML;
if(i < 10){
tds[j].style.backgroundColor = 'green';
} else if(i >= 11 && i <= 20){
tds[j].style.backgroundColor = 'yellow';
} else if(i > 20){
tds[j].style.backgroundColor = 'red';
}
}
});
First off, don't use the same ID's for any elements on a page. It is a unique identifier. If you want to reference more than one element, then use a class instead.
The simplest way to achieve what you want is using two classes - one to define xxx, and then one to define its status (colour). Also, if you use semantic naming (instead of .green,.yellow,.red) you will get good understanding of your code.
ex
.xxx{ font-weight: bold;}
.less10 { background: green;}
.between1120 {background: yellow; }
.over21 { background: red; }
You cannot set CSS depending on the content inside the element. For this you would need some simple jQuery/javascript or your chosen programming language to loop through all the xxx-classes in the table, and add the status class accordingly.
ex
<td class="xxx less10">5</td>
<td class="xxx between1120">15</td>
<td class="xxx over21">35</td>
Firstly you should change the ID xxx to Class xxx.
function checkForm(){
$('td.xxx').each(function(){
var val=parseInt($(this).text());
if(val<=10) $(this).css({background:'green'});
else if(val>10 && val<=20) $(this).css({background:'yellow'});
else if(val>20) $(this).css({background:'red'});
}
}
I think that should work with jQuery.
Here is what you want :
Demo Here</>
<table id="tableData" name="tableData">
<tr>
<td>
abc
</td>
<td class="xxx">
5
</td>
<td>
abcd
</td>
</tr>
<tr>
<td>
aaaa
</td>
<td class="xxx">
15
</td>
<td>
bbbb
</td>
</tr>
<tr>
<td>
ccc
</td>
<td class="xxx">
25
</td>
<td>
dddd
</td>
</tr>
</table>
Javascript
$(document).ready(function () {
var arr = $(".xxx").toArray();
for (var i = 0; i < arr.length; i++) {
if (parseInt(arr[i].innerText) < 10) {
$(arr[i])[0].bgColor = "green";
}
else if (parseInt(arr[i].innerText) >= 11 && parseInt(arr[i].innerText) <= 20) {
$(arr[i])[0].bgColor = 'yellow';
}
else if (parseInt(arr[i].innerText) > 20) {
$(arr[i])[0].bgColor = 'red';
}
}
});

Categories

Resources