Starter Template for Creating a jQuery Plugin

18. January 2012

Every time I needed to create a jQuery plugin, I would debate with myself on how to structure the code. It occurred to me that it would be helpful to stub out a sample plugin template that I could grab and jump right into coding.

After looking around at various jQuery plugin patterns, I came up with the template at the end of this post. I think it has a nice structure, but would enjoy hearing any suggestions. The one thing I may add to this in the future is the ability to programmatically uninstall the plugin.

Here are some samples of how you might invoke the plugin and then call it's API.

// Install the plugin on all matching elements
$("div.myPlugin").myPlugin();

// Install the plugin and override a default setting
$("div.myPlugin").myPlugin({
    playPauseSelector:'#playbutton'
});

// Control multiple instances at once 
//  and also maintain the fluent API using
//  the jQuery plugin function to call the API
$("div.myPlugin").myPlugin("play", 2); 
$("div.myPlugin").myPlugin("play", 2).myPlugin("pause"); 

// Control a single instance by accessing the attached API
// Only the officially supported API methods can be called
$("div.myPlugin").get(0).MyPlugin.play(2); 

Here is the code:

NOTE: Some parts of the code are included only to illustrate aspects of the structure. See the code comments for more explanation.

(function ($) {

    // Create the plugin jQuery function
    $.fn.myPlugin = function (optionsOrCommand, param) {

        // Setup the default options
        var defaultOptions = {
            playPauseSelector: '.MyPluginPlayPauseButton',
            playingClass: 'playing',
            pausedClass: 'paused'
        };
        
        // Define a function to setup the plugin instance
        var create = function (root, options) {
 
            // Grab a reference to the root DOM element that is the target of the plugin
            var _root = $(root);
            var _playPauseButton = null;

            // Update the settings with any overrides
            var _opts = $.extend({}, defaultOptions, (options || {}));
            
            // Create internal helper methods
            var _play = function(idx) {
                _playPauseButton.removeClass(pausedClass).addClass(playingClass);
            };
            
            var _pause = function() {
                _playPauseButton.removeClass(playingClass).addClass(pausedClass);
            };
            
            // Define an init function to kick off the plugin functionality
            var _init = function () {
                _playPauseButton = $(playPauseSelector, _root);
                _playPauseButton.bind('click', function() {
                    var self = $(this);
                    if (self.hasClass(playingClass)) {
                        _pause();
                    } else {
                        _play();
                    }
                });
            };
            
            // Return an object containing a reference to the API and the init method
            // We don't want the init in the API so it is separated here
            return {
                controller: {
                    init: function () {
                        _init();
                    }
                },
                api: {
                    pause: function () {
                        _pause();
                    },
                    play: function (idx) {
                        _play(idx);
                    }
                }
            };
        };
        
        // Cycle through each matching element and return them  
        // so as not to break the jQuery fluent API
        return this.each(function () {

            // Grab a reference to the root element
            var root = this;

            // Determine whether or not the plugin is already installed
            var exists = !(root.MyPlugin === undefined || root.MyPlugin === null);

            // Initialize the optionsOrCommand so we can handle either input
            if (optionsOrCommand === undefined || optionsOrCommand === null) {
                optionsOrCommand = {};
            }

            // If the optionsOrCommand is a string then map it to the API
            if (optionsOrCommand.constructor == String) {
                if (exists) {
                    switch (optionsOrCommand) {
                        case 'pause':
                        case 'play':
                            if (typeof root.MyPlugin[optionsOrCommand] === 'function') {
                                    root.MyPlugin[optionsOrCommand](param);
                            }
                            break;
                        default:
                            return;
                    }
                }
                return;
            }

            // If the plugin is not installed then create it and apply the API
            // to the DOM element.
            if (!exists) {
                var plugin = create(root, optionsOrCommand);
                root.MyPlugin = plugin.api;
                plugin.controller.init();
            }
        });
    };
})(jQuery);

Here are some links to other interesting plugin patterns:
JQUERY GENERATOR BY Kees C. Bakker
Creating a jQuery plugin
Keeping the plugin and jQuery separate

jQuery, Programming , ,

A jQuery action plugin to insert a custom function into the method chain

19. May 2010

I was hanging out at the #jquery IRC Channel through freenode Web IRC and a user wanted to have a callback function on the removeClass method.  This seems like a strange request since the removeClass method is a synchronous call and returns the list of jQuery items which were manipulated.

The problem could be solved as follows:

var items = $(".target").removeClass("target");
doSomething();
items.addClass("newTarget");

However the user was not satisfied with this option, so I suggested adding a callback to the removeClass method like so:

(function($) {
  var oldRemoveClass = $.fn.removeClass;
  $.fn.removeClass = function(value, callback) {
    var ret = oldRemoveClass.call(this, value);
    if ($.isFunction(callback)) {
      callback.call(ret);
    }
    return ret;
  }
})(jQuery);

NOTE: A sample is available on JS Bin.

However, after some consideration, I thought of a another option (again, of questionable value, but hey...) Why not add a jQuery function that takes a callback and returns the fluent context. The code would be quite similar to the above but would apply more broadly.

Plugin code

/*Final plugin*/
(function($) {
  $.fn.action = function(callback) {
    var ret = this;
    if ($.isFunction(callback)) {
      callback.call(ret);
    }
    return ret;
  }
})(jQuery);

Now with the new function in place the user can string a custom function into the method chain. The original solution would be refactored to look like this.

$(".target").removeClass("target").action(doSomething).addClass("newTarget");

The action plugin could allow the callback to filter or replace the item context, but I think that would hurt readability and lead to hidden bugs.

jQuery, Programming , , ,

Moved blog from Blogger to BlogEngine.NET

19. May 2010

I managed my old blog with Blogger but the FTP deployment process was kind-of-a pain. Google agreed and stopped supporting it.  At the time, I was going to switch to a .NET blog solution, but I didn't love any of the options out there and was too lazy to copy over the old posts manually.  Additionally, Twitter was gaining momentum and seemed to fit with the limited amount I had to say.

After a while I started having ideas for longer posts and wanted to put them on the blog, but I felt I had to upgrade first.  Needless to say that didn't happen. 

When BlogEngine.Net came along I wanted to import my posts with their BlogML import helper but for some reason I couldn't get it working. Recently, I tried it again and the current version (1.6.1) worked like a charm.  I have all my old posts and I am ready to put up some content.

I'd like to thank Aaron Lerch for his PowerShell script which I used to extract my BlogML from Blogger.

Script: BloggerToBlogML.ps1
Website: http://www.aaronlerch.com/blog/
License: Public Domain

The PowerShell script relies on the blogML .NET library which can be downloaded from Codeplex.

Programming , , ,

RC4 encryption for classic ASP stops working

7. May 2008

This was an interesting gotcha which I'd like to blame on Visual Studio 2008, but I supposed it's not entirely deserved.

Microsoft dropped support for Intellisense and color-coding for classic ASP in Visual Studio 2008, but I still use it to maintain some old sites.

Well, after some minor changes to an old site, suddenly, the RC4 encryption code, by Mike Shaffer, that I was using didn't work any more. The answer really surprised me.

To make a LONG story short, I opened the site in VS08 and created a new include file for a page I was modifying. I included the file into the primary page and that caused the RC4 code to stop producing the correct output.

Visual Studio 2008 created the new include file using UTF-8 encoding. Well, for some reason that broke the RC4 encryption code. I guess it's because each character would be 2 bytes instead of 1? I thought that was just Unicode and classic ASP won't even process a Unicode file (UNICODE ASP files are not supported). I don't know.

My resolution was to make sure the classic ASP page and all its includes were saved as ANSI text. Here Notepad++ (Menu > Format > Encode in ANSI) came in very handy again.

Maybe someday I will understand The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character. Scott Hanselman also has an interesting post on the CodePage versus the CharSet in classic ASP.

, , ,

Microsoft Drops Classic ASP Support in Visual Studio 2008

7. May 2008

I upgraded to Visual Studio 2008 as soon as it came out. I was tempted to get rid of Visual Studio 2005 since VS08 had multi-targeting support and I assumed that the new Visual Studio had all the functionality of previous versions. Well, I was wrong.

For better or worse, I still have to support some Classic ASP and VBScript code, well Microsoft dropped Intellisense and color-coding for classic ASP. Recommended solution: Keep my 2.5 GB install of VS05 around for those days when you need it.

I can't believe it myself. How detrimental or hard was it to maintain even just basic syntax highlighting? Even an open source product like Notepad++ offers that. Wow.

I recently found another gotcha which I will include in another post.

UDPATE [5/12/2008]: Looks like VS08 SP1 will bring back support for classic ASP. Cool!

, ,

HttpFileCollection Always Zero

18. January 2008
I ran into and interesting problem recently trying to get an AJAX style file upload working with an HttpHandler. The HttpFileCollection (Request.Files) was always empty with a count of zero.
(Request.Files == 0) == true

I was using the AjaxFileUpload, a jQuery plug-in intended to be paired with PHP, but compatible with any server side handler. Since I use ASP.NET, I wrote an HttpHandler to receive the form post, save the file on the server, and send a JSON response.

Without investigating too closely, it looks like the AjaxFileUpload creates an iframe, moves the html file input into the iframe and submits it to specified handler. I didn't want to user "runat='server'" controls so I just put a plain html input tag in the page like so.

<input type="file" id="AjaxFileInput" />

I ran my project and everything seemed to be working and submitting, fine, but the HttpFileCollection always had a count of 0.

I created a simple page with no ajax that posted back to itself to test the plain HTML inputs, and the Request.Files collection was still empty. I tried adding the "runat='server'" attribute and it suddenly worked, of course. I didn't want to user server controls, because I wanted to add new inputs as needed with client script.

It seemed like the Request.Files collection would only be populated if I used the ASP.NET server controls, but I could not think of any reason why. Finally, I looked at the code rendered by the server controls and discovered the answer.

Every site I found said there were 2 things you needed to get the HttpFileCollection to populate.

1) Make sure the form method is "POST"
2) Make sure the form enctype is "multipart/form-data"

The AjaxFileUpload code dutifully had this right, however there is one more thing that ASP.NET needs.

3) The HTML file input must have a "name" attribute.

Here is a final sample of an accurately constructed plain HTML form that will populate the Request.Files collection.

<form action="" method="POST" name="form1" id="form1" enctype="multipart/form-data">
    <input type="file" id="AjaxFileInput" name="AjaxFileInput" />
</form>


This seems like a bug, to me, and I don't know if .NET 3.0-5 fixes this. This document does not reflect what I have found to be true.

Print Page from IE without Prompt

31. October 2007
This code should send a page straight to the printer without prompting the user. It worked in IE6, but I don't know about IE7. I am sure there are some security concerns with it.


<html>
<head>
<script language="javascript">
    function ieExecWB(intOLEcmd, intOLEparam)
    {
        var WebBrowser = '<OBJECT ID="PrintBrowser" WIDTH=0 HEIGHT=0 CLASSID="CLSID:8856F961-340A-11D0-A96B-00C04FD705A2"></OBJECT>';
        document.body.insertAdjacentHTML('beforeEnd', WebBrowser);
        if ( ( ! intOLEparam ) || ( intOLEparam < -1 )  || ( intOLEparam > 1 ) )
        { 
            intOLEparam = 1;
        }
        PrintBrowser.ExecWB(intOLEcmd, intOLEparam);
        PrintBrowser.outerHTML = "";
    }
</script>
</head>
<body>

<p><a href="javascript:ieExecWB(6, -1);">print</a></p>

</body>
</html>

NOTE: I don't know where I found this code so if someone needs attribution or it violates a copyright or something, let me know.

Optional Radio Buttons

11. August 2007

It has always bugged me that you can't really make radio buttons optional on web form. You can load all the radio buttons in a group as un-checked, but as soon as the user selects one then a value from that check box group will be submitted.

You can get around this problem by adding one option to the group and labeling it "N/A" or something. This gives the user an out. Incredibly, I have never been able to convince any of my customers to go that route. In fact, even if the radio group is required they will want the all of the options unchecked initially.

Most people get around this by writing their own logic over check boxes, but I decided to take a different approach. I finally wrote some JavaScript which will un-check a radio button if the button you click is already checked, thus allowing the user an escape route.


function RadioClick(radio) {
var inputs = document.getElementsByTagName("input");
var checked = (radio.getAttribute("shadow") == "true");
for(var i = 0; i < inputs.length; i++) {
if (inputs[i].getAttribute("type") == "radio" && inputs[i].name == radio.name) {
inputs[i].setAttribute("shadow", "false");
}
}
if (checked) {
radio.checked = false;
} else {
radio.setAttribute("shadow", "true");
}
}

All you have to do is add this script to your page and then just add "RadioClick" to the radio button's onClick event, like so...


<input id="ctl01" type="radio" name="GroupName" value="ctl01"
checked="checked" onclick="javascript:RadioClick(this);" />


You could easily make this script unobtrusive by adding a method that assigns onClick event by group name when the page loads.

DataFormatString Doesn't Work

14. October 2006
I think this might be old news, but it was new to me. If you are using a GridView (and potentially any DataBoundColumn) and you take advantage of the DataFormatString you will be in for a surprise--it may not work.

[Here is the "bug" report]

You must also set the HtmlEncode attribute to false. The explanation in the bug report doesn't make sense to me though. So what if the bound value is HTML encoded. Can't the format string still be applied? I guess if the datatype is changed to string then special format strings like "{0:C}" for currency or "{0:MM/dd/YYYY}" for DateTime won't work.

Generic HttpContext Singleton

6. September 2006

Joel Ross has an interesting post about implementing the singleton pattern for objects used during the lifecycle of a web request.

This got me thinking about the Generic singleton implementation that I have used before. I wondered if you could combine the two to make a Generic HttpContext singleton. Here is my first attempt. I think it should work, but I have not tried it.

I guess the key is the "key". It may be better to use the fully qualified class name for the key instead of just using the generic type's name. It may also make sense to add a "Singleton_" prefix to the name to give it a namespace within the HttpContext.

using System;
using System.Collections.Generic;
using System.Web;
using System.Runtime.Serialization;

namespace jedatu
{
    public class ContextSingleton where T : ISerializable, new()
    {
        protected ContextSingleton() { }

        public static T Instance
        {
            get { return Factory.Current.Instance; }
        }

        private class Factory
        {
            static Factory() { }

            internal static Factory Current = new Factory();

            internal static readonly T _instance = new T();

            internal T Instance
            {
                get
                {
                    if (HttpContext.Current == null) { return null; }
                    if (HttpContext.Current.Items[_instance.GetType().Name] == null)
                    {
                        HttpContext.Current.Items[_instance.GetType().Name] = _instance;
                    }
                    return (T)HttpContext.Current.Items[_instance.GetType().Name];
                }
            }
        }
    }
}