javascript get current filescript path - javascript

How can I get the path of the current script in javascript using jQuery
for example I have site.com/js/script.js and there is a code in this script:
$(document).ready(function() {
alert( ... this code ... );
}
It Should return alert box with the "/js/script.js" message. This function should work like magic __FILE__ constant in php
So, why do I need this?
I want to set background image dynamically:
$("somediv").css("background-image", "url(" + $SCRIPT_PATH + "/images/img.png)");
and images directory is the /js directory, near the script.js file
and js folder's name can be dynamically set, so script and images can be in the /myprogect/javascript-files directory

You can rely on the fact that each <script> element has to be evaluated* before the next one is inserted into the DOM.
This means that the script currently evaluated (as long as it is part of your markup and not dynamically inserted) will be the last one in the NodeList retrieved with getElementsByTagName( 'script' ).
This allows you to read that elements src attribute and from that determine the folder that the script is being served from - like this:
var scriptEls = document.getElementsByTagName( 'script' );
var thisScriptEl = scriptEls[scriptEls.length - 1];
var scriptPath = thisScriptEl.src;
var scriptFolder = scriptPath.substr(0, scriptPath.lastIndexOf( '/' )+1 );
console.log( [scriptPath, scriptFolder] );
I tried this technique with 3 scripts loaded from different folders and get this output
/*
["http://127.0.0.1:8000/dfhdfh/folder1/script1.js", "http://127.0.0.1:8000/dfhdfh/folder1/"]
["http://127.0.0.1:8000/dfhdfh/folder2/script2.js", "http://127.0.0.1:8000/dfhdfh/folder2/"]
["http://127.0.0.1:8000/dfhdfh/folder3/script3.js", "http://127.0.0.1:8000/dfhdfh/folder3/"]
*/
* from John Resigs blog linked to above
This means that when the script finally executes that it'll be the
last script in the DOM - and even the last element in the DOM (the
rest of the DOM is built incrementally as it hits more script tags, or
until the end of the document).
Update
As pimvdb points out - this will work as the script is being evaluated. You will need to store the path somehow if you are going to use it later. You can't query the DOM at a later point. If you use the same snippet for each script the value of scriptFolder will be overwritten for each script. You should give each script a unique variable perhaps?
Wrapping your script in its own scope closes over the value of scriptFolder making it available to the rest of the script without fear of being overwritten
(function() {
var scriptEls = document.getElementsByTagName( 'script' );
var thisScriptEl = scriptEls[scriptEls.length - 1];
var scriptPath = thisScriptEl.src;
var scriptFolder = scriptPath.substr(0, scriptPath.lastIndexOf( '/' )+1 );
$( function(){
$('#my-div').click(function(e){
alert(scriptFolder);
});
});
})();

Add the following code to your JS :
var retrieveURL = function(filename) {
var scripts = document.getElementsByTagName('script');
if (scripts && scripts.length > 0) {
for (var i in scripts) {
if (scripts[i].src && scripts[i].src.match(new RegExp(filename+'\\.js$'))) {
return scripts[i].src.replace(new RegExp('(.*)'+filename+'\\.js$'), '$1');
}
}
}
};
Suppose these are the scripts called in your HTML :
<script src="assets/js/awesome.js"></script>
<script src="assets/js/oldcode/fancy-stuff.js"></script>
<script src="assets/js/jquery/cool-plugin.js"></script>
Then, you can use the function like this
var awesomeURL = retrieveURL('awesome');
// result : 'assets/js/'
var awesomeURL = retrieveURL('fancy-stuff');
// result : 'assets/js/oldcode/'
var awesomeURL = retrieveURL('cool-plugin');
// result : 'assets/js/jquery/'
Note that this only works when there are no two script files in your HTML with the same name. If you have two scripts with the same name that are located in a different folder, the result will be unreliable.
Note
If you dynamically add scripts to your page, you need to make sure your code is executed after the last script has been added to the DOM.
The follow example shows how to do this with one dynamically loaded script. It outputs a JSON array with the src link for scripts that load an external js file and a base64-encoded string of the JS content for inline scripts:
var addScript = function(src, callback) {
var s = document.createElement( 'script' );
s.setAttribute( 'src', src );
document.body.appendChild( s );
s.onload = callback;
}
var retrieveURL = function(filename) {
var scripts = document.getElementsByTagName('script');
if (scripts && scripts.length > 0) {
for (var i in scripts) {
if (scripts[i].src && scripts[i].src.match(new RegExp(filename+'\\.js$'))) {
return scripts[i].src.replace(new RegExp('(.*)'+filename+'\\.js$'), '$1');
}
}
}
};
addScript('https://code.jquery.com/jquery-1.12.4.min.js', function() {
var scripts = document.getElementsByTagName('script');
var sources = [];
for (var i = 0; i < scripts.length; i++) {
if(scripts[i].src == '') {
sources.push(btoa(scripts[i].innerHTML));
} else {
sources.push(scripts[i].src);
}
}
document.body.innerHTML += '<pre>' + JSON.stringify(sources,null,2) + '</pre>';
});
See also this Fiddle.

I don't think jQuery provide such a functionality.
Anyway you can get currentc script path path (both fully http and relative) by reading the answer here: What is my script src URL?

Can't you set a kind of path variable in the js? So, you save the path of the file in the file itself.
For example:
$(function() {
var FilePath = "js/some/path";
setMyDynamicImageUsingPath(FilePath);
});
// ...
function setMyDynamicImageUsingPath(path) {
$("somediv").css("background-image", "url(" + path + "/images/img.png)");
}

Related

How do I load an HTML file (Which has another js file linked) using an XMLHttpRequest so that the javascript from the HTML page loaded still works? [duplicate]

I tried to load some scripts into a page using innerHTML on a <div>. It appears that the script loads into the DOM, but it is never executed (at least in Firefox and Chrome). Is there a way to have scripts execute when inserting them with innerHTML?
Sample code:
<!DOCTYPE html>
<html>
<body onload="document.getElementById('loader').innerHTML = '<script>alert(\'hi\')<\/script>'">
Shouldn't an alert saying 'hi' appear?
<div id="loader"></div>
</body>
</html>
Here is a method that recursively replaces all scripts with executable ones:
function nodeScriptReplace(node) {
if ( nodeScriptIs(node) === true ) {
node.parentNode.replaceChild( nodeScriptClone(node) , node );
}
else {
var i = -1, children = node.childNodes;
while ( ++i < children.length ) {
nodeScriptReplace( children[i] );
}
}
return node;
}
function nodeScriptClone(node){
var script = document.createElement("script");
script.text = node.innerHTML;
var i = -1, attrs = node.attributes, attr;
while ( ++i < attrs.length ) {
script.setAttribute( (attr = attrs[i]).name, attr.value );
}
return script;
}
function nodeScriptIs(node) {
return node.tagName === 'SCRIPT';
}
Example call:
nodeScriptReplace(document.getElementsByTagName("body")[0]);
You have to use eval() to execute any script code that you've inserted as DOM text.
MooTools will do this for you automatically, and I'm sure jQuery would as well (depending on the version. jQuery version 1.6+ uses eval). This saves a lot of hassle of parsing out <script> tags and escaping your content, as well as a bunch of other "gotchas".
Generally if you're going to eval() it yourself, you want to create/send the script code without any HTML markup such as <script>, as these will not eval() properly.
Here is a very interesting solution to your problem:
http://24ways.org/2005/have-your-dom-and-script-it-too
So use this instead of script tags:
<img src="empty.gif" onload="alert('test');this.parentNode.removeChild(this);" />
You can create script and then inject the content.
var g = document.createElement('script');
var s = document.getElementsByTagName('script')[0];
g.text = "alert(\"hi\");"
s.parentNode.insertBefore(g, s);
This works in all browsers :)
I do this every time I wanna insert a script tag dynamically !
const html =
`<script>
alert('👋 there ! Wanna grab a 🍺');
</script>`;
const scriptEl = document.createRange().createContextualFragment(html);
parent.append(scriptEl);
NOTE: ES6 used
EDIT 1:
Clarification for you guys - I've seen a lot of answers use appendChild and wanted to let you guys know that it works exactly as append
I used this code, it is working fine
var arr = MyDiv.getElementsByTagName('script')
for (var n = 0; n < arr.length; n++)
eval(arr[n].innerHTML)//run script inside div
I had this problem with innerHTML, I had to append a Hotjar script to the "head" tag of my Reactjs application and it would have to execute right after appending.
One of the good solutions for dynamic Node import into the "head" tag is React-helment module.
Also, there is a useful solution for the proposed issue:
No script tags in innerHTML!
It turns out that HTML5 does not allow script tags to be dynamically added using the innerHTML property. So the following will not execute and there will be no alert saying Hello World!
element.innerHTML = "<script>alert('Hello World!')</script>";
This is documented in the HTML5 spec:
Note: script elements inserted using innerHTML do not execute when
they are inserted.
But beware, this doesn't mean innerHTML is safe from cross-site scripting. It is possible to execute JavaScript via innerHTML without using tags as illustrated on MDN's innerHTML page.
Solution: Dynamically adding scripts
To dynamically add a script tag, you need to create a new script element and append it to the target element.
You can do this for external scripts:
var newScript = document.createElement("script");
newScript.src = "http://www.example.com/my-script.js";
target.appendChild(newScript);
And inline scripts:
var newScript = document.createElement("script");
var inlineScript = document.createTextNode("alert('Hello World!');");
newScript.appendChild(inlineScript);
target.appendChild(newScript);
Here's a more modern (and concise) version of mmm's awesome solution:
function executeScriptElements(containerElement) {
const scriptElements = containerElement.querySelectorAll("script");
Array.from(scriptElements).forEach((scriptElement) => {
const clonedElement = document.createElement("script");
Array.from(scriptElement.attributes).forEach((attribute) => {
clonedElement.setAttribute(attribute.name, attribute.value);
});
clonedElement.text = scriptElement.text;
scriptElement.parentNode.replaceChild(clonedElement, scriptElement);
});
}
Note: I've also tried alternative solutions using cloneNode() or outerHTML but that didn't work.
You could do it like this:
var mydiv = document.getElementById("mydiv");
var content = "<script>alert(\"hi\");<\/script>";
mydiv.innerHTML = content;
var scripts = mydiv.getElementsByTagName("script");
for (var i = 0; i < scripts.length; i++) {
eval(scripts[i].innerText);
}
Here a solution that does not use eval, and works with scripts, linked scripts , as well as with modules.
The function accepts 3 parameters :
html : String with the html code to insert
dest : reference to the target element
append : boolean flag to enable appending at the end of the target element html
function insertHTML(html, dest, append=false){
// if no append is requested, clear the target element
if(!append) dest.innerHTML = '';
// create a temporary container and insert provided HTML code
let container = document.createElement('div');
container.innerHTML = html;
// cache a reference to all the scripts in the container
let scripts = container.querySelectorAll('script');
// get all child elements and clone them in the target element
let nodes = container.childNodes;
for( let i=0; i< nodes.length; i++) dest.appendChild( nodes[i].cloneNode(true) );
// force the found scripts to execute...
for( let i=0; i< scripts.length; i++){
let script = document.createElement('script');
script.type = scripts[i].type || 'text/javascript';
if( scripts[i].hasAttribute('src') ) script.src = scripts[i].src;
script.innerHTML = scripts[i].innerHTML;
document.head.appendChild(script);
document.head.removeChild(script);
}
// done!
return true;
}
For anyone still trying to do this, no, you can't inject a script using innerHTML, but it is possible to load a string into a script tag using a Blob and URL.createObjectURL.
I've created an example that lets you run a string as a script and get the 'exports' of the script returned through a promise:
function loadScript(scriptContent, moduleId) {
// create the script tag
var scriptElement = document.createElement('SCRIPT');
// create a promise which will resolve to the script's 'exports'
// (i.e., the value returned by the script)
var promise = new Promise(function(resolve) {
scriptElement.onload = function() {
var exports = window["__loadScript_exports_" + moduleId];
delete window["__loadScript_exports_" + moduleId];
resolve(exports);
}
});
// wrap the script contents to expose exports through a special property
// the promise will access the exports this way
var wrappedScriptContent =
"(function() { window['__loadScript_exports_" + moduleId + "'] = " +
scriptContent + "})()";
// create a blob from the wrapped script content
var scriptBlob = new Blob([wrappedScriptContent], {type: 'text/javascript'});
// set the id attribute
scriptElement.id = "__loadScript_module_" + moduleId;
// set the src attribute to the blob's object url
// (this is the part that makes it work)
scriptElement.src = URL.createObjectURL(scriptBlob);
// append the script element
document.body.appendChild(scriptElement);
// return the promise, which will resolve to the script's exports
return promise;
}
...
function doTheThing() {
// no evals
loadScript('5 + 5').then(function(exports) {
// should log 10
console.log(exports)
});
}
I've simplified this from my actual implementation, so no promises that there aren't any bugs in it. But the principle works.
If you don't care about getting any value back after the script runs, it's even easier; just leave out the Promise and onload bits. You don't even need to wrap the script or create the global window.__load_script_exports_ property.
Here is a recursive function to set the innerHTML of an element that I use in our ad server:
// o: container to set the innerHTML
// html: html text to set.
// clear: if true, the container is cleared first (children removed)
function setHTML(o, html, clear) {
if (clear) o.innerHTML = "";
// Generate a parseable object with the html:
var dv = document.createElement("div");
dv.innerHTML = html;
// Handle edge case where innerHTML contains no tags, just text:
if (dv.children.length===0){ o.innerHTML = html; return; }
for (var i = 0; i < dv.children.length; i++) {
var c = dv.children[i];
// n: new node with the same type as c
var n = document.createElement(c.nodeName);
// copy all attributes from c to n
for (var j = 0; j < c.attributes.length; j++)
n.setAttribute(c.attributes[j].nodeName, c.attributes[j].nodeValue);
// If current node is a leaf, just copy the appropriate property (text or innerHTML)
if (c.children.length == 0)
{
switch (c.nodeName)
{
case "SCRIPT":
if (c.text) n.text = c.text;
break;
default:
if (c.innerHTML) n.innerHTML = c.innerHTML;
break;
}
}
// If current node has sub nodes, call itself recursively:
else setHTML(n, c.innerHTML, false);
o.appendChild(n);
}
}
You can see the demo here.
Filter your script tags and run each of them with eval
var tmp= document.createElement('div');
tmp.innerHTML = '<script>alert("hello")></script>';
[...tmp.children].filter(x => x.nodeName === 'SCRIPT').forEach(x => eval(x.innerText));
Yes you can, but you have to do it outside of the DOM and the order has to be right.
var scr = '<scr'+'ipt>alert("foo")</scr'+'ipt>';
window.onload = function(){
var n = document.createElement("div");
n.innerHTML = scr;
document.body.appendChild(n);
}
...will alert 'foo'. This won't work:
document.getElementById("myDiv").innerHTML = scr;
And even this won't work, because the node is inserted first:
var scr = '<scr'+'ipt>alert("foo")</scr'+'ipt>';
window.onload = function(){
var n = document.createElement("div");
document.body.appendChild(n);
n.innerHTML = scr;
}
Krasimir Tsonev has a great solution that overcome all problems.
His method doesn't need using eval, so no performance nor security problems exist.
It allows you to set innerHTML string contains html with js and translate it immediately to an DOM element while also executes the js parts exist along the code. short ,simple, and works exactly as you want.
Enjoy his solution:
http://krasimirtsonev.com/blog/article/Convert-HTML-string-to-DOM-element
Important notes:
You need to wrap the target element with div tag
You need to wrap the src string with div tag.
If you write the src string directly and it includes js parts, please take attention to write the closing script tags correctly (with \ before /) as this is a string.
Use $(parent).html(code) instead of parent.innerHTML = code.
The following also fixes scripts that use document.write and scripts loaded via src attribute. Unfortunately even this doesn't work with Google AdSense scripts.
var oldDocumentWrite = document.write;
var oldDocumentWriteln = document.writeln;
try {
document.write = function(code) {
$(parent).append(code);
}
document.writeln = function(code) {
document.write(code + "<br/>");
}
$(parent).html(html);
} finally {
$(window).load(function() {
document.write = oldDocumentWrite
document.writeln = oldDocumentWriteln
})
}
Source
Try using template and document.importNode. Here is an example:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Sample</title>
</head>
<body>
<h1 id="hello_world">Sample</h1>
<script type="text/javascript">
var div = document.createElement("div");
var t = document.createElement('template');
t.innerHTML = "Check Console tab for javascript output: Hello world!!!<br/><script type='text/javascript' >console.log('Hello world!!!');<\/script>";
for (var i=0; i < t.content.childNodes.length; i++){
var node = document.importNode(t.content.childNodes[i], true);
div.appendChild(node);
}
document.body.appendChild(div);
</script>
</body>
</html>
You can also wrap your <script> like this and it will get executed:
<your target node>.innerHTML = '<iframe srcdoc="<script>alert(top.document.title);</script>"></iframe>';
Please note: The scope inside srcdoc refers to the iframe, so you have to use top like in the example above to access the parent document.
Single line solution below:
document.getElementsByTagName("head")[0].append(document.createRange().createContextualFragment('<script src="https://google.com/file.js"></script>'));
My solution for this problem is to set a Mutation Observer to detect <script></script> nodes and then replace it with a new <script></script> node with the same src. For example:
let parentNode = /* node to observe */ void 0
let observer = new MutationObserver(mutations=>{
mutations.map(mutation=>{
Array.from(mutation.addedNodes).map(node=>{
if ( node.parentNode == parentNode ) {
let scripts = node.getElementsByTagName('script')
Array.from(scripts).map(script=>{
let src = script.src
script = document.createElement('script')
script.src = src
return script
})
}
})
})
})
observer.observe(document.body, {childList: true, subtree: true});
Gabriel Garcia's mention of MutationObservers is on the right track, but didn't quite work for me. I am not sure if that was because of a browser quirk or due to a mistake on my end, but the version that ended up working for me was the following:
document.addEventListener("DOMContentLoaded", function(event) {
var observer = new MutationObserver(mutations=>{
mutations.map(mutation=>{
Array.from(mutation.addedNodes).map(node=>{
if (node.tagName === "SCRIPT") {
var s = document.createElement("script");
s.text=node.text;
if (typeof(node.parentElement.added) === 'undefined')
node.parentElement.added = [];
node.parentElement.added[node.parentElement.added.length] = s;
node.parentElement.removeChild(node);
document.head.appendChild(s);
}
})
})
})
observer.observe(document.getElementById("element_to_watch"), {childList: true, subtree: true,attributes: false});
};
Of course, you should replace element_to_watch with the name of the element that is being modified.
node.parentElement.added is used to store the script tags that are added to document.head. In the function used to load the external page, you can use something like the following to remove no longer relevant script tags:
function freeScripts(node){
if (node === null)
return;
if (typeof(node.added) === 'object') {
for (var script in node.added) {
document.head.removeChild(node.added[script]);
}
node.added = {};
}
for (var child in node.children) {
freeScripts(node.children[child]);
}
}
And an example of the beginning of a load function:
function load(url, id, replace) {
if (document.getElementById(id) === null) {
console.error("Element of ID "+id + " does not exist!");
return;
}
freeScripts(document.getElementById(id));
var xhttp = new XMLHttpRequest();
// proceed to load in the page and modify innerHTML
}
Building up on Danny '365CSI' Engelman's comment, here is an universal solution:
<script>
alert("This script always runs.");
script01 = true;
</script>
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
onload="if(typeof script01==='undefined') eval(this.previousElementSibling.innerHTML)">
Use this as innerHTML (i.e. loaded by XMLHttpRequest) or directly (i.e. inserted by PHP backend), the script always loads once.
Explanation: script loaded as innerHTML is not executed, but onload content atribute is. If the script was not executed (added as innerHTML) then the script is executed in image onload event. If the script was loaded (added by backend) then script01 variable is defined and onload will not run the script for the second time.
Execute (Java Script) tag from innerHTML
Replace your script element with div having a class attribute class="javascript" and close it with </div>
Don't change the content that you want to execute (previously it was in script tag and now it is in div tag)
Add a style in your page...
<style type="text/css"> .javascript { display: none; } </style>
Now run eval using jquery(Jquery js should be already included)
$('.javascript').each(function() {
eval($(this).text());
});`
You can explore more here, at my blog.
For me the best way is to insert the new HTML content through innerHtml and then use
setTimeout(() => {
var script_el = document.createElement("script")
script_el.src = 'script-to-add.js'
document.body.appendChild(script_el)
}, 500)
The setTimeout is not required but it works better. This worked for me.
My own twist, using modern JS and typescript. Not sure why people are filtering on tagName etc when querySelector is right there.
Works a charm for me:
function makeScriptsExecutable(el: Element) {
el.querySelectorAll("script").forEach(script => {
const clone = document.createElement("script")
for (const attr of script.attributes) {
clone.setAttribute(attr.name, attr.value)
}
clone.text = script.innerHTML
script.parentNode?.replaceChild(clone, script)
})
}
simple, no eval, no functions:
fetch('/somepage')
.then(x=>x.text())
.then(x=>{
divDestination.innerHTML=x;
divDestination.querySelectorAll("script")
.forEach(x=>{
var sc=document.createElement("script");
sc.appendChild(document.createTextNode(x.innerText));
divDestination.appendChild(sc)
})
})

Ejs compiled function does not execute script tag [duplicate]

I tried to load some scripts into a page using innerHTML on a <div>. It appears that the script loads into the DOM, but it is never executed (at least in Firefox and Chrome). Is there a way to have scripts execute when inserting them with innerHTML?
Sample code:
<!DOCTYPE html>
<html>
<body onload="document.getElementById('loader').innerHTML = '<script>alert(\'hi\')<\/script>'">
Shouldn't an alert saying 'hi' appear?
<div id="loader"></div>
</body>
</html>
Here is a method that recursively replaces all scripts with executable ones:
function nodeScriptReplace(node) {
if ( nodeScriptIs(node) === true ) {
node.parentNode.replaceChild( nodeScriptClone(node) , node );
}
else {
var i = -1, children = node.childNodes;
while ( ++i < children.length ) {
nodeScriptReplace( children[i] );
}
}
return node;
}
function nodeScriptClone(node){
var script = document.createElement("script");
script.text = node.innerHTML;
var i = -1, attrs = node.attributes, attr;
while ( ++i < attrs.length ) {
script.setAttribute( (attr = attrs[i]).name, attr.value );
}
return script;
}
function nodeScriptIs(node) {
return node.tagName === 'SCRIPT';
}
Example call:
nodeScriptReplace(document.getElementsByTagName("body")[0]);
You have to use eval() to execute any script code that you've inserted as DOM text.
MooTools will do this for you automatically, and I'm sure jQuery would as well (depending on the version. jQuery version 1.6+ uses eval). This saves a lot of hassle of parsing out <script> tags and escaping your content, as well as a bunch of other "gotchas".
Generally if you're going to eval() it yourself, you want to create/send the script code without any HTML markup such as <script>, as these will not eval() properly.
Here is a very interesting solution to your problem:
http://24ways.org/2005/have-your-dom-and-script-it-too
So use this instead of script tags:
<img src="empty.gif" onload="alert('test');this.parentNode.removeChild(this);" />
You can create script and then inject the content.
var g = document.createElement('script');
var s = document.getElementsByTagName('script')[0];
g.text = "alert(\"hi\");"
s.parentNode.insertBefore(g, s);
This works in all browsers :)
I do this every time I wanna insert a script tag dynamically !
const html =
`<script>
alert('👋 there ! Wanna grab a 🍺');
</script>`;
const scriptEl = document.createRange().createContextualFragment(html);
parent.append(scriptEl);
NOTE: ES6 used
EDIT 1:
Clarification for you guys - I've seen a lot of answers use appendChild and wanted to let you guys know that it works exactly as append
I used this code, it is working fine
var arr = MyDiv.getElementsByTagName('script')
for (var n = 0; n < arr.length; n++)
eval(arr[n].innerHTML)//run script inside div
I had this problem with innerHTML, I had to append a Hotjar script to the "head" tag of my Reactjs application and it would have to execute right after appending.
One of the good solutions for dynamic Node import into the "head" tag is React-helment module.
Also, there is a useful solution for the proposed issue:
No script tags in innerHTML!
It turns out that HTML5 does not allow script tags to be dynamically added using the innerHTML property. So the following will not execute and there will be no alert saying Hello World!
element.innerHTML = "<script>alert('Hello World!')</script>";
This is documented in the HTML5 spec:
Note: script elements inserted using innerHTML do not execute when
they are inserted.
But beware, this doesn't mean innerHTML is safe from cross-site scripting. It is possible to execute JavaScript via innerHTML without using tags as illustrated on MDN's innerHTML page.
Solution: Dynamically adding scripts
To dynamically add a script tag, you need to create a new script element and append it to the target element.
You can do this for external scripts:
var newScript = document.createElement("script");
newScript.src = "http://www.example.com/my-script.js";
target.appendChild(newScript);
And inline scripts:
var newScript = document.createElement("script");
var inlineScript = document.createTextNode("alert('Hello World!');");
newScript.appendChild(inlineScript);
target.appendChild(newScript);
Here's a more modern (and concise) version of mmm's awesome solution:
function executeScriptElements(containerElement) {
const scriptElements = containerElement.querySelectorAll("script");
Array.from(scriptElements).forEach((scriptElement) => {
const clonedElement = document.createElement("script");
Array.from(scriptElement.attributes).forEach((attribute) => {
clonedElement.setAttribute(attribute.name, attribute.value);
});
clonedElement.text = scriptElement.text;
scriptElement.parentNode.replaceChild(clonedElement, scriptElement);
});
}
Note: I've also tried alternative solutions using cloneNode() or outerHTML but that didn't work.
You could do it like this:
var mydiv = document.getElementById("mydiv");
var content = "<script>alert(\"hi\");<\/script>";
mydiv.innerHTML = content;
var scripts = mydiv.getElementsByTagName("script");
for (var i = 0; i < scripts.length; i++) {
eval(scripts[i].innerText);
}
Here a solution that does not use eval, and works with scripts, linked scripts , as well as with modules.
The function accepts 3 parameters :
html : String with the html code to insert
dest : reference to the target element
append : boolean flag to enable appending at the end of the target element html
function insertHTML(html, dest, append=false){
// if no append is requested, clear the target element
if(!append) dest.innerHTML = '';
// create a temporary container and insert provided HTML code
let container = document.createElement('div');
container.innerHTML = html;
// cache a reference to all the scripts in the container
let scripts = container.querySelectorAll('script');
// get all child elements and clone them in the target element
let nodes = container.childNodes;
for( let i=0; i< nodes.length; i++) dest.appendChild( nodes[i].cloneNode(true) );
// force the found scripts to execute...
for( let i=0; i< scripts.length; i++){
let script = document.createElement('script');
script.type = scripts[i].type || 'text/javascript';
if( scripts[i].hasAttribute('src') ) script.src = scripts[i].src;
script.innerHTML = scripts[i].innerHTML;
document.head.appendChild(script);
document.head.removeChild(script);
}
// done!
return true;
}
For anyone still trying to do this, no, you can't inject a script using innerHTML, but it is possible to load a string into a script tag using a Blob and URL.createObjectURL.
I've created an example that lets you run a string as a script and get the 'exports' of the script returned through a promise:
function loadScript(scriptContent, moduleId) {
// create the script tag
var scriptElement = document.createElement('SCRIPT');
// create a promise which will resolve to the script's 'exports'
// (i.e., the value returned by the script)
var promise = new Promise(function(resolve) {
scriptElement.onload = function() {
var exports = window["__loadScript_exports_" + moduleId];
delete window["__loadScript_exports_" + moduleId];
resolve(exports);
}
});
// wrap the script contents to expose exports through a special property
// the promise will access the exports this way
var wrappedScriptContent =
"(function() { window['__loadScript_exports_" + moduleId + "'] = " +
scriptContent + "})()";
// create a blob from the wrapped script content
var scriptBlob = new Blob([wrappedScriptContent], {type: 'text/javascript'});
// set the id attribute
scriptElement.id = "__loadScript_module_" + moduleId;
// set the src attribute to the blob's object url
// (this is the part that makes it work)
scriptElement.src = URL.createObjectURL(scriptBlob);
// append the script element
document.body.appendChild(scriptElement);
// return the promise, which will resolve to the script's exports
return promise;
}
...
function doTheThing() {
// no evals
loadScript('5 + 5').then(function(exports) {
// should log 10
console.log(exports)
});
}
I've simplified this from my actual implementation, so no promises that there aren't any bugs in it. But the principle works.
If you don't care about getting any value back after the script runs, it's even easier; just leave out the Promise and onload bits. You don't even need to wrap the script or create the global window.__load_script_exports_ property.
Here is a recursive function to set the innerHTML of an element that I use in our ad server:
// o: container to set the innerHTML
// html: html text to set.
// clear: if true, the container is cleared first (children removed)
function setHTML(o, html, clear) {
if (clear) o.innerHTML = "";
// Generate a parseable object with the html:
var dv = document.createElement("div");
dv.innerHTML = html;
// Handle edge case where innerHTML contains no tags, just text:
if (dv.children.length===0){ o.innerHTML = html; return; }
for (var i = 0; i < dv.children.length; i++) {
var c = dv.children[i];
// n: new node with the same type as c
var n = document.createElement(c.nodeName);
// copy all attributes from c to n
for (var j = 0; j < c.attributes.length; j++)
n.setAttribute(c.attributes[j].nodeName, c.attributes[j].nodeValue);
// If current node is a leaf, just copy the appropriate property (text or innerHTML)
if (c.children.length == 0)
{
switch (c.nodeName)
{
case "SCRIPT":
if (c.text) n.text = c.text;
break;
default:
if (c.innerHTML) n.innerHTML = c.innerHTML;
break;
}
}
// If current node has sub nodes, call itself recursively:
else setHTML(n, c.innerHTML, false);
o.appendChild(n);
}
}
You can see the demo here.
Filter your script tags and run each of them with eval
var tmp= document.createElement('div');
tmp.innerHTML = '<script>alert("hello")></script>';
[...tmp.children].filter(x => x.nodeName === 'SCRIPT').forEach(x => eval(x.innerText));
Yes you can, but you have to do it outside of the DOM and the order has to be right.
var scr = '<scr'+'ipt>alert("foo")</scr'+'ipt>';
window.onload = function(){
var n = document.createElement("div");
n.innerHTML = scr;
document.body.appendChild(n);
}
...will alert 'foo'. This won't work:
document.getElementById("myDiv").innerHTML = scr;
And even this won't work, because the node is inserted first:
var scr = '<scr'+'ipt>alert("foo")</scr'+'ipt>';
window.onload = function(){
var n = document.createElement("div");
document.body.appendChild(n);
n.innerHTML = scr;
}
Krasimir Tsonev has a great solution that overcome all problems.
His method doesn't need using eval, so no performance nor security problems exist.
It allows you to set innerHTML string contains html with js and translate it immediately to an DOM element while also executes the js parts exist along the code. short ,simple, and works exactly as you want.
Enjoy his solution:
http://krasimirtsonev.com/blog/article/Convert-HTML-string-to-DOM-element
Important notes:
You need to wrap the target element with div tag
You need to wrap the src string with div tag.
If you write the src string directly and it includes js parts, please take attention to write the closing script tags correctly (with \ before /) as this is a string.
Use $(parent).html(code) instead of parent.innerHTML = code.
The following also fixes scripts that use document.write and scripts loaded via src attribute. Unfortunately even this doesn't work with Google AdSense scripts.
var oldDocumentWrite = document.write;
var oldDocumentWriteln = document.writeln;
try {
document.write = function(code) {
$(parent).append(code);
}
document.writeln = function(code) {
document.write(code + "<br/>");
}
$(parent).html(html);
} finally {
$(window).load(function() {
document.write = oldDocumentWrite
document.writeln = oldDocumentWriteln
})
}
Source
Try using template and document.importNode. Here is an example:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Sample</title>
</head>
<body>
<h1 id="hello_world">Sample</h1>
<script type="text/javascript">
var div = document.createElement("div");
var t = document.createElement('template');
t.innerHTML = "Check Console tab for javascript output: Hello world!!!<br/><script type='text/javascript' >console.log('Hello world!!!');<\/script>";
for (var i=0; i < t.content.childNodes.length; i++){
var node = document.importNode(t.content.childNodes[i], true);
div.appendChild(node);
}
document.body.appendChild(div);
</script>
</body>
</html>
You can also wrap your <script> like this and it will get executed:
<your target node>.innerHTML = '<iframe srcdoc="<script>alert(top.document.title);</script>"></iframe>';
Please note: The scope inside srcdoc refers to the iframe, so you have to use top like in the example above to access the parent document.
Single line solution below:
document.getElementsByTagName("head")[0].append(document.createRange().createContextualFragment('<script src="https://google.com/file.js"></script>'));
My solution for this problem is to set a Mutation Observer to detect <script></script> nodes and then replace it with a new <script></script> node with the same src. For example:
let parentNode = /* node to observe */ void 0
let observer = new MutationObserver(mutations=>{
mutations.map(mutation=>{
Array.from(mutation.addedNodes).map(node=>{
if ( node.parentNode == parentNode ) {
let scripts = node.getElementsByTagName('script')
Array.from(scripts).map(script=>{
let src = script.src
script = document.createElement('script')
script.src = src
return script
})
}
})
})
})
observer.observe(document.body, {childList: true, subtree: true});
Gabriel Garcia's mention of MutationObservers is on the right track, but didn't quite work for me. I am not sure if that was because of a browser quirk or due to a mistake on my end, but the version that ended up working for me was the following:
document.addEventListener("DOMContentLoaded", function(event) {
var observer = new MutationObserver(mutations=>{
mutations.map(mutation=>{
Array.from(mutation.addedNodes).map(node=>{
if (node.tagName === "SCRIPT") {
var s = document.createElement("script");
s.text=node.text;
if (typeof(node.parentElement.added) === 'undefined')
node.parentElement.added = [];
node.parentElement.added[node.parentElement.added.length] = s;
node.parentElement.removeChild(node);
document.head.appendChild(s);
}
})
})
})
observer.observe(document.getElementById("element_to_watch"), {childList: true, subtree: true,attributes: false});
};
Of course, you should replace element_to_watch with the name of the element that is being modified.
node.parentElement.added is used to store the script tags that are added to document.head. In the function used to load the external page, you can use something like the following to remove no longer relevant script tags:
function freeScripts(node){
if (node === null)
return;
if (typeof(node.added) === 'object') {
for (var script in node.added) {
document.head.removeChild(node.added[script]);
}
node.added = {};
}
for (var child in node.children) {
freeScripts(node.children[child]);
}
}
And an example of the beginning of a load function:
function load(url, id, replace) {
if (document.getElementById(id) === null) {
console.error("Element of ID "+id + " does not exist!");
return;
}
freeScripts(document.getElementById(id));
var xhttp = new XMLHttpRequest();
// proceed to load in the page and modify innerHTML
}
Building up on Danny '365CSI' Engelman's comment, here is an universal solution:
<script>
alert("This script always runs.");
script01 = true;
</script>
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
onload="if(typeof script01==='undefined') eval(this.previousElementSibling.innerHTML)">
Use this as innerHTML (i.e. loaded by XMLHttpRequest) or directly (i.e. inserted by PHP backend), the script always loads once.
Explanation: script loaded as innerHTML is not executed, but onload content atribute is. If the script was not executed (added as innerHTML) then the script is executed in image onload event. If the script was loaded (added by backend) then script01 variable is defined and onload will not run the script for the second time.
Execute (Java Script) tag from innerHTML
Replace your script element with div having a class attribute class="javascript" and close it with </div>
Don't change the content that you want to execute (previously it was in script tag and now it is in div tag)
Add a style in your page...
<style type="text/css"> .javascript { display: none; } </style>
Now run eval using jquery(Jquery js should be already included)
$('.javascript').each(function() {
eval($(this).text());
});`
You can explore more here, at my blog.
For me the best way is to insert the new HTML content through innerHtml and then use
setTimeout(() => {
var script_el = document.createElement("script")
script_el.src = 'script-to-add.js'
document.body.appendChild(script_el)
}, 500)
The setTimeout is not required but it works better. This worked for me.
My own twist, using modern JS and typescript. Not sure why people are filtering on tagName etc when querySelector is right there.
Works a charm for me:
function makeScriptsExecutable(el: Element) {
el.querySelectorAll("script").forEach(script => {
const clone = document.createElement("script")
for (const attr of script.attributes) {
clone.setAttribute(attr.name, attr.value)
}
clone.text = script.innerHTML
script.parentNode?.replaceChild(clone, script)
})
}
simple, no eval, no functions:
fetch('/somepage')
.then(x=>x.text())
.then(x=>{
divDestination.innerHTML=x;
divDestination.querySelectorAll("script")
.forEach(x=>{
var sc=document.createElement("script");
sc.appendChild(document.createTextNode(x.innerText));
divDestination.appendChild(sc)
})
})

Displaying file name extensions in html using external js

I am trying to display the file name extensions using html and an external javascript but my website remains blank. I got this example code from another stackoverflow answer but I am unable to get it to work. Shouldn't the extension of my variables be shown when I call the function in my html like such?
<script>getExtension(file1);</script>
js
var file1 = "index.php";
var file2 = "test.js";
function getExtension(filename) {
return filename.substring(filename.lastIndexOf('.')+1, filename.length) || filename;
}
You are getting the extension correctly but you don't write it to your output anywhere. There are different approaches to output the value, here are two possibilities:
document.write()
The Document.write() method writes a string of text to a document stream. But be careful, calling document.write on a closed (loaded) document automatically calls document.open, which will clear the document.
var file1 = "index.php";
var file2 = "test.js";
function getExtension(filename) {
return filename.substring(filename.lastIndexOf('.')+1, filename.length) || filename;
}
// This will add "php" to the document
document.write(getExtension(file1));
// This will clear the document and replace it with "js" - added 1 second delay to visualize it
window.onload = function () {
setTimeout( 'document.write(getExtension(file2))', 1000 );
}
<h1>My content</h1>
createTextNode() + appendChild()
You can also create a text node and append it to your body. Of course you could also create any other element and add it wherever you want.
var file1 = "index.php";
var file2 = "test.js";
function getExtension(filename) {
return filename.substring(filename.lastIndexOf('.')+1, filename.length) || filename;
}
// Create a text node
var t = document.createTextNode(getExtension(file1));
// Append it to the body
document.body.appendChild(t);
<h1>My content</h1>

HTML JavaScript delay downloading img src until node in DOM

Hi I have markup sent to me from a server and I set it as the innerHTML of a div element for the purpose of traversing the tree, finding image nodes, and changing their src values. Is there a way to prevent the original src value from being downloaded?
Here is what I am doing
function replaceImageSrcsInMarkup(markup) {
var div = document.createElement('div');
div.innerHTML = markup;
var images = div.getElementsByTagName('img');
images.forEach(replaceSrc);
return div.innerHTML;
}
The problem is that in browsers as soon as you do:
var img = document.createElement('img'); img.src = 'someurl.com' the browser fires off a request to someurl.com. Is there a way to prevent this without resorting to parsing the markup myself? If there is in no other way does anyone know a good way of parsing the markup with as little code as possible to accomplish my goal?
I know you are already happy with your solution, but I think it would be worth sharing a safe method for future users.
You can now simply use the DOMParser object to generate an external document from your HTML string, instead of using a div created by your current document as container.
DOMParser specifically avoids the pitfalls mentioned in the question and other threats: no img src download, no JavaScript execution, even in elements attributes.
So in your case you can safely do:
function replaceImageSrcsInMarkup(markup) {
var parser = new DOMParser(),
doc = parser.parseFromString(markup, "text/html");
// Manipulate `doc` as a regular document
var images = doc.getElementsByTagName('img');
for (var i = 0; i < images.length; i += 1) {
replaceSrc(images[i]);
}
return doc.body.innerHTML;
}
Demo: http://jsfiddle.net/94b7gyg9/1/
Note: with your current code, browsers will still try downloading the resource initially specified in your img nodes src attribute, even if you change it before the end of JS execution. Trace network transactions in this demo: http://jsfiddle.net/94b7gyg9/
Rather than append the new markup to the DOM before you change the img sources, create an element, set it's inner HTML, change the source of the images and then finally, append the changed markup to the page.
Here's a fully-worked sample.
<!DOCTYPE html>
<html>
<head>
<script>
"use strict";
function byId(id,parent){return (parent == undefined ? document : parent).getElementById(id);}
//function allByClass(className,parent){return (parent == undefined ? document : parent).getElementsByClassName(className);}
function allByTag(tagName,parent){return (parent == undefined ? document : parent).getElementsByTagName(tagName);}
function newEl(tag){return document.createElement(tag);}
//function newTxt(txt){return document.createTextNode(txt);}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded()
{
byId('goBtn').addEventListener('click', onGoBtnClick, false);
}
var dummyString = "<img src='img/girl.png'/><img src='img/gfx07.jpg'/>";
function onGoBtnClick(evt)
{
var div = newEl('div');
div.innerHTML = dummyString;
var mImgs = allByTag('img', div);
for (var i=0, n=mImgs.length; i<n; i++)
{
mImgs[i].src = "img/murderface.jpg";
}
document.body.appendChild(div);
}
</script>
<style>
</style>
</head>
<body>
<button id='goBtn'>GO!</button>
</body>
</html>
You could directly parse the markup string using a regex to replace the img src. Searching for all the img src urls in the string and then replacing them with the new url.
var regex = /<img[^>]+src="?([^"\s]+)"?\s*\/>/g;
var imgUrls = [];
while ( m = regex.exec( markup ) ) {
imgUrls.push( m[1] );
}
imgUrls.forEach(function(url) {
markup = markup.replace(url,'new-url');
});
Another solution might be, if you have access to it, to set the all the img src to an empty string, and put the url in in a data-src attribute. Having your markup string look like something like this
markup = '
';
Then setting this markup to your div.innerHTML won't trigger any download from the browser. And you can still parse it using regular DOM selector.
div.innerHTML = markup;
var images = div.getElementsByTagName('img');
images.forEach(function(img){
var oldSrc = img.getAttribute('data-src');
img.setAttribute('src', 'new-url');
});

How to use Javascript to check and load CSS if not loaded?

I need to check (in Javascript) if a CSS file is loaded and if not then to load it. jQuery is fine.
Just check to see if a <link> element exists with the href attribute set to your CSS file's URL:
if (!$("link[href='/path/to.css']").length)
$('<link href="/path/to.css" rel="stylesheet">').appendTo("head");
The plain ol' JS method is simple too, using the document.styleSheets collection:
function loadCSSIfNotAlreadyLoadedForSomeReason () {
var ss = document.styleSheets;
for (var i = 0, max = ss.length; i < max; i++) {
if (ss[i].href == "/path/to.css")
return;
}
var link = document.createElement("link");
link.rel = "stylesheet";
link.href = "/path/to.css";
document.getElementsByTagName("head")[0].appendChild(link);
}
loadCSSIfNotAlreadyLoadedForSomeReason();
I just had to write something like that and I wanted to share it. This one is prepared for multiple cases.
If there is no request for the css file (css file isn't linked ...)
If there is a request for the css file, but if it failed (css file is no longer available ...)
var styles = document.styleSheets;
for (var i = 0; i < styles.length; i++) {
// checking if there is a request for template.css
if (styles[i].href.match("template")) {
console.log("(Iteration: " + i + ") Request for template.css is found.");
// checking if the request is not successful
// when it is successful .cssRules property is set to null
if (styles[i].cssRules != null && styles[i].cssRules.length == 0) {
console.log("(Iteration: " + i + ") Request for template.css failed.");
// fallback, make your modification
// since the request failed, we don't need to iterate through other stylesheets
break;
} else {
console.log("(Iteration: " + i + ") Request for template.css is successful.");
// template.css is loaded successfully, we don't need to iterate through other stylesheets
break;
}
}
// if there isn't a request, we fallback
// but we need to fallback when the iteration is done
// because we don't want to apply the fallback each iteration
// it's not like our css file is the first css to be loaded
else if (i == styles.length-1) {
console.log("(Iteration: " + i + ") There is no request for template.css.");
// fallback, make your modification
}
}
TL;DR version
var styles = document.styleSheets;
for (var i = 0; i < styles.length; i++) {
if (styles[i].href.match("css-file-name-here")) {
if (styles[i].cssRules != null && styles[i].cssRules.length == 0) {
// request for css file failed, make modification
break;
}
} else if (i == styles.length-1) {
// there is no request for the css file, make modification
}
}
Update: Since my answer got a few upvotes and this led me to revise the code, I decided to update it.
// document.styleSheets holds the style sheets from LINK and STYLE elements
for (var i = 0; i < document.styleSheets.length; i++) {
// Checking if there is a request for the css file
// We iterate the style sheets with href attribute that are created from LINK elements
// STYLE elements don't have href attribute, so we ignore them
// We also check if the href contains the css file name
if (document.styleSheets[i].href && document.styleSheets[i].href.match("/template.css")) {
console.log("There is a request for the css file.");
// Checking if the request is unsuccessful
// There is a request for the css file, but is it loaded?
// If it is, the length of styleSheets.cssRules should be greater than 0
// styleSheets.cssRules contains all of the rules in the css file
// E.g. b { color: red; } that's a rule
if (document.styleSheets[i].cssRules.length == 0) {
// There is no rule in styleSheets.cssRules, this suggests two things
// Either the browser couldn't load the css file, that the request failed
// or the css file is empty. Browser might have loaded the css file,
// but if it's empty, .cssRules will be empty. I couldn't find a way to
// detect if the request for the css file failed or if the css file is empty
console.log("Request for the css file failed.");
// There is a request for the css file, but it failed. Fallback
// We don't need to check other sheets, so we break;
break;
} else {
// If styleSheets.cssRules.length is not 0 (>0), this means
// rules from css file is loaded and the request is successful
console.log("Request for the css file is successful.");
break;
}
}
// If there isn't a request for the css file, we fallback
// But only when the iteration is done
// Because we don't want to apply the fallback at each iteration
else if (i == document.styleSheets.length - 1) {
// Fallback
console.log("There is no request for the css file.");
}
}
TL;DR
for (var i = 0; i < document.styleSheets.length; i++) {
if (document.styleSheets[i].href && document.styleSheets[i].href.match("/template.css")) {
if (document.styleSheets[i].cssRules.length == 0) {
// Fallback. There is a request for the css file, but it failed.
break;
}
} else if (i == document.styleSheets.length - 1) {
// Fallback. There is no request for the css file.
}
}
Something like this will do (using jQuery):
function checkStyleSheet(url){
var found = false;
for(var i = 0; i < document.styleSheets.length; i++){
if(document.styleSheets[i].href==url){
found=true;
break;
}
}
if(!found){
$('head').append(
$('<link rel="stylesheet" type="text/css" href="' + url + '" />')
);
}
}
Peer to the comment made by JFK about the accepted answer:
I understood the question as "how to check whether a css file is
loaded or not" rather than "how to check if the element
exists".
The element may exist (and the path may be correct too) but it
doesn't mean that the css file was successful loaded.
If you access a link Element via getElementById you'll not be able to check/read the style defined inside the CSS file.
In order to check if a style has been successfully loaded we have to use getComputedStyle (or currentStyle for IE).
HTML
//somewhere in your html document
<div id="css_anchor"></div>
CSS
//somewhere in your main stylesheet
#css_anchor{display:none;}
JAVASCRIPT
//js function to check the computed value of a style element
function get_computed_style(id, name){
var element = document.getElementById(id);
return element.currentStyle ? element.currentStyle[name] : window.getComputedStyle ? window.getComputedStyle(element, null).getPropertyValue(name) : null;
}
//on document ready check if #css_anchor has been loaded
$(document).ready( function() {
if(get_computed_style('css_anchor', 'display')!='none'){
//if #css_anchor style doesn't exist append an alternate stylesheet
var alternateCssUrl = 'http://example.com/my_alternate_stylesheet.css';
var stylesheet = document.createElement('link');
stylesheet.href = alternateCssUrl;
stylesheet.rel = 'stylesheet';
stylesheet.type = 'text/css';
document.getElementsByTagName('head')[0].appendChild(stylesheet);
}
});
Part of the answer comes from: myDiv.style.display returns blank when set in master stylesheet
Demo here: http://jsfiddle.net/R9F7R/
Apart from all the nice answers above, you can simply put a dummy element inside your markup and in your css file, give it any style other than default. Then in the code check if the attribute is applied to the dummy element, and if not, load the css. Just a thought though, not a neat way to do that thing you want done.
My 2 cents. This checks whether there are any rules set on the css or not, meaning that it was or not successfully loaded
if(jQuery("link[href='/style.css']").prop('sheet').cssRules.length == 0){
//Load the css you want
}
The document object contains a stylesheet collection with all the loaded stylesheets.
For a reference see http://www.javascriptkit.com/domref/stylesheet.shtml
You can loop this collection to verify that the stylesheet you want to verify is in it and thus loaded by the browser.
document.styleSheets[0] //access the first external style sheet on the page
There are some browser incompatibilities you should look out for though.
One way: use document.getElementsByTagName("link") iterate over each and check if its href is equal to the CSS file you check.
Another way: if you know some CSS rule being set only in that file, check if this rule really apply e.g. check if background of something is really red.
You can either check if the filename is within your markup, like:
var lnks = document.getElementsByTagName('link'),
loadcss = true;
for(var link in lnks) {
href = link.getAttribute('href');
if( href.indexOf('foooobar.css') > -1) ){
loadcss = false;
return false;
}
});
if( loadcss ) {
var lnk = document.createElement('link'),
head = document.getElementsByTagName('head')[0] || document.documentElement;
lnk.rel = 'stylesheet';
lnk.type = 'text/css';
lnk.href = '//' + location.host + 'foooobar.css';
head.insertBefore(lnk, head.firstChild);
}
or you could check for a specific className which should be available, if the stylesheet was loaded. This probably comes a little closer to a feature detection.
var links = document.getElementsByTagName('link');
var file = 'my/file.css';
var found = false;
for ( var i in links )
{
if ( links[i].type == 'text/css' && file == links[i].href ) {
found = true; break;
}
}
if ( !( found ) ) {
var styles = document.getElementsByTagName('style');
var regexp = new RegExp('/\#import url\("?' + file + '"?\);/');
for ( var i in styles )
{
if ( styles[i].src == file ) {
found = true; break;
} else if ( styles[i].innerHTML.match(regexp) ) {
found = true; break;
}
}
}
if ( !( found ) ) {
var elm = document.createElement('link');
elm.href = file;
document.documentElement.appendChild(elm);
}
For a nice consistent and repeatable experience, I've written these two jQuery plugins that mimic the $.getScript(url, callback) jQuery method (however they will NOT force reloading from the server like $.getScript(). There are two methods: one that will load a CSS file anytime it's called, and one that will only load it once. I find the former handy during development when I'm making changes, and the latter great for a speedy deployment.
/**
* An AJAX method to asynchronously load a CACHED CSS resource
* Note: This removes the jQuery default behaviour of forcing a refresh by means
* of appending a datestamp to the request URL. Actual caching WILL be subject to
* server/browser policies
*/
$.getCachedCss = function getCachedCss(url, callback)
{
$('<link>',{rel:'stylesheet', type:'text/css', 'href':url, media:'screen'}).appendTo('head');
if (typeof callback == 'function')
callback();
}
/**
* An AJAX method to asynchronously load a CACHED CSS resource Only ONCE.
* Note: This removes the jQuery default behaviour of forcing a refresh by means
* of appending a datestamp to the request URL. Actual caching WILL be subject to
* server/browser policies
*/
$.getCachedCssOnce = function getCachedCssOnce(url, callback)
{
if (!$("link[href='" + url + "']").length) {
$.getCachedCss(url, callback);
if (typeof callback == 'function')
callback();
}
}
Usage example:
$(function() {
$.getCachedCssOnce("pathToMyCss/main.css");
)}
Usage example with callback:
$(function() {
$.getCachedCssOnce("pathToMyCss/main.css", function() {
// Do something once the CSS is loaded
});
use .sheet in jQuery:
HTML:
<link rel="stylesheet" href="custom.css">
jQuery:
if($("link[href='custom.css']")[0].sheet.cssRules.length==0){
//custom.css was not loaded, do your backup loading here
}
simple way using javascript..,
loadCssIfNotLoaded('https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css');
loadCssIfNotLoaded('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css');
function loadCssIfNotLoaded(url) {
var element=document.querySelectorAll('link[href="' + url + '"]');
if (element.length == 0)
{
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
document.getElementsByTagName("head")[0].appendChild(link);
}
}
In one line, with jQuery.
If the #witness div is visible, we have to load the css file.
In the HTML, we have a:
<div id="witness"></div>
In the CSS file to load, we have:
#witness{display:none;}
So, if the css file is loaded, the #witness div is not visible. We can check with jQuery and make decision.
!$('#witness').is(':visible') || loadCss() ;
As a snippet:
function loadCss(){
//...
console.log('Css file required');
};
!$('#witness').is(':visible') || loadCss();
#witness{display:none;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<div id="witness"></div>

Categories

Resources