How to copy a nodelist? - javascript

I am trying to set up a search feature for a large table that has about 1000 rows. The problem with this is that the rendering eats up performance by a lot. This is because I am iterating through all the rows in the table and setting the style of the ones that do not contain the search query to 'none'.
For some reason, the browser renders all the elements again each time I make a change. I am getting the table data as a nodelist via document.quesrtySelectorAll('tbody tr'). My solution is to copy this to a new object, do the search and style changes on the new object, and then copy it back, causing the browser to only have to re-render the table once.
let items = document.querySelectorAll('tbody tr');
let itemsArr = [...items];
for (let ele of itemsArr) {
. . .
if (!lower_case_table_value.match(lower_case_search_value)) {
ele.style.display = 'none';
}
else {
ele.style.display = 'table-row';
}
}
let list = document.querySelector('tbody');
for (let i = 0; i < items.length; i++) {
list.appendChild(itemsArr[i]);
}
No matter what I do, though, any changes I make on the new object also makes the changes to the old. I removed the second for loop and it behaves like nothing happened. It is like it is not a copy, but a pointer.
Does anybody know who to copy a nodelist to a new object and make sure they are separate and not pointers to one another?

just tumbled on the same problem today and found that you can copy a DOM node element with the method "node.cloneNode" e.g.:
let p = document.getElementById("para1")
let p_prime = p.cloneNode(true)
I took the snippet from the MDN documentation that you can find here:
https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode
most probably you already found the solution for this, but anyway, I leave this here for the record.

let items = document.querySelectorAll('tbody tr');
let itemsArr = [...items];
here items is a nodeList
so itemsArray stores also a nodeList
now if you try to iterate over itemsArray
there is only one thing in there which is the nodeList you put in there
insted if you add spread items into itemsArray you'll be storing elements not the entire odeList

if you want to get a new Array instead of a chilNode
use Array.from(nodeToCopy)
this returns a new Array instead of a child node

Related

How to get products name insade all arrray?

enter image description hereı want to set custom event with gtm but ı dont know how to get multple producs name . ı can get one of the items name but ı can not get all of them.
document.getElementsByClassName('cartAndCheckout__items')[0].innerText.split('\nStokta var\n3.329,00 TL')[0]
thats the code ı wrote
Seems you just need to iterate the elements that you find via document.getElementsByClassName
let products = []
let elements = document.getElementsByClassName('cartAndCheckout__items')
for(let i=0; i<elements.length; i++){
//products.push(elements[i].innerText)
//based on your comment,if you just want to get the name
let link = elements[i].querySelector('div.mb-2 a')
let title = link.getAttribute("title")
products.push(title)
}
document.getElementsByClassName('cartAndCheckout__items') returns a collection; it looks and mostly works like an array, even though it isn't an Array. You're getting the first item with [0], but you can also for instance loop over all the items:
let products = []
for (const element of document.getElementsByClassName('cartAndCheckout__items')) {
products.push(element.innerText.split('\nStokta var\n3.329,00 TL')[0])
}
Usually if you're building one list from another it's better to use map, but as the collection isn't actually an Array, you have to call it in a roundabout way:
const elements = document.getElementsByClassName('cartAndCheckout__items')
const products = Array.prototype.map.call(elements,
e => e.innerText.split('\nStokta var\n3.329,00 TL')[0])
Please note that ES6 is not supported in GTM, thus you either need to translate the above solution (which is basicly replace "const" and "let" with var :D) or implement the JavaScript natively and push the result to the DataLayer.

How to validate a selectors without running the entire test from beginning

I was wonder if someone found an easy way to verify a selector work properly with the TestCafe syntax without running the entire test over and over?
E.g:
I want to verify that the last table column cells has all the values in a array I provide:
in order to do I try to detect the last column but for validation, I have to execute the entire testcafe test from start.
How do you recommend approaching this scenario?
The following snippet I wrote to verify I catch all the right column cells, but it took me a while because each time I had to run the test again.
console.log(`total rows: ${await (Selector('tbody > tr').find('td:nth-of-type(3)')).count}`);
Another thing, how I save to array the most right column cells?
Source:
Stay tuned to Selector Debug Panel feature.
As for your 'save to array the most right column cells' question, selector provides methods and properties to select elements on the page and get their state, but has no 'rows' and 'columns' properties.
So, you can use the following approach to iterate over your table cells:
const table = Selector('#table');
const rowCount = await table.find('tr').count;
const columnCount = await table.find('tr').nth(0).find('td').count;
for(let i = 0; i < rowCount; i++) {
for(let j = 0; j < columnCount; j++) {
let tdText = await table.find('tr').nth(i).find('td').nth(j).textContent;
}
}
and add required cells to an array inside the loop

Error: Cannot read property 'replaceChild' of undefined

I am trying to update a table to display the new row when I save a record. My code currently loads the entire table again along with the existing table. For example, I'm seeing rows 1-15, then I add a row and see 31 total rows, rows 1-15 and then rows 1-16 all in the same table.
Here is my code for the replace function which calls my original load function when it's time to replace the data. I based this off another example I saw.
function replaceData() {
var old_tbody = document.getElementsByTagName('tbody');
var new_tbody = document.createElement('tbody');
loadData(new_tbody);
old_tbody.parentNode.replaceChild(new_tbody,old_tbody);
console.log("Data replaced");
}
I'm clearly not a JavaScript expert so any help would be appreciated.
getElementsByTagName function returns a HTML Collection (array-like object), that means that you have to specify which item of that array you want (even if there is only one item).
So your code basically should be:
var old_tbody = document.getElementsByTagName('tbody')[0];
Or you can use querySelector which returns only the first matched element. Like this:
var old_tbody = document.querySelector('tbody');
getElementsByTagName actually returns a collection of elements, so parentNode
doesn't work. To get the actual DOM node you would do the following.
var old_tbody = document.getElementsByTagName('tbody')[0];

How to get a node by index in ag-grid?

AgGrid expects node(s) to be passed in to lot of it's data functions. How do you get a node by index? Look at the snip below:
api.forEachNode(function(node){
api.refreshRows([node]);
})
I can pass the node parameter to refreshRows() function since I'm getting it through forEachNode().
How do you get a node by index without iterating through forEachNode() ?
This might be a bit late for this question, but anyway for people who are searching for this in the future :
Apart from the answers given, you can also get the row node by using the following ways,
// Getting the row node by the row index
cont rowNode1 = api.getDisplayedRowAtIndex(rowIndex);
In some cases, the above approach is not suitable because the rowIndex can get changed when you do some changes to your grid (sort, filter, etc.).
The other method is to use the id of the row which will not change even if you sort, filter... the grid.
getRowNode(id) : Returns the row node with the given ID. The row node id is the one you provided with the callback getRowNodeId(data), otherwise, the id is a number auto-generated by the grid when the row data is set.
// Getting rowNode by row id
const rowNode2 = api.getRowNode(rowId);
You can use getVirtualRow() method to get a single row. This function is a part of the Row Model. You can get the Row Model by getModel() function.
var model = api.getModel();
console.log(model.getVirtualRow(idx));
Building on #Charlie H's answer, it's entirely possible that since the version that he was using, the API has changed a bit. I'm using the (current as of December 2017) version 15.0. I found that rowsToDisplay[] contains an array of rows accessible. The following, for example, does exactly what you'd think it would:
onCellEditingStarted: function (event) {
var displayModel = gridOptions.api.getModel();
var rowNode = displayModel.rowsToDisplay[event.rowIndex];
rowNode.setRowHeight(100);
gridOptions.api.onRowHeightChanged();
},
If you want to iterate through the grid row by row in order you can use this (where $scope.grid is your grid name):
$scope.high_index = 0;
$scope.rows = [];
$scope.grid.api.forEachNode(function (node) {
$scope.rows[node.childIndex] = node.id;
if (node.childIndex > $scope.high_index)
$scope.high_index = node.childIndex;
});
for (i = 0; i <= $scope.high_index; i++) {
var node = $scope.grid.api.getRowNode($scope.rows[i]);
}
Or if you want a row node by child index you can now use (after setting up $scope.rows above):
var node = $scope.grid.api.getRowNode($scope.rows[i]);
Where i is the row number you want.

Dynamic id creation with Javascript

I am creating a table that changes size based on a javascript array. I am modeling reservations and when the page loads it loops through an array of reservation objects and creates a table to represent them. I have a checkbox created as the first element of each row as such:
checkboxCell.innerHTML = "<input type='checkbox'>";
I want to be able to know which row(or checkbox) I am clicking when they are pressed. Since these are being creating in one line, they would all have the same ID. So I either need a way to know where I am in the table or a way to create a different ID for each of these elements.
You can generate incremental ID's for your new elements within the loop which is creating the new HTML elements, e.g. assuming you're appending inputs to an element with an ID of parent-element:
function makeElements(){
var parent = document.getElementById('parent-element');
for (var i = 1; i < 11; i++) {
var newElement = document.createElement('input');
newElement.id = 'input-number-' + i;
newElement.type = "checkbox";
parent.appendChild(newElement);
}
}
As commented by connexo however you are probably better off using an existing library to take care of this sort of thing.

Categories

Resources