An HTML table containing 50 states and their population with an attached paginator to view the information in small sections.
State | Abbr | Population |
---|
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 Node
s 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>