Updating the DOM with arrays JavaScript - javascript

I am having a problem when I try to update the DOM with new information coming from an API.
Every time that I click to add new users, the array displays the old, and new information. Ideally, it would update the array first and then display only the new information. I will attach a picture of what is happening. I would like to every time the user click on add new user, the DOM update with only the information of that new user.
HTML part
<table class="table is-fullwidth table is-hoverable table-info">
<thead>
<tr">
<th title="Channel Name" class="has-text-left"> Channel Name </th>
<th title="View per week" class="has-text-right"> View per week </th>
</tr>
</thead>
<tbody id="body-table">
<tr id="tr-table">
</tr>
</tbody>
</table>
script.js
const trline = document.getElementById('body-table')
let usersList = [];
async function getnewUsers(){
const res = await fetch('https://randomuser.me/api')
const data = await res.json()
// create an instance of the results
const user = data.results[0]
// create the new user
const newUser = {
name:`${user.name.first} ${user.name.last}`,
social: Math.floor(Math.random() * 10000 )
}
// update the new user to the database...
addData(newUser)
}
function addData(obj) {
usersList.push(obj)
// update the information on the screen
updateDOM()
}
function updateDOM( providedData = usersList){
providedData.forEach(item => {
const element = document.createElement('tr')
element.innerHTML = `
<td class="has-text-left cname"> ${item.name} </td>
<td class="has-text-right cview"> ${item.social} k</td>
`
trline.appendChild(element)
})
}
addUser.addEventListener('click', getnewUsers)
Result picture:

I found the problem and the solution.
I didn't reset the HTML part to clear before adding a new item. I had to fix the function updateDOM with this: trline.innerHTML = ''
After that, the function works fine.
function updateDOM( providedData = usersList){
trline.innerHTML = '' // clear everything before adding new stuff
providedData.forEach(item => {
const element = document.createElement('tr')
element.innerHTML = `
<td class="has-text-left cname"> ${item.name} </td>
<td class="has-text-right cview"> ${item.social} k</td>
`
trline.appendChild(element)
})
}

Related

How to search/find a words/data (in this case "Country ") from a table ( api data) with a search box?

I want to search country names with that text box, find the specific data from that massive list of names, and show that the only data to the user, not all the data. Please help me to do that. I have no idea how to do this.
// api section
const tbody = document.querySelector('#tbody');
const getdata = async () => {
const endpoint = "https://api.covid19api.com/summary",
response = await fetch(endpoint),
data = await response.json(),
Countries = data.Countries;
Countries.forEach(countryObj => {
let { Country, NewConfirmed, TotalConfirmed, NewDeaths, TotalDeaths, NewRecovered, TotalRecovered, Date } = countryObj;
tbody.innerHTML += `<tr>
<td>${Country}</td>
<td>${NewConfirmed}</td>
<td>${TotalConfirmed}</td>
<td>${NewDeaths}</td>
<td>${TotalDeaths}</td>
<td>${NewRecovered}</td>
<td>${TotalRecovered}</td>
<td>${Date}</td>
</tr>`;
});
}
getdata();
<---------------------------- search box function------------->
// Don't know how to do it.....help me ....Thanks in advance :)
<!--------------search Box & search button ------- -->
<input type="text" id="myInput" placeholder=" Search Country " >
<input type="submit" value="Search" class="submit">
<!----------------data table--------------- -->
<table class="table">
<thead>
<tr>
<th scope="col">Country</th>
<th scope="col">NewConfirmed</th>
<th scope="col">TotalConfirmed</th>
<th scope="col">NewDeaths</th>
<th scope="col">TotalDeaths</th>
<th scope="col">NewRecovered</th>
<th scope="col">TotalRecovered</th>
<th scope="col">Last Updated on</th>
</tr>
</thead>
<tbody id="tbody">
</tbody>
</table>
I want to search country names with that text box, find the specific data from that massive list of names, and show that the only data to the user, not all the data. Please help me to do that.
The only thing that is actually needed here is a way to 'search' the data and display those results. This can be done using the filter() method for an array.
Essentially, you just need to store your data in a global variable that can be filtered later, based on user input. Also, I usually make it a point to separate certain functionality, like displaying data. So instead of displaying the country data inside of the getData() function, I would create a separate function that just filters and displays data. This way you can call it after you fetch the data, and then call that same function each time you search (rather than have repeated code that displays countries in the table).
let countriesData = [];
const getdata = async () => {
const endpoint = "https://api.covid19api.com/summary",
response = await fetch(endpoint),
data = await response.json();
countriesData = data.Countries;
_DisplayCountries();
}
const _DisplayCountries = (c = "") => {
let tbody = document.querySelector("#tbody");
tbody.innerHTML = ``;
countriesData.filter(country => country.Country.toLowerCase().includes(c.toLowerCase())).forEach(result => {
tbody.innerHTML += `<tr>
<td>${result.Country}</td>
<td>${result.NewConfirmed}</td>
<td>${result.TotalConfirmed}</td>
<td>${result.NewDeaths}</td>
<td>${result.TotalDeaths}</td>
<td>${result.NewRecovered}</td>
<td>${result.TotalRecovered}</td>
<td>${result.Date}</td>
</tr>`;
});
}
getdata();
document.querySelector("#mySubmit").addEventListener("click", e => {
_DisplayCountries(document.querySelector("#myInput").value);
});
<input type="text" id="myInput" placeholder=" Search Country ">
<input type="submit" id="mySubmit" value="Search" class="submit">
<table class="table">
<thead>
<tr>
<th scope="col">Country</th>
<th scope="col">NewConfirmed</th>
<th scope="col">TotalConfirmed</th>
<th scope="col">NewDeaths</th>
<th scope="col">TotalDeaths</th>
<th scope="col">NewRecovered</th>
<th scope="col">TotalRecovered</th>
<th scope="col">Last Updated on</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
NOTES
There are definitely a lot of different ways to filter() the data and I opted for a very simple includes() method. I also added toLowerCase() to the country and the search string to make it case-insensitive, but I understand there are several ways to do this as well.
let regex = new RegExp(c, "i");
countriesData.filter(country => country.Country.match(regex))
That for example would also return a list of search results that are case-insensitive.

React updating state needs 2 clicks in order to update

Let me start off by saying that I did look for other topics but I haven't found a solution yet so I'd like to walk through this with you guys.
I have a simple website with a search bar, a search result list and a div where I display the item I click in the result list.
The issue starts when I click an item in the results list. I need to click it twice for it to update the div where I display the item.
What happens when I click an item from the search results list:
const getProductById = (store) => {
if (store == "STORENAME") {
Axios.get(
`http://localhost:3001/get-storename-product-by-id/${productId}`
).then((response) => {
console.log(response.data);
setProductResultList(response.data);
setProductTitle(response.data[0].title);
setProductImg(response.data[0].imageSrc);
setFinalProductId(response.data[0].productId);
});
} else {
Axios.get(`http://localhost:3001/get-storename-product-by-id/${productId}`).then(
(response) => {
console.log(response.data);
setProductResultList(response.data);
setProductTitle(response.data[0].title);
setProductImg(response.data[0].imageSrc);
setFinalProductId(response.data[0].productId);
}
);
}
};
The function fetches all the data linked to the productId of the clicked product (this returns all the historic data I have on the item in an array with objects (1 object for each row)).
How I show the item on the page:
<div className="item">
<div>
<img
src={productImg}
alt={productTitle}
width="250px"
height="250px"
/>
<p className="product-id-span">
Product Id: {finalProductId}
</p>
<p className="m-0">Product name:</p>
<p>{productTitle}</p>
<div className="historical-info">
<span>latest prices:</span>
<div className="table-responsive">
<table class="table text-white">
<thead>
<tr>
<th scope="col">Price</th>
<th scope="col">Date</th>
</tr>
</thead>
<tbody>
{productResultList.map((val, key) => {
let parsedPrice = parseInt(val.price);
let parsedPriceInEuros = parsedPrice / 100;
const finalPrice = new Intl.NumberFormat(
"de-DE",
{ style: "currency", currency: "EUR" }
).format(parsedPriceInEuros);
return (
<tr>
<td>
Price:
{val.store == "STORENAME"
? finalPrice
: val.price}
</td>
<td>{val.date}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</div>
</div>
What I've tried:
I tried to only set state to the productResultList in the getProductById function, and set
the other state when the useEffects note changes to the productResultList.
useEffect(() => {
setProductTitle(productResultList[0].title);
setProductImg(productResultList[0].imageSrc);
setFinalProductId(productResultList[0].productId);
}, [productResultList]);
Could someone perhaps explain what I'm doing wrong or what's the right way to do this?
Note:
I changed the real store names to STORENAME because it's not neccessary in this example.
resultList = the list with results when I search, productResultList is the list with objects of the clicked product.
So the solution to this problem was fairly different to what I expected.
The function that initializes getProductById() sets state to productId first so I can use that in my request as you can see.
Because setting state is asynchronious, the productId was not available on the first request.
I fixed this by passing the productId as a parameter to the getProductById function so it does not have to wait for the state to be changed.

Grab the classname of nested HTML elements with Puppeteer

I am trying to get get all of the td elements that have a class name of calendarCellRegularPast I have tried multiple attempts with page.$, page.$$, page.eval and many others and cannot seem to get the proper element. I got the object at on point but was unable to parse it. I know that there is a way to do this but I am new to puppeteer and Javascript honestly and just cannot figure it out.
This is a calendar of scheduled work days and I am trying to get even further down and grab the date and time. I want to have that in a for loop though so I can grab all of the work times.
I don't need a fix to my code as I know its messy but I just need to know how to grab those elements
Here is some of my code so far, though it is messy as I was trying numerous things
const el = await page.$("#scrollContainer > table > tbody > tr:nth-child(2) > td:nth-child(1)")
const className = await el.getProperty('className')
const getElm = async() => {
try {
const elm = await page.evaluate(()=> {
let persons = [];
const weekElm = document.querySelectorAll("#scrollContainer > table > tbody");
document.querySelector("#scrollContainer > table > tbody > tr:nth-child(2) > td:nth-child(3)")
console.log('\n\n\n', weekElm.length, '\n\n\n');
try {
console.log(weekElm);
} catch (e) {
console.error('could not log "weeksElm"');
}
for (let i = 2; i < weekElm.length; i++) {
try {
const trd = weekElm[i];
console.log(trd);
const tr = document.querySelectorAll(`tr:nth-child${i+1}`)
persons.push(tr);
try {
console.log('\n\n\n', tr, '\n\n\n');
} catch (e) {
console.error('could not log "\\n\\n\\n, tr, \\n\\n\\n"');
}
for ( let j = 0; j < tr.length; i++) {
const td = (tr.querySelectorAll(`td:nth-child${j}`))
try {
pass
} catch (e) {
console.error('could not log "weeksElm"');
}
}
} catch (e) {}
}
})
console.log(persons);
} catch (e){
console.error("unable to get the work schedule \n unfortunately i still work");
}
}
}
Firstly, I would recommend using a http client and cheerio.js for scraping, as it's faster and more lightweight. But I can understand why you would use puppeteer because of authentication.
You can get the html of the page using puppeteer by using page.content() and then pass that into cheerio. But if you wanted to do it using a http client, it would look like this:
const axios = require("axios");
const cheerio = require("cheerio");
const PAGEURL = "example.com";
function getData() {
return new Promise((resolve, reject) => {
axios.get(PAGEURL).then(res => res.data).then(HTML => {
const $ = cheerio.load(HTML);
// scrape data here
resolve()
}).catch(err => reject(err));
})
}
First, here's a mock-up of your HTML:
<table class="etmSchedualTable">
<tbody>
<tr>
<td class="calanderCellRegularPast">
<table class="etmCursor">
<tbody>
<tr>
<td>
<span class="calanderDateNormal"> 01</span>
</td>
<td>
<span class="calanderCellRegularPast etmNoBorder">
<span>17:15</span> " - "
<span>23:45</span>
<span class="etmMoreLink">more...</span>
</span>
</td>
</tr>
<tr>
<td>
<span class="calanderDateNormal"> 02</span>
</td>
<td>
<span class="calanderCellRegularPast etmNoBorder">
<span>17:15</span> " - "
<span>23:45</span>
<span class="etmMoreLink">more...</span>
</span>
</td>
</tr>
</tbody>
</table>
</td>
<tr>
</tbody>
</table>
At the end, we want an array with objects that look like this:
{
date: 1,
start: "17:45",
end: "21:15"
}
To do so, we first need to select the parent that holds all the items we want. In this case it's $$(".etmCursor tbody tr"). This will give use a list of all the tr's in the table .etmCursor.
Now we have to loop through table rows (tr) and get the object properties.
const timetable = $(".etmCursor tbody tr").map((i, elm) => {
return {
date: $(elm).find(".calendarDateNormal").text(),
start: $(elm).find(".CalendarCellRegularPast:nth-child(1)").text(),
end: $(elm).find(".CalendarCellRegularPast:nth-child(2)").text(),
}
}).toArray();
The .map() method works in reverse to a normal map, where you get the element before the index. And since it uses a collection, we have to use toArray() to turn it to an array.
We use the .find() to do a search on the selected object.
.text() then gets use the text of that element.
Now we should have the data you wanted.

Verify table Row's data attribute with keys in a dictionary

What I'm trying to do here, is, for every row in the table, I want to verify the row's data attribute (Note that the data attribute of this row is the socket One of the keys in the clientel dictionary previously made) with the keys in the dictionary clientel. If both match, do nothing. If the key is in the row but not in the dictionary, perform a function and if there's a key in the dictionary but it's not in a row, then add that row.
let clientel = {
socket101: ['Rick', '192.590.49.1', 'Win10', 'Norway', '15:49.00'],
socket102: ['Anthony', '192.90.897.0', 'Win7', 'Negritine', '19:19:38']
};
function man_table() {
const table = document.getElementById('table-body');
for(let i in clientel) {
for(let ih = 0, row; row = table.rows[ih]; ih++) {
ass = row.getAttribute('data');
if (ass in clientel) {}
else if (!(ass in clientel)) {table.deleteRow(ih); continue;}
else if (clientel[i] !== ass) {
let row = table.insertRow(i);
let client = clientel[i];
row.setAttribute('data', i);
let name = row.insertCell(0);
let ip = row.insertCell(1);
let os = row.insertCell(2);
let country = row.insertCell(3);
let timee = row.insertCell(4);
name.innerHTML = client[0];
ip.innerHTML = client[1];
os.innerHTML = client[2];
country.innerHTML = client[3];
timee.innerHTML = client[4];
}
}
}
}
Why doesn't this add the tables and
Is there a better way to do this?
Example of the HTML table (On Request):
<div id="table">
<table>
<thead>
<tr>
<th>Name</th>
<th>IP</th>
<th>OS</th>
<th>Country</th>
<th>Connected Since</th>
</tr>
</thead>
<tbody id="table-body">
<tr>
<td>Rick</td>
<td>192.423.41.5</td>
<td>Win 7</td>
<td>Bulgaria</td>
<td>A few moments</td>
</tr>
</tbody>
</table>
</div>
Don't try and do too many things at the same time. Adding rows in the same loop where you're deleting rows is going to cause confusion.
Notice that your HTML does not actually have the data attributes on the TR elements, so your code will never match any rows. Also, trying to this: let row = table.insertRow(i); will fail because i is a string ("socket101" etc)
The delete first looks for rows that don't have a corresponding entry in the clientel dictionary. [...table.rows] converts the HTMLCollection into an array so that filter can be used, which simply returns the entry from the dictionary matching it's data attribute. This will be null for any row that doesn't have an entry.
Once we have a list of rows that don't have matching clients remove the rows. Find the index of the row by deconstructing the row ( .forEach({rowIndex}) => foo(rowIndex) has the same effect as .forEach(row) => foo(row.rowIndex) ), and then delete the row (remembering to account for the table header row).
Adding the new row is about the same as the delete. The .map( (key, index) ) => [ key, index ] ) is used to preserve the index of each client so the row can be added in the correct place later. The filter is similar as the delete but instead of including things that exist, it includes anything that doesn't exist. This depends on null being effectively the same as false (i.e !null evaluates as true). Adding rows is done by using HTML, which is faster than creating elements/nodes individually.
let clientel = {
socket101: ['Rick', '192.590.49.1', 'Win10', 'Norway', '15:49.00'],
socket102: ['Anthony', '192.90.897.0', 'Win7', 'Negritine', '19:19:38']
};
function init() {
const table = document.getElementById('table-body');
// remove anything that doesn't have a row in the clientel map
[...table.rows].filter( (row) => !clientel[row.getAttribute('data')] )
.forEach( ({rowIndex}) => table.deleteRow(rowIndex - 1) )
// add anything that doesn't exist in the table
Object.keys(clientel)
.map( (key, index) => [ key, index ] )
.filter( ([key, index]) => !table.querySelector(`tr[data="${key}"]`) )
.forEach( ([key, index]) => {
var row = table.insertRow(index)
row.setAttribute('data', key);
row.innerHTML = clientel[key].map( value => `<td>${value}</td>` ).join("");
});
}
document.addEventListener("DOMContentLoaded", init);
</script>
<div id="table">
<table>
<thead>
<tr>
<th>Name</th>
<th>IP</th>
<th>OS</th>
<th>Country</th>
<th>Connected Since</th>
</tr>
</thead>
<tbody id="table-body">
<tr data="socket101">
<td>Rick</td>
<td>192.423.41.5</td>
<td>Win 7</td>
<td>Bulgaria</td>
<td>A few moments</td>
</tr>
<tr data="socket103">
<td>Whoever</td>
<td>127.0.0.1</td>
<td>OS/1</td>
<td>The Moon</td>
<td>Whatever</td>
</tr>
</tbody>
</table>
</div>
</body>

Mapping table children content with puppeteer

My goal is to fetch .textContent from different <td> tags, each lying within a separate <tr>.
I think the problem lies within the table variable, as I am not checking the correct variable for children. Currently, data variable is only fetching the first <tr>, so price evaluates with this code. However, volume and turnover does not. I think it is a simple fix but I just can't figure it out!
JavaScript:
try {
const tradingData = await page.evaluate(() => {
let table = document.querySelector("#trading-data tbody");
let tableData = Array.from(table.children);
let data = tableData.map(tradeData => {
console.log(tradeData);
let price = tradeData.querySelector(".quoteapi-price").textContent;
console.log(price);
let volume = tradeData.querySelector("quoteapi-volume").textContent;
console.log(volume);
let turnover = tradeData.querySelector("quoteapi-value").textContent;
console.log(turnover);
return { price, volume, turnover };
})
return data;
});
console.log(tradingData);
} catch (err) {
console.log(err);
}
HTML:
<table id="trading-data" class="qq_table">
<tbody>
<tr class="qq_tr_border_bot">
<td>Price</td>
<td class="qq_td_right quoteapi-number quoteapi-price" data-quoteapi="price">$0.105</td>
</tr>
<tr class="qq_tr_border_bot">
<td>Change</td>
<td class="qq_td_right pos" data-quoteapi="changeSignCSS">
<span data-quoteapi="change (signed)" class="quoteapi-number quoteapi-price quoteapi-change">0.005</span>
<span data-quoteapi="pctChange (pct)" class="quoteapi-number quoteapi-pct-change">(5.00%)</span>
</td>
</tr>
<tr class="qq_tr_border_bot">
<td>Volume</td>
<td class="qq_td_right quoteapi-number quoteapi-volume" data-quoteapi="volume scale=false">5,119,162</td>
</tr>
<tr>
<td>Turnover</td>
<td class="qq_td_right quoteapi-number quoteapi-value" data-quoteapi="value scale=false">$540,173</td>
</tr>
</tbody>
</table>
For example, this should return price="$0.11", volume="3,900,558", turnover="$412,187"
You only need the map function when you are expecting multiple tables or tbodies. As this seems not to be the case in your example, you can do it like this:
const tradingData = await page.evaluate(() => {
let table = document.querySelector("#trading-data tbody");
let price = table.querySelector(".quoteapi-price").textContent;
let volume = table.querySelector(".quoteapi-volume").textContent;
let turnover = table.querySelector(".quoteapi-value").textContent;
return { price, volume, turnover };
});
console.log(tradingData);

Categories