This is the scenario. I have a <table> like this:
<table>
<tr>
<th>No.</th>
<th>Name</th>
<th>Price</th>
</tr>
<tr>
<td>1</td>
<td>Coca cola</td>
<td>5</td>
</tr>
<tr>
<td>2</td>
<td>Pepsi</td>
<td>5</td>
</tr>
<tr>
<td>3</td>
<td>Water</td>
<td>3</td>
</tr>
</table>
Now, user will highlight 2 first rows, for example. My task is to extract the appropriate 3 XPaths to get data in each column.
Does anyone have any idea about this? I have thought about it a lot but haven't found any good solution yet. Please help me. Thanks a lot.
EDIT!!!
You can try using Scraper from Google Chrome extension for more detail. What I want is just a little bit like it. Thanks.
Let's say you add a class to the highlighted rows, and after highlighting your html DOM is like this:
<table id="highlights">
<tr>
<th>No.</th>
<th>Name</th>
<th>Price</th>
</tr>
<tr class="highlighted">
<td>1</td>
<td>Coca cola</td>
<td>5</td>
</tr>
<tr class="highlighted">
<td>2</td>
<td>Pepsi</td>
<td>5</td>
</tr>
<tr>
<td>3</td>
<td>Water</td>
<td>3</td>
</tr>
</table>
I added id="highlights" just to make it distingushable, if you have other tables in the page. then this is what you have to do:
var noXpath = "//table[#id='highlights']/tbody/tr[#class='highlighted']/td[1]/text()";
var nameXpath = "//table[#id='highlights']/tbody/tr[#class='highlighted']/td[2]/text()";
var priceXpath = "//table[#id='highlights']/tbody/tr[#class='highlighted']/td[3]/text()";
function getData(xpathQuery) {
var iterator = document.evaluate(xpathQuery, document.body, null, XPathResult.ANY_TYPE, null);
var nodes = [];
var node;
while (node = iterator.iterateNext()) {
nodes.push(node.nodeValue);
}
return nodes;
}
var noData=getData(noXpath);
var namesData=getData(nameXpath);
var priceData=getData(priceXpath);
and this is the working DEMO.
Related
I've hardly used javascript and I'm stuck:
I've got a table with id JamTable
I'm trying to write some JS that will get me an array of each <td> value for any row clicked on, so that I can present it in a popup, wing it back to the server via POST request using an ajax call and then update the elements on the table so no postback is required - but so far I can't even get an array populated.
I've got:
$(document).ready(function () {
// Get all table row elements <tr> in table 'JamTable' into var 'tr'
var tr = $('#JamTable').find('tr');
// Bind a 'click' event for each of those <tr> row elements
tr.bind('click', function (e) {
// so that when a row is clicked:
var r = $(this).closest('tr').row;
var myArray = new Array(r.cells);
for (var c = 0, col; col = r.cells[c]; c++) {
alert(col.text)
}
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id="JamTable">
<tbody>
<tr>
<td>1</td>
<td>JAM</td>
<td>0.004</td>
</tr>
<tr>
<td>3</td>
<td>BOB</td>
<td>0.24</td>
</tr>
<tr>
<td>9</td>
<td>Nasty Simon</td>
<td>94.3</td>
</tr>
</tbody>
</table>
Yeah I'm totally lost when it comes to JS
Using proper event-delegation is key to success in such scenarios. Catching "click" events on rows is guaranteed to work, even with dynamically-added rows (which were added to the DOM after the event listener was defined)
Breakdown (see comments):
const tableElm = document.querySelector('table')
// listen to "click" event anywhere on the <table>
tableElm.addEventListener('click', onTableClick)
function onTableClick(e){
// event delegation
const rowElm = e.target.closest('tr')
// traverse each child of the row (HTMLCollection). map the text value into an Array
// https://stackoverflow.com/a/34250397/104380
const values = rowElm ? [...rowElm.children].map(td => td.innerText) : []
// print result
console.clear()
console.log( values )
}
<table>
<tbody>
<tr>
<td>1</td>
<td>JAM</td>
<td>0.004</td>
</tr>
<tr>
<td>3</td>
<td>BOB</td>
<td>0.24</td>
</tr>
<tr>
<td>9</td>
<td>Nasty Simon</td>
<td>94.3</td>
</tr>
</tbody>
</table>
You should probably also have some unique id on the <tr> if you are sending data back to the server, it might need to know to which row it belongs to
You can delegate the event from tr. On click of it get the children. Using Array.from will create an array of td. Using map to iterate that and get the text from the td
$("#JamTable").on('click', 'tr', function(e) {
let k = Array.from($(this).children()).map((item) => {
return item.innerHTML;
})
console.log(k)
})
td {
border: 1px solid green;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id='JamTable'>
<tbody>
<tr>
<td>1</td>
<td>JAM</td>
<td>0.004</td>
</tr>
<tr>
<td>3</td>
<td>BOB</td>
<td>0.24</td>
</tr>
<tr>
<td>9</td>
<td>Nasty Simon</td>
<td>94.3</td>
</tr>
</tbody>
</table>
You don't need jquery for that.
You may use querySelectorAll to get the trs and simply children on the tr node to get the tds
const trs = [...document.querySelectorAll('tr')]
trs.forEach(tr => tr.addEventListener('click', e => {
// whenever it is clicked, get its tds
const values = [...tr.children].map(td => td.innerText)
console.log('click', values)
}, false))
<table>
<tbody>
<tr>
<td>1</td>
<td>JAM</td>
<td>0.004</td>
</tr>
<tr>
<td>3</td>
<td>BOB</td>
<td>0.24</td>
</tr>
<tr>
<td>9</td>
<td>Nasty Simon</td>
<td>94.3</td>
</tr>
</tbody>
</table>
As #vsync suggested, better to use event delegation in case you have a lot of rows to avoid binding several clicks. This also allows to add more rows later on without to have to bind more click handler
edit2 still thx to #vsync, avoid using onclick and prefer addEventListener to avoid overriding existing events
const table = document.querySelector('table')
table.addEventListener('click', e => {
if (e.target.nodeName !== 'TD') { return }
const values = [...e.target.parentNode.children].map(c => c.innerText)
console.log(values)
}, false)
<table>
<tbody>
<tr>
<td>1</td>
<td>JAM</td>
<td>0.004</td>
</tr>
<tr>
<td>3</td>
<td>BOB</td>
<td>0.24</td>
</tr>
<tr>
<td>9</td>
<td>Nasty Simon</td>
<td>94.3</td>
</tr>
</tbody>
</table>
$('#JamTable tbody tr').click(function () {
var arr = [];
$($(this).children('td')).each(function (index, val) {
arr.push(val.innerText);
});
console.log(arr);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id="JamTable">
<tbody>
<tr>
<td>1</td>
<td>JAM</td>
<td>0.004</td>
</tr>
<tr>
<td>3</td>
<td>BOB</td>
<td>0.24</td>
</tr>
<tr>
<td>9</td>
<td>Nasty Simon</td>
<td>94.3</td>
</tr>
</tbody>
</table>
I know how to do this with jquery, by doing this:
$("#rateTable tr:even").css("background", "#e7edea");
However, the format I am using will not allow jquery. I tried the code below, but it is saying that the style is null. Does anyone see what I am doing wrong?
document.getElementById("rateTable tr:even").style.background = "#e7edea";
<table id="rateTable">
<tr>
<td>Blue</td>
<td>Green</td>
</tr>
<tr>
<td>Red</td>
<td>Purple</td>
</tr>
<tr>
<td>Teal</td>
<td>Yellow</td>
</tr>
</table>
You don't really need JavaScript for this. It could been done in plain CSS with the nth-child pseudo-class.
tr:nth-child(even) {
background: #E7EDEA;
}
<table id="rateTable">
<tr>
<td>Blue</td>
<td>Green</td>
</tr>
<tr>
<td>Red</td>
<td>Purple</td>
</tr>
<tr>
<td>Teal</td>
<td>Yellow</td>
</tr>
</table>
You can do it with #rateTable tr:nth-of-type(even) as your selector. document.selectElementById() only works when you give it the exact string being used as an id, not a CSS selector. I would do it with CSS because it will update automatically and is likely faster, but you can do it in JS if you want:
const evenRows = document.querySelectorAll("#rateTable tr:nth-of-type(even)")
for (const row of evenRows) row.style.background = "#e7edea"
<table id="rateTable">
<tr>
<td>Blue</td>
<td>Green</td>
</tr>
<tr>
<td>Red</td>
<td>Purple</td>
</tr>
<tr>
<td>Teal</td>
<td>Yellow</td>
</tr>
<tr>
<td>Orange</td>
<td>Pink</td>
</tr>
</table>
Try to use querySelectorAll and next iterate over received collection to change background. See below:
var trToChange = document.querySelectorAll("#rateTable tr:nth-child(even)");
for(var i = 0; i < trToChange.length; i++){
trToChange[i].style.background = "#e7edea";
}
I want to remove the TR if its 2nd TD value is similar to another TRs TD value and it's last TD value shouldn't be HIT. And the another scenario is if I have 3 TRs with the same data then 2 of them should be removed and 1 should remain there.
Example:
<table>
<tr>
<td>ID</td>
<td>Ref No</td>
<td>Name</td>
<td>Result</td>
</tr>
<tr>
<td>1</td>
<td>1121</td>
<td>Joseph</td>
<td>CLEAR</td>
</tr>
<tr>
<td>2</td>
<td>1122</td>
<td>Mike</td>
<td>CLEAR</td>
</tr>
<tr>
<td>3</td>
<td>1122</td>
<td>Mike</td>
<td>CLEAR</td>
</tr>
<tr>
<td>4</td>
<td>1122</td>
<td>Mike</td>
<td>HIT</td>
</tr>
<tr>
<td>5</td>
<td>1123</td>
<td>Jim</td>
<td>HIT</td>
</tr>
<tr>
<td>6</td>
<td>1124</td>
<td>James</td>
<td>CLEAR</td>
</tr>
<tr>
<td>7</td>
<td>1124</td>
<td>James</td>
<td>CLEAR</td>
</tr>
<tr>
<td>8</td>
<td>1124</td>
<td>James</td>
<td>CLEAR</td>
</tr>
</table>
What I want:
<table>
<tr>
<td>ID</td>
<td>Ref No</td>
<td>Name</td>
<td>Result</td>
</tr>
<tr>
<td>1</td>
<td>1121</td>
<td>Joseph</td>
<td>CLEAR</td>
</tr>
<tr>
<td>4</td>
<td>1122</td>
<td>Mike</td>
<td>HIT</td>
</tr>
<tr>
<td>5</td>
<td>1123</td>
<td>Jim</td>
<td>HIT</td>
</tr>
<tr>
<td>6</td>
<td>1124</td>
<td>James</td>
<td>CLEAR</td>
</tr>
</table>
Can anybody tell me how to achieve this task?
Any help would be highly appreciated.
So i made this clumsy answer for you. You can check it out in the fiddle here.
EDIT: after some discussion about what should the behaviour be, i updated the fiddle. so now it adds the check if there are any fields in the duplicates that have a "HIT" value in fourth column it will keep the first row with HIT value, otherwise it will keep the first value for each unique second column value.
I am sure there is a better/simpler/more effective way to do this with jQuery, but that is what I came up with. The basic algorithm is this: get all rows and iterate. For each row: find the value in second td (column), check all subsequent rows, fetch the value in second column there and compare them. if they are the same, remove the duplicate row from DOM.
//get the table rows, this should be done with a different selector if there are more tables e.g. with class or id...
$tableRows = $("tr");
//iterate over all elements (rows)
$tableRows.each(function(index, element) {
var $element = $(element);
//get the value of the current element
var currentRowValue = $element.find("td:nth-child(2)").text();
//check all elements that come after the current element if the value matches, if so, remove the matching element
for (var i = index + 1; i < $tableRows.length; i++) {
var $rowToCompare = $($tableRows[i]);
var valueToCompare = $rowToCompare.find("td:nth-child(2)").text();
if(valueToCompare === currentRowValue) {
//remove the duplicate from dom
//if the second row (the duplicate) has 4th column of "HIT" then keep the second row and remove the first row
var duplicateRowFourthColumnVal = $rowToCompare.find("td:nth-child(4)").text();
if(duplicateRowFourthColumnVal == "HIT") {
$element.remove();
}
else {
$rowToCompare.remove();
}
}
}
});`
Please take a look at this FIDDLE. How would you make sure it only matches the occurrence of Sodium that appear at the beginning of the line in a table cell, for example :
<td>Sodium</td>, <td>Sodium (from Kitchen Salt)</td>
but not
<td>Vitamin sodium</td>,<td>Fish Sodium</td>
My attempt
`var find_Sodium = /^Sodium/
alert($('.'+title+'table').find('td:contains(find_Sodium)').next().html());`
isn't working.
$.ajax({
url: "url.json",
success: function (data) {
$(data.query.results.json.json).each(function (index, item) {
var title = item.title;
var table = item.table;
if (table.indexOf("Sodium") >= 0) {
$('.'+ title+'table').html(''+table+'');
var find_Sodium = /^Sodium/;
alert($('.'+title+'table').find('td:contains(find_Sodium)').next().html());
}
});
},
error: function () {}
});
Table Structure:
<table class="tablesorter">
<thead>
<tr>
<td>Ingredient</td>
<td>Amount</td>
<td>% Daily Value**</td>
</tr>
</thead>
<tbody>
<tr>
<td>Calories</td>
<td>10</td>
<td></td>
</tr>
<tr>
<td>Sodium</td>
<td>2g</td>
<td><1</td>
</tr>
<tr>
<td>Vitamin C</td>
<td>110mg</td>
<td>4</td>
</tr>
<tr>
<td>Potassium sodium</td>
<td>235mg</td>
<td>6</td>
</tr>
<tr>
<td>Omega 6</td>
<td>1100mg</td>
<td>*</td>
</tr>
<tr>
<td>Vitamin Sodium</td>
<td>1200mg</td>
<td>*</td>
</tr>
<tr>
<td>Vitamin E</td>
<td>300mg</td>
<td>*</td>
</tr>
</tbody>
</table>
:contains does not accept a regex, the way to do this is to filter()
$('.'+title+'table').find('td').filter(function() {
return $(this).text().indexOf('Sodium') === 0;
}).next().html();
FIDDLE
using indexOf === 0 makes sure Sodium has an index of zero, being the first thing to occur in the elements text
I have a table in a HTML file, and I want to calculate one cell's value based on other cells' values and a cell value as a formula. Take a look at this picture:
In my table, I have 1900 ratios and three factors in three columns. How I can calculate the result column values based on their respective formula column value?
Note: I use only *, /, +, and - in my formulas. Also, the table has an ID (ratiotable), but the <tr> and <td> don't have any ID or class names assigned to them.
If you know another webpage that did something like this, please let me see this.
Thank you very much.
Edit: The HTML code is like this. Note, I snipped a number of the rows. The actual number of rows is 1900:
<table>
<thead>
<tr>
<th>Ratio Name</th>
<th>Factor1</th>
<th>Factor2</th>
<th>Factor3</th>
<th>Formula</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr>
<td>Name1</td>
<td>22</td>
<td>11</td>
<td></td>
<td>Factor1/Factor2</td>
<td></td>
</tr>
<tr>
<td>Name2</td>
<td>22</td>
<td>33</td>
<td>11</td>
<td>Factor1/Factor2*Factor3</td>
<td></td>
</tr>
<tr>
<td>Name1</td>
<td>12</td>
<td></td>
<td>4</td>
<td>(Factor1+Factor2)/Factor3</td>
<td></td>
</tr>
</tbody>
This was a really interesting challenge. I've put together a little JSFiddle here that does what you describe. It essentially replaces the string parts of the formula with their value counterparts, then evals the result. Hopefully this helps!
My Markup
<table>
<thead>
<tr>
<th>Ratio Name</th>
<th>Factor1</th>
<th>Factor2</th>
<th>Factor3</th>
<th>Formula</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr>
<td>Name1</td>
<td>22</td>
<td>11</td>
<td></td>
<td>Factor1/Factor2</td>
<td></td>
</tr>
<tr>
<td>Name2</td>
<td>22</td>
<td>33</td>
<td>11</td>
<td>Factor1/Factor2*Factor3</td>
<td></td>
</tr>
<tr>
<td>Name1</td>
<td>12</td>
<td></td>
<td>4</td>
<td>(Factor1+Factor2)/Factor3</td>
<td></td>
</tr>
</tbody>
</table>
My JavaScript
$(function() {
$('tbody tr').each(function() {
var $this = $(this),
f1 = $this.find('td').eq(1).text() || 0,
f2 = $this.find('td').eq(2).text() || 0,
f3 = $this.find('td').eq(3).text() || 0,
formula = $this.find('td').eq(4).text(),
resultTd = $this.find('td').last();
formula = formula.replace('Factor1',f1)
.replace('Factor2',f2)
.replace('Factor3',f3);
resultTd.text(eval(formula));
});
});
loop through all rows and use javascript eval() function
function cellValue(x,y){
return $('#ratiotable').find('tr:eq('+(y+1)+')>td:eq('+(x+1)+')').text();
}
function setCellValue(x,y,s){
$('#ratiotable').find('tr:eq('+(y+1)+')>td:eq('+(x+1)+')').text(s);
}
$(function(){
$.each($('#ratiotable tr'),function(i,tr){
var Factor1 = parseInt(cellValue(0,i));
var Factor2 = parseInt(cellValue(1,i));
var Factor3 = parseInt(cellValue(2,i));
var Formula = $.trim(cellValue(3,i));
var Result = eval(Formula);
setCellValue(4,i,Result);
});
});
like this