jQuery .load() not firing on images (probably caching?) - javascript

I have some pretty basic jQuery code:
...
$(this).find('img').load(function(){
loadedImages++;
if(loadedImages == $this.find('img').length){
...
However, thats not firing consistently. It fires if i do a hard refresh or close my browser, but a normal refresh, or just hitting the same URL twice at any time without erasing the cache makes the .load() never fire.
Any ideas on how to fix this?

I think this has been discussed before. It’s not the caching per se that is the problem but the timing: the images may already have been loaded by the time the event handler is attached (so it never gets fired). This may also occur if no caching happens, for example in a multithreaded browser on a very fast connection. Fortunately, there is a property .complete that you can use:
var load_handler = function() {
loadedImages++;
…
}
$(this).find('img').filter(function() {
return this.complete;
}).each(load_handler).end().load(load_handler);
You can also create your own event attach function:
jQuery.fn.extend({
ensureLoad: function(handler) {
return this.each(function() {
if(this.complete) {
handler.call(this);
} else {
$(this).load(handler);
}
});
}
});
And then call it as follows:
$(this).find('img').ensureLoad(function(){
loadedImages++;
if(loadedImages == $this.find('img').length){
…
});

A way would be to add a "dummy variable" at the end of the URL that you use to grab the image... such as the time in milliseconds appended as a query string param.
Edit: Preventing the Browser to Cache images is a very bad idea in 99% of the cases as it will slow down your application. Instead you should check if an image is already loaded, and if not wait for the load event to complete.

As Ron and El Guapo said, the answer is to add a query at the end of the URL. I did this like this:
$(this).find('img').each(function(){
$(this).attr('src',$(this).attr('src')+'?'+new Date().getTime())
}).load(function(){
//This will always run now!

Related

Unload JS loaded via load() to avoid duplicates?

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);
});
}());

Firefox return false; (preventDefault();) and window.location.reload(); together

I think it very stupid question, but after hours of google it - i have no idea or solution.
Point is that i need reload page after handling "click" event on my web-site. In chrome\opera it's works without problems. But in firefox i have some bug. My JS code with comments:
$('#project_create').click(function() {
var projectname = $("#project_name").val();
var projectdescription = $("#project_description").val();
$.post("projects/add_project.php", {
project_name: projectname,
project_description: projectdescription
});
$("#create_project_form").hide("slow");
$("#project_list").show("slow");
//return false; - if it uncomment, all work, but next page reloader doesn't work.
window.location.reload(); //but if it's work, FireFox doesn't send $.post-query
});
I need to work booth methods, because after click - script put in $_SESSION['my_var'] some variable, and it variable is avaliable after reload page only. Maybe there are other ways to do it? As I understand the problem here in features with firefox and preventDefault();
Thanks!
The issue is just you reload the page before performing the ajax request.
Try to reload page in the ajax success callback handler :
$.post("projects/add_project.php", {
project_name: projectname,
project_description: projectdescription
}, function(){
window.location.reload();
});
And remove your old window.location.reload()
When you do a return, code after that line will not be reached anymore and is considered "dead code". One does not simply put code after a return.
Another is that there's and issue when using return false to prevent default default actions. It prevents delegation/bubbling. Event handlers hooked higher up in the DOM tree (especially ones hooked with on()) won't be executed. If delegation matters, don't use it.
If your goal is to prevent the default action of the link and do stuff in JS, use event.preventDefault instead. The event object is passed in as the first argument in the handler.
$('#project_create').click(function(event) {
event.preventDefault();
// rest of the code
});
In addition to what the other answers suggest, you can also execute the location.reload method asynchronously using setTimeout:
setTimeout(function() { location.reload(); }, 1);
return false;
EDIT: The entire idea of running an asynchonous AJAX request and reloading the page immediately afterwards is flawed, of course, since the AJAX request may not have been completed by the time you reload the page.
You should therefore use a callback, as suggested by the accepted answer. Alternatively, you could use a synchronous AJAX request, but that would freeze execution until the request has completed, which is generally not desirable.

How can I use the JavaScript bodyOnLoad() function to be executed only once?

How can I use the JavaScript bodyOnLoad() function to be executed only once?
I just need it to be loaded only once and not each time that the page refreshes. Is there another function that can provide this functionality?
Thanks!
You can do it easily using hash:
(function() {
if(location.hash) return;
document.addEventListener("DOMContentLoaded",bodyOnLoad);
location.hash = "loaded";
})();
function bodyOnLoad() {
alert("alerting only on the first load");
}
It alerts only for the first time, if you try to reload by F5.
It is easy to implement, but if you use hash for something else or if you don't want to alter url, Kamil and Rajesh in the comments at your question have better ideas.
Note that DOMContentLoaded doesn't work in IE<9. For loder browser support, read about cross-browser fallback.
Also note that altering location.hash doesn't reload the page.

Send information about clicked link to the server before redirect

We're creating a click tracking app, that builds heatmaps. I'm writing a script which users are suppose to insert into their pages for tracking to work.
It works fine on elements, which doesn't require a redirect or form submit. For example, if I click on h1 or p or whatever, it works perfectly correct. But, if I click on a a, request to our server never happens before the normal redirect.
In the last couple of days I tried a lot of ways to do that. First of, I tried a normal AJAX call, since it was a cross-domain request I had to use JSONP, but again, that AJAX call did not have time to execute before the redirect. Adding async: false would have solved the problem, but it doesn't work with JSONP requests. So I decided to add a flag variable which indicates that it is safe to move on with redirect and used an empty while loop to wait until it becomes try in the ajax callback. But the while loop was blocking the execution flow, so callback never got a chance to set that variable to true. Here is some simplified code:
$(document).on('click', function (e) {
//part of the code is omitted
$.ajax({
url: baseUrl,
data: data,
type: "get",
dataType: "jsonp",
crossDomain: true,
complete: function (xhr, status,) {
itsSafeToMoveOn = true;
}
});
while(!itsSafeToMoveOn){}
return true;
});
The next thing I tried is to use unload page event to wait until total ajax calls in progress would become zero (I had a counter implemented) and then to move on with redirect. It worked in Firefox and IE, but in WebKit there was this error:
Error: Too much time spent in unload handler
After that I realized that I don't care about the server response and using img.src for the request would be an ideal fit for this case. So at this point code looks like this:
$(document).click(function (e) {
//part of the code is ommited
(new Image).src = baseUrl + '?' + data;
if (tag === "a" || clickedElement.parents().has("a")) {
sleep(100);
}
return true;
});
That way I increased the overall script performance slightly, but problem with links remains unchanged. The sleep function appears to be also blocking the execution flow and request never happens.
The only idea left is to return false from the event handler and than redirect manually to the clicked element's href or to call submit() on the form, but it will complicate things to much and believe me it's already a huge pain in the ass to debug this script in different browsers.
Does anyone have any other ideas?
var globalStopper = true;
$(document).on('click', function (e) {
if (globalStopper === false)
return true; //proceed with click if stopper is NOT set
else {
globalStopper = false; //release the breaks
$.ajax({
//blahblah
complete: function (xhr, status,) {
$(elem).click(); //when ajax request done - "rerun" the click
}
});
return false; //DO NOT let browser process the click
}
});
Also, instead of adding image, try adding script. And then add the script to the HEAD section. This way the browser will "wait" until it's loaded.
$(document).on('click', function (e) {
var scriptTag = document.createElement("script");
scriptTag.setAttribute("type", "text/javascript");
scriptTag.setAttribute("src", url);
document.getElementsByTagName("head")[0].appendChild(scriptTag);
return true;
}
I would take a look at the navigator sendBeacon API mentioned in this stack overflow answer or directly linked to here.
From the description on the site
navigator.sendBeacon(url, data) - This method addresses the needs of analytics and diagnostics code that typically attempts to send data to a web server prior to the unloading of the document.
You can save information to ajax request in cookies or localStorage and make any worker that will send information. Saving to cookies or localStorage is faster then ajax-request. You can do next:
$(document).click(function (e) {
var queue = localStorage.getItem('requestQueue');
queue.push(data);
localStorage.setItem('requestQueue',queue);
});
$(function(){
setInterval(function(){
var queue = localStorage.getItem('requestQueue');
while (queue.length > 0) {
var data = queue.pop();
$.ajax({
...
success: function(){
localStorage.setItem('requestQueue', queue);
}
});
}
},intervalToSendData);
});
So, when user click on link or send a form, data will be saved to storage and after user go to next page, this worker starts and send data to your server.
The JavaScript is basically executed in single thread. It is not possible to have your callback function executed and at the same time have an infinite loop waiting for a flag variable from it. The infinite loop will occupy the single execution thread and the callback will never be called.
Best approach is to cancel the default handler of your event and bubbling for it (basically return false if you are really building your tracking code with jQuery), and do the necessary actions (redirect page to the necessary address if a link was clicked or trigger other default actions), but this would take a lot of careful work to recreate all the possible combinations of actiona and callbacks.
Another approach is to:
1) Look for something specific to your code in the event data
2) If it is not present - make an AJAX call and in its callback re-trigger the same even on the same element, but this time with your specific bit added to the even data; after the AJAX call return false
3) If your specific bits are present in the data - simply do nothing, allowing the default event processing to take place.
The either approach may bite, however.
So if I understand right, you want your ajax logs completed before the page unloads and follows a link href. This sounds like a perfect case where you could consider using Deferreds in jQuery.
When your user clicks on anything that's supposed to take him away from the page, just check your promise status. If it's not resolved, you could throw a modal window over the page, and ask the user to wait til the progress is complete. Then, add a new pipe to your deferred, telling it to change the location href once everything is complete.
Let me know if this is the scenario. If it is, I'll explain in more detail. No use continuing if I didn't understand your requirement properly

How can I tell when changes to jquery html() have finished?

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);

Categories