8

I'm trying to build some small widgets tools that webmasters can embed in their websites.

Is there any way that the webmaster can simply load this tool by including a script like this?

<script src="http://mysite/widget.js"></script>

I tried to inject it by loading it in AJAX and then do an appendChild() on the body, it's working this way but the JS is not executed.

Content to be injected:

<div>one div</div>
<script>alert('some js')</script>

widget.js

function load(content) {
  var $body = document.body,
      $div = document.createElement("div");
  $div.id = "widget";
  $div.innerHTML = content; 
  $body.appendChild($div);
}

The content variable contains HTML and JS but the JS is not executed when injected.

Nhan
  • 3,595
  • 6
  • 30
  • 38
TheShun
  • 1,128
  • 6
  • 15
  • 21

2 Answers2

7

Since you don't know where you script will be added - to the head or body tag - and consequently when it will be executed, you need to make sure that DOM is ready, so:

 function load(content) {

    if (document.readyState === "complete" || document.readyState === "loaded") {
         // manipulate DOM
    } else {
       document.addEventListener('DOMContentLoaded', function() {
        // manipulate DOM
    })
}

Also, since scripts elements added using innerHTML don't get executed, you'd have to either use eval or better create a script like that:

var s = document.createElement('script');
s.text = "alert(\"hi\");"
document.getElementsByTagName('head')[0].appendChild(s);

However, there is no point to add <script> tag to execute some script in your template, since your script is already running when you're adding DOM so do all necessary setups there.

Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • looks good but the script i show is just an example, in real it's much more complicate than alert(). The js i want to load depend on which template widget i load. that's why i make it all in one generated by a php file. You mean i have to load the script and the html separately ? – TheShun Nov 22 '16 at 07:32
  • what I'm saying is that your `widget.js` should be split into `js` and `html` parts (templates). initialize js part of your widget using the code inside `widget.js`, not code inside ` – Max Koretskyi Nov 22 '16 at 08:21
  • To use `eval` just pass `js` as a string `eval('alert("hello")')`. However, `eval()` is not a good option since it won't get executed under strict [CSP](https://developer.mozilla.org/en-US/docs/Web/Security/CSP/Using_Content_Security_Policy). Also, you'd still have to have separate `js` and `html` code since `eval` can't work reliably work with `html` – Max Koretskyi Nov 22 '16 at 08:23
  • so i will have to do 2 request to the server ? on for the html template and one for the js script right ? – TheShun Nov 22 '16 at 10:27
  • no, just put `js` into `widget.js`, and store templates in variables inside `js`. you can start working on it and then ask another question with specifics and link it here – Max Koretskyi Nov 22 '16 at 11:26
  • using your solution i'm trying to find a clean way to store my template html in the javascript file http://stackoverflow.com/questions/40742552/encode-and-pass-html-from-php-to-javascript/40743294 is it ok to have a – TheShun Nov 22 '16 at 14:10
  • Warning! `document.createElement('script')` will be set `async` to `true` by default. It may cause a problem if first js file is large, slow loading but second js file is load faster and depend on first js file. – vee Oct 15 '20 at 09:56
3

UPDATE

please see Maximus' answer.


Right, the default native innerHTML doesn't execute js for safe sake.

jQuery or zepto $.fn.append() can satisfy your need, here is the reason (from zepto source code)

if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' &&
           (!el.type || el.type === 'text/javascript') && !el.src)
          window['eval'].call(window, el.innerHTML)

You can see in the function $.fn.append it will see if it's a script, if so it will use eval to run the content. You may need eval to, but be careful since some webmasters may deliver 'vice' code

jiajianrong
  • 868
  • 8
  • 24