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 comparing two arrays of objects. Unfortunately, I didn't come 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 outcome 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;
}
You 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>
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>
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
},[])
Not that elegant, but to me slightly more comprehensive (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>
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.