Posts tagged ‘javascript’


Taking a peek inside __VIEWSTATE

If you’ve ever viewed-source on a website that uses Microsoft technology like ASP or .NET, you may have noticed a massive blob of unintelligible text stored in a input field called __VIEWSTATE. What you’re seeing is actually a bunch of Base64 encoded data that gets passed back and forth between the server and the client. I don’t understand how anyone ever thought this was a good idea, but there are a ton of sites that still use this technique. Just check out some old enterprise applications or any Microsoft website and you’ll see what I mean. The United and US Airways websites are a couple other good examples. Unless the __VIEWSTATE is encrypted, you’ll be able to take a look inside using this simple bookmarklet:

javascript:xmp=document.createElement("xmp");
txt=document.createTextNode(atob(document.getElementsByName("__VIEWSTATE")[0].value));
xmp.appendChild(txt);
document.body.insertBefore(xmp,document.body.firstChild);

Drag this link to your toolbar to try it: Decode ViewState

  comments

Pinterest’s 25 edge cases

I was snooping around the JavaScript that Pinterest uses for their “Pin It” button. No real reason. I’m just nosy.

Most of the code is what you would expect, but what jumped out at me were the 25 websites that get special treatment. In fact, Pinterest’s JavaScript is riddled with special handling for edge-cases on third-party sites. As a developer, I have the tendency to always search for the universal solution – one piece of code that works everywhere. While universal code is arguably “better”, this more often than not is what code looks like in the “real world”.

lookup: {
        artsy: {
            page: {
                seek: [/^https?:\/\/(.*?\.|)artsy\.net\/artwork\//,
                    /^https?:\/\/(.*?\.|)artsy\.net\/post\//
                ]
            },
            img: {
                seek: [/^https?:\/\/(.*?\.|)artsy\.net\//],
                act: "lookup"
            }
        },
        behance: {
            img: {
                seek: [/^http:\/\/behance\.vo\.llnwd\.net\//],
                act: "lookup"
            }
        },
        dasauge: {
            img: {
                seek: [/^https?:\/\/cdn?[0-9]\.dasauge\.net\//],
                act: "lookup"
            }
        },
        dailymotion: {
            page: {
                seek: [/^https?:\/\/.*?\.dailymotion\.com\//],
                act: "lookup",
                via: "id",
                multimedia: true,
                doNotCrawl: true
            }
        },
        dreamstime: {
            img: {
                seek: [/(.*?)\.dreamstime\.com\//],
                act: "lookup"
            }
        },
        etsy: {
            page: {
                seek: [/^https?:\/\/.*?\.etsy\.com\/listing\//],
                patch: {
                    img: function (g) {
                        return g.replace(/il_(.*?)\./,
                            "il_570xN.")
                    }
                }
            },
            img: {
                seek: [/^https?:\/\/.*?\.etsystatic\.com\//],
                patch: function (g) {
                    return g.replace(/il_(.*?)\./, "il_570xN.")
                },
                act: "lookup"
            }
        },
        fivehundredpx: {
            page: {
                seek: [/^https?:\/\/500px\.com\/photo\//],
                act: "lookup",
                via: "id",
                doNotCrawl: true
            },
            img: {
                seek: [/pcdn\.500px\.net\//],
                act: "lookup"
            }
        },
        facebook: {
            page: {
                seek: [/^https?:\/\/(.*?\.|)facebook\.com\//],
                act: "close",
                msg: "privateDomain",
                patch: function (g) {
                    return g.replace(/%privateDomain%/, "Facebook")
                }
            }
        },
        flickr: {
            page: {
                seek: [/^https?:\/\/www\.flickr\.com\//],
                act: "lookup",
                via: "id",
                doNotCrawl: true
            },
            img: {
                seek: [/staticflickr.com\//, /static.flickr.com\//],
                act: "lookup"
            }
        },
        geograph: {
            img: {
                seek: [/^https?:\/\/(.*?)\.geograph\.org\./],
                act: "lookup"
            }
        },
        googleReader: {
            page: {
                seek: [/^https?:\/\/.*?\.google\.com\/reader\//],
                act: "close",
                msg: "privateDomain",
                patch: function (g) {
                    return g.replace(/%privateDomain%/, "Google Reader")
                }
            }
        },
        googleList: {
            page: {
                seek: [/^https?:\/\/www\.google\.com\/search(.*&tbm=isch.*)/],
                patch: function (g) {
                    g.f.debug("patching Google Image Search results");
                    var i, j, a, b, c, d;
                    if (i = g.d.getElementById("ires")) {
                        i = i.getElementsByTagName("A");
                        j = 0;
                        for (a = i.length; j < a; j += 1) {
                            d = c = "";
                            if (i[j].href) {
                                b = i[j].href.split("imgrefurl=");
                                if (b[1]) c = b[1].split("&")[0];
                                b = i[j].href.split("imgurl=");
                                if (b[1]) d = b[1].split("&")[0]
                            }
                            if (c && d) {
                                b = i[j].getElementsByTagName("IMG");
                                if (b[0]) {
                                    g.f.set(b[0], "data-pin-url", decodeURIComponent(c));
                                    g.f.set(b[0], "data-pin-media", decodeURIComponent(d))
                                }
                            }
                        }
                    }
                }
            }
        },
        imdb: {
            img: {
                seek: [/^https?:\/\/(.*?)\.media-imdb\.com\/images\//],
                patch: function (g) {
                    return g.replace(/@@(.*)/,
                        "@@._V1_SX800.jpg")
                }
            }
        },
        kickstarter: {
            page: {
                seek: [/^https?:\/\/.*?\.kickstarter\.com\/projects\//],
                act: "lookup",
                via: "id",
                multimedia: true
            }
        },
        pinterest: {
            page: {
                seek: [/^https?:\/\/(.*?\.|)pinterest\.com\//],
                act: "close",
                msg: "installed"
            }
        },
        polyvore: {
            page: {
                seek: [/^https?:\/\/(.*?\.|)polyvore\.com\//],
                act: "lookup",
                via: "id",
                doNotCrawl: true
            },
            img: {
                seek: [/^https?:\/\/(.*?)\.polyvoreimg\.com\//],
                act: "lookup"
            }
        },
        shutterstock: {
            img: {
                seek: [/^https?:\/\/image.shutterstock\.com\//, /^https?:\/\/thumb(.*?).shutterstock\.com\//],
                act: "lookup"
            }
        },
        slideshare: {
            page: {
                seek: [/^https?:\/\/.*?\.slideshare\.net\//],
                act: "lookup",
                via: "id",
                multimedia: true,
                doNotCrawl: true
            }
        },
        soundcloud: {
            page: {
                seek: [/^https?:\/\/soundcloud\.com\//],
                act: "lookup",
                via: "id",
                multimedia: true,
                doNotCrawl: true
            }
        },
        stumbleuponFrame: {
            page: {
                seek: [/^https?:\/\/(.*?\.|)stumbleupon\.com\/su/],
                act: "bustFrame",
                serviceName: "StumbleUpon",
                frameId: ["tb-stumble-frame", "stumbleFrame"]
            }
        },
        ted: {
            page: {
                seek: [/^https?:\/\/(.*?)\.ted\.com\/talks\//],
                act: "lookup",
                via: "id",
                multimedia: true,
                doNotCrawl: true
            },
            img: {
                seek: [/^https?:\/\/(.*?)\.ted\.com\//],
                act: "lookup"
            },
            iframe: {
                seek: [/^https?:\/\/(.*?)\.ted\.com\//],
                act: "lookup",
                via: "id"
            }
        },
        tumblr: {
            img: {
                seek: [/^https?:\/\/.*?\.media\.tumblr\.com\//],
                patch: function (g) {
                    return g.replace(/_(\d+)\.jpg$/, "_1280.jpg")
                }
            }
        },
        tumblrList: {
            page: {
                seek: [/^https?:\/\/www\.tumblr\.com\/tagged/, /^https?:\/\/www\.tumblr\.com\/dashboard/],
                patch: function (g) {
                    g.f.debug("patching Tumblr search or index");
                    var i, j, a, b, c, d, e, h;
                    i = g.d.getElementsByTagName("LI");
                    j = 0;
                    for (a =
                        i.length; j < a; j += 1) {
                        h = g.f.get(i[j], "data-tumblelog-content-rating");
                        b = i[j].getElementsByTagName("A");
                        e = "";
                        c = 0;
                        for (d = b.length; c < d; c += 1)
                            if (b[c].id && b[c].id.split("permalink_")[1]) {
                                e = b[c].href;
                                break
                            }
                        if (e) {
                            b = i[j].getElementsByTagName("IMG");
                            c = 0;
                            for (d = b.length; c < d; c += 1)
                                if (h === "adult") {
                                    g.f.set(b[c], "data-pin-nopin", true);
                                    g.f.debug("do not pin per Tumblr content rating: " + b[c].src);
                                    g.f.log("nsfw_per_domain", b[c].src, e)
                                } else g.f.set(b[c], "data-pin-url", e)
                        }
                    }
                }
            }
        },
        vimeo: {
            page: {
                seek: [/^https?:\/\/vimeo\.com\//],
                act: "lookup",
                via: "link",
                patch: function (g) {
                    g.f.debug("patching Vimeo page");
                    var i, j, a, b, c, d;
                    i = g.d.getElementsByTagName("LI");
                    j = 0;
                    for (a = i.length; j < a; j += 1) {
                        if (i[j].id && i[j].id.match(/^clip/)) {
                            b = i[j].id.split("clip");
                            if (b[1]) {
                                b[1] = b[1].replace(/_/, "");
                                g.f.thumbMedia("http://vimeo.com/" + b[1], "vimeo", "link")
                            }
                        } else {
                            b = i[j].getElementsByTagName("A");
                            if (b[0])(b = g.f.get(b[0], "data-id")) && g.f.thumbMedia("http://vimeo.com/" + b, "vimeo", "link")
                        }
                        b = i[j].getElementsByTagName("IMG");
                        c = 0;
                        for (d = b.length; c < d; c += 1) g.f.set(b[c],
                            "data-pin-nopin", true)
                    }
                }
            },
            iframe: {
                seek: [/^http?s:\/\/vimeo\.com\/(\d+)/, /^http:\/\/player\.vimeo\.com\/video\/(\d+)/],
                act: "lookup",
                via: "link",
                patch: function (g) {
                    var i = null;
                    g = g.split("#")[0].split("?")[0].split("/").pop();
                    if (g > 1E3) i = "http://vimeo.com/" + g;
                    return i
                },
                att: "src"
            }
        },
        youtube: {
            page: {
                seek: [/^https?:\/\/www\.youtube\.com\/watch/],
                act: "lookup",
                via: "link",
                multimedia: true,
                extended: true
            },
            video: {
                seek: [/^https?:\/\/(.*?\.|)youtube\.com\/videoplayback/],
                act: "lookup",
                via: "link",
                att: "data-youtube-id",
                patch: function (g) {
                    var i = null;
                    if (g) i = "http://www.youtube.com/embed/" + g;
                    return i
                }
            },
            iframe: {
                seek: [/^https?:\/\/(.*?\.|)youtube\.com\/embed\//],
                act: "lookup",
                via: "link"
            },
            embed: {
                seek: [/^http:\/\/s\.ytimg\.com\/yt\//],
                patch: function (g) {
                    var i = null;
                    g = g.split("video_id=");
                    if (g[1]) {
                        i = g[1].split("&")[0];
                        i = "http://www.youtube.com/embed/" + i
                    }
                    return i
                },
                att: "flashvars"
            },
            img: {
                seek: [/^https?:\/\/(.*?\.|)ytimg\.com\/(vi|li)\//, /img.youtube.com\/vi\//],
                act: "lookup"
            }
        }
    }
 2 comments

Simple expandable menu with jQuery

I recently needed a nested and expandable navigation system (accordion style) for a project I was working on.  I took a look around at the existing jquery plugins and was surprised by the complexity of them. To me, there’s no reason for a simple menu to require 200 lines of JavaScript.  Thankfully jQuery makes it possible to pack a lot of punch into just a few lines of code.  While I recognize the other plugins offer more functionality, I wanted to show that these days it sometimes makes more sense to just roll your own solution. Not only does this solution cut down on the number of bytes that the user has to download, but you’ll also have code that is more concise and easier to maintain.

Click here to check out the demo and download the code

Here’s the jQuery magic that I came up with:

$(document).ready(function() {
    $(".menu").click(function(e) {
        // unhighlight the previous menu selection
        $(".menu .selected").removeClass("selected");
        // highlight the selected item & its parents
        $(e.target).closest("li").addClass("selected").parent().parent().addClass("selected");
    });
});

The HTML is a standard nested list and looks something like this:

<div class='menu'>
    <ul>
        <li><a href='http://www.onlineaspect.com'>My blog</a></li>
        <li><a href='#'>My profiles</a>
            <ul>
                <li><a href='http://www.facebook.com/joshfraser'>Facebook</a></li>
                <li><a href='http://www.linkedin.com/in/joshuafraser'>LinkedIn</a></li>
                <li><a href='http://www.twitter.com/joshfraser'>Twitter</a></li>
            </ul>
        </li>
    </ul>
</div>

And the CSS is easy to customize to match your own look and feel:

.menu {
    width:200px;
}
.menu a {
    color:#666;
    text-decoration:none;
}
.menu ul {
    padding:0;
    border-top:1px solid #CCC;
}
.menu ul li {
    list-style:none;
    border-bottom:1px solid #CCC;
}
.menu ul li a {
    text-indent:6px;
    display:block;
    padding:6px;
}
.menu ul li a:hover, .menu ul li.selected a  {
    color:#FFF;
    background-color:#006363;
}
/* nested items */
.menu ul li ul {
    display:none;
}
.menu ul li.selected ul {
    display:block;
}
.menu ul li.selected ul a {
    color:#666;
    background-color:white;
    border-left:6px solid white;
}
.menu ul li ul li {
    border-bottom:1px solid white;
}
.menu ul li ul li:first-child {
    border-top:1px solid white;
}
.menu ul li.selected ul li.selected a, .menu ul li.selected ul li a:hover {
    color:#FFF;
    background-color:#7EB0B0;
    border-left:6px solid #006363;
}

That’s it. Remember – guard your bytes!

 6 comments

Backwards compatible window.postMessage()

You can find the latest version of this library on Github.

Simple cross-domain messaging

This blog post explains how to implement a backwards compatible version of window.postMessage() to handle all your cross-domain messaging needs. If you’re in a hurry, you can skip directly to the demo or just grab the following files:

Background

One of the trickiest things you will ever run into on the web is the same origin policy. The same origin policy basically limits how scripts and frames on different domains can talk to each other.  The same origin policy is an important part of your security on the web. For example, it prevents someone from being able to steal your password from another frame on the page. The annoying thing is there are sometimes perfectly valid reasons for frames on different domains to need to talk to one another.   One good example of this would be the Facebook Connect library where facebook.com needs to be able to communicate with non-facebook.com domains.  Over the years we’ve developed a series of hacks to work around this browser limitation. Some developers have used flash while others have relied on a window.location.hash hack.  Facebook worked around it by getting people to install a cross domain communication channel.  It got pretty ridiculous until the browser makers finally decided to give us a way to do cross-domain messaging without all the nonsense.  The result was window.postMessage() which is supported by the latest browsers like Firefox 3, Safari 4, Chrome and IE 8.  Unfortunately, as usual we’re going to need a backwards compatible version before we can take advantage of this new functionality.

I found a couple great examples of people who have tackled this already.  Luke Shepard wrote xd.js which is part of the open-sourced Facebook Connect code.  I also found Ben Alman’s jQuery plugin which does a really nice job.  Both of these scripts are great, but neither fits quite right with my needs.  For one, I wanted the smallest possible script written in pure JavaScript.  I’m a fan of jQuery, but since I’ll be installing this code on other people’s domains I can’t assume that jQuery will be available and while I could load it up it’s important to keep the file size small.  So what I did was start with Ben’s code and took out all of the jQuery dependencies. Here is the result:

The code

// everything is wrapped in the XD function to reduce namespace collisions
var XD = function(){

    var interval_id,
    last_hash,
    cache_bust = 1,
    attached_callback,
    window = this;

    return {
        postMessage : function(message, target_url, target) {
            if (!target_url) {
                return;
            }
            target = target || parent;  // default to parent
            if (window['postMessage']) {
                // the browser supports window.postMessage, so call it with a targetOrigin
                // set appropriately, based on the target_url parameter.
                target['postMessage'](message, target_url.replace( /([^:]+:\/\/[^\/]+).*/, '$1'));
            } else if (target_url) {
                // the browser does not support window.postMessage, so use the window.location.hash fragment hack
                target.location = target_url.replace(/#.*$/, '') + '#' + (+new Date) + (cache_bust++) + '&' + message;
            }
        },
        receiveMessage : function(callback, source_origin) {
            // browser supports window.postMessage
            if (window['postMessage']) {
                // bind the callback to the actual event associated with window.postMessage
                if (callback) {
                    attached_callback = function(e) {
                        if ((typeof source_origin === 'string' && e.origin !== source_origin)
                        || (Object.prototype.toString.call(source_origin) === "[object Function]" && source_origin(e.origin) === !1)) {
                             return !1;
                         }
                         callback(e);
                     };
                 }
                 if (window['addEventListener']) {
                     window[callback ? 'addEventListener' : 'removeEventListener']('message', attached_callback, !1);
                 } else {
                     window[callback ? 'attachEvent' : 'detachEvent']('onmessage', attached_callback);
                 }
             } else {
                 // a polling loop is started & callback is called whenever the location.hash changes
                 interval_id && clearInterval(interval_id);
                 interval_id = null;
                 if (callback) {
                     interval_id = setInterval(function() {
                         var hash = document.location.hash,
                         re = /^#?\d+&/;
                         if (hash !== last_hash && re.test(hash)) {
                             last_hash = hash;
                             callback({data: hash.replace(re, '')});
                         }
                     }, 100);
                 }
             }
         }
    };
}();

Usage:

There are two parts to using this code: posting and listening. Both are relatively simple. To post a message we call XD.postMessage with a message, a URL and the frame that we want to talk to. Notice that we start off by passing the URL of the parent page to the child frame. This is important so the child knows how to talk back to the parent.

// pass the URL of the current parent page to the iframe using location.hash
src = 'http://joshfraser.com/code/postmessage/child.html#' + encodeURIComponent(document.location.href);
document.getElementById("xd_frame").src = src;

function send(msg) {
    XD.postMessage(msg, src, frames[0]);
    return false;
}

Setting up the listener on the child is also easy to do:

var parent_url = decodeURIComponent(document.location.hash.replace(/^#/, ''));

XD.receiveMessage(function(message){
    window.alert(message.data + " received on "+window.location.host);
}, 'http://onlineaspect.com');

I recommend taking a look at this barebones example to understand better how the various pieces fit together. This is still a work in progress and I’d love any feedback you have on it. I’m particularly interested in adding Flash as an alternative method before falling back to fragments. This is what the Facebook code does and I like it because it eliminates the nasty polling every 100ms.

Got other thoughts on how to make this better? Let me know in the comments.

 55 comments

Stop flash from covering HTML content

Browse through a few developer forums and you will find lots of people pulling their hair out over flash content covering up their dropdown menus or modal windows.  The problem is especially bad in IE (surprise, surprise). The fix is actually quite simple as long as you can edit the embed code for your flash. The trick is to change the wmode parameter to either “transparent” or “opaque”.  You should do this for both the <embed> and <object> tags like this:

<object width="200" height="300" data="example.swf" type="application/x-shockwave-flash">
    <param name="quality" value="high" />
    <param name="wmode" value="transparent" />
    <param name="src" value="example.swf" />
</object>

The problem is you don’t always have the option of changing the embed code.  What if you’re developing a JavaScript widget and don’t have any control over how the flash is embedded?  The solution is more complicated than you would expect.  It turns out there are a slew of IE bugs around the <object> tag that escalate what should be a simple task into a real headache.  Basically you need to replace each <embed> and <object> tag with a cloned version that has had its wmode parameter fixed. Here’s how you do it:

<script type="text/javascript">

function fix_flash() {
    // loop through every embed tag on the site
    var embeds = document.getElementsByTagName('embed');
    for(i=0; i<embeds.length; i++)  {
        embed = embeds[i];
        var new_embed;
        // everything but Firefox & Konqueror
        if(embed.outerHTML) {
            var html = embed.outerHTML;
            // replace an existing wmode parameter
            if(html.match(/wmode\s*=\s*('|")[a-zA-Z]+('|")/i))
                new_embed = html.replace(/wmode\s*=\s*('|")window('|")/i,"wmode='transparent'");
            // add a new wmode parameter
            else
                new_embed = html.replace(/<embed\s/i,"<embed wmode='transparent' ");
            // replace the old embed object with the fixed version
            embed.insertAdjacentHTML('beforeBegin',new_embed);
            embed.parentNode.removeChild(embed);
        } else {
            // cloneNode is buggy in some versions of Safari & Opera, but works fine in FF
            new_embed = embed.cloneNode(true);
            if(!new_embed.getAttribute('wmode') || new_embed.getAttribute('wmode').toLowerCase()=='window')
                new_embed.setAttribute('wmode','transparent');
            embed.parentNode.replaceChild(new_embed,embed);
        }
    }
    // loop through every object tag on the site
    var objects = document.getElementsByTagName('object');
    for(i=0; i<objects.length; i++) {
        object = objects[i];
        var new_object;
        // object is an IE specific tag so we can use outerHTML here
        if(object.outerHTML) {
            var html = object.outerHTML;
            // replace an existing wmode parameter
            if(html.match(/<param\s+name\s*=\s*('|")wmode('|")\s+value\s*=\s*('|")[a-zA-Z]+('|")\s*\/?\>/i))
                new_object = html.replace(/<param\s+name\s*=\s*('|")wmode('|")\s+value\s*=\s*('|")window('|")\s*\/?\>/i,"<param name='wmode' value='transparent' />");
            // add a new wmode parameter
            else
                new_object = html.replace(/<\/object\>/i,"<param name='wmode' value='transparent' />\n</object>");
            // loop through each of the param tags
            var children = object.childNodes;
            for(j=0; j<children.length; j++) {
                if(children[j].getAttribute('name').match(/flashvars/i)) {
                    new_object = new_object.replace(/<param\s+name\s*=\s*('|")flashvars('|")\s+value\s*=\s*('|")[^'"]*('|")\s*\/?\>/i,"<param name='flashvars' value='"+children[j].getAttribute('value')+"' />");
                }
            }
            // replace the old embed object with the fixed versiony
            object.insertAdjacentHTML('beforeBegin',new_object);
            object.parentNode.removeChild(object);
        }
    }
}

</script>

This solution is adapted from code I found on QIndex. Hopefully this version is a little cleaner and easier for people to find.

Note: There is also a jquery version of this code, complements of José Nobile.
 51 comments

Queue events that occur before JavaScript is loaded

One of the common recommendations for speeding up your website is to put your JavaScript at the bottom of your page instead of including it inside the head tag. The difference this simple placement can have is impressive, especially if you are dealing with sizable JavaScript libraries that are usually 50k at best.

One downside with putting your JavaScript at the bottom is that your fast clicking visitors may click on links that won’t work. The reason this happens is because the JavaScript that those links trigger hasn’t been downloaded yet. Usually those links will work on the second or third try, but it makes for a bad user experience and a poor first impression.

I decided to fix it by queuing up those user-triggered actions and replaying them as soon as the document is ready. I wrote a wrapper that I can use anytime I have code that depends on my JavaScript being downloaded and available.

The concept is simple. Instead of calling functions directly when a user triggers an action, I add the function call to a queue. When the document is ready, I loop through the queue and execute each of the actions in the order that they occurred. I put this code inline inside my head tag so it is available as soon possible. The rest of my JavaScript can then be included right before the closing body tag without worrying about this race condition between the browser and website visitor.

<script type="text/javascript">

var loaded = false;
var action_queue = new Array();

function when_ready(callback) {
    // skip the queue if the document has already loaded
    if (loaded == true)
        eval(callback);
    else {
        action_queue.push(callback);
    }
}

function dequeue_actions() {
    for (i in action_queue) {
        eval(action_queue[i]);
        delete(action_queue[i]); // cleanup after ourselves
    }
    loaded = true;
}

</script>

I then trigger dequeue_actions() as soon as the document is ready:

// using jQuery
$(document).ready(function(){
    dequeue_actions();
});

// this works too
onload = dequeue_actions;

You can then safely make function calls using when_ready(). For example:

<a onclick="select('foo')">foo</a>

becomes

<a onclick="when_ready('select(\'foo\')')">foo</a>

In my testing, the results have been very smooth with delays being almost unnoticeable. Of course, your experience will vary depending on the size of your document and how long it takes for your document to be ready.

This code is pure JavaScript and should work in every modern browser. I’ve tested it in IE6+, FF2+ and Safari 3+.

 9 comments

Reading GET variables with JavaScript

One of the things that isn’t immediately obvious in JavaScript is how to access GET variables. I’ve seen lots of different implementations for this around the web, but the majority of them are bulkier than they need to be. Here’s my favorite way to do it:

<script type="text/javascript">
    function $_GET(q,s) {
        s = s ? s : window.location.search;
        var re = new RegExp('&'+q+'(?:=([^&]*))?(?=&|$)','i');
        return (s=s.replace(/^?/,'&').match(re)) ? (typeof s[1] == 'undefined' ? '' : decodeURIComponent(s[1])) : undefined;
    }
</script>

What this gives you is a JavaScript implementation of PHP’s $_GET functionality.  I use a regular expression to keep the code to a minimum. Here is a simple example of how to use it:

// this code would print "hello world" if it was at http://localhost/index.php?var1=hello&var2=world
var var1 = $_GET('var1');
var var2 = $_GET('var2');
document.write(var1 + " " + var2);

Another thing I like about this implementation is that it makes it easy to parse GET variables from arbitrary search strings (ex “?var1=hello&var2=world”).  This is handy if you need to access GET variables from an HTML src parameter such as an image or script tag.

// get the src parameter and split it down to the search query string
var src = document.getElementById('example').src;
params = src.split('?');
var var1 = $_GET('var1','?'+params[1]);
Updated 01/14/11 with Kip Robinson’s improvements from the comments
 34 comments

Auto detect a time zone with JavaScript

This blog post will attempt to explain how to automatically detect your user’s time zone using JavaScript. If you’re in a hurry, you can skip directly to the JavaScript timezone detection code on Github.

Previous attempts to solve this problem:

Server side:

Time is not included in an HTTP request. This means that there is no way to get your user’s time zone using a server side scripting language like PHP.

IP address geocoding:

Another method that people have used to address this problem is to geocode your visitors IP address. IP geocoding is what is used when you go to a website and are shown an ad to “meet other singles in Boulder”. Unfortunately, for simply detecting a timezone, IP geo-coding is an expensive way to go. Just check out the prices for Maxmind and ip2location. There’s no way I’m paying for that. I did find a free provider called hostip, but it is worthless as it couldn’t decide whether I live in CA or NC.

With JavaScript:

The common JavaScript that is used to detect a visitor’s timezone is:

var myDate = new Date();
document.write(myDate.getTimezoneOffset());

As I started reading up on the getTimezoneOffset code I realized it was too buggy to be used in any critical application. The function returned inconsistent results in different browsers and it never seemed to account for daylight savings time correctly. It quickly became clear that I was going to have to write my own script if I wanted this to work.

How I ended up doing it:

There are basically two things needed to figure out a visitors time zone. First, we need to determine the time offset from Greenwich Mean Time (GMT). This can easily be done by creating two dates (one local, and one in GMT) and comparing the time difference between them:

var rightNow = new Date();
var jan1 = new Date(rightNow.getFullYear(), 0, 1, 0, 0, 0, 0);
var temp = jan1.toGMTString();
var jan2 = new Date(temp.substring(0, temp.lastIndexOf(" ")-1));
var std_time_offset = (jan1 - jan2) / (1000 * 60 * 60);

The second thing that you need to know is whether the location observes daylight savings time (DST) or not. Since DST is always observed during the summer, we can compare the time offset between two dates in January, to the time offset between two dates in June. If the offsets are different, then we know that the location observes DST. If the offsets are the same, then we know that the location DOES NOT observe DST.

var june1 = new Date(rightNow.getFullYear(), 6, 1, 0, 0, 0, 0);
temp = june1.toGMTString();
var june2 = new Date(temp.substring(0, temp.lastIndexOf(" ")-1));
var daylight_time_offset = (june1 - june2) / (1000 * 60 * 60);
var dst;
if (std_time_offset == daylight_time_offset) {
    dst = "0"; // daylight savings time is NOT observed
} else {
    dst = "1"; // daylight savings time is observed
}

Once, I had this code written, the next step was to compile a list of the various time zones around the world along with their opinions on DST. I actually ended up using the list of time zones from Microsoft Windows. It was rather time consuming to compile this list, so I hope you can make use of my work to save yourself some time.

Please let me know if you have any comments, questions or problems with this code. As with anything that I post on this blog, feel free to use this code however you want. Just don’t blame me if it breaks.

Update (06/27/07):

My code wasn’t correctly detecting timezones in the lower hemisphere. I have added hemisphere detection for all our Aussie friends out there. I also fixed a bug in the convert() function that was leaving off the + sign at certain offsets. Thanks Val for pointing this out and helping me with the fix.

Update (10/24/08):

Fixed the bug that Rama and Will pointed out in the comments.

Update (12/22/10):

Jon Nylander has taken my original code and written a a more robust solution. Use his version instead.
 129 comments