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/
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.
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;
});
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.)
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.
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'); }
});
});