Tuesday 28th May, 2024
By Allan Jardine

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):

NamePositionOfficeSalary
Tiger NixonSystem ArchitectEdinburgh$320,800
Garrett WintersAccountantTokyo$170,750
Ashton CoxJunior Technical AuthorSan Francisco$86,000
Cedric KellySenior Javascript DeveloperEdinburgh$433,060
Airi SatouAccountantTokyo$162,700
Brielle WilliamsonIntegration SpecialistNew York$372,000
Herrod ChandlerSales AssistantSan Francisco$137,500
Rhona DavidsonIntegration SpecialistTokyo$327,900
Colleen HurstJavascript DeveloperSan Francisco$205,500
Sonya FrostSoftware EngineerEdinburgh$103,600
Jena GainesOffice ManagerLondon$90,560
Quinn FlynnSupport LeadEdinburgh$342,000
Charde MarshallRegional DirectorSan Francisco$470,600
Haley KennedySenior Marketing DesignerLondon$313,500
Tatyana FitzpatrickRegional DirectorLondon$385,750
Michael SilvaMarketing DesignerLondon$198,500
Paul ByrdChief Financial Officer (CFO)New York$725,000
Gloria LittleSystems AdministratorNew York$237,500
Bradley GreerSoftware EngineerLondon$132,000
Dai RiosPersonnel LeadEdinburgh$217,500
Jenette CaldwellDevelopment LeadNew York$345,000
Yuri BerryChief Marketing Officer (CMO)New York$675,000
Caesar VancePre-Sales SupportNew York$106,450
Doris WilderSales AssistantSydney$85,600
Angelica RamosChief Executive Officer (CEO)London$1,200,000
Gavin JoyceDeveloperEdinburgh$92,575
Jennifer ChangRegional DirectorSingapore$357,650
Brenden WagnerSoftware EngineerSan Francisco$206,850
Fiona GreenChief Operating Officer (COO)San Francisco$850,000
Shou ItouRegional MarketingTokyo$163,000
Michelle HouseIntegration SpecialistSydney$95,400
Suki BurksDeveloperLondon$114,500
Prescott BartlettTechnical AuthorLondon$145,000
Gavin CortezTeam LeaderSan Francisco$235,500
Martena MccrayPost-Sales supportEdinburgh$324,050
Unity ButlerMarketing DesignerSan Francisco$85,675
Howard HatfieldOffice ManagerSan Francisco$164,500
Hope FuentesSecretarySan Francisco$109,850
Vivian HarrellFinancial ControllerSan Francisco$452,500
Timothy MooneyOffice ManagerLondon$136,200
Jackson BradshawDirectorNew York$645,750
Olivia LiangSupport EngineerSingapore$234,500
Bruno NashSoftware EngineerLondon$163,500
Sakura YamamotoSupport EngineerTokyo$139,575
Thor WaltonDeveloperNew York$98,540
Finn CamachoSupport EngineerSan Francisco$87,500
Serge BaldwinData CoordinatorSingapore$138,575
Zenaida FrankSoftware EngineerNew York$125,250
Zorita SerranoSoftware EngineerSan Francisco$115,000
Jennifer AcostaJunior Javascript DeveloperEdinburgh$75,650
Cara StevensSales AssistantNew York$145,600
Hermione ButlerRegional DirectorLondon$356,250
Lael GreerSystems AdministratorLondon$103,500
Jonas AlexanderDeveloperSan Francisco$86,500
Shad DeckerRegional DirectorEdinburgh$183,000
Michael BruceJavascript DeveloperSingapore$183,000
Donna SniderCustomer SupportNew 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:

CSS
JS

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 shown
  • boolean previousNext which controls if the Previous and Next buttons are shown
  • boolean pageOf which controls if the / {pages} text is shown immediately after the input 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:

  1. 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. The page.info() method is used to get the data used for these logic checks.
  2. 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.
  3. 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.