Preventing DOMSubtreeModified event in Chrome from firing with every change in JS - javascript

everyone. There's an issue with Chrome (I don't know if it happens with other browsers as well). The situation is with a website that is a few years old. Was developped with standard JavaScript, not jQuery. There are thousands of codelines, so updating it now is not an option.
The thing is, this reads data from a database at the server, which returns some times 10 rows, some times over 500. That is the normal operation. In any case, the request is done via simple AJAX, and it returns an XML. The database responds in less than a second, the formatting of the XML is also in a second, maybe two on a slow day. But when JavaScript receives it back, that's the problem. With 10 elements in the XML there's no problem, but when it's 500, I want to kill myself.
After days of debugging and trying to find the problem, I finally did, but don't know how to fix it. The problem is that when the XML returns, every element is displayed in a simple <table>, meaning every node is basically a <tr> and every node's child is a <td> and <input type="text">. In the end, no matter how many rows it has, the table has 7 columns.
So here's an example of what it does. It simply goes through the nodes and creates the rows and cells with their input fields:
var table = document.getElementById("theTable");
for (var i = 0; i < theXML.childNodes.length; i++)
{
var row = table.insertRow(table.rows.length);
row.style.backgroundColor = "Red";
for (var j = 0; j < theXML.childNodes[i].childNodes.length; j++)
{
var cell = row.insertCell(row.cells.length);
cell.width = "200px";
cell.className = "cellClass";
var d = document.createElement("input")
d.type = "text";
d.width = "190px";
cell.appendChild(d);
}
}
Now, the problem
The code does what it's supposed to do, the table is created with the right rows, columns and inputs, but each and every single time it goes through a width, appendChild, className or other attribute setting, Chrome calls a DOMSubtreeModified event, which is first defined in the Startup() function in the content.js file (not part of my project, so it most be Chrome's). The event calls a onDomChange function which receives document as parameter, which only calls updateDocumentListeners and sends it the parameter, which calls updateInputListeners, which goes through EVERY SINGLE INPUT in the document using a for(), just to call updateInputListeners, which only return true or false depending on some conditions.
Now, when there are only 10 rows, that means that in the end we'll have 70 inputs, which in turn means that by the end it would have called the onDomChange several times, that is: 70 + 69 + 68 + ...., because it goes through everything that's been added before. And that was to be also multiplied by every single attribute you set.
In other words, with every input created, it calls onDomChange. On every attribute I set, it calls onDomChange. If I add a second input, it goes twice, once for the element and/or the attribute I'm setting and another for the previous one. Here's what it does:
//This is the code I found while debugging
function updateDocumentListeners(document)
{
var inputs = document.getElementsByTagName('INPUT');
for (var i = 0; i < inputs.length; ++i)
updateInputListeners(inputs[i]);
}
With 500 inputs, meaning also 500 cells, it simply won't end. Oh, and if there are already other inputs (including types hidden, radio, etc), they are included in the cycle. And then Chrome has the audacity to send a message with an option telling me that it's taking too long and prompts for whether I want to wait or cancel.
What I've tried
Since it all happens because of the event, I've tried to remove it, process the table and then reinstate it, but I can't remove it, because it's declared like this in content.js
document.addEventListener('DOMSubtreeModified', function(){ onDomChangeNoThrow(document); }, true);
This means I can't use removeEventListener, because it is defined as an anonymous function. I've already tried it.
Any suggestions?
Thanks.

Related

How can you stop scrolling on JqGrid when editing in batch mode

The JqGrid example at http://www.guriddo.net/demo/guriddojs/edit_add_delete/inline_batch/index.html shows how to use 'batch editing'. However when the edit button is clicked, focus scrolls to the last rows initial edit column. How can that be made to focus on the first row instead? Also how can it be used with paging?
The first requirement can be accomplished if you loop in reverse order, since the ids are get from the first row to the last - i.e. the function startEdit can look like:
function startEdit() {
var grid = $("#jqGrid");
var ids = grid.jqGrid('getDataIDs');
for (var i = ids.length - 1; i >= 0; i--) {
grid.jqGrid('editRow',ids[i]);
}
}
As for the other requirements it depends what should be done when the new page is requested - to save existing edits or cancel them.
For this purpose it is needed to use onPaging and maybe onSortCol (in case of sorting) events, but all depend on the specific requirements above

How to Interact w/ Items from NodeList Iterating Through Them One After Another Not Simultaneously

I have a form with address rows. Each row has a link (.address-row a.a-link-normal) that when clicked a pop-up shows up. The pop-up has a button #delete-address-popover-button-announce this button needs to be clicked, which opens another confirmation dialog with button .rm-address-button this also gets clicked and one address row is deleted.
I want do this automatically with JavaScript. I tried all kinds of code variation using getElementsByClassName for live HTMLcollection, using querySelectorAll for static NodeList. Tried overloading NodeList, converting it to Array with .slice , placing the whole code in a function with setInterval ... each variant I tried has either no effect, or the code only deletes the first address and has no effect on the rest.
Here is the latest code variant, I also leave the comments from previous trials:
//function delayX() {
var addressBook = document.getElementById('address-list');
var adresRow = addressBook.querySelectorAll('.address-row a.a-link-normal');
console.log('Before for loop' + adresRow.length);
var i;
for (i = 0; i < adresRow.length; ++i) {
//Array.prototype.slice.call(adresRow).forEach(function (element) {
//element.click();
adresRow.item(i).click();
console.log('Click' + adresRow);
function delAddresses() {
document.getElementById('delete-address-popover-button-announce').innerHTML = "Deleting..";
document.getElementById('delete-address-popover-button-announce').click();
console.log('delete-address-popover-button-announce here');
}
setTimeout(delAddresses, 1500);
function delConfirm() {
document.querySelector('.rm-address-button').innerHTML = "Deleting..";
document.querySelector('.rm-address-button').click();
console.log('rm-address-button here');
}
setTimeout(delConfirm, 2500);
}
//});
console.log('End for loop' + adresRow.length);
//}
//setInterval(delayX, 5000);
The odd thing is, that the FOR iterates as many times as there are items in the NodeList, which I see in the browser console by the console.log counts. It looks like the for loop iterates as many times as we want it to, however only the first address gets deleted and then the code stops. I also should mention that after address row is deleted the page gets AJAX-like refresh.
I've been working on this since yesterday and feel rather stuck at this point. Help and advice will be much appreciated!

Handsontable Performance Issues On Updating Comments to Cells

I have a function that updates a comment to every single cell in a row. This function is called many times by a higher level function that loops through every row in the table and determines what comments to apply.
This all works fine. See a simplified version of the code below.
// Loop through all hot rows and determine comment to apply
var loopThroughHotRows = function (hot) {
var rows = hot.getSourceData().length;
for (var i = 0; i < rows; i++) {
var comment = "some comment determined by another function";
applyResponseCommentsToRow(hot, comment, i);
}
}
// Apply comments to a whole row in a passed handsontable
var applyCommentsToRow = function (hot, comment, logicalrow) {
var cols = hot.countCols();
var commentsPlugin = hot.getPlugin('comments');
for (var i = 0; i < cols; i++) {
// render being issued for each comment set.
// need to restrict rendering somehow.
commentsPlugin.setCommentAtCell(logicalrow, i, comment);
}
}
The problem is that each time a comment is applied to a cell. The rendering of the entire handsontable instance is initiated. Causing the web browser to get blocked/chug/become very slow and responsive until all the rendering is complete.
So my question is. Is there some way to prevent Handsontable from rendering each time that a new comment is applied to a cell? Either by temporarily disabling all rendering or adding the comments in a different manner?
I actually ended up figuring out a solution to this on my own. If you set the comment of the cell by calling the hot.getCellMeta() function.
This actually bypasses the re-rendering of the handsontable. See updated function below.
// Apply comments to a whole row in a passed handsontable
var applyCommentsToRow = function (hot, comment, logicalrow) {
var cols = hot.countCols();
for (var i = 0; i < cols; i++) {
// New method of writing comment to cell. Does not cause handsontable re-render.
hot.getCellMeta(logicalrow, i).comment = {'value':responseComments};
}
// Call render once after all comments have been assigned to row!
hot.render();
}
The first thing I can think of to enhance the speed of your function is to not change the comment in cells when it's not necessary. (old comment value = new comment value). To do that, you simply have to compare both String before doing setCommentAtCell :
if(comment.localeCompare(hot.getPlugin('comments').getCommentAtCell(logicalRow,i)) != 0)
commentsPlugin.setCommentAtCell(logicalRow, i, comment);
I used a little example to quickly test this change that you can find in this JSFiddle. (For the sake of 'quick testing', I trigger the change comment for every cell when I copy : whether you use ctrl+C in the table, or you use the action copy in the context menu).
You will see that it will freeze the first time (as every cell will be modified), but the second time there is no freeze at all since the changes are not necessary.

Javascript - How to run part of the code only after the page refreshes?

Important: Even though this could probably be done with php (I'm using WooCommerce on Wordpress), I want this script to run only on my browser. That's why I want to do it in Javascript. Hence, I'm using Custom Javascript for Websites to load my script from my browser.
What my script should do: Check if the data of a specific element has been changed.
Logic: Step 1) Get and locally remember specific string (order number) of a specific class. Step 2) Refresh the page. Step 3) Again get the specific string (order number) of a specific class. Step 4) If strings do not match, run a function (play an audio).
Problem: After page reloads, the script starts to run again from the beginning. Hence, it results in overriding the stored string. As a result, stored and newly fetched string are always equal.
Question: How do I make the script to run the 3rd step only after refresh so that the stored data doesn't override itself?
Current code:
var OrderIdOld = document.getElementsByClassName("row-title"); // Select every single element with ClassName "row-title"
var x = (OrderIdOld[0].innerText); // Get the string of the first element of the class "row-title"
var compareOld = x.slice(-1); // Get the last element of the string (since it will be a number, we can change the string into a number easily later)
localStorage.setItem("compareOld", compareOld); // Store this element in local storage, so that it can be used after page reloads.
setInterval ("window.location.reload()", 30000); // Reload page every 30 secs.
var remembered = localStorage.getItem("compareOld"); // Assign stored element to a new var.
var n = compareOld.valueOf(); // Turn stored element into a number (for easy comparison later).
var OrderIdNew = document.getElementsByClassName("row-title"); // Select every single element with ClassName "row-title"
var y = (OrderIdNew[0].innerText); // Get the string of the first element of the class "row-title"
var compareNew = y.slice(-1); // Get the last element of the string (since it will be a number, we can change the string into a number easily later)
var m = compareNew.valueOf(); // Turn fetched element into a number (for easy comparison later).
function beep() {
var snd = new Audio("data:audio/wav;base64,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qJrx4KOQjahZxWbcZgztj2c49nKmkId44S71j0c8eV9yDK6uPRzx5X18eDvjvQ6yKo9ZSS6l//8elePK/Lf//IInrOF/FvDoADYAGBMGb7FtErm5MXMlmPAJQVgWta7Zx2go+8xJ0UiCb8LHHdftWyLJE0QIAIsI+UbXu67dZMjmgDGCGl1H+vpF4NSDckSIkk7Vd+sxEhBQMRU8j/12UIRhzSaUdQ+rQU5kGeFxm+hb1oh6pWWmv3uvmReDl0UnvtapVaIzo1jZbf/pD6ElLqSX+rUmOQNpJFa/r+sa4e/pBlAABoAAAAA3CUgShLdGIxsY7AUABPRrgCABdDuQ5GC7DqPQCgbbJUAoRSUj+NIEig0YfyWUho1VBBBA//uQZB4ABZx5zfMakeAAAAmwAAAAF5F3P0w9GtAAACfAAAAAwLhMDmAYWMgVEG1U0FIGCBgXBXAtfMH10000EEEEEECUBYln03TTTdNBDZopopYvrTTdNa325mImNg3TTPV9q3pmY0xoO6bv3r00y+IDGid/9aaaZTGMuj9mpu9Mpio1dXrr5HERTZSmqU36A3CumzN/9Robv/Xx4v9ijkSRSNLQhAWumap82WRSBUqXStV/YcS+XVLnSS+WLDroqArFkMEsAS+eWmrUzrO0oEmE40RlMZ5+ODIkAyKAGUwZ3mVKmcamcJnMW26MRPgUw6j+LkhyHGVGYjSUUKNpuJUQoOIAyDvEyG8S5yfK6dhZc0Tx1KI/gviKL6qvvFs1+bWtaz58uUNnryq6kt5RzOCkPWlVqVX2a/EEBUdU1KrXLf40GoiiFXK///qpoiDXrOgqDR38JB0bw7SoL+ZB9o1RCkQjQ2CBYZKd/+VJxZRRZlqSkKiws0WFxUyCwsKiMy7hUVFhIaCrNQsKkTIsLivwKKigsj8XYlwt/WKi2N4d//uQRCSAAjURNIHpMZBGYiaQPSYyAAABLAAAAAAAACWAAAAApUF/Mg+0aohSIRobBAsMlO//Kk4soosy1JSFRYWaLC4qZBYWFRGZdwqKiwkNBVmoWFSJkWFxX4FFRQWR+LsS4W/rFRb/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////VEFHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU291bmRib3kuZGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjAwNGh0dHA6Ly93d3cuc291bmRib3kuZGUAAAAAAAAAACU=");
snd.play();
} // Function that will play the sound.
if (n!=m) {
beep();
} // Run function if two numbers are not equal.
Additional: I have gathered the parts of the code from various Questions on stackoverflow. Meaning, I have been searching about this topic for the past week. This is my first questions ever. Hopefully I formatted the question so that it easy to understand.
EDIT: The issue has been solved. I used the idea of only running the first step of function on the first load of page. This post helped me to get functionality working https://stackoverflow.com/a/22334768/7929506
The working code looks like this:
function beep() {
window.open('https://www.youtube.com/watch?v=EQ3zPIj2O5k','_blank');
}
if (sessionStorage.getItem("visit") == null) {
var OrderIdOld = document.getElementsByClassName("row-title")[0].innerText.slice(-1).valueOf();
sessionStorage.setItem("OrderIdOld", OrderIdOld);
sessionStorage.setItem("visit", new Date());
setTimeout("window.location.reload()", 10000);
}
else {
var OrderIdNew = document.getElementsByClassName("row-title")[0].innerText.slice(-1).valueOf();
var x = sessionStorage.getItem("OrderIdOld");
if (x==OrderIdNew) {
setTimeout("window.location.reload()", 10000);
}
else {
beep();
var x = sessionStorage.getItem("OrderIdOld");
sessionStorage.removeItem("OrderIdOld");
sessionStorage.removeItem("visit");
setTimeout("window.location.reload()", 10000);
}
}
When you refresh the page first time, you can simply append a flag variable to URL to identify weather its a refresh or a first time load. Based on that, you can pass a localized variable to script and if that is set then you need to refresh else not.

Why would .append randomly fail in jquery?

I'm using jquery to add elements to a blank list.
on the page I have:
<ul id="myList">
</ul>
and I go through a loop like this in the script that's called from a dynamically created event handler. (It's "onDrop" of a list item having been sorted with a drag operation)
var myListItemHTML;
for (var i = 0 ; i < 5 ; i++)
{
myListItemHTML += '<li id=listItem'+i+'>This is item number'+i+'</li>';
}
$('#myList').append(myListItemHTML);
and if I check after...
if ($('#myList li').length == 0 )
{
alert('Going to crash now since I'm expecting list items')
}
roughly 95% of the time the list is populated, but about 5% of the time I hit my alert that's going to cause an exception later.
Has anyone run into this? Is there a callback or way to know when/if the append really happens?
var myListItemHTML;
for (var i = 0 ; i < 5 ; i++)
{
$('#myList').append('<li id=listItem'+i+'>This is item number'+i+'</li>');
}
Try just appending inside the for loop.
if ($('#myList li').length == 0 )
{
alert('Going to crash now since I\'m expecting list items')
}
You need a \ before the ' so it doesn't conflict.
edit: jsfiddle that shows "undefined" http://jsfiddle.net/gyEre/1/
This is a bit of a longshot, because I'm making an assumption about your code beyond what was posted. Here goes:
I had a problem with some code once, which worked perfectly in all Browsers except Chrome, wherein it would fail randomly and seemingly without cause.
My problem, ultimately, was that Chrome was actually executing my JavaScript too fast, and that it was throwing off some of the timing in some of the AJAX calls that were being made earlier.
My code was such that AJAX event A triggered, which then passed data to AJAX event B. In Chrome only, though, I found that event B was on occasion occurring before event A, and that was the error condition.
If I recall, I think the solution was to force one key AJAX request to be made synchronously, though that should be used with care. Please see: http://api.jquery.com/jQuery.ajaxSetup/
I hope that's not too vague to be helpful. Good luck!
What I've learned is that I can't trust jquery for DOM manipulation called through dynamically created events. I welcome someone to prove me wrong, but this approach:
addListItem = function (itemID, itemText)
{
var li = document.createElement('li');
li.setAttribute("id", itemID);
var liText = document.createTextNode(itemText);
li.appendChild(goalTextNode);
document.getElementById('myList').appendChild(li);
}
for (var i = 0 ; i < 5 ; i++)
{
addListItem('listItem'+i, 'Item Text'+i);
}
will work 100% of the time and never fail.
In my experience, if the script in which the prepend function is used in the -tag, it will not work because the script is loaded before the DOM is ready.
To avoid this kind frustration, is a good habit to place your javascript before the tag.

Categories