onbeforeunload event and differences between browsers

[tweetmeme service=bit.ly]
The [recently in webkit] supported onbeforeunload window event can be very useful to inform the user if they are about to loose some data which they haven’t saved yet etc. So, in your method that the event calls you will probably have some logic to decide if the method should return or not (returning a string from the method causes the message to be displayed to the user).

This is all very well and good, but we came across an instance where outside of this method we wanted to set a variable to stop the message, request a new window.location (to download an file) and then turn the variable on again. The code looked a little like this:

window.shouldWarnTheUser = false;
window.location = '/download/my.file';
window.shouldWarnTheUser = true;

And the onbeforeunload callback looked a bit like this:

window.onbeforeunload = function() {
  if (window.shouldWarnTheUser) {
    return "Some warning!";
  }
}

This gave us different results between different browsers and took some time for us to figure out why.

Consider the following example:

window.onbeforeunload = function() {
  console.log("3");
}

console.log("1");
window.location = '/download/my.file';
console.log("2");

Question:

What order do the numbers get logged out?

Answer:

  • Webkit: 1, 2, 3. (Safari 4.0 and Chrome 4.0 on OS X 10.6.2)
  • Firefox: 1, 3, 2. (FF 3.5 on OS X 10.6.2)
  • Both understandable depending on how you think the events are fired

  • Internet Exploder: 3, 1, 2. – What?! (IE 8 on Vista)

Datatypes in Javascript – setting the record straight

[tweetmeme]
It’s a question that often comes up and there are many places on the internet that give an inaccurate answer, so here’s the real-deal.

Javascript has three primitive datatypes:

  • number
  • string
  • boolean

There are another two special datatypes:

  • null
  • undefined

The others are variations of object:

  • object
  • function
  • array

See here for further reading.

Detecting an invalidly parsed date in Javascript

[tweetmeme]
When parsing a date in Javascript, it is important to ensure that the resultant object has been parsed correctly. This is made slightly tricky as the resultant object will always be a Date object if created with new Date().

The problem is compounded by differences between IE and other browsers. IE will return a NaN whereas other browsers will report "Invalid Date".

Use the following code to be sure:

var d = new Date("Malformed date")
if (d != "NaN" && d != "Invalid Date") {
  // date is good
} else {
  // date is bad
}

Does anyone else have any good ways of doing this detection?

Using jQuery and Ajax to load sub-sections of a webpage

OK, so this isn’t really rocket science, but there are a couple of neat uses for this technique:

Ajax powered tab sets

Tabs can; broadly-speaking, be loaded in three different ways:

  • Immediately loaded – i.e. when the webpage has loaded, it is the default selected tab.
  • Later (deferred loading) – i.e. after the user clicks a tab, go off to the server and retrieve the contents of a tab, or
  • Lazy loading – e.g. load in the background before the user clicks a tab.  This enhances the user experience as the contents of a tab are immediately shown, but on the flip side means requires more traffic and the user may never click the tab anyway, effectively wasting bandwidth.

Content panes, portal layouts etc

As with tabs, content panes on a page can be loaded in several ways, although lazy loading is probably not very useful.

Solution

Here’s a javascript (using jQuery) implementation of the above which supports loading now, loading on page load and lazy loading (which takes place after the on page loads):

ajaxLoader : {
    /** A queue of embeds to load on document ready */
    loadQueue: {},
    
    /** A queue of embeds to load on after the load queue */
    lazyQueue: {},
    
    /**
     * Called on Document Ready to load all the queued embeds
     */
    init: function() {
        jQuery.each(this.loadQueue, function(id, url) {
            wbHtmlWidgets.embed._load(id, url);
        });
        
        jQuery.each(this.lazyQueue, function(id, url) {
            wbHtmlWidgets.embed._load(id, url);
        });
    },
    
    _load: function(id, url) {
        $("#" + id).load(url);
    },
    
    /**
     * Loads a url into a dom id immediately - ensure that the page has
     * completely loaded before using this method, as manipulating the DOM
     * before it has been created does nasty things in browsers such as IE.
     * If you want to embed content after the page has loaded, use embed.afterLoad()
     * @param {string} id The div id to load the embed into
     * @param {string} url The url to load into the specified div id
     */
    now: function(id, url) {
        this._load(id, url);
    },
    
    /**
     * Loads a url into a dom id after the onLoad queue has been processed.
     * @param {string} id The div id to load the embed into
     * @param {string} url The url to load into the specified div id
     */
    lazy: function(id, url) {
        this.lazyQueue[id] = url;
    },
    
    /**
     * Loads a url into a dom id after the page has loaded.
     * If you want to embed content now, use embed.now()
     * @param {string} id The div id to load the embed into
     * @param {string} url The url to load into the specified div id
     */
    afterLoad: function(id, url) {
        if ( $("#"+id).html() == '' ) {
            $("#"+id).html('<image src="/images/spinner.gif" />');
        }
        this.loadQueue[id] = url;
    }
}

To get this to work, ensure that you have an onLoad (document ready) event on your page…

$(document).ready(function(){
    ajaxLoader.init();
});

Then to make the magic work, in your HTML you may want to do something like:

<span id="tab-0"></span>              
<script type="text/javascript">
//<!&#91;CDATA&#91;
    ajaxLoader.afterLoad("tab-0", "http://your/tab/address");
//&#93;&#93;>
</script>