document.write, I pity the fool. But also suggest ways in which he can better himself

This post has been updated since i first wrote it, originally I suggested using innerHTML to insert the content, the problem there is that scripts added in that way do not execute.

Document.write has been causing me pain this week, prepare for a small amount of constructive venting.

You might remember the Write method from those days you were learning how to write JS, whack it in and it writes whatever you give it to the screen. That’s fine but the problems occur when you think a bit more about how it does that and almost always you should be using something else.

If I write a script using Write and include it in the page body, any writes will be made in that same position. That makes sense as it’s part of the source parse and script evaluation, it’s where the browser is up to.

What it totally screws up is when I then want to make your damn 3rd party script deferable / lazy loadable! Why? Because once the page has been loaded any subsequent calls to document.write will replace the entire page.

Fix document.write

We can overwrite the document.write function but the difficulty is what with. My best guess at the moment is that;

  1. Before the page is loaded write calls are performed normally
  2. Once loaded, we replace Write with an new function
  3. When called we append the content to the body element.

Example with jQuery.

var fDocumentWrite = function(str,bOverrule){
     if (bOverrule === true) {
          //superwrite is a reference to the original document.write function
          fSuperWrite.call(this,str);
     }
     else {
          jQuery(document.body).append(str);
     }
};

Why not something else?

  • We cannot be sure of the content, if it’s text it could just be a text node that we append, but if it’s HTML it needs to be parsed and each created... Or shoved in a container anyway.
  • We can’t use innerHTML, doing so on the body will destroy event hooks appied to that code, innerHTML replaces all that content so it’ll also be slow. Also scripts dont execute when inserted using innerHTML.
  • Don’t defer the script? It’s safest, but with a decent amount of testing we can make this safe enough. Though there will always be a vender somewhere crazy enough to break anything

If you have a better fix for this then let me know, I've about four scripts I want to defer and it's only Wednesday.

In summary, don't use document.write, Doug Crockford says not to do and, even though he has a beard, you should trust him on this.

JavaScript patterns. Closures, jQuery plugins

I've been re-writing a lot of JavaScript recently, as part of a move from Prototype to jQuery, and it's given me a great chance to concentrate on how it's written rather than what's written.

Carousel implementation

Plugins are a major plus in jQuery, but they're not suited to everything. A plugin is called to act on items in a collection, in the case of a carousel it would be the carousel container. A carousel is a perfect example of where a plugin is an appropriate solution, they're also ripe to be created as objects, keeping all variables and functions contained and aligned to the element in question.

Tooltip implementation

Again these are suited to being plugins, but not as objects. When the plugin is initialised we can do a single pass to bind all the actions, we don't need to store information about the state of the tool tip as we do with a carousel.

Lazy-loader implementation

My implementation of a lazy-loader is called as part of the init methods of a page, it's classic functional programming and aims to be unintrusive when writing or executing code. I've implemented it using a closure (actually, using loose augmentation in the article below) where all methods but the initial call are private methods. Since it has no elements to modify it is unsuited to being a plugin.

Sources/Reading

Implementing 3D Secure

3D Secure (3DS) is an authorisation step that can be added to a purchase journey, it's aim to avoid card fraud by asking for additional security information. There has been lots of discussion/criticism around it's implementation, my aim in this post is not about these issues but about how to actually implement it.

The 3DS process is not hosted on the vendors server, instead the user is directed to a third party site to enter their details. The current recommendation is that this is done through an iFrame on the vendors site. The previous recommendation was that the user is redirected to the 3DS pages.

A key issue here (aside from the phishing concerns!) is that iFrames are hard to handle without JavaScript, the vendor must post details to the 3DS server so we need to provide non-js messaging and controls so the user can continue and complete the process.

3D Secure process

  1. The 3DS page is included through an iFrame
  2. Within the iFrame, the customer negotiates the 3DS process and the result is then posted back to a page hosted by the vendor
  3. The vendors page uses the result of the call to update the transaction held in session data. We can use JavaScript at this point to submit the parent page
  4. The backend system determines the appropriate result page, success or failure, and this is presented to the user

For the non-js version, as mentioned, the vendors page loaded in the iFrame should include a button that targets the parent page (target=top") so the user can continue manually.

Read more information on 3D Secure (Wikipedia)