Jump to Table of Contents

Example: Tables

An HTML table containing 50 states and their population with an attached paginator to view the information in small sections.

2010 US Population (according to census.gov)
State Abbr Population
Items Per Page:

Setting Up the Interface

First we need to construct the HTML for the table and controls.

<table>
    <caption>
        2010 US Population <em>(according to <a href="http://www.census.gov/2010census/" target="_blank">census.gov</a>)</em>
    </caption>
    <thead>
        <tr>
            <th></th>
            <th>State</th>
            <th>Abbr</th>
            <th>Population</th>
        </tr>
    </thead>
    <tbody>
    </tbody>
</table>
<div class="controls">
    <ul>
        <li class="control-first"><a class="control" data-type="first">First</a></li>
        <li class="control-prev"><a class="control" data-type="prev">Prev</a></li>
        <li class="control-next"><a class="control" data-type="next">Next</a></li>
        <li class="control-last"><a class="control" data-type="last">Last</a></li>
    </ul>
    <div class="currentPage"></div>
    <div class="rowsPerPage">
        Items Per Page:
        <select class="perPage">
            <option value="10">10</option>
            <option value="20">20</option>
            <option value="-1">Show All</option>
        </select>
    </div>
</div>

Now we can add some CSS to make it a little more presentable.

<style scoped>
#demo {
    color: #333;
    font-size: 11pt;
    font-family: 'Segoe UI Semilight', 'Open Sans', 'Helvetica Neue', Verdana, Arial, Helvetica, sans-serif;
    font-weight: 300;
}

/** TABLE **/
#demo table {
    border-bottom: 1px solid #ddd;
}
#demo caption {
    padding-bottom: 10px;
    font-size: 0.9em;
}
#demo th,
#demo td {
    background: none;
    border: none;
}
#demo th {
    font-weight: 500;
    height: 20px;
    line-height: 20px;
    padding: 5px 10px;
}
#demo th + th,
#demo td + td {
    border-left: 1px solid #ddd;
}
#demo tbody tr td {
    border-top: 1px solid #ddd;
}
#demo td {
    padding: 3px 10px;
}
#demo tr:hover td {
    background: #eee;
}

/** CONTROLS **/
#demo .controls {
    padding-top: 15px;
    padding-bottom: 10px;
    position: relative;
}
#demo .controls ul {
    margin: 0;
    padding: 0;
}
#demo .controls li {
    list-style: none;
    display: inline-block;
    zoom: 1; *display: inline;
}
#demo .controls ul,
#demo .controls div {
    display: inline-block;
    zoom: 1; *display: inline;
    vertical-align: middle;
}
#demo .controls a {
    display: inline-block;
    zoom: 1; *display: inline;
    border: solid 1px #ddd;
    text-decoration: none;
    line-height: 1.7em;
    padding: 0 8px;
    color: #4A4A4A;
    background: #fff;
    cursor: pointer;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}
#demo .controls a:hover {
    background-color: #eee;
    color: #000;
}
#demo .controls a:active {
    background: #333;
    color: #fff;
    border-color: #333;
}
#demo .controls a.disabled,
#demo .controls a.disabled:hover {
    color: #f2f2f2;
    border-color: #f2f2f2;
    background: #fff;
    cursor: default;
}
#demo .currentPage {
    padding-left: 20px;
}
#demo .rowsPerPage {
    position: absolute;
    right: 0;
    line-height: 1.8em;
    background: red;
}
#demo .controls select {
/*
    background: none;
    font-size: 12px;
    line-height: 1.7em;
    border: 1px solid #ddd;
    border-radius: 0;
    padding-left: 8px;
    margin-left: 5px;
    -webkit-appearance: none;
*/
}
</style>

JavaScript

Our small table application will have four main parts:

  • Showing the page requested
  • Updating the controls available
  • Wiring the controls
  • Wiring the paginator

Setting Up the YUI Instance

Now we need to create our YUI instance and tell it to load the modules.

YUI().use('paginator', 'node', 'datatype-number-format', function (Y) {

    // Your code goes here...

});

There are only a few modules we need to require:

  • paginator: Gives us the paginator
  • node: Allows us to access the elem ents on the page and gives us basic event attachment
  • datatype-number-format: Let's us format the population numbers

Defining our variables

We start off by defining the variables we need for the app.

// node references
var demo = Y.one('#demo'),
    tbody = demo.one('tbody'),
    controls = demo.one('.controls'),

    // templates
    rowTemplate = '<tr><td>{count}</td><td>{state}</td><td>{abbr}</td><td align="right">{population}</td></tr>',
    currentPageTemplate = 'Page {page} of {totalPages}',

    // data to display
    data = [
        { state: "Alabama",        abbr: "AL", population: 4447100  },
        { state: "Alaska",         abbr: "AK", population: 626932   },
        // ...
        { state: "Wisconsin",      abbr: "WI", population: 5363675  },
        { state: "Wyoming",        abbr: "WY", population: 493782   }
    ],

    // paginator
    pg = new Y.Paginator({
        itemsPerPage: 10,
        totalItems: data.length
    });

// let's do a one time pass through the data to format the population numbers
Y.Array.each(data, function (val, key) {
    val.population = Y.Number.format(val.population, {thousandsSeparator: ','});
});

We have three references to Nodes to prevent repetative look ups.

We have created two template variables that will be used to populate rows (rowTemplate) and the current page (currentPageTemplate) in the paginator.

We have an Array of data to be parsed through to build the table when we change pages or change how many rows are visible at one time.

Finally, we define our paginator showing 10 items per page. We set the totalItems to the number of items in the data array.

After we define our variables, we loop through the data array to format the population numbers.

showPage Method

Next, we need to define how we are going to get data from the Array to the table's body.

function showPage(page) {
    // get the page number from the paginator if there isn't one provided
    page || (page = pg.get('page'));

    // number of items to display
    var itemsPerPage = pg.get('itemsPerPage'),
        totalItems = pg.get('totalItems') - 1,
        min = (page - 1) * itemsPerPage,
        max = Math.min( (page * itemsPerPage) - 1, totalItems),
        rows = '',
        i;

    // if our math left us with less than the minimum, use the total number of items
    if (max < min) {
        max = totalItems;
    }

    // loop through the data and build the templates
    for (i = min; i <= max; i++ ) {
        rows += Y.Lang.sub(rowTemplate, Y.mix({count: i + 1}, data[i]));
    }

    // set the table's body to the new rows
    tbody.setHTML(rows);

    // update the paginator's display
    Y.all('.currentPage').set('text',
        Y.Lang.sub(currentPageTemplate, pg.getAttrs())
    );
}

The variable definition is pretty straight forward with the exception of min and max; that can look a little confusing, so let's take it step by step.

First, we set min to the current page minus one and multiply it by the total number of items per page. This will give you a zero based index for the first item on the page — which is what we want when working with an Array.

Next, we set max to the smallest of either the last "virtual" item on the page (page * itemsPerPage - 1) or the total number of items. Since our last page may not end exactly at the last item (think 20 items per page and 50 total items), this will ensure that max is never more than we have to show.

Finally, we make sure max is greater than, or equal to, min.

The for loop starts at min and goes through max building a string of each row using Y.Lang.sub and the rowTemplate.

Then we set the table's body to the rows we created, and update the paginator's display.

updateControls Method

When our paginator's page changes, we need to update the controls to set what is disabled, and what is not.

function updateControls() {
    var hasNext = pg.hasNextPage(),
        hasPrev = pg.hasPrevPage();

    controls.one('.control-first a').toggleClass('disabled', !hasPrev);
    controls.one('.control-prev a').toggleClass('disabled', !hasPrev);
    controls.one('.control-next a').toggleClass('disabled', !hasNext);
    controls.one('.control-last a').toggleClass('disabled', !hasNext);
}

Y.Paginator has two methods that return boolean values about it's current state: hasNextPage and hasPrevPage. We use these methods to determin if the control buttons should be disabled or enabled.

For example, if there is not a previous page (false returned from hasPrevPage), the first control has the disabled ClassName applied, otherwise it is removed.

Control Event Binding

Now we need to describe what happens when the controls are interacted with.

Y.one('#demo').delegate('click', function (e) {
    e.preventDefault();

    var control = e.currentTarget,
        type = control.getData('type');

    if (control.hasClass('disabled')) {
        return;
    }

    switch (type) {
        case 'first': pg.set('page', 1); break;
        case 'prev': pg.prevPage(); break;
        case 'next': pg.nextPage(); break;
        case 'last': pg.set('page', pg.get('totalPages')); break;
    }

}, '.control');

Y.one('#demo .perPage').on('change', function (e) {
    pg.set('itemsPerPage', e.currentTarget.get('value'));
});

First off, when the "buttons" are clicked and are not disabled, we go to their respective page.

When the perPage select element is changed, we set the itemsPerPage of our paginator to the new value. We also set our paginator's page to 1 to reset the table's view.

Binding Paginator

The last thing we need to do before we wrap this up is bind our paginator's change events.

pg.after('itemsPerPageChange', function () {
    if (pg.get('page') === 1) {
        showPage();
        updateControls();
    } else {
        pg.set('page', 1);
    }
});

pg.after('pageChange', function (e) {
    showPage(e.newVal);
    updateControls();
});

First, after our paginator fires the itemsPerPageChange event, we go to the first page unless it's already at the first page. If it is at the first page, we show the new page and update the controls.

After our paginator fires the pageChange event, we show that page, then update the controls.

Show initial state!

Our last step is to show the initial page and set the initial state of the controls by updating them.

showPage();
updateControls();

The Whole Example

That's it! Now let's put it all together.

<style scoped>
#demo {
    color: #333;
    font-size: 11pt;
    font-family: 'Segoe UI Semilight', 'Open Sans', 'Helvetica Neue', Verdana, Arial, Helvetica, sans-serif;
    font-weight: 300;
}

/** TABLE **/
#demo table {
    border-bottom: 1px solid #ddd;
}
#demo caption {
    padding-bottom: 10px;
    font-size: 0.9em;
}
#demo th,
#demo td {
    background: none;
    border: none;
}
#demo th {
    font-weight: 500;
    height: 20px;
    line-height: 20px;
    padding: 5px 10px;
}
#demo th + th,
#demo td + td {
    border-left: 1px solid #ddd;
}
#demo tbody tr td {
    border-top: 1px solid #ddd;
}
#demo td {
    padding: 3px 10px;
}
#demo tr:hover td {
    background: #eee;
}

/** CONTROLS **/
#demo .controls {
    padding-top: 15px;
    padding-bottom: 10px;
    position: relative;
}
#demo .controls ul {
    margin: 0;
    padding: 0;
}
#demo .controls li {
    list-style: none;
    display: inline-block;
    zoom: 1; *display: inline;
}
#demo .controls ul,
#demo .controls div {
    display: inline-block;
    zoom: 1; *display: inline;
    vertical-align: middle;
}
#demo .controls a {
    display: inline-block;
    zoom: 1; *display: inline;
    border: solid 1px #ddd;
    text-decoration: none;
    line-height: 1.7em;
    padding: 0 8px;
    color: #4A4A4A;
    background: #fff;
    cursor: pointer;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}
#demo .controls a:hover {
    background-color: #eee;
    color: #000;
}
#demo .controls a:active {
    background: #333;
    color: #fff;
    border-color: #333;
}
#demo .controls a.disabled,
#demo .controls a.disabled:hover {
    color: #f2f2f2;
    border-color: #f2f2f2;
    background: #fff;
    cursor: default;
}
#demo .currentPage {
    padding-left: 20px;
}
#demo .rowsPerPage {
    position: absolute;
    right: 0;
    line-height: 1.8em;
    background: red;
}
#demo .controls select {
/*
    background: none;
    font-size: 12px;
    line-height: 1.7em;
    border: 1px solid #ddd;
    border-radius: 0;
    padding-left: 8px;
    margin-left: 5px;
    -webkit-appearance: none;
*/
}
</style>

<table>
    <caption>
        2010 US Population <em>(according to <a href="http://www.census.gov/2010census/" target="_blank">census.gov</a>)</em>
    </caption>
    <thead>
        <tr>
            <th></th>
            <th>State</th>
            <th>Abbr</th>
            <th>Population</th>
        </tr>
    </thead>
    <tbody>
    </tbody>
</table>
<div class="controls">
    <ul>
        <li class="control-first"><a class="control" data-type="first">First</a></li>
        <li class="control-prev"><a class="control" data-type="prev">Prev</a></li>
        <li class="control-next"><a class="control" data-type="next">Next</a></li>
        <li class="control-last"><a class="control" data-type="last">Last</a></li>
    </ul>
    <div class="currentPage"></div>
    <div class="rowsPerPage">
        Items Per Page:
        <select class="perPage">
            <option value="10">10</option>
            <option value="20">20</option>
            <option value="-1">Show All</option>
        </select>
    </div>
</div>


<script>
YUI().use('paginator', 'node', 'datatype-number-format', function (Y) {
    // node references
    var demo = Y.one('#demo'),
        tbody = demo.one('tbody'),
        controls = demo.one('.controls'),

        // templates
        rowTemplate = '<tr><td>{count}</td><td>{state}</td><td>{abbr}</td><td align="right">{population}</td></tr>',
        currentPageTemplate = 'Page {page} of {totalPages}',

        // data to display
        data = [
            { state: "Alabama",        abbr: "AL", population: 4447100  },
            { state: "Alaska",         abbr: "AK", population: 626932   },
            { state: "Arizona",        abbr: "AZ", population: 5130632  },
            { state: "Arkansas",       abbr: "AR", population: 2673400  },
            { state: "California",     abbr: "CA", population: 33871648 },
            { state: "Colorado",       abbr: "CO", population: 4301261  },
            { state: "Connecticut",    abbr: "CT", population: 3405565  },
            { state: "Delaware",       abbr: "DE", population: 783600   },
            { state: "Florida",        abbr: "FL", population: 15982378 },
            { state: "Georgia",        abbr: "GA", population: 8186453  },
            { state: "Hawaii",         abbr: "HI", population: 1211537  },
            { state: "Idaho",          abbr: "ID", population: 1293953  },
            { state: "Illinois",       abbr: "IL", population: 12419293 },
            { state: "Indiana",        abbr: "IN", population: 6080485  },
            { state: "Iowa",           abbr: "IA", population: 2926324  },
            { state: "Kansas",         abbr: "KS", population: 2688418  },
            { state: "Kentucky",       abbr: "KY", population: 4041769  },
            { state: "Louisiana",      abbr: "LA", population: 4468976  },
            { state: "Maine",          abbr: "ME", population: 1274923  },
            { state: "Maryland",       abbr: "MD", population: 5296486  },
            { state: "Massachusetts",  abbr: "MA", population: 6349097  },
            { state: "Michigan",       abbr: "MI", population: 9938444  },
            { state: "Minnesota",      abbr: "MN", population: 4919479  },
            { state: "Mississippi",    abbr: "MS", population: 2844658  },
            { state: "Missouri",       abbr: "MO", population: 5595211  },
            { state: "Montana",        abbr: "MT", population: 902195   },
            { state: "Nebraska",       abbr: "NE", population: 1711263  },
            { state: "Nevada",         abbr: "NV", population: 1998257  },
            { state: "New Hampshire",  abbr: "NH", population: 1235786  },
            { state: "New Jersey",     abbr: "NJ", population: 8414350  },
            { state: "New Mexico",     abbr: "NM", population: 1819046  },
            { state: "New York",       abbr: "NY", population: 18976457 },
            { state: "North Carolina", abbr: "NC", population: 8049313  },
            { state: "North Dakota",   abbr: "ND", population: 642200   },
            { state: "Ohio",           abbr: "OH", population: 11353140 },
            { state: "Oklahoma",       abbr: "OK", population: 3450654  },
            { state: "Oregon",         abbr: "OR", population: 3421399  },
            { state: "Pennsylvania",   abbr: "PA", population: 12281054 },
            { state: "Rhode Island",   abbr: "RI", population: 1048319  },
            { state: "South Carolina", abbr: "SC", population: 4012012  },
            { state: "South Dakota",   abbr: "SD", population: 754844   },
            { state: "Tennessee",      abbr: "TN", population: 5689283  },
            { state: "Texas",          abbr: "TX", population: 20851820 },
            { state: "Utah",           abbr: "UH", population: 2233169  },
            { state: "Vermont",        abbr: "VT", population: 608827   },
            { state: "Virginia",       abbr: "VA", population: 7078515  },
            { state: "Washington",     abbr: "WA", population: 5894121  },
            { state: "West Virginia",  abbr: "WV", population: 1808344  },
            { state: "Wisconsin",      abbr: "WI", population: 5363675  },
            { state: "Wyoming",        abbr: "WY", population: 493782   }
        ],

        // paginator
        pg = new Y.Paginator({
            itemsPerPage: 10,
            totalItems: data.length
        });

    // let's do a one time pass through the data to format the population numbers
    Y.Array.each(data, function (val, key) {
        val.population = Y.Number.format(val.population, {thousandsSeparator: ','});
    });


    Y.one('#demo').delegate('click', function (e) {
        e.preventDefault();

        var control = e.currentTarget,
            type = control.getData('type');

        if (control.hasClass('disabled')) {
            return;
        }

        switch (type) {
            case 'first': pg.set('page', 1); break;
            case 'prev': pg.prevPage(); break;
            case 'next': pg.nextPage(); break;
            case 'last': pg.set('page', pg.get('totalPages')); break;
        }

    }, '.control');

    Y.one('#demo .perPage').on('change', function (e) {
        pg.set('itemsPerPage', e.currentTarget.get('value'));
    });


    function showPage(page) {
        // get the page number from the paginator if there isn't one provided
        page || (page = pg.get('page'));

        // number of items to display
        var itemsPerPage = pg.get('itemsPerPage'),
            totalItems = pg.get('totalItems') - 1,
            min = (page - 1) * itemsPerPage,
            max = Math.min( (page * itemsPerPage) - 1, totalItems),
            rows = '',
            i;

        // if our math left us with less than the minimum, use the total number of items
        if (max < min) {
            max = totalItems;
        }

        // loop through the data and build the templates
        for (i = min; i <= max; i++ ) {
            rows += Y.Lang.sub(rowTemplate, Y.mix({count: i + 1}, data[i]));
        }

        // set the table's body to the new rows
        tbody.setHTML(rows);

        // update the paginator's display
        Y.all('.currentPage').set('text',
            Y.Lang.sub(currentPageTemplate, pg.getAttrs())
        );
    }

    function updateControls() {
        var hasNext = pg.hasNextPage(),
            hasPrev = pg.hasPrevPage();

        controls.one('.control-first a').toggleClass('disabled', !hasPrev);
        controls.one('.control-prev a').toggleClass('disabled', !hasPrev);
        controls.one('.control-next a').toggleClass('disabled', !hasNext);
        controls.one('.control-last a').toggleClass('disabled', !hasNext);
    }

    pg.after('itemsPerPageChange', function () {
        if (pg.get('page') === 1) {
            showPage();
            updateControls();
        } else {
            pg.set('page', 1);
        }
    });

    pg.after('pageChange', function (e) {
        showPage(e.newVal);
        updateControls();
    });

    showPage();
    updateControls();

});
</script>