|
|||||||
| JavaScript JavaScript (not Java) Discussion and technical support, including AJAX and frameworks (JQuery, MooTools, Prototype...) |
![]() |
|
|
Thread Tools | Rate Thread | Display Modes |
|
#1
|
|||
|
|||
|
solution: lazy loading JS ad code containing document.write()
Hi everybody!
After I have received lots of helpful information here, Iīd like to give back today... Some of you may have experienced problems using JS ad code (e.g. google adsense etc). This can include complete page freezing due to ad server lags... the solution would be to lazy load the code. Unfortunately ad code often includes document.write() statements, which makes it impossible to run it if the page has already rendered. Also most adserver policies forbid changes in the delivered code, so you have to stick with document.write() ... Ok whats the solution here: simply overload document.write for a short period of time, and replace it with a function that buffers the output, and later writes the buffer to the innerHTML of a div. Of course you have to restore the old document.write function afterwards... Here is the code: Code:
var domWrite = (function(){ // by Frank Thuerigen
// private
// (probably helper functions for code sanitizing etc.)
return function( pDiv, pSrc ){ // public
var e = ( typeof pDiv == 'string' ?
document.getElementById( pDiv ) :
pDiv ),
s = document.createElement('script'),
dw = document.write, // save document.write()
buf = '', // output string buffer
oldbuf = '', // output string buffer
t = '', // timeout
ms = 100; // milliseconds
function evals( pString ){ // eval embedded script tags in HTML code
var scripts = [],
script,
regexp = /<script[^>]*>([\s\S]*?)<\/script>/gi;
while ((script = regexp.exec(pString))) scripts.push(script[1]);
scripts = scripts.join('\n');
if (scripts) eval(scripts);
}
function exec(){ // output & restore document.write()
if ( buf !== oldbuf ){
oldbuf = buf;
t=window.setTimeout( exec, ms ); // repeat test timeout
}
else {
e.innerHTML = buf; // write output to element
evals( buf ); // tbd: correct eval sequence if multiple scripts
document.write=dw; // restore document.write()
}
}
document.write = function( pString ){ // overload document.write()
window.clearTimeout(t);
oldbuf = buf;
buf += pString; // add string to buffer
t=window.setTimeout( exec, ms );
}
s.setAttribute('language','javascript');
s.setAttribute('type','text/javascript');
s.setAttribute('src', pSrc);
document.getElementsByTagName('head')[0].appendChild(s);
}
})();
Code:
<script type="text/javascript"><!-- google_ad_client = "pub-568508999XXXXX"; /* header block */ google_ad_slot = "05460XXXXX"; google_ad_width = 468; google_ad_height = 15; //--> </script> <script type="text/javascript"> domWrite( 'myDivId', 'http://pagead2.googlesyndication.com/pagead/show_ads.js' ); </script> ...and finally here you can see it at work: http://blog.phpbuero.de/?p=26 The ads below the right sidebar are loaded using this technique. CAVEAT: I donīt know wether google approves this, but since it doesnīt change their code or functionality I donīt think it is a problem. Anyway, use it on your own risk! I hope this helps ... Have fun, Frankie |
|
#2
|
|||
|
|||
|
This improved version can handle several simultaneous calls without interfering:
Code:
var domWrite = (function(){ // by Frank Thuerigen
// private
var c=0; // counter for running domWrite() calls
function evals( pString ){ // eval embedded script tags in HTML code
var scripts = [],
script,
regexp = /<script[^>]*>([\s\S]*?)<\/script>/gi;
while ((script = regexp.exec(pString))) scripts.push(script[1]);
scripts = scripts.join('\n');
if (scripts) eval(scripts);
}
return function( pDiv, pSrc ){ // public
var e = ( typeof pDiv == 'string' ?
document.getElementById( pDiv ) :
pDiv ), // the div element
s = document.createElement('script'),
dw = document.write, // save document.write()
buf = '', // output string buffer
oldbuf = '', // output string buffer
t = '', // timeout
ms = 100; // milliseconds
function exec(){ // output & restore document.write()
if ( buf !== oldbuf ){
oldbuf = buf;
t=window.setTimeout( exec, ms ); // repeat test timeout
}
else {
e.innerHTML = buf; // write output to element
evals( buf ); // tbd: correct eval sequence if multiple scripts
if ( (c--) === 0 ) { // no more domWrite()s running
document.write=dw; // restore document.write()
}
}
}
document.write = function( pString ){ // overload document.write()
window.clearTimeout(t);
oldbuf = buf;
buf += pString; // add string to buffer
t=window.setTimeout( exec, ms );
}
c++; // inc domWrite() counter
s.setAttribute('language','javascript');
s.setAttribute('type','text/javascript');
s.setAttribute('src', pSrc);
document.getElementsByTagName('head')[0].appendChild(s);
}
})();
|
|
#3
|
|||
|
|||
|
The next version ( and the last for now ). Improvement is that it now really works for simultaneous downloads... which the previous one did not, at least not crossbrowser.
This Version has been tested on IE 5.5-6-7 FF 1.x-2.x-3.x SAFARI 2.x-3.x and several OPERA Versions. Code:
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();
}
}
})();
Implementation in HTML has changed a bit (but became easier I guess): Code:
<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>
Have fun, Frankie Last edited by FrankT; 11-10-2008 at 06:07 PM. |
|
#4
|
|||
|
|||
|
Still a work in progress ;-)
Iīm testing with other ad code on my site, so if you have a problem with the downloaded JS file please give me some time Iīm problably working on it... just check back after some time or contact me... Frankie |
|
#5
|
|||
|
|||
|
This will work for certain ads but a few improvements are needed. Believe it or not, the following is valid HTML. The alert dialog will show 1 and div will be a reference to the written out DIV element.
HTML Code:
document.write("<scr" + "ipt>var foo=1;<\/script>");
alert(foo);
document.write("<div id=foo><\/div>");
var div= document.getElementById("foo");
A working example of pre-load emulation of document.write in a post-load context can be seen at http://www.app.com/ I am still struggling with a couple of things: 1. The write()'ing script using various document properties to access written element and 2. Handling the execution of event handlers for the window onload event (or DOMContentReady) as this may have all ready fired. HTML Code:
document.write("<img src=\"foo\"/>");
var _i= document.images[document.images.length - 1]
|
|
#6
|
||||
|
||||
|
there's a simple way
just add the defer attrib, set to true to any script tag.
with defer, the page can continue to load while the script downloads and writes, and everything magically falls into it's right place. let me be clear, a defer'ed tag can doc.write after the page loads, without wiping out the whole page.... works in 99% of browsers. the folks with old 3rd-party browsers will just have to wait for the tag to load...
__________________
libs: mini (updated) ASPmini myIO (new) dnd tmpl8 apps: snippets blog photos crypto image editor crapplets: json browse json view compressor time grads |
|
#7
|
|||
|
|||
|
|
|
#8
|
||||
|
||||
|
__________________
libs: mini (updated) ASPmini myIO (new) dnd tmpl8 apps: snippets blog photos crypto image editor crapplets: json browse json view compressor time grads |
|
#9
|
|||
|
|||
|
Quote:
In Firefox, the output of document.write() for defer'd scripts is appended to the body. In IE the effect is the same as document.write()'ing to a closed document. HTML Code:
<html> <head><title> Test</title> </head> <body> <h3> Hello, world</h3> <script type="text/javascript" defer="true"> document.write("<h2> Hello, script <\/h2> "); </script> <h3> Hello again, world</h3> </body> </html> HTML Code:
<h3>Hello, world</h3> <script ... > </script> <h3> Hello again, world</h3> <h2>Hello, script </h2> <!-- Wrong order --> IE simply outputs HTML Code:
<h2>Hello, script </h2> IMHO, this is a shame and the functionality provided by write() serves a legitimate need: By allowing the script author to define her HTML structure without having to know the parent container (albeit using strings instead of DOM methods) and the page author to dictate where the output should be placed, it decouples the page and the script. Requiring the JS developer to either know ahead of time where to place any required HTML or the page author to "take action" after a script has run (to embed the content where it really needs to go) makes including "widgets" very difficult. For now, document.write() is the only way to achieve this decoupling. Using the defer attribute and placing the output adjacent to the script seems to make sense to me and should be supported, but the devil is in the details: What happens when you document.write("<!-- ")? |
|
#10
|
||||
|
||||
|
Quote:
regarding dynamic content placement, you can use document.appendChild or innerHTML to insert new content anywhere in the document. If you are replacing a document.write type of functionality, i don't see the issue with appending just before the script tag. before, in, or after, as long as it touches the script tag, DOM-added content should appear the same as written content. I don't see how document.write provides anything that can't be done with DOM methods, other than possibly pausing execution by writing new script tags. If defer doesn't work, then i would highly recommend avoiding document.write, as the user must wait on the remote transfer. With dom methods, you can defer the script and add the content after page load, when the remote file connects and downloads.
__________________
libs: mini (updated) ASPmini myIO (new) dnd tmpl8 apps: snippets blog photos crypto image editor crapplets: json browse json view compressor time grads Last edited by rnd me; 10-13-2009 at 10:09 PM. |
|
#11
|
||||
|
||||
|
Quote:
you can use dom methods to find script tags, thus giving you a DOM slot where the original tag was located, just like a document.write script would "know". this pattern works for inline, non-deferred scripts, but not post-onload, dynamically added ones. but hey; that's the same limit as document.write right? Magiacally, since scripts load one-at-a-time, top-to-bottom, the last script tag is always the "active" one. Code:
(function(){
var t= document.getElementsByTagName("script");
t= t[ t.length -1];
var tx = t.text || t.textContent || t.innerText;
if(tx && t.src){ try{ eval(tx) }catch(y){}; }
}());
Code:
if(tx && t.src){ try{ eval(tx) }catch(y){}; }
If you want to do this kind of thing after the page loads, you will have to iterate all script tags, and match the filename to the external script. This mean that a script must know it's filename. If you are hosting widgets for anyone to include, hard-coding in a filename is not a problem. just a few lines at the end of your script, and you should be able to hit those script tags anytime, anywhere.
__________________
libs: mini (updated) ASPmini myIO (new) dnd tmpl8 apps: snippets blog photos crypto image editor crapplets: json browse json view compressor time grads |
![]() |
| Bookmarks |
| Currently Active Users Viewing This Thread: 1 (0 members and 1 guests) | |
| Thread Tools | |
| Display Modes | Rate This Thread |
|
|