Problem beim dynamischen Nachladen von JS in Webkit-Browsern
sonic
- javascript
Hi!
Ich versuche im Moment, in einer JS-Datei weitere JS-Dateien nachzuladen, falls die nicht bereits vorhanden sind, im Speziellen probiere ich, jQuery nachzuladen und zwar mit folgendem Code:
if (typeof $ == 'undefined')
{
try
{
var script_tag = document.createElement('script');
script_tag.setAttribute('type', 'text/javascript');
script_tag.setAttribute('src', "jquery-1.4.2.min.js");
// Callback for FF, Chrome, Safari, Opera
if (typeof(script_tag.addEventListener) != 'undefined')
{
script_tag.addEventListener('load', startup, false);
}
else
{
// Callback IE
var loadFunction = function()
{
if (this.readyState == 'complete' || this.readyState == 'loaded')
{
startup();
}
};
script_tag.onreadystatechange = loadFunction;
}
var head = document.getElementsByTagName("head")[0];
head.appendChild(script_tag);
}
catch(e)
{
document.write('<script type="text/javascript" src="jquery-1.4.2.min.js"></script>');
startup();
}
}
else
{
startup();
}
Das Ganze funktioniert in Opera, FF und IE6 auch erstaunlich gut (in der startup-funktion wird dann ein jquery-Plugin geladen), bloß bei Chrome und Safari habe ich das Problem, dass ich auf meiner eigentlichen HTML-Seite immer folgenden Fehler bekomme:
Uncaught ReferenceError: $ is not defined
Das HTML sieht grob gesagt so aus:
<html>
<head>
<script type="text/javascript" src="loader.js"></script>
<title>Test</title>
</head>
<body>
<h1 id="test">TEST HEADING</h1>
<script type="text/javascript">
$('h1').css('color', 'red');
</script>
</body>
</html>
Meine Theorie ist, dass Chrome/Safari das inline-JS auf der Seite schon evaluieren, bevor die neue Datei fertig geladen ist, aber ich wüsste nicht, wie ich den Browser davon abhalten kann.
Falls jemand hierzu eine Idee hat, gebt bitte einfach bescheid, freue mich über jeden kreativen Vorschlag! (den grundsätzlichen Aufbau kann ich allerdings nicht ändern, d.h. das inline-JS in einen Callback zu packen o.ä. wird nicht funktionieren)
Hallo sonic,
ich habe mal etwas mit dem Nachladen von Javascripten experimentiert:
http://www.j-berkemeier.de/test/Nachladen.html
Im Quelltext steht, welche Version synchron oder asynchron läuft, und welcher Browser nicht mitspielt.
Nach meinem jetzigen Kenntnisstand empfehle ich dir für synchrones Nachladen die Version 1b mit ".text".
Gruß, Jürgen
den grundsätzlichen Aufbau kann ich allerdings nicht ändern, d.h. das inline-JS in einen Callback zu packen o.ä. wird nicht funktionieren
Dieser Aufbau ist aber für das Problem verantwortlich.
Wenn du in einem script-Element im <body> jQuery verwenden willst, dann musst du es SYNCHRON davor laden. Mit createElement("script")/appendChild lädst du es jedoch ASYNCHRON. Damit ist natürlich nicht garantiert, dass das externe Script geladen ist, wenn die Scripte im <body> ausgeführt werden.
Wenn du synchron laden willst, verwende ausschließlich document.write. Dann wird das Parsing des Dokuments und damit das Ausführen der Scripte angehalten, bis jQuery geladen und ausgeführt ist.
Du kannst aber m.W. nicht direkt nach document.write() deine Startup-Funktion ausführen. document.write("<script src='...'></script>">) ist nur für darauffolgende Scripte synchron, der Code direkt nach dem document.write-Aufruf hat noch keinen Zugriff auf die Objekte, die im externen Script definiert werden. Bei document.write() ist ein Callback also nicht möglich.
Folgendes ginge m.W. (ungetestet)
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<!-- Hier kann jQuery eingebunden sein oder auch nicht -->
<script>
[code lang=javascript]if (!window.jQuery) {
// Lädt das externe Script und blockt den HTML-Parser
document.write("<script src='jquery.js'><\/script>");
}
</script>
<script>
startup(); // Hier ist garantiert, dass jQuery geladen ist
</script>
</head>
<body>
<h1 id="test">TEST HEADING</h1>
<script type="text/javascript">
$('h1').css('color', 'red'); // Hier ist ebenfalls garantiert, dass jQuery geladen ist
</script>
</body>
</html>[/code]
Allgemein ist das natürlich ein verbesserungsfähiger Aufbau. Üblicherweise läd man JavaScript-Code aus Performancegründen am Ende des Dokuments, dann völlig asynchron mit Callbacks. JavaScript-Code mitten ins Dokument einzubetten, erschwert die ganze Sache und macht asynchrones Laden mit Callback unmöglich. Das nennt sich Unobtrusive JavaScript.
Mathias
den grundsätzlichen Aufbau kann ich allerdings nicht ändern, d.h. das inline-JS in einen Callback zu packen o.ä. wird nicht funktionieren
Dieser Aufbau ist aber für das Problem verantwortlich.
Ist klar, aber wie gesagt, den kann ich leider nicht ändern (ist auch nicht auf meinem Mist gewachsen :-).
Wenn du in einem script-Element im <body> jQuery verwenden willst, dann musst du es SYNCHRON davor laden. Mit createElement("script")/appendChild lädst du es jedoch ASYNCHRON. Damit ist natürlich nicht garantiert, dass das externe Script geladen ist, wenn die Scripte im <body> ausgeführt werden.
Wenn du synchron laden willst, verwende ausschließlich document.write. Dann wird das Parsing des Dokuments und damit das Ausführen der Scripte angehalten, bis jQuery geladen und ausgeführt ist.
Du kannst aber m.W. nicht direkt nach document.write() deine Startup-Funktion ausführen. document.write("<script src='...'></script>">) ist nur für darauffolgende Scripte synchron, der Code direkt nach dem document.write-Aufruf hat noch keinen Zugriff auf die Objekte, die im externen Script definiert werden. Bei document.write() ist ein Callback also nicht möglich.
Das ist ein guter Hinweis. Ich habe jetzt mal folgendes versucht:
if (typeof $ == 'undefined')
{
document.write('<script type="text\/javascript" src="jquery-1.4.2.min.js"><\/script>');
document.write('<script type="text\/javascript">startup();<\/script>');
}
else
{
startup();
}
Das funktioniert in FF, Opera, Chrome und Safari. Nur leider mackt der IE6 jetzt rum und behauptet, $ wäre undefined...
Allgemein ist das natürlich ein verbesserungsfähiger Aufbau. Üblicherweise läd man JavaScript-Code aus Performancegründen am Ende des Dokuments, dann völlig asynchron mit Callbacks. JavaScript-Code mitten ins Dokument einzubetten, erschwert die ganze Sache und macht asynchrones Laden mit Callback unmöglich. Das nennt sich Unobtrusive JavaScript.
Ja klar, aber ich hab das wie gesagt nicht in der Hand. Die CMS-Infrastruktur, in die das integriert werden muss, ist so alt und verkorkst, dass es keine Chance gibt, in der Footer-Template festzustellen, welche JS-Dependencies die aktuellen Content-Templates brauchen und mit welchen Parametern die JS-Objekte initialisiert werden müssten
Mathias
if (typeof $ == 'undefined')
{
document.write('<script type="text/javascript" src="jquery-1.4.2.min.js"></script>');
document.write('<script type="text/javascript">startup();</script>');
}
else
{
startup();
}
>
> Das funktioniert in FF, Opera, Chrome und Safari. Nur leider mackt der IE6 jetzt rum und behauptet, $ wäre undefined...
Der Internet Explorer kennt das [defer-Attribut](http://www.w3.org/TR/html5/semantics.html#attr-script-defer). Wenn du den zweiten Script [defer](http://msdn.microsoft.com/en-us/library/ms533719(v=VS.85).aspx) gibst, dann sollte es nach dem Parsen des gesamten Dokuments ausgeführt werden (und damit nach dem Ausführen aller synchron eingebetteter Scripte). Ob das auch in diesem Fall funktioniert und wie sich dann jQuerys $(document).ready verhält - das Dokument ist dann schon fertig geparst -, weiß ich allerdings nicht. Ich vermute aber, jQuery fängt das mit if ( document.readyState === "complete" ) ab.
Grüße, Mathias
h1,
Falls jemand hierzu eine Idee hat, gebt bitte einfach bescheid, freue mich über jeden kreativen Vorschlag! (den grundsätzlichen Aufbau kann ich allerdings nicht ändern, d.h. das inline-JS in einen Callback zu packen o.ä. wird nicht funktionieren)
Gerade das ist entscheidend dafür, dass es mit allen Browsern funktioniert: Alles was mit dem nachgeladenen JS funktionieren soll, muss innerhalb der callback-Funktion notiert sein.
Hotti