Tag Archives: jQuery

Getting started with jQuery plugins

jQuery plugins aren’t that difficult to write, but some folks seem to have a hard time understanding how to get started, so I thought I would write a quick blog post about that. I’ll show how to make a pretty simple keyword highlighter which also wraps your pre/code elements in a div with a header span element.

I’ve written a few jQuery plugins before. One of my favorites is jquery.empuzzle. This is a favorite not only because it’s incredibly fun, but because it covers a few areas of jquery plugin development:

  1. default options
  2. merge options
  3. custom selectors
  4. plugin structure

It also covers a particular gotcha when your plugin interacts with images (i.e. images may load after document ready is fired). It’s definitely worth checking it out, but I’d like to show a much simpler plugin here.

The structure

jQuery plugins are commonly written as an *immediate function*. This is creates a closure which executes immediate on the jQuery object itself:

;(function($) {
    // your plugin goes here.
})(jQuery);

I often wrap a jQuery plugin in semicolons as you see above to prevent any automatic semicolon insertion errors. This is something I would consider a ‘best practice’ and I suggest you do the same.

Within this structure, you’ll want to extend jQuery to include your plugin’s code.

$.fn is a pointer to the jQuery prototype object. When you execute a jQuery call like this:

var examples = $('.test-elements');

.. you will receive a jQuery object. This object represents an array of results matching the selector passed to the jQuery function. You can use console.log() from within your plugin function to see how you might interact with this object.

;(function($) {
    $.fn.example = function(arg) {
        console.log(this);
    };
})(jQuery);

You now have the start to a plugin called ‘example’. You can execute this ‘plugin’ in the normal way.

var examples = $('.test-elements');
examples.example();

// same as: $('.test-elements').example();

Now, you’ll want to iterate over each object found by the query selector and perform whatever functionality your plugin will provide. Let’s define some functionality so we can have a (somewhat) useful plugin. We’ll keep with the ‘example’ theme and write a plugin which stylizes a <code> tag and adds a small box in the top-left corner which says ‘Example’. (I got this idea from Twitter’s Bootstrap framework, see here). We’ll also ‘highlight’ a few keywords within our example.

Here’s what we want our final product to look like (after some hideous styling, of course):

For the goal screenshot, I’ve used Google’s prettify plugin, which is licensed under Apache 2.0. I don’t use this in the final product.

There will be a few things we’ll need to do in this plugin:

  1. Merge settings specified by the user with our default settings
  2. Wrap the code block in a styled ‘example’ box
  3. “parse” the contents for a select set of keywords
  4. Wrap any found keywords with a style span

For the parsing bit, I’ll just use a global RegEx replace on the contents of the code element.

Providing defaults

Providing defaults in a jQuery plugin and allowing a user to pass options to merge isn’t difficult. Your plugin function will accept a parameter I like to call ‘opts’ and return a `this.each` function. Within the `.each` function, you’ll extend the defaults object with those options passed into your plugin function and store the merged options into a new object called ‘options’. A very simple example which writes out to console.log might look like this:

;(function($) {
    var defaults = {
        arr: [1,2,3,4,5,6],
        sample: "sample",
        style: "style"
    };

    var example = function(opts) {
        return this.each(function() {
            // merge opts with defaults into new object
            // so changes don't change defaults
            var options = $.extend({}, defaults, opts);
            console.log({ 
                elem: this.innerText,
                options: options
            });
        });
    };

    $.fn.example = example;
})(jQuery);

Here are some options we might want to allow for the example plugin:

  1. A class name for the box and a class name for the label
  2. An object mapping keywords to class names.
  3. A parse error callback
  4. A data attribute to specify a different ‘Example’ box text (so each element can define its own label)

Our defaults object will look like this (more or less):

    var defaults = {
        boxCss: "example-box",
	labelCss: "example-label",
        keywords: { "function":"blue", "this":"blue", "jQuery":"red" },
        onError: function() { },
        exampleAttr: ""
    };

Wrapping our targets

To make our lives simple, we’ll first wrap the target element with a div for our example box. Then, we’ll add a span just before the code block. We can improve performance by creating the wrapper div’s HTML outside of the ‘.each’ function. Because we’re allowing the option of pulling the labeling span’s text from the code element itself, we’ll have to build that HTML within the ‘.each’ function.

Here’s what we have so far.

;(function($) {
    var defaults = {
        boxCss: "example-box",
        labelCss: "example-label",
        keywords: { "function":"blue", "this":"blue", "jQuery":"red" },
        onError: function() { },
        exampleAttr: ""
    };

    var example = function(opts) {
        var options = $.extend({}, defaults, opts);
        
        var wrapperHtml = [
            '<div class="',
            options.boxCss,
            '"></div>'
        ].join('');

        var labelArr = [
            '<span class="',
            options.labelCss,
            '">',
            "Example",
            '</span>'
        ];

        return this.each(function() {
            var labelText = "Example";
            if(options.exampleAttr) {
                labelText = $(this).attr(options.exampleAttr);
            }

            var labelHtml = [
                '<span class="',
                options.labelCss,
                '">',
                labelText,
                '</span>'
            ].join('');

            $(this).wrap(wrapperHtml);
            $(this).before(labelHtml);
        });
    };

    $.fn.example = example;
})(jQuery);

onError function

The onError function we allow in the options object does nothing more than provide a message if we don’t have a code element to update with keyword highlights. We will supply a message to the callback function and return from the current iteration of ‘.each’. This goes at the beginning of the ‘this.each’ function.

// Find node for replacing text.
var replacementNode = $(this).children('code').andSelf().filter('code');

if(replacementNode.length == 0) {
    if(typeof options.onError === "function"){
        options.onError("No code nodes found");
    }
    
    return;
}

With the combination of children/andSelf/filter in the above code, we allow the plugin to operate on both ‘pre’ and ‘code’ elements.

When allowing users to pass functions as callbacks or to provide added functionality to a plugin, *always* check that it is a function.

“Parsing” contents

In the interest of saving some time, I’m going to use a regular expression to replace text within the code block. This isn’t necessarily the most efficient way to achieve a budget syntax highlighter, but it will work.

To do this, we’ll need to grab the text from our code node in which we’ll replace keywords. Then, for every keyword in the hash of keywords-to-classes, we’ll create a span to represent the highlighted keyword. Then, we’ll do a search and replace using a global regular expression object, substituting each found keyword with the keyword wrapped in a span element. This goes at the end of the ‘this.each’ function.

    var originalText = replacementNode.text();
            
    Object.keys(options.keywords).forEach(function(key,idx){
        var replacement = [
            '<span class="',
            options.keywords[key],
            '">',
            key,
            '</span>'
        ].join('');
        
        var re = new RegExp(key,"g");
        originalText = originalText.replace(re, replacement);
    });

The only thing left to do is to add styles to your document and you’re all set with a customized syntax highlighter!

There are a few issues with this simple implementation. First, keywords don’t get merged (I’ll leave that as an exercise for you). Second, this only highlights keywords. It doesn’t provide regex matching for full-text highlights. In other words, this won’t highlight comments.

If you’re unfamiliar with jQuery plugins, or you’re planning to begin writing a keyword highlighter plugin, this should at least get you started!

Try it out!

Check out the jsfiddle.

Get the code

The code for this blog post is available on Github.

Flattr this!

Posting complex models to ASP.NET MVC

Nick Riggs wrote a nice little jQuery plugin called postify that I have found extremely handy on many occasions. The code builds posted data into a format which is more understandable to the default ModelBinder in ASP.NET MVC.

Check out his post for details.

Here is Nick Riggs code for $.postify:

// postify.js
// Converts an object to an ASP.NET MVC  model-binding-friendly format
// Author: Nick Riggs
// http://www.nickriggs.com

$.postify = function(value) {
    var result = {};

    var buildResult = function(object, prefix) {
        for (var key in object) {

            var postKey = isFinite(key)
                ? (prefix != "" ? prefix : "") + "[" + key + "]"
                : (prefix != "" ? prefix + "." : "") + key;

            switch (typeof (object[key])) {
                case "number": case "string": case "boolean":
                    result[postKey] = object[key];
                    break;

                case "object":
                    if (object[key].toUTCString)
                        result[postKey] = object[key].toUTCString().replace("UTC", "GMT");
                    else {
                        buildResult(object[key], postKey != "" ? postKey : key);
                    }
            }
        }
    };

    buildResult(value, "");

    return result;
};

Flattr this!

jquery.empuzzle @github

For the past few days, I’ve been writing this plugin called jquery.empuzzle. It was inspired by ‘Jigsy’ at cityposh.com.

It is a basic N-puzzle

The syntax is as simple as calling the plugin on a single image.

    $('#second').empuzzle();

You can even get a little more into it and provide quite a few options.

$(function() {
   $('img').empuzzle({ 
        size: 4,
        target: $('#target'),
        blank: 'BR',
        randomize: function(game, defaultRandomizer) {
            defaultRandomizer.call(this, game);
        }, 
        win: function(game) { 
            alert("You're a winner!"); 
        },
        anim: { 
            duration: 200, 
            complete: function() { console.log('Move completed!'); }
        },
        DEBUG: true
   });
});

You could easily add a move counter in the anim.complete function. That function is basically the complete function that normally gets passed to jquery.animate(). I’ve had to curry the anim.complete function so the normal complete function performs the necessary internal tasks.

Enough about all that, check it out!

Flattr this!

Loading newer versions of jQuery and jQuery UI (noConflict)

Here’s an interesting problem:

You have an ASP.NET Web Forms application which references jQuery 1.3.x and would require a lot of testing to upgrade to a newer version of jQuery. You’re adding functionality to this application, and you really want to use jQuery 1.5 or jQuery 1.6 going forward with new development. But…, jQuery 1.3.x is referenced in the master page. Also, you want to load jQuery UI 1.8.x targeting the newer version of jQuery.

Continue reading Loading newer versions of jQuery and jQuery UI (noConflict)

Flattr this!

jQuery Pocket Reference by David Flanagan

jQuery Pocket Reference by David Flanagan
jQuery Pocket Reference Image

I’ve enjoyed previous books by David Flanagan and decided to read jQuery Pocket Reference. I thought I would quickly skim through the chapters because I considered myself fairly proficient in jQuery. After the first chapter and Flanagan’s explanations of jQuery’s method, object, and function (‘a’ versus ‘the’), I decided to read more in-depth. I’m glad, because this is one of the best books I’ve read in O’Reilly’s Pocket Reference library. I was surprised to have found a one which has a perfect balance between API, examples, and explanation.

For developers who want to learn jQuery, you will be able to learn nearly all you need to get started from this book. When I first heard about jQuery, I purchased a much larger book, which ended up being about 80% reprinting the API on jquery.com. If you’re like me, and you prefer insight, hints, and gotchas which encourage you to write some code, then this book is perfect for you.

For developers familiar with jQuery, you may learn a little from this book. Flanagan covers a lot of overloads to common jQuery functions. Some of them, I never knew existed. The recent release of jQuery 1.5 has actually added more functionality than what is covered in this book.

The only thing I found a little odd about this book is how the jQuery Selectors chapter was at the end of the book. Considering jQuery is a framework for querying the DOM, using selectors, I would expect that content to be the first covered. On the other hand, as a reference, you may expect the most used content at the end of the book. Luckily, Flanagan knows what he’s doing and tells you to review the Selectors chapter if you’re rusty or unfamiliar.

Flattr this!

Fairly Accurate JavaScript Browser Detection

I use jQuery for almost everything I do in JavaScript. The only real problem is that jQuery’s browser name and version detection doesn’t provide exactly what I want.

For instance, if I’m using Google Chrome 6.0.427.0, I wanted a script that would say “Hey, you’re using Chrome 6.0.427.0”.

I found such a script online and modified it to be a javascript object, so I thought I’d share. I didn’t really do much to modify this code other than changing the comment style and a couple of other things. Below the code, there are link to download.

*browser_detection.js*

/**
 * Modifed from the source at http://www.javascripter.net/faq/browsern.htm
 */

function BrowserDetector()
{
  this.init();
}

BrowserDetector.prototype =
{
  nVer: navigator.appVersion,
  nAgt: navigator.userAgent,
  browserName: navigator.appName,
  fullVersion: '' + parseFloat(navigator.appVersion),
  majorVersion: parseInt(navigator.appVersion, 10),
  nameOffset: null,
  verOffset: null,
  ix: null,
  init: function ()
  { /* In MSIE, the true version is after "MSIE" in userAgent */
    if ((this.verOffset = this.nAgt.indexOf("MSIE")) != -1)
    {
      this.browserName = "Microsoft Internet Explorer";
      this.fullVersion = this.nAgt.substring(this.verOffset + 5);
    } /* In Opera, the true version is after "Opera" */
    else if ((this.verOffset = this.nAgt.indexOf("Opera")) != -1)
    {
      this.browserName = "Opera";
      this.fullVersion = this.nAgt.substring(this.verOffset + 6);
    } /* In Chrome, the true version is after "Chrome" */
    else if ((this.verOffset = this.nAgt.indexOf("Chrome")) != -1)
    {
      this.browserName = "Chrome";
      this.fullVersion = this.nAgt.substring(this.verOffset + 7);
    } /* In Safari, the true version is after "Safari" */
    else if ((this.verOffset = this.nAgt.indexOf("Safari")) != -1)
    {
      this.browserName = "Safari";
      this.fullVersion = this.nAgt.substring(this.verOffset + 7);
    } /* In Firefox, the true version is after "Firefox" */
    else if ((this.verOffset = this.nAgt.indexOf("Firefox")) != -1)
    {
      this.browserName = "Firefox";
      this.fullVersion = this.nAgt.substring(this.verOffset + 8);
    } /* In most other browsers, "name/version" is at the end of userAgent */
    else if ((this.nameOffset = this.nAgt.lastIndexOf(' ') + 1) < (this.verOffset = this.nAgt.lastIndexOf('/')))
    {
      this.browserName = this.nAgt.substring(this.nameOffset, this.verOffset);
      this.fullVersion = this.nAgt.substring(this.verOffset + 1);
      if (this.browserName.toLowerCase() == this.browserName.toUpperCase())
      {
        this.browserName = navigator.appName;
      }
    } /* trim the fullVersion string at semicolon/space if present */
    if ((this.ix = this.fullVersion.indexOf(";")) != -1)
    {
      this.fullVersion = this.fullVersion.substring(0, this.ix);
    }
    if ((this.ix = this.fullVersion.indexOf(" ")) != -1)
    {
      this.fullVersion = this.fullVersion.substring(0, this.ix);
    }

    this.majorVersion = parseInt('' + this.fullVersion, 10);
    if (isNaN(this.majorVersion))
    {
      this.fullVersion = '' + parseFloat(navigator.appVersion);
      this.majorVersion = parseInt(navigator.appVersion, 10);
    }
  }
}

Say you have two DOM elements, called *brower_name* and *browser_version*, you can use this script this way (using jQuery):

 
	jQuery(document).ready(function($){
		var browser_name = $('#browser_name');
		var browser_version = $('#browser_version');
		
		var browser = new BrowserDetector();
		browser_name.text(browser.browserName);
		browser_version.text(browser.fullVersion);
	});

Below are the files:
browser_detection.js [2.6 KB (2670 bytes)]
browser_detection.min.js [1.7 KB (1758 bytes)]
browser_detection.packed.js [1.2 KB (1263 bytes)]

Flattr this!

jQuery check/uncheck all Checkboxes in ASP.NET

For some odd reason, ASP.NET GridView wraps html elements in a span element. When you apply a CssClass to the asp:CheckBox for instance, you will have a span with that class and inside you’ll have an input with type=checkbox.

So how do you access all of the checkboxes?

     $('.chkAllSelector > input:checkbox').click(function() {
         $('.chkRowSelector > input:checkbox').each(
             function(){
                 this.checked = $('.chkAllSelector > input:checkbox').attr('checked');
             }
         );
     });
     $('.chkRowSelector > input:checkbox').click(function() {
     if ($('.chkAllSelector > input:checkbox').is(':checked') && !($(this).is(':checked')))
          { $('.chkAllSelector > input:checkbox').removeAttr('checked'); }
      });

Flattr this!