-
Notifications
You must be signed in to change notification settings - Fork 38
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.
<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>
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.
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 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>'
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.
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
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.