Ejecutando elementos <script> insertados con .innerHTML

Resuelto phidah asked hace 14 años • 24 respuestas

Tengo un script que inserta contenido en un elemento usando innerHTML.

El contenido podría ser por ejemplo:

<script type="text/javascript">alert('test');</script>
<strong>test</strong>

El problema es que el código dentro de la <script>etiqueta no se ejecuta. Lo busqué en Google un poco pero no hubo soluciones aparentes. Si inserté el contenido usando jQuery, $(element).append(content);las partes del script se evaleditaron antes de inyectarse en el DOM.

¿Alguien tiene un fragmento de código que ejecute todos los <script>elementos? El código jQuery era un poco complejo, por lo que no podía entender cómo se hacía.

Editar :

Al echar un vistazo al código jQuery, logré descubrir cómo lo hace jQuery, lo que resultó en el siguiente código:

Demo:
<div id="element"></div>

<script type="text/javascript">
  function insertAndExecute(id, text)
  {
    domelement = document.getElementById(id);
    domelement.innerHTML = text;
    var scripts = [];

    ret = domelement.childNodes;
    for ( var i = 0; ret[i]; i++ ) {
      if ( scripts && nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
            scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
        }
    }

    for(script in scripts)
    {
      evalScript(scripts[script]);
    }
  }
  function nodeName( elem, name ) {
    return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
  }
  function evalScript( elem ) {
    data = ( elem.text || elem.textContent || elem.innerHTML || "" );

    var head = document.getElementsByTagName("head")[0] || document.documentElement,
    script = document.createElement("script");
    script.type = "text/javascript";
    script.appendChild( document.createTextNode( data ) );
    head.insertBefore( script, head.firstChild );
    head.removeChild( script );

    if ( elem.parentNode ) {
        elem.parentNode.removeChild( elem );
    }
  }

  insertAndExecute("element", "<scri"+"pt type='text/javascript'>document.write('This text should appear as well.')</scr"+"ipt><strong>this text should also be inserted.</strong>");
</script>
phidah avatar Apr 07 '10 18:04 phidah
Aceptado

Versión ES6 simplificada de la respuesta de @joshcomley con un ejemplo.

Sin JQuery, sin biblioteca, sin evaluación, sin cambios de DOM, solo Javascript puro.

http://plnkr.co/edit/MMegiu?p=preview

function setInnerHTML(elm, html) {
  elm.innerHTML = html;
  
  Array.from(elm.querySelectorAll("script"))
    .forEach( oldScriptEl => {
      const newScriptEl = document.createElement("script");
      
      Array.from(oldScriptEl.attributes).forEach( attr => {
        newScriptEl.setAttribute(attr.name, attr.value) 
      });
      
      const scriptText = document.createTextNode(oldScriptEl.innerHTML);
      newScriptEl.appendChild(scriptText);
      
      oldScriptEl.parentNode.replaceChild(newScriptEl, oldScriptEl);
  });
}

Uso

$0.innerHTML = HTML;    // does *NOT* run <script> tags in HTML
setInnerHTML($0, HTML); // does run <script> tags in HTML
allenhwkim avatar Dec 03 '2017 01:12 allenhwkim

Aquí hay una solución muy interesante a su problema: http://24ways.org/2005/have-your-dom-and-script-it-too

Entonces se vería así:

<img src="empty.gif" onload="alert('test');this.parentNode.removeChild(this);" />
user447963 avatar Sep 15 '2010 03:09 user447963

No debes usar la propiedad internalHTML sino el método appendChild del Nodo: un nodo en un árbol de documentos [HTML DOM]. De esta manera podrás llamar más tarde a tu código inyectado.

Asegúrese de comprender que node.innerHTML no es lo mismo que node.appendChild . Es posible que desee dedicar algo de tiempo a la Referencia del cliente Javascript para obtener más detalles y el DOM. Espero que lo siguiente ayude...

Trabajos de inyección de muestra:

<!DOCTYPE HTML>
<html>
<head>
    <title>test</title>
    <script language="javascript" type="text/javascript">
        function doOnLoad() {
            addScript('inject',"function foo(){ alert('injected'); }");
        }
    
        function addScript(inject,code) {
            var _in = document.getElementById('inject');
            var scriptNode = document.createElement('script');
            scriptNode.innerHTML = code;
            _in.appendChild(scriptNode);
        }
    </script>
</head>
<body onload="doOnLoad();">
    <div id="header">some content</div>
    <div id="inject"></div>
    <input type="button" onclick="foo(); return false;" value="Test Injected" />
</body>
</html>
Expandir fragmento

Andreas avatar Apr 07 '2010 14:04 Andreas

El script del OP no funciona en IE 7. Con la ayuda de SO, aquí hay un script que sí funciona:

exec_body_scripts: function(body_el) {
  // Finds and executes scripts in a newly added element's body.
  // Needed since innerHTML does not run scripts.
  //
  // Argument body_el is an element in the dom.

  function nodeName(elem, name) {
    return elem.nodeName && elem.nodeName.toUpperCase() ===
              name.toUpperCase();
  };

  function evalScript(elem) {
    var data = (elem.text || elem.textContent || elem.innerHTML || "" ),
        head = document.getElementsByTagName("head")[0] ||
                  document.documentElement,
        script = document.createElement("script");

    script.type = "text/javascript";
    try {
      // doesn't work on ie...
      script.appendChild(document.createTextNode(data));      
    } catch(e) {
      // IE has funky script nodes
      script.text = data;
    }

    head.insertBefore(script, head.firstChild);
    head.removeChild(script);
  };

  // main section of function
  var scripts = [],
      script,
      children_nodes = body_el.childNodes,
      child,
      i;

  for (i = 0; children_nodes[i]; i++) {
    child = children_nodes[i];
    if (nodeName(child, "script" ) &&
      (!child.type || child.type.toLowerCase() === "text/javascript")) {
          scripts.push(child);
      }
  }

  for (i = 0; scripts[i]; i++) {
    script = scripts[i];
    if (script.parentNode) {script.parentNode.removeChild(script);}
    evalScript(scripts[i]);
  }
};
Larry K avatar Jul 14 '2010 20:07 Larry K

Aquí hay un script más corto y eficiente que también funciona para scripts con la srcpropiedad:

function insertAndExecute(id, text) {
    document.getElementById(id).innerHTML = text;
    var scripts = Array.prototype.slice.call(document.getElementById(id).getElementsByTagName("script"));
    for (var i = 0; i < scripts.length; i++) {
        if (scripts[i].src != "") {
            var tag = document.createElement("script");
            tag.src = scripts[i].src;
            document.getElementsByTagName("head")[0].appendChild(tag);
        }
        else {
            eval(scripts[i].innerHTML);
        }
    }
}

Nota: si bien evalpuede causar una vulnerabilidad de seguridad si no se usa correctamente, es mucho más rápido que crear una etiqueta de script sobre la marcha.

DividedByZero avatar Nov 03 '2014 14:11 DividedByZero

Pruebe este fragmento:

function stripAndExecuteScript(text) {
    var scripts = '';
    var cleaned = text.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(){
        scripts += arguments[1] + '\n';
        return '';
    });

    if (window.execScript){
        window.execScript(scripts);
    } else {
        var head = document.getElementsByTagName('head')[0];
        var scriptElement = document.createElement('script');
        scriptElement.setAttribute('type', 'text/javascript');
        scriptElement.innerText = scripts;
        head.appendChild(scriptElement);
        head.removeChild(scriptElement);
    }
    return cleaned;
};


var scriptString = '<scrip' + 't + type="text/javascript">alert(\'test\');</scr' + 'ipt><strong>test</strong>';
document.getElementById('element').innerHTML = stripAndExecuteScript(scriptString);
fantactuka avatar Jun 03 '2010 14:06 fantactuka