I'm trying to build some small widgets tools that webmasters can embed in their websites.
Is there any way that the webmaster can simply load this tool by including a script like this?
<script src="http://mysite/widget.js"></script>
I tried to inject it by loading it in AJAX and then do an appendChild() on the body, it's working this way but the JS is not executed.
Content to be injected:
<div>one div</div>
<script>alert('some js')</script>
widget.js
function load(content) {
var $body = document.body,
$div = document.createElement("div");
$div.id = "widget";
$div.innerHTML = content;
$body.appendChild($div);
}
The content variable contains HTML and JS but the JS is not executed when injected.
Since you don't know where you script will be added - to the head or body tag - and consequently when it will be executed, you need to make sure that DOM is ready, so:
function load(content) {
if (document.readyState === "complete" || document.readyState === "loaded") {
// manipulate DOM
} else {
document.addEventListener('DOMContentLoaded', function() {
// manipulate DOM
})
}
Also, since scripts elements added using innerHTML don't get executed, you'd have to either use eval or better create a script like that:
var s = document.createElement('script');
s.text = "alert(\"hi\");"
document.getElementsByTagName('head')[0].appendChild(s);
However, there is no point to add <script> tag to execute some script in your template, since your script is already running when you're adding DOM so do all necessary setups there.
UPDATE
please see Maximus' answer.
Right, the default native innerHTML doesn't execute js for safe sake.
jQuery or zepto $.fn.append() can satisfy your need, here is the reason (from zepto source code)
if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' &&
(!el.type || el.type === 'text/javascript') && !el.src)
window['eval'].call(window, el.innerHTML)
You can see in the function $.fn.append it will see if it's a script, if so it will use eval to run the content. You may need eval to, but be careful since some webmasters may deliver 'vice' code
Related
I'm loading a HTML partial through ajax. The partial is attached to the DOM by using innerHTML on an existing node.
The partial contains a few script tags at the bottom, something like:
<script src="/Scripts/Griffin.Editor.js" type="text/javascript"></script>
<script type="text/javascript">
marked.setOptions({
renderer: new marked.Renderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: true,
smartLists: true,
smartypants: false
});
var textParser = {
parse: function (text) {
return marked(text);
}
}
var prismHighlighter = {
highlight: function (blockElements, inlineElements) {
blockElements.forEach(function(item) {
Prism.highlightElement(item);
});
}
};
var editor = new Griffin.Editor('editor', textParser);
editor.syntaxHighlighter = prismHighlighter;
editor.preview();
</script>
However, as the script tags are not executed, I traverse the loaded partial to identify all script tags. I then create new script nodes in the DOM and attach them to the HEAD.
Something like:
var scripts = viewElem.getElementsByTagName('script');
for (let i = 0; i < len; i++) {
var scriptTag = scripts[0];
let node = document.createElement('script');
if (scriptTag.src && scriptTag.src.length > 0) {
node.src = scriptTag.src;
node.type = scriptTag.type;
} else {
node.text = scriptTag.text;
node.type = scriptTag.type;
//had eval here before (instead of attaching the embedded script to the HEAD).
}
document.head.appendChild(node);
scriptTag.parentNode.remove(scriptTag);
}
From what I understand the browser should load the referenced scripts before invoking the embedded script. That is however not the case for me, because the JS console complains about not finding the object defined in the dependency script.
If I use a timer and eval the embedded script in it everything works. But that seems as a ugly workaround and I really want to understand the mechanics behind the load behavior (i.e. why the scripts are not executed when the partial is attached to the DOM and why the referenced scripts are not loaded directly when I add the nodes to the HEAD tag).
From what I've encountered, you can't have immediately executing JavaScript inside an Ajax response. The reason being you are trying to execute JavaScript inside another JavaScript function. So the browser has no idea which executing context to use with this scenario.
I would recommend using deferred execution like you mentioned. Except, you need to let the browser interpret the Ajax response first. For example:
$.get('url', function (html) {
// html = "<script>function myTest () { console.log('here'); }</script>"
$('#result').html(html);
// Now that the DOM has had time to parse the response we can do:
myTest();
});
Notice it is the Ajax callback invoking the response function not the response immediately executing itself. Hope this helps.
I found a really great article explaining in depth how scripts are loaded into the browser.
In essence you can't be sure of execution order per default when you include scripts dynamically. To be sure of the order you need to do one of the following
a. Use async=false if supported
b. Use readyState (for ie<10)
c. Use defer attribute.
Try to use the mentioned features in that order to be sure.
However, even if you do all that you will still get screwed if you mix embedded scripts (code in in the script tag) with referenced scripts (using src attribute).
The problem is that the embedded scripts will run directly, even if the references script tags are added before. To solve that you need to push the embedded scripts into a queue and hook the load event for all referenced scripts.
Once all referenced scripts have toggled the load even you are free to invoke the embedded scripts (either by added the script tags to an element or by eval their text property).
Source: http://blog.gauffin.org/2015/07/embedded-script-tags-in-content-loaded-through-ajax-and-execute-the-script-tags-dynamically/
When loading JavaScript libraries (like jQuery), they tend to create an object on the Window element.
I'm trying to work out how I can detect when the library has just loaded so I've gone down the path of trying to detect a property change on the window object...
window.addEventListener("DOMAttrModified", invalidate, false);
function invalidate(evt)
{
console.log('attrChange = ' + evt.attrChange);
}
... This doesn't work.
Perhaps someone knows of another way to solve both this solution and a mechanism to detect when an external library has loaded.
p.s. I have looked at the onload tag for the script tag but I'm concerned as it's not in the W3C.
p.p.s. Ultimately, I'm trying to implement a function where it is safe to use jQuery code.
If your js code is lower then script tag then don't worry, html rendering stops while script tag is not loaded, so if your js is lower then library is already loaded.
Also you may check for existence of some variable (like $).
if (typeof jQuery != 'undefined') {
// is loaded
} else {
// is not loaded
}
Or in loop though interval:
var interval = setInterval(function () {
if (typeof jQuery != 'undefined') {
clearInterval(interval);
// do what you want
}
});
I'm trying to write a function which will append a javascript file to the DOM, but I am looking to have the rest of the code wait until the newly added JS file is completely loaded. Here is an example of what I am trying to accomplish, although this code doesn't work properly:
$(document).ready(function () {
var newScript = document.createElement("script");
newScript.setAttribute("type", "text/javascript");
newScript.src = "http://www.domain.com/script.js";
document.getElementsByTagName("body")[0].appendChild(newScript);
$(newScript).ready(function () { // This is the idea of what I'm trying to do, but this doesn't seem to actually wait until the new file is completely loaded.
foo.bar(); // foo is a new global variable which is declared in the newScript. This causes an error "foo is not defined".
// Here is where more code I wish to execute should continue.
});
});
As Musa mentioned in the comments above. Use jQuery's getScript and use the success callback function to trigger your other functions.
If you want more robust module loading functionality, then require.js works great in this capacity. Check out: http://requirejs.org/docs/why.html for an overview. I use require.js specifically for lazy-loading script modules.
Using jQuery (as you've tagged), it's extremely easy:
$.getScript('/script.js', function() {
foo.bar();
});
There's a few different ways to do this... via libraries or "by hand," so to speak, using only the browser APIs and straight JavaScript. For an answer on how to do this in JS only, look here for Stoyan's post to give you guidance. Basically, the gist of it is setting an event handler to both the script's unload and onreadystatechange properties and then check to see if the readyState is "loaded" or "complete" (if it exists at all). It would look something like this:
var done = false;
newScript.onload = newScript.onreadystatechange = function () {
if (!done && (!newScript.readyState || newScript.readyState === "loaded" || newScript.readyState === "complete)) {
done = true;
// run your actual code here
}
};
I have a small script
document.write("<html><head><script src='/js/jquery-1.4.2.min.js' type='text/javascript'></scr"
+ "ipt><script>alert($"+"().jquery);</scri" + "pt></head></html>");
But I get a $ is undefined in Internet Explorer. I think it tries to run the script before loading the library.
However this runs in Firefox. Please help.
Edit: I open a new window and write to that window's document.
What's wrong with creating the <script> tag the proper way? document.write is evil, end of discussion.
Try with this:
var load_script = function(options) {
options.owner_document = options.owner_document || document;
var script_tag = options.owner_document.createElement('script');
script_tag.setAttribute('type', 'text/javascript');
script_tag.setAttribute('src', options.src);
script_tag.onload = function() {
script_tag.onreadystatechange = null;
options.callback && options.callback();
};
script_tag.onreadystatechange = function() {
if (script_tag.readyState == 'loaded' || script_tag.readyState == 'complete') {
script_tag.onload = null;
options.callback && options.callback();
}
};
options.owner_document.getElementsByTagName('head')[0].appendChild(script_tag);
};
as you see, there's a simple API on that snippet:
src - source of the script
owner_document - document where the script will be inserted, defaults to the current document where the script is running from
callback - function to run after the script has loaded, anything that requires the src script is safe to be run inside this closure.
example usage:
// sample loading of jQuery
load_script({
src: '/js/jquery-1.4.2.min.js',
callback: function() {
// jQuery is available at this point, run your code.
}
});
alternatively, you can use loaders like requiere.js and LABjs
The thing is that you have unintentionally triggered IE to load your scripts in a non-blocking way. Read this for more information: Loading JavaScript without blocking.
The page suggests that you use this code to get notified when the script has finished loading:
//Internet Explorer only
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "/js/jquery-1.4.2.min.js";
script.onreadystatechange = function(){
if (script.readyState == "loaded" ||
script.readyState == "complete"){
script.onreadystatechange = null;
// Your code goes here.
alert("Script is ready!");
}
};
document.body.appendChild(script);
try running your second script on page load(window.onload) , or try inserting the script in the body, not in the head section.
try :
document.write("<html><head><script src='/js/jquery-1.4.2.min.js' type='text/javascript'></scr"
+ "ipt><script>window.onload = function(){alert($"+"().jquery);};</scri" + "pt></head></html>");
the browser won't load the library instantly (unless it has it in its cache) so your script might get called before the library is loaded.
See this solution:
JavaScript's document.write Inline Script Execution Order
(also note the comment, it's important)
You could write a recursive function that waits for the jquery library to load and then executes any code once the library is loaded. You probably want to add a conditional that breaks out of the recursion if it gets too deep.
document.write("<html><head><script src='/js/jquery-1.4.2.min.js' type='text/javascript'></scr"+ "ipt><script type='text/javascript'>var wait_for_jQuery = function() {if (typeof $ === 'undefined') {setTimeout('wait_for_jQuery()', 1000);} else {alert($"+"().jquery);}}; wait_for_jQuery(); </scri" + "pt></head></html>");
Working example here: http://www.jsfiddle.net/YqTgM/36/
Have you tried moving the script tag running your code to the end of the body tag? According to Yahoo's best practices (creators of YSlow):
The problem caused by scripts is that
they block parallel downloads. The
HTTP/1.1 specification suggests that
browsers download no more than two
components in parallel per hostname.
If you serve your images from multiple
hostnames, you can get more than two
downloads to occur in parallel. While
a script is downloading, however, the
browser won't start any other
downloads, even on different
hostnames.
So the result would look like...
document.write("<html><head><script src='/js/jquery-1.4.2.min.js' type='text/javascript'></scr"
+ "ipt></head><body><script>alert($"+"().jquery);</scri" + "pt></body></html>");
Although I would also advocate not using document.write().
Give this a try?
http://jsfiddle.net/MP75r/
If you want simplified conditional loading of jQuery, there are many options out there already; no need to reinvent the wheel. Already mentioned are RequireJS and LabJS. I am using Head.js and it couldn't be easier to accomplish the goal of non-blocking loading while respecting execution order.
you are just creating a tag to reference jQuery, and as soon as its created next statement is an alert. There is no way specified by you that alert statement should run after after file load.
A simple timer should do it.
(function(){
function wait() {
if (typeof jQuery == 'undefined') {
setTimeout(wait, 50);
} else {
run(); // your code to execute
}
}
wait();
})();
Example on jsfiddle: http://jsfiddle.net/madr/Gtafq/
Inside the <script></script> tag wrap your code in a document ready function so the libraries are loaded before running.
$(document).ready(function() {
// put all your jQuery code here
});
I'm having some concurrency issues with a webpage I'm building. Basically, I have three script files that I'm using to hold some data:
script1.js:
var myValue = 1;
script2.js:
var myValue = 2;
script3.js:
var myValue = 3;
And I have one page, mypage.html that basically looks like this:
<html>
<script>
function get_number()
{
var script_file = GetQueryStringValue( "run" );
e = document.createElement('script');
e.type='text/javascript';
e.src = script_file + ".js"
head.appendChild( e );
document.write( myValue );
}
</script>
<body onload="get_number()">
<div onclick="get_number()">Click me!</div>
</body>
</html>
The main idea with this page is that you would query it like this:
mypage.html?run=script1
which would tell the get_number() function to dynamically insert script1.js in to mypage.htm. Then, I call get_number() to display the value loaded from the script.
Now, I've stripped down the above to what I think are the relevant parts and I've left out a bunch of stuff, obviously. My actual code loads a large array of values and is more interesting... But, I'm hoping someone can help me out with this regardless.
What I'm finding is that in IE, the number displays correctly.
In Firefox, Chrome and Safari, I get an error that myValue is undefined. However, if I click the Click me div that I created, the number displays correctly. So, I know I'm correctly loading the javascript external file. But, it just isn't loaded in time for my get_number() function to work correctly onload.
Now, I hacked up my file a little so that the get_number function looks like this:
function get_number()
{
var script_file = GetQueryStringValue( "run" );
e = document.createElement('script');
e.type='text/javascript';
e.src = script_file + ".js"
head.appendChild( e );
setTimeout( function() { document.write( myValue ), 10 } );
}
The setTimeout delays enough for the DOM to be updated and the javascript to be loaded in most cases. But, this is a total hack. Firefox tends to load this 'improved' code correctly all the time while Chrome and Safari manage to get the value about 50% of the time.
So, I guess I'm wondering, is there a better way to accomplish what I'm trying to do here? It's very important that the value be driven externally (by the query string), but other than that requirement, I'm very flexible.
This method of dynamically loading script files using DOM methods like head.appendChild is asynchronous, meaning that the page does not wait for it to load before running any further code. If you did not want this asynchronous behaviour, you could load them with regular elements in your HTML, or you could mess around with a 'synchronous' XMLHttpRequest to grab it and eval() to run it.
You probably don't want to do that, though, because being asynchronous makes the entire page load faster, anyway. You will probably just need to add some logic that waits for the dynamically loaded script to have loaded before you go on with the next bit of code. This may, however, involve polling using setInterval() until you notice a variable from the included script has been defined. Or, you could add code in the included script that calls a method to whatever has been waiting for it.
Or, you could look at how jQuery does something similar, look for where the ajax method is defined, specifically these parts...
var script = document.createElement("script");
// then later ...
script.onload = script.onreadystatechange = function(){
if ( !done && (!this.readyState ||
this.readyState == "loaded" || this.readyState == "complete") ) {
// do processing
head.removeChild( script );
}
};
// ...
head.appendChild(script);
Could you possibly add a function call to your script files, so that when they're run, they call the function that does your
document.write( myValue );
So that script1.js would be
var myValue = 1;
scriptLoaded();
and your main file would have:
function scriptLoaded(){
document.write( myValue );
}