This code is just stuck in the Body it does exactly what I expect but I don't understand WHY.
Particularly I dont see how the webservice gets called, it looks like the script adds the call to the Head section and maybe reloads the page but I'm not sure and don't like not knowing or what this line means - script.onload = script.onreadystatechange = function ()
Can anyone explain please?
var script = document.createElement("script"),
head = document.getElementsByTagName("head")[0],
url = "https://services.postcodeanywhere.co.uk/PostcodeAnywhere/Interactive/FindByPostcode/v1.00/json.ws?";
// Build the query string
url += "&Key=" + encodeURI(pca_Key);
url += "&Postcode=" + encodeURI(postcode);
url += "&CallbackFunction=PostcodeAnywhere_FindByPostcode_End";
script.src = url;
// Make the request
script.onload = script.onreadystatechange = function () {
if (!this.readyState || this.readyState === "loaded" || this.readyState === "complete") {
script.onload = script.onreadystatechange = null;
if (head && script.parentNode)
head.removeChild(script);
}
}
head.insertBefore(script, head.firstChild);
Stu
Basically, it's making a JSONP call to get some data from postcodeanywhere.co.uk. JSONP uses script elements. The script from postcodeanywhere.co.uk will call the function identified by the CallbackFunction argument on the script URL, passing it a JavaScript object literal, something like this:
PostcodeAnywhere_FindByPostcode_End({
name: value
})
You haven't shown it, but presumably there's a function with that name defined at global scope in the script making the call.
This is to work around the SOP, which doesn't allow ajax calls cross-origin.
How it does it is by creating a script element, assigning it the src URL, appending it to the page, and then hooking the readystate event so it can clean up after itself by removing the element again. (That last bit isn't quite what it could be, not all browsers fire the readystate event on script elements, to do this thoroughly you have to hook both readystate and load. Or do the cleanup in the callback. But having the script element lying around is harmless.) It should also be using encodeURIComponent, not encodeURI. And there's no need for the stuff with the head element, you can just append the script directly to document.documentElement.
This script grammatically creates <script/> HTML tag with given src URL and appends it to the beginning of the <head> in the page DOM:
<head>
<script src="https://services.postcodeanywhere.co.uk/PostcodeAnywhere/Interactive/FindByPostcode/v1.00/json.ws?&Key=...&Postcode=...&Postcode="></script>
<!-- the rest of the HEAD -->
</head>
It also attaches event handler to both onload and onreadystatechange. This event is triggered when the script is loaded. Interestingly, it removes the event handler and the <script/> tag once it is loaded, cleaning up after itself.
Probably the script runs some code immediately after being downloaded for its side-effects. E.g. it creates a global object or modifies the DOM. Once the script is done, it is removed for some reason.
1) It creates a DOM element,
2) puts it in the head element, thus downloads it.
3) Executes it
4) Removes the DOM element.
Note that any global functions and variables will be available later, so code from it could be executed, and variables accessed after it is removed.
Related
In the JS library I am writing, I have this loadScript func:
function loadScript(src, callback) {
var script = document.createElement('script');
script.src = src;
// script.type = "text/javascript";
// script.async = false;
if (typeof callback !== 'undefined') {
script.onload = function () {
callback();
};
}
document.head.appendChild(script);
}
Inside the main js file, I use it to load the dependencies dynamically after the main js file is loaded, initiate the JSLib object in its callback.
loadScript(baseUrl + '/dependencies/you-load-me-long-time.js', function() {
window.JSLib = true;
}
Then I have a web page that calls this library.
var jsLib = new JSLib({...});
The problem that I encounter is that - in the web page that loads this JS library, the browser complains that the JSLib is not defined because the dependency file you-load-me-long-time.js has not finished loading yet when the script in the web page is executed.
A work-around that seems to be working for now is that, in the web page, I wrap the initiation code in a $(window).load(function() {}); call.
Is there any way that I can overcome this timing issue? ex: "blocking" the loading of the rest of the web page until JSLib is loaded (doesn't sound like a good idea anyway), etc...
There are only two ways to create blocking dynamic script loaded via JS and both are fairly undesirable.
If the document is still being parsed, you can use document.write() to insert a <script> tag at the current document location. That will then be parsed and loaded synchronously.
If the script resource is on the same origin as the document, you can fetch the script with a synchronous Ajax call and then eval the script.
Since neither of these is particularly desirable, the usual work-around is to surface a callback all the way back to the caller of the script so that they can participate in when the async operation is done and put their code in that async callback.
var jsLib = new JSLib({...}, function() {
// put code here that uses the jsLib because now it is loaded
});
For this messy reason, it is usually not a good practice to make the completion of a constructor be an async operation. It significantly complicates the use of the object.
More common would be to let the constructor just create the shell of the object and then require a .load(fn) method call to actually load it. This will likely lessen the chance of callers misuing the library.
var jsLib = new JSLib({....});
jsLib.load(function(err) {
if (err) {
// error loading the library
} else {
// library is loaded now and all functionality can be used
}
});
FYI, your idea to use $(window).load() is not a good idea. That method may accidentally work just because it delays the timing enough until your script happens to be loaded, but the window load event does not specifically wait until dynamically loaded scripts have been loaded so it is not a reliable way to wait for your script.
I have a javascript function which makes an external call to an API which in turn injects some HTML into my page.
Now i want to execute a function which checks this script and if it does not inject the HTML into my page i want to do something else here. The problem is i cannot call this function on document.ready nor window.load as the first script to the external API gets executed after these 2 events.
The script is something like below:
(function () {
var o = ccs_cc_args; o.push(['_SKey', '35455c2f']); o.push(['_ZoneId', '311e740881']);
var sc = document.createElement('script'); sc.type = 'text/javascript'; sc.async = true;
sc.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + 'cdn.cnetcontent.com/jsc/h.js';
var n = document.getElementsByTagName('script')[0]; n.parentNode.insertBefore(sc, n);
})();
Now the function I wrote is like below:
$(document).ready(function () {
if ($("#ccs-inline-content").html() == "") {
$('#ProductDetails > ul li:has(a[href="#tabs-1"])').hide()
$("#ProductDetails").tabs('refresh');
$("#ProductDetails").tabs('option', 'active', 1);
}
});
So i need to force the second function to run after the first script which does not happen.
According to the spec, after the executing a script, browsers must
Fire a simple event named afterscriptexecute that bubbles (but is not cancelable) at the script element.
If the script is from an external file, fire a simple event named load at the script element.
Otherwise, the script is internal; queue a task to fire a
simple event named load at the script element.
So you can add a load or afterscript event listener to the script element:
scriptElement.addEventListener('load', callback);
If you don't have a reference to the script element, you can use event delegation and add the event listener to an ancestor, e.g. document.
Note this will only work with afterscriptexecute, which bubbles, but not with load, which doesn't.
Also note the event listener may run for multiple script elements, so you may have to filter the desired one.
document.addEventListener('afterscriptexecute', function(e) {
if(e.target.src === url)
callback();
});
You can also use event delegation in the capture phase. Then both afterscriptexecute and load will work, and the chance that some other listener stops the propagation of the event (which would avoid the execution of the callback) will be lower.
document.addEventListener('load', function(e) {
if(e.target.tagName.toLowerCase() == 'script' && e.target.src === url)
callback();
}, true);
I have an iframe that has a onload event. This event called a function (iframe_load) that I placed into a server side script. It appears that when my screen is launched, the onload event is triggered before the server side script has loaded and I get an error as the function is not found.
I have got around this by changing the onload event to call a checking function (iframe_check_load) in the client side script. This checks for the existence of parameter in the server side script, where if found it will then call the original function (iframe_load).
However ideally I would prefer not to have this checking function and to keep the client side code to a minimum. Is there a way I can add some code to the onload event to do this check, without having to use the checking function?
My current code:
function iframe_check_load(ctrl) {
if(typeof iframe_flag != "undefined"){
iframe_load();
}
}
<IFRAME id=iFrame2 onload=iframe_check_load() ></IFRAME>
I am sure there must be better ways to do all of this, please go easy as I'm still learning JS!
Since that there's no guarantee that the script is loaded before the frame, or vice versa, at least one checking must be performed in order to know if the external script is already available when the frame is loaded.
If the frame is loaded before the external script is available, you can use ONLOAD attribute on the element that load the external script to notify that it's already loaded. This will ensure that the iframe_load is always called. Assuming that there's no network error.
<SCRIPT>
//the saved "ctrl" parameter for iframe_load if
//the frame is loaded before custom_scripts.js.
var ctrlParam; //undefined as default
//script loaded handler for custom_scripts.js
function customScriptLoaded() {
//call iframe_load only if ctrlParam is not undefined.
//i.e.: frame is already loaded.
//will do nothing otherwise. leave it to iframe_check_load.
if (typeof ctrlParam != "undefined") {
iframe_load(ctrlParam);
}
}
//check whether it's safe to call iframe_load.
//assuming that "iframe_flag" is defined by custom_scripts.js.
function iframe_check_load(ctrl) {
if (typeof iframe_flag != "undefined") {
//custom_scripts.js already loaded.
//call iframe_load now.
iframe_load(ctrl);
} else {
//custom_scripts.js not yet loaded.
//save parameter and defer call to iframe_load.
//iframe_load will be called by customScriptLoaded.
//ctrl parameter must not be undefined in order to work.
console.log('Error: ctrl parameter of iframe_check_load is undefined. iframe_load will never be called.');
ctrlParam = ctrl;
}
}
</SCRIPT>
<!--Note: Don't forget to duble-quotes attribute values-->
<SCRIPT id="custom_scripts" type="text/javascript" src="htmlpathsub/custom/custom_scripts.js" UserSuppliedFullPath="1" onload="customScriptLoaded()"></SCRIPT>
<!--Using "this" (the IFRAME element) as the "ctrl" parameter-->
<IFRAME id="iFrame2" onload="iframe_check_load(this)"></IFRAME>
I understand that JS is single threaded and synchronously executed. Therefore when i add a file to my browser head tag that file is executed as soon as its encountered. Then it goes to the next script tag & executes that file. My question is when I add a js file dynamically to an HTML head tag. How does the browser executes that file?
Is it like that the file is executed as soon as the file is loaded wherever the current execution is. Or is it that we can control how that file is executed?
When the script is loaded, it will be executed as soon as possible. That is, if some other javascript function is executing, like a clickhandler or whatever, that will be allowed to finish first - but this is a given because, as you say, in browsers JavaScript normally execute in a single thread.
You can't control that part of the script loading, but you could use this pattern - heavily inspired by JSONP:
inserted script:
(function () {
var module = {
init: function () {
/* ... */
}
}
ready(module); // hook into "parent script"
}());
script on main page:
function ready(o) {
// call init in loaded whenever you are ready for it...
setTimeout(function () { o.init(); }, 1000);
}
The key here is the ready function that is defined on your page, and called from the script you insert dynmaically. Instead of immediately starting to act, the script will only tell the parent page that it is loaded, and the parent page can then call back to the inserted scripts init function whenever it wants execution to start.
What happens when a JavaScript file is dynamically loaded ( very simplified, no checks ):
the file is loaded;
if there is function call e.g. doSomething() or (function(){...})(), the code is executed(of course you must have the definitions);
if there are only function definitions, nothing is happening until the function call.
See this example: 3 files are loaded, 2 are executed immediately, 1 is waiting the timeout.
Edit:
The script tag can be placed anywhere in the page. Actually it is better to be placed at the end of the page if the onload event is not used (yahoo speed tips).
With HTML5 JavaScript has web workers MDN MSDN wikipedia.
Considering a way to do this is
var js=document.createElement('script')
js.setAttribute("type","text/javascript")
js.setAttribute("src", filename)
document.getElementsByTagName("head")[0].appendChild(js);
// ^ However this technique has been pointed to be not so trusworthy (Read the link in the comment by Pomeh)
But answering your question
How does the browser executes that file?
As soon as the script is added to the DOM
Is it like that the file is executed as soon as the file is loaded wherever the current execution is?
Yes
Or is it that we can control how that file is executed?
Its better if you attach an onload event handler, rather than a nasty tricks.
Here is some code you can try to get an answer to your question.
<script>
var s = document.createElement('script'), f = 1;
s.src="http://code.jquery.com/jquery-1.7.2.js";
document.head.appendChild(s)
s.onload = function(){
console.log(2);
f = 0
}
while(f){
console.log(1);
}
</script>
This code should ideally print a 2 when the script loads, but if you notice, that never happens
Note: This WILL kill you browser!
I'm loading a Javascript file that has a function in it that I'd like to call. This is not possible, since between 'initiating' the load, and actually calling the function, the JS isn't loaded yet. Of course, I could do something like setTimeout('functionCall();',5000);, but I don't think this is a very efficient method, and it seems really unstable to me. That's why I was wondering whether there was a better way to do it.
Here's the code I'm using. The function in question is called controllerStart. If I comment out the last line here, and type it into a Javascript terminal (like on Chrome developer tools), everything works.
function loadController(name){
clearAll();
scriptContainer.innerHTML = '';
var scriptElement = document.createElement('script');
scriptElement.setAttribute('src','controllers/' + name + '.js');
scriptElement.setAttribute('type','text/javascript');
scriptContainer.appendChild(scriptElement);
controllerStart();// <-- Doesn't work from here, but works on a terminal
}
Thanks for taking a look!
call the function after you reference the Javascript.
JS loading is SYNCHRONOUS.
So.....
---- js library call -----
----- call the function here -----
Profit!
edit: specifically :
function loadController(name){
clearAll();
scriptContainer.innerHTML = '';
var scriptElement = document.createElement('script');
scriptElement.setAttribute('src','controllers/' + name + '.js');
scriptElement.setAttribute('type','text/javascript');
scriptContainer.appendChild(scriptElement);
scriptElement.onload = new function(){controllerStart();};
//something like that.
}
edit 2: my bad - use "onload" not "load" - too much jquery on the brain
more about "onload" here: http://www.onlinetools.org/articles/unobtrusivejavascript/chapter4.html
There is a good post from Nicholas C. Zackas about it which you can read here, that includes just the code you want to use.
Basically it's just a function that includes both a URL of a JS file to load, and a callback to execute when it's been loaded. It then creates the <script> tag, attaches the callback to execute after it's loaded (via the onreadystatechange or the onload event) and inserts it into the DOM.
Then, all you need to do is call it like:
loadScript('controllers/' + name + '.js', controllerStart);
From the sound of it you're loading your JavaScript asynchronously. In more recent browsers there's an onload event on the tag which fires when it's finished loading. But if you need cross-browser support, the only way to do it is to either:
(a) if it's loaded from the same server as your page, load your javascript using AJAX and then execute it with eval() when it's loaded (instead of using <script> tags),
(b) if you control the file add an event trigger to the end of it which you then listen for in your main JavaScript file. JQuery's bind() and trigger() functions are handy for that.
(c) if it's loaded from somewhere else and you don't control it (but do have permission to redistribute it), relocate it to your server and follow (a).
You also could do away with asynchronous loading and just put a stream of <script> tags in your header, the old fashioned way. That guarantees that each script won't be run until after the previous script has finished.