<div>
でinnerHTML
を使用して、ページにいくつかのスクリプトをロードしようとしました。スクリプトはDOMにロードされているように見えますが、実行されません(少なくともFirefoxとChromeでは)。 innerHTML
でスクリプトを挿入するときにスクリプトを実行する方法はありますか?
サンプルコード:
<!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>
eval() を使用して、DOMテキストとして挿入したスクリプトコードを実行する必要があります。
MooToolsはこれを自動的に行いますが、jQueryも同様です(バージョンによって異なります。jQueryバージョン1.6+はeval
を使用します)。これにより、<script>
タグを解析してコンテンツをエスケープする手間や、その他の「落とし穴」を大幅に削減できます。
一般に、自分でeval()
を使用する場合は、<script>
などのHTMLマークアップなしでスクリプトコードを作成/送信する必要があります。これらは適切にeval()
になりません。
ここにあなたの問題に対する非常に興味深い解決策があります: http://24ways.org/2005/have-your-dom-and-script-it-too
したがって、スクリプトタグの代わりにこれを使用します。
<img src="empty.gif" onload="alert('test');this.parentNode.removeChild(this);" />
以下は、すべてのスクリプトを実行可能なスクリプトに再帰的に置き換えるメソッドです。
function nodeScriptReplace(node) {
if ( nodeScriptIs(node) === true ) {
node.parentNode.replaceChild( nodeScriptClone(node) , node );
}
else {
var i = 0;
var children = node.childNodes;
while ( i < children.length ) {
nodeScriptReplace( children[i++] );
}
}
return node;
}
function nodeScriptIs(node) {
return node.tagName === 'SCRIPT';
}
function nodeScriptClone(node){
var script = document.createElement("script");
script.text = node.innerHTML;
for( var i = node.attributes.length-1; i >= 0; i-- ) {
script.setAttribute( node.attributes[i].name, node.attributes[i].value );
}
return script;
}
呼び出しの例:
nodeScriptReplace(document.getElementsByTagName("body")[0]);
スクリプトを作成してからコンテンツを注入できます。
var g = document.createElement('script');
var s = document.getElementsByTagName('script')[0];
g.text = "alert(\"hi\");"
s.parentNode.insertBefore(g, s);
これはすべてのブラウザで動作します:)
私はこのコードを使用しました、それはうまく機能しています
var arr = MyDiv.getElementsByTagName('script')
for (var n = 0; n < arr.length; n++)
eval(arr[n].innerHTML)//run script inside div
まだこれをしようとしている人はいません、innerHTML
を使用してスクリプトを挿入することはできませんが、Blob
およびURL.createObjectURL
を使用して文字列をスクリプトタグに読み込むことは可能です。
文字列をスクリプトとして実行し、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)
});
}
私はこれを実際の実装から単純化したので、バグがないという約束はありません。しかし、原理は機能します。
スクリプトの実行後に値を取得する必要がない場合は、さらに簡単です。 Promise
およびonload
ビットを省いてください。スクリプトをラップしたり、グローバルなwindow.__load_script_exports_
プロパティを作成したりする必要さえありません。
広告サーバーで使用する要素のinnerHTMLを設定する再帰関数は次のとおりです。
// 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);
}
}
デモを見ることができます こちら 。
parent.innerHTML = code
ではなく$(parent).html(code)
を使用します。
以下は、document.write
を使用するスクリプトとsrc
属性を介してロードされたスクリプトも修正します。残念ながら、これでもGoogle AdSenseスクリプトでは機能しません。
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
})
}
Krasimir Tsonevには、すべての問題を克服する優れたソリューションがあります。彼の方法ではevalを使用する必要がないため、パフォーマンスやセキュリティの問題はありません。 jsを含むhtmlを含むinnerHTML文字列を設定し、すぐにDOM要素に変換すると同時に、コードに沿って存在するjs部分を実行できます。短く、シンプルで、望みどおりに動作します。
彼のソリューションをお楽しみください:
http://krasimirtsonev.com/blog/article/Convert-HTML-string-to-DOM-element
重要な注意事項:
テンプレートとdocument.importNodeを使用してみてください。以下に例を示します。
<!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>
この問題に対する私の解決策は、 Mutation Observer を設定して<script></script>
ノードを検出し、同じsrcを持つ新しい<script></script>
ノードに置き換えることです。例えば:
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});
ここでは、eval
を使用せず、scripts、linked scripts、およびmodulesで動作するソリューション。
この関数は3つのパラメーターを受け入れます。
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;
}
ガブリエルガルシアによるMutationObserversの言及は正しい方向に進んでいますが、私にはうまくいきませんでした。それがブラウザの奇抜によるものなのか、それとも私のミスによるものなのかはわかりませんが、結局私のために機能するバージョンは次のとおりでした:
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});
};
もちろん、element_to_watch
を変更する要素の名前に置き換える必要があります。
node.parentElement.added
は、document.head
に追加されるスクリプトタグを格納するために使用されます。外部ページの読み込みに使用される関数では、次のようなものを使用して、関連性のないスクリプトタグを削除できます。
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]);
}
}
そして、ロード関数の開始の例:
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
}
はい、できますが、DOMの外部で行う必要があり、順序が正しい必要があります。
var scr = '<scr'+'ipt>alert("foo")</scr'+'ipt>';
window.onload = function(){
var n = document.createElement("div");
n.innerHTML = scr;
document.body.appendChild(n);
}
...「foo」を警告します。これは機能しません:
document.getElementById("myDiv").innerHTML = scr;
ノードが最初に挿入されるため、これでも機能しません。
var scr = '<scr'+'ipt>alert("foo")</scr'+'ipt>';
window.onload = function(){
var n = document.createElement("div");
document.body.appendChild(n);
n.innerHTML = scr;
}
次のようにできます:
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);
}