How can I prevent jQuery from triggering multiple times even with unbinders? - javascript

I have both a PHP (demo.php) and JavaScript file (demo.js). I have a function that is supposed to create and object over a server, return it's key, use it to create its path, and then create a URL to it. However, only one URL can be active at a time so it is important that this only happens once. However, it is happening multiple times, and often sending me to a retired link. The last link created always works, but I want to prevent having to go looking for the links generated by this error.
My PHP (demo.php) looks very much like:
<div id="svg_graph">
// just a graph (code is irrelevant)
</div>
<div id="prompt">
<a id="startup" class="button"> Create New Object </a>
</div>
And my JS looks like:
function funcA(){
$("#svg_graph").mousedown(function(){
$(this).mousemove(function(e){
// irrelevant code
});
});
$("#svg_graph").mouseup(function(){
// $("a#startup").bind("click",function(){ // doesn't work either
$("a#startup").click(function(){
// Supposed to prevent multiple clicks, but doesn't
$("a#startup").unbind("click");
funcB();
});
$(this).unbind("mousemove");
});
}
// This function is being called more than once but I don't know why
function funcB(data){
var obj = createObj(data);
var url = "some predefined url" + obj.key;
window.location.replace(url);
)

These days, you should be using .off(), though .unbind() should stil work.
If unbinding is indeed the issue, then you might have better luck with :
$("#svg_graph").off('mouseup').on('mouseup', function() {
$("a#startup").off('click').on('click', function() {
funcB();
return false; // probably
});
});

Related

How can I create a dynamic product page using HTML, CSS, and Javascript

I currently only know javascript. But the thing is I looked up how to do it and some people talk about something called localStorage. I have tried this and for some reason when I jump to a new page those variables aren't kept. Maybe I am doing something wrong? I jump to a new page via
and all I want do do is select a certain image. take that image to a new page and add it to that page.
I tried using the localStorage variables and even turning it into JSON.stringify and doing JSON.parse when trying to call the localstorage to another script. It didn't seem to work for me. Is there another solution?
This is some of my code. There are two scripts.
document.querySelectorAll(".card").forEach(item => {
item.addEventListener("click", onProductClick);
})
var div;
var productImg;
var ratingElement;
var reviewCount;
var price;
function onProductClick(){
// This took a week to find out (this.id)
// console.log(this.id);
div = document.getElementById(this.id);
productImg = div.getElementsByTagName('img')[0];
ratingElement = div.getElementsByTagName('a')[2];
reviewCount = div.getElementsByTagName('a')[3]
price = div.getElementsByTagName('a')[4];
console.log(div.getElementsByTagName('a')[4]);
var productData = [div, productImg,ratingElement,reviewCount,price];
window.localStorage.setItem("price", JSON.stringify(price));
}
function TranslateProduct(){
console.log("Hello");
}
This is script 2
var productPageImage = document.getElementById("product-image");
var myData = localStorage['productdata-local'];
var value =JSON.parse(window.localStorage.getItem('price'));
console.log(value);
// function setProductPage(img){
// if(productImg != null){
// return;
// }
// console.log(window.price);
// }
To explain my thought process on this code in the first script I have multiple images that have event listeners for a click. I wanted to Click any given image and grab all the data about it and the product. Then I wanted to move that to another script (script 2) and add it to a dynamic second page. yet I print my variables and they work on the first script and somehow don't on the second. This is my code. in the meantime I will look into cookies Thank you!
Have you tried Cookies
You can always use cookies, but you may run into their limitations. These days, cookies are not the best choice, even though they have the ability to preserve data even longer than the current window session.
or you can make a GET request to the other page by attaching your serialized object to the URL as follows:
http://www.app.com/second.xyz?MyObject=SerializedData
That other page can then easily parse its URL and deserialize data using JavaScript.
you can check this answer for more details Pass javascript object from one page to other

How to automatically reload a web page

I want my website page to reload once when it has already opened for the first time. I wrote this function in my javascript file for that...
var i;
$(document).ready(function(){
for ( i=0;i<1;i++){
if(i===0){
location.reload();
break;
}
}
});
But the page keeps reloading again and again as if the above function was a recursive one.
How do I do this?
P.S I'm doing it because of this issue.
<script type='text/javascript'>
(function() {
if( window.localStorage ) {
if( !localStorage.getItem('firstLoad') ) {
localStorage['firstLoad'] = true;
window.location.reload();
} else
localStorage.removeItem('firstLoad');
}
})();
</script>
Here is what's happening:
The page loads for the first time, jQuery calls any handlers on the document.ready event
The page reloads
The document.ready call is made again
repeat
Out of curiosity, why would you want to do that? And why do you have a for loop that will run for one iteration?
Also, to answer your question as far as I know the only way to make sure the page doesn't reload is use a cookie that lasts for about 5 seconds. Then, on document.ready check for that cookie and if it exists then don't reload.
You must either set a cookie (or use javascript's localStorage), or use xhr to retrieve a value held on a remote server.
If you want to use cookies, it's as simple as
document.cookie = "username=John Doe";
where the document.cookie is a query string of the form (x=y;a=b;n=z)
If you want the page to reload every time the user vists, be sure to unset the cookie once you've done any necessary processing when a page reload has been set.
$( window ).load(function() {
if (window.location.href.indexOf('reload')==-1) {
window.location.replace(window.location.href+'?reload');
}
});
Code is ok. But if the page is opened from another page with a link to an id (.../page.html#aa) the code only works with firefox. With other browsers reload the page without going to id. (Sorry for my english).
I found the solution with this code. It is assumed that the page is refreshed no earlier than one hour. Otherwise, add minutes to the oggindex variable.
<script>
var pagina = window.location.href;
var d = new Date();
var oggiindex = d.getMonth().toString()+'-'+d.getDate().toString()+'-'+d.getHours().toString();
if (localStorage.ieriindex != oggiindex)
{
localStorage.setItem("ieriindex", oggiindex);
window.location.replace(pagina);
}
</script>
Yours code executed each time $(document).ready(), so it's not surprise that your loop is infinity - each load finished as ready state.
If you give more detailed requirements we can solve it with no using window object as data holder. It's bad way but you can set it for test.
Window object stores variables not depend on reload because it's higher then document.
Let's try:
if( window.firstLoad == undefined ){
// yours code without any loop
// plus:
window.firstLoad = false;
}
You can make it with localStorage API.
Check this link also, it's giving more information about window object variables:
Storing a variable in the JavaScript 'window' object is a proper way to use that object?

Detect when an iframe is loaded

I'm using an <iframe> (I know, I know, ...) in my app (single-page application with ExtJS 4.2) to do file downloads because they contain lots of data and can take a while to generate the Excel file (we're talking anything from 20 seconds to 20 minutes depending on the parameters).
The current state of things is : when the user clicks the download button, he is "redirected" by Javascript (window.location.href = xxx) to the page doing the export, but since it's done in PHP, and no headers are sent, the browser continuously loads the page, until the file is downloaded. But it's not very user-friendly, because nothing shows him whether it's still loading, done (except the file download), or failed (which causes the page to actually redirect, potentially making him lose the work he was doing).
So I created a small non-modal window docked in the bottom right corner that contains the iframe as well as a small message to reassure the user. What I need is to be able to detect when it's loaded and be able to differenciate 2 cases :
No data : OK => Close window
Text data : Error message => Display message to user + Close window
But I tried all 4 events (W3Schools doc) and none is ever fired. I could at least understand that if it's not HTML data returned, it may not be able to fire the event, but even if I force an error to return text data, it's not fired.
If anyone know of a solution for this, or an alternative system that may fit here, I'm all ears ! Thanks !
EDIT : Added iframe code. The idea is to get a better way to close it than a setTimeout.
var url = 'http://mywebsite.com/my_export_route';
var ifr = $('<iframe class="dl-frame" src="'+url+'" width="0" height="0" frameborder="0"></iframe>');
ifr.appendTo($('body'));
setTimeout(function() {
$('.dl-frame').remove();
}, 3000);
I wonder if it would require some significant changes in both frontend and backend code, but have you considered using AJAX? The workflow would be something like this: user sends AJAX request to start file generating and frontend constantly polls it's status from the server, when it's done - show a download link to the user. I believe that workflow would be more straightforward.
Well, you could also try this trick. In parent window create a callback function for the iframe's complete loading myOnLoadCallback, then call it from the iframe with parent.myOnLoadCallback(). But you would still have to use setTimeout to handle server errors/connection timeouts.
And one last thing - how did you tried to catch iframe's events? Maybe it something browser-related. Have you tried setting event callbacks in HTML attributes directly? Like
<iframe onload="done()" onerror="fail()"></iframe>
That's a bad practice, I know, but sometimes job need to be done fast, eh?
UPDATE
Well, I'm afraid you have to spend a long and painful day with a JS debugger. load event should work. I still have some suggestions, though:
1) Try to set event listener before setting element's src. Maybe onload event fires so fast that it slips between creating element and setting event's callback
2) At the same time try to check if your server code plays nicely with iframes. I have made a simple test which attempts to download a PDF from Dropbox, try to replace my URL with your backed route's.
<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
<iframe id="book"></iframe>
<button id="go">Request downloads!</button>
<script>
var bookUrl = 'https://www.dropbox.com/s/j4o7tw09lwncqa6/thinkpython.pdf';
$('#book').on('load', function(){
console.log('WOOT!', arguments);
});
$('#go').on('click', function(){
$('#book').attr('src', bookUrl);
});
</script>
UPDATE 2
3) Also, look at the Network tab of your browser's debugger, what happens when you set src to the iframe, it should show request and server's response with headers.
I've tried with jQuery and it worked just fine as you can see in this post.
I made a working example here.
It's basically this:
<iframe src="http://www.example.com" id="myFrame"></iframe>
And the code:
function test() {
alert('iframe loaded');
}
$('#myFrame').load(test);
Tested on IE11.
I guess I'll give a more hacky alternative to the more proper ways of doing it that the others have posted. If you have control over the PHP download script, perhaps you can just simply output javascript when the download is complete. Or perhaps redirect to a html page that runs javascript. The javascript run, can then try to call something in the parent frame. What will work depends if your app runs in the same domain or not
Same domain
Same domain frame can just use frame javascript objects to reference each other. so it could be something like, in your single page application you can have something like
window.downloadHasFinished=function(str){ //Global pollution. More unique name?
//code to be run when download has finished
}
And for your download php script, you can have it output this html+javascript when it's done
<script>
if(parent && parent.downloadHasFinished)
parent.downloadHasFinished("if you want to pass a data. maybe export url?")
</script>
Demo jsfiddle (Must run in fullscreen as the frames have different domain)
Parent jsfiddle
Child jsfiddle
Different Domains
For different domains, We can use postMessage. So in your single page application it will be something like
$(window).on("message",function(e){
var e=e.originalEvent
if(e.origin=="http://downloadphp.anotherdomain.com"){ //for security
var message=e.data //data passed if any
//code to be run when download has finished
}
});
and in your php download script you can have it output this html+javascript
<script>
parent.postMessage("if you want to pass data",
"http://downloadphp.anotherdomain.com");
</script>
Parent Demo
Child jsfiddle
Conclusion
Honestly, if the other answers work, you should probably use those. I just thought this was an interesting alternative so I posted it up.
You can use the following script. It comes from a project of mine.
$("#reportContent").html("<iframe id='reportFrame' sandbox='allow-same-origin allow-scripts' width='100%' height='300' scrolling='yes' onload='onReportFrameLoad();'\></iframe>");
Maybe you should use
$($('.dl-frame')[0].contentWindow.document).ready(function () {...})
Try this (pattern)
$(function () {
var session = function (url, filename) {
// `url` : URL of resource
// `filename` : `filename` for resource (optional)
var iframe = $("<iframe>", {
"class": "dl-frame",
"width": "150px",
"height": "150px",
"target": "_top"
})
// `iframe` `load` `event`
.one("load", function (e) {
$(e.target)
.contents()
.find("html")
.html("<html><body><div>"
+ $(e.target)[0].nodeName
+ " loaded" + "</div><br /></body></html>");
alert($(e.target)[0].nodeName
+ " loaded" + "\nClick link to download file");
return false
});
var _session = $.when($(iframe).appendTo("body"));
_session.then(function (data) {
var link = $("<a>", {
"id": "file",
"target": "_top",
"tabindex": "1",
"href": url,
"download": url,
"html": "Click to start {filename} download"
});
$(data)
.contents()
.find("body")
.append($(link))
.addBack()
.find("#file")
.attr("download", function (_, o) {
return (filename || o)
})
.html(function (_, o) {
return o.replace(/{filename}/,
(filename || $(this).attr("download")))
})
});
_session.always(function (data) {
$(data)
.contents()
.find("a#file")
.focus()
// start 6 second `download` `session`,
// on `link` `click`
.one("click", function (e) {
var timer = 6;
var t = setInterval(function () {
$(data)
.contents()
.find("div")
// `session` notifications
.html("Download session started at "
+ new Date() + "\n" + --timer);
}, 1000);
setTimeout(function () {
clearInterval(t);
$(data).replaceWith("<span class=session-notification>"
+ "Download session complete at\n"
+ new Date()
+ "</span><br class=session-notification />"
+ "<a class=session-restart href=#>"
+ "Restart download session</a>");
if ($("body *").is(".session-restart")) {
// start new `session`,
// on `.session-restart` `click`
$(".session-restart")
.on("click", function () {
$(".session-restart, .session-notification")
.remove()
// restart `session` (optional),
// or, other `session` `complete` `callback`
&& session(url, filename ? filename : null)
})
};
}, 6000);
});
});
};
// usage
session("http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf", "ECMA_JS.pdf")
});
jsfiddle http://jsfiddle.net/guest271314/frc82/
In regards to your comment about to get a better way to close it instead of setTimeout. You could use jQuery fadeOut option or any of the transitions and in the 'complete' callback remove the element. Below is an example you can dump right into a fiddle and only need to reference jQuery.
I also wrapped inside listener for 'load' event to not do the fade until the iFrame has been loaded as question originally was asking.
// plugin your URL here
var url = 'http://jquery.com';
// create the iFrame, set attrs, and append to body
var ifr = $("<iframe>")
.attr({
"src": url,
"width": 300,
"height": 100,
"frameborder": 0
})
.addClass("dl-frame")
.appendTo($('body'))
;
// log to show its part of DOM
console.log($(".dl-frame").length + " items found");
// create listener for load
ifr.one('load', function() {
console.log('iframe is loaded');
// call $ fadeOut to fade the iframe
ifr.fadeOut(3000, function() {
// remove iframe when fadeout is complete
ifr.remove();
// log after, should no longer exist in DOM
console.log($(".dl-frame").length + " items found");
});
});
If you are doing a file download from a iframe the load event wont fire :) I was doing this a week ago. The only solution to this problem is to call a download proxy script with a tag and then return that tag trough a cookie then the file is loaded. min while yo need to have a setInterval on the page witch will watch for that specific cookie.
// Jst to clearyfy
var token = new Date().getTime(); // ticks
$('<iframe>',{src:"yourproxy?file=somefile.file&token="+token}).appendTo('body');
var timers = [];
timers[timers.length+1] = setInterval(function(){
var _index = timers.length+1;
var cookie = $.cooke(token);
if(typeof cookie != "undefined"){
// File has been downloaded
$.removeCookie(token);
clearInterval(_index);
}
},400);
in your proxy script add the cookie with the name set to the string sent bay the token url parameter.
If you control the script in server that generates excel or whatever you are sending to iframe why don't you put a UID flag and store it in session with value 0, so... when iframe is created and server script is called just set UID flag to 1 and when script is finished (the iframe will be loaded) just put it to 2.
Then you only need a timer and a periodic AJAX call to the server to check the UID flag... if it's set to 0 the process doesn't started, if it's 1 the file is creating, and finally if it's 2 the process has been ended.
What do you think? If you need more information about this approach just ask.
What you are saying could be done for images and other media formats using $(iframe).load(function() {...});
For PDF files or other rich media, you can use the following Library:
http://johnculviner.com/jquery-file-download-plugin-for-ajax-like-feature-rich-file-downloads/
Note: You will need JQuery UI
You can use this library. The code snippet for you purpose would be something like:
window.onload = function () {
rajax_obj = new Rajax('',
{
action : 'http://mywebsite.com/my_export_route',
onComplete : function(response) {
//This will only called if you have returned any response
// instead of file from your export script
// In your case 2
// Text data : Error message => Display message to user
}
});
}
Then you can call rajax_obj.post() on your download link click.
Download
NB: You should add some header to your PHP script so it force file download
header('Content-Disposition: attachment; filename="'.$file.'"');
header('Content-Transfer-Encoding: binary');
There is two solutions that i can think of. Either you have PHP post it's progress to a MySQL table where from frontend will be pulling information from using AJAX calls to check up on the progress of the generation. Using somekind of unique key that is being generated when accessing the page would be ideal for multiple people generating excel files at the same time.
Another solution would be to use nodejs & then in PHP post the progress of the excel file using cURL or a socket to a nodejs service. Then when receiving updates from PHP in nodejs you simply write the progress of the excel file for the right socket. This will cut off some browser support though. Unless you go through with it using external libraries to bring websocket support for pretty much all browsers & versions.
Hope this answer helped. I was having the same issue previous year. Ended up doing AJAX polling having PHP post progress on the fly.
Try this:
Note: You should be on the same domain.
var url = 'http://mywebsite.com/my_export_route',
iFrameElem = $('body')
.append('<iframe class="dl-frame" src="' + url + '" width="0" height="0" frameborder="0"></iframe>')
.find('.dl-frame').get(0),
iDoc = iFrameElem.contentDocument || iFrameElem.contentWindow.document;
$(iDoc).ready(function (event) {
console.log('iframe ready!');
// do stuff here
});

An event when document stops loading

I have a web page whose large parts are constructed on the front-end, in JavaScript. When the basic HTML is parsed and the DOM tree is constructed, the "ready" event triggers. At this event, a JavaScript code launches that continues with the construction of the web page.
$().ready(function() {
$(document.body).html('<img src = "img.png" />');
$.ajax({
url: 'text.txt'
});
});
As you can see in this tiny example,
new elements are added to DOM
more stuff, like images is being loaded
ajax requests are being sent
Only when all of this finishes, when the browser's loading progress bar disappears for the first time, I consider my document to be actually ready. Is there a reasonable way to handle this event?
Note that this:
$().ready(function() {
$(document.body).html('<img src = "img.png" />');
$.ajax({
url: 'text.txt'
});
alert('Document constructed!');
});
does not suffice, as HTTP requests are asynchronous. Maybe attaching handlers to every single request and then checking if all have finished is one way to do what I want, but I'm looking for something more elegant.
Appendix:
A more formal definition of the event I'm looking for:
The event when both the document is ready and the page's dynamic content finishes loading / executing for the first time, unless the user triggers another events with mouse moving, clicking etc.
As described here, you can sign up for load events for individual images when they finally load. However, as also described there, that may not be reliable across browsers or if the image is already in the browser's cache. So I think at least part of that is not 100% trustworthy. With that said, you can easily hook to a bunch of different asynchronous events and then only react when all of them have completed. For example:
$(document.body).html('<img src="img.png" id="porcupine"/>');
var ajaxPromise1 = $.get(...);
var ajaxPromise2 = $.get(...);
var imgLoadDeferred1 = $.Deferred();
$("#porcupine").load(
function() {
imgLoadDeferred1.resolve();
}
);
$.when(ajaxPromise1, ajaxPromise2, imgLoadDeferred1).done(
function () {
console.log("Everything's done.");
}
);
If you want to allow for the fact that the image load may never generate an event, you could also set a timer which would go off eventually and try to resolve the imgLoadDeferred1 if it is not already resolved. That way you don't get stuck waiting for an event that never happens.
You are looking for .ajaxStop().
Used like so:
$("#loading").ajaxStop(function(){
$(this).hide();
});
It does't really matter what DOM element you attach to unless you want to use this as is done in the above example.
Source: http://api.jquery.com/ajaxStop/
Add global counter to ajax calls in way that when ajax request is ready it calls addToReadyCounter(); function that will +1 to glogal var readyCount;.
Within same function check your readyCount and trigger event handler
var readyCount = 0, ajaxRequestCount = 5;
function addToReadyCount() {
++readyCount;
if (readyCount >= ajaxRequestCount) {
documentReadyEvent();
}
}
$.ajax(
...
success: function(data){
addToReadyCount();
}
...);
If you are only using success: ... for addToReaadyCount documentReadyEvent() only triggers if all call succeed, use other .ajax() event handlers to increment counter in case of error.

How do you get a javascript function to only be running one instance at any one point?

I have some code that I'd like to run on a page. My problem is that I don't want it to be run more than once at any one point. It can run multiple times, but just not while another instance of itself is running. I'm using jQuery and loading ajax content.
I just need something that prevents users from clicking hundereds of times and building up the que and pinging my server heaps.
Is this possible?
Thanks.
If it's just the one function being called in different areas, sounds like you're just wanting some sort of basic semaphore test, e.g:
isRunning = false;
function whatever() {
if (!isRunning) {
isRunning = true;
$.ajax(...., function() {
isRunning = false;
});
}
}
This question looks slightly related.
RE your edit: It's also worth noting that whatever javascript solution you put in place here to avoid a barrage of requests should additionally be considered on the server side.
Just keep a variable to flag whether it's running or not:
var running = true;
... complete: function() { running = false; }
I think it would be more user-friendly if you disabled your controls for the time the ajax request is on its way.
This way not only would you be saved from mass clicking, but also the user would know that there is no point in killing the mouse.

Categories

Resources