Externe Scripts nachladen (auch mit document.write)

von Frank Thürigen | 3 | 1 Kommentar | 18300 Aufrufe

Anzeige Hier werben

Für viele Dienste (Werbung, Statistik) muss man Scripte eines Anbieters auf seine Seite einbinden. Wenn der Server, von dem dieser Code nachgeladen wird, laggt, dann friert die gesamte Webseite ein und wartet bis das externe Script geladen ist.

Weil diese Scripte oft document.write()-Befehle enthalten kann man den Code aber nicht einfach per Ajax später nachladen und per eval() ausführen: document.write() funktioniert nur während der Browser die Webseite aufbaut. Wenn diese Seite schon aufgebaut ist, wird dadurch die gesamte Seite zerschossen.

Hier die Lösung für dieses Problem:

domWrite() Funktion  
JavaScript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
var domWrite = (function(){            // by Frank Thuerigen
 // private 

 var dw = document.write,              // save document.write()
          myCalls = [],                // contains all outstanding Scripts
          t = '';                      // timeout
 
 function startnext(){                 // start next call in pipeline
  if ( myCalls.length > 0 ) {
   if ( Object.watch ) console.log( 'next is '+myCalls[0].f.toString() );
   myCalls[0].startCall();
   }
  }

 function evals( pCall ){            // eval embedded script tags in HTML code
  var scripts = [],
      script,
      regexp = /<script[^>]*>([\s\S]*?)<\/script>/gi;
  while ((script = regexp.exec(pCall.buf))) scripts.push(script[1]);
  scripts = scripts.join('\n');
  if (scripts) {
   eval(scripts);
   }
  }

 function finishCall( pCall ){
   pCall.e.innerHTML = pCall.buf;             // write output to element
   evals( pCall );
   document.write=dw;                        // restore document.write()
   myCalls.shift();
   window.setTimeout( startnext, 50 );
   }

 function testDone( pCall ){
   var myCall = pCall;
   return function(){
    if ( myCall.buf !== myCall.oldbuf ){
     myCall.oldbuf = myCall.buf;
     t=window.setTimeout( testDone( myCall ), myCall.ms );
     }
    else {
     finishCall( myCall );
     }
    }
   }  
   
 function MyCall( pDiv, pSrc, pFunc ){                    // Class
  this.e = ( typeof pDiv == 'string' ? 
             document.getElementById( pDiv ) :
             pDiv ),                     // the div element
  this.f = pFunc || function(){},
  this.stat = 0,                         // 0=idle, 1=waiting, 2=running, 3=finished
  this.src = pSrc,                       // script source address
  this.buf = '',                         // output string buffer
  this.oldbuf = '',                      // compare buffer
  this.ms = 100,                         // milliseconds
  this.scripttag;                        // the script tag 
  }
 
 MyCall.prototype={
  startCall: function(){
   this.f.apply( window );                 // execute settings function
   this.stat=1;
   var that = this;                            // status = waiting
   document.write = (function(){
    var o=that,
        cb=testDone( o ),
        t;
    return function( pString ){            // overload document.write()
     window.clearTimeout( t );
     o.stat=2;                             // status = running
     window.clearTimeout(t);
     o.oldbuf = o.buf;
     o.buf += pString;                     // add string to buffer
     t=window.setTimeout( cb, o.ms );
     };
    })();
   var s=document.createElement('script');
   s.setAttribute('language','javascript');
   s.setAttribute('type','text/javascript');
   s.setAttribute('src', this.src);
   document.getElementsByTagName('head')[0].appendChild(s);
   }
  }
  
 return function( pDiv, pSrc, pFunc ){  // public
  var c = new MyCall( pDiv, pSrc, pFunc );
  myCalls.push( c );
  if ( myCalls.length === 1 ){
   startnext();
   }
  }
 })();

Als Datei zum einbinden in die eigene Seite kann man das hier herunterladen.

Erklärung

Die Funktionalität ist prinzipiell recht einfach, aber im Detail sehr vertrackt...

  1. Wegen der "same origin policy" (AJAX requests dürfen nur vom selben Server aufgerufen werden) muß ein Skript von einem fremden Server erst als script node erzeugt werden und dann in den HEAD der Seite eingesetzt werden.
  2. Bevor dies geschieht, muß die "document.write()" Funktion in eine temporäre Variable gesichert werden, danach wird "document.write()" mit einer eigenen Pufferfunktion überschrieben.
  3. Die Pufferfunktion erhält den String, der normalerweise direkt beim rendern geschrieben würde. Dieser wird in der Puffervariable "buf" angehängt. Außerdem wird bei jedem Aufruf ein "timeout()" neu gestartet, der prüft ob sich in den letzten 100ms "buf" verändert hat. Falls nicht wird "buf" in dem DIV gespeichert, das man bei Aufruf der Funktion mitgegeben hat.
  4. Ganz am Schluß wird die originale document.write() Funktion aus der Sicherung wiederhergestellt.
  5. Zum Abarbeiten mehrerer simultaner Aufrufe der Funktion wurde ein Script Queue hinzugefügt. Das ganze gleicht einer asynchronen Batch-Verarbeitung.

Beispielanwendung mit Google Adsense

Für google adsense sieht der HTML code etwa so aus:

 
HTML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<div id="googlesidebar1"></div>
<script type="text/javascript">
 domWrite(
  'googlesidebar1',
  'http://pagead2.googlesyndication.com/pagead/show_ads.js',
  function(){
   google_ad_client = "pub-YourOwnGoogleCode";
   google_ad_slot = "YourOwnGoogleCode";
   google_ad_width = 160;
   google_ad_height = 600;
   }
  );
</script>

ACHTUNG: ich kann nicht garantieren, daß google das erlaubt. Es sollte problemlos sein da der google adsense code in keiner Weise verändert wird. Trotzdem macht das jeder auf seine eigene Verantwortung!

Hier noch mal eine Seite wo man das ganze in Aktion sehen kann... die Werbung in der rechten Sidebar unter den Menüs ( 2 Blöcke ) und im Footer der Seite ( 1 Block ) wurde auf diese Weise asynchron und simultan nachgeladen:

http://blog.phpbuero.de/?p=26

Weiterführende Informationen insbesondere in Bezug auf XHTML und document.write() gibt es auch noch (englisch) bei John Resigs Blog

Ich hoffe das hilft dem einen oder anderen...

Grüße, Frankie

Über den Autor: Frank Thürigen
System Architekt für komplexe Javascript und FullAJAX Anwendungen in Berlin, Entwickler der twoBirds Bibliothek
Profilseite betrachten

Kommentare: Externe Scripts nachladen (auch mit document.write)

Neuen Kommentar schreiben
IE7
Beantworten

Leider wird die Werbung im IE7 nicht nachgeladen. IE8 konnte ich bis jetzt noch nicht überprüfen.

Benutzer gelöscht am 21.09.2009 um 11:21