Search code examples
javascripthtmldomembed

How to make the browser run JavaScript code injected by DOM manipulation?


I want to serve a little HTML snippet that other pages can include.

The HTML content would look something like this:

<div id="coolIncludable" style="display:none"> <!--be invisible until you are finished initializing -->
   <div>Cool stuff here</div>
   <div>More cool stuff and so on...</div>
</div>
<script src="http://somehwere/jquery.js"></script>
<script src="http://somewhere/something.js"></script>
<script>
    $(function(){$('#coolIncludable').show();});//<- actually contained in a script file, just for illustration
</script>

I'm planning to use the method detailed here: https://www.w3schools.com/howto/howto_html_include.asp to do the actual including. Let's say the page looks something like this:

<html>
    <head>
    </head>
    <body>
        <H1>Content before snippet</H1>
        <div id="registerWrapper" html-include="http://somehwere/snippet.html">
            No content loaded
        </div>
        <H1>Content after snippet</H1>
        <script type="text/javascript" src="http://somehwere/html-include.js"></script>
    </body>
</html>

The HTML snippet gets loaded and embedded all right, but the JavaScript that comes with it never gets executed. Is there a way to embed content including scripts that makes sure they are executed?

I don't expect to control the embedding page, so I cannot rely on it having jQuery or anything else loaded. I therefore avoided using jQuery in the embedding function and restricted myself to plain JavaScript. Loading jQuery is one of the things the <script> tags at the end of the snippets would do.


Solution

  • In retrospect, my mistake is glaringly obvious:

    What does the line $(function(){$('#coolIncludable').show();}); do? does it execute $('#coolIncludable').show();? No it doesn't. It registers a callback to do so that gets triggered by the 'load' event, which already has fired, and won't fire again.

    On the other hand, that's really a moot point because the code never even gets executed.

    Here's what I learned about dynamic loading of javascript

    Injecting script tags directly does not work

    • script tags injected by setting element.innerHtml do not get executed
    <div id="snippet">
        ...
    </div>
    <!-- neither of these will be executed -->
    <script type="text/javascript">alert("stuff");</script> 
    <script type="text/javascript" src="http://somewhere/script.js"></script>
    
    

    Creating script tags dynamically does work

    What does work is dynamic tag generation the way it is described in this article: JavaScript Madness: Dynamic Script Loading

    var head= document.getElementsByTagName('head')[0];
    var script= document.createElement('script');
    script.type= 'text/javascript';
    script.src= 'helper.js';
    head.appendChild(script);
    //At this point (or soon after) the code in helper.js will be executed
    

    You would do that in your loading script. My loading script looks something like this:

    function importJsFiles(){
        const scriptFiles = ["http://somewhere/jquery.js",
                             "http://somewhere/stuff.js",
                             "http://somewhere/bootstrapSnippet.js"];
        for (let i = 0; i< scriptFiles.length; i++){
            importJsFile(scriptFiles[i]);
        }
    }
    
    function includeSnippet(){
        //See https://www.w3schools.com/howto/howto_html_include.asp
        //We still have to do it without jQuery, 
        //because at the time this executes the injected jquery.js 
        //hasn't run and we do not yet have jQuery available
    }
    
    
    
    importJsFiles();
    //At this point, all script tags are created, but the scripts are NOT loaded yet
    includeSnippet();
    //At this point, the dom is in its final shape
    bootstrapSnippet(); //<- This won't work, because the script tags injected by this code won't be evaluated until after this file/function/whatever returns
    
    
    //bootstrapSnippet.js
    function bootstrapSnippet(){
        alert("Snippet JS running")
        if (window.jQuery) {
                
                alert("JQuery is avaliable"); //<- This will fire, because the new script 
                                // tags are evaluated in order, and we put the
                                // jquery.js one before the bootstrapSnippet.js one
            }
    
            //So now we CAN do this:
            $('#coolIncludable').show(); 
    }
    
    bootstrapSnippet();
    

    There are many more interesting ideas and details in this post that I picked the link above from. I hope someday I'll find time to explore them all.