I have a js code which resets the value of elements on jsp by type on ajax response. Code runs a for loop for all elements; gets all elements by name for nearly 800-900 elements. IE 8 gives annoying popup message for unresponsive script. I have been through lot of articles regarding it but none helped so far or I couldnt implement to fix an issue.
Below is the code which is causing pop up.
function clearField(ele,eleType)
{
if(eleType=="checkbox")
{
ele.prop('checked', false);
}
else if(eleType=="text")
{
ele.val("");
ele.removeAttr('disabled');
ele.removeAttr('readonly');
}
else if(eleType=="radio")
{
if(ele.is(':checked'))
{
ele.prop('checked', false);
}
}
else if(eleType=="multiple")
{
if(ele.data('echMultiselect')!==undefined)
{
ele.multiselect("uncheckAll");
ele.multiselect("refresh");
}
}
else if(eleType=="hidden")
{
ele.val("");
}
else
{
ele.val("Select");
ele.removeAttr('disabled');
ele.removeAttr('readonly');}
}
Above function gets called in for loop iteration. ele is fetched as below and passed to a function.
var ele = $("input[name='"+ elementName+"']");
Kindly suggest if any improvement can be done or any other approach can be used to implement the same.
The unresponsive script popup is caused by a long-running function. This blocks javascript's single threaded event loop and renders the page unresponsive until the function exits. If you run the clear asynchronously, the event loop won't get blocked. For example, you can replace clearField(ele,eleType) with setImmediate(clearField.bind(null,ele,eleType)). This is a quick hack to free up the event loop and prevent the popup from appearing, but does not address the performance issues underneath.
DOM access is an expensive operation and accessing hundreds of elements should be avoided, if possible. If your use case is to always reset all the fields to a default stage, I'd suggest having a looooong HTML string of all your elements and just calling $(parent).html(htmlString) to set the elements. This way you only need one DOM access and the effect is instantanious.
http://api.jquery.com/html/
Related
I've been at this quite a while, have dug everywhere and I'm about at my wits end. The solution seems simple: Inject some javascript code into the chromium browser to wait for an element to appear (the browser has already finished loading and without waiting for the element, my .net code races past the automation parts I want to do on the page), then proceed.
I'm following the examples set here and here (see the constructor).
However, the raise event isn't firing like I figured it should be. It's probably due to my javascript code - or it's possible I've missed something somewhere else.
Here is the js code:
private void InjectJsWait(string eventName, string selector, int time, string eventStage)
{
string script = #"function waitForElementToDisplay(##SELECTOR##, ##TIME##) {
if(document.querySelector(##SELECTOR##)!=null) {
window.boundEvent.raiseEvent('##EVENTNAME##', {eventStage: ##STAGE##});
return;
}
else {
setTimeout(function() {
waitForElementToDisplay(##SELECTOR##, ##TIME##);
}, ##TIME##);
}
}";
script = Regex.Replace(script, "##SELECTOR##", selector);
script = Regex.Replace(script, "##TIME##", time.ToString());
script = Regex.Replace(script, "##EVENTNAME##", eventName);
script = Regex.Replace(script, "##STAGE##", eventStage);
browser.ExecuteScriptAsync(script);
}
Any help would be greatly appreciated.
Using a MutationObserver should be able to do this.
You would simply have to check the observed change and filter out the one you need; no guessing whether or not an event has fired or element has been added.
Keep in mind that this needs Chrome 18+ or 26+ to work
This is a very simple use case. Show an element (a loader), run some heavy calculations that eat up the thread and hide the loader when done. I am unable to get the loader to actually show up prior to starting the long running process. It ends up showing and hiding after the long running process. Is adding css classes an async process?
See my jsbin here:
http://jsbin.com/voreximapewo/12/edit?html,css,js,output
To explain what a few others have pointed out: This is due to how the browser queues the things that it needs to do (i.e. run JS, respond to UI events, update/repaint how the page looks etc.). When a JS function runs, it prevents all those other things from happening until the function returns.
Take for example:
function work() {
var arr = [];
for (var i = 0; i < 10000; i++) {
arr.push(i);
arr.join(',');
}
document.getElementsByTagName('div')[0].innerHTML = "done";
}
document.getElementsByTagName('button')[0].onclick = function() {
document.getElementsByTagName('div')[0].innerHTML = "thinking...";
work();
};
(http://jsfiddle.net/7bpzuLmp/)
Clicking the button here will change the innerHTML of the div, and then call work, which should take a second or two. And although the div's innerHTML has changed, the browser doesn't have chance to update how the actual page looks until the event handler has returned, which means waiting for work to finish. But by that time, the div's innerHTML has changed again, so that when the browser does get chance to repaint the page, it simply displays 'done' without displaying 'thinking...' at all.
We can, however, do this:
document.getElementsByTagName('button')[0].onclick = function() {
document.getElementsByTagName('div')[0].innerHTML = "thinking...";
setTimeout(work, 1);
};
(http://jsfiddle.net/7bpzuLmp/1/)
setTimeout works by putting a call to a given function at the back of the browser's queue after the given time has elapsed. The fact that it's placed at the back of the queue means that it'll be called after the browser has repainted the page (since the previous HTML changing statement would've queued up a repaint before setTimeout added work to the queue), and therefore the browser has had chance to display 'thinking...' before starting the time consuming work.
So, basically, use setTimeout.
let the current frame render and start the process after setTimeout(1).
alternatively you could query a property and force a repaint like this: element.clientWidth.
More as a what is possible answer you can make your calculations on a new thread using HTML5 Web Workers
This will not only make your loading icon appear but also keep it loading.
More info about web workers : http://www.html5rocks.com/en/tutorials/workers/basics/
I have the following code (Backbone view, rendering using Handlebars):
_this.$el.addClass("loading");
_this.el.innerHTML = _this.template({
some: data
});
_this.otherCPUConsumingRenderingFunctions();
_this.$el.removeClass("loading");
The CSS class displays a "Loading" message on screen to warn the user, since rendering takes time due to a large amount of data and a complex rendering.
My problem is that the CSS class is correctly applied (I see it in the inspector) but nothing is displayed on screen.
If I put breakpoints and go step-by-step, it will work perfectly.
The issue occurs both with Chrome and Firefox.
No rendering function in browsers is synchronous. So your otherCPUConsumingRenderingFunctions is most probably returning as soon as you call it. It does it's thing later asynchronously.
That is why your loading class gets removed as soon as it is added.
Most likely you'll need to use a callback after the rendering function completes. Also remember expensive rendering operations, depending upon their design, can be blocking — meaning the dom does not get a chance to re-render until all the work is done. In this case it will add the loading class and remove it all before the dom redraws. Stepping through your code provides the browser time to re-render which is why you'll see it working when debugging.
Perhaps something like this
_this.otherCPUConsumingRenderingFunctions = function (callback) {
// do work here
callback();
};
_this.$el.addClass("loading");
_this.el.innerHTML = _this.template({
some: data
});
// You can use a timeout to "schedule" this work on the next tick.
// This will allow your dom to get updated before the expensive work begins.
window.setTimeout(function () {
_this.otherCPUConsumingRenderingFunctions(function () {
// Ensure this only runs after the rendering completes.
_this.$el.removeClass("loading");
});
}, 1);
The backburner.js project was created to help mitigate this kind of problem. It works well with Backbone too.
To see the problem in action, see this jsbin. Clicking on the button triggers the buttonHandler(), which looks like this:
function buttonHandler() {
var elm = document.getElementById("progress");
elm.innerHTML = "thinking";
longPrimeCalc();
}
You would expect that this code changes the text of the div to "thinking", and then runs longPrimeCalc(), an arithmetic function that takes a few seconds to complete. However, this is not what happens. Instead, "longPrimeCalc" completes first, and then the text is updated to "thinking" after it's done running, as if the order of the two lines of code were reversed.
It appears that the browser does not run "innerHTML" code synchronously, but instead creates a new thread for it that executes at its own leisure.
My questions:
What is happening under the hood that is leading to this behavior?
How can I get the browser to behave the way I would expect, that is, force it to update the "innerHTML" before it executes "longPrimeCalc()"?
I tested this in the latest version of chrome.
Your surmise is incorrect. The .innerHTML update does complete synchronously (and the browser most definitely does not create a new thread). The browser simply does not bother to update the window until your code is finished. If you were to interrogate the DOM in some way that required the view to be updated, then the browser would have no choice.
For example, right after you set the innerHTML, add this line:
var sz = elm.clientHeight; // whoops that's not it; hold on ...
edit — I might figure out a way to trick the browser, or it might be impossible; it's certainly true that launching your long computation in a separate event loop will make it work:
setTimeout(longPrimeCalc, 10); // not 0, at least not with Firefox!
A good lesson here is that browsers try hard not to do pointless re-flows of the page layout. If your code had gone off on a prime number vacation and then come back and updated the innerHTML again, the browser would have saved some pointless work. Even if it's not painting an updated layout, browsers still have to figure out what's happened to the DOM in order to provide consistent answers when things like element sizes and positions are interrogated.
I think the way it works is that the currently running code completes first, then all the page updates are done. In this case, calling longPrimeCalc causes more code to be executed, and only when it is done does the page update change.
To fix this you have to have the currently running code terminate, then start the calculation in another context. You can do that with setTimeout. I'm not sure if there's any other way besides that.
Here is a jsfiddle showing the behavior. You don't have to pass a callback to longPrimeCalc, you just have to create another function which does what you want with the return value. Essentially you want to defer the calculation to another "thread" of execution. Writing the code this way makes it obvious what you're doing (Updated again to make it potentially nicer):
function defer(f, callback) {
var proc = function() {
result = f();
if (callback) {
callback(result);
}
}
setTimeout(proc, 50);
}
function buttonHandler() {
var elm = document.getElementById("progress");
elm.innerHTML = "thinking...";
defer(longPrimeCalc, function (isPrime) {
if (isPrime) {
elm.innerHTML = "It was a prime!";
}
else {
elm.innerHTML = "It was not a prime =(";
}
});
}
I'm in the process of authoring a completely client side web language reference site. A problem that I encountered today; I have a side panel that is a unordered list of terms and they have onmouseover event listeners. I decided it would be a good idea to add a delay prior to execution and cancel the event at run-time if the mouse was no longer over that element. This is what I've come up with but I feel there must be a better way.
var currentXCoordinate=0
var currentYCoordinate=0
var elementFromCurrentMousePosition=0
function trackCurrentMousePosition(event) {
if (document.elementFromPoint(event.clientX, event.clientY).nodeName=="SPAN") {
elementFromCurrentMousePosition=document.elementFromPoint(event.clientX, event.clientY).parentNode
}
else {
elementFromCurrentMousePosition=document.elementFromPoint(event.clientX, event.clientY)
}
return (currentXCoordinate=event.clientX, currentYCoordinate=event.clientY, elementFromCurrentMousePosition)
}
function initPreview(event, obj) {
arg1=event
arg2=obj
setTimeout("setPreviewDataFields(arg1, arg2)", 100)
}
function setPreviewDataFields(event, obj) {
if ('bubbles' in event) {
event.stopPropagation()
}
else {
event.cancelBubble=true
}
if (elementFromCurrentMousePosition!=obj) {
return 0;
}
The code goes on to do all the wonderful stuff I want it to do if execution wasn't cancelled by the previous if statement. The problem is this method is seeming to be really processor intensive.
To sum it up: on page load all my event listeners are registered, cursor position is being tracked by a onmousemove event. Applicable list items have a onmouseover event that calls the initPreview function which just waits a given period of time before calling the actual setPreviewDataFields function. If at run-time the cursor is no longer over the list element the function stops by return 0.
Sadly that's the best I could come up with. If anyone can offer up a better solution I would be very grateful.
Why not just use mouseout to tell when the mouse leaves an element? Running all of that code every time the mouse moves isn't ideal.
Also, you really shouldn't pass a string to setTimeout like that. Instead, pass a function. As a bonus, you can get rid of those evil global variables arg1 and arg2. With those being globals, I think you will run into issues if init gets called again before the timeout expires.