3

I have a situation where I need to import external content into a dom element, then take that input and inject it into a different dom element. However, it must be done without any library.

The content can be a mix of script tags, a tags, iframes etc...

In the following example I'm saving the external content into the text area element, then use the append method of jQuery and saves it into the container, which results in the execution of the script and the addition of the anchor element to the container div.

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
  <textarea style="display:none" id ="thirdParty">
    <a href="http://www.google.com">
      test 
    </a>
    <script>
      alert("3rd party content");

    </script>
  </textarea>

  <div id="container">

  </div>

  <button onclick="inject()">
    Test
  </button>

  <script>
    function inject() {
      $('#container').append(document.getElementById('thirdParty').value);
    }
  </script>

</body>

Is there anyway to recieve the same result without the usage of jQuery?

Zakaria Acharki
  • 66,747
  • 15
  • 75
  • 101
  • I've updated the answer. It's now fully functional and doesn't rely on `eval()`. This is the correct way to solve this issue. – Scott Marcus Nov 17 '16 at 16:41

3 Answers3

3

Yes, using innerHTML :

document.getElementById('container').innerHTML += document.getElementById('thirdParty').value

Hope this helps.

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
  <textarea style="display:none" id ="thirdParty">
    <a href="http://www.google.com">
      test 
    </a>
    <script>
      alert("3rd party content");
    </script>
  </textarea>

  <div id="container">

  </div>

  <button onclick="inject()">
    Test
  </button>

  <script>
    function inject() {
      document.getElementById('container').innerHTML += document.getElementById('thirdParty').value;

      var elements = document.getElementById('container').getElementsByTagName('script')
      for (var i = 0; i < elements.length; i++){
        eval(elements[i].innerHTML);
      }
    }
  </script>
</body>
Zakaria Acharki
  • 66,747
  • 15
  • 75
  • 101
  • 1
    I've already tried that, but unfortunately the script (in this example the alert message) doesn't get executed as happens with jQuery, which is something I need. – user5841295 Nov 17 '16 at 15:42
  • 1
    Well, if you insert javascript code with innerHTML [it will not be executed automatically unless you use jQuery or mootools](http://stackoverflow.com/questions/1197575/can-scripts-be-inserted-with-innerhtml). So you will have to `eval` it yourself manually, according to the linked answer. – Roberto Linares Nov 17 '16 at 15:49
  • @RobertoLinares Not true, see my answer. – Scott Marcus Nov 17 '16 at 15:52
  • 1
    @ScottMarcus I tried your response on the StackOverflow's default demo and on jsfiddle but the alert doesn't show. – Roberto Linares Nov 17 '16 at 15:56
  • 1
    `eval` should not be used in any production circumstance. – Scott Marcus Nov 17 '16 at 15:58
  • @ZakariaAcharki See my updated answer for a working solution that avoids the dreaded `eval()`. – Scott Marcus Nov 17 '16 at 16:43
  • @ScottMarcus Yes indeed, creating a script element via document.createElement() is much better than eval(). – Roberto Linares Nov 17 '16 at 23:17
3

The JS runtime won't run the script by simply copying the script from the hidden placeholder because the runtime has already finished parsing the code. The script must be injected as a NEW <script> element for it to be processed.

Also, relying on eval() is widely known to be a dangerous practice and is to be avoided at all costs.

This code does exactly what you are seeking:

var btn = document.getElementById("btnGo");
btn.addEventListener("click", inject);
var output = document.getElementById("container");

function inject() {
  
      // It's important to extract the contents of a textarea without parsing
      // so use ".textContent", instead of ".innerHTML". Otherwise, you'll
      // get the "<" and ">" characters as "&lt;" and "&gt;"
      var newContent = document.getElementById("thirdParty").textContent;
  
      // We'll need to separate the <script> from the non-script content ***
      var start = newContent.indexOf("<script>") + 8;
      var end = newContent.indexOf("<\/script>");
      var scriptCode = newContent.substring(start, end);
      console.log("Script code is: " + scriptCode);
  
      var nonScriptCode = newContent.replace(scriptCode, "").replace("<script>","").replace("<\/script>","");
      console.log("Non-script code is: " + nonScriptCode);

      // *******************************************************************
  
      // In order for the script to execute properly, it must be injected into
      // the DOM as a NEW script, not simply moved from one location to the other.
      var scriptNode = document.createElement("script");

      // And here, we also don't want the content to be parsed as HTML 
      // (in case there are < symbols in the script) so we use ".textContent" again.
      scriptNode.textContent = scriptCode;
  
      // Then we can inject that script into the DOM
       output.appendChild(scriptNode);
  
      // Now, when injecting the content into another element, it's important
      // that the contents do get parsed, so here we use ".innerHTML"
      output.innerHTML= nonScriptCode;
}
<textarea style="display:none" id ="thirdParty">
    <a href="http://www.google.com">
        test 
    </a>
    <script>
        alert("3rd party content");

    </script>
</textarea>

<div id="container">

</div>

<button id="btnGo">
    Test
</button>
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • @Liam That's just because of the Stack Overflow snippet environment. You can see the link appear and clicking it will "attempt" to navigate you, but again the SO snippet environment is blocking the actual navigation. – Scott Marcus Nov 17 '16 at 15:56
  • 1
    @Liam I've updated the answer. It's now fully functional and doesn't rely on `eval()`. This is the correct way to solve this issue. – Scott Marcus Nov 17 '16 at 16:41
-1

Replace your inject to:

function inject() {
    document.getElementById('container').innerHTML += document.getElementById('thirdParty').value;
}
Zakaria Acharki
  • 66,747
  • 15
  • 75
  • 101
JKong
  • 254
  • 1
  • 8