Input Paging plugin
DataTables 2 introduces the concept of features, which are table control and information components that are placed around the table with the layout
option. There are several features built-in, and some of the extensions add their own, but I'd like to use this blog post to introduce a new feature plugin for DataTables, demonstrating its abilities, and also detailing just how easy it is to make a feature plugin for DataTables.
The feature plugin I'll introduce here is called InputPaging and it provides a control that can replace the built-in paging
feature. Its abilities include:
- Showing the end user the current page number
- Allowing the end user to type the page number they want to view
- Optional previous/next buttons
- Optional first/last buttons
- Optional total page count
- Integrates automatically with DataTable's default styling, Bootstrap 3/4/5, Bulma, Foundation, jQuery UI and FomanticUI.
Here is an example of it in practice (the InputPaging control is at the bottom right of the table):
Name | Position | Office | Salary |
---|---|---|---|
Tiger Nixon | System Architect | Edinburgh | $320,800 |
Garrett Winters | Accountant | Tokyo | $170,750 |
Ashton Cox | Junior Technical Author | San Francisco | $86,000 |
Cedric Kelly | Senior Javascript Developer | Edinburgh | $433,060 |
Airi Satou | Accountant | Tokyo | $162,700 |
Brielle Williamson | Integration Specialist | New York | $372,000 |
Herrod Chandler | Sales Assistant | San Francisco | $137,500 |
Rhona Davidson | Integration Specialist | Tokyo | $327,900 |
Colleen Hurst | Javascript Developer | San Francisco | $205,500 |
Sonya Frost | Software Engineer | Edinburgh | $103,600 |
Jena Gaines | Office Manager | London | $90,560 |
Quinn Flynn | Support Lead | Edinburgh | $342,000 |
Charde Marshall | Regional Director | San Francisco | $470,600 |
Haley Kennedy | Senior Marketing Designer | London | $313,500 |
Tatyana Fitzpatrick | Regional Director | London | $385,750 |
Michael Silva | Marketing Designer | London | $198,500 |
Paul Byrd | Chief Financial Officer (CFO) | New York | $725,000 |
Gloria Little | Systems Administrator | New York | $237,500 |
Bradley Greer | Software Engineer | London | $132,000 |
Dai Rios | Personnel Lead | Edinburgh | $217,500 |
Jenette Caldwell | Development Lead | New York | $345,000 |
Yuri Berry | Chief Marketing Officer (CMO) | New York | $675,000 |
Caesar Vance | Pre-Sales Support | New York | $106,450 |
Doris Wilder | Sales Assistant | Sydney | $85,600 |
Angelica Ramos | Chief Executive Officer (CEO) | London | $1,200,000 |
Gavin Joyce | Developer | Edinburgh | $92,575 |
Jennifer Chang | Regional Director | Singapore | $357,650 |
Brenden Wagner | Software Engineer | San Francisco | $206,850 |
Fiona Green | Chief Operating Officer (COO) | San Francisco | $850,000 |
Shou Itou | Regional Marketing | Tokyo | $163,000 |
Michelle House | Integration Specialist | Sydney | $95,400 |
Suki Burks | Developer | London | $114,500 |
Prescott Bartlett | Technical Author | London | $145,000 |
Gavin Cortez | Team Leader | San Francisco | $235,500 |
Martena Mccray | Post-Sales support | Edinburgh | $324,050 |
Unity Butler | Marketing Designer | San Francisco | $85,675 |
Howard Hatfield | Office Manager | San Francisco | $164,500 |
Hope Fuentes | Secretary | San Francisco | $109,850 |
Vivian Harrell | Financial Controller | San Francisco | $452,500 |
Timothy Mooney | Office Manager | London | $136,200 |
Jackson Bradshaw | Director | New York | $645,750 |
Olivia Liang | Support Engineer | Singapore | $234,500 |
Bruno Nash | Software Engineer | London | $163,500 |
Sakura Yamamoto | Support Engineer | Tokyo | $139,575 |
Thor Walton | Developer | New York | $98,540 |
Finn Camacho | Support Engineer | San Francisco | $87,500 |
Serge Baldwin | Data Coordinator | Singapore | $138,575 |
Zenaida Frank | Software Engineer | New York | $125,250 |
Zorita Serrano | Software Engineer | San Francisco | $115,000 |
Jennifer Acosta | Junior Javascript Developer | Edinburgh | $75,650 |
Cara Stevens | Sales Assistant | New York | $145,600 |
Hermione Butler | Regional Director | London | $356,250 |
Lael Greer | Systems Administrator | London | $103,500 |
Jonas Alexander | Developer | San Francisco | $86,500 |
Shad Decker | Regional Director | Edinburgh | $183,000 |
Michael Bruce | Javascript Developer | Singapore | $183,000 |
Donna Snider | Customer Support | New York | $112,000 |
Usage
Before detailing how to create your own custom feature plugins, let's explore how to use the InputPaging plugin.
Initialisation
The initialisation of this example is done quite simply with:
new DataTable('#inputPaging', {
layout: {
bottomEnd: 'inputPaging'
}
});
Note that this example has the new paging control replacing the default paging
control which is normally at the bottomEnd
of the table. Due to how layout
works, it would be quite possible to have this control shown both above and below the table, or to show the default paging feature as well as this one if you so wished!
Sources
The sources for this plugin are available in the DataTables plugins git repo and are released under the MIT license. The distribution files to include directly on your page are available on the DataTables CDN:
If you are using a bundler, this plugin is available on npm as datatables.net-feature-inputpaging
. To use it with ES Modules, simply include the package and it will register itself as available as a feature:
import DataTable from 'datatables.net-dt';
import 'datatables.net-feature-inputpaging';
new DataTable('#example', {
layout: {
bottomEnd: 'inputPaging'
}
});
This StackBlitz example demonstrates the new plugin being used in a Typescript + Vite build environment.
Options
The inputPaging
feature has three options:
boolean
firstLast
which controls if the First and Last buttons are shownboolean
previousNext
which controls if the Previous and Next buttons are shownboolean
pageOf
which controls if the/ {pages}
text is shown immediately after theinput
for the current page number.
To set an option, use the inputPaging
option as an object in layout
- e.g.:
new DataTable('#example', {
layout: {
bottomEnd: {
inputPaging: {
pageOf: false
}
}
}
});
Creating a feature plugin
Creating a feature plugin for DataTables is very simple - use the DataTable.feature.register()
static method to register your feature with DataTables. The reference documentation has full details of the method, but essentially it takes two parameters; the feature name (i.e. how to reference it in layout
), and a function to create the DOM elements and event listeners for the feature. This function should return a DOM node and is passed in two parameters - the DataTable settings object, and the options for the feature.
It is much easier to picture in code:
DataTable.feature.register('myToolbar', function (settings, opts) {
// Define defaults
let options = Object.assign({
option1: false,
option2: false
}, opts);
let container = document.createElement('div');
// do something with the options and container
// ...
return container;
});
That's it - you've created a DataTables feature plugin! Now let's explore how to build the paging control.
DOM elements
We need to create several DOM elements with event handlers. For the discussion here we'll work with DataTables' default styling, so we need to use the same structure as the paging
control. This is what we want:
<div class="dt-inputpaging dt-paging">
<button class="dt-paging-button disabled">«</button>
<button class="dt-paging-button disabled">‹</button>
<div class="dt-paging-input">
<input class="" type="text" inputmode="numeric" pattern="[0-9]*" style="width: 3ch;">
<span class=""> / 6</span>
</div>
<button class="dt-paging-button">›</button>
<button class="dt-paging-button">»</button>
</div>
Creating DOM nodes in vanilla JS is always a verbose operation in code, so let's create a helper function for it:
/**
* Create a new element
*
* @param tag Tag name
* @param className Class to assign
* @param text Text to show in the element
* @param fn Click event handler
* @returns Element
*/
function createElement(tag, className, text, fn) {
var el = document.createElement(tag);
if (className) {
el.className = className;
}
if (text) {
el.textContent = text;
}
if (fn) {
el.addEventListener('click', fn);
}
return el;
}
Then creating the "first" button might look like the following. Please note the use of i18n
to get the language string defined for the first paging button. This ensures full integration with DataTable's existing options. Additionally, page()
is used in the event handler to trigger the page change.
let first = createElement(
'button',
'dt-paging',
api.i18n('oPaginate.sFirst', '\u00AB'),
() => api.page('first').draw(false)
);
Each of the buttons is created and then attached to a parent element, the one returned from the feature creation function. Despite the use of the helper function, this code is still fairly verbose, so for brevity, please see the code here.
Input event handler
This is where it starts to get a little bit more interesting. Only number values have any relevance in the input
element, so we can use an event handler to disable the end user for typing non-numeric characters:
// Block characters other than numbers
input.addEventListener('keypress', function (e) {
if (e.charCode < 48 || e.charCode > 57) {
e.preventDefault();
}
});
We also want to take two actions when the end user enters a new number into the input
. The first is obvious - we need to use page()
to change the DataTable's display page to the new number (it has built-in correction should the end user enter a page that doesn't exist).
The second action is to adjust the width of the input
to fit the content. This is useful for the end user as it lets them see the full page number, without taking more width than is required (important as it allows for tables with millions of rows). For this, we use the number of characters in the string with the ch
unit which is the width of a 0
(similar to em
, but for digits!):
// On new value, redraw the table
input.addEventListener('input', function () {
if (input.value) {
api.page(input.value - 1).draw(false);
}
// Auto adjust the width so the content is visible
input.style.width = (input.value.length + 2) + 'ch';
});
DataTables event
Finally, when the DataTable is drawn it will issue a draw
event, at which point we need to update our control with the following actions:
- Indicate visually if any of the buttons are disabled (e.g. no point in allowing a click on the "First" button when already on the first page!). This is done by simply adding or removing the
disabled
class from the buttons created above. Thepage.info()
method is used to get the data used for these logic checks. - Next, if an external operation changes the current page (e.g. a search will reset the paging) we need to write the new value into the
input
so the end user can see what page they are on. - And finally the total number of pages indicator needs to be updated, also using the information from
page.info()
.
api.on('draw', () => {
let info = api.page.info();
// Update the classes for the "jump" buttons to show what is available
first.classList.toggle('disabled', info.page === 0);
previous.classList.toggle('disabled', info.page === 0);
next.classList.toggle('disabled', info.page === info.pages-1);
last.classList.toggle('disabled', info.page === info.pages-1);
// Set the new page value into the input box
if (input.value !== info.page + 1) {
input.value = info.page + 1;
}
// Show how many pages there are
of.textContent = ' / ' + info.pages;
});
Just the start
The complete listing for the code built above can be found here. You might have noticed that this code doesn't take into account the different styling frameworks that DataTables supports, such as Bootstrap and Bulma. I've simplified the actual code used for the fully featured plugin to keep this discussion somewhat more manageable! If you are interested, check out the source for the plugin - the key is how to handle the DOM structure, which is slightly different for each styling framework.
Creating your own plugins
If you are feeling inspired and create your own feature plugin, please get in touch and share it with the community! I'd love to see what you can cook up.