Skip to content
pulkitsinghal edited this page Nov 14, 2011 · 7 revisions

Usage

writeCapture can be used with or without jQuery (see Other Usage below) and can be loaded asynchronously. For async loading, ControlJS is a good option, but any async loading library or technique should work.

Note that all the calls shown here accept a common set of Options unless otherwise noted.

Install

As a jQuery plugin

<script
  src="http://code.jquery.com/jquery-1.6.4.min.js"
  type="text/javascript"
  charset="utf-8">
</script>

<script
  src="http://raw.github.com/iamnoah/writeCapture/master/writeCapture.js"
  type="text/javascript"
  charset="utf-8">
</script>

<script
  src="http://raw.github.com/iamnoah/writeCapture/master/plugin/jquery.writeCapture.js"
  type="text/javascript"
  charset="utf-8">
</script>

Automatic Asynchronous Writes

Perhaps the most common use for writeCapture is to prevent ads and other 3rd party scripts that use document.write from blocking the DOM while the page loads. writeCapture has a special method specifically for this case. Simply call

$.writeCapture.autoAsync();

at some point in the document before the scripts that use document.write.

You can also pass a function to be called when all the async writes have finished:

$.writeCapture.autoAsync(function() {
	alert('Ads loaded!'); // don't do this though, it would be annoying
});

(Actually you can pass all the options you'd pass to any writeCapture call, see below for details).

Note that at this time, autoAsync is only supported when jQuery is available. However, you can easily add support to you page by defining writeCaptureSupport.onLoad. See "Implementing writeCaptureSupport" below.

It's important to note that this will not prevent a cross-domain script tag placed directly in the page from DOM blocking if the external server is down, or running slow due to heavy load. For example, in the following markup, the browser will block at the script trying to load foo.js from example.com.

<script src="http://example.com/foo.js" ...

However, if your script uses document.write to write out the external script tag like (which is actually quite common) the browser will not block:

<script>document.write('<scrip'+'t src="http://example.com/foo.js"> </s'+'cript>');</script>

If you would rather not add more code using document.write you can use a placeholder instead:

<div id="placeholder"> </div>

<script> 
$(document).ready(function() {
	$('#placeholder').writeCapture().html('<script src="http://example.com/foo.js"> </script>');
});
</script>

Note that when using the placeholder method, calling autoAsync is unnecessary.

Other Usage

If autoAsync isn't working for your script, or you have another use case like Ajax loading HTML containing scripts, writeCapture provides a flexible API to accomplish this.

The easiest way to use writeCapture is through the jQuery plugin:

$('#target-el').writeCapture().html('<script src="http://evilAdServer.com/doSomeDocWrite.js"> </script>');

The remainder of this documentation will describe somewhat lower level use cases and go into the finer details of the library.

Key Method: writeCapture.sanitize(html,options) (source) is the heart of the library. The plugin and convenience functions are simply wrappers around this function.

In the examples below, note that writeCapture.sanitize doesn't execute scripts, the scripts are executed by jQuery and the browser when the HTML returned by sanitize is inserted into the page via html(), replaceWith() or any other jQuery HTML manipulation method. You don't have to use jQuery, but your library of choice does need to be able to execute scripts inside an HTML fragment.

Inline and same domain scripts

Inline scripts are always executed synchronously. Scripts on the same domain are also downloaded and executed synchronously by default.

var html1 = '<div>Some HTML with <script type="text/javascript">document.write("scripts");</script> in it.</div>';

$('#someElement').html(writeCapture.sanitize(html1));
$('#someElement').html() === '<div>Some HTML with scripts in it.</div>'

XDomain scripts (or asyncAll: true)

If any of the scripts being loaded is on another domain (or you pass asyncAll: true), they will be downloaded and run asynchronously, so any actions that script takes, including document.write calls, will happen later. If you need to take some action after the scripts run, use the done callback:

// http://example.com/xdomain.js = document.write('a script on another domain');

var html2 = '<div>Some HTML with <script type="text/javascript" src="http://example.com/xdomain.js"> </script>.</div>';

$('#someElement').html(
    writeCapture.sanitize(
        html2,
        function () { // this callback will be used when the cross-domain script has finished loading
            $('#someElement').html() === '<div>Some HTML with a script on another domain.</div>'
        }
    )
);
// The following will fail because the cross-domain script hasn't run yet)
// $('#someElement').html() == '<div>Some HTML with a script on another domain.</div>'

Scripts on the same domain can also be downloaded asynchronously, if desired:

// local.js = document.write('a local script, loaded async');

var html3 = '<div>Some HTML with <script type="text/javascript" src="local.js"> </script>.</div>';

$('#someElement').html(
    writeCapture.sanitize(
        html3,
        {
            asyncAll: true, // same domain scripts will be loaded async
            done: function () {
                $('#someElement').html() === '<div>Some HTML with a local script, loaded async.</div>'
            }
        }
    )
);

Note that this option does not affect inline scripts, which are always executed immediately.

Multiple HTML Fragments

writeCapture uses an internal queue to ensure that all scripts are run in the correct order, so you don't need to work about multiple calls interfering with each other

Convenience Functions

There are 3 convenience functions on the writeCapture object in addition to autoAsync and sanitize. They are html, replaceWith and load.

writeCapture.html and writeCapture.replaceWith behave similarly to the same functions in jQuery. html sanitizes the content and replaces the given element's innerHTML. replaceWith sanitizes and replaces the entire element with the result.

writeCapture.load is similar to jQuery's load method but does not currently support using a selector to filter what is injected. The content from the given URL will be sanitized.

// body contains a div with id 'foo'
$('body').html() === "<div id=foo></div>";

// this is our html that has scripts which do document.write
var str_html = "<div><script>some script with document.write() in it</script></div>";

// include the scripts, and have document.write "point" to '#foo' element
writeCapture.html('#foo', str_html, function(){
    // str_html has been successfully loaded into the 'foo' div.
    // all calls to document.write() have been executed.
});

You could also write:

// any DOM element can be used
var el = document.getElementById('foo');
writeCapture.html(el, str_html, ...

replaceWith:

writeCapture.replaceWith('#bar',html2,function() {
	/*
		$('body).html() === 
		'<div id="foo"><div>Some HTML with scripts in it.</div></div><div>Some HTML with a script on another domain.</div>';
	*/    	
});

/*
	foo.php returns 
	<div>Some HTML with <script type="text/javascript">document.write("scripts");</script> in it.</div>
*/
writeCapture.load('#foo','foo.php',function() {
	// $('#foo').html() === '<div>Some HTML with scripts in it.</div>'	
});

Note that all of these functions will work using nolib-support, but only id based selectors will be supported. You can also pass the element itself. If jQuery is used, any jQuery selector is allowed, but only the first matched element will be affected.