Is there a callback for element.innerHTML assignments? - javascript

When using Head.js, and setting the .src attribute of a script element, there is callback method that is called when the script is ready.
However, I wanted to load a script by assigning text to .innerHTML. When doing this the same callback did not fire when I updated/edited the code to use this property instead.
/*addScriptText1
** modified from head.js
**
**
*/
function addScriptText1(file_name, callback, key) {
var element = document.createElement('script');
element.async = true;
element.innerHTML = localStorage[file_name];
element.onreadystatechange = element.onload = function () {
$A.log('callback for element called');
if ((!element.readyState || /loaded|complete/.test(element.readyState))) {
localStorage[key + '_loaded'] = true;
callback();
}
};
document.head.appendChild(element);
}

Scripts are executed immediately when inline scripts are inserted into the DOM. This all happens synchronously, so you don't need a callback.
async has no effect here because you are not doing a network request. Similarly, the readyState is not useful because you are creating it programatically, so the script is going to be immediately loaded.

Using innerHTML on a script element is not supported cross-browser (including Firefox iirc). I would not recommend this approach. I would suggest eval or new Function instead.
Can scripts be inserted with innerHTML?

Related

Function not available after document.body.appendChild(scriptElement)

Here is the code I am using for dynamically including script tag with src in my HTML page. There is a function in that newly imported javascript file that I want to use:
var scriptFile= document.createElement("script");
scriptFile.src = "something.js";
var something=document.body.appendChild(scriptFile);
something.js contains a function called doSomething(). Now when I call this function immediately after the appendChild above, it say doSomething is not defined. However, when I fire it from the Chrome console, it executes successfully. I am not sure why is this happening.
When you append the script tag with the source, first the source will be parsed and compiled which is an asyn process ( non blocking ).
If you try to invoke the method immediately this would throw an error as the source has not been compiled yet.
Bind a load event which gets triggered when the script is available. This will make sure you are running the contents of the script tag only after it has completely loaded.
var scriptFile= document.createElement("script");
scriptFile.addEventListener('load', function() {
console.log('Script is ready to execute');
// invoke your function here
});
scriptFile.src = "something.js";
var something=document.body.appendChild(scriptFile);
Listen for onload
var scriptFile= document.createElement("script")
scriptFile.src = "something.js"
document.body.appendChild(scriptFile)
scriptFile.onload = () => {
// call something.js functions here
}

Javascript onload and script callback functions, which takes the precedence?

I'm loading an external script that uses callback function, which returns some specific data. If this data is not received error should be displayed.
Here is the code I've made:
<script>
//setting initial state so that function will only work once
var visitors_loaded=false;
var my_callback = function( data ) {
if (visitors_loaded) return 0;
if (data) {
//success: callback function is called and it has a proper data
visitors_loaded=true;
alert(JSON.stringify(data));
}
else alert ('error'); //something went wrong
};
</script>
<script onload="my_callback(null)" onerror="my_callback(null)"
src="https://api.clicky.com/api/stats/4?site_id=32020&sitekey=9a19b1a4d1171193&type=visitors&date=this-month&output=json&json_callback=my_callback"></script>
As you can see... many things that can go wrong with the script, so I naturally added an onerror event. This on error event actually fires if you change host name or domain of the script to something non-existent.
However, if you only make changes to the url of the script, it can still connects to the server and fires an onload event instead. My callback function will not be called for those invalid requests, so I added an onload handler as well.
Now the problem is, if all loaded normally and data was returned, it will fire both, callback function and onload. I have noticed that callback function is triggered before the onload and set the visitors_loaded variable so that the handler function is only called once.
So far it works perfectly in JS fiddle and my offline site but I wonder if this is an expected behavior? Will that json_callback function always have precedence before the onload handler?
https://jsfiddle.net/5sfk9ht5/4/
Will that json_callback function always have precedence before the onload handler?
If the external script calls my_callback synchronously then yes.
The scripting section in the official html5 specification describes how these things are supposed to work. The specification is quite general and has to deal with a lot of details conserning encoding, CORS, ignore-destructive-writes counter and so on. But for this question we don't care about these specifics.
In step 4 there is a note:
Note: This is where the script is compiled and actually executed.
And in step 7 the load event is fired:
fire a simple event named load at the script element.
So the specification defines that the load event is always fired after the script has been executed.
As you see the specification also tells us why the onerror event is not fired if you change the URL. The error event is only created if loading the script fails. But all requests to https://api.clicky.com/api/stats/ return a HTTP 200 status. The invalid URLs return XML and thus a SyntaxError is thrown. But this does not cause the onerror handler to be triggered.
As others have mentioned if the callback is called asynchronously they can call your callback after the onload event. But I don't see a reason why they would do this async in your external script.
onload in older IE might not work for you 'onload' handler for 'script' tag in internet explorer, if you want to run all browsers you might need something like this https://jsfiddle.net/e287523t/2/ and should work for all
function addScript(fileSrc, callback) {
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.onreadystatechange = function() {
if (this.readyState == 'complete') {
callback();
}
}
script.onload = callback;
script.src = fileSrc;
head.appendChild(script);
}
Then the rest is defining my_callback and calling addScript
my_callback = function(someData) {
alert(JSON.stringify(someData));
};
Now the problem is, if all loaded normally and data was returned, it will fire both, callback function and onload. I have noticed that callback function is triggered before the onload and set the visitors_loaded variable so that the handler function is only called once.
This is because the callback is launched from within the called script from api.clicky.com
So far it works perfectly in JS fiddle and my offline site but I wonder if this is an expected behavior?
I see what you are getting at, a related question about what happens when a script fails is here, but I did some tests for you and here are the results.
tester.html:
<script>
var onLoadTest = function() {
console.log("Onload Called!");
};
var callbacktest = function() {
console.log("Called from other script!");
};
var errortest = function() {
console.log("Callback OnError!");
};
</script>
<script onload="onLoadTest()" onerror="errortest()"
src="script.js"></script>
script.js:
function otherScriptFunc()
{
//call the function in the original script
callbacktest()
}
otherScriptFunc(); // first call to other script
setTimeout(otherScriptFunc, 0); // final call to other script
Results from the console log
Called from other script!
Onload Called!
Called from other script!
Your OnLoad will be called when the JS in the other place has finished being parsed (async functions will do their own thing). For example, otherScriptFunc(); will call before onload but setTimeout(otherScriptFunc, 0); will be called after onload
Your OnError will only be called if there is a GET request error. IE, the file cannot be found, or URL cannot be resolved - nothing about what is in the file. (I tested it separately, just mess with the file name)
Your callback passed to the other script could be called whenever the other script feels like it. It has a reference to it and could decide to hold onto it for a little while and call it later after it has played around. Which means it could be in an async call waiting for data elsewhere. Which means, theoretically, your onload could in fact be called before the callback, but it depends on the other script and there is not a lot you can do about it.
Will that json_callback function always have precedence before the onload handler?
It's not about precedence, it is just dependent upon when the other script decides to call it.

Conditional javascript loading

For my backend I want to automatically load javascript files when it detects certain elements. Here is an example:
if($('.wysiwyg').length>0) {
include('javascript/ckeditor/ckeditor.js');
$(".wysiwyg").ckeditor();
}
But when I execute the code I get $(".wysiwyg").ckeditor is not a function because it seems the browser is still loading or parsing the javascript file that was included on the line before. If I put an alert popup right before the function it does work because it "pauzes" the script I guess and gives it time to load the file.
Is there a way I can know when the file is actually loaded so that the followed code can be executed?
EDIT:
Seems that I asked this question a bit too soon. I found out the e.onload property for a callback function that solved this problem. This is my function now if others might stumble upon the same problem:
function include(script, callback) {
var e = document.createElement('script');
e.onload = callback;
e.src = script;
e.type = "text/javascript";
document.getElementsByTagName("head")[0].appendChild(e);
}
if($('.wysiwyg').length>0) {
include('javascript/ckeditor/ckeditor.js', function() {
$(".wysiwyg").ckeditor();
});
}
Why not use the built in ajax-based getScript?
It also has a callback mechanism that allows you to execute some code only after the required script has been succesfully loaded :
function include(script,callback){
$.getScript(script, function() {
if(typeof callback == 'function')
callback.apply({},arguments);
});
}
and then you can use it in such a manner:
if($('.wysiwyg').length>0) {
include('javascript/ckeditor/ckeditor.js',function(){
$(".wysiwyg").ckeditor();
});
}
When you're using jQuery with promises you can use a modified version of the above code like so:
function include(srcURL) {
var deferred = new $.Deferred();
var e = document.createElement('script');
e.onload = function () { deferred.resolve(); };
e.src = srcURL;
document.getElementsByTagName("head")[0].appendChild(e);
return deferred.promise();
}
Then you can use the above code with a '$.when(include('someurl.js'))' call.
This will let you have
The global window context (which you need for CKEditor)
The ability to defer executing other code until the when resolves
A script that doesn't require a callback and a context for that to be passed because jQuery is handling that with the promises functionality it includes.
I hope this helps someone else who is looking for more than a callback, and multiple scripts to be loaded with jQuery's promises/deferred functionality.
You can also try YepNope - a conditional javascript loader
yepnope is an asynchronous conditional resource loader that's
super-fast, and allows you to load only the scripts that your users
need.
You can do it this way
$(document).ready(function()
{
if($('.wysiwyg').length>0) {
$('head').append('<script language="javascript" src="javascript/ckeditor/ckeditor.js"></script>');
$(".wysiwyg").ckeditor();
}
});
Modernizr can do this for you. See this MetaFlood article: Use jQuery and Modernizr to load javascript conditionally, based on existence of DOM element.

$(document).ready() when jQuery is loaded as a dynamically appended <script>

For reasons that are beyond my control I have to load jQuery via a dynamically appended <script> tag, and only do this upon some arbitrary event, not at page load.
My problem is in detecting the moment when jquery is ready, for the code below doesn't work:
(function(){
var s=document.createElement('script');
s.setAttribute('type','text/javascript');
s.setAttribute('src','http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js');
document.getElementsByTagName('head')[0].appendChild(s);
})()
$(document).ready(function() {
// something that uses jquery and currently doesn't work
});
How do I detect the moment when jquery is ready to be used in this particular configuration?
Thanks in advance
Use the onload and onreadystatechange event handlers:
var scr = document.createElement('script'),
head = document.getElementsByTagName('head')[0];
scr.onload = scr.onreadystatechange = function(){
if( scr.readyState ){
if(scr.readyState === 'complete' || scr.readyState === 'loaded'){
scr.onreadystatechange = null;
myReadyFunc();
}
}
else{
myReadyFunc();
}
};
head.insertBefore(scr, head.firstChild);
function myReadyFunc() {
$(document).ready(function() {
// something that uses jquery and currently doesn't work
});
}
An old-school answer :)
You can create a recursive polling function that check to see if the $ object exists eg:
function poller(){
if($.length != 0){
//do what you want
}else{
setTimeout(poller, 100);
}
}
And right after you load the jQuery script run the poller function.
You can handle the <script> element's onreadystatechange event, and, if this.readyState is complete or loaded, run your function.
For Firefox, handle the onload event.
You can expose this in a wrapper function which takes a function as a parameter, and calls it if jQuery has been loaded, or puts it in an array (to call in the load handler) if it's not loaded.

Execute javascript after external javascript document has loaded

I want to include a remote js file and then invoke a function once that has finished executing. I thought I could do something like this:
var sc = document.createElement('script');
sc.setAttribute('type', 'text/javascript');
sc.setAttribute('src', src);
sc.innerHTML = "alert('testing');"
parentNode.appendChild(sc);
Turns out, the alert('testing') gets wiped out be whatever is in the file. Is there anyway to do this?
This function will load library from scriptPath and execute passed handler function once script is loaded:
loadExternalScript : function(scriptPath, handler) {
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = scriptPath;
script.charset = 'utf-8';
script.onload = handler;
head.appendChild(script);
}
First thing is, forget about using src and inner contents on the same script tag. It doesn't work in any general way, although John Resig gave it some thought in this blog post.
Second thing is, decide whether you want to load the script synchronously or asynchronously. If the script is large or long-running, you'll either want to do it asynchronously, or do it synchronously at the bottom of the page so as not to block rendering.
Your approach (dynamically appending script tags) will load and run it asynchronously, which means the code that should run after it's finished needs to go in a callback that fires when the script is finished. Setting this up isn't very straightforward, so I'd suggest either using jQuery and its ajax.getScript function, or just copy the getScript functionality right out of the jQuery source (lines 3473-3505 of jQuery 1.3.2).
If you want to avoid all of that, just load it synchronously. This is done by using document.write. Your provided example would look like:
document.write("<scr" + "ipt src='" + src + "' type='text/javascript'></script>");
// The script is guaranteed to have executed at this point
alert('testing');
Be sure to keep "script" split up like that, I'm not sure why but it's a quirk of JavaScript.
Have you tried just creating a second script element containing the code you want to run and adding that after the you've added the one that needs downloading?
Adding another
<script></script>
section after the first one should work. AFAIK you can't mix external and inline JS in one tag.
However I'm not sure whether putting code into "innerHTML" will work as expected. I'm interested to see whether it does.
You might be able to use the sc load event to figure out when that script has loaded then do some action.
example http://iamnoah.blogspot.com/2008/01/ie-script-load-event.html
I created this script for myself yesterday. It uses jQuery to load JavaScript files via AJAX and adds them in a script tag to the header, and then calls a callback function I pass it.
Has been working fine for me.
/**
* Fetches and executes JavaScript files from the server.
* #param files A list of files to load, or a single filename as a string.
* #param callback The function to call when the process is done. Passes a boolean success value as the only parameter.
* #param thisObject The calling object for the callback.
*/
window.include = function(files, callback, thisObject) {
var current_location = null;
var recursive = false;
if(!(thisObject instanceof Object)) {
thisObject = window;
}
if(files instanceof Array || files instanceof Object) {
if(files.length > 0) {
current_location = files.shift();
recursive = true;
}
else {
callback.apply(thisObject, [true]);
return;
}
}
else if(typeof files == 'string') {
current_location = files;
}
else {
callback.apply(thisObject, [false]);
return;
}
if((current_location instanceof String || typeof current_location == 'string') && current_location != '')
{
$.ajax({
type : 'GET',
url : current_location,
timeout : 5000,
success : function(data) {
var scriptTag = $(document.createElement('script'));
scriptTag.attr('type', 'text/javascript');
scriptTag.html(data);
$('head').append(scriptTag);
if(recursive) {
window.adlib.include(files, callback, thisObject);
}
else {
callback.apply(thisObject, [true]);
}
},
error : function() {
callback.apply(thisObject, [false]);
}
});
}
}

Categories