# Datatables

Datatables solutions for massive datasets.

[![npm](https://img.shields.io/badge/npm-v4.2.0-blue)](https://www.npmjs.com/package/@preline/datatable) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Demo](https://img.shields.io/badge/demo-online-brightgreen)](https://preline.co/plugins/datatables.html)

## Contents

- [Overview](#overview)
- [Installation](#installation)
- [Basic usage](#basic-usage)
- [CSS](#css)
- [Configuration Options](#configuration-options)
- [JavaScript API](#javascript-api)
- [Events](#events)
- [Common Patterns](#common-patterns)
- [License](#license)

## Overview

The Datatables component provides a powerful data table interface with sorting, searching, pagination, and row selection capabilities. It integrates with DataTables.net for advanced table functionality.

**Key Features:**
- Column sorting
- Search functionality
- Pagination
- Row selection (single and multiple)
- Customizable paging controls
- Integration with DataTables.net
- Programmatic control via JavaScript API
- Event system for table interactions

## Installation

To get started, install DataTable plugin via npm, else you can skip this step if you are already using Preline UI as a package.

```bash
npm i jquery datatables.net-dt @preline/datatable
```

### CSS

Use [`@source`](https://tailwindcss.com/docs/functions-and-directives#source-directive) to register the plugin's JavaScript path for Tailwind CSS scanning, then [`@import`](https://tailwindcss.com/docs/functions-and-directives#import-directive) the plugin's CSS files into your Tailwind CSS file.

```css
@import "tailwindcss";

/* @preline/datatable */
@source "../node_modules/@preline/datatable/*.js";
@import "./node_modules/@preline/datatable/variants.css";
@import "./node_modules/@preline/datatable/theme.css";

.dt-layout-row:has(.dt-search),
.dt-layout-row:has(.dt-length),
.dt-layout-row:has(.dt-paging) {
  display: none !important;
}
```

### JavaScript

Include the JavaScript that powers the interactive elements near the end of your `</body>` tag:

```html
<script src="./node_modules/jquery/dist/jquery.min.js"></script>
<script src="./node_modules/datatables.net-dt/js/dataTables.dataTables.min.js"></script>
<script src="./node_modules/@preline/datatable/index.js"></script>
```

### Manual Initialization

Use the `non-auto` entry if you need manual initialization. In this mode, automatic initialization on page load is not included, so the component should be initialized explicitly.

```html
<script src="./node_modules/jquery/dist/jquery.min.js"></script>
<script src="./node_modules/datatables.net-dt/js/dataTables.dataTables.min.js"></script>
<script type="module">
  import HSDataTable from "@preline/datatable/non-auto.mjs";

  new HSDataTable(document.querySelector("#datatable"));
</script>
```

### Via Bundler

When using a bundler (Vite, webpack, etc.), import the plugin directly as an ES module. `jQuery` and `DataTable` are referenced as globals, so expose them on `window` before importing the plugin.

`@preline/datatable` is the auto-init entry: it scans the DOM and initializes matching elements automatically.

```js
import jQuery from "jquery";
window.jQuery = jQuery;
window.$ = jQuery;
import DataTable from "datatables.net";
window.DataTable = DataTable;

import "@preline/datatable";
```

`@preline/datatable/non-auto` is the manual entry: use it when you want explicit control over when initialization happens, either via `autoInit()` or by creating a specific instance yourself.

```js
import jQuery from "jquery";
window.jQuery = jQuery;
window.$ = jQuery;
import DataTable from "datatables.net";
window.DataTable = DataTable;

import HSDataTable from "@preline/datatable/non-auto";

HSDataTable.autoInit();

// Or initialize a specific element manually
const el = document.querySelector("#datatable");
if (el) new HSDataTable(el);
```

### TypeScript

This package ships with TypeScript type definitions. No additional `@types/` package is needed.

## Basic usage

The following example demonstrates the minimal HTML structure required for a datatable component. This is a base template without custom styling - you can apply your own CSS classes and styles as needed. The table supports sorting, searching, and pagination.

```html
<div data-hs-datatable>
  <table>
    <thead>
      <tr>
        <th scope="col">
          Name
          <svg class="hidden hs-datatable-ordering-desc:block" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
          <svg class="hidden hs-datatable-ordering-asc:block" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>
        </th>
        <th scope="col">
          Age
          <svg class="hidden hs-datatable-ordering-desc:block" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
          <svg class="hidden hs-datatable-ordering-asc:block" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>
        </th>
        <th scope="col" class="--exclude-from-ordering">
          Action
        </th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>John Brown</td>
        <td>45</td>
        <td>
          <button type="button">Delete</button>
        </td>
      </tr>
    </tbody>
  </table>

  <div style="display: none;" data-hs-datatable-paging>
    <button type="button" data-hs-datatable-paging-prev>
      <span aria-hidden="true">«</span>
      <span class="sr-only">Previous</span>
    </button>
    <div data-hs-datatable-paging-pages></div>
    <button type="button" data-hs-datatable-paging-next>
      <span class="sr-only">Next</span>
      <span aria-hidden="true">»</span>
    </button>
  </div>
</div>
```

**Structure Requirements:**
- `data-hs-datatable`: Required on the container element
- Standard HTML `<table>` structure with `<thead>` and `<tbody>`
- Optional paging controls with `data-hs-datatable-paging`
- Optional search input with `data-hs-datatable-search`
- Optional row selection checkboxes

**Initial State:**
- Table displays all data
- Sorting is enabled on columns (unless excluded)
- Pagination controls are hidden if not needed

## CSS

Please add the following CSS to the head. This is necessary because Datatables.net does not allow to add custom markup with options disabled, such as `paging: false` or `searching: false`.

```html
<style>
  .dt-layout-row:has(.dt-search),
  .dt-layout-row:has(.dt-length),
  .dt-layout-row:has(.dt-paging) {
    display: none !important;
  }
</style>
```

## Configuration Options

### Data Options

> **Note:** Please note that this component requires the use of the [Datatables.net](https://datatables.net/) plugin. Most options available in the plugin are available in our wrapper.

Data options are specified in the `data-hs-datatable` attribute as a JSON object.

| Option | Target Element | Type | Default | Description |
| --- | --- | --- | --- | --- |
| `data-hs-datatable` | Container | - | - | Activate a Datatable by specifying on an element. Should be added to the container. |
| `:pageBtnClasses` | Inside `data-hs-datatable` | string | `"-"` | Sets the specified classes to each paging element inside the `data-hs-datatable-paging-pages`. |
| `:rowSelectingOptions.selectAllSelector` | Inside `data-hs-datatable` | string (CSS selector) | `"[data-hs-datatable-row-selecting-all]"` | A valid CSS selector (the element must be a checkbox or radio button), when the element is changed, all elements specified by `rowSelectingOptions.individualSelector` (or, if not specified, then `data-hs-datatable-row-selecting-individual`) on the current page will be changed. |
| `:rowSelectingOptions.individualSelector` | Inside `data-hs-datatable` | string (CSS selector) | `"[data-hs-datatable-row-selecting-individual]"` | A valid CSS selector (the element must be a checkbox or radio button), affects the change of the element specified in `rowSelectingOptions.selectAllSelector`, if not all elements are selected on the current page, then it switches to `unchecked` state, if vice versa, then in `checked`. |

**Example:**
```html
<div data-hs-datatable='{
  "pageBtnClasses": "px-3 py-1 border rounded",
  "rowSelectingOptions": {
    "selectAllSelector": "#hs-select-all",
    "individualSelector": ".hs-row-checkbox"
  }
}'>
  <!-- Table structure -->
</div>
```

### Class Options

CSS class modifiers use `--` prefix to control datatable behavior.

| Class Modifier | Target Element | Purpose |
| --- | --- | --- |
| `--exclude-from-ordering` | Table header (`<th>`) | A modifier that allows you to disable the ordering functionality in a column. Should be added to the thead column. |

### Required CSS Classes / Data Attributes

These data attributes define the structure and must be present for specific features to function.

| Data Attribute | Required On | Purpose |
| --- | --- | --- |
| `data-hs-datatable-search` | Input element | Defines which element will be responsible for searching in the datatable (must be an `input`). |
| `data-hs-datatable-page-entities` | Select element | Determines which element will be responsible for switching the number of elements on the page (must be a `select`, depends on the change event). |
| `data-hs-datatable-paging` | Container element | Defines a paging container, that contains pagination, next and previous buttons. |
| `data-hs-datatable-paging-prev` | Button element | Defines a previous button element. |
| `data-hs-datatable-paging-next` | Button element | Defines a next button element. |
| `data-hs-datatable-row-selecting-all` | Checkbox/Radio element | Defines a "Select all" checkbox/radio, if it's not specified in the `rowSelectingOptions.selectAllSelector` parameter. |
| `data-hs-datatable-row-selecting-individual` | Checkbox/Radio element | Defines a checkbox/radio that will be responsible for selecting a specific row if it is not specified in the `rowSelectingOptions.individualSelector` parameter. |
| `data-hs-datatable-info` | Container element | Defines an info container. |
| `data-hs-datatable-info-from` | Element | Defines an element that showing `start` number. |
| `data-hs-datatable-info-to` | Element | Defines an element that showing `end` number. |
| `data-hs-datatable-info-length` | Element | Defines an element that showing `recordsTotal` number. |

### Tailwind Modifiers

| Name | Description |
| --- | --- |
| `hs-datatable-ordering-asc:*` | A modifier that allows you to set Tailwind classes to elements inside `thead th` or `thead td` when ordering changes to `asc`. |
| `hs-datatable-ordering-desc:*` | A modifier that allows you to set Tailwind classes to elements inside `thead th` or `thead td` when ordering changes to `desc`. |

## JavaScript API

The `HSDataTable` object is available in the global `window` object after the plugin is loaded.

### Instance Methods

These methods are called on a datatable instance.

| Method | Parameters | Return Type | Description |
| --- | --- | --- | --- |
| `destroy()` | None | `void` | Destroys the datatable instance, removes all generated markup, classes, and event listeners. Use when removing datatable from DOM. |

### Static Methods

These methods are called directly on the `HSDataTable` class.

| Method | Parameters | Return Type | Description |
| --- | --- | --- | --- |
| `HSDataTable.getInstance(target, isInstance)` | `target`: `HTMLElement \| string` (CSS selector)<br>`isInstance`: `boolean` (optional) | `HTMLElement \| { id: string \| number, element: HSDataTable } \| null` | Returns the datatable instance or element associated with the target. If `isInstance` is `true`, returns collection item object `{ id, element }` where `element` is the `HSDataTable` instance. If `isInstance` is `false` or omitted, returns the DOM element (`HTMLElement`). Returns `null` if datatable instance is not found. |

### Usage Examples

**Example 1: Accessing DataTables.net instance**
```javascript
// Get the datatable instance
const instance = HSDataTable.getInstance('#hs-datatable', true);

if (instance) {
  const { element } = instance;
  // Access underlying DataTables.net instance
  const { dataTable } = element;

  if (dataTable) {
    // Use DataTables.net methods
    dataTable.search('query').draw();
  }
}
```

**Example 2: Destroying datatable instance**
```javascript
const instance = HSDataTable.getInstance('#hs-datatable', true);

if (instance) {
  const { element } = instance;
  const destroyBtn = document.querySelector('#hs-destroy-btn');

  destroyBtn.addEventListener('click', () => {
    element.destroy();
  });
}
```

**Example 3: Destroying datatable instance**
```javascript
const instance = HSDataTable.getInstance('#hs-datatable', true);

if (instance) {
  const { element } = instance;
  const removeBtn = document.querySelector('#hs-remove-btn');

  removeBtn.addEventListener('click', () => {
    // Clean up before removing from DOM
    element.destroy();
    // Now safe to remove the element
    document.querySelector('#hs-datatable').remove();
  });
}
```

## Events

> **Note:** Please note that this component requires the use of the [Datatables.net](https://datatables.net/) plugin. Most events available in the plugin are available in our wrapper.

Datatable instances emit events through the underlying DataTables.net instance. Access the DataTables.net instance via `element.dataTable` to listen to events.

**Common DataTables.net Events:**
- `draw` - When table is redrawn
- `order` - When column ordering changes
- `search` - When search is performed
- `page` - When page changes
- `select` - When row is selected
- `deselect` - When row is deselected

### Event Usage Example

```javascript
// Get datatable instance
const instance = HSDataTable.getInstance('#hs-datatable', true);

if (instance) {
  const { element } = instance;
  const { dataTable } = element;

  if (dataTable) {
    // Listen to draw event
    dataTable.on('draw.dt', () => {
      console.log('Table redrawn!');
      // Perform actions after table redraws
      // e.g., update UI, reinitialize plugins
    });

    // Listen to order event
    dataTable.on('order.dt', () => {
      console.log('Column ordering changed!');
    });

    // Listen to search event
    dataTable.on('search.dt', () => {
      console.log('Search performed!');
    });
  }
}
```

## Common Patterns

### Pattern 1: With Search

Add search functionality to the datatable.

```html
<input type="text" data-hs-datatable-search placeholder="Search...">

<div data-hs-datatable>
  <!-- Table structure -->
</div>
```

### Pattern 2: With Row Selection

Enable row selection with checkboxes.

```html
<div data-hs-datatable>
  <table>
    <thead>
      <tr>
        <th>
          <input type="checkbox" data-hs-datatable-row-selecting-all>
        </th>
        <!-- Other columns -->
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>
          <input type="checkbox" data-hs-datatable-row-selecting-individual>
        </td>
        <!-- Other cells -->
      </tr>
    </tbody>
  </table>
</div>
```

### Pattern 3: Exclude Column from Ordering

Disable sorting on specific columns.

```html
<th scope="col" class="--exclude-from-ordering">
  Actions
</th>
```

## License

Copyright (c) 2024 Htmlstream.

Licensed under the [MIT License](https://opensource.org/licenses/MIT).
