6

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/

ekkis
  • 9,804
  • 13
  • 55
  • 105

5 Answers5

5

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.

Pointy
  • 405,095
  • 59
  • 585
  • 614
  • oh... how nasty. I've tested the `$('#main').load()` approach and it seems to behave the same. it *does* run the script but also it throws it away... – ekkis Nov 22 '11 at 22:19
  • It runs the scripts when you **don't** do the special hack to select out just a fragment of the HTML that the AJAX call returns. – Pointy Nov 22 '11 at 22:19
  • oh, I see... so it sounds like Kevin B's beastly code may be my only alternative. grr... – ekkis Nov 22 '11 at 22:23
  • do you have thoughts on my question to Kevin B? – ekkis Nov 22 '11 at 22:30
  • The jQuery rationale is that they don't want to leave the scripts in there because running them *again* later should the content be moved would *probably* be wrong. It's a tricky question, semantically. – Pointy Nov 22 '11 at 22:43
  • well... they made the decision and we have to live with it; but the reason I got into this mess is because I wanted to associate a stylesheet with a script and make the script responsible for loading it. can you think of another way to accomplish this, since I haven't access to the array of scripts? or, another way to put it, how can I access within the script, the tag that loaded the script? – ekkis Nov 22 '11 at 23:25
  • 2
    That's a hard thing to do. I think it probably calls for stepping back and trying to come up with an alternative strategy altogether. – Pointy Nov 23 '11 at 00:14
  • thanks. I think you're right. I'll go with Kevin B's beastly code :) – ekkis Nov 23 '11 at 00:26
  • My 2c: well, if a script is loaded from a child page, anyone (thus jQuery people) would assume that the script belongs EXCLUSIVELY to that page. Once that page is unloaded (navigated away from), nothing better than to discard all unecessary load. I use Pointy's approach where I callback the script after the page is loaded ($.load(..., function () { $.getScrpit(...) });. – zequinha-bsb Nov 23 '11 at 00:40
  • In my experience, Internet Explorer ALONE, does not recognize external css for child pages loaded with ajax. The styling has to be explicit in the loaded page or ... in the calling page. – zequinha-bsb Nov 23 '11 at 00:41
  • @zequinha-bsb: see my latest edit, I've put the code up on a site for perusal. I think that by "child" page you mean the page that the .load() calls - it would be fine if the script belongs exclusively to that page. the problem here is I don't seem to have a way to get a hold of the tag that imports the script, within the script – ekkis Nov 23 '11 at 05:57
1

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;
});
Kevin B
  • 94,570
  • 16
  • 163
  • 180
  • ok, let me redirect the question: if I wanted to pick up the `css` attribute on the script tag that invoked me, how would I do that, other than the way I'm currently doing it? – ekkis Nov 22 '11 at 22:30
  • yes, the solution you propose will work. the problem is that I wanted to handle that inside of the invoked script such that the caller needn't be responsible for such special handling – ekkis Nov 22 '11 at 23:52
  • That's understandable. Maybe it would be better for you to do what i did with $.ajax, only, without actually processing anything. i'll post a sample. I've never used it that way, but as long as your scripts are after the elements they alter, it should work. – Kevin B Nov 23 '11 at 00:14
  • I was excited by your last edit, but it doesn't work. actually, this blog post: http://poeticcode.wordpress.com/2007/10/03/innerhtml-and-script-tags/#comment-8765 talks about this problem, outside the context of jQuery, which basically amounts to your first suggestion. I've closed the issue with my thanks because I think it's the only solution. – ekkis Nov 23 '11 at 00:31
  • I've reopened the issue because I can't get your solution to work. I've put all the code here: http://www.arix.com/tmp/loadWithJs/ in hopes you can take a look. Basically, the code in the `plugin.js` runs when the scriptNode is appended (I'm appending to the head) - however, at that time the node has not yet been appended and therefore is not yet available in the `$('script')` array - so I'm still SOL – ekkis Nov 23 '11 at 05:48
  • See my code, first snippet. i made a small change `$('head')[0].appendChild(scriptNode);` – Kevin B Nov 23 '11 at 07:34
  • .append() is a jQuery method, and jQuery methods seem to do odd things with script tags. using native code seems to solve the problem. – Kevin B Nov 23 '11 at 07:35
  • in a strange turn of events, the .appendChild() solves the problem of the tag not-yet existing, by deferring execution of the script, but causes problems in that functions therein defined are not immediately available (see http://stackoverflow.com/questions/8283360/where-are-scripts-loaded-after-an-ajax-call-redux). JQuery's .append(), OTOH, executes the scripts immediately and therefore solves my second problem. So it seems I'm screwed! I can't have my cake and eat it too :( – ekkis Nov 27 '11 at 21:20
1

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.)

  • I think why you can't execute Javascript over and over, without some type of "effort", is because it would be way to easy to brake it, and make it simply redo that task a million times. (And your server wouldn't like you for that. But only a theory.) – Daniel H. Hemmingsen Nov 24 '11 at 01:53
  • hei Daniel, thanks for joining in! pre-loading files is unsuitable because depending on what the user wants to do we'll load different files. so to load everything would be costly and not very useful – ekkis Nov 24 '11 at 01:57
  • You are welcome. Have been hacking on this for a half hour now, if not even a hour. Should sleep. ^^ Hehe. (The curse of being a coder from time to time.) – Daniel H. Hemmingsen Nov 24 '11 at 01:59
  • Ups, wanted to post something more. Loading the JS file, doesn't really matter. What most matters these days, is if you actually use it. (Take Coffee And Power for example. I think we have around 50-100 JS files.) You just only "activate/engage" the actual code, when needed. (The only thing we really use AJAX for, is sending data to PHP really. In rough.) – Daniel H. Hemmingsen Nov 24 '11 at 02:01
0

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.

zequinha-bsb
  • 719
  • 4
  • 10
  • I had it loaded in the *child* page only because one can call the it directly and see that without a jQuery .load() in the middle everything works as it should. but it isn't necessary and I've removed it from the version that I have at arix.com - without solving the problem – ekkis Nov 23 '11 at 06:15
0

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'); }
    });
});
ekkis
  • 9,804
  • 13
  • 55
  • 105
  • grr... turns out this solution has issues too: http://stackoverflow.com/questions/8283360/where-are-scripts-loaded-after-an-ajax-call-redux – ekkis Nov 27 '11 at 20:29