I'm confused about the behavior of jQuery's .append() method in a for loop.
This code attaches and displays the file names on the div element. It works fine, but I'm reading the code, and I'm in the understanding that the file name gets appended in sequence/one after the other as it loops. But it appears like they get displayed together once the loop finishes. I also tried to intercept it with an alert to check.
Why does it happen this way?
$("#fileInput").on("change", function() {
$(".input-feedback").html('');
for(var i = 0; i < this.files.length; i++) {
var file = this.files[i];
console.log(file.name);
alert('test');
$(".input-feedback").append("<p>"+ file.name +"</p>");
}
});
Files are added one at a time but...
Usually browsers wait until js code run is completelly finished and js is idle waiting for events in order to perform the dom rendering (it depends on browser, the type of event which you are running from and on DOM changes applied).
If you want to see how each item is added for sure, then best way is run code asyncronously with setTimeout or setInterval.
Related
I'm trying to evaluate the javascript on a page before I do a query because the html that I'm looking for doesn't exist in the AngleSharp document.
There is a method: document.ExecuteScript(string )
But I don't know how to use it compared to how I've seen other libraries used. For example, some python code looks like this...
wait.until(presence_of_element_located((By.ID, "class-name")))
Which just pauses the code I guess until the entire page is evaluated. Elements can then be searched.
In AngleSharp it looks like I have to run ExecuteScript method to do the same thing. But it just throws an exception (Jint.Runtime.JavaScriptException: 'results is not defined') and it returns an object as a result - which is completely obfuscating, not helpful at all.
What do I do so that my next command:
IHtmlCollection<IElement> cells = document.QuerySelectorAll(s);
actually looks through the entire document and not just the initial HTML?
I think there is more than just one question here. So I'll break it down.
First, let's get some basics here:
AngleSharp is a browser core - its not a browser, and it is not a JS engine.
AngleSharp provides the ability to extend it with, for instance, a JS engine. AngleSharp.Js is such an engine based on Jint, however, its still experimental and complicated scripts will definitely not run.
Before we already get into the event loop and async loading details, I'd recommend to make sure that whatever script you expect to run, really runs.
Now to the specifics:
ExecuteScript is a little helper from AngleSharp.Js that actually runs a piece of JS code that you provide. I guess its not at all what you want.
If you just want to "wait" until something is there you can do that with a few lines of C# code:
var maxTime = TimeSpan.FromSeconds(1.5);
var totalTime = TimeSpan.Zero;
var pollTime = TimeSpan.FromMilliseconds(25);
while (totalTime < maxTime)
{
await Task.Delay(pollTime.Milliseconds);
// check condition
if (document.QuerySelector(".foo.bar") != null)
{
// run zoned code
break;
}
totalTime += pollTime;
}
AngleSharp.Js actually has various methods to get into the event loop, i.e., to wait until JS has completed the current work.
For instance, WaitUntilAvailable() can be used to wait until the load event (and related) has been handled.
To enqueue some action the Then() extension method was added. All these extension methods live directly on the IDocument instance.
I have a really simple line of code. I have a tabstrip provided by Kendo library
i = 0;
x = 10;
while (i < x) {
var tabStrip = $("#myId").data("kendoTabStrip");
tabStrip.select(i);
i++;
}
When I go step by step using debugger everything is ok - tabStrip.select(i) method is being invoked and works perfectly. But when I run it without debugger it just behaves like there was no this line. I do not understand why, and I don't know how to solve this.
(i and x variables are just sample variables, maybe the information that the method is invoked inside the while loop is important)
var tabGroupObject = $("<div>").attr("id", "myId")
tabGroupObject = $(tabGroupObject).kendoTabStrip({
animation: {
open: {
effects: "fadeIn"
}
}
});
var tabStrip = tabGroupObject.data("kendoTabStrip");
Seems to be a synchronization issue, very common in JavaScript when dealing with Ajax calls or DOM modifications. That's why it works when you execute the code step by step giving enough time for the actions to happen.
My recommendation would be to read a little about Async JavaScript and try to implement a callback function that triggers once the animation finish its task.
Assumption:- I'm assuming you are looking for an ajax trigger event which gets fired in the browser in response to the select of the tabScript.
Solution:- If that's the case please know that browsers combines all the ajax events on an element within a set amount of time into one event to reduce the number of unwanted post calls, what you can do is try adding a delay if you want these events to be called else it would simply trigger the even which gets called on the tabSctrip.select(9) as mentioned by dfsq.
I have a hefty script to run synchronously and I want to display a message like "Processing, please wait..." before it runs so the user isn't wondering why the page is frozen for a few seconds. I'm trying to do something like:
messageBox.html("Processing, please wait...");
// run hefty script
messageBox.html("Finished!");
But the page blocks before the message is displayed, even though the messageBox.html() statement comes first. Why is this?
Sometimes it makes sense to fire the "hefty script" in a timeout.
messageBox.html("Processing, please wait...");
setTimeout(function () {
heftyScript();
messageBox.html("Finished!");
}, 1);
The reason this happens is because it often holds UI updates until the end of the event loop (after your "hefty" script has finished). Setting a timeout ensures that hefty script doesn't run until a subsequent iteration of the event loop (letting the UI update at the end of the current iteration beforehand).
I would consider Web Workers in this case: http://www.html5rocks.com/en/tutorials/workers/basics/
UPDATE:
If for some reason you cannot use them then you should split your processing on smaller chunks and run them asynchronously. Locking UI is not an option.
Here is what you can do:
function heftyScript() {
var arr = [...];
var chunk_start = 0;
function do_chunk() {
for( var i = 0; i < 100; ++i ) { // 100 items per chunk
if( chunk_start >= arr.length)
return;
process( arr[chunk_start++] ); // process one element
}
window.setTimeout(do_chunk,10);
}
do_chunk();
}
It depends where the time is being spent. If it's downloading and parsing the JavaScript file, the messageBox.html() must be pure html or you do it in a script block before referencing the external file. If the time spent is running that long function then setTimeout(function () { heftyScript(); messageBox.html('finished'); }, 1); works wonderfully.
I have some javascript placed inside of the jquery $(document).ready function. It searches for an anchor in the url, and then runs a separate function to display matching content.
The code executes if I place an alert inside of the if statement somewhere, but wont' run otherwise. I've stored all the anchor names in an array called 'anchorNameList', and am checking to see if the anchor in the URL exists.
I only want the function to run on the initial pageload, so I set the default value of 'currentAnchor' to 1000 and change it on each iteration.
if (currentAnchor == 1000 && document.location.hash.substring(1)) {
var checkForThisAnchor = document.location.hash.substring(1);
for (var j=0; j < anchorNameList.length; j++) {
if (anchorNameList[j] == checkForThisAnchor) {
expandMe(j);
}
}
}
In my experience, when a JavaScript problem magically fixes itself by adding an innocuous alert() somewhere, the source of the problem is typically an asynchronous request.
Under non-alert circumstances the async request hasn't finished yet. But by adding the alert, it has a chance to finish, and therefore allowing your code to travel a different code-path than it would have hit without the response of the complete asynchronous call.
I switched the onload event from jQuery's document.ready to window.ready. It worked properly about 30% of the time, so it was definitely a timing issue. It seemed the main JavaScript function on the page, which is retrieving list items, was running slowly. I just moved this entire expanding function to the end of that list retrieval function, so it just runs linearly.
I'm using jQuery to change the HTML of a tag, and the new HTML can be a very long string.
$("#divToChange").html(newHTML);
I then want to select elements created in the new HTML, but if I put the code immediately following the above line it seems to create a race condition with a long string where the changes that html() is making may not necessarily be finished rendering. In that case, trying to select the new elements won't always work.
What I want to know is, is there an event fired or some other way of being notified when changes to html() have finished rendering ? I came across the jQuery watch plugin, which works alright as workaround but it's not ideal. Is there a better way ?
As a commenter already mentioned, JavaScript is single threaded, so you can't get race conditions.
What may trip you up however, is the fact that the UI will not update itself based on JavaScript, until a thread is finished. This means that the entire method must finish, including all code after you call html(...), before the browser will render the content.
If your code after calling html(...) relies on the layout of the page being recalculated before continuing, you can do something like this:
$("#divToChange").html(newHTML);
setTimeout(function() {
// Insert code to be executed AFTER
// the page renders the markup
// added using html(...) here
}, 1);
Using setTimeout(...) with a time of 1 in JavaScript defers execution until after the current JavaScript code in the calling function finishes and the browser has updated the UI. This may solve your problem, though it is difficult to tell unless you can provide a reproducible example of the error you're getting.
use .ready jQuery function
$("#divToChange").html(newHTML).ready(function () {
// run when page is rendered
});
It's 7 years latter and I just ran into a scenario exactly like the one #mikel described, where I couldn't avoid a "timer based solution". So, I'm just sharing the solution I developed, in case anyone out there is still having issues with this.
I hate having setTimeouts and setIntervals in my code. So, I created a small plugin that you can put where you think it's best. I used setInterval, but you can change it to setTimeout or another solution you have in mind. The idea is simply to create a promise and keep checking for the element. We resolve the promise once it is ready.
// jquery.ensure.js
$.ensure = function (selector) {
var promise = $.Deferred();
var interval = setInterval(function () {
if ($(selector)[0]) {
clearInterval(interval);
promise.resolve();
}
}, 1);
return promise;
};
// my-app.js
function runWhenMyElementExists () {
// run the code that depends on #my-element
}
$.ensure('#my-element')
.then(runWhenMyElementExists);