Where are scripts loaded after an ajax call?

2019-01-19 18:26发布

问题:

suppose you have a simple web page that dynamically loads content that looks like this:

- main.html -

<!DOCTYPE html>
<html xmlns="https://www.w3.org/1999/xhtml">
<head>
<script type="text/javascript"
   src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-1.6.2.js">
</script>
<script type="text/javascript">
$(function() {
    $.ajax({
         type: 'get', cache: false, url: '/svc.html',
         success: function(h) {
             $('#main').html(h);
         }
    });
});
</script>
</head>

<body>
    <div id='main'>
    loading...
    </div>
</body>
</html>

and that the page it loads uses a little Javascript in a separate file:

- svc.html -

<script type="text/javascript"
   src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-1.6.2.js">
</script>
<script type="text/javascript" src="/plugin.js" css="/plugin.css"></script>
<div id='inner'>
dynamically loaded content
</div>

notice the css attribute on the script tag - it indicates a style-sheet that belongs to the script and which the script will load for us. Here's the script:

- plugin.js -

var css = $('<link />', {
    rel: "stylesheet", type: "text/css", href: $('script:last').attr('css')
});
$('head').append(css);

and, finally, the style-sheet, which merely colours the inner div that gets loaded, as proof that it all works:

- plugin.css -

#inner
{
    border: solid 1px blue;
}

now, you'll notice the way the style-sheet gets loaded: we look at $('script') and pick off the last one, then we grab the css attribute and attach a link item to the head of the document.

I can visit /svc.html and the javascript runs, loads my style-sheet, all's cool - however, if I visit /main.html, the javascript runs but fails to find the loading of the plugin.js in the array of $('script') (and therefore fails to load the stylesheet). Note that the plugin script does run, it just doesn't see itself.

Is this a bug? a limitation of the jQuery AJAX method? shouldn't $('script') reflect all scripts that have been loaded?

* edit *

after much wailing and gnashing of teeth, I decided to go for Kevin B's solution, however, I can't get it to work, basically because the plugin.js code runs at the time the scriptNode gets inserted, but within the plugin code, the node has not yet been inserted and is thus not available in the $('script') array - so I'm still SOL. I've put all the code for review here:

http://www.arix.com/tmp/loadWithJs/

回答1:

The jQuery code that adds HTML to the DOM always strips out <script> tags. It runs them and then throws them away.

An exception to that behavior is when you use "$.load()" with the hack that allows you to load a fragment of a page:

$.load("http://something.com/whatever #stuff_I_want", function() { ... });

In that case, the scripts are stripped and not evaluated/run.



回答2:

you can use $.ajax to get the html, strip out the script tags yourself, append the content, and then append the script tag to the location in the dom where you want it so that it executes the way you want it to.

$.ajax({...}).done(function(html){
  var htmlToAppend = html;

  // replace all script tags with divs
  htmlToAppend.replace(/<script/ig,"<div class='script'");
  htmlToAppend.replace(/<\/script>/ig,"</div>");

  // detach script divs from html
  htmlToAppend = $(htmlToAppend);
  var scripts = htmlToAppend.find(".script").detach();

  // append html to target div
  htmlToAppend.appendTo("#target");

  // loop through scripts and append them to the dom
  scripts.each(function(){
    var $script = $(this), 
        scriptText = $script.html(), 
        scriptNode = document.createElement('script');

    $(scriptNode).attr('css', $script.attr('css');
    scriptNode.appendChild(document.createTextNode(scriptText));
    $("#target")[0].appendChild(scriptNode);
  });

});

I haven't tested this code, but it is based on code from history.js

Edit: here's a simpler solution more tailored for your needs:

$("#target").load(url,function(obj){
    // get css attribute from script tag using the raw returned html
    var cssPath = obj.responseText.match(/css="(.*)"/i)[1];
    $('<link />', {
        rel: "stylesheet", type: "text/css", href: cssPath
    }).appendTo('head');

});

since .load() strips the script tags and then executes them, this will read the raw response text and get the css path from it, then add the link element.

I'm not entirely sure why it is handled this way.

Edit: see comment.

$.ajax({...}).done(function(html){
  $("#target")[0].innerHTML = html;
});


回答3:

My solution would be rather simple

Why not load the the files needed in main, on the first load all together? Then simply have a listener event. (Check if the content is simply loaded?)

The only solution for loading on demand...

You simply has to AJAX call the script file itself...

<script type="text/javascript">
$(function () {
    $.ajax({
        type: 'get',
        cache: false, 
        url: 'svc.html',
            success: function(response) {
                if (response) { // If any response aka, loaded the svc.html file...
                    $('#main').html(response);
                    $.ajax({
                        url: 'plugin.js',
                        dataType: 'script',
                        success: function(reponse) {
                            // You can now do something with the response here even...
                        }
                    });
                }
            }
    });
});
</script>

My way

Make some type of loader instead on the actual page on load before displaying anything. (Plenty of tutorials with jQuery around the web for this.)



回答4:

On of the problems here is that you CANNOT reload the jQuery in every page. Just the main page should have the jQuery call:

<script type='text/javascript' src='jQuery-url'></script>

Or only one page can call it and ONLY THE CHILDREN (yes, the pages .LOADed from there) will be able to use jQuery.

I've ran into that a few times before I learned my lesson. This might not be the entire answer for your problems but could be the beginning of solving them.



回答5:

ok, after many struggles, I've implemented Kevin B's solution into a plugin useful to others. Improvements welcome! (should I maybe put it in github?)

- loadWithJs.js -

// inspired by Kevin B's suggestions
// at: http://stackoverflow.com/questions/8234215/where-are-scripts-loaded-after-an-ajax-call

(function ($) {
    $.fn.loadWithJs = function (o) {
        var target = this;
        var success = o.success;

        o.success = function (html) {
            // convert script tags
            html = $('<div />').append(
                html.replace(/<(\/)?script/ig, "<$1codex")
            );
            // detach script divs from html
            var scripts = html.find("codex").detach();
            // populate target
            target.html(html.children());

            // loop through scripts and append them to the dom
            scripts.each(function () {
                var script = $(this)[0];
                var scriptNode = document.createElement('script');

                for (var i = 0; i < script.attributes.length; i++) {
                    var attr = script.attributes[i];
                    $(scriptNode).attr(attr.name, attr.value);
                }
                scriptNode.appendChild(
                    document.createTextNode($(script).html())
                );
                target[0].appendChild(scriptNode);
            });

            if (success) success();
        };

        $.ajax(o);
    };
})(jQuery);

then the caller can do:

$(function() {
    $('#main').loadWithJs({
         type: 'get', cache: false, 
         url: '/svc.html',
         success: function() { console.log('loaded'); }
    });
});