Category: jQuery


jQuery.ganttView a Lightweight Gantt Chart for jQuery

June 11th, 2010 — 8:05am

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.





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.

4 comments » | JavaScript, jQuery

jQuery Plugin: tablePager

June 5th, 2009 — 10:38am

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 <= (pageCount - 1)) {
                pageIndex = toIndex;
                updatePaging();
                updateIndicator();
                if (opts.onPageChange) {
                    opts.onPageChange.call(pageIndex);
                }
            }
        }
 
        function changePageSize(toSize) {
            pageSize = toSize;
            pageCount = getPageCount();
            updatePaging();
            updateIndicator();
            if (opts.onPageSizeChange) {
                opts.onPageSizeChange.call(pageSize);
            }
        }
 
        function updateIndicator() {
	    if (opts.indicator) {
            	opts.indicator.val((pageIndex + 1) + "/" + pageCount);
	    }
        }
 
        function updatePaging() {
            var start = pageIndex * pageSize;
            var end = start + pageSize;
            var i = 0;
            var rows = getRows();
            rows.each(function() {
                if (i >= start && i < end) {
                    $(rows[i]).show();
                } else {
                    $(rows[i]).hide();
                }
                i++;
            });
        }
 
        init();
 
        return {
            changePage: function(moveTo) { changePage(moveTo - 1); },
            getPageSize: function() { return pageSize; },
            getPageIndex: function() { return pageIndex; }
        };
    };
})(jQuery);

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:

<span class="first">First</span>&nbsp;&nbsp;
<span class="prev">Prev</span>&nbsp;&nbsp;
<span class="next">Next</span>&nbsp;&nbsp;
<span class="last">Last</span>
<br><br>
<input type="text" class="curpage" />&nbsp;&nbsp;
<select class="pagesize">
    <option value="5">5</option>
    <option value="10">10</option>
    <option value="20">20</option>
</select>

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.

2 comments » | JavaScript, jQuery

jQuery vs. the ASP.NET Update Panel Revisited – Event Delegation

December 5th, 2008 — 1:17pm

Recently I was again struggling with the ASP.NET Update Panel killing my JavaScript interactions because it replaces the DOM elements it surrounds on every refresh.  My previous solution for this was simply to rebind those events after each Update Panel refresh was completed.  This works perfectly well, but it always seemed a little heavy handed to me.

So in digging into this issue a little more, I came upon a few posts that talked about using event delegation to solve this problem (thanks to everyone out there who wrote about delegation, cause you're smarter than I am).  The basic idea is that you attach events at a higher level in the DOM tree and use the DOM's event bubbling mechanism to catch them.  For example, let's say we have the following ASPX snippet:

<div id="container">
    <ul>
        <asp:UpdatePanel>
            <li class="listItem">Item One</li>
            <li class="listItem">Item Two</li>
            <li class="listItem">Item Three</li>
            <li class="listItem">Item Four</li>
        </asp:UpdatePanel>
    </ul>
</div>

Now let's assume that you've done this somewhere in JavaScript:

function bindLiEvents() {
    $("li.listItem").bind("click", function() { alert($(this).html()); });
}

When this JavaScript is executed jQuery loops over all the found elements and individually attaches an event handler to each.  So already there is an inherent performance gain to be had by not doing this looping.  Furthermore there is now the problem with the Update Panel.  If the user takes some action that causes the panel to refresh, then the four <li> elements are destroyed and recreated and unless I call bindLiEvents again after the refresh I loose this functionality.

Enter the event delegation concept.  Why not attach my handler to a DOM element that's outside the Update Panel – the <div> element in this case.  This may seem a bit counter intuitive, but if you're aware of how event bubbling works in the DOM you can see how this might work.

Event bubbling simply means that events at lower levels in the DOM tree are propagated to higher levels.  For example when an <li> is clicked in our example, the click event is also fired on the <ul> and the <div> and beyond.  We can use this behavior to our advantage in this instance because event bubbling is intrinsic to DOM elements – when new elements are added they automatically exhibit this behavior.

So now let's look at how we might attach an event to the <div> element:

function bindLiEvents() {
    $("#container").delegate("click", "li.listItem",
        function() { alert($(this).html()); });
}

Don't worry about where the delegate method comes from – it's not native to jQuery but I'll get to that.  What's happening here is that I'm binding a very generic event handler to the <div> element, and telling that handler to listen for click events on elements that match the "li.listItem" selector.  So now, when an event bubbles up from an <li> element the handler on the <div> fires, tests if the event originated on an <li> with a class of "listItem" and then calls the function callback.

There are advantages to using the approach.  First, event handlers are attached only once, and only at a single point which saves time when doing the initial bind.  Second, any new elements added to the Update Panel will automatically fire the delegated event handler – no rebind is required.

So if you want to try this out for yourself, here is a jQuery plugin for the delegate method:

(function ($) {
    var bubbledEvents = ['click', 'dblclick', 'mousedown', 'mouseup', 'mousemove',
        'mouseover', 'mouseout', 'keydown', 'keypress', 'keyup'];
    var allowedEvents = { };
    $.each(bubbledEvents, function(idx,event) { allowedEvents[event] = true; });
    $.fn.extend({
        delegate: function (event, selector, func) {
            return $(this).each(function () {
                if (allowedEvents[event])
                    $(this).bind(event, function (e) {
                        var el = $(e.target), result = false;
                        while (!$(el).is("body")) {
                            if ($(el).is(selector)) {
                                result = func.apply($(el)[0], [e]);
                                if (result === false) { e.preventDefault(); }
                                return;
                            }
                            el = $(el).parent();
                        }
                    });
            });
        },
      
  undelegate: function (event) { return $(this).each(function () {
            $(this).unbind(event); }); }
    });
})(jQuery);

2 comments » | JavaScript, jQuery

Use jQuery.post to submit to ASP.NET MVC Actions

November 26th, 2008 — 7:02pm

Recently, Microsoft announced that jQuery will be shipping with future versions of Visual Studio.  In fact, today the ASP.NET MVC bits already ship with jQuery.  ASP.NET MVC is extremely friendly with jQuery. 

One of the things that ASP.NET MVC gives you over web forms is an easy way to expose actions to the client.  Here's where AJAX using jQuery really shines.  In the past if you wanted to initiate some server side action using JavaScript, you'd need to expose a web service of some kind and then call into your server from there.  With ASP.NET MVC you can simply submit a post to an action via a URL and you're off.

For example, I might have the following action called 'DoSomething' on my 'Thing' controller:

public ActionResult DoSomething(int thingId, string thingName, 
        DateTime thingDate) {

        var db = new ThingEntities();
        var thing = (from Thing t in db.Things
                                      where t.ID == thingId,
                                      select t).First<Thing>();

        thing.Name = thingName;
        thing.ThingDate = thingDate;

        db.SaveChanges(true);

return Content("true");
}

Calling this method from JavaScript becomes almost trivial using the jQuery.post method:

$.post("/Thing/DoSomething", { thingId: 7, thingName: 'New Thing', thingDate: '1/1/2008' }, function(data) { alert(data); });

So the post method has three parameters:

  1. The URL of the action to post to. 
  2. An object comprising the data to send with the post. 
  3. A callback method which fires when the request returns. 

It's as simple as that.  So now making calls back to your server from JavaScript is as easy as writing a controller action that takes simple types as parameters.  There are several different jQuery AJAX methods to chose from and obviously some more advanced things that you can do, but as you can see the basics are pretty simple.

2 comments » | ASP.NET MVC, jQuery

Optimizing jQuery Selector Performance

October 20th, 2008 — 11:12am

Despite the fact that the capacity of modern JavaScript engines is expanding rapidly, I found myself recently fighting with JS performance on a very large web page.  This was an ASP.NET application with one very complex page, very large DOM tree, lots of elements and lots of JS interactions.

As the page became larger and more data was pumped into the DOM the time it took to locate an element or set of elements in the DOM began to lengthen.  My attempts to locate any solid information in Google on improving this situation was fruitless.  So, I starting digging through the jQuery source code to see how the selector engine worked…this was also useless.  Clearly the skills of the JS Ninjas that created jQuery are far superior to mine.

So, then it just became a thought experiment.  Knowing that jQuery uses a context sensitive combination of XPath, native browser API's, and some fancy string parsing for CSS3 selectors there are a few optimizations that come to mind.

Qualify your selector strings when searching by class.
Because one way that jQuery can find DOM elements is XPath style searching, it's important to qualify your selector strings.  Turning $(".some-class") into $("div.container > div.head > div.some-class") can really make a difference.  This is especially true when the branches of your DOM tree become very deep.

Don't Qualify your selector when searching by id.
Because searching by id uses the browsers native getElementById method (which is very fast) it doesn't make sense to overly qualify a selector string that's looking for a unique element.  So $("div#someid") is actually slower than $("#someid").

Select By Element(s)
Don't forget that your argument to $() can also be a reference to an element directly.  One of the techniques that I've used to get over huge numbers of DOM elements is to do some pre-caching of element objects before doing any manipulation.  For example I might keep an array in JavaScript of all of the elements of a particular group.  

Say you have a bunch of textboxes on a page and they're scattered all around in deeply nested trees.  Half of the textboxes belong to group A and the other group B.  I might set up two JavaScript arrays (elementsAarray, and elementsBarray) and before the page is rendered I'll go and grab all of those elements and plop them into my arrays.  This way I can simply grab that array of elements in group A with $(elementsAarray) which completely cuts out the need for DOM traversal.

- – - – -

There are likely other ways to tweak your selectors and I'd love to hear how other people have tackled this issue.  Here's hoping that JavaScript engines get so fast that we don't have to care!

1 comment » | jQuery

jQuery Plugin: wrapList

October 2nd, 2008 — 5:01pm

Ok, so now I'm on a roll with the jQuery plugins.  This time it's a simple plugin that takes a single unordered list and segments it at predefined intervals into additional lists.  Each list is then floated to the left making it appear to wrap content to the next "column".  The plugin is extremely simple to use but works really well on all sorts of content.

Download: jQuery.wrapList.zip

1 comment » | jQuery

Back to top