It's well known, that 'array of objects' format of data storage is well suitable for data persisting. I'd be extremely grateful if a JavaScript guru helps me with finding the answer to how do I read this HTML-table with vanilla JavaScript and transport data from it into the following array of objects.
I have produced tons of code, mostly paring two arrays of objects. Unfortunately, I didn't e even closer to a solution.
The table to scrape data from:
<table>
<tbody>
<tr>
<td colspan="3">Canada</td>
</tr>
<tr>
<td>Toronto</td>
<td>Montreal</td>
<td>Vancouver</td>
</tr>
<tr>
<td colspan="3">USA</td>
</tr>
<tr>
<td>New York</td>
<td>Chicago</td>
<td>Boston</td>
</tr>
<tr>
<td>Washington</td>
<td>Detroit</td>
<td>Los Angeles</td>
</tr>
</tbody>
</table>
Expected oute to be like so:
[
{"country":"Canada","city":"Toronto"},
{"country":"Canada","city":"Montreal"},
{"country":"Canada","city":"Vancouver"},
{"country":"USA","city":"New York"},
{"country":"USA","city":"Chicago"},
{"country":"USA","city":"Boston"},
{"country":"USA","city":"Washington"},
{"country":"USA","city":"Detroit"},
{"country":"USA","city":"Los Angeles"}
]
The code is valid, unlike the approach:
let theResult = [];
arrayOfCountriesAndCitiesObjects.forEach((item, iIndex) => {
arrayOfCitiesObjects.forEach((elem, eIndex) => {
if(item.city !== elem.city && item.iIndex < elem.eIndex) theResult.push(copy(elem, item));
});
});
function copy(firstObj) {
for (let i = 1; i < arguments.length; i++) {
let arg = arguments[i];
for (let key in arg) {
firstObj[key] = arg[key];
}
}
return firstObj;
}
It's well known, that 'array of objects' format of data storage is well suitable for data persisting. I'd be extremely grateful if a JavaScript guru helps me with finding the answer to how do I read this HTML-table with vanilla JavaScript and transport data from it into the following array of objects.
I have produced tons of code, mostly paring two arrays of objects. Unfortunately, I didn't e even closer to a solution.
The table to scrape data from:
<table>
<tbody>
<tr>
<td colspan="3">Canada</td>
</tr>
<tr>
<td>Toronto</td>
<td>Montreal</td>
<td>Vancouver</td>
</tr>
<tr>
<td colspan="3">USA</td>
</tr>
<tr>
<td>New York</td>
<td>Chicago</td>
<td>Boston</td>
</tr>
<tr>
<td>Washington</td>
<td>Detroit</td>
<td>Los Angeles</td>
</tr>
</tbody>
</table>
Expected oute to be like so:
[
{"country":"Canada","city":"Toronto"},
{"country":"Canada","city":"Montreal"},
{"country":"Canada","city":"Vancouver"},
{"country":"USA","city":"New York"},
{"country":"USA","city":"Chicago"},
{"country":"USA","city":"Boston"},
{"country":"USA","city":"Washington"},
{"country":"USA","city":"Detroit"},
{"country":"USA","city":"Los Angeles"}
]
The code is valid, unlike the approach:
let theResult = [];
arrayOfCountriesAndCitiesObjects.forEach((item, iIndex) => {
arrayOfCitiesObjects.forEach((elem, eIndex) => {
if(item.city !== elem.city && item.iIndex < elem.eIndex) theResult.push(copy(elem, item));
});
});
function copy(firstObj) {
for (let i = 1; i < arguments.length; i++) {
let arg = arguments[i];
for (let key in arg) {
firstObj[key] = arg[key];
}
}
return firstObj;
}
Share
Improve this question
asked May 6, 2019 at 12:48
Maksym DudykMaksym Dudyk
1,1731 gold badge16 silver badges18 bronze badges
4
- 1 is it possible to change the markup for you? i.e. adding css classes to that table rows? – messerbill Commented May 6, 2019 at 12:53
- Absolutely, actually, I have simplified the table, the true one is full of css' classes around both countries and cities. – Maksym Dudyk Commented May 6, 2019 at 13:08
- does @Nina Scholz's answer fit your needs? Otherwise i can provide one using some class-selector logic – messerbill Commented May 6, 2019 at 13:13
-
@MaksymDudyk : If you happen to deal with much larger data and performance considerations do matter, you might want to check out my answer below as it gives you certain advantage in that regard, while
for
loop solution can be much faster on a small input table, though. – Yevhen Horbunkov Commented May 7, 2019 at 4:24
6 Answers
Reset to default 3You could store the value of colSpan === 3
as country and push all other values as city to the result set.
This works with plain Javascript without any libraries.
var result = [],
country = '';
document
.querySelectorAll('table td')
.forEach(td => {
if (td.colSpan === 3) {
country = td.innerHTML;
return;
}
result.push({ country, city: td.innerHTML.trim() });
});
console.log(result);
<table>
<tbody>
<tr>
<td colspan="3">Canada</td>
</tr>
<tr>
<td>Toronto</td>
<td>Montreal</td>
<td>Vancouver</td>
</tr>
<tr>
<td colspan="3">USA</td>
</tr>
<tr>
<td>New York</td>
<td>Chicago</td>
<td>Boston</td>
</tr>
<tr>
<td>Washington</td>
<td>Detroit</td>
<td>Los Angeles</td>
</tr>
</tbody>
</table>
You can use for
to loop thru each tr
. Find the td
on each tr
, If there is only 1, store the text on currentCountry
variable. If more than one, push the object to the result variable.
var currentCountry = "";
var result = [];
var tr = document.querySelectorAll('table tr');
for (var i = 0; i < tr.length; i++) {
var td = tr[i].querySelectorAll('td');
if (td.length === 1) currentCountry = td[0].innerHTML;
else if (td.length > 1) {
for (var a = 0; a < td.length; a++) {
result.push({country: currentCountry,city: td[a].innerHTML});
}
}
}
console.log(result);
<table>
<tbody>
<tr>
<td colspan="3">Canada</td>
</tr>
<tr>
<td>Toronto</td>
<td>Montreal</td>
<td>Vancouver</td>
</tr>
<tr>
<td colspan="3">USA</td>
</tr>
<tr>
<td>New York</td>
<td>Chicago</td>
<td>Boston</td>
</tr>
<tr>
<td>Washington</td>
<td>Detroit</td>
<td>Los Angeles</td>
</tr>
</tbody>
</table>
var country = null, result = [];
var tds = Array.from(document.querySelectorAll("#myTable tbody tr td"));
for (var i = 0; i < tds.length; i++) {
let item = tds[i];
if (item.getAttribute("colspan") == "3") {
country = item.innerText;
continue;
}
result.push({ country: country, city: item.innerText });
}
console.log(result);
<table id="myTable">
<tbody>
<tr>
<td colspan="3">Canada</td>
</tr>
<tr>
<td>Toronto</td>
<td>Montreal</td>
<td>Vancouver</td>
</tr>
<tr>
<td colspan="3">USA</td>
</tr>
<tr>
<td>New York</td>
<td>Chicago</td>
<td>Boston</td>
</tr>
<tr>
<td>Washington</td>
<td>Detroit</td>
<td>Los Angeles</td>
</tr>
</tbody>
</table>
Using reduce
const items = document.querySelectorAll('table tbody td')
const results = [...items].reduce((allItems, item)=>{
if(item.getAttribute('colspan') === '3'){
allItems['country'] = item.textContent
return allItems
}
allItems.push({country: allItems['country'],city:item.textContent})
return allItems
},[])
You need to assign all <tr>
which contain country names a special class. Then use querySelectorAll
and use forEach
loop.
const tr = document.querySelectorAll('tr');
const arr = []
let count = '';
tr.forEach(x => {
if(x.classList.contains('head')){
count = x.children[0].innerHTML
}
else{
let child = [...x.querySelectorAll('td')]
arr.push(...child.map(a => ({country:count,city:a.innerHTML})))
}
})
console.log(arr)
<table>
<tbody>
<tr class="head">
<td colspan="3">Canada</td>
</tr>
<tr>
<td>Toronto</td>
<td>Montreal</td>
<td>Vancouver</td>
</tr>
<tr class="head" >
<td colspan="3">USA</td>
</tr>
<tr>
<td>New York</td>
<td>Chicago</td>
<td>Boston</td>
</tr>
<tr>
<td>Washington</td>
<td>Detroit</td>
<td>Los Angeles</td>
</tr>
</tbody>
</table>
Not that elegant, but to me slightly more prehensive (while being the fastest for larger input data samples) reduce()
solution:
const result = [...document.getElementsByTagName('td')].reduce((res, item) => (item.getAttribute('colspan') == 3 ? res.country = item.textContent : res.obj = [...(res.obj || []), {country: res.country, city: item.textContent}], res), {}).obj;
console.log(result);
<table>
<tbody>
<tr>
<td colspan="3">Canada</td>
</tr>
<tr>
<td>Toronto</td>
<td>Montreal</td>
<td>Vancouver</td>
</tr>
<tr>
<td colspan="3">USA</td>
</tr>
<tr>
<td>New York</td>
<td>Chicago</td>
<td>Boston</td>
</tr>
<tr>
<td>Washington</td>
<td>Detroit</td>
<td>Los Angeles</td>
</tr>
</tbody>
</table>