Archive for the ‘JavaScript’ Category

SelectList 0.6

Tuesday, February 19th, 2013

I released a new version of selectList, the multiple selection jQuery plugin. This version introduces jQuery 1.9 support, and you can get it from the project homepage, jQuery plugins (did I mention I’m happy the plugins site is back?), or from GitHub.

By the way, this release is the first one that I made with Grunt, the JavaScript build tool. My previous, homebrew solution for building jQuery plugins involved shell, Perl scripts, and Java, all duct taped together with a Makefile — and was exactly as horrible as it sounds. Grunt made the build process a lot more straightforward, and, hey, it’s all JavaScript now! If you’re a JavaScript developer, I wholeheartedly recommend you give Grunt a shot.

JQuery valHooks

Friday, August 24th, 2012

In selectList version 0.5, I made use of an interesting feature of jQuery, called valHooks. It’s used internally in jQuery (since version 1.6) and there’s not much documentation on it, but it can be very useful for plugin authors.

Valhooks allow you to change the way the jQuery method .val() works for certain elements, giving you control over how element values are set and retrieved. You define a function and assign it to a specific element type, and whenever .val() is called on that element type, your function is invoked to process the call.

Let’s take a look at an example — the code below is a very simple jQuery plugin that makes use of a get valhook to convert the value of an input field before it is retrieved. In practical terms, it allows you to enter an expression representing the number of bytes, with the ability to parse quantifiers like “kB” for kilobytes or “MB” for megabytes, and automatically translate them into bytes. So, “1k” or “1kB” becomes “1024″, “1M” or “1MB” becomes “1048576″, and so on.

(function ($) {
    // jQuery plugin definition
    $.fn.bytesInput = function () {
        $(this).filter('input[type="text"]').each(function() {
            $(this).data('bytesInput', true);
        return this;

    var origHook;

    // There might already be valhooks for the "text" type
    if ($.valHooks.text)
        // Preserve the original valhook function
        origHook = $.valHooks.text.get;
        // Make room for a new valhook
        $.valHooks.text = {};

    $.valHooks.text.get = function (el) {
        // Does this element have our data field?
        if (!$(el).data('bytesInput'))
            // No -- check if there was a valhook function already defined
            if (origHook)
                // There was, so go ahead and call it
                return origHook(el);
                // No previous function, return undefined to have jQuery
                // take care of retrieving the value
                return undefined;

        // Try parsing the expression
        if (matches = el.value.match(/(\d+)\s*([kKmMgG]?)[bB]?/)) {
            switch (matches[2].toLowerCase()) {
                case 'k':
                    // Kilobytes
                    return matches[1] * 1024;
                case 'm':
                    // Megabytes
                    return matches[1] * 1048576;
                case 'g':
                    // Gigabytes
                    return matches[1] * 1073741824;
                    // Just bytes
                    return matches[1];
            // Can't parse the expression -- just return the current value
            return el.value;

The example is pretty straightforward, and if you’re familiar with how jQuery plugins work, you should have no trouble understanding it. One specific thing that might be worth pointing out is how the .data() method is used to distinguish the special input field from other inputs (for which the .val() method should work the usual way). The plugin initialization function attaches data to the element at the "bytesInput" key — the get valhook function then checks for the presence of that key, and only does its thing if the key is there. Otherwise, it either falls back to the original valhook function (if there was one), or returns undefined, which makes jQuery process it in the traditional manner. This way the plugin can be used with other plugins or pieces of code that set their own valhooks for input elements, and not interfere with them.

If you’d like to see the above example in action, I put it up on jsFiddle.

Decrypting the User Agent String in JavaScript

Friday, September 10th, 2010

I test all my jQuery plugin releases in a simple testing environment that I developed. It’s nothing sophisticated, but it allows me to do much of the testing automatically and generates nice reports. The results of the tests are saved in a database, and for each test there is (among other stuff) information about which plugin version was tested, with which jQuery version, and on what browser.

For that purpose, I wanted to extract the browser name and version number from the user agent string that browsers use to reveal their identity to web pages. However, it’s not that simple, since there is no common format of the user agent string and each browser vendor seems to have their own idea of what to put in it.

For example — which version of Opera is this?

Opera/9.80 (X11; Linux i686; U; en) Presto/2.2.15 Version/10.10

Is it 9.80, or 10.10? If you’re curious, it’s 10.10, and this Dev.Opera blog post explains it.

(If you’re even more curious about why this user agent string business is such a mess, I recommend you read this story — apart from being informative, it’s also funny.)

Anyway, for my testing platform I wrote a JavaScript function that extracts the browser name and version (by default just the major and minor release number) from the user agent string. I’m posting it here in case someone finds it useful too. It recognizes the “big five” browsers that have a significant market share (Firefox, Internet Explorer, Opera, Chrome, and Safari), as these are the browsers that I test my plugins on.

Here’s the source code:

 * Extracts the browser name and version number from user agent string.
 * @param userAgent
 *            The user agent string to parse. If not specified, the contents of
 *            navigator.userAgent are parsed.
 * @param elements
 *            How many elements of the version number should be returned. A
 *            value of 0 means the whole version. If not specified, defaults to
 *            2 (major and minor release number).
 * @return A string containing the browser name and version number, or null if
 *         the user agent string is unknown.
function identifyBrowser(userAgent, elements) {
    var regexps = {
            'Chrome': [ /Chrome\/(\S+)/ ],
            'Firefox': [ /Firefox\/(\S+)/ ],
            'MSIE': [ /MSIE (\S+);/ ],
            'Opera': [
                /Opera\/.*?Version\/(\S+)/,     /* Opera 10 */
                /Opera\/(\S+)/                  /* Opera 9 and older */
            'Safari': [ /Version\/(\S+).*?Safari\// ]
        re, m, browser, version;

    if (userAgent === undefined)
        userAgent = navigator.userAgent;

    if (elements === undefined)
        elements = 2;
    else if (elements === 0)
        elements = 1337;

    for (browser in regexps)
        while (re = regexps[browser].shift())
            if (m = userAgent.match(re)) {
                version = (m[1].match(new RegExp('[^.]+(?:\.[^.]+){0,' + --elements + '}')))[0];
                return browser + ' ' + version;

    return null;

A few examples of user agent strings and the returned results:

Mozilla/5.0 (X11; U; Linux i686; en-US; rv: Gecko/20100825 Ubuntu/9.10 (karmic) Firefox/3.6.9 Firefox 3.6 Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.19 Safari/533.4 Chrome 5.0 Opera/9.80 (X11; Linux i686; U; en) Presto/2.2.15 Version/10.10 Opera 10.10 Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0) MSIE 8.0

  • Archives

  • Categories

  • Meta

  • Latest Tweets

    Warning: Illegal string offset 'last_access' in /usr/local/www/ on line 334

    Warning: Illegal string offset 'time_limit' in /usr/local/www/ on line 334

    Warning: Illegal string offset 'last_access' in /usr/local/www/ on line 336

    Warning: Illegal string offset 'twitter_api' in /usr/local/www/ on line 234

    Warning: Illegal string offset 'user_token' in /usr/local/www/ on line 262

    Warning: Illegal string offset 'user_secret' in /usr/local/www/ on line 263

    Warning: Illegal string offset 'consumer_key' in /usr/local/www/ on line 264

    Warning: Illegal string offset 'consumer_secret' in /usr/local/www/ on line 265

    Warning: Illegal string offset 'twitter_username' in /usr/local/www/ on line 270

    Warning: Illegal string offset 'show_retweets' in /usr/local/www/ on line 272

    Warning: Illegal string offset 'exclude_replies' in /usr/local/www/ on line 275

    Warning: Illegal string offset 'twitter_data' in /usr/local/www/ on line 282

    Warning: Illegal string offset 'twitter_data' in /usr/local/www/ on line 350

    Warning: Illegal string offset 'twitter_data' in /usr/local/www/ on line 351
    Twitter outputted an error:
    Warning: Illegal string offset 'time_format' in /usr/local/www/ on line 484
  • Follow odyniec on Twitter