Even though it's incredibly bad practice, most advertising servers still use document.write. It's terrible because placing <script type="text/javascript" src="http://www.ad-server.com/foo.js" /> where you want the ads to appear is page blocking.

I'm trying to move the script tag to the very bottom of the page (so at least everything above it loads), but then I need a way to have everything it outputs written to a specific place (preferably with jQuery selectors).

Basically what I currently have to work with is a dynamic third-party ad tag that was the result of an ad server call, saved as a string. Sometimes the string is HTML only. Sometimes it's a bunch of script tags. Sometimes it's a mix of embedded Javascript, HTML, and script tags. It could essentially be anything. Often times the script tags call document.write or call other tags which call document.write (don't we love daisy-chaining ad networks!)

If I were to just do $('#foo').html(string) or $('#foo').append(string) then that would work fine if the string only consists of HTML. But jQuery does something funky in the html() or append() functions when script tags are involved, and nothing is outputted.

I tried overwriting the document.write function which is eerily easy to do in Javascript, it seems. Unfortunately, that didn't work either for me (unless I did it wrong). After doing some research, it seems that a lot of people are having this same issue, and a potential solution was written at https://github.com/iamnoah/writeCapture and https://github.com/iamnoah/writeCapture/tree/writeCapture2 to capture document.write and solve the problem.

Unfortuantely, I found it mangling the output more often than not. so I'm looking for other alternatives.

MUCH TIA!!

Recommended Answers

All 22 Replies

Hi Dani,

Yes I've always disliked document.write() in ad scripts and elsewhere.

I think you must be onto the right approach with $('#foo').html(string) and
$('#foo').append(string). I wasn't aware that script tags wouldn't work when written in this way but I expect it's the browser engine that won't play ball rather than jQuery doing something funky. In particular, I expect that a script tag can't be embedded any deeper than as a direct child of <HEAD> or <BODY>.

If so, then (providing #foo is iteslf not deep-embedded within the <BODY>) you can try using #foo as a marker rather than a container. Insert the HTML-string with $("#foo").before(string);.

If that still doesn't work then you could try something slightly more complicated :

  • parse your string into HTML | SCRIPT | HTML | SCRIPT segments.
  • loop through the segments.
  • if segment[i] is HTML, then $("#foo").before(segment);
  • if segment[i] is SCRIPT, parse out its src, then $("<script>").insertBefore("#foo").attr('src', src);

You may still run into the problem that any dynamically created <script> with a document.write(...) expression of its own could blitz the entire document (the DOM and all javascript members) and leave you with just the freshly written stuff. It depends on whether browsers fetch these dynamically inserted scripts before or after window.load fires (or possibly before document.readyState reaches 'complete').

If any one browser dosn't want to play, then I'm afrid you may just have to live with the blocking.

Try targeting an iFrame placeholder and populate it only after the page has loaded.

Airshow, unfortunately it is jQuery doing something funky. There's a thread on StackOverflow that goes into the detail of exactly what that is, but unfortunately the workaround they provide only works for a single pre-known script tag. I need to accomidate any random mix of script tags and inline script.

http://stackoverflow.com/questions/610995/jquery-cant-append-script-element

Troy, I actually already am attempting to use an IFRAME, but unfortunately it's not working. Here's my code so far ...

$('<iframe />').appendTo(ad_placement).attr({'width': 728, 'height': 90, 'frameborder': '0', 'marginheight': '0', 'marginwidth': '0', 'scrolling': 'no'});       
ad_placement.find('iframe').ready(function() {
    $(this).contents().find('body').css({'margin': '0px', 'padding': '0px', 'overflow': 'hidden'}).html(string_from_ad_server);
});

The reason I wanted to use an IFRAME in the first place is because, more often than not, third-party ad tags not only use lots of document.write, but also are an awful mess of badly-designed HTML and improperly nested HTML tags that can screw up the layout of the rest of the page. I want to isolate them.

I still believe the solution lies in overloading the document.write function.

Member Avatar for LastMitch

@Dani

Have you try <object> tags? I hardly used <iframe> now, I only used <object> to substitute <iframe>

$('<object />').appendTo(ad_placement).attr({'width': 728, 'height': 90, 'border': '0', 'marginheight': '0', 'marginwidth': '0', 'scrolling': 'no'});

ad_placement.find('object').ready(function() {
$(this).contents().find('body').css({'margin': '0px', 'padding': '0px', 'overflow': 'hidden'}).html(string_from_ad_server);
});

It's getting late now, mostly I will find out what is the correct answer to your question.

Dani, you're right, jQuery does something very funky with script tags! I still think my HTML | SCRIPT | HTML | SCRIPT segments idea may work but avoid jQuery by using the POJS workaround for each script element. Be sure to observe the rule that script tags should not be embedded in elements other than HEAD or BODY. This point is made in the SO entry.

Or, another idea :

  • At each insertion point in the document, hard-code <script id="script_N"></script> (where N = 1, 2, 3, etc.) for each script element of the ad, and a <div id="ad_N" /> placeholder for each HTML clause. Thus you have made an "inert ad template", specific to each particular ad.
  • At bottom of the document, have an inline script that sets the hard-coded scripts' src's and writes the specific HTML strings into their placeholders.
  • Same proviso as before re window.onload/document.readyState.

I agree, this would be a pain because you have to prepare a template and a script for each ad but if it works ....

Airshow, the call to the ad server is made client-side, so it's not possible to hard code anything since the string is only available as a javascript variable to begin with.

I understand how to use the StackOverflow method to load a script, but how can it work with inline javascript??

Is there a way to force jQuery to not use the domManip function? :) Another thing I was looking at was figuring out a way to escape the string before injection, so jQuery won't know what to do with it, and then unescaping it once it was injected. That didn't work but maybe I did it wrong??

Dani, OK that indeed makes a big difference.

I'm working on a slightly different tack now and need to test with an actual ad script. Tried finding one from source of a Daniweb page but have managed to get confused with multiple scripts (moreover am in desperate need to go to the supermarket). Could you possibly distill out an ad script for me to bite on please?

This example includes lots of comments, but that's a real-world possibility:

<!--/* OpenX JavaScript tag */-->

<!-- /*
 * The tag in this template has been generated for use on a
 * non-SSL page. If this tag is to be placed on an SSL page, change the
 * 'http://ox-d.netline.com/...'
 * to
 * 'https://ox-d.netline.com/...'
 */ -->

<script type="text/javascript">
if (!window.OX_ads) { OX_ads = []; }
OX_ads.push({ "auid" : "215601", "r":"%%CLICK_URL_UNESC%%" });
</script>
<script type="text/javascript">
document.write('<scr'+'ipt src="http://ox-d.netline.com/w/1.0/jstag"><\/scr'+'ipt>');
</script>
<noscript><iframe id="501ad8903503e" name="501ad8903503e" src="http://ox-d.netline.com/w/1.0/afr?auid=215601&cb=%%CACHEBUSTER%%" frameborder="0" scrolling="no" width="728" height="90"><a href="http://ox-d.netline.com/w/1.0/rc?cs=501ad8903503e&cb=%%CACHEBUSTER%%" ><img src="http://ox-d.netline.com/w/1.0/ai?auid=215601&cs=501ad8903503e&cb=%%CACHEBUSTER%%" border="0" alt=""></a></iframe></noscript>

Back from the supermarket

Thanks Dani, I'm on it.

Dani,

I think I've made some progress. Earlier approaches abandoned.

In the stripped-down page below, ads are written in the normal way but not into their intended locations on the page. Instead, they are loaded at the bottom of the page and the resulting DOM fragment is moved later (in a window.onload handler) into placeholders embedded at suitable points in the page's HTML.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://www.facebook.com/2008/fbml" xml:lang="en" lang="en-US">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<title>DaniWeb | Non-blocking ads | Tests </title>
<style>
body {
    margin: 10px;
}
#logo1 {
    background: url("http://static.daniweb.com/images/sprite-icons.gif") no-repeat -705px -615px;px Arial;
    height: 25px;
    margin: 15px 0px 0px;
    width: 234px;
    display: block;
    clear: both;
}
.ads {
    display: none;
}
.ad {
    width: 150px;
    border: 1px dashed #000;
    margin: 5px 0;
    padding: 5px;
    background-color: #6699CC;
}
</style>
<script>
window.onload = function() {
    $("div.ad").each(function(i) {
        var $this = $(this);
        var seq = $this.data('seq');
        $this.find("script").remove();
        $('#ad_placeholder_' + $this.data('seq')).append($this);
    });
}
</script>
</head> 

<body>

<!-- Page's HTML including placeholders for ads -->
<div id="logo1"></div>
<div>Page section 1</div>
<div id="ad_placeholder_1"></div>
<div>Page section 2</div> 
<div id="ad_placeholder_2"></div>
<div>Page section 3</div> 
<div id="ad_placeholder_3"></div>
<div>Page section 4</div> 

<!-- Below here, hidden ads that will be moved into placeholders when the page (including ads) has loaded -->
<div class="ads">
    <div class="ad" data-seq="1">
        <div>Dummy Ad 1</div>
        <script>document.write('<div>Ad 1 doc.write text</div>');</script>
    </div>

    <div class="ad" data-seq="2">
        <script>document.write('<div>Ad 2 doc.write text</div>');</script> 
        <div>Dummy Ad 2</div> 
    </div>

    <div class="ad" data-seq="3" style="width:728px;height:90px;">
        <!--/* OpenX JavaScript tag */-->
        <!-- /*
         * The tag in this template has been generated for use on a
         * non-SSL page. If this tag is to be placed on an SSL page, change the
         * 'http://ox-d.netline.com/...'
         * to
         * 'https://ox-d.netline.com/...'
         */ -->
        <script type="text/javascript">
        if (!window.OX_ads) { OX_ads = []; }
        OX_ads.push({ "auid" : "215601", "r":"%%CLICK_URL_UNESC%%" });
        </script>
        <script type="text/javascript">
        document.write('<scr'+'ipt src="http://ox-d.netline.com/w/1.0/jstag"><\/scr'+'ipt>');
        </script>
        <noscript><iframe id="501ad8903503e" name="501ad8903503e" src="http://ox-d.netline.com/w/1.0/afr?auid=215601&cb=%%CACHEBUSTER%%" frameborder="0" scrolling="no" width="728" height="90"><a href="http://ox-d.netline.com/w/1.0/rc?cs=501ad8903503e&cb=%%CACHEBUSTER%%" ><img src="http://ox-d.netline.com/w/1.0/ai?auid=215601&cs=501ad8903503e&cb=%%CACHEBUSTER%%" border="0" alt=""></a></iframe></noscript>
    </div>
</div>

</body>
</html>

With this approach the ads load in the normal way - the scripts are exactly as delivered by the ad-agent. No machinations are necessary. Also, no iframe/object is required.

Interestingly, I found I had to manually remove the script tags before moving the ads otherwise (in Opera) the document.write() statements would execute again and blitz the page.

This may not be a complete solution but hopefully a useful stepping-stone.

Two aspect are uncertain :

  • I can't tell whether any daisy-chaining is happening so the ability of this approach to handle daisy-chains remains unknown.
  • In the stripped-down page I can't tell whether the ads' blocking action has been averted. Needs testing in a full daniweb page.

Tested in:
- Opera 12.02 - OK
- Chrome 21.0 - OK
- IE9 under Win7 - ad image re-renders unreliably after moving.

commented: Thank you so much for your help +14

I had played around with that idea initially and didn't have much luck with it :( Amazingly, I was able to get something that does work. Tested in Firefox, Opera and Chrome.

<div id="insert-here"></div>

<div id="ad" style="display:none">
    <!--/* OpenX JavaScript tag */-->

    <!-- /*
     * The tag in this template has been generated for use on a
     * non-SSL page. If this tag is to be placed on an SSL page, change the
     * 'http://ox-d.netline.com/...'
     * to
     * 'https://ox-d.netline.com/...'
     */ -->

    <script type="text/javascript">
    if (!window.OX_ads) { OX_ads = []; }
    OX_ads.push({ "auid" : "215601", "r":"%%CLICK_URL_UNESC%%" });
    </script>
    <script type="text/javascript">
    document.write('<scr'+'ipt src="http://ox-d.netline.com/w/1.0/jstag"><\/scr'+'ipt>');
    </script>
</div>


<div id="blah" style="display:none">
    <script type="text/javascript">
    <!--
    var s = $('div#ad').html();
    var ad = $('<div />', {'width': 728, 'height': 90}).html(s).appendTo('#insert-here');
    //-->
    </script>
</div>

The problem is that it doesn't seem to be working in a production environment :( It's stripping all the script tags upon insert.

Update: It looks like my test was working because jQuery executes <script> tags before it strips them, and somewhere down the daisy chain, there was a <noscript> tag that was being executed. Me thinks??!!

You're soooo not going to believe this but I got writeCapture2 to work!! :)

Update: Don't even need the full writeCapture 2. Its library did the trick.

Literally as easy as ...

var ad_string = 'blah blah blah';
var ad_placement = $('#ad-placement');
elementWrite.toElement(ad_placement[0]).write(ad_string).close();

Dani, that's great.

Does this approach allow you to target an IFRAME?

I was unable to get it to work when targeting an iframe. I might try again, because I think I know what I did wrong. However, my big reason for wanting to target an iframe was to protect the rest of the page from malformed HTML, and elementWrite actually sanitizes the strings that it writes, making it a non-issue.

The only thing I'm not sure about is whether it only sanitizes the initial string passed into it, or whether it actually goes the next step to sanitize the final result the string dynamically generates through multiple nested calls to document.write.

... whether it actually goes the next step to sanitize the final result ...

Good point. Easier to test if you have your own daisy-chain I guess.

OK, I think I got it. The elementWrite library is basically a replacement for Javascript's InnerHTML function, only it sanitizes output in a way more desirable for this particular need.

WriteCapture uses elementWrite to load ads asynchronously (in other words, it waits for multiple nested calls to document.write to complete, and then takes the super final output string and sends it to elementWrite) ... If I am understanding everything correctly???

Check this out and let me know whether I'm right, because I think I'm confusing myself a bit: https://groups.google.com/forum/?fromgroups=#!forum/writecapturejs-users

Dani, I've not got into this too deeply but there appears to be considerable differences in the behaviour of WriteCapture.js and WriteCapture2.js.

The post entitled New Feature! Automatic Asynchronous Writes was started 6/11/10 and I guess relates to V1.

The post entitled WriteCapture2.js and sanitize() is much more recent and indicates that V2 neither sanitizes nor queues insertions. I don't understand the reasoning for these seemingly retrograde steps.

A

WriteCapture.js sanitized and required jQuery. WriteCapture2.js is a complete rewrite and relies on the elementWrite library (written by the same author) to do the actual output, and it's elementWrite that does the sanitizing.

OK, got it. That makes a whole lot more sense now.

Member Avatar for Troy3

I still think that using <iframe src="ads1.html"> is the best solution posssible.
!The "ads1" request(s)-code, should reside on the ads1.htm page content.

And the benefits are:

  1. iframes load async., will not hold the page load;
  2. you can size, style, place and format them anyway you like without the common trouble ;
  3. unexpected document.write(s) will not end up messing your page layout;
  4. It's easy.

p.s.:
I'm not being able to use / send PM from this account
and I'm seing "Are you a human?" question field!

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.