Posterous theme by Cory Watilo

Filed under: JavaScript

Yet Another jQuery Validation Plugin (this time with tinyness)

I've used a number of pure JavaScript libraries and jQuery plugins to handle client-side validation over the years but I never really liked any of them.  As I often do when I can't find exactly what I want, I built a new one.  

There are certainly libraries out there that cover a broader set of features, but my goal here was to make it dirt simple.  So simple in fact that anyone should be able to read the source and in a few minutes figure out how to make it conform to novel requirements.  The entire framework is shown here in the following gist:

As you can see it takes up just over 100 lines and offers out-of-the-box support for a number of standard validation patterns.  However, additional validator methods can be added via the options object.  Below is a usage sample that will make things more clear:

In the above example you can see that adding validation to any input element is as simple as defining a "data-validators" attribute to the element.  The value of this attribute is essentially a JSON string with some syntactic sugar to make it easier on the eyes.  For example, you don't have to surround the whole string with curly braces and if you are defining a validator such as "required" but have no additional options to specify you don't have to do "required:{}" for the JSON to be valid.  If you look at the "expandValidatorString" method in the source you can see that these elements are added dynamically before the JSON is evaluated.

At some point I'll put this up as a repository with more complete documentation but I wanted to get it out there for some feedback before that.  Let me know what you think.

jQuery.ganttView a Lightweight Gantt Chart for jQuery

I just wanted to drop a quick post about a new jQuery plugin that I've been working on for a recent project. As the title of the post implies it renders a simple HTML based Gantt chart using jQuery and the excellent date.js library. The screenshot below should give you an idea of what can be done with the plugin.
Media_httpwwwthegrubb_jfagg

The current version is fairly stable and has been tested in FireFox 3.6, Safari 4, Chrome 5, and IE8 and works well in all those browsers. There are likely a few issues with it in IE7 and I wouldn't even bother checking IE6, unless someone sends me a patch I have no interest in supporting IE6.
The source code and documentation can be found on GitHub: http://github.com/thegrubbsian/jquery.ganttView
Let me know what you think and if there are any specific features you'd like to see added.

jQuery Plugin: tablePager

Here's a quick table pager plugin for jQuery. Unlike a number of the other plugins out there, this one doesn't alter the DOM tree of the table itself it just hides the rows that are not available for the current page. In this way you can still get access to all the elements of the table throughout the lifetime of the page. Plugin Source:
(function($) {
    $.fn.tablePager = function(options) {

        /*
        options = {
        firstBtn: Element,
        prevBtn: Element,
        nextBtn: Element,
        lastBtn: Element,
        indicator: Element,
        sizeSelect: Element,
        pageSize: Number,
        onPageChange: Callback,
        onPageSizeChange: Callback
        };
        */

        var tbl = this;
        var defaults = { pageSize: 10 };
        var opts = $.extend(defaults, options);

        var pageIndex = 0;
        var pageSize = opts.pageSize;
        var rowCount = getRows().length;
        var pageCount = getPageCount();

        function init() {
            bindControlHandlers();
            updatePaging();
            updateIndicator();
        }

        function getRows() {
            return tbl.find("tbody tr");
        }

        function bindControlHandlers() {
            if (opts.firstBtn) { 
                opts.firstBtn.unbind().bind("click", 
                    function() { changePage(0); }); 
            }
            if (opts.prevBtn) { 
                opts.prevBtn.unbind().bind("click", 
                        function() { changePage(pageIndex - 1); }); 
            }
            if (opts.nextBtn) { 
                opts.nextBtn.unbind().bind("click", 
                        function() { changePage(pageIndex + 1); }); 
            }
            if (opts.lastBtn) { 
                opts.lastBtn.unbind().bind("click", 
                        function() { changePage(pageCount - 1); }); 
            }
            if (opts.sizeSelect) { 
                opts.sizeSelect.unbind().bind("change", 
                    function() { changePageSize(parseInt($(this).val())); }); 
            }
        }

        function getPageCount() {
            return (rowCount % pageSize) ? 
                Math.floor(rowCount / pageSize) + 1 : (rowCount / pageSize);
        }

        function changePage(toIndex) {
            if (toIndex >= 0 && toIndex = start && i 

Usage:

var pager = $("#testTable").tablePager({
    firstBtn: $(".first"), // The element that pages to the first page
    prevBtn: $(".prev"), // The element that pages to the previous page
    nextBtn: $(".next"), // The element that pages to the next page
    lastBtn: $(".last"), // The element that pages to the last page
    indicator: $(".curpage"), // The input element that stores the current page (i.e. "1/5")
    sizeSelect: $(".pagesize"), // The select element that indicates the current page size
    pageSize: 5, // The initial page size
    onPageChange: function(newIndex) { alert(newIndex); }, // Page change callback
    onPageSizeChange: function(newSize) { alert(newSize); } // Page size change callback
});

pager.changePage(2); // Causes the page to change from outside the plugin
var pageIndex = pager.getPageIndex(); // Returns the current page index
var pageSize = pager.getPageSize(); // Returns the current page size


And here's some example markup for the pager elements:

First  
Prev  
Next  
Last

  

    5
    10
    20



As you can see it's really simple and flexible.  You can supply any elements you want to the pager.  If you don't supply a first and last button elements for example then those will be ignored, if you don't specify an indicator then that will be ignored too.  The most important thing is that your DOM stays intact, this allows you to continue to manipulate the "hidden" portions of the table (i.e. the stuff not on the current page) without having to access some crazy expando property that other pager plugins use to stash the un-shown portions of the table.

As always, I'd appreciate any feedback.

Custom JavaScript Events with the Observer Pattern

Anyone that's worked with JavaScript in the browser knows that it's all about events.  And in our daily work with the DOM we use the provided events like "click", "change", and "load" all over the place.  But what if you want to create new events for custom objects that might not even have any associated UI.  Unfortunately, the W3C standard for custom events in JavaScript has spotty implementation across browsers, or none at all in some cases.

So, what are we to do...well...here's a simple implementation of the observer pattern that can help solve the lack of internal support for custom events.  Here's the Observer "classes":

var Observer = function() {
    this.observations = [];
};

var Observation = function(name, func) {
    this.name = name;
    this.func = func;
};

Observer.prototype = {
    observe: function(name, func) {
        var exists = this.observations.findAll(function(i) {
            return i.name == name && i.func == func; }).length > 0;
        if (!exists) { this.observations.push(new Observation(name, func)); }
    },
    unobserve: function(name, func) {
        this.observations.remove(function(i) {
            return i.name == name && i.func == func;
        });
    },
    fire: function(name, data, scope) {
        var funcs = this.observations.findAll(function(i) {
            return i.name == name; });
        funcs.forEach(function(i) { i.func.call(scope || window, data); });
    }
};

First thing you'll notice is methods on the "observations" array like forEach() and findAll().  These methods are added by my set of JavaScript extensions found in a previous post.  You'll also notice that this is very simple...so simple in fact that I'm not going to go through it line-by-line...I'm just going to demonstrate.  Here's how you might use this object:

var Person = function() {

    this.name = "John Doe";
    this.age = 23;
    this.observer = new Observer();
    
    this.changeName = function(newName) {
        this.name = newName;
        this.observer.fire("nameChanged", newName);
    };
    
    this.changeAge = function(newAge) {
        this.age = newAge;
        this.observer.fire("ageChanged", newAge);
    };
};

var p1 = new Person();
var p2 = new Person();

p1.observer.observe("nameChanged", alertNameChanged);
p2.observer.observe("ageChanged", alertAgeChanged);

function alertNameChanged(data) { alert("name changed to: " + data); }
function alertAgeChanged(data) { alert("age changed to: " + data); }

p1.changeName("James Doeson");
p2.changeAge(35);

Super simple...create an observer, observe a few things by name, fire a few things by name.  So now you have a behavior very similar to custom events.  One thing to note is that you can change the scope of the callback by passing a third parameter to the observe() method.  So, if you want this to reference some different object within the callback function, just pass it in.