Geeks With Blogs
Rajesh Pillai My experiments with asp.net mvc, jquery, wpf, silverlight, sharepoint, tdd and design patterns.

Welcome back.   This time we will look at creating a simple jquery pager plugin to use with asp.net mvc project.  Please note the intent is to learn
the basics of jquery plugin development.  The control as such is not recommended for use in production environment.

I recommend having a look at the jQuery Authoring Guideline at
docs.jquery.com/Plugins/Authoring

Let's begin our journey into the mystery of jquery plugin.  The first step is to create a self executing anonymous function.  Anonymous functions play
a very important role in plugin development.  Lets dissect it.

Let's have a look at the application in action.

 

First let's understand the different way in which a function can be declared.



Pattern 1

function hi() { 

    alert('say hi'); 

}


Pattern 2

var hi = function() { 

    alert('say hi'); 

}



The result of the above two function is the same.
hi();   // displays the alert

Steps to invoke anonymous function



    # original

    function hi() { alert('say hi'); }

    

    # step 1 

    # WRAP EVERYTHING IN () SO THAT WE HAVE A CONTEXT TO INVOKE THE FUNCTION

    > (function hi() { alert('say hi'); })

    

    # now call () to invoke this function

    > (function hi() { alert('say hi'); })();



The final code is a bit awkward look for the uninitiated eye.  But, that is all anonymous function is all about.  With this understanding the
below code now seems to make sense.


(function($) {

}) (jQuery);


In the above function we are passing "$" as the parameter to the anonymous function.  "$" in this case is 
jQuery object that is passed while invoking this function.


A note about self executing anonymous function

Self-invoking functions are anonymous functions declared at run time and then invoke it immediately. Since 
they are anonymous functions they can’t be invoked twice. They are a good candidate for initialization work 
which is exactly what is happening in the jQuery plugin pattern.

Now let's quickly have a look at how we can extend the jQuery object.
    (function($) {
        $.fn.pager = function(url, callback, loadData) {
        }
    }) (jQuery);

jQuery offers a mechanism for adding in methods and functionality to the existing jQuery object.

$.fn.pager  (indicates we are extending the jQuery object witha pager function.

Let's move on.  Our function requires three parameter to function correclty.  The URL of the action method, 
the callback function to invoke and a boolean indicator whether to load data on first call or not.



Since jQuery should support muliple element we should wrap the call to our main function in an each() block.

(function($) {

    $.fn.pager = function(url, callback, loadData) {

        var cb = callback;

        var url = url;

        var element; // = $(this);

        this.each(function() {

            element = $(this);

            buildPager();

        });

        function buildPager() {

            var ht = '<table class="slickpager" style="margin-left:auto;margin-right:auto">';
            ht += '<tr >';
            ht += '<td  id="prev" class="previous previous-off" >«</td>';
            ht += '<td >';
            ht += 'Page <input type="text" style="width:30px" id="pageno" value="1"/> of <span id="totalpage">10</span>';
            ht += '</td>';
            ht += '<td id="next" class="next next-on"><a href="#">»</a></td>   ';
            ht += '</tr>';
            ht += '</table>';
            element.html(ht);

            $("table.slickpager td", element).css("background-color", "#DAEAF6");

            $(element).css("background-color", "#DAEAF6");

            if (loadData) {
                var parameter = "pageNo=1";
                post(url, parameter, callback, "json");
            }
            else {

                attachPageEvents(url, cb);
            }
        }

}) (jQuery);

The buildPager() function creates the necessary HTML to generate the pager widget.  Me being lazy "pageNo"parameter is 
hardcoded. You can make it as a parameter to the function.  This parameter is passed to the URL to fetch paginated records.

The attachPageEvent() function hooks up the events for the "next", "previous" events.

function attachPageEvents(url, callback) {

    $("table.slickpager td a", element).unbind("click");

    $("table.slickpager td a", element).click(function(event) {
        event.preventDefault();
        var qs = getQueryString(this.href)["page"];
        var parameter = "pageno=" + qs;
        post(url, parameter, callback, "json");
    });

    $("table.slickpager input#pageno", element).unbind("blur");

    $("table.slickpager input#pageno", element).blur(function(event) {
        var pageno = $(this).val();
        var parameter = "pageno=" + pageno;
        post(url, parameter, callback, "json");
    });
}



The post method is show below.  This function is important as it is here that the callback function that the client passed is invoked on success of the post.



function post(url, parameter, callback, dataType) {

    $.ajax({

        type: "post",

        url: url,

        data: parameter,

        dataType: dataType,

        beforeSend: function(XMLHttpRequest) {

        },

        success: function(data) {

            updatePager(url, data);

            attachPageEvents(url, callback);



            if (typeof callback == 'function') {

                callback.call(this, data);

            }

        },

        error: function(msg) {

            alert("error : " + msg);

        },

        complete: function(XMLHttpRequest, textStatus) {

        }

    });

    }


The following block invokes the callback function.

if (typeof callback == 'function') {
    callback.call(this, data);
}


The updatePager() function updates the status of the pager control.
function updatePager(url, msg) {

    // set the prev text and link.
    $("table.slickpager td#prev", element).removeClass("previous-off").addClass("previous-on");

    var prevText = $("table.slickpager td#prev", element).text();

    var lnkPrev = "<a href='" + url + "?page=" + (msg.PageIndex - 1).toString() + "'>" + prevText + "</a>";

    $("table.slickpager td#prev", element).html(lnkPrev);



    // set the next text and link.

    var nextText = $("table.slickpager tbody tr td#next", element).text();

    var lnkNext = "<a href='" + url + "?page=" + (msg.PageIndex + 1).toString() + "'>" + nextText + "</a>";



    $("table.slickpager td#next", element).removeClass("next-off").addClass("next-on");

    $("table.slickpager td#next", element).html(lnkNext);



    $("table.slickpager input#pageno", element).val(msg.PageIndex);

    $("table.slickpager td span#totalpage", element).text(msg.TotalPages.toString());



    if (!msg.HasPreviousPage) {

        var prevText = $("table.slickpager td#prev", element).text();

        $("table.slickpager td#prev", element).html(prevText);

        $("table.slickpager td#prev").removeClass("previous").addClass("previous-off");

    }

    if (!msg.HasNextPage) {

        var nextText = $("table.slickpager td#next", element).text();

        $("table.slickpager td#next", element).html(nextText);

        $("table.slickpager td#next", element).removeClass("next").addClass("next-off");

    }

}



There are some optimizations which we could do.  I may possibly highlight that in another blog post.
Now comes the most important part.

    return this;


Invoke this statement at the end of all the function.  This will keep the jQuery design
of chainability of objects intact.



To use this pager component in your page

<script type="text/javascript">

    $(function() {

        $("#pager-demo").pager("/home/GetRandomData", callback, true);

    });



    function callback(msg) {

        if (msg == null)

            return false;

        

        if (msg.Items.length > 0) {

            var data = "<table border='0' width='100%'>"

            data += "<thead><tr>";

            data += "<th>Book Name</th>";

            data += "<th>Tag</th>";

            data += "</tr></thead>";

            $(msg.Items).each(function() {

                data += "<tr>";

                data += "<td><a href='#'>" + this.BookName + "</a></td><td>" + this.Tag + "</td>";

            });

            data += "</table>";

            //alert(data);

            $("div#content").html(data);

        }

        else {

            $("div#content").html("<i>There are no books to be displayed.</i>");

        }

    }

</script>

</pre>

Modify the callback method as per your requirement.  The HTML markup is shown below.



<div id="widget">

    <div id="content"></div>

    <div id="pager-demo">

    </div>

</div>

</pre

 

For reference the css is given below

.slickpager a{

    border:solid 1px #9aafe5;

    margin-right:2px;

}



.slickpager .previous-off, .slickpager .next-off {

    border:solid 1px #DEDEDE;

    color:#888888;

    display:block;

    float:left;

    font-weight:bold;

    margin-right:2px;

    padding:3px 4px;

}

.slickpager .next a, .slickpager .previous a {

    font-weight:bold;

}

.slickpager .active{

    background:#2e6ab1;

    color:#FFFFFF;

    font-weight:bold;

    display:block;

    float:left;

    padding:4px 6px;

}



.slickpager a:link, .slickpager a:visited {

    color:#0e509e;

    display:block;

    float:left;

    padding:3px 6px;

    text-decoration:none;

}



.slickpager a:hover{

     border:solid 1px #0e509e

}

And here's the entire source code for jquery.slickpager.js

 

(function($) {
    $.fn.pager = function(url, callback, loadData) {
        var cb = callback;
        var url = url;
        var element; // = $(this);

        this.each(function() {
            element = $(this);
            buildPager();
        });

        function buildPager() {
            var ht = '<table class="slickpager" style="margin-left:auto;margin-right:auto">';
            ht += '<tr >';
            ht += '<td  id="prev" class="previous previous-off" >«</td>';
            ht += '<td >';
            ht += 'Page <input type="text" style="width:30px" id="pageno" value="1"/> of <span id="totalpage">10</span>';
            ht += '</td>';
            ht += '<td id="next" class="next next-on"><a href="#">»</a></td>   ';
            ht += '</tr>';
            ht += '</table>';

            element.html(ht);

            $("table.slickpager td", element).css("background-color", "#DAEAF6");

            $(element).css("background-color", "#DAEAF6");

            if (loadData) {
                var parameter = "pageNo=1";
                post(url, parameter, callback, "json");
            }
            else {
                attachPageEvents(url, cb);
            }
        }

        function attachPageEvents(url, callback) {
            $("table.slickpager td a", element).unbind("click");
            $("table.slickpager td a", element).click(function(event) {
                event.preventDefault();
                var qs = getQueryString(this.href)["page"];
                var parameter = "pageno=" + qs;
                post(url, parameter, callback, "json");
            });

            $("table.slickpager input#pageno", element).unbind("blur");

            $("table.slickpager input#pageno", element).blur(function(event) {
                var pageno = $(this).val();
                var parameter = "pageno=" + pageno;
                post(url, parameter, callback, "json");
            });
        }

        function post(url, parameter, callback, dataType) {
            $.ajax({
                type: "post",
                url: url,
                data: parameter,
                dataType: dataType,
                beforeSend: function(XMLHttpRequest) {
                    //$(element).html('<img id="loader-img" src="/Images/ajax-loader.gif" />');
                },
                success: function(data) {
                    updatePager(url, data);
                    attachPageEvents(url, callback);

                    if (typeof callback == 'function') {
                        callback.call(this, data);
                    }
                },
                error: function(msg) {
                    alert("error : " + msg);
                },
                complete: function(XMLHttpRequest, textStatus) {
                }
            });
        }

        function getQueryString(href) {
            var assoc = new Array();

            if (href != '') {
                var queryString = unescape(href.substring(1));
                queryString = unescape(href.substring(href.lastIndexOf("?") + 1));
            }
            else {
                var queryString = unescape(location.search.substring(1));
            }

            var keyValues = queryString.split('&');
            for (var i in keyValues) {
                var key = keyValues[i].split('=');
                assoc[key[0]] = key[1];
            }
            return assoc;
        }

        function updatePager(url, msg) {
            // set the prev text and link.

            $("table.slickpager td#prev", element).removeClass("previous-off").addClass("previous-on");

            var prevText = $("table.slickpager td#prev", element).text();

            var lnkPrev = "<a href='" + url + "?page=" + (msg.PageIndex - 1).toString() + "'>" + prevText + "</a>";
            $("table.slickpager td#prev", element).html(lnkPrev);


            // set the next text and link.
            var nextText = $("table.slickpager tbody tr td#next", element).text();
            var lnkNext = "<a href='" + url + "?page=" + (msg.PageIndex + 1).toString() + "'>" + nextText + "</a>";


            $("table.slickpager td#next", element).removeClass("next-off").addClass("next-on");
            $("table.slickpager td#next", element).html(lnkNext);

            $("table.slickpager input#pageno", element).val(msg.PageIndex);
            $("table.slickpager td span#totalpage", element).text(msg.TotalPages.toString());

            if (!msg.HasPreviousPage) {
                var prevText = $("table.slickpager td#prev", element).text();
                $("table.slickpager td#prev", element).html(prevText);
                $("table.slickpager td#prev").removeClass("previous").addClass("previous-off");
            }
            if (!msg.HasNextPage) {
                var nextText = $("table.slickpager td#next", element).text();
                $("table.slickpager td#next", element).html(nextText);
                $("table.slickpager td#next", element).removeClass("next").addClass("next-off");
            }
        }
        return this;
    }
})(jQuery);

Hope you enjoyed this blog post.  I'll soon post the source link to this blog.

 

Posted on Friday, November 27, 2009 12:41 PM jQuery , asp.net mvc | Back to top


Comments on this post: jQuery Receipe : Build a simple pager plugin to use with asp.net mvc

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © Rajesh Pillai | Powered by: GeeksWithBlogs.net