In a jQuery project, I am adding several images to a page after it has finished loading, using the append() function. After all the appends are done, I need to call a function that detects the width and height of each of these new elements, so I can properly align them in the page.
This is working well on my rather slow development PC. But when testing it on a fast machine in a quick browser (e.g. Chrome), it turns out that calling the layout function right after the appends are done leads to random, weird behavior. Executing the layout function again with a slight delay solves the problem. I guess "javascript is done appending the new elements" is not the same as "the browser is done displaying them", so measuring their size is not yet possible.
So what I need is some way to tell when the new elements are actually there, so that I can reliably detect their widths and heights. I don't think document.ready() would help in this case, since the elements are added by a function that is called in document.load().
This is the code I copied from another stackoverflow thread (by a dude named Pointy, thanks by the way!)
function called_by_document_load() {
var promises = [];
for (var i=0; i<my_pics.length; i++) {
(function(url, promise) {
var img = new Image();
img.onload = function () {
promise.resolve();
};
img.src = url;
}) (my_pics[i]['url'], promises[i] = $.Deferred());
}
$.when.apply($, promises).done(function() {
// add the new images using append()
// when done, call my own layout function
}
... with the array my_pics containing the URLs of the images to load.
Any ideas? :-)
Thanks in advance,
snorri
After adding the new images using append, yield the control to the browser so that the browser can update the ui and then invoke your layout function. You can do so my using a setTimeout with delay set to 0.
$.when.apply($, promises).done(function() {
// add the new images using append()
setTimeout(function () {
// when done, call my own layout function
}, 0);
}
There are many SO threads that explain why this works and is useful.
Here is one good explanation
Also, worth watching is the video by Philip Roberts on browser's event loop.
Related
You'll have to forgive me if I show any kind of ineptitude here, jquery and java isn't my usual area of work. But here goes:
I have a page that shows a random list of items that are pulled from a server using an API call. The idea is that every time the user clicks "generate" a new list is produced and inserted into the page. This works but it's very fast and all the user sees is a list rapidly changing. To spruce things up I've decided to put some nice animations and effects in.
I've got a jquery function that loops through each element in the list of child elements and toggles the css style of the child element so that an effect from the animate.css library is applied. The problem is when I have another function that loads the new list and this is called immediately and therefore all of the css transitions are ignored; or rather they don't get a chance to run because the second method immediately triggers.
I've tried using a callback and had no joy, I've tried using deferred objects. No luck at all.
Here's the code I have so far:
function removeOldContent() {
$('#removableContent > div').each(function (index) {
var elm = $(this);
setTimeout(function () {
elm.toggleClass('customAnim', function () {
$(this).remove();
});
}, index * 150);
});
}
function getList() {
var rId = $('.tab-content').find('.active').data('d-id');
var serviceUrl = '/GetRandom/GetList';
$.ajax({
type: 'POST',
url: serviceUrl,
data: {
reportId : rId
},
success: function(data) {
$('#reportContainer').html(data).fadeIn('slow');
}
});
}
Ideally I'd like to be able to let removeOldContent() finish completely, after all the timeouts have run. And then trigger getList() to update the content. I'll work on making a nice transition for the inbound data but first I just need to get this working.
Any advice or pointers would be greatly appreciated.
***** Update ******
I've made a fiddle. Not giving me the same error as my dev env but should be close enough for you to see
https://jsfiddle.net/rdt1pfhk/9/
Your problem is with the timing of events. Your removeOldContent function uses a setTimeout function which in turn animates and removes the items from the DOM. Your getList() function was executing before the other function had finished. I put a quick untidy solution together using your fiddle. I return a jquery deferred object from you removeOldContent method and then only call the getList when that is resolved (and the older items removed from the dom). It is not the neatest but it will point you in the right direction. I updated your fiddle here: https://jsfiddle.net/rdt1pfhk/16/
function removeOldContent() {
var deferred = new jQuery.Deferred();
....
return deferred;
}
$(document).on('click', '.quickPick', function (e) {
removeOldContent().then(function(){
getList();
});
});
If I understood correctly you need to only delay some functions for as long as you need. I think the answer you are looking for could be found here:
How to delay calling of javascript function?
Also, I'd like to mention that I don't see a
$(document).ready(function () {
//your code here
});
anywhere. Maybe I'm wrong but since you mentioned that CSS is ignored, are you sure the page is loaded before your code starts being executed?
I'm building a dynamic website that loads all pages inside a "body" div via jquery's load(). The problem is I have a script looped with setInterval inside the loaded PHP page, the reason being I want the script loaded only when that page is displayed. Now I discovered that the scripts keep running even after "leaving" the page (loading something else inside the div without refresh) and if I keep leaving / returning the loops stack up flooding my server with GET requests (from the javascript).
What's a good way to unload all JS once you leave the page? I could do a simple dummy var to not load scripts twice, but I would like to stop the loop after leaving the page because it's causing useless traffic and spouting console errors as elements it's supposed to fill are no longer there.
Sorry if this has already been asked, but it's pretty hard to come up with keywords for this.
1) why don't you try with clearInterval?
2) if you have a general (main) function a( ) { ... } doing something you can just override it with function a() { }; doing nothing
3) if you null the references to something it will be garbage collected
no code provided, so no more I can do to help you
This really sounds like you need to reevaluate your design. Either you need to drop ajax, or you need to not have collisions in you method names.
You can review this link: http://www.javascriptkit.com/javatutors/loadjavascriptcss2.shtml
Which gives information on how to remove the javascript from the DOM. However, modern browsers will leave the code in memory on the browser.
Since you are not dealing with real page loads/unloads I would build a system that simulates an unload event.
var myUnload = (function () {
var queue = [],
myUnload = function () {
queue.forEach(function (unloadFunc) {
undloadFunc();
});
queue = [];
};
myUnload.add = function (unloadFunc) {
queue.push(unloadFunc);
};
return myUnload;
}());
The code that loads the new pages should just run myUnload() before it loads the new page in.
function loadPage(url) {
myUnload();
$('#page').load(url);
}
Any code that is loaded by a page can call myUnload.add() to register a cleanup function that should be run when a new page is loaded.
// some .js file that is loaded by a page
(function () {
var doSomething = function () {
// do something here
},
timer = setInterval(doSomething, 1000);
// register our cleanup callback with unload event system
myUnload.add(function () {
// since all of this code is isolated in an IIFE,
// clearing the timer will remove the last reference to
// doSomething and it will automatically be GCed
// This callback, the timer var and the enclosing IIFE
// will be GCed too when myUnload sets queue back to an empty array.
clearInterval(timer);
});
}());
What is the best way to use the jQuery load function synchronously.
I need to load an image but can't execute the next line of code until that image has loaded.
I could loop a variable until the load has completed but was wondering if there was a better way of doing that.
var img = jQuery('<img src="' + url + '"/>').load(function () {
});
//Run code here once img load has comlpeted.
You can also use CallBack function to get Synchronous Behaviour -
var result = $('#main-container').load( 'html/Welcomeform.html',
function () {
if($("textarea").find("#mail-id")){
$("textarea#mail-id").val(email_id);
}
} );
From what I know, the load event will always fire asynchronously, except if the image is already cached (in some browsers). The only reliable solution is to put the code in a callback like you did. However, to make sure the load handler will always be fired in all browsers, even if the image is cached, make sure to add the handler before setting the src property of the image.
var img = jQuery('<img src="' + url + '"/>').load(runner);
function runner() {
//run code here once image is loaded
}
I arrived here looking for a similar solution. From the reads, it is not possible with .load, you need to use an AJAX request, as the question comment points out.
In my case I need to load a html file and I have added a transition to change the content. The old content were showed before the new one after the transition even if I was showing the content inside the load callback.
var main = $("#main");
main.load("about.html", displaySection);
function displaySection () {
main.show('blind');
}
My workaround has been to run the transition that shows the loaded content inside a timeout function with a of 200 for the delay parameter.
var main = $("#main");
main.load("about.html", displaySection);
function displaySection () {
setTimeout(function() {
main.show('blind');
}, 200);
}
The problem could be if the connection is so slow that the new page takes more than 200 ms to load, but in this case I wonder the callback will be launched later on. I don't understand why is not working without the timeout that I feel quite ugly, but it solved my problem ... just in case any other has not given a thought on this possibility.
The callback function in load() will fire once the basic elements of the screen have been retrieved, (the actual html) but doesn't wait for images to finish, you can use this to make it wait.
$('#holder').load(function() {
var imgcount = $('#holder img').length;
$('#holder img').load(function(){
imgcount--;
if (imgcount == 0) {
/* now they're all loaded, let's display them! */
}
});
});
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);
I need to do something like this:
Execute a piece of code
Start to load an image and block the script execution
When the image is loaded resume the execution
Execute the rest of the code
I know that the simplest way is to assign a function on the onload event of the image and then execute the rest of the code in the function, but if it's possible i want to have a "linear" behaviour blocking the script execution and then resume it.
So, is there a cross-browser way to do this?
The only way to block script execution is to use a loop, which will also lock up most browsers and prevent any interaction with your web page.
Firefox, Chrome, Safari, Opera and IE all support the complete property, which was finally standardised in HTML5. This means you can use a while loop to halt script execution until the image has finished downloading.
var img = new Image();
img.src = "/myImage.jpg";
document.body.appendChild(img);
while (!img.complete)
{
// do nothing...
}
// script continues after image load
That being said, I think you should look at ways of achieving your goal without locking up the browser.
If you don't mind having a preprocessing step, try Narrative Javascript, which you can
image.onload = new EventNotifier();
image.onload.wait->();
This suggestion is not exactly what you asked for, but I offer it as a possible alternative.
Create a CSS class with the background-image you want to use. When your app starts, assign this CSS class to a DIV that is either hidden out of site or sized to zero by zero pixels. This will ensure the image is loaded from the server. When you want to load the image (step two above), use the CSS class you create; this will happen quickly. Maybe quickly enough that you need not block the subsequent code execution?
I wouldn't try to block script execution completely, as that could make the browser slow down, or even alert the user that a script is taking too long to execute.
What you can do is 'linearize' your code by using events to finish work. You will need to add a time out to the function, as the image may never load.
Example:
var _img = null;
var _imgDelay = 0;
var _finished = false;
function startWork(){
_img = document.createElement('img');
_img.onload = onImgLoaded;
_img.src = 'yourimg.png';
// append img tag to parent element here
// this is a time out function in case the img never loads,
// or the onload event never fires (which can happen in some browsers)
imgTimeout();
}
function imgTimeout(){
if (_img.complete){
// img is really done loading
finishWork();
}
else{
// calls recursively waiting for the img to load
// increasing the wait time with each call, up to 12s
_imgDelay += 3000;
if (_imgDelay <= 12000){ // waits up to 30 seconds
setTimeout(imgTimeout, _imgDelay);
}
else{
// img never loaded, recover here.
}
}
}
function onImgLoaded(){
finishWork();
}
function finishWork(){
if (!_finished){
// continue here
_finished = true;
}
}
You can use xmlhttprequest and use synchronous mode.
var url = "image.php?sleep=3";
var img = new Image;
var sjax = new XMLHttpRequest();
img.src = url;
sjax.open("GET", url, false);
sjax.send(null);
alert(img.complete);
The trick here is we load the same image twice, first by using the Image object, and also by using ajax in synchronous mode. The Image object isn't needed, I just assumed that's how you want to load it. The key though is that if ajax completes, then the image will be fully downloaded an in the browser's cache. As such, the image will also be available for use by the Image object.
This does assume that the image is served with cache friendly http headers. Otherwise, it's behavior might vary in different browsers.