Overview

DataTable component based on datatables.net with advanced instructions.

Reference

Instance Methods
Name Description
public constructor() Create a new instance of the Datatable class.
Parameters:
  • element: string|HTMLTableElement|JQuery
    HTMLTableElement selector, element, or JQuery object.
  • options: DatatableOptions
    An object with the following custom options inherited from DataTables.Settings.
    • firstAjax?: boolean
      If asynchronous mode (options.ajax) is enabled, whether to request table data remotely first. If true, request table data first; if false, do not request table data until the Datatable.reload method is called. Default is true.
    • locale?: 'en'|'ja'
      Locale of the displayed text. Default is English (en).
The following values are used as defaults for the options.
  • scrollX: boolean true
  • dom: string `<'row'<'col-12 dataTables_pager'p>><'row'<'col-12'tr>><'row'<'col-12 dataTables_pager'p>>`
  • pageLength: number 30
  • searchDelay: number 500
  • processing: boolean true
  • serverSide: boolean If the ajax option is present, it is automatically set to true.
  • fnServerParams: (aoData: any) => If Ajax is enabled, the following data is sent to the server.
    • start: number Starting point of the current data set (0 index base, i.e., 0 is the first record).
    • length: number The number of records the table can display.
    • order: string Columns to be ordered.
    • dir: string Direction of sorting. It will be asc or desc, indicating ascending or descending order, respectively.
    • draw: number A drawing counter. Used to ensure that responses from the server are drawn in sequence.
                    import {components} from 'metronic-extension';

// Initialize the component and set up event listeners.
let targetIndex = 0;
const myTable = new components.Datatable(document.getElementById('myTable'), {
  columnDefs: [
    {targets: targetIndex++, data: 'name'},
    {targets: targetIndex++, data: 'position'},
  ]
});

targetIndex = 0;
const myTableWithAjax = new components.Datatable(document.getElementById('myTableWithAjax'), {
  ajax: {
    url: '/api/persons/pages'
  },
  columnDefs: [
    {targets: targetIndex++, data: 'name'},
    {targets: targetIndex++, data: 'position'},
  ]
});
                  
public reload() Reload the table data from the Ajax data source.
Parameters:
  • resetPaging: boolean
    Reset (default action or true) or hold the current paging position (false).
Return:
  • Promise<any> JSON data returned by the server.
public adjustColumns() Adjust column layout.
public filter() Filter row by the specified string.
Parameters:
  • columnSelector: any
    Column selector. See here for more information.
  • input: string
    Search string to apply to the table.
  • regex: boolean
    Whether to treat input as a regular expression (true) or not (default is false).
                        <table id="myTable" class="table table-row-bordered gy-5">
  <thead>
    <tr class="text-start text-gray-700 fw-bold fs-7 gs-0">
      <th>Name</th>
      <th>Position</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Airi Satou</td>
      <td>Accountant</td>
    </tr>
    <tr>
      <td>Angelica Ramos</td>
      <td>Chief Executive Officer (CEO)</td>
    </tr>
  </tbody>
</table>
                      
                        import {components} from 'metronic-extension';

// Initialize the component and set up event listeners.
let targetIndex = 0;
const myTable = new components.Datatable(document.getElementById('myTable'), {
  columnDefs: [
    {targets: targetIndex++, name: 'name'},
    {targets: targetIndex++, name: 'position'},
  ],
});

// Search by column position.
myTable.filter(1, 'CEO');

// Search by column name.
myTable.filter('position:name', 'CEO');
                      
public getContainer() Returns a table wrapper element.
Parameters:
  • asHtmlElement: boolean
    If true, get it as an HTMLElement, if false, get it as a jQuery object. Default is false.
Return:
  • JQuery|HTMLElement Table wrapper element.
public getFilterContainer() Returns a table filter container element.
Parameters:
  • asHtmlElement: boolean
    If true, get it as an HTMLElement, if false, get it as a jQuery object. Default is false.
Return:
  • JQuery|HTMLElement|null Filter container element.
public createRow() Create a row.
Parameters:
  • data: any
    Data to use for the new row. This may be an array, object, Javascript object instance or a tr element. If a data structure is used (i.e. array or object) it must be in the same format as the other data in the table (i.e. if your table uses objects, pass in an object with the same properties here!).
  • paging: boolean
    The type of drawing after the row is added. If true, paging is reset to the first page; if false Paging is not reset and the current page is displayed. Default is true.
Return:
  • Datatable
                        <table id="myTable" class="table table-row-bordered gy-5">
  <thead>
    <tr class="text-start text-gray-700 fw-bold fs-7 gs-0">
      <th>Name</th>
      <th>Position</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Airi Satou</td>
      <td>Accountant</td>
    </tr>
    <tr>
      <td>Angelica Ramos</td>
      <td>Chief Executive Officer (CEO)</td>
    </tr>
  </tbody>
</table>
                      
                        import {components} from 'metronic-extension';

// Initialize the component and set up event listeners.
let targetIndex = 0;
const myTable = new components.Datatable(document.getElementById('myTable'), {
  columnDefs: [
    {targets: targetIndex++, data: 'name'},
    {targets: targetIndex++, data: 'position'},
  ],
});

// Add object as a new row.
myTable.createRow({name: 'Ashton Cox', position: 'Junior Technical Author'});

// Add HTML element as a new row.
const row = document.createElement('tr');
row.insertAdjacentHTML('afterbegin', '<td>Bradley Greer</td><td>Software Engineer</td>');
myTable.createRow(row);
                      
public deleteRow() Delete row.
Parameters:
  • rowSelector: any
    Row selector. See here for more information.
Return:
  • Datatable
                        <table id="myTable" class="table table-row-bordered gy-5">
  <thead>
    <tr class="text-start text-gray-700 fw-bold fs-7 gs-0">
      <th>Name</th>
      <th>Position</th>
      <th>#</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Airi Satou</td>
      <td>Accountant</td>
      <td><button data-on-delete class="btn btn-primary">Delete</button></td>
    </tr>
    <tr>
      <td>Angelica Ramos</td>
      <td>Chief Executive Officer (CEO)</td>
      <td><button data-on-delete class="btn btn-primary">Delete</button></td>
    </tr>
  </tbody>
</table>
                      
                        import {components} from 'metronic-extension';

// Initialize the component and set up event listeners.
let targetIndex = 0;
const myTable = new components.Datatable(document.getElementById('myTable'), {
  columnDefs: [
    {targets: targetIndex++, data: 'name'},
    {targets: targetIndex++, data: 'position'},
    {targets: targetIndex++, data: 'actions'},
  ],
});

// Delete at specified position; the second row is deleted.
myTable.deleteRow(1);

// Specify a row element to delete.
$('#myTable tbody').on('click', '[data-on-delete]', event => {
  // Get selection row.
  const row = event.currentTarget.closest('tr');

  // Delete row.
  myTable.deleteRow(row);
});
                      
public updateRow() Update row.
Parameters:
  • rowSelector: any
    Row selector. See here for more information.
  • data: any[]|object
    Data source object for the data source of the row. This will be an array if you use DOM sourced data, otherwise it will be the array / object / instance that is used to populate the table with data.
  • redraw: boolean
    Reloads the table data after updating a row if true, otherwise does not. Default is true.
Return:
  • Datatable
                        <table id="myTable" class="table table-row-bordered gy-5">
  <thead>
    <tr class="text-start text-gray-700 fw-bold fs-7 gs-0">
      <th>Name</th>
      <th>Position</th>
      <th>#</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Airi Satou</td>
      <td>Accountant</td>
      <td><button data-on-update class="btn btn-primary">Update name</button></td>
    </tr>
    <tr>
      <td>Angelica Ramos</td>
      <td>Chief Executive Officer (CEO)</td>
      <td><button data-on-update class="btn btn-primary">Update name</button></td>
    </tr>
  </tbody>
</table>
                      
                        import {components} from 'metronic-extension';

// Initialize the component and set up event listeners.
let targetIndex = 0;
const myTable = new components.Datatable(document.getElementById('myTable'), {
  columnDefs: [
    {targets: targetIndex++, data: 'name'},
    {targets: targetIndex++, data: 'position'},
    {targets: targetIndex++, data: 'actions'},
  ],
});

$('#myTable tbody').on('click', '[data-on-update]', event => {
  // Display name input dialog.
	const name = window.prompt('Please enter a new name.');
  if (!name)
    // Cancel input or do nothing if input is empty.
    return;

  // Get selection row.
  const row = event.currentTarget.closest('tr');

  // Update the name column of row.
  myTable.updateRow(row, {name}, false);
});
                      
public getRowData() Gets the data for a single row or all rows in the DataTable. You can access the column data in a row using the name specified in the data property during column definition.
Parameters:
  • rowSelector: any
    Row selector. See here for more information.
Return:
  • any[]|object An array of row data objects if no row selector is provided, or a single row data object if a selector is provided.
                        <table id="myTable" class="table table-row-bordered gy-5">
  <thead>
    <tr class="text-start text-gray-700 fw-bold fs-7 gs-0">
      <th>Name</th>
      <th>Position</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Airi Satou</td>
      <td>Accountant</td>
    </tr>
    <tr>
      <td>Angelica Ramos</td>
      <td>Chief Executive Officer (CEO)</td>
    </tr>
  </tbody>
</table>
                      
                        import {components} from 'metronic-extension';

// Initialize the component and set up event listeners.
let targetIndex = 0;
const myTable = new components.Datatable(document.getElementById('myTable'), {
  columnDefs: [
    {targets: targetIndex++, data: 'name'},
    {targets: targetIndex++, data: 'position'},
  ],
});

// Retrieve the first row of data.
// The result is "{name: 'Airi Satou', position: 'Accountant'}".
const row = myTable.getRowData(0);

// Get all row data.
// The retrieved result is "[{name: 'Airi Satou', position: 'Accountant'}, {name: 'Angelica Ramos', position: 'Chief Executive Officer (CEO)'}]".
const rows = myTable.getRowData();
                      
public getRowCount() Get the number of rows.
Parameters:
  • rowSelector: any|undefined
    Row selector. See here for more information.
Return:
  • number Number of rows.
                    // Get the number of rows for which the ".select" CSS class is set.
myTable.getRowCount('.selected');

// Get the number of all rows.
myTable.getRowCount();
                  
public getRowNodes() Get row HTML elements.
Return:
  • HTMLTableRowElement[] HTML elements of row.
public getRowObject() Get the DataTable API instance containing the selected rows.
Parameters:
  • rowSelector: any
    Row selector. See here for more information.
Return:
  • DataTables.RowsMethods DataTable API instance containing the selected rows.
public column() Select the column found by a the column selector.
Parameters:
  • columnSelector: any
    Column selector. See here for more information.
  • modifier?: DataTables.ObjectSelectorModifier|undefined
    Specifies the order, paging, and filtering status of the selected columns. Default is none (undefined). See here for more information.
Return:
  • DataTables.ColumnMethods DataTable API instance with selected column in the result set.
public clear() Clear the table of all data.
Return:
  • Datatable
protected ajaxErrorHook() Hook function called when an error occurs in an Ajax request to retrieve table data that can be implemented in a subclass.
This method receives an HTTP status code and an XMLHttpRequest object.
Parameters:
  • httpStatusCode: number
    HTTP status code.
  • xhr: XMLHttpRequest
    XMLHttpRequest object.
                    ajaxErrorHook(httpStatusCode, xhr) {
  if (httpStatusCode === 403)
    // Redirect in case of authentication error (403).
    location.replace('/');
}
                  
Instance Properties
Name Description
public api: DataTables.Api DataTables.Api instance. This is read-only.

Basic

Simple DataTable with no server-side processing.
DataTable has most features enabled by default, so all you need to do to use it with your own tables is to call the construction function.
Name Position Office Age Start date Salary
Tiger Nixon System Architect Edinburgh 61 2011/04/25 320800
Garrett Winters Accountant Tokyo 63 2011/07/25 170750
Ashton Cox Junior Technical Author San Francisco 66 2009/01/12 86000
Cedric Kelly Senior Javascript Developer Edinburgh 22 2012/03/29 433060
              <!--begin::Wrapper-->
<div class="d-flex flex-stack flex-wrap mb-5">
  <!--begin::Search-->
  <div class="d-flex align-items-center position-relative my-1 mb-2 mb-md-0">
    <!--begin::Svg Icon | path: icons/duotune/general/gen021.svg-->
    <span class="svg-icon svg-icon-1 position-absolute ms-4">
      <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
        <rect opacity="0.5" x="17.0365" y="15.1223" width="8.15546" height="2" rx="1" transform="rotate(45 17.0365 15.1223)" fill="currentColor" />
        <path d="M11 19C6.55556 19 3 15.4444 3 11C3 6.55556 6.55556 3 11 3C15.4444 3 19 6.55556 19 11C19 15.4444 15.4444 19 11 19ZM11 5C7.53333 5 5 7.53333 5 11C5 14.4667 7.53333 17 11 17C14.4667 17 17 14.4667 17 11C17 7.53333 14.4667 5 11 5Z" fill="currentColor" />
      </svg>
    </span>
    <!--end::Svg Icon-->
    <input data-ref="basicTableKeyword" data-on-search-basic-table type="text" class="form-control form-control-solid w-300px ps-15" placeholder="Search by name" maxlength="70" />
  </div>
  <!--end::Search-->
</div>
<!--end::Wrapper-->
<!--begin::Table-->
<table data-ref="basicTable" class="table table-row-bordered gy-5">
  <thead>
    <tr class="text-start text-gray-700 fw-bold fs-7 gs-0">
      <th>Name</th>
      <th>Position</th>
      <th>Office</th>
      <th>Age</th>
      <th>Start date</th>
      <th>Salary</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Tiger Nixon</td>
      <td>System Architect</td>
      <td>Edinburgh</td>
      <td>61</td>
      <td>2011/04/25</td>
      <td>320800</td>
    </tr>
    <tr>
      <td>Garrett Winters</td>
      <td>Accountant</td>
      <td>Tokyo</td>
      <td>63</td>
      <td>2011/07/25</td>
      <td>170750</td>
    </tr>
    <tr>
      <td>Ashton Cox</td>
      <td>Junior Technical Author</td>
      <td>San Francisco</td>
      <td>66</td>
      <td>2009/01/12</td>
      <td>86000</td>
    </tr>
    <tr>
      <td>Cedric Kelly</td>
      <td>Senior Javascript Developer</td>
      <td>Edinburgh</td>
      <td>22</td>
      <td>2012/03/29</td>
      <td>433060</td>
    </tr>
  </tbody>
</table>
<!--end::Table-->
            
              import {components} from 'metronic-extension';

// Initialize the DataTable.
const initBasicTable = () => {
  let targetIndex = 0;
  return new components.Datatable(ref.basicTable, {
    columnDefs: [
      {targets: targetIndex++, name: 'name'},
      {targets: targetIndex++, name: 'position'},
      {targets: targetIndex++, name: 'office'},
      {targets: targetIndex++, name: 'age'},
      {targets: targetIndex++, name: 'startDate'},
      {targets: targetIndex++, name: 'salary'},
    ],
    pageLength: 3
  });
}

// Initialize the form.
const initBasicTableSearchForm = () => {
  $('body').on('input', '[data-on-search-basic-table]', () => {
    // Filter data by keyword.
    basicTable.filter('name:name', ref.basicTableKeyword.val());
  });
}

// Get references to elements with data-ref attributes.
const ref = components.selectRef();

// Initialize the component and set up event listeners.
const basicTable = initBasicTable();
initBasicTableSearchForm();
            

Server-side processing

Server-side processing is required to make this demo work. Click here to learn how to build server-side processing.
Name Position Office Age Start date Salary
              <!--begin::Wrapper-->
<div class="d-flex flex-stack flex-wrap mb-5">
  <!--begin::Search-->
  <div class="d-flex align-items-center position-relative my-1 mb-2 mb-md-0">
    <!--begin::Svg Icon | path: icons/duotune/general/gen021.svg-->
    <span class="svg-icon svg-icon-1 position-absolute ms-4">
      <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
        <rect opacity="0.5" x="17.0365" y="15.1223" width="8.15546" height="2" rx="1" transform="rotate(45 17.0365 15.1223)" fill="currentColor" />
        <path d="M11 19C6.55556 19 3 15.4444 3 11C3 6.55556 6.55556 3 11 3C15.4444 3 19 6.55556 19 11C19 15.4444 15.4444 19 11 19ZM11 5C7.53333 5 5 7.53333 5 11C5 14.4667 7.53333 17 11 17C14.4667 17 17 14.4667 17 11C17 7.53333 14.4667 5 11 5Z" fill="currentColor" />
      </svg>
    </span>
    <!--end::Svg Icon-->
    <input data-ref="serverSideProcessingTableKeyword" data-on-search-server-side-processing-table type="text" class="form-control form-control-solid w-300px ps-15" placeholder="Search by name" maxlength="70" />
  </div>
  <!--end::Search-->
</div>
<!--end::Wrapper-->
<!--begin::Table-->
<table data-ref="serverSideProcessingTable" class="table table-row-bordered gy-5">
  <thead>
    <tr class="text-start text-gray-700 fw-bold fs-7 gs-0">
      <th>Name</th>
      <th>Position</th>
      <th>Office</th>
      <th>Age</th>
      <th>Start date</th>
      <th>Salary</th>
    </tr>
  </thead>
  <tbody></tbody>
</table>
<!--end::Table-->
            
              import {components} from 'metronic-extension';

// Initialize the DataTable.
const initServerSideProcessingTable = () => {
  let targetIndex = 0;
  return new components.Datatable(ref.serverSideProcessingTable, {
    ajax: {
      url: '/api/persons/pages',
      data: d => {
        // Set filter parameters.
        d.search = {keyword: ref.serverSideProcessingTableKeyword.val()};
      }
    },
    columnDefs: [
      {targets: targetIndex++, data: 'name'},
      {targets: targetIndex++, data: 'position'},
      {targets: targetIndex++, data: 'office'},
      {targets: targetIndex++, data: 'age'},
      {targets: targetIndex++, data: 'startDate'},
      {targets: targetIndex++, data: 'salary'},
    ],
    pageLength: 3
  });
}

// Initialize the form.
const initServerSideProcessingTableSearchForm = () => {
  $('body').on('input', '[data-on-search-server-side-processing-table]', () => {
    // Reload when the filter is changed.
    // The filter information is set in the parameters sent to the server from the ajax.data optional function.
    serverSideProcessingTable.reload();
  })
}

// Get references to elements with data-ref attributes.
const ref = components.selectRef();

// Initialize the component and set up event listeners.
const serverSideProcessingTable = initServerSideProcessingTable();
initServerSideProcessingTableSearchForm();
            
This table loads data by Ajax. The latest data that has been loaded is shown below. This data will update automatically as any additional data is loaded.
              {
  "data": [
    {
      "id": 5,
      "name": "Airi Satou",
      "position": "Accountant",
      "office": "Tokyo",
      "age": 33,
      "startDate": "2008-11-28",
      "salary": 162700
    },
    {
      "id": 25,
      "name": "Angelica Ramos",
      "position": "Chief Executive Officer (CEO)",
      "office": "London",
      "age": 47,
      "startDate": "2009-10-09",
      "salary": 1200000
    },
    {
      "id": 3,
      "name": "Ashton Cox",
      "position": "Junior Technical Author",
      "office": "San Francisco",
      "age": 66,
      "startDate": "2009-01-12",
      "salary": 86000
    }
  ],
  "recordsTotal": 57,
  "recordsFiltered": 57,
  "draw": "1"
}
            
This is a route script used in server-side processing created with express-sweet, which extends Node.js express.
Server-side processing scripts can be written in any language, using the protocol described in the DataTables documentation.
              const router = require('express').Router();
const {query, validationResult} = require('express-validator');
const PersonModel = require('../../models/PersonModel');

// Get person page data.
router.get('/pages', [
  // Validate parameters.
  query('start').not().isEmpty().isInt({min: 0}),
  query('length').not().isEmpty().isInt({min: 1}),
  query('order').not().isEmpty().isIn(['name', 'position', 'office', 'age', 'startDate', 'salary']),
  query('dir').not().isEmpty().isIn(['asc', 'desc']),
  query('search.keyword').trim().optional({nullable: true, checkFalsy: true}).isLength({max: 70}),
  query('draw').not().isEmpty().isInt({min: 1}),
], async (req, res) => {
  // Check validation results.
  const result = validationResult(req);
  if (!result.isEmpty())
    // If the parameter is invalid, a 400 error is returned.
    return void res.status(400).end();

  // Get page data.
  const data = await PersonModel.paginate(req.query);

  // Set the received drawing count as-is in the response.
  data.draw = req.query.draw;
  res.json(data);
});

module.exports = router;
            
This is a Model script used in server-side processing created with express-sweet, which extends Node.js express.
              const Model = require('express-sweet').database.Model;
const {merge} = require('deep-fusion');

module.exports = class extends Model {
  static get table() {
    return 'person';
  }

  static get attributes() {
    return {
      id: {
        type: this.DataTypes.INTEGER,
        primaryKey: true,
        autoIncrement: true
      },
      name: this.DataTypes.STRING,
      position: this.DataTypes.STRING,
      office: this.DataTypes.STRING,
      age: this.DataTypes.INTEGER,
      startDate: this.DataTypes.DATE,
      salary: this.DataTypes.DECIMAL(10, 2),
    };
  }

  /**
   * Get page data.
   */
  static async paginate(paginationOptions) {
    // Initialize options.
    paginationOptions = merge({
      start: 0,
      length: 30,
      order: 'name',
      dir: 'asc',
      search: {
        keyword: null,
      },
    }, paginationOptions);

    // Where clause for filtering data.
    const whereClause = {};
    if (paginationOptions.search.keyword)
      whereClause.name = {[super.Op.like]: `%${paginationOptions.search.keyword}%`};

    // Calculate total records.
    const recordsTotal = await super.count();

    // Calculate filtered records.
    const recordsFiltered = await super.count({where: whereClause});

    // Fetch paginated data.
    const data = await super.findAll({
      where: whereClause,
      offset: parseInt(paginationOptions.start, 10),
      limit: parseInt(paginationOptions.length, 10),
      order: [
        [super.col(paginationOptions.order), paginationOptions.dir],
      ],
      raw: true,
    });

    return {data, recordsTotal, recordsFiltered};
  }
}
            

Column visibility integration

This example shows how to show/hide table columns using the colvis plugin.
Name Position Office Age Start date Salary
Tiger Nixon System Architect Edinburgh 61 2011/04/25 320800
Garrett Winters Accountant Tokyo 63 2011/07/25 170750
Ashton Cox Junior Technical Author San Francisco 66 2009/01/12 86000
Cedric Kelly Senior Javascript Developer Edinburgh 22 2012/03/29 433060
              <!--begin::Table-->
<table data-ref="columnVisibilityTable" class="table table-row-bordered gy-5">
  <thead>
    <tr class="text-start text-gray-700 fw-bold fs-7 gs-0">
      <th>Name</th>
      <th>Position</th>
      <th>Office</th>
      <th>Age</th>
      <th>Start date</th>
      <th>Salary</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Tiger Nixon</td>
      <td>System Architect</td>
      <td>Edinburgh</td>
      <td>61</td>
      <td>2011/04/25</td>
      <td>320800</td>
    </tr>
    <tr>
      <td>Garrett Winters</td>
      <td>Accountant</td>
      <td>Tokyo</td>
      <td>63</td>
      <td>2011/07/25</td>
      <td>170750</td>
    </tr>
    <tr>
      <td>Ashton Cox</td>
      <td>Junior Technical Author</td>
      <td>San Francisco</td>
      <td>66</td>
      <td>2009/01/12</td>
      <td>86000</td>
    </tr>
    <tr>
      <td>Cedric Kelly</td>
      <td>Senior Javascript Developer</td>
      <td>Edinburgh</td>
      <td>22</td>
      <td>2012/03/29</td>
      <td>433060</td>
    </tr>
  </tbody>
</table>
<!--end::Table-->
            
              import {components} from 'metronic-extension';

// Initialize the DataTable.
const initColumnVisibilityTable = () => {
  let targetIndex = 0;
  return new components.Datatable(ref.columnVisibilityTable, {
    dom: `<'row align-items-center'<'col-auto'B><'col dataTables_pager'p>><'row'<'col-12'tr>><'row'<'col-12 dataTables_pager'p>>`,
    columnDefs: [
      {targets: targetIndex++, data: 'name'},
      {targets: targetIndex++, data: 'position'},
      {targets: targetIndex++, data: 'office'},
      {targets: targetIndex++, data: 'age'},
      {targets: targetIndex++, data: 'startDate'},
      {targets: targetIndex++, data: 'salary'},
    ],
    pageLength: 3,
    buttons: [
      // Button to toggle column visibility.
      {
        extend: 'colvis',
        text: 'Show / hide columns',
        // Columns selector that defines the columns to include in the column visibility button set.
        // CSS selectors, column indexes, etc. can be used to specify columns to switch visibility.
        columns: ':eq(1),:eq(2),:eq(3),:eq(4),:eq(5)',
        // columns: [1,2,3,4,5],
      }
    ],
    // Save the column visibility in the browser.
    stateSave: true,
    /* Enable/Disable saving for various datatables elements. Delete any items you do not wish to save.
        - data.columns.search: This option allows for the saving of the search applied to individual columns to be enabled or disabled.
        - data.columns.visible: This option allows for the saving of the visibility of the columns to be enabled or disabled.
        - data.length: This option allows for the saving of the page length to be enabled or disabled.
        - data.order: This option allows for the saving of the tables column sorting to be enabled or disabled.
        - data.paging: This option allows for the saving of the paging to be enabled or disabled.
        - data.scroller: This option allows for the saving of the scroller position to be enabled or disabled.
        - data.search: This option allows for the saving of the search to be enabled or disabled.
        - data.searchBuilder: This option allows for the saving of the searchBuilder state to be enabled or disabled.
        - data.searchPanes: This option allows for the saving of the searchPanes state to be enabled or-  disabled.
        - data.select: This option allows for the saving of the select state to be enabled or disabled.
    */
    stateSaveParams: (_, data) => {
      delete data.columns.search;
      // delete data.columns.visible;
      delete data.length;
      delete data.order;
      delete data.paging;
      delete data.scroller;
      delete data.search;
      delete data.searchBuilder;
      delete data.searchPanes;
      delete data.select;
    }
  });
}

// Get references to elements with data-ref attributes.
const ref = components.selectRef();

// Initialize the component and set up event listeners.
const columnVisibilityTable = initColumnVisibilityTable();
            

Column visibility with icon button

This is an example of using icon buttons to show/hide table columns.
Name Position Office Age Start date Salary
Tiger Nixon System Architect Edinburgh 61 2011/04/25 320800
Garrett Winters Accountant Tokyo 63 2011/07/25 170750
Ashton Cox Junior Technical Author San Francisco 66 2009/01/12 86000
Cedric Kelly Senior Javascript Developer Edinburgh 22 2012/03/29 433060
              <!--begin::Table-->
<table data-ref="columnVisibilityWithIconButtonTable" class="table table-row-bordered gy-5">
  <thead>
    <tr class="text-start text-gray-700 fw-bold fs-7 gs-0">
      <th>Name</th>
      <th>Position</th>
      <th>Office</th>
      <th>Age</th>
      <th>Start date</th>
      <th>Salary</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Tiger Nixon</td>
      <td>System Architect</td>
      <td>Edinburgh</td>
      <td>61</td>
      <td>2011/04/25</td>
      <td>320800</td>
    </tr>
    <tr>
      <td>Garrett Winters</td>
      <td>Accountant</td>
      <td>Tokyo</td>
      <td>63</td>
      <td>2011/07/25</td>
      <td>170750</td>
    </tr>
    <tr>
      <td>Ashton Cox</td>
      <td>Junior Technical Author</td>
      <td>San Francisco</td>
      <td>66</td>
      <td>2009/01/12</td>
      <td>86000</td>
    </tr>
    <tr>
      <td>Cedric Kelly</td>
      <td>Senior Javascript Developer</td>
      <td>Edinburgh</td>
      <td>22</td>
      <td>2012/03/29</td>
      <td>433060</td>
    </tr>
  </tbody>
</table>
<!--end::Table-->
            
              import {components} from 'metronic-extension';

// Initialize the DataTable.
const initColumnVisibilityWithIconButtonTable = () => {
  let targetIndex = 0;
  return new components.Datatable(ref.columnVisibilityWithIconButtonTable, {
    dom: `<'row align-items-center'<'col dataTables_pager'p><'col-auto'B>><'row'<'col-12'tr>><'row'<'col-12 dataTables_pager'p>>`,
    columnDefs: [
      {targets: targetIndex++, data: 'name'},
      {targets: targetIndex++, data: 'position'},
      {targets: targetIndex++, data: 'office'},
      {targets: targetIndex++, data: 'age'},
      {targets: targetIndex++, data: 'startDate'},
      {targets: targetIndex++, data: 'salary'},
    ],
    pageLength: 3,
    buttons: {
      dom: {
        button: {
          // Disable the default button class (btn btn-secondary) to set the icon button (btn-icon).
          className: null,
        },
        buttonLiner: {
          // By default, a span element is added within the button, so disable it.
          tag: null,
        }
      },
      buttons: [
        // Button to toggle column visibility.
        {
          extend: 'colvis',
          columns: ':eq(1),:eq(2),:eq(3),:eq(4),:eq(5)',
          text: '<i class="ki-solid ki-gear fs-1"></i>',

          // Icon button style. Switch between them as you wish.
          // className: 'btn btn-sm btn-icon btn-color-primary btn-active-light-primary',
          className: 'btn btn-icon btn-color-gray-500 btn-active-color-primary',

          // Display column drop-downs inside the table container. If this is not set, the column dropdowns will overflow the table container.
          align: 'container',
        }
      ],
    },
    // Save the column visibility in the browser.
    stateSave: true,
    /* Enable/Disable saving for various datatables elements. Delete any items you do not wish to save.
        - data.columns.search: This option allows for the saving of the search applied to individual columns to be enabled or disabled.
        - data.columns.visible: This option allows for the saving of the visibility of the columns to be enabled or disabled.
        - data.length: This option allows for the saving of the page length to be enabled or disabled.
        - data.order: This option allows for the saving of the tables column sorting to be enabled or disabled.
        - data.paging: This option allows for the saving of the paging to be enabled or disabled.
        - data.scroller: This option allows for the saving of the scroller position to be enabled or disabled.
        - data.search: This option allows for the saving of the search to be enabled or disabled.
        - data.searchBuilder: This option allows for the saving of the searchBuilder state to be enabled or disabled.
        - data.searchPanes: This option allows for the saving of the searchPanes state to be enabled or-  disabled.
        - data.select: This option allows for the saving of the select state to be enabled or disabled.
    */
    stateSaveParams: (_, data) => {
      delete data.columns.search;
      // delete data.columns.visible;
      delete data.length;
      delete data.order;
      delete data.paging;
      delete data.scroller;
      delete data.search;
      delete data.searchBuilder;
      delete data.searchPanes;
      delete data.select;
    }
  });
}

// Get references to elements with data-ref attributes.
const ref = components.selectRef();

// Initialize the component and set up event listeners.
const columnVisibilityWithIconButtonTable = initColumnVisibilityWithIconButtonTable();
            

Disable first ajax call

In this example, the Ajax call for the initial table data retrieval is disabled and Ajax is called to load the data when the button is pressed.
Setting the firstAjax option to false in the DataTable constructor disables the initial Ajax call.
Server-side processing is required to make this demo work. Click here to learn how to build server-side processing.
Name Position Office Age Start date Salary
              <!--begin::Wrapper-->
<div class="d-flex flex-stack flex-wrap mb-5">
  <!--begin::Toolbar-->
  <div class="d-flex justify-content-end">
    <button data-on-load-disable-first-ajax-call-table type="button" class="btn btn-primary">Call Ajax to load data</button>
  </div>
  <!--end::Toolbar-->
</div>
<!--end::Wrapper-->
<!--begin::Table-->
<table data-ref="disableFirstAjaxCallTable" class="table table-row-bordered gy-5">
  <thead>
    <tr class="text-start text-gray-700 fw-bold fs-7 gs-0">
      <th>Name</th>
      <th>Position</th>
      <th>Office</th>
      <th>Age</th>
      <th>Start date</th>
      <th>Salary</th>
    </tr>
  </thead>
  <tbody></tbody>
</table>
<!--end::Table-->
            
              import {components} from 'metronic-extension';

// Initialize the DataTable.
const initDisableFirstAjaxCallTable = () => {
  let targetIndex = 0;
  return new components.Datatable(ref.disableFirstAjaxCallTable, {
    firstAjax: false,
    ajax: '/api/persons/pages',
    columnDefs: [
      {targets: targetIndex++, data: 'name'},
      {targets: targetIndex++, data: 'position'},
      {targets: targetIndex++, data: 'office'},
      {targets: targetIndex++, data: 'age'},
      {targets: targetIndex++, data: 'startDate'},
      {targets: targetIndex++, data: 'salary'},
    ],
    pageLength: 3
  });
}

// Initialize the form.
const initDisableFirstAjaxCallTableForm = () => {
  $('body').on('click', '[data-on-load-disable-first-ajax-call-table]', () => disableFirstAjaxCallTable.reload());
}

// Get references to elements with data-ref attributes.
const ref = components.selectRef();

// Initialize the component and set up event listeners.
const disableFirstAjaxCallTable = initDisableFirstAjaxCallTable();
initDisableFirstAjaxCallTableForm();
            
This is a route script used in server-side processing created with express-sweet, which extends Node.js express.
Server-side processing scripts can be written in any language, using the protocol described in the DataTables documentation.
              const router = require('express').Router();
const {query, validationResult} = require('express-validator');
const PersonModel = require('../../models/PersonModel');

// Get person page data.
router.get('/pages', [
  // Validate parameters.
  query('start').not().isEmpty().isInt({min: 0}),
  query('length').not().isEmpty().isInt({min: 1}),
  query('order').not().isEmpty().isIn(['name', 'position', 'office', 'age', 'startDate', 'salary']),
  query('dir').not().isEmpty().isIn(['asc', 'desc']),
  query('search.keyword').trim().optional({nullable: true, checkFalsy: true}).isLength({max: 70}),
  query('draw').not().isEmpty().isInt({min: 1}),
], async (req, res) => {
  // Check validation results.
  const result = validationResult(req);
  if (!result.isEmpty())
    // If the parameter is invalid, a 400 error is returned.
    return void res.status(400).end();

  // Get page data.
  const data = await PersonModel.paginate(req.query);

  // Set the received drawing count as-is in the response.
  data.draw = req.query.draw;
  res.json(data);
});

module.exports = router;
            
This is a Model script used in server-side processing created with express-sweet, which extends Node.js express.
              const Model = require('express-sweet').database.Model;
const {merge} = require('deep-fusion');

module.exports = class extends Model {
  static get table() {
    return 'person';
  }

  static get attributes() {
    return {
      id: {
        type: this.DataTypes.INTEGER,
        primaryKey: true,
        autoIncrement: true
      },
      name: this.DataTypes.STRING,
      position: this.DataTypes.STRING,
      office: this.DataTypes.STRING,
      age: this.DataTypes.INTEGER,
      startDate: this.DataTypes.DATE,
      salary: this.DataTypes.DECIMAL(10, 2),
    };
  }

  /**
   * Get page data.
   */
  static async paginate(paginationOptions) {
    // Initialize options.
    paginationOptions = merge({
      start: 0,
      length: 30,
      order: 'name',
      dir: 'asc',
      search: {
        keyword: null,
      },
    }, paginationOptions);

    // Where clause for filtering data.
    const whereClause = {};
    if (paginationOptions.search.keyword)
      whereClause.name = {[super.Op.like]: `%${paginationOptions.search.keyword}%`};

    // Calculate total records.
    const recordsTotal = await super.count();

    // Calculate filtered records.
    const recordsFiltered = await super.count({where: whereClause});

    // Fetch paginated data.
    const data = await super.findAll({
      where: whereClause,
      offset: parseInt(paginationOptions.start, 10),
      limit: parseInt(paginationOptions.length, 10),
      order: [
        [super.col(paginationOptions.order), paginationOptions.dir],
      ],
      raw: true,
    });

    return {data, recordsTotal, recordsFiltered};
  }
}
            

Subtable

This example demonstrates expandable rows in a DataTables table. Sub-row data is populated using DataTable's template methods with simple JS data objects, which can be generated locally or fetched from an API.
Order ID Customer Name Order Date Shipping Address Payment Status Delivery Date Order Status Order Type Discount Order Notes
1001 John Smith 2024-12-01 123 Main St, City Paid 2024-12-05 Delivered Online 0 No special instructions
1002 Emily Johnson 2024-12-15 456 Oak Rd, Town Pending 2024-12-18 Processing In-Store 5 Requires gift wrapping
1003 Alice Johnson 2024-12-03 789 Oak St, Suburb Unpaid 2024-12-07 Processing Online 10% Deliver during business hours
1004 Michael Brown 2024-12-20 789 Pine Ave, City Paid 2024-12-22 Shipped Online 10 N/A
              <!--begin::Table-->
<table data-ref="subtable" class="table table-row-bordered gy-5">
  <thead>
    <tr class="text-start text-gray-700 fw-bold fs-7 gs-0">
      <th>Order ID</th>
      <th>Customer Name</th>
      <th>Order Date</th>
      <th>Shipping Address</th>
      <th>Payment Status</th>
      <th>Delivery Date</th>
      <th>Order Status</th>
      <th>Order Type</th>
      <th>Discount</th>
      <th>Order Notes</th>
      <th class="text-end"></th>
    </tr>
  </thead>
  <tbody>
    <!--begin::SubTable template-->
    <template id="subtable-template">
      <tr>
        <td colspan="2">
          <div class="d-flex align-items-center gap-3">
            <!--begin:Icon-->
            <span class="symbol symbol-40px">
              <span class="symbol-label bg-transparent">
                <i class="ki-duotone ki-plus-square fs-2x">
                  <span class="path1"></span>
                  <span class="path2"></span>
                  <span class="path3"></span>
                </i>
              </span>
            </span>
            <!--end:Icon-->
            <!--begin:Info-->
            <span class="fs-7 fw-bold" data-subtable-field="name">name</span>
            <!--end:Info-->
          </div>
        </td>
        <td class="text-end">
          <div class="text-gray-900 fs-7">Quantity</div>
          <div class="fs-7 fw-bold" data-subtable-field="quantity">1</div>
        </td>
        <td class="text-end">
          <div class="text-gray-900 fs-7">Unit Price</div>
          <div class="fs-7 fw-bold" data-subtable-field="unit_price">1</div>
        </td>
      </tr>
    </template>
    <!--end::SubTable template-->

    <tr>
      <td>1001</td>
      <td>John Smith</td>
      <td>2024-12-01</td>
      <td>123 Main St, City</td>
      <td>Paid</td>
      <td>2024-12-05</td>
      <td>Delivered</td>
      <td>Online</td>
      <td>0</td>
      <td>No special instructions</td>
      <td class="text-end">
        <button data-on-expand-row type="button" class="btn btn-sm btn-icon btn-light btn-active-light-primary toggle h-25px w-25px">
          <i class="ki-duotone ki-plus fs-3 m-0 toggle-off"></i>
          <i class="ki-duotone ki-minus fs-3 m-0 toggle-on"></i>
        </button>
      </td>
    </tr>
    <tr>
      <td>1002</td>
      <td>Emily Johnson</td>
      <td>2024-12-15</td>
      <td>456 Oak Rd, Town</td>
      <td>Pending</td>
      <td>2024-12-18</td>
      <td>Processing</td>
      <td>In-Store</td>
      <td>5</td>
      <td>Requires gift wrapping</td>
      <td class="text-end">
        <button data-on-expand-row type="button" class="btn btn-sm btn-icon btn-light btn-active-light-primary toggle h-25px w-25px">
          <i class="ki-duotone ki-plus fs-3 m-0 toggle-off"></i>
          <i class="ki-duotone ki-minus fs-3 m-0 toggle-on"></i>
        </button>
      </td>
    </tr>
    <!-- This row has no subtable data -->
    <tr>
      <td>1003</td>
      <td>Alice Johnson</td>
      <td>2024-12-03</td>
      <td>789 Oak St, Suburb</td>
      <td>Unpaid</td>
      <td>2024-12-07</td>
      <td>Processing</td>
      <td>Online</td>
      <td>10%</td>
      <td>Deliver during business hours</td>
      <td class="text-end">
        <button data-on-expand-row type="button" class="btn btn-sm btn-icon btn-light btn-active-light-primary toggle h-25px w-25px">
          <i class="ki-duotone ki-plus fs-3 m-0 toggle-off"></i>
          <i class="ki-duotone ki-minus fs-3 m-0 toggle-on"></i>
        </button>
      </td>
    </tr>
    <tr>
      <td>1004</td>
      <td>Michael Brown</td>
      <td>2024-12-20</td>
      <td>789 Pine Ave, City</td>
      <td>Paid</td>
      <td>2024-12-22</td>
      <td>Shipped</td>
      <td>Online</td>
      <td>10</td>
      <td>N/A</td>
      <td class="text-end">
        <button data-on-expand-row type="button" class="btn btn-sm btn-icon btn-light btn-active-light-primary toggle h-25px w-25px">
          <i class="ki-duotone ki-plus fs-3 m-0 toggle-off"></i>
          <i class="ki-duotone ki-minus fs-3 m-0 toggle-on"></i>
        </button>
      </td>
    </tr>
  </tbody>
</table>
<!--end::Table-->
            
              import {components} from 'metronic-extension';

// Get references to elements with data-ref attributes.
const ref = components.selectRef();

// Initialize the DataTable.
const initSubtable = () => {
  let targetIndex = 0;
  return new components.Datatable(ref.subtable, {
    responsive: true,
    columnDefs: [
      {targets: targetIndex++, responsivePriority: 1, data: 'orderId', name: 'orderId'},
      {targets: targetIndex++, responsivePriority: 2, data: 'customerName', name: 'customerName'},
      {targets: targetIndex++, data: 'orderDate', name: 'orderDate'},
      {targets: targetIndex++, data: 'shippingAddress', name: 'shippingAddress'},
      {targets: targetIndex++, data: 'paymentStatus', name: 'paymentStatus'},
      {targets: targetIndex++, data: 'deliveryDate', name: 'deliveryDate'},
      {targets: targetIndex++, data: 'orderStatus', name: 'orderStatus'},
      {targets: targetIndex++, data: 'orderType', name: 'orderType'},
      {targets: targetIndex++, data: 'discount', name: 'discount'},
      {targets: targetIndex++, data: 'orderNotes', name: 'orderNotes'},
      {
        targets: targetIndex++,
        responsivePriority: 3,
        data: 'showSubtable',
        name: 'showSubtable', 
        render: (data, type, row) => {
          // Check if subtable data exists for this row.
          const hasSubtableData = subtableData.some(item => item.orderId.toString() === row.orderId);
          return hasSubtableData ? data : '';
        },
      },
    ],
    pageLength: 4,
    drawCallback: settings => {
      resetSubtable();
    },
  });
}

// Handles the row expand button click event.
const handleRowExpand = () => {
  $(subtableContainer).on('click', '[data-on-expand-row]', event => {
    event.stopImmediatePropagation();
    event.preventDefault();

    const expandButton = event.currentTarget;
    const row = expandButton.closest('tr');
    const rowClasses = ['isOpen', 'border-bottom-0'];
    
    // Toggle subtable visibility.
    if (row.classList.contains('isOpen')) {
      // Close subtable.
      removeSubtableRows(row);
      row.classList.remove(...rowClasses);
      expandButton.classList.remove('active');
    } else {
      // Open subtable.
      const orderId = subtable.getRowData(row).orderId;
      const subtableRows = subtableData.filter(data => data.orderId == orderId);
      const subtableRowCount = subtableRows.length;

      for (let [index, data] of Object.entries(subtableRows))
        populateSubtableRow(data, row, parseInt(index, 10), subtableRowCount);

      row.classList.add(...rowClasses);
      expandButton.classList.add('active');
    }
  })
}

// Removes subtable rows associated with the given parent row, excluding rows with the 'child' class.
const removeSubtableRows = parentRow => {
  let nextRow = parentRow.nextSibling;

  while (nextRow) {
    // Store the next sibling before potentially removing nextRow.
    const nextSibling = nextRow.nextSibling; 

    if (nextRow.getAttribute('data-is-subtable-row') === 'true')
      // Remove the subtable row.
      nextRow.parentNode.removeChild(nextRow);
      else if (!nextRow.classList.contains('child'))
      // Stop if the next row is not a subtable row and doesn't have the 'child' class.
      break;

    // Move to the next sibling.
    nextRow = nextSibling; // Update nextRow to the stored next sibling.
  }
}

// Populates a subtable row with data and inserts it into the table.
const populateSubtableRow = (data, parentRow, index, subtableRowCount) => {
  // Clone the template row.
  const subRowFragment = subtableTemplate.content.cloneNode(true);
  const subRow = subRowFragment.children[0]; 

  // Set an attribute to identify the subtable row.
  subRow.setAttribute('data-is-subtable-row', 'true');
  
  // Populate data fields in the subtable row.
  const nameElement = subRow.querySelector('[data-subtable-field="name"]');
  const quantityElement = subRow.querySelector('[data-subtable-field="quantity"]');
  const unitPriceElement = subRow.querySelector('[data-subtable-field="unit_price"]');

  nameElement.innerText = data.name;
  quantityElement.innerText = data.quantity;
  unitPriceElement.innerText = data.unitPrice;

  // Apply rounded corners based on row position.
  applyRoundedCorners(subRow, index, subtableRowCount);

  // Insert the new subtable row into the table.
  const tbody = subtableContainer.querySelector('tbody');
  tbody.insertBefore(subRow, parentRow.nextSibling);
}

// Applies rounded corners to the first and last rows of the subtable.
const applyRoundedCorners = (subRow, index, subtableRowCount) => {
  const columns = subRow.querySelectorAll('td');
  const firstColumn = columns[0];
  const lastColumn = columns[columns.length - 1];

  if (subtableRowCount === 1) {
    // Single row.
    firstColumn.classList.add('rounded', 'rounded-end-0');
    lastColumn.classList.add('rounded', 'rounded-start-0');
    subRow.classList.add('border-bottom-0');
  } else {
    // Multiple rows.
    if (index === 0) { // First row.
      firstColumn.classList.add('rounded-start', 'rounded-top-0');
      lastColumn.classList.add('rounded-end', 'rounded-top-0');
      subRow.classList.add('border-bottom-0');
    }
    if (index === subtableRowCount - 1) { // Last row.
      firstColumn.classList.add('rounded-start', 'rounded-bottom-0');
      lastColumn.classList.add('rounded-end', 'rounded-bottom-0');
    }
  }
}

// Resets the subtable by removing all subtable rows and resetting row styles.
const resetSubtable = () => {
  const subtableRows = subtableContainer.querySelectorAll('[data-is-subtable-row="true"]');
  for (const subtableRow of subtableRows)
    subtableRow.parentNode.removeChild(subtableRow);

  const rows = subtableContainer.querySelectorAll('tbody tr');
  for (const row of rows) {
    row.classList.remove('isOpen');
    const expandButton = row.querySelector('button[data-on-expand-row]');
    if (expandButton) {
      expandButton.classList.remove('active');
    }
  }
}

// Sample subtable data.
const subtableData = [
  {
    "orderId": 1001,
    "name": "Wireless Mouse",
    "quantity": 1,
    "unitPrice": 25.99
  },
  {
    "orderId": 1001,
    "name": "Keyboard",
    "quantity": 2,
    "unitPrice": 45.00
  },
  {
    "orderId": 1002,
    "name": "Desk Lamp",
    "quantity": 1,
    "unitPrice": 34.50
  },
  {
    "orderId": 1002,
    "name": "Notebook",
    "quantity": 3,
    "unitPrice": 12.00
  },
  {
    "orderId": 1002,
    "name": "USB Hub",
    "quantity": 2,
    "unitPrice": 18.75
  },
  {
    "orderId": 1004,
    "name": "Office Chair",
    "quantity": 1,
    "unitPrice": 120.00,
  }
];


// DOM element references.
const subtableContainer = ref.subtable.get(0);
const subtableTemplate = document.getElementById('subtable-template');// Get subtable template.

// Initialize the component and set up event listeners.
const subtable = initSubtable();
handleRowExpand();