Using An IFrame To Override document.write() Inside A DOM (Document Object Model) Sandbox
Last week, I talked about loading GitHub Gist content after the DOM (Document Object Model) had loaded. This was a somewhat complex operation since the remote Gist Script tag uses document.write() in order to inject the Gist content into the calling document. If document.write() is called after the parent document has been "closed," the write() call will overwrite the entire page. As such, we had to override the definition of the document.write() method during the Gist load. This approach left me somewhat unsatisfied since overriding the document.write() method felt "dirty;" as such, I wanted to see if I could use an IFrame to sandbox the script loading in such a way that the primary document.write() method never had to be touched.
document.write( " .... LINK TAG FOR GIST CSS .... " ); document.write( " .... GIST HTML CONTENT .... " );
Since we want to load the content of the Gist asynchronously, we have to override the meaning of document.write() at the time the Gist script is loaded. In order to not affect the calling document, I'm going to instantiate an IFrame element and then write the Gist Script tag to the content of the IFrame. However, since we ultimately want to pipe the content of the Gist back into the calling document, we also have to override the meaning of document.write() within the IFrame before the Gist Script tag is injected.
At the 1,000-foot view, the workflow looks something like this:
- Create an IFrame.
- Create a proxy for document.write().
- Override the IFrame's document.write() method to point to the "proxy."
- Inject the Gist script tag into the IFrame.
- Use the "written" Gist content.
- Discard the IFrame.
Before we get into this workflow, let's just take a look at the demo code first so that we can see how the final product is going to be used:
As you can see, this demo is exactly the same as the one in my previous blog post. Our jQuery plugin provides a $.getGist() function which will load the contents of the remote GitHub Gist. Like all asynchronous methods in modern jQuery, this one returns a Deferred promise object which will eventually resolve with the contents of the Gist.
Ok, now that we've refreshed our memory in terms of what we want to do, let's take a look at the $.getGist() plugin to see how an IFrame is being used to override document.write() in a way that leaves the parent document unaffected.
jquery.gist-frame.js - Our Remote GitHub Gist Loader Plugin
There's a good amount of utility code here that is Gist-specific; however, if you look at the following code block, I think you'll get the main idea behind how this is working:
This is the code that is being written to the IFrame document. It consists of two Script tags - the first one overrides the document.write() method (in the context of the IFrame); the second one loads the remote GitHub Gist script.
As you can see, the overridden version of document.write() points to another method, document.proxyWrite(). This two-layer abstraction allows us to change the definition of the underlying proxyWrite() property without having to change the definition of document.write(). This way, the first call to document.write(), as performed by the GitHub Gist script, can write the stylesheet; the second call to document.write() can parse the Gist content and resolve the aforementioned Deferred promise.
Want to use code from this post? Check out the license.
Once again, making your day (w/o any additional valuable input)
I still appreciate it :D
I started using gists on my blog this week and I noticed my site dying.
Thanks @Ben for making some awesomr code to fix the problem. ;)