RagingSmurf Code

jQuery Plugin – Pageable Table

image of paging links My goal for this for plugin: Write a plugin that manages the fetching, paging and displaying of a JSON result-set in a BootStrap paging style.

I started with code from a previous post, where I put together a jQuery plugin template that implements MetaData, and also keeps a copy of itself in the DOM. It’s pretty useful, in that, it lets you set inline HTML with predefined jQuery metadata. You can also later fetch the state of the instance after it’s pushed into the DOM.

HTML Table

First thing we need is a table placeholder container to publish to. Since we setup each table with its own datasource, we can have multiple tables managing result-sets.

We point the table to a JSON data-source, add a Knockout template to format our results. Also, we include a standard set of paging containers in our table’s footer.

<table class="table" data="{source: '/home/someRows', pageSize : 5 }">
    <caption>
        Pageable Table
    </caption>
    <tbody data-bind="template: { name: 'customer-template', foreach: customers }">
    </tbody>
    <tfoot>
        <tr>
            <td>
                <script type="text/html" id="customer-template">
                <tr>
                    <td>
                        <p data-bind="text: Name"></p>
                        <p>Date of Birth: <span data-bind="text: DOB"></span></p>
                    </td>
                </tr>
                </script>
            </td>
        </tr>
        <tr>
            <td data="{ layout : 'pagination' }">
                <div class="pagination" style="margin: 10px; display: none;">
                    <!-- layout : 'pagination' -->
                    <ul class='numbers'>
                        <!-- Container for Page by Numbered Links -->
                    </ul>
                </div>
                <!-- layout : 'prevnext' -->
                <ul class="pager" style="display: none;">
                    <!-- Container for Page by Next Previous Links -->
                </ul>
                <span class="current"></span>
            </td>
        </tr>
    </tfoot>
</table>

JSON Results (Interface)

We need the formatting of the paging results to be returned in a very predictable format.

    interface IPagingResults
    {
        int Pages { get; set; }
        int Current { get; set; }
        bool IsFirst { get; set; }
        bool IsLast { get; set; }
        bool HasNext { get; set; }
        bool HasPrevious { get; set; }
    }

JSON Results Class

So we start with a basic paging interface, and then build our results class around it.

    public class ResultSet : IPagingResults
    {
        public int Pages { get; set; }
        public int Current { get; set; }
        public bool IsFirst { get; set; }
        public bool IsLast { get; set; }
        public bool HasNext { get; set; }
        public bool HasPrevious { get; set; }
        public List Results { get; set; }
    }

    public class DataRow
    {
        public String Name { get; set; }
        public DateTime DOB { get; set; }
    }

Controller Action (returning JSON)

With the SomeRows action, we return a generated list of our generic customers, as well as the paging data. PagedList is out of the scope of this article, but well worth looking at for server-side paging stuff.

[HttpPost]
public JsonResult SomeRows(int? page, int? size)
{

    List customers = new List();

    for (int i = 0; i < 23; i++)
    {
        customers.Add(new DataRow { Name = "Customer " + i.ToString(), 
                                    DOB = (new DateTime().AddDays(i)) });
    }

    int pageNumber = page ?? 1;
    int pageSize = size ?? 5;
    var pageOfCustomers = customers.ToPagedList(pageNumber, pageSize);

    var results = new ResultSet
    {
        Pages = pageOfCustomers.PageCount,
        Current = pageOfCustomers.PageNumber,
        IsFirst = pageOfCustomers.IsFirstPage,
        IsLast = pageOfCustomers.IsLastPage,
        HasPrevious = pageOfCustomers.HasPreviousPage,
        HasNext = pageOfCustomers.HasNextPage,
        Results = pageOfCustomers.ToList()
    };

    return Json(results);
}

Implementing the Pageable Table

First we setup Ajax with some default options. Then we setup the MetaData plugin to evaluate all of our DOM elements with data attributes.

Next we add create our payload object. This will get called to rebind our Knockout view when a paging call is made. The cb(response) is an internal call that rebuilds the paging after the view is updated.

$(function () {

    //Global ajax settings
    $.ajaxSetup({
        global: false,
        type: "POST",
        headers: { "cache-control": "no-cache" }
    });

    //Setup metadata plugin to use data attribute.
    $.metadata.setType("attr", "data");
    //Create a knockout view in the paging payload.
    var obj = {
        //Whenever paging happens, this function will be run.
        run: function (source, destination, page, size, cb) {
            $.post(source, { page: page, size: size }, function (response) {
                //KO - ViewModel
                function MyViewModel() {
                    //KO - Mapping
                    this.customers = response.Results;
                }
                //Push new data to the view.
                ko.applyBindings(new MyViewModel(), destination);
                //raise callback to the jQuery Plugin (rebuild numbered paging).
                cb(response);
            });
        }
    };
    //Create instance of the plugin, setting defaults. 
    var tbl = $('.table').myPaging({ selectedPage: 1, pageSize: 5, payload: obj });

});

Final Thoughts

The paging type can be set to dynamic, so that shorter result-sets (under 4 pages) render Previous and Next links, instead of a list of pages. This project felt a bit bloated for the amount code I needed to achieve a working sample. But it looks like it will solve a functionality need I have.

References to the plugin, as well as KnockoutJS, MetaData, and jQuery (1.8.2) are needed for this code to work. All of which are part of the sample project over on GitHub.

jQuery Plugin Shell

I can see some UI work coming up on the horizon, so I decided to brush up on some jQuery plugin development. @elijahmanor has a nice thorough msdn article in which he covers plugin development; well worth the read. The article’s code sums up as jWatermark, and is extended with jQuery metadata.

So the following Gist will serve as a baseline for my upcoming jQuery plugins.

What I really appreciate with the metadata plugin, is the support to predefine jQuery settings from within an element.

<div id="divObject" class="{key: 12345, text: 'State'}" />

Simple Google Analytics Library

Google publishes a whole host of useful APIs. The one I get the most business value, with the least effort, is Google Analytics. The following library tracks Page Speed, Events, Sessions, Funnels and Transactions within GA.

First, we need to add variables to track the page load, and the target GA account.

<script type="text/javascript">
var plstart = new Date();
var _gaid = '<%= ConfigurationManager.AppSettings["AnalyticsKey"] %>';
</script>

Then we can add the library reference.

<script type="text/javascript" src="GA.js"></script>

Finally, we can call individual methods.

<script type="text/javascript" >
GA.event.track('category', 'action', 'label', 1, true);
GA.session.track('category', 'action', 'label', 1, true);
GA.funnel.track('http://www.somedomain.com/index.html');
GA.transaction.track('Anchorage', 'AK', 'USA', 1234);
</script>

We can then see the calls pushed out to the FireBug console.

Firebug Results

Typically we would’t want to track localhost traffic, and also likely, we’d want to separate Test traffic vs. Production traffic. So we set the our local Web.config key to blank.

  <!-- Google Analytics Key - left blank so localhost will not be tracked. -->
  <add key="AnalyticsKey" value="" />

Then we can then manage the key in the Web.Debug.Config and Web.Release.Config.

<!-- Google Analytics Key - Set for based transformation type (Debug or Release). -->
<appSettings>
  <add key="AnalyticsKey" value="UA-XXXXXXXX-1" xdt:Transform="Replace" xdt:Locator="Match(key)" />
</appSettings>

All the code for this post can be found over at GoogleAnalyticsJS.