Manipulating DOM data without affecting the view - javascript

<!DOCTYPE html>
<html><body>
<p id="intro">Hello <em id="abcd">intro</em> World!</p>
<script type="text/javascript">
var txt=document.getElementById("intro").innerHTML;
var el = document.createElement("span");
el.innerHTML = txt;
var aa = el.getElementById("abcd").innerHTML;
alert( aa );
</script>
</body></html>
The above is a simple snippet. Actually I have an HTML editor and when the user saves the data I should save only the required content. Here I am getting the content of an element and manipulating it with DOM and pass the details to the server. This way I will not change the page content (user view remains the same) and he/she will continue editing the document.
The above is a simple example but in the real case I have to remove, change and move certain elements. The above code fails el.getElementById("abcd").innerHTML. Appreciate any pointers.

You can create a hidden iframe to manipulate all your changes, thus creating a separate DOM, then simply pull back the results you want.
var iframe;
if (document.createElement && (iframe = document.createElement('iframe'))) {
iframe.name = iframe.id = "externalDocument";
iframe.className = "hidden";
document.body.appendChild(iframe);
var externalDocument;
if (iframe.contentDocument) {
externalDocument = iframe.contentDocument;
} else if (iframe.contentWindow) {
externalDocument = iframe.contentWindow.document;
}
else if (window.frames[iframe.name]) {
externalDocument = window.frames[iframe.name].document;
}
if (externalDocument) {
externalDocument.open();
externalDocument.write('<html><body><\/body><\/html>');
externalDocument.close();
/* Run your manipulations here */
var txt = document.getElementById("intro").innerHTML;
var el = document.createElement("span");
el.innerHTML = txt;
/* Attach your objects to the externalDocument */
externalDocument.body.appendChild(el);
/* Reference the externalDocument to manipulate */
var aa = externalDocument.getElementById("abcd").innerHTML;
alert(aa);
}
/* Completed manipulation - Remove iFrame */
document.removeChild(iframe);
}
I have it working here:
http://jsfiddle.net/ucpvP/

Try using jQuery like given below.
function SaveData() //Your add function
{
var txt=$("#intro").html();
$(document).append("<span id='abcd'>" + txt+ "</span>");
var aa = $("#abcd").hmtl();
alert(aa);
}

You can use a DOM Element that is never appended to the DOM.
I use this 'cleanup' function:
function cleanup(str){
var tester = document.createElement('div'),
invalid, result;
tester.innerHTML = str;
//elements I don't allow
invalid = tester.querySelectorAll('script,object,iframe,style,hr,canvas');
// the cleanup (remove unwanted elements)
for (var i=0;i<invalid.length;(i+=1)){
invalid[i].parentNode.removeChild(invalid[i]);
}
result = tester.innerHTML;
tester = invalid = null;
//diacritics to html-encoded
return result.replace(/[\u0080-\u024F]/g,
function(a) {return '&#'+a.charCodeAt(0)+';';}
)
.replace(/%/g,'%25');
}
//usage:
cleanup(document.getElementById("intro").innerHTML);
You can extend the function with your own code to remove, change and move certain elements.

Related

Copy the contents of a div into another div [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)
})
})

Javascript: How to check/compare if a String contains some tag?

I'd like to use pure JS to check if some String, the textareas .innerHTML = newContent below, contains some tag (h1in my case) at the beginning (=as first child). What would be the best way to do this?
Thanks!
function submitNewSectionContent(e) {
for (var i = 0; i < sections.length; i++)
let newHeading = document.getElementById('edit-title').value;
/* edit-title is text-input*/
let newContent = document.getElementById('edit-sectionText').innerHTML;
/* edit-sectionText is textarea */
if (newContent.indexOf('<h1>') > -1 && newContent.indexOf('<h1>') < 10) { /* <h1> is at beginning so replace with newHeading */
let toberemoved = newContent.match('<h1>.*<\/h1>');
newContent = newContent.replace(toberemoved[0], '').trim();
sections[i].innerHTML = '<h1>'+newHeading+'</h1>' + sections[i].innerHTML;
} elseย { /* newContent has no h1 as first child, so add h1 from newHeading */
sections[i].innerHTML = '<h1>'+newHeading+'</h1>' + newContent;
}
}
}
Problem with Regular expressions is they do not really work well with HTML. So Your best bet is to convert it to a DOM fragment and do the manipulations and convert it back. Only issue with this method really is you can lose formatting. There are libraries out there that can pretty print HTML.
function updateHeadline(txt) {
const ta = document.querySelector("textarea");
const data = ta.value; // read value, not innerHTML
const temp = document.createElement('div'); // temp div to hold html
temp.innerHTML = data; // set the html to the temp element
let firstChild = temp.firstElementChild // look at the dom
if (!firstChild || firstChild.tagName!=="H1") { // see if we have an h1
firstChild = document.createElement("h1") // if not create one
temp.prepend(firstChild) // add it to the front
}
firstChild.innerHTML = txt // set the new text of the h1
ta.value = temp.innerHTML // put the content back into the textarea
}
const btn = document.querySelector("button");
btn.addEventListener("click", function (e) {
e.preventDefault()
updateHeadline(document.querySelector("#text").value)
})
textarea {
width: 300px;
height: 200px;
}
<textarea>
<p>Some other text</p>
<p>Some more text</p>
</textarea>
<input value="foo" id="text"/>
<button>Set</button>
You could use regex like so, (updated based on comment)
if( /^\s*<h1>/gi.test(stringToTest) ) {
//logic here
}
It checks if the stringToTest begins with ^ tag
See here : https://regex101.com/r/vSo4sL/1
convert to dom, and parse dom
this portion of code makes it possible to treat a chain to retrieve titles placed in the H1 tag and (on the fly) treat the string of characters.
It's easily expandable for future processing : or tag processing or other ...!
commented code
<html>
<script>
var s="<H1>Hey Title</H1>\n Hello,\n other title <H1>Green!</H1>\n Ipsum dolore sit...";
console.log(s);
console.log("-------------------------");
var partialDoc = document.createElement( 'html' );
partialDoc.innerHTML = s;
var parsed='';
var titles=[];
treatment(partialDoc);
console.log("\n-------------------------\n");
console.log("parsed",parsed);
console.log("\n-------------------------\n");
console.log("s var contains "+titles.length+ " H1 tag");
console.log("titles "+titles);
function treatment(root) {
var child = root.firstChild;
while (child) {
// child.nodeName = H1 | H2 | P etc...
// child.nodeType = 1
// catch H1
if (child.nodeName=='H1') {
// append your title,
parsed+=" [H1 FOUND content= {"+child.innerText+"} H1])";
// or
// parsed+="<H1>"+child.innerText+"<H1>";
// add your own process here
// add this title in array
// or what you want...
titles.push(child.innerText);
// next part of document
child = child.nextSibling;
continue;
}
// capture other text than H1
if (child.nodeType==3) { // Node Type Text
parsed+=child.nodeValue;
}
if (child.nodeType==1) { // Node Type ELEMENT, : sub nodes...
treatment(child);
}
// continue the rest of doc
child = child.nextSibling;
}
}
</script>
</html>
One way you could do it is: Node.firstElementChild which will avoid giving child node as #text for white-spaces and Node.nodeName
let firstChild = document.getElementById('edit-sectionText').firstElementChild;
if(firstChild.nodeName === "H1"){
firstChild.innerHTML = "Replacement Value"
}
Note & Update: The earlier api that I had suggested Node.firstChild will not prevent white-spaces which gives #text node and comments as #comment node.
2nd Way: Node.children and picking the first child out of it should have a similar result to Node.firstElementChild.
let elem = document.getElementById('edit-sectionText');
if(elem){
let firstChild = elem.children[0];
}
Update based on comments: Using Dom Parser Interface
The interface allows to parse XML or HTML source from a string based on the mime type provided for its method parseFromString(string, mimeType)
It will give the top level #document node with parsed HTML from the string where if exists <h1> or <H1> at the beginning would be the first child of body and subsequently can be tested via tagName property.
Note: Takes care of preceding HTML comments and spaces at the beginning but a caveat is doesn't check fully closed tags ex: var s = \t <h1>I am a heading <h1> here the <h1> was never closed and in the result will two fully formed headings at the body with content : I am a heading and ""
let textAreaString = document.getElementById("edit-sectionText").value;
const domParser = new DOMParser();
const parsedDoc = domParser.parseFromString(textAreaString, "text/html");
if (parsedDoc.body.firstElementChild.tagName === "H1") {
//yes it starts with <h1> or <H1>
}

How to add JavaScript to a page after the document has already loaded? [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)
})
})

HTML to Javascript [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)
})
})

how to add javascript in innerHTML? [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)
})
})

Categories