How to stop JavaScript execution when Ajax loads new content? - javascript

In the website I am working on, the content is loaded with Ajax, along with any JavaScript included. I am doing this because all the pages are of the same layout, but only the content is the different.
The problem is when a "content" has JavaScript in it, I was afraid the script will continue executing even after new content has been loaded. So I made this test to make sure.
First a main page that will load 2 other pages :
<script src="scripts/jquery.min.js"></script>
<script>
function loadPage1(){
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4 && xhttp.status == 200) {
response = xhttp.responseText;
$("#content").remove();
$("#mainContent").append(response);
}
};
xhttp.open("GET", "page1.html", true);
xhttp.send();
}
function loadPage2(){
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4 && xhttp.status == 200) {
response = xhttp.responseText;
$("#content").remove();
$("#mainContent").append(response);
}
};
xhttp.open("GET", "page2.html", true);
xhttp.send();
}
</script>
<button onclick="loadPage1()">Load page 1</button>
<button onclick="loadPage2()">Load page 2</button>
<div id="mainContent">
</div>
And then the 2 pages with JavaScript content, basically just spamming the console with "I am page x" every second.
Page 1:
<div id="content">
<h1>Page 1</h1>
<script type="text/javascript">
setInterval(function(){
console.log("I am page 1");
},1000);
</script>
</div>
Page 2:
<div id="content">
<h1>Page 2</h1>
<script type="text/javascript">
setInterval(function(){
console.log("I am page 2");
},1000);
</script>
</div>
The Ajax loads fine, I can see the h1 changing from Page 1 to Page 2. But in the console, I can see that the first script is still spamming even after the content has been removed.
Is there a way to prevent such behavior? Preferably while keeping each script in it's proper place, and not by moving all scripts to the "main page" .
EDIT: To avoid confusion, setInterval() is not the main problem, it's merely an example. I'm asking how do you usually deal with such a problem with Ajax and JavaScript

Even though the 1st <script> block has been replaced, you see the console logs because of the way javascript works.
Your anonymous function
function(){
console.log("I am page 1");
}
will live on and keep executing till you call clearInterval or move away from this page.
This is also the case when you add a click handlers eg $('#some_button').on("click",function(evt){/*do something*/});
Even if you have a variable declared, like <script>var x='data1';</script>
and then you delete the enclosing <script> tag, the variable x will continue to exist.
In both cases, the references to the functions and variables are stored somewhere. References to the setInterval, and click handler function are held by the event handlers. A reference to any var and function you declare is held in the window object (unless you use a closure).
In your newly loaded script, you could re-assign stuff: the behavior of a function will be the last loaded behavior. Any calls made to that function will execute the new instructions because they too have now been assigned to the window object(scope).
In summary,
You will need to clear interval
The functions and vars you declare will exist till you change them
Re:Question in comment
oh, so If I declare a new behavior for a function, it wouldn't give me an error like it would with static programming languages. It 2ould save me a lot of work if I can keep the namings the same. I guess all I need to do is clearInterval, and keep whatever functions as they are.
Right but rather than draw a parallel to other programming languages, try and see these as instructions that are interpreted immediately when you inject the script tag into the DOM. So, what you are doing is actually just re-assigning the properties of the window object. To understand this better, open the developer console on chrome and run these in order:
window.hasOwnProperty("xy")
var xy=1
window.hasOwnProperty("xy")
window.xy
xy="foo"
typeof window.xy
typeof window
This should help you understand how the JavaScript engine is treating your code.

You removed/replaced the content, not the script, you are still on the same page. also you only load new data into that page, so your script is still running in that page unless its stopped explicitly or when the page reloads.

Related

Siemens webserver Image change using JavaScript in html

Im trying to dynamically change a picture in html page according to a true/false condition using javascript.
If the variable is 0 an image and if it was 1 another picture should be shown in html page.
There are many pictures in a project that need to be changed i need a function that can do this application. But i dont know if it can be done with a single function or i should use a function for each variable.
Is siemens webserver the variable change is applied automatically by typing :="X":
when x is 0 instead of :="X": the number 0 is replaced and for the 1 instead of :="X": the number 1 is replaced.
Im familiar with html coding and how to change the picture using the image name and adding 0 or 1 after image name.
For example i name a picture stop0.png and another picture stop1.png . now in the html code i type stop:="X":.png in this way picture changes according to variable x
But this method needs the page to be refreshed to show the change. I want to do this in the easiest way possible without page refresh.
hmi should be designed in one page (for example named test.htm) and another html page with following code would update every thing in "test.htm" every second.
<!DOCTYPE html>
<html>
<body>
<div id="demo">
<h2>auto update page</h2>
</div>
<script>
function loadDoc() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("demo").innerHTML = this.responseText;
}
};
xhttp.open("GET", "test.htm", true);
xhttp.send();
}
setInterval("loadDoc()", 1000);
</script>
</body>
</html>

Running script after DOM is finished

I'm dynamically creating menu items to be placed on the menu bar, based on the headers of external files, imported with an XMLHttpRequest(). As I navigate through the different pages, this menu bar is dynamically updated.
This works fine.
I load each document into an individual div element, in a sort of stack of cards, and hide all but the setVisible() page.
window.onload = function(){
loadContent("personalinfo");
loadContent("academicalinfo");
loadContent("employmentinfo");
setVisible("academicalinfo");
/*setMenues("academicalinfo");*/
}
The last action of setVisible() is to call setMenues(), which is responsible for reading all of the headers of said main window. This also works fine-ish.
function loadMenues(file) {
var rightmenu = document.getElementById("right-menu");
while(rightmenu.firstChild){
rightmenu.removeChild(rightmenu.firstChild);
}
[].forEach.call(document.getElementById(file).children,
function(custompaddingchild) {
/* searching for h1 and adding menu items happens here */
}
);
The problem arises when the DOM elements are not loaded yet, as when the page loads. Since there are no ElementsById(file) in the document element until the page is completely rendered, it fails to add the menu items onload.
I have tried adding an EventListener on the "load" event of the window and on the document, I have tried executing the function on the end of the body of the main page, and on the on onload= argument of <body> (which runs even before the subpages are captured, leaving me with a blank page instead of the actual content), but as it seems, none of them seems to happen after the page is completely loaded.
Adding a 2 second delay before running the functions is not an effective solution. Besides, adding a delay to the onload function will not affect the result, and will only increase loading time by two seconds.
Clicking any of the menues which update the menues work as intended. The only problem is onload.
<div class="menu-item" onclick="setVisible('personalinfo');"><span>Personal information</span></div>
How can I make sure the page delays the setVisible() function until after the page is rendered? All the sources I've found claim the "load" event is triggered after the page is rendered, but it doesn't seem to be triggered in my case. The DOMContentLoaded event isn't triggered either, but I suspect I don't want this one. The click event, or a scroll event on the window, in contrast, do trigger correctly.
Mozilla, Window: load event
Javascript.info, Page: DOMContentLoaded, load, beforeunload, unload
Edit: As per request, here is loadContent():
function loadContent(file) {
var request = new XMLHttpRequest();
request.open("GET", file+".html?_=" + new Date().getTime());
request.onreadystatechange=function(){
var loadedcontent = request.responseText;
document.getElementById(file).innerHTML = loadedcontent;
}
request.send();
}
Edit 2:
Full code is available at https://github.com/mazunki/portfolioweb-stackoverflowquestion
You need to use promises: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
Try this
function loadContent(file) {
return new Promise(function (resolve, reject) {
var request = new XMLHttpRequest();
request.open("GET", file+".html?_=" + new Date().getTime());
request.onreadystatechange=function(){
var loadedcontent = request.responseText;
document.getElementById(file).innerHTML = loadedcontent;
resolve();
}
request.send();
});
}
window.onload = function(){
// when you finish all the content loading
Promise.all([
loadContent("personalinfo"),
loadContent("academicalinfo"),
loadContent("employmentinfo")
]).then(function() {
// Load menu
setVisible("academicalinfo");
/*setMenues("academicalinfo");*/
})
}
Sorry, but you tried to use the "library" https://github.com/ded/domready ? I think that is the simpliest solutuion for you.
Then you code would be:
function loadMenues(file) {
domready(function () {
var rightmenu = document.getElementById("right-menu");
while(rightmenu.firstChild){
rightmenu.removeChild(rightmenu.firstChild);
}
[].forEach.call(document.getElementById(file).children,
function(custompaddingchild) {/* searching for h1 and adding menu items happens here */ }
);
})
}
While this only stands as a fallback solution, and is not reliable over slow network connections, I solved it by placing the following inside the menuhandling.js file, called with <script src="menuhandling.js" type="text/javascript" defer></script>:
setTimeout(function(){
setVisible("personalinfo")
/*setMenues("personalinfo")*/
},500) // half a second
loadContent() is called for all files in stackhandling.js, with no delay, and not document.onload nor window.onload.

Automatic refresh using AJAX not working?

I've got a chat function in my website for two users to chat with each other, and I'm using JavaScript, AJAX, and PHP for it.
At the moment, it won't refresh the chat area automatically unless I submit a reply to the chat or refresh the page. I can't figure out why.
JavaScript Function
function checkReply(threadid) {
// XMLHttpRequest
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
document.getElementById("chatwrap").innerHTML = xmlhttp.responseText;
setInterval(checkReply(threadid), 10000);
}
}
xmlhttp.open("GET","inc/chatreply.php?chatid="+ threadid,true);
xmlhttp.send();
}
The event handler is on the <div> the responseText will end up in:
<div id="chatwrap" onload="checkReply('.$threadid.')"></div>
$threadid is a GET variable set at the top of the page:
$threadid = (int)$_GET['chatid'];
UPDATE
Seeing that you were in a PHP state already, the syntax was correct.
The problem is that the div doesn't possess an onload event. You'll have to attach it to the body tag, or include a script in the head or below the div, as it will only execute after the div has been rendered.
You're not including the PHP variable correctly. At the moment you are passing the string .$threadid. to the checkReply function. You will have to drop into PHP mode again before using this syntax by using the delimiters <?php & ?>.
<div id="chatwrap" onload="checkReply(<?php echo $threadid; ?>)"></div>
This should work better.

Randomly load different JavaScript between each page load using loadExternalHTMLPage

I have a site and I want it to randomly load a different HTML5 Javascript animation each time the page is loaded, JavaScript is by far one of the weakest of my skills and I appreciate any help in advance and if this happens to be duplicate (I've tried searching) then please vote for the question to be closed.
Basically the method I have used is a dirty one and most likely the reason its not working, basically I tried randommath and had no luck and put this down to my JS skills being extremely weak, the alternative method which looked easier doesn't work either and this is basically inserting a HTML on page load, so for example a.html and b.html which both contain different scripts.
This is what my code looks like:
HTML
<html>
<head>
<script src="js/insert.js"></script><!-- This inserts the Random Page -->
</head>
<body onload="loadExternalHTMLPage()">
<div id="injectjs"> </div>
<canvas="id"> </canvas>
<script src="js/animation-lib-pageA.js"></script><!--Library for pageA -->
<script src="js/animation-lib-pageB.js"></script><!--Library for pageB -->
</body>
</html>
Inject.js
function loadExternalHTMLPage() {
var xmlhttp;
var pagesToDisplay = ['a.html', 'b.html'];
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
} else {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
document.getElementById("jsinsert").innerHTML = xmlhttp.responseText;
}
}
var randomnumber = Math.floor(Math.random() * pagesToDisplay.length);
xmlhttp.open("GET", pagesToDisplay[randomnumber], true);
xmlhttp.send();
}
Most JS Guru's should be able to see that I'm randomly inserting a.html and b.html on page load, now this works but the problem is the scripts contained within a.html and b.html are not executing. (using firebug I can clearly see that the scripts are being inserted as intended).
so for example a and b looks like:
a.html
<script> window.onload = function () { }</script>
b.html
<script> window.onload = function () { } </script>
Basically the code within A and B are valid and work fine within this insert and I've filled the above examples as just a placeholder. A and B both contain JavaScript that executes animation contained within the canvas but it doesn't work at present and I suspect its something to do with the fact I'm loading the scripts after the page has been loaded. Thanks in advance.
You can randomly load the html for A or B and then run its animation.
This example uses jQuery which makes the task of loading remote html easier. Here is a link to the jquery .load function which replaces an existing elements html with the downloaded html: http://api.jquery.com/load/ If you want pure javascript, you can use that [messier!] alternative, but the logic remains the same.
These are the steps:
Be sure the web page has loaded,
Randomly pick A or B to load/execute,
Replace the html in #injectjs with htmlForA or htmlForB,
Wait until the html has been fully replaced,
Run the appropriate animationA or animationB.
Here is starter code. (Be sure you include the jQuery library)
<script>
window.onload(){
// randomly load html+animation A or B
if(Math.random()<.50){
$('#injectjs').load(
'yourSite.com/HtmlForA.html', // first load the html for A
function(){ animationA(); } // then run animationA
);
}else{
$('#injectjs').load(
'yourSite.com/HtmlForB.html', // first load the html for B
function(){ animationB(); } // then run animationB
);
}
}
</script>
You can always use eval() to execute the content you downloaded ... :) (not recommended).
Or you can modify the html page on server to include the random script you want before serving the page to the user (you don't state platform) since it's anyways changed at page load.

Use JavaScript to prevent a later `<script>` tag from being evaluated?

This is a bit of an oddball use case, but I have my reasons:
I'd like to be able to write
<script type="text/javascript" src="first.js"></script>
<script type="text/javascript" src="second.js"></script>
in my markup and, using the code in first.js, prevent or delay the execution of second.js. Is this possible, in any browser? What if the contents of first.js are inlined? (If it helps, assume that the second script tag has an id attribute.)
Since I've gotten a couple of answers that missed what I'm getting at, I should clarify:
The solution must be entirely within first.js. Anything that require changes to the original HTML of the page, or to second.js, is not acceptable.
It is acceptable to load second.js via Ajax and execute it using eval. That's the easy part. The hard part is preventing the immediate execution of second.js.
Assume that you don't know what's in second.js. So, you can't just replace each global function called by second.js with a no-op function. (Plus, this would almost certainly lead to errors.)
If you know of a solution that works in some browsers but not in others, I'd love to hear it.
Example: To make this a little more concrete, let's say that the code
<script type="text/javascript">
function func() {
window.meaningOfLife = 42;
window.loadSecond();
};
setTimeout(func, 10);
</script>
precedes the two script includes, and that second.js contains the line
if (window.meaningOfLife !== 42) {throw new Error();}
first.js should be able to prevent this error by delaying second.js from executing until window.loadSecond is run. (Assume the implementation of window.loadSecond is also in first.js.) It is not allowed to touch window.meaningOfLife.
Update: Alohci's answer meets these requirements, but only on the condition that the second script tag comes immediately after the first, with nothing but whitespace in between. If someone could extend his hack to avoid that requirement, without introducing other unwanted consequences, that would be amazing...
Given your specific requirements set, this is actually quite simple and should work completely cross-browser. It does require however, that first.js immediately precedes second.js without anything between them except white space.
First, let's assume that the HTML looks like this:
<!DOCTYPE html>
<html>
<head>
<title>Test Case</title>
<meta charset="UTF-8" />
<script type="text/javascript">
function func() {
window.meaningOfLife = 42;
window.loadSecond();
};
</script>
<script type="text/javascript" src="first.js"></script>
<script type="text/javascript" src="second.js"></script>
</head>
<body>
<p>Lorem ipsum dolor sit amet ...</p>
Run Func()
</body>
</html>
I've removed the setTimeout because that can cause func() to run before start.js runs causing a "loadSecond is not defined" error. Instead, I've provided an anchor to be clicked on to run func().
Second, let's assume that second.js looks like this:
document.body.appendChild(document.createTextNode("second.js has run. "));
if (window.meaningOfLife !== 42) {throw new Error();}
Here, I've just added a line to append some text to the document body, so that it is easier to see when second.js actually runs.
Then the solution for first.js is this:
function loadSecond()
{
var runSecond = document.createElement("script");
runSecond.setAttribute("src", "second.js");
document.body.appendChild(runSecond);
}
document.write("<script type='application/x-suppress'>");
The loadSecond function is just there to run second.js when func() runs.
The key to the solution is the document.write line. It will inject into the HTML <script type='application/x-suppress'> between the close script tag of first.js and the open script tag of second.js.
The parser will see this and start a new script element. Because the type attribute has a value which is not one that the browser recognises as being JavaScript, it will not attempt to run its content. (So there are an infinite number of possible type attribute values you could use here, but you must include a type attribute, as in its absence, the browser will assume that the script's content is JavaScript.)
The second.js script's opening tag will then be parsed as text content of the new script element and not executed. Finally the second.js script's closing tag will be re-purposed to close the new script element instead, which means that the remainder of the HTML is parsed correctly.
You can see a working version at http://www.alohci.net/static/jsprevent/jsprevent.htm
In first.js, set var shouldILoad = true
Then, load second.js this way:
<script>
if (shouldILoad) {
(function() {
var myscript = document.createElement('script');
myscript.type = 'text/javascript';
myscript.src = ('second.js');
var s = document.getElementById('myscript');
s.parentNode.insertBefore(myscript, s);
})();
}
</script>
(where 'myscript' is the ID of some element before which you'd like to insert the new Script element)
As far as I know, you can't. If the markup looks like
<script type="text/javascript" src="first.js"></script>
<script type="text/javascript" src="second.js"></script>
you can't access the second script element from within first.js, as it hasn't been added to the DOM at the moment the first script runs (even not if you assign an id to the second element). It doesn't matter whether the code of second.js is put inline or in an external file.
The only thing I don't understand is your second point. First you say that you can't control the markup of the document, but then you state it is possible to load second.js dynamically (using AJAX).
Following article describes the way you could block (3-rd party) scripts loading/execution from your script (including the both tag in the page head and dynamically added tags).
To handle existing tags on a page:
Use a MutationObserver to observe script elements insertion and inside the MutationObserver callback backup the script (to enable/insert it later) and change the script type to "javascript/blocked" (not works in IE, Edge, Firefox). Also you could handle deprecated (but working) beforescriptexecute event in Firefox to prevent script load.
Manually set type "javascript/blocked" (works everywhere including IE and Edge) like
<script type="text/javascript" type="javascript/blocked" src="second.js"></script>, then backup it in MutationObserver callback and re-add it later.
To handle dynamically added tags
Monkey-patch the document.createElement.
Override ‘src’ and ‘type’ descriptors on the HTMLScriptElement prototype.
Also this guys provide a yett library with the approach described in the article.
All <script> tags have their own execution context, which makes it nearly impossible to interfere with each other. Of course you've got the (infamous) global object (referenced by window in browsers).
Preventing the execution of second.js is rather simple: break it!
Assuming that second.js tries to call document.getElementById for example:
Working example of breaking jQuery, then loading later (with dependecies).
Tested on: IE 6+, FF 3.6+, Chrome
end of first.js
var execute;
// saving our position
var scripts = document.getElementsByTagName("script");
var i = scripts.length;
// breaking getElementById
var byId = document.getElementById;
document.getElementById = null;
var interval = setInterval(function () {
if (i != scripts.length) {
var second = scripts[i];
// stop polling
clearInterval(interval);
// fix getElementById
document.getElementById = byId;
// set the delayed callback
execute = function (onload) {
var script = document.createElement("script");
script.src = second.src;
script.onload = script.onreadystatechange = onload;
document.getElementsByTagName("head")[0].appendChild(script);
};
}
}, 100);
anytime you wanna execute second.js
execute(function(){
// second.js dependant code goes here...
});
Note: the onload parameter for execute is optional.
Just to see if this was possible, I had first.js send a synchronous XHR to a PHP file, and had the PHP file delete second.js. When the readyState reached '4', I had the JS alert something, to stop the thread. Then I went and checked the server... Yeah, second.js was deleted. And yet, it wouldn't work. I'd close the alert box, and the code that was in second.js would still be executed, despite the fact that the file was gone.
I don't really know what this means, but the answer to your question is probably, "No, it's not possible."
you may use setTimeout() to delay the execution of some code

Categories

Resources