Is it safe to assume that the last script element* in the document when the script runs** is the currently running script?
For example, I want to create a script that can be dropped anywhere in the body of of a page and display an element in the same place. I'm doing something like this:
function getCurrentScriptElement() {
var scripts = document.getElementsByTagName('script');
return scripts[scripts.length - 1];
}
var script = getCurrentScriptElement();
var view = document.createElement('span');
/* Put stuff in our view... */
script.parentNode.insertBefore(view, script);
Assuming the script is in the body of the document, is this "safe?" Will the getCurrentScriptElement function always return the running script? If not, how can it be done?
I'd like to do this without tying the script to a specific id attribute or similar, I'd like it to just be positional.
I created an example here that pulls in this script. One answer suggested that other scripts could create a condition where an example like this would break. Is it possible to add other scripts to this example that will break it?
It was suggested that other scripts with defer or async attributes could break this. Can anyone give an example of how such a script might work?
As I understand it, defer means load the DOM first, and then run the script with the defer tag. How would the defer attribute appearing on another script element affect the behavior of getCurrentScriptElement?
async, as I understand it, means start fetching that script and keep parsing the DOM at the same time, don't wait... but when it hits my script it should still stop and wait, right?
I don't see how either one could affect it, can anyone provide an example?
* I'm only interested in external scripts for the purpose of this question.
** Not the last script element in the entire document, but the last script element in the document at the time when it runs. The rest of the document shouldn't be loaded yet, right?
It's not an absolute guarantee no. Check out this JSFiddle: http://jsfiddle.net/jAsek/
<!DOCTYPE html>
<title>Test case</title>
<div>
<p>At the start</p>
<script id="first">
var scr1 = document.createElement("script");
scr1.setAttribute("id", "early");
document.body.appendChild(scr1);
</script>
<p>After the first script</p>
<script id="second">
function getCurrentScriptElement() {
var scripts = document.getElementsByTagName('script');
return scripts[scripts.length - 1];
}
alert(getCurrentScriptElement().id);
</script>
<p>At the end</p>
</div>
Here the alert reports the id of the injected script "early", not the id of currently running script "second".
There's no practical difference between internal and external scripts.
I don’t think it’s a safe assumption at all, as browsers execute javascript code quite differently depending on a number of things (like if you have other script elements in the head, if they are external etc.).
You should just require people to use a dummy element with a custom id or class. That way you will also make it possible to do whatever you do multiple times a page without having to run the script multiple times.
This is also what is done when using widgets, for example Google’s +1 button.
An alternative would be to use document.write to write additional content while the script is executed. This will not replace the script tag however, but simply add something after it.
You probably want to use document.currentScript that is currently supported by 90% of browsers and fallback to document.scripts[document.scripts.length-1] if you're targetting IE
function writeHere(element)
{
var sc = document.currentScript || document.scripts[document.scripts.length-1] ;
sc.parentNode.insertBefore(element, sc);
// or in jquery $(sc).before($(element));
}
note: I didn't test document.scripts[document.scripts.length-1] thoroughly but it should work in most cases (but not in Alohci exemple).
And this is a fix for IE so who cares :)
Related
In tutorials I've learnt to use document.write. Now I understand that by many this is frowned upon. I've tried print(), but then it literally sends it to the printer.
So what are alternatives I should use, and why shouldn't I use document.write? Both w3schools and MDN use document.write.
The reason that your HTML is replaced is because of an evil JavaScript function: document.write().
It is most definitely "bad form." It only works with webpages if you use it on the page load; and if you use it during runtime, it will replace your entire document with the input. And if you're applying it as strict XHTML structure it's not even valid code.
the problem:
document.write writes to the document stream. Calling document.write on a closed (or loaded) document automatically calls document.open which will clear the document.
-- quote from the MDN
document.write() has two henchmen, document.open(), and document.close(). When the HTML document is loading, the document is "open". When the document has finished loading, the document has "closed". Using document.write() at this point will erase your entire (closed) HTML document and replace it with a new (open) document. This means your webpage has erased itself and started writing a new page - from scratch.
I believe document.write() causes the browser to have a performance decrease as well (correct me if I am wrong).
an example:
This example writes output to the HTML document after the page has loaded. Watch document.write()'s evil powers clear the entire document when you press the "exterminate" button:
I am an ordinary HTML page. I am innocent, and purely for informational purposes. Please do not <input type="button" onclick="document.write('This HTML page has been succesfully exterminated.')" value="exterminate"/>
me!
the alternatives:
.innerHTML This is a wonderful alternative, but this attribute has to be attached to the element where you want to put the text.
Example: document.getElementById('output1').innerHTML = 'Some text!';
.createTextNode() is the alternative recommended by the W3C.
Example: var para = document.createElement('p');
para.appendChild(document.createTextNode('Hello, '));
NOTE: This is known to have some performance decreases (slower than .innerHTML). I recommend using .innerHTML instead.
the example with the .innerHTML alternative:
I am an ordinary HTML page.
I am innocent, and purely for informational purposes.
Please do not
<input type="button" onclick="document.getElementById('output1').innerHTML = 'There was an error exterminating this page. Please replace <code>.innerHTML</code> with <code>document.write()</code> to complete extermination.';" value="exterminate"/>
me!
<p id="output1"></p>
Here is code that should replace document.write in-place:
document.write=function(s){
var scripts = document.getElementsByTagName('script');
var lastScript = scripts[scripts.length-1];
lastScript.insertAdjacentHTML("beforebegin", s);
}
You can combine insertAdjacentHTML method and document.currentScript property.
The insertAdjacentHTML() method of the Element interface parses the specified text as HTML or XML and inserts the resulting nodes into the DOM tree at a specified position:
'beforebegin': Before the element itself.
'afterbegin': Just inside the element, before its first child.
'beforeend': Just inside the element, after its last child.
'afterend': After the element itself.
The document.currentScript property returns the <script> element whose script is currently being processed. Best position will be beforebegin — new HTML will be inserted before <script> itself. To match document.write's native behavior, one would position the text afterend, but then the nodes from consecutive calls to the function aren't placed in the same order as you called them (like document.write does), but in reverse. The order in which your HTML appears is probably more important than where they're place relative to the <script> tag, hence the use of beforebegin.
document.currentScript.insertAdjacentHTML(
'beforebegin',
'This is a document.write alternative'
)
As a recommended alternative to document.write you could use DOM manipulation to directly query and add node elements to the DOM.
Just dropping a note here to say that, although using document.write is highly frowned upon due to performance concerns (synchronous DOM injection and evaluation), there is also no actual 1:1 alternative if you are using document.write to inject script tags on demand.
There are a lot of great ways to avoid having to do this (e.g. script loaders like RequireJS that manage your dependency chains) but they are more invasive and so are best used throughout the site/application.
I fail to see the problem with document.write. If you are using it before the onload event fires, as you presumably are, to build elements from structured data for instance, it is the appropriate tool to use. There is no performance advantage to using insertAdjacentHTML or explicitly adding nodes to the DOM after it has been built. I just tested it three different ways with an old script I once used to schedule incoming modem calls for a 24/7 service on a bank of 4 modems.
By the time it is finished this script creates over 3000 DOM nodes, mostly table cells. On a 7 year old PC running Firefox on Vista, this little exercise takes less than 2 seconds using document.write from a local 12kb source file and three 1px GIFs which are re-used about 2000 times. The page just pops into existence fully formed, ready to handle events.
Using insertAdjacentHTML is not a direct substitute as the browser closes tags which the script requires remain open, and takes twice as long to ultimately create a mangled page. Writing all the pieces to a string and then passing it to insertAdjacentHTML takes even longer, but at least you get the page as designed. Other options (like manually re-building the DOM one node at a time) are so ridiculous that I'm not even going there.
Sometimes document.write is the thing to use. The fact that it is one of the oldest methods in JavaScript is not a point against it, but a point in its favor - it is highly optimized code which does exactly what it was intended to do and has been doing since its inception.
It's nice to know that there are alternative post-load methods available, but it must be understood that these are intended for a different purpose entirely; namely modifying the DOM after it has been created and memory allocated to it. It is inherently more resource-intensive to use these methods if your script is intended to write the HTML from which the browser creates the DOM in the first place.
Just write it and let the browser and interpreter do the work. That's what they are there for.
PS: I just tested using an onload param in the body tag and even at this point the document is still open and document.write() functions as intended. Also, there is no perceivable performance difference between the various methods in the latest version of Firefox. Of course there is a ton of caching probably going on somewhere in the hardware/software stack, but that's the point really - let the machine do the work. It may make a difference on a cheap smartphone though. Cheers!
The question depends on what you are actually trying to do.
Usually, instead of doing document.write you can use someElement.innerHTML or better, document.createElement with an someElement.appendChild.
You can also consider using a library like jQuery and using the modification functions in there: http://api.jquery.com/category/manipulation/
This is probably the most correct, direct replacement: insertAdjacentHTML.
Try to use getElementById() or getElementsByName() to access a specific element and then to use innerHTML property:
<html>
<body>
<div id="myDiv1"></div>
<div id="myDiv2"></div>
</body>
<script type="text/javascript">
var myDiv1 = document.getElementById("myDiv1");
var myDiv2 = document.getElementById("myDiv2");
myDiv1.innerHTML = "<b>Content of 1st DIV</b>";
myDiv2.innerHTML = "<i>Content of second DIV element</i>";
</script>
</html>
Use
var documentwrite =(value, method="", display="")=>{
switch(display) {
case "block":
var x = document.createElement("p");
break;
case "inline":
var x = document.createElement("span");
break;
default:
var x = document.createElement("p");
}
var t = document.createTextNode(value);
x.appendChild(t);
if(method==""){
document.body.appendChild(x);
}
else{
document.querySelector(method).appendChild(x);
}
}
and call the function based on your requirement as below
documentwrite("My sample text"); //print value inside body
documentwrite("My sample text inside id", "#demoid", "block"); // print value inside id and display block
documentwrite("My sample text inside class", ".democlass","inline"); // print value inside class and and display inline
I'm not sure if this will work exactly, but I thought of
var docwrite = function(doc) {
document.write(doc);
};
This solved the problem with the error messages for me.
I would like to pass a variable declared between <script> TAGs to the URL in a iframe TAG, like this:
<script>
var1 = 1;
</script>
<iframe src="http://link_to_site?param=var1>
But this does not work. Is it possible to do it and how should i do ?
Thanks
First you would need a way to identify the target element in your JavaScript code. For example, give the element an id attribute:
<iframe id="myIframe" />
(There are certainly other ways to identify an element. Fairly complex queries can be constructed to identify specific elements throughout the DOM. But an id is a particularly straightforward approach at least for this example.)
Then in your JavaScript code you'd get a reference to that element and set its src property:
var var1 = 1;
document.getElementById('myIframe').src = 'http://link_to_site?param=' + var1;
One thing to note is that this code would need to execute after that element exists on the page. If it runs above that element on the page then that element won't yet exist and getElementById() would return nothing.
"After" could mean that it runs immediately but exists "lower" on the page than the element. Or it could mean that the code is placed anywhere on the page but doesn't actually execute until a later time, such as in an event handler.
*FYI- Sorry, but the accepted answer doesn't actually work. Read below. *
I suggest that you use the jQuery JS library to assist in your task. It normalizes the JS environment so you don't have to worry about what browser your user is using.
It also makes it easier to do some simple tasks like getting specific elements etc.
Here is code that shows how to dynamically create an iFrame AFTER the page has finished loading. This is important because JS runs synchronously: in other words, if you write JS for a specific element, but that element doesn't yet exist on the page, the JS will fail.
By wrapping your code in:
$(document).ready(function(){})
you ensure that the page will finishing loading BEFORE your JS executes.
<script type="text/javascript" src="https://code.jquery.com/jquery-2.2.0.min.js"></script>
<body>
</body>
<script type="text/javascript">
var1 = 1;
$(document).ready(function(){
var theiFrame = '<iframe src="http://link_to_site?param=' + var1 + '">';
$('body').append(theiFrame);
})
</script>
iFrames are a little weird in that they mess with the "onload" event of the document. That's another reason to dynamically add your iFrame to the DOM instead of adding it as an HTML tag. Read this article for more info:
http://www.aaronpeters.nl/blog/iframe-loading-techniques-performance
In tutorials I've learnt to use document.write. Now I understand that by many this is frowned upon. I've tried print(), but then it literally sends it to the printer.
So what are alternatives I should use, and why shouldn't I use document.write? Both w3schools and MDN use document.write.
The reason that your HTML is replaced is because of an evil JavaScript function: document.write().
It is most definitely "bad form." It only works with webpages if you use it on the page load; and if you use it during runtime, it will replace your entire document with the input. And if you're applying it as strict XHTML structure it's not even valid code.
the problem:
document.write writes to the document stream. Calling document.write on a closed (or loaded) document automatically calls document.open which will clear the document.
-- quote from the MDN
document.write() has two henchmen, document.open(), and document.close(). When the HTML document is loading, the document is "open". When the document has finished loading, the document has "closed". Using document.write() at this point will erase your entire (closed) HTML document and replace it with a new (open) document. This means your webpage has erased itself and started writing a new page - from scratch.
I believe document.write() causes the browser to have a performance decrease as well (correct me if I am wrong).
an example:
This example writes output to the HTML document after the page has loaded. Watch document.write()'s evil powers clear the entire document when you press the "exterminate" button:
I am an ordinary HTML page. I am innocent, and purely for informational purposes. Please do not <input type="button" onclick="document.write('This HTML page has been succesfully exterminated.')" value="exterminate"/>
me!
the alternatives:
.innerHTML This is a wonderful alternative, but this attribute has to be attached to the element where you want to put the text.
Example: document.getElementById('output1').innerHTML = 'Some text!';
.createTextNode() is the alternative recommended by the W3C.
Example: var para = document.createElement('p');
para.appendChild(document.createTextNode('Hello, '));
NOTE: This is known to have some performance decreases (slower than .innerHTML). I recommend using .innerHTML instead.
the example with the .innerHTML alternative:
I am an ordinary HTML page.
I am innocent, and purely for informational purposes.
Please do not
<input type="button" onclick="document.getElementById('output1').innerHTML = 'There was an error exterminating this page. Please replace <code>.innerHTML</code> with <code>document.write()</code> to complete extermination.';" value="exterminate"/>
me!
<p id="output1"></p>
Here is code that should replace document.write in-place:
document.write=function(s){
var scripts = document.getElementsByTagName('script');
var lastScript = scripts[scripts.length-1];
lastScript.insertAdjacentHTML("beforebegin", s);
}
You can combine insertAdjacentHTML method and document.currentScript property.
The insertAdjacentHTML() method of the Element interface parses the specified text as HTML or XML and inserts the resulting nodes into the DOM tree at a specified position:
'beforebegin': Before the element itself.
'afterbegin': Just inside the element, before its first child.
'beforeend': Just inside the element, after its last child.
'afterend': After the element itself.
The document.currentScript property returns the <script> element whose script is currently being processed. Best position will be beforebegin — new HTML will be inserted before <script> itself. To match document.write's native behavior, one would position the text afterend, but then the nodes from consecutive calls to the function aren't placed in the same order as you called them (like document.write does), but in reverse. The order in which your HTML appears is probably more important than where they're place relative to the <script> tag, hence the use of beforebegin.
document.currentScript.insertAdjacentHTML(
'beforebegin',
'This is a document.write alternative'
)
As a recommended alternative to document.write you could use DOM manipulation to directly query and add node elements to the DOM.
Just dropping a note here to say that, although using document.write is highly frowned upon due to performance concerns (synchronous DOM injection and evaluation), there is also no actual 1:1 alternative if you are using document.write to inject script tags on demand.
There are a lot of great ways to avoid having to do this (e.g. script loaders like RequireJS that manage your dependency chains) but they are more invasive and so are best used throughout the site/application.
I fail to see the problem with document.write. If you are using it before the onload event fires, as you presumably are, to build elements from structured data for instance, it is the appropriate tool to use. There is no performance advantage to using insertAdjacentHTML or explicitly adding nodes to the DOM after it has been built. I just tested it three different ways with an old script I once used to schedule incoming modem calls for a 24/7 service on a bank of 4 modems.
By the time it is finished this script creates over 3000 DOM nodes, mostly table cells. On a 7 year old PC running Firefox on Vista, this little exercise takes less than 2 seconds using document.write from a local 12kb source file and three 1px GIFs which are re-used about 2000 times. The page just pops into existence fully formed, ready to handle events.
Using insertAdjacentHTML is not a direct substitute as the browser closes tags which the script requires remain open, and takes twice as long to ultimately create a mangled page. Writing all the pieces to a string and then passing it to insertAdjacentHTML takes even longer, but at least you get the page as designed. Other options (like manually re-building the DOM one node at a time) are so ridiculous that I'm not even going there.
Sometimes document.write is the thing to use. The fact that it is one of the oldest methods in JavaScript is not a point against it, but a point in its favor - it is highly optimized code which does exactly what it was intended to do and has been doing since its inception.
It's nice to know that there are alternative post-load methods available, but it must be understood that these are intended for a different purpose entirely; namely modifying the DOM after it has been created and memory allocated to it. It is inherently more resource-intensive to use these methods if your script is intended to write the HTML from which the browser creates the DOM in the first place.
Just write it and let the browser and interpreter do the work. That's what they are there for.
PS: I just tested using an onload param in the body tag and even at this point the document is still open and document.write() functions as intended. Also, there is no perceivable performance difference between the various methods in the latest version of Firefox. Of course there is a ton of caching probably going on somewhere in the hardware/software stack, but that's the point really - let the machine do the work. It may make a difference on a cheap smartphone though. Cheers!
The question depends on what you are actually trying to do.
Usually, instead of doing document.write you can use someElement.innerHTML or better, document.createElement with an someElement.appendChild.
You can also consider using a library like jQuery and using the modification functions in there: http://api.jquery.com/category/manipulation/
This is probably the most correct, direct replacement: insertAdjacentHTML.
Try to use getElementById() or getElementsByName() to access a specific element and then to use innerHTML property:
<html>
<body>
<div id="myDiv1"></div>
<div id="myDiv2"></div>
</body>
<script type="text/javascript">
var myDiv1 = document.getElementById("myDiv1");
var myDiv2 = document.getElementById("myDiv2");
myDiv1.innerHTML = "<b>Content of 1st DIV</b>";
myDiv2.innerHTML = "<i>Content of second DIV element</i>";
</script>
</html>
Use
var documentwrite =(value, method="", display="")=>{
switch(display) {
case "block":
var x = document.createElement("p");
break;
case "inline":
var x = document.createElement("span");
break;
default:
var x = document.createElement("p");
}
var t = document.createTextNode(value);
x.appendChild(t);
if(method==""){
document.body.appendChild(x);
}
else{
document.querySelector(method).appendChild(x);
}
}
and call the function based on your requirement as below
documentwrite("My sample text"); //print value inside body
documentwrite("My sample text inside id", "#demoid", "block"); // print value inside id and display block
documentwrite("My sample text inside class", ".democlass","inline"); // print value inside class and and display inline
I'm not sure if this will work exactly, but I thought of
var docwrite = function(doc) {
document.write(doc);
};
This solved the problem with the error messages for me.
I'm working on some custom Javascript for a CMS template at work. I want to be able to make a certain <li> element on the page receive the class of "current" for that page only. So in the global page head I have something like this:
<script type="text/javascript">
function makeCurrent(id) {
var current = document.getElementById(id);
current.setAttribute("class", "current"); // for most browsers
current.setAttribute("className", "current"); // for ie
}
</script>
Then in each individual page <head>, I have something like this:
<script type="text/javascript">makeCurrent("undergraduate")</script>
On the page I have a nav <ul>, with something like:
<li id="undergraduate">Undergraduate Program</li>
<li id="graduate">Graduate Program</li>
etc.
When I load the page, the class is not applied. When I look in the Firebug console, it gives the error message:
current is null
(x) current.setAttribute("class", "current");
I'm still getting the hang of writing solid, raw javascript (learning backwards after jQuery, you know how it goes), but I want to write it in just JS. What idiotic newbie mistake am I making?
Thanks everyone!
If you execute the javascript before the DOM tree has finished loading, it will return null. So an idea would be to call the function all the way at the end before you close the body tag.
This is why most JavaScript libraries have support for a dom-ready event, modern browsers have this as well (domcontentloaded) however for wide browser support it's a little trickier to do it for yourself (well, not that difficult, 3 or 4 different ways I think.)
The element does not exist yet when that script is being evaluated. Put it in the body's onload handler or something instead, so it executes once the DOM is in place.
An example of how to do this without touching any markup:
function callWhenLoaded(func) {
if (window.addEventListener) {
window.addEventListener("load", func, false);
} else if (window.attachEvent) {
window.attachEvent("onload", func);
}
}
callWhenLoaded(function() { makeCurrent("undergraduate"); });
The DOM is not fully loaded if you run makeCurrent in your head. You should put that script after your <li> tags.
Your code can be optimized: you can set a class attribute directly with current.className = 'current';.
The reason is that your script is being run before the page load is complete, and therefore before the DOM is populated.
You need to make sure you only call the function after page load is complete. Do this by triggering it using document.onload() or an onload event on the body tag.
After all the technical answers have been spewed out already, I'm going to skip all those which it very well could be and go for some of the more obvious ones I've run into which have caused me to facepalm once I've realised:
Typo in the identity
The identity isn't what you think it is because it's being generated or partially generated by the web framework you're using i.e. in ASP.NET you could set the client id to "MyControl" only to find that by the time it is rendered in the client it's "Page_1$Control_0$MyControl$1"
You've prepended it with a # in one or more of the incorrect places, for instance, although you're not using jQuery in your example if the object id is MyControl, in jQuery and CSS you reference it using #MyControl, but in the actual id of the object, you didn't use #. In document.getElementById() you don't use a # like you would in jQuery and CSS, but you may have used it inadvertently.
You've set the name element in the control instead of the id.
As other people have mentioned though, it could be down to not waiting for the element to be available at the time you're referencing it.
I've created a JavaScript script that can be pasted on someone's page to create an iFrame. I would like for the person to be able to paste the script where they would like the iFrame to appear.
However, I can't figure out how to append the DOM created iFrame to the location where the script has been pasted. It always appends it to the very bottom of the body.
How do I append in place?
Mm. You could do:
document.write("<div id='iframecontainer'></div>");
document.getElementById('iframecontainer').innerHTML = '...';
But that feels ugly/wrong in a lot of different levels. I am not aware of any other alternatives, though.
EDIT: Actually, a quick google search revealed this artlcle which discusses why document.write is ugly and a nice alternative for the particular pickle you're in: Give your script tag an ID!
<script id="iframeinserter" src=".."></script>
And then you can get a reference to the script tag and insert the iframe before it:
var newcontent = document.createElement('iframe');
var scr = document.getElementById('iframeinserter');
scr.parentNode.insertBefore(newcontent, scr);
Paulo's answer can also be done without the ID, by simply looking for the last <script> element. This must be the script block we're in, because JavaScript guarantees all content past the closing tag of the current script block has not yet been parsed(*):
var scripts= document.getElementsByTagName('script');
var script= scripts[scripts.length-1];
script.parentNode.insertBefore(d, script);
This can be put in an onload/ready function if you like, but if so the ‘var script’ must be calculated at include-time and not in the ready function (when that executes, more <script>s will have been parsed).
(*: except in the case of <script defer>.)
If the user can give an id of an element that will be where the iframe should be, then it would be possible to just use css to move the iframe to where it should be on the page.