Basic example - HTML markup
The Datatable component can render your data in three ways. In the first
one, you simply create a HTML markup for your table nested within a div tag
with a data-twe-datatable-init
attribute - you can customize
your table later by adding data-twe-attributes
to the wrapper.
Some of the more advanced options for columns, described in the
Advanced Data Structure
section can be also used by setting
data-twe-attributes
directly to a th
tag (f.e.
<th data-twe-sort="false">
).
Name | Position | Office | Age | Start date | Salary |
---|---|---|---|---|---|
Tiger Nixon | System Architect | Edinburgh | 61 | 2011/04/25 | $320,800 |
Garrett Winters | Accountant | Tokyo | 63 | 2011/07/25 | $170,750 |
Ashton Cox | Junior Technical Author | San Francisco | 66 | 2009/01/12 | $86,000 |
Cedric Kelly | Senior Javascript Developer | Edinburgh | 22 | 2012/03/29 | $433,060 |
Airi Satou | Accountant | Tokyo | 33 | 2008/11/28 | $162,700 |
Brielle Williamson | Integration Specialist | New York | 61 | 2012/12/02 | $372,000 |
Herrod Chandler | Sales Assistant | San Francisco | 59 | 2012/08/06 | $137,500 |
Rhona Davidson | Integration Specialist | Tokyo | 55 | 2010/10/14 | $327,900 |
Colleen Hurst | Javascript Developer | San Francisco | 39 | 2009/09/15 | $205,500 |
Sonya Frost | Software Engineer | Edinburgh | 23 | 2008/12/13 | $103,600 |
Jena Gaines | Office Manager | London | 30 | 2008/12/19 | $90,560 |
Quinn Flynn | Support Lead | Edinburgh | 22 | 2013/03/03 | $342,000 |
Charde Marshall | Regional Director | San Francisco | 36 | 2008/10/16 | $470,600 |
Haley Kennedy | Senior Marketing Designer | London | 43 | 2012/12/18 | $313,500 |
<div data-twe-datatable-init>
<table>
<thead>
<tr>
<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>$320,800</td>
</tr>
<tr>
<td>Garrett Winters</td>
<td>Accountant</td>
<td>Tokyo</td>
<td>63</td>
<td>2011/07/25</td>
<td>$170,750</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>$86,000</td>
</tr>
<tr>
<td>Cedric Kelly</td>
<td>Senior Javascript Developer</td>
<td>Edinburgh</td>
<td>22</td>
<td>2012/03/29</td>
<td>$433,060</td>
</tr>
<tr>
<td>Airi Satou</td>
<td>Accountant</td>
<td>Tokyo</td>
<td>33</td>
<td>2008/11/28</td>
<td>$162,700</td>
</tr>
<tr>
<td>Brielle Williamson</td>
<td>Integration Specialist</td>
<td>New York</td>
<td>61</td>
<td>2012/12/02</td>
<td>$372,000</td>
</tr>
<tr>
<td>Herrod Chandler</td>
<td>Sales Assistant</td>
<td>San Francisco</td>
<td>59</td>
<td>2012/08/06</td>
<td>$137,500</td>
</tr>
<tr>
<td>Rhona Davidson</td>
<td>Integration Specialist</td>
<td>Tokyo</td>
<td>55</td>
<td>2010/10/14</td>
<td>$327,900</td>
</tr>
<tr>
<td>Colleen Hurst</td>
<td>Javascript Developer</td>
<td>San Francisco</td>
<td>39</td>
<td>2009/09/15</td>
<td>$205,500</td>
</tr>
<tr>
<td>Sonya Frost</td>
<td>Software Engineer</td>
<td>Edinburgh</td>
<td>23</td>
<td>2008/12/13</td>
<td>$103,600</td>
</tr>
<tr>
<td>Jena Gaines</td>
<td>Office Manager</td>
<td>London</td>
<td>30</td>
<td>2008/12/19</td>
<td>$90,560</td>
</tr>
<tr>
<td>Quinn Flynn</td>
<td>Support Lead</td>
<td>Edinburgh</td>
<td>22</td>
<td>2013/03/03</td>
<td>$342,000</td>
</tr>
<tr>
<td>Charde Marshall</td>
<td>Regional Director</td>
<td>San Francisco</td>
<td>36</td>
<td>2008/10/16</td>
<td>$470,600</td>
</tr>
<tr>
<td>Haley Kennedy</td>
<td>Senior Marketing Designer</td>
<td>London</td>
<td>43</td>
<td>2012/12/18</td>
<td>$313,500</td>
</tr>
</tbody>
</table>
</div>
// Initialization for ES Users
import {
Datatable,
initTWE,
} from "tw-elements";
initTWE({ Datatable });
Basic data structure
The second option is a very basic data structure, where columns are represented by an array of strings and so is each row. The table will match each string in a row to a corresponding index in a columns array. This data structure, as it's based on indexes, not key-value pairs, can be easily used for displaying data from the CSV format.
<div id="datatable"></div>
// Initialization for ES Users
import { Datatable } from "tw-elements";
const basicData = {
columns: ['Name', 'Position', 'Office', 'Age', 'Start date', 'Salary'],
rows: [
["Tiger Nixon", "System Architect", "Edinburgh", "61", "2011/04/25", "$320,800"],
["Garrett Winters", "Accountant", "Tokyo", "63", "2011/07/25", "$170,750"],
["Ashton Cox", "Junior Technical Author", "San Francisco", "66", "2009/01/12", "$86,000"],
["Cedric Kelly", "Senior Javascript Developer", "Edinburgh", "22", "2012/03/29", "$433,060"],
["Airi Satou", "Accountant", "Tokyo", "33", "2008/11/28", "$162,700"],
["Brielle Williamson", "Integration Specialist", "New York", "61", "2012/12/02", "$372,000"],
["Herrod Chandler", "Sales Assistant", "San Francisco", "59", "2012/08/06", "$137,500"],
["Rhona Davidson", "Integration Specialist", "Tokyo", "55", "2010/10/14", "$327,900"],
["Colleen Hurst", "Javascript Developer", "San Francisco", "39", "2009/09/15", "$205,500"],
["Sonya Frost", "Software Engineer", "Edinburgh", "23", "2008/12/13", "$103,600"],
["Jena Gaines", "Office Manager", "London", "30", "2008/12/19", "$90,560"],
["Quinn Flynn", "Support Lead", "Edinburgh", "22", "2013/03/03", "$342,000"],
["Charde Marshall", "Regional Director", "San Francisco", "36", "2008/10/16", "$470,600"],
["Haley Kennedy", "Senior Marketing Designer", "London", "43", "2012/12/18", "$313,500"]
],
};
new Datatable(document.getElementById('datatable'), basicData)
const basicData = {
columns: ['Name', 'Position', 'Office', 'Age', 'Start date', 'Salary'],
rows: [
["Tiger Nixon", "System Architect", "Edinburgh", "61", "2011/04/25", "$320,800"],
["Garrett Winters", "Accountant", "Tokyo", "63", "2011/07/25", "$170,750"],
["Ashton Cox", "Junior Technical Author", "San Francisco", "66", "2009/01/12", "$86,000"],
["Cedric Kelly", "Senior Javascript Developer", "Edinburgh", "22", "2012/03/29", "$433,060"],
["Airi Satou", "Accountant", "Tokyo", "33", "2008/11/28", "$162,700"],
["Brielle Williamson", "Integration Specialist", "New York", "61", "2012/12/02", "$372,000"],
["Herrod Chandler", "Sales Assistant", "San Francisco", "59", "2012/08/06", "$137,500"],
["Rhona Davidson", "Integration Specialist", "Tokyo", "55", "2010/10/14", "$327,900"],
["Colleen Hurst", "Javascript Developer", "San Francisco", "39", "2009/09/15", "$205,500"],
["Sonya Frost", "Software Engineer", "Edinburgh", "23", "2008/12/13", "$103,600"],
["Jena Gaines", "Office Manager", "London", "30", "2008/12/19", "$90,560"],
["Quinn Flynn", "Support Lead", "Edinburgh", "22", "2013/03/03", "$342,000"],
["Charde Marshall", "Regional Director", "San Francisco", "36", "2008/10/16", "$470,600"],
["Haley Kennedy", "Senior Marketing Designer", "London", "43", "2012/12/18", "$313,500"]
],
};
new twe.Datatable(document.getElementById('datatable'), basicData)
Need even more robust tables? Try Data Den.
- Quick customization & hyper-focus on data management
- Easily integrate it with any project (not only MDB)
- Column Pinning, Drag&Drop Columns, Advanced Filtering & much more
For enterprise projects & users seeking advanced data controls. Tailor your data your way.
Advanced data structure
The last and most advanced data structure allows customizing each column (sort, width, fixed, field) and matches values from each row to a column in which the `field` equals to a given key value. This data format can be easily used to display JSON data.
You can also use a mixed version, where columns are an array of object and each row is an array of strings.
<div id="datatable"></div>
// Initialization for ES Users
import { Datatable } from "tw-elements";
const advancedData = {
columns: [
{ label: 'Name', field: 'name', sort: true },
{ label: 'Position', field: 'position', sort: false },
{ label: 'Office', field: 'office', sort: false },
{ label: 'Age', field: 'age', sort: false },
{ label: 'Start date', field: 'date', sort: true },
{ label: 'Salary', field: 'salary', sort: false },
],
rows: [
{ name: "Tiger Nixon", position: "System Architect", office: "Edinburgh", age: 61, date: "2011/04/25", salary: "$320,800" },
{ name: "Garrett Winters", position: "Accountant", office: "Tokyo", age: 63, date: "2011/07/25", salary: "$170,750" },
{ name: "Ashton Cox", position: "Junior Technical Author", office: "San Francisco", age: 66, date: "2009/01/12", salary: "$86,000" },
{ name: "Cedric Kelly", position: "Senior Javascript Developer", office: "Edinburgh", age: 22, date: "2012/03/29", salary: "$433,060" },
{ name: "Airi Satou", position: "Accountant", office: "Tokyo", age: 33, date: "2008/11/28", salary: "$162,700" },
{ name: "Brielle Williamson", position: "Integration Specialist", office: "New York", age: 61, date: "2012/12/02", salary: "$372,000" },
{ name: "Herrod Chandler", position: "Sales Assistant", office: "San Francisco", age: 59, date: "2012/08/06", salary: "$137,500" },
{ name: "Rhona Davidson", position: "Integration Specialist", office: "Tokyo", age: 55, date: "2010/10/14", salary: "$327,900" },
{ name: "Colleen Hurst", position: "Javascript Developer", office: "San Francisco", age: 39, date: "2009/09/15", salary: "$205,500" },
{ name: "Sonya Frost", position: "Software Engineer", office: "Edinburgh", age: 23, date: "2008/12/13", salary: "$103,600" },
{ name: "Jena Gaines", position: "Office Manager", office: "London", age: 30, date: "2008/12/19", salary: "$90,560" },
{ name: "Quinn Flynn", position: "Support Lead", office: "Edinburgh", age: 22, date: "2013/03/03", salary: "$342,000" },
{ name: "Charde Marshall", position: "Regional Director", office: "San Francisco", age: 36, date: "2008/10/16", salary: "$470,600" },
{ name: "Haley Kennedy", position: "Senior Marketing Designer", office: "London", age: 43, date: "2012/12/18", salary: "$313,500" }
],
};
new Datatable(document.getElementById('datatable'), advancedData)
const advancedData = {
columns: [
{ label: 'Name', field: 'name', sort: true },
{ label: 'Position', field: 'position', sort: false },
{ label: 'Office', field: 'office', sort: false },
{ label: 'Age', field: 'age', sort: false },
{ label: 'Start date', field: 'date', sort: true },
{ label: 'Salary', field: 'salary', sort: false },
],
rows: [
{ name: "Tiger Nixon", position: "System Architect", office: "Edinburgh", age: 61, date: "2011/04/25", salary: "$320,800" },
{ name: "Garrett Winters", position: "Accountant", office: "Tokyo", age: 63, date: "2011/07/25", salary: "$170,750" },
{ name: "Ashton Cox", position: "Junior Technical Author", office: "San Francisco", age: 66, date: "2009/01/12", salary: "$86,000" },
{ name: "Cedric Kelly", position: "Senior Javascript Developer", office: "Edinburgh", age: 22, date: "2012/03/29", salary: "$433,060" },
{ name: "Airi Satou", position: "Accountant", office: "Tokyo", age: 33, date: "2008/11/28", salary: "$162,700" },
{ name: "Brielle Williamson", position: "Integration Specialist", office: "New York", age: 61, date: "2012/12/02", salary: "$372,000" },
{ name: "Herrod Chandler", position: "Sales Assistant", office: "San Francisco", age: 59, date: "2012/08/06", salary: "$137,500" },
{ name: "Rhona Davidson", position: "Integration Specialist", office: "Tokyo", age: 55, date: "2010/10/14", salary: "$327,900" },
{ name: "Colleen Hurst", position: "Javascript Developer", office: "San Francisco", age: 39, date: "2009/09/15", salary: "$205,500" },
{ name: "Sonya Frost", position: "Software Engineer", office: "Edinburgh", age: 23, date: "2008/12/13", salary: "$103,600" },
{ name: "Jena Gaines", position: "Office Manager", office: "London", age: 30, date: "2008/12/19", salary: "$90,560" },
{ name: "Quinn Flynn", position: "Support Lead", office: "Edinburgh", age: 22, date: "2013/03/03", salary: "$342,000" },
{ name: "Charde Marshall", position: "Regional Director", office: "San Francisco", age: 36, date: "2008/10/16", salary: "$470,600" },
{ name: "Haley Kennedy", position: "Senior Marketing Designer", office: "London", age: 43, date: "2012/12/18", salary: "$313,500" }
],
};
new twe.Datatable(document.getElementById('datatable'), advancedData)
Search
The search field is not a part of the Datatable - place an input field on
your page and use .search()
method to filter entries.
<div class="relative mb-3" data-twe-input-wrapper-init>
<input
type="search"
class="peer block min-h-[auto] w-full rounded border-0 bg-transparent px-3 py-[0.32rem] leading-[1.6] outline-none transition-all duration-200 ease-linear focus:placeholder:opacity-100 peer-focus:text-primary data-[twe-input-state-active]:placeholder:opacity-100 motion-reduce:transition-none dark:text-white dark:placeholder:text-neutral-300 dark:peer-focus:text-primary [&:not([data-twe-input-placeholder-active])]:placeholder:opacity-0 dark:autofill:shadow-autofill"
id="datatable-search-input"
placeholder="Search" />
<label
for="datatable-search-input"
class="pointer-events-none absolute left-3 top-0 mb-0 max-w-[90%] origin-[0_0] truncate pt-[0.37rem] leading-[1.6] text-neutral-500 transition-all duration-200 ease-out peer-focus:-translate-y-[0.9rem] peer-focus:scale-[0.8] peer-focus:text-primary peer-data-[twe-input-state-active]:-translate-y-[0.9rem] peer-data-[twe-input-state-active]:scale-[0.8] motion-reduce:transition-none dark:text-neutral-400 dark:peer-focus:text-primary"
>Search
</label>
</div>
<div id="datatable-search"></div>
// Initialization for ES Users
import { Datatable, Input, initTWE } from "tw-elements";
initTWE({ Input });
const data = {
columns: [
{
label: 'Name',
field: 'name'
},
'Position',
'Office',
'Age',
'Start date',
'Salary',
],
rows: [
["Tiger Nixon", "System Architect", "Edinburgh", "61", "2011/04/25", "$320,800"],
["Garrett Winters", "Accountant", "Tokyo", "63", "2011/07/25", "$170,750"],
["Ashton Cox", "Junior Technical Author", "San Francisco", "66", "2009/01/12", "$86,000"],
["Cedric Kelly", "Senior Javascript Developer", "Edinburgh", "22", "2012/03/29", "$433,060"],
["Airi Satou", "Accountant", "Tokyo", "33", "2008/11/28", "$162,700"],
["Brielle Williamson", "Integration Specialist", "New York", "61", "2012/12/02", "$372,000"],
["Herrod Chandler", "Sales Assistant", "San Francisco", "59", "2012/08/06", "$137,500"],
["Rhona Davidson", "Integration Specialist", "Tokyo", "55", "2010/10/14", "$327,900"],
["Colleen Hurst", "Javascript Developer", "San Francisco", "39", "2009/09/15", "$205,500"],
["Sonya Frost", "Software Engineer", "Edinburgh", "23", "2008/12/13", "$103,600"],
["Jena Gaines", "Office Manager", "London", "30", "2008/12/19", "$90,560"],
["Quinn Flynn", "Support Lead", "Edinburgh", "22", "2013/03/03", "$342,000"],
["Charde Marshall", "Regional Director", "San Francisco", "36", "2008/10/16", "$470,600"],
["Haley Kennedy", "Senior Marketing Designer", "London", "43", "2012/12/18", "$313,500"]
],
};
const instance = new Datatable(document.getElementById('datatable-search'), data)
document.getElementById('datatable-search-input').addEventListener('input', (e) => {
instance.search(e.target.value);
});
const data = {
columns: [
{
label: 'Name',
field: 'name'
},
'Position',
'Office',
'Age',
'Start date',
'Salary',
],
rows: [
["Tiger Nixon", "System Architect", "Edinburgh", "61", "2011/04/25", "$320,800"],
["Garrett Winters", "Accountant", "Tokyo", "63", "2011/07/25", "$170,750"],
["Ashton Cox", "Junior Technical Author", "San Francisco", "66", "2009/01/12", "$86,000"],
["Cedric Kelly", "Senior Javascript Developer", "Edinburgh", "22", "2012/03/29", "$433,060"],
["Airi Satou", "Accountant", "Tokyo", "33", "2008/11/28", "$162,700"],
["Brielle Williamson", "Integration Specialist", "New York", "61", "2012/12/02", "$372,000"],
["Herrod Chandler", "Sales Assistant", "San Francisco", "59", "2012/08/06", "$137,500"],
["Rhona Davidson", "Integration Specialist", "Tokyo", "55", "2010/10/14", "$327,900"],
["Colleen Hurst", "Javascript Developer", "San Francisco", "39", "2009/09/15", "$205,500"],
["Sonya Frost", "Software Engineer", "Edinburgh", "23", "2008/12/13", "$103,600"],
["Jena Gaines", "Office Manager", "London", "30", "2008/12/19", "$90,560"],
["Quinn Flynn", "Support Lead", "Edinburgh", "22", "2013/03/03", "$342,000"],
["Charde Marshall", "Regional Director", "San Francisco", "36", "2008/10/16", "$470,600"],
["Haley Kennedy", "Senior Marketing Designer", "London", "43", "2012/12/18", "$313,500"]
],
};
const instance = new twe.Datatable(document.getElementById('datatable-search'), data)
document.getElementById('datatable-search-input').addEventListener('input', (e) => {
instance.search(e.target.value);
});
Advanced search
When using the searching method, you can specify which columns it should take under consideration - pass as a second argument a field (or array of fields). By default, searching will apply to all columns.
<div class="mb-3">
<div class="relative mb-4 flex w-full flex-wrap items-stretch">
<input
id="advanced-search-input"
type="search"
class="relative m-0 -me-0.5 block w-[1px] min-w-0 flex-auto rounded-s border border-solid border-neutral-300 bg-transparent bg-clip-padding px-3 py-[0.25rem] text-base font-normal leading-[1.6] text-neutral-700 outline-none transition duration-200 ease-in-out focus:z-[3] focus:border-primary focus:text-neutral-700 focus:shadow-[inset_0_0_0_1px_rgb(59,113,202)] focus:outline-none dark:border-neutral-600 dark:text-neutral-200 dark:placeholder:text-neutral-200 dark:focus:border-primary dark:autofill:shadow-autofill"
placeholder="Search"
aria-label="Search"
aria-describedby="button-addon1" />
<!--Search button-->
<button
class="relative z-[2] flex items-center rounded-e bg-primary px-6 py-2.5 text-xs font-medium uppercase leading-tight text-white shadow-md transition duration-150 ease-in-out hover:bg-primary-700 hover:shadow-lg focus:bg-primary-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-primary-800 active:shadow-lg"
type="button"
id="advanced-search-button"
data-twe-ripple-init
data-twe-ripple-color="light">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="h-5 w-5">
<path
fill-rule="evenodd"
d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
clip-rule="evenodd" />
</svg>
</button>
</div>
</div>
<div id="datatable"></div>
// Initialization for ES Users
import { Datatable, Input, initTWE } from "tw-elements";
initTWE({ Input });
const data = {
columns: [
{
label: 'Name',
field: 'name'
},
'Position',
'Office',
'Age',
'Start date',
'Salary',
],
rows: [
["Tiger Nixon", "System Architect", "Edinburgh", "61", "2011/04/25", "$320,800"],
["Garrett Winters", "Accountant", "Tokyo", "63", "2011/07/25", "$170,750"],
["Ashton Cox", "Junior Technical Author", "San Francisco", "66", "2009/01/12", "$86,000"],
["Cedric Kelly", "Senior Javascript Developer", "Edinburgh", "22", "2012/03/29", "$433,060"],
["Airi Satou", "Accountant", "Tokyo", "33", "2008/11/28", "$162,700"],
["Brielle Williamson", "Integration Specialist", "New York", "61", "2012/12/02", "$372,000"],
["Herrod Chandler", "Sales Assistant", "San Francisco", "59", "2012/08/06", "$137,500"],
["Rhona Davidson", "Integration Specialist", "Tokyo", "55", "2010/10/14", "$327,900"],
["Colleen Hurst", "Javascript Developer", "San Francisco", "39", "2009/09/15", "$205,500"],
["Sonya Frost", "Software Engineer", "Edinburgh", "23", "2008/12/13", "$103,600"],
["Jena Gaines", "Office Manager", "London", "30", "2008/12/19", "$90,560"],
["Quinn Flynn", "Support Lead", "Edinburgh", "22", "2013/03/03", "$342,000"],
["Charde Marshall", "Regional Director", "San Francisco", "36", "2008/10/16", "$470,600"],
["Haley Kennedy", "Senior Marketing Designer", "London", "43", "2012/12/18", "$313,500"]
],
};
const instance = new Datatable(document.getElementById('datatable'), data)
const advancedSearchInput = document.getElementById('advanced-search-input');
const search = (value) => {
let [phrase, columns] = value.split(" in:").map((str) => str.trim());
if (columns) {
columns = columns.split(",").map((str) => str.toLowerCase().trim());
}
instance.search(phrase, columns);
};
document
.getElementById("advanced-search-button")
.addEventListener("click", (e) => {
search(advancedSearchInput.value);
});
advancedSearchInput.addEventListener("keydown", (e) => {
if (e.keyCode === 13) {
search(e.target.value);
}
});
const data = {
columns: [
{
label: 'Name',
field: 'name'
},
'Position',
'Office',
'Age',
'Start date',
'Salary',
],
rows: [
["Tiger Nixon", "System Architect", "Edinburgh", "61", "2011/04/25", "$320,800"],
["Garrett Winters", "Accountant", "Tokyo", "63", "2011/07/25", "$170,750"],
["Ashton Cox", "Junior Technical Author", "San Francisco", "66", "2009/01/12", "$86,000"],
["Cedric Kelly", "Senior Javascript Developer", "Edinburgh", "22", "2012/03/29", "$433,060"],
["Airi Satou", "Accountant", "Tokyo", "33", "2008/11/28", "$162,700"],
["Brielle Williamson", "Integration Specialist", "New York", "61", "2012/12/02", "$372,000"],
["Herrod Chandler", "Sales Assistant", "San Francisco", "59", "2012/08/06", "$137,500"],
["Rhona Davidson", "Integration Specialist", "Tokyo", "55", "2010/10/14", "$327,900"],
["Colleen Hurst", "Javascript Developer", "San Francisco", "39", "2009/09/15", "$205,500"],
["Sonya Frost", "Software Engineer", "Edinburgh", "23", "2008/12/13", "$103,600"],
["Jena Gaines", "Office Manager", "London", "30", "2008/12/19", "$90,560"],
["Quinn Flynn", "Support Lead", "Edinburgh", "22", "2013/03/03", "$342,000"],
["Charde Marshall", "Regional Director", "San Francisco", "36", "2008/10/16", "$470,600"],
["Haley Kennedy", "Senior Marketing Designer", "London", "43", "2012/12/18", "$313,500"]
],
};
const instance = new twe.Datatable(document.getElementById('datatable'), data)
const advancedSearchInput = document.getElementById('advanced-search-input');
const search = (value) => {
let [phrase, columns] = value.split(" in:").map((str) => str.trim());
if (columns) {
columns = columns.split(",").map((str) => str.toLowerCase().trim());
}
instance.search(phrase, columns);
};
document
.getElementById("advanced-search-button")
.addEventListener("click", (e) => {
search(advancedSearchInput.value);
});
advancedSearchInput.addEventListener("keydown", (e) => {
if (e.keyCode === 13) {
search(e.target.value);
}
});
Selectable rows
When the data-twe-selectable
option is set to
true
, user can interact with your table by selecting rows - you
can get the selected rows by listening to the
rowSelected.twe.datatable
event.
<div
id="datatable"
data-twe-selectable="true"
data-twe-multi="true">
</div>
// Initialization for ES Users
import { Datatable } from "tw-elements";
const data = {
columns: [
{ label: "Name", field: "name" },
{ label: "Position", field: "position" },
{ label: "Office", field: "office" },
{ label: "Age", field: "age" },
{ label: "Start date", field: "date" },
{ label: "Salary", field: "salary" },
],
rows: [
{
name: "Tiger Nixon",
position: "System Architect",
office: "Edinburgh",
age: 61,
date: "2011/04/25",
salary: "$320,800",
},
{
name: "Garrett Winters",
position: "Accountant",
office: "Tokyo",
age: 63,
date: "2011/07/25",
salary: "$170,750",
},
{
name: "Ashton Cox",
position: "Junior Technical Author",
office: "San Francisco",
age: 66,
date: "2009/01/12",
salary: "$86,000",
},
{
name: "Cedric Kelly",
position: "Senior Javascript Developer",
office: "Edinburgh",
age: 22,
date: "2012/03/29",
salary: "$433,060",
},
{
name: "Airi Satou",
position: "Accountant",
office: "Tokyo",
age: 33,
date: "2008/11/28",
salary: "$162,700",
},
{
name: "Brielle Williamson",
position: "Integration Specialist",
office: "New York",
age: 61,
date: "2012/12/02",
salary: "$372,000",
},
{
name: "Herrod Chandler",
position: "Sales Assistant",
office: "San Francisco",
age: 59,
date: "2012/08/06",
salary: "$137,500",
},
{
name: "Rhona Davidson",
position: "Integration Specialist",
office: "Tokyo",
age: 55,
date: "2010/10/14",
salary: "$327,900",
},
{
name: "Colleen Hurst",
position: "Javascript Developer",
office: "San Francisco",
age: 39,
date: "2009/09/15",
salary: "$205,500",
},
{
name: "Sonya Frost",
position: "Software Engineer",
office: "Edinburgh",
age: 23,
date: "2008/12/13",
salary: "$103,600",
},
{
name: "Jena Gaines",
position: "Office Manager",
office: "London",
age: 30,
date: "2008/12/19",
salary: "$90,560",
},
{
name: "Quinn Flynn",
position: "Support Lead",
office: "Edinburgh",
age: 22,
date: "2013/03/03",
salary: "$342,000",
},
{
name: "Charde Marshall",
position: "Regional Director",
office: "San Francisco",
age: 36,
date: "2008/10/16",
salary: "$470,600",
},
{
name: "Haley Kennedy",
position: "Senior Marketing Designer",
office: "London",
age: 43,
date: "2012/12/18",
salary: "$313,500",
},
],
};
const datatable = document.getElementById('datatable');
new Datatable(datatable, data);
datatable.addEventListener('rowSelected.twe.datatable', (e) => {
console.log(e.selectedRows, e.selectedIndexes, e.allSelected);
})
const data = {
columns: [
{ label: "Name", field: "name" },
{ label: "Position", field: "position" },
{ label: "Office", field: "office" },
{ label: "Age", field: "age" },
{ label: "Start date", field: "date" },
{ label: "Salary", field: "salary" },
],
rows: [
{
name: "Tiger Nixon",
position: "System Architect",
office: "Edinburgh",
age: 61,
date: "2011/04/25",
salary: "$320,800",
},
{
name: "Garrett Winters",
position: "Accountant",
office: "Tokyo",
age: 63,
date: "2011/07/25",
salary: "$170,750",
},
{
name: "Ashton Cox",
position: "Junior Technical Author",
office: "San Francisco",
age: 66,
date: "2009/01/12",
salary: "$86,000",
},
{
name: "Cedric Kelly",
position: "Senior Javascript Developer",
office: "Edinburgh",
age: 22,
date: "2012/03/29",
salary: "$433,060",
},
{
name: "Airi Satou",
position: "Accountant",
office: "Tokyo",
age: 33,
date: "2008/11/28",
salary: "$162,700",
},
{
name: "Brielle Williamson",
position: "Integration Specialist",
office: "New York",
age: 61,
date: "2012/12/02",
salary: "$372,000",
},
{
name: "Herrod Chandler",
position: "Sales Assistant",
office: "San Francisco",
age: 59,
date: "2012/08/06",
salary: "$137,500",
},
{
name: "Rhona Davidson",
position: "Integration Specialist",
office: "Tokyo",
age: 55,
date: "2010/10/14",
salary: "$327,900",
},
{
name: "Colleen Hurst",
position: "Javascript Developer",
office: "San Francisco",
age: 39,
date: "2009/09/15",
salary: "$205,500",
},
{
name: "Sonya Frost",
position: "Software Engineer",
office: "Edinburgh",
age: 23,
date: "2008/12/13",
salary: "$103,600",
},
{
name: "Jena Gaines",
position: "Office Manager",
office: "London",
age: 30,
date: "2008/12/19",
salary: "$90,560",
},
{
name: "Quinn Flynn",
position: "Support Lead",
office: "Edinburgh",
age: 22,
date: "2013/03/03",
salary: "$342,000",
},
{
name: "Charde Marshall",
position: "Regional Director",
office: "San Francisco",
age: 36,
date: "2008/10/16",
salary: "$470,600",
},
{
name: "Haley Kennedy",
position: "Senior Marketing Designer",
office: "London",
age: 43,
date: "2012/12/18",
salary: "$313,500",
},
],
};
const datatable = document.getElementById('datatable');
new twe.Datatable(datatable, data);
datatable.addEventListener('rowSelected.twe.datatable', (e) => {
console.log(e.selectedRows, e.selectedIndexes, e.allSelected);
})
Scroll
Setting maximum height/width will enable vertical/horizontal scrolling.
<div
id="datatable"
data-twe-max-height="520"
data-twe-max-width="520"></div>
// Initialization for ES Users
import { Datatable } from "tw-elements";
const basicData = {
columns: ['Name', 'Position', 'Office', 'Age', 'Start date', 'Salary'],
rows: [
["Tiger Nixon", "System Architect", "Edinburgh", "61", "2011/04/25", "$320,800"],
["Garrett Winters", "Accountant", "Tokyo", "63", "2011/07/25", "$170,750"],
["Ashton Cox", "Junior Technical Author", "San Francisco", "66", "2009/01/12", "$86,000"],
["Cedric Kelly", "Senior Javascript Developer", "Edinburgh", "22", "2012/03/29", "$433,060"],
["Airi Satou", "Accountant", "Tokyo", "33", "2008/11/28", "$162,700"],
["Brielle Williamson", "Integration Specialist", "New York", "61", "2012/12/02", "$372,000"],
["Herrod Chandler", "Sales Assistant", "San Francisco", "59", "2012/08/06", "$137,500"],
["Rhona Davidson", "Integration Specialist", "Tokyo", "55", "2010/10/14", "$327,900"],
["Colleen Hurst", "Javascript Developer", "San Francisco", "39", "2009/09/15", "$205,500"],
["Sonya Frost", "Software Engineer", "Edinburgh", "23", "2008/12/13", "$103,600"],
["Jena Gaines", "Office Manager", "London", "30", "2008/12/19", "$90,560"],
["Quinn Flynn", "Support Lead", "Edinburgh", "22", "2013/03/03", "$342,000"],
["Charde Marshall", "Regional Director", "San Francisco", "36", "2008/10/16", "$470,600"],
["Haley Kennedy", "Senior Marketing Designer", "London", "43", "2012/12/18", "$313,500"]
],
};
const datatable = document.getElementById('datatable');
new Datatable(datatable, basicData);
const basicData = {
columns: ['Name', 'Position', 'Office', 'Age', 'Start date', 'Salary'],
rows: [
["Tiger Nixon", "System Architect", "Edinburgh", "61", "2011/04/25", "$320,800"],
["Garrett Winters", "Accountant", "Tokyo", "63", "2011/07/25", "$170,750"],
["Ashton Cox", "Junior Technical Author", "San Francisco", "66", "2009/01/12", "$86,000"],
["Cedric Kelly", "Senior Javascript Developer", "Edinburgh", "22", "2012/03/29", "$433,060"],
["Airi Satou", "Accountant", "Tokyo", "33", "2008/11/28", "$162,700"],
["Brielle Williamson", "Integration Specialist", "New York", "61", "2012/12/02", "$372,000"],
["Herrod Chandler", "Sales Assistant", "San Francisco", "59", "2012/08/06", "$137,500"],
["Rhona Davidson", "Integration Specialist", "Tokyo", "55", "2010/10/14", "$327,900"],
["Colleen Hurst", "Javascript Developer", "San Francisco", "39", "2009/09/15", "$205,500"],
["Sonya Frost", "Software Engineer", "Edinburgh", "23", "2008/12/13", "$103,600"],
["Jena Gaines", "Office Manager", "London", "30", "2008/12/19", "$90,560"],
["Quinn Flynn", "Support Lead", "Edinburgh", "22", "2013/03/03", "$342,000"],
["Charde Marshall", "Regional Director", "San Francisco", "36", "2008/10/16", "$470,600"],
["Haley Kennedy", "Senior Marketing Designer", "London", "43", "2012/12/18", "$313,500"]
],
};
const datatable = document.getElementById('datatable');
new twe.Datatable(datatable, basicData);
Fixed header
Use the data-twe-fixed-header
attribute to ensure that a
table's header is always visible while scrolling.
<div
id="datatable"
data-twe-max-height="460"
data-twe-fixed-header="true"></div>
// Initialization for ES Users
import { Datatable } from "tw-elements";
const basicData = {
columns: ['Name', 'Position', 'Office', 'Age', 'Start date', 'Salary'],
rows: [
["Tiger Nixon", "System Architect", "Edinburgh", "61", "2011/04/25", "$320,800"],
["Garrett Winters", "Accountant", "Tokyo", "63", "2011/07/25", "$170,750"],
["Ashton Cox", "Junior Technical Author", "San Francisco", "66", "2009/01/12", "$86,000"],
["Cedric Kelly", "Senior Javascript Developer", "Edinburgh", "22", "2012/03/29", "$433,060"],
["Airi Satou", "Accountant", "Tokyo", "33", "2008/11/28", "$162,700"],
["Brielle Williamson", "Integration Specialist", "New York", "61", "2012/12/02", "$372,000"],
["Herrod Chandler", "Sales Assistant", "San Francisco", "59", "2012/08/06", "$137,500"],
["Rhona Davidson", "Integration Specialist", "Tokyo", "55", "2010/10/14", "$327,900"],
["Colleen Hurst", "Javascript Developer", "San Francisco", "39", "2009/09/15", "$205,500"],
["Sonya Frost", "Software Engineer", "Edinburgh", "23", "2008/12/13", "$103,600"],
["Jena Gaines", "Office Manager", "London", "30", "2008/12/19", "$90,560"],
["Quinn Flynn", "Support Lead", "Edinburgh", "22", "2013/03/03", "$342,000"],
["Charde Marshall", "Regional Director", "San Francisco", "36", "2008/10/16", "$470,600"],
["Haley Kennedy", "Senior Marketing Designer", "London", "43", "2012/12/18", "$313,500"]
],
};
const datatable = document.getElementById('datatable');
new Datatable(datatable, basicData);
const basicData = {
columns: ['Name', 'Position', 'Office', 'Age', 'Start date', 'Salary'],
rows: [
["Tiger Nixon", "System Architect", "Edinburgh", "61", "2011/04/25", "$320,800"],
["Garrett Winters", "Accountant", "Tokyo", "63", "2011/07/25", "$170,750"],
["Ashton Cox", "Junior Technical Author", "San Francisco", "66", "2009/01/12", "$86,000"],
["Cedric Kelly", "Senior Javascript Developer", "Edinburgh", "22", "2012/03/29", "$433,060"],
["Airi Satou", "Accountant", "Tokyo", "33", "2008/11/28", "$162,700"],
["Brielle Williamson", "Integration Specialist", "New York", "61", "2012/12/02", "$372,000"],
["Herrod Chandler", "Sales Assistant", "San Francisco", "59", "2012/08/06", "$137,500"],
["Rhona Davidson", "Integration Specialist", "Tokyo", "55", "2010/10/14", "$327,900"],
["Colleen Hurst", "Javascript Developer", "San Francisco", "39", "2009/09/15", "$205,500"],
["Sonya Frost", "Software Engineer", "Edinburgh", "23", "2008/12/13", "$103,600"],
["Jena Gaines", "Office Manager", "London", "30", "2008/12/19", "$90,560"],
["Quinn Flynn", "Support Lead", "Edinburgh", "22", "2013/03/03", "$342,000"],
["Charde Marshall", "Regional Director", "San Francisco", "36", "2008/10/16", "$470,600"],
["Haley Kennedy", "Senior Marketing Designer", "London", "43", "2012/12/18", "$313,500"]
],
};
const datatable = document.getElementById('datatable');
new twe.Datatable(datatable, basicData);
Fixed columns
Making a column sticky requires setting two options - width and fixed. A
first option is a number of pixels, while the other one can be either a
true
( in which case the column will stick on the left) or a
string right
.
Using fixed columns in a vertically scrollable table, requires setting an
option fixedHeader
to true
as well.
When using a HTML markup instead of a data structure you can still use
this feature by setting data-twe-width
and
data-twe-fixed
attributes on your th
tags.
<div id="datatable"></div>
// Initialization for ES Users
import { Datatable } from "tw-elements";
const basicData = {
columns: [
{ label: 'Name', field: 'name', sort: true, width: 200, fixed: true },
{ label: 'Position', field: 'position', sort: false, width: 200 },
{ label: 'Office', field: 'office', sort: false, width: 200, fixed: true },
{ label: 'Age', field: 'age', sort: false, width: 200 },
{ label: 'Start date', field: 'date', sort: true, width: 200 },
{ label: 'Salary', field: 'salary', sort: false, width: 200, fixed: 'right' },
],
rows: [
["Tiger Nixon", "System Architect", "Edinburgh", "61", "2011/04/25", "$320,800"],
["Garrett Winters", "Accountant", "Tokyo", "63", "2011/07/25", "$170,750"],
["Ashton Cox", "Junior Technical Author", "San Francisco", "66", "2009/01/12", "$86,000"],
["Cedric Kelly", "Senior Javascript Developer", "Edinburgh", "22", "2012/03/29", "$433,060"],
["Airi Satou", "Accountant", "Tokyo", "33", "2008/11/28", "$162,700"],
["Brielle Williamson", "Integration Specialist", "New York", "61", "2012/12/02", "$372,000"],
["Herrod Chandler", "Sales Assistant", "San Francisco", "59", "2012/08/06", "$137,500"],
["Rhona Davidson", "Integration Specialist", "Tokyo", "55", "2010/10/14", "$327,900"],
["Colleen Hurst", "Javascript Developer", "San Francisco", "39", "2009/09/15", "$205,500"],
["Sonya Frost", "Software Engineer", "Edinburgh", "23", "2008/12/13", "$103,600"],
["Jena Gaines", "Office Manager", "London", "30", "2008/12/19", "$90,560"],
["Quinn Flynn", "Support Lead", "Edinburgh", "22", "2013/03/03", "$342,000"],
["Charde Marshall", "Regional Director", "San Francisco", "36", "2008/10/16", "$470,600"],
["Haley Kennedy", "Senior Marketing Designer", "London", "43", "2012/12/18", "$313,500"]
],
};
const datatable = document.getElementById('datatable');
new Datatable(datatable, basicData);
const basicData = {
columns: [
{ label: 'Name', field: 'name', sort: true, width: 200, fixed: true },
{ label: 'Position', field: 'position', sort: false, width: 200 },
{ label: 'Office', field: 'office', sort: false, width: 200, fixed: true },
{ label: 'Age', field: 'age', sort: false, width: 200 },
{ label: 'Start date', field: 'date', sort: true, width: 200 },
{ label: 'Salary', field: 'salary', sort: false, width: 200, fixed: 'right' },
],
rows: [
["Tiger Nixon", "System Architect", "Edinburgh", "61", "2011/04/25", "$320,800"],
["Garrett Winters", "Accountant", "Tokyo", "63", "2011/07/25", "$170,750"],
["Ashton Cox", "Junior Technical Author", "San Francisco", "66", "2009/01/12", "$86,000"],
["Cedric Kelly", "Senior Javascript Developer", "Edinburgh", "22", "2012/03/29", "$433,060"],
["Airi Satou", "Accountant", "Tokyo", "33", "2008/11/28", "$162,700"],
["Brielle Williamson", "Integration Specialist", "New York", "61", "2012/12/02", "$372,000"],
["Herrod Chandler", "Sales Assistant", "San Francisco", "59", "2012/08/06", "$137,500"],
["Rhona Davidson", "Integration Specialist", "Tokyo", "55", "2010/10/14", "$327,900"],
["Colleen Hurst", "Javascript Developer", "San Francisco", "39", "2009/09/15", "$205,500"],
["Sonya Frost", "Software Engineer", "Edinburgh", "23", "2008/12/13", "$103,600"],
["Jena Gaines", "Office Manager", "London", "30", "2008/12/19", "$90,560"],
["Quinn Flynn", "Support Lead", "Edinburgh", "22", "2013/03/03", "$342,000"],
["Charde Marshall", "Regional Director", "San Francisco", "36", "2008/10/16", "$470,600"],
["Haley Kennedy", "Senior Marketing Designer", "London", "43", "2012/12/18", "$313,500"]
],
};
const datatable = document.getElementById('datatable');
new twe.Datatable(datatable, basicData);
Async data
Loading content asynchronously is an important part of working with data
tables - with TE Datatable you can easily display content after fetching it
from API by using the update
method. Additionally, setting a
loading
option to true
will disable all
interactions and display a simple loader while awaiting data.
The example below demonstrates loading data after the button is pressed.
<div id="datatable"></div>
// Initialization for ES Users
import { Datatable } from "tw-elements";
const columns = [
{ label: 'Address', field: 'address' },
{ label: 'Company', field: 'company' },
{ label: 'Email', field: 'email' },
{ label: 'Name', field: 'name' },
{ label: 'Phone', field: 'phone' },
{ label: 'Username', field: 'username' },
{ label: 'Website', field: 'website' },
];
const asyncTable = new Datatable(
document.getElementById('datatable'),
{ columns, },
{ loading: true }
);
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.json())
.then((data) => {
asyncTable.update(
{
rows: data.map((user) => ({
...user,
address: `${user.address.city}, ${user.address.street}`,
company: user.company.name,
})),
},
{ loading: false }
);
});
const columns = [
{ label: 'Address', field: 'address' },
{ label: 'Company', field: 'company' },
{ label: 'Email', field: 'email' },
{ label: 'Name', field: 'name' },
{ label: 'Phone', field: 'phone' },
{ label: 'Username', field: 'username' },
{ label: 'Website', field: 'website' },
];
const asyncTable = new twe.Datatable(
document.getElementById('datatable'),
{ columns, },
{ loading: true }
);
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.json())
.then((data) => {
asyncTable.update(
{
rows: data.map((user) => ({
...user,
address: `${user.address.city}, ${user.address.street}`,
company: user.company.name,
})),
},
{ loading: false }
);
});
Action buttons
With the Datatable it's possible to render custom content, such as action
buttons and attach listeners to their events. Keep in mind, that the
component rerenders content when various actions occur (f.e. sort, search)
and event listeners need to be updated. To make it possible, the components
emits a custom event render.twe.datatable
.
<div id="datatable"></div>
// Initialization for ES Users
import { Datatable } from "tw-elements";
const customDatatable = document.getElementById("datatable");
const setActions = () => {
document.querySelectorAll(".call-btn").forEach((btn) => {
btn.addEventListener("click", () => {
console.log(`call ${btn.attributes["data-twe-number"].value}`);
});
});
document.querySelectorAll(".message-btn").forEach((btn) => {
btn.addEventListener("click", () => {
console.log(
`send a message to ${btn.attributes["data-twe-email"].value}`
);
});
});
};
customDatatable.addEventListener("render.twe.datatable", setActions);
new Datatable(
customDatatable,
{
columns: [
{ label: "Name", field: "name" },
{ label: "Position", field: "position" },
{ label: "Office", field: "office" },
{ label: "Contact", field: "contact", sort: false },
],
rows: [
{
name: "Tiger Nixon",
position: "System Architect",
office: "Edinburgh",
phone: "+48000000000",
email: "tiger.nixon@gmail.com",
},
{
name: "Sonya Frost",
position: "Software Engineer",
office: "Edinburgh",
phone: "+53456123456",
email: "sfrost@gmail.com",
},
{
name: "Tatyana Fitzpatrick",
position: "Regional Director",
office: "London",
phone: "+42123432456",
email: "tfitz@gmail.com",
},
].map((row) => {
return {
...row,
contact: `
<button
type="button"
data-twe-ripple-init
data-twe-ripple-color="dark"
data-twe-number=${row.phone}
class="call-btn inline-block rounded-full border border-primary p-1.5 me-1 uppercase leading-normal text-primary transition duration-150 ease-in-out hover:border-primary-accent-300 hover:bg-primary-50/50 hover:text-primary-accent-300 focus:border-primary-600 focus:bg-primary-50/50 focus:text-primary-600 focus:outline-none focus:ring-0 active:border-primary-700 active:text-primary-700 dark:text-primary-500 dark:hover:bg-blue-950 dark:focus:bg-blue-950 [&>svg]:h-4 [&>svg]:w-4">
<svg xmlns="http://www.w3.org/2000/svg" fill="#3B71CA" viewBox="0 0 24 24" stroke-width="1.3" stroke="#3B71CA">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 002.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106c-.44-.11-.902.055-1.173.417l-.97 1.293c-.282.376-.769.542-1.21.38a12.035 12.035 0 01-7.143-7.143c-.162-.441.004-.928.38-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 00-1.091-.852H4.5A2.25 2.25 0 002.25 4.5v2.25z" />
</svg>
</button>
<button
type="button"
data-twe-ripple-init
data-twe-ripple-color="light"
data-twe-email=${row.email}
class="message-btn inline-block rounded-full border border-primary bg-primary text-white p-1.5 uppercase leading-normal shadow-primary-3 transition duration-150 ease-in-out hover:bg-primary-accent-300 hover:shadow-primary-2 focus:bg-primary-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-primary-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong [&>svg]:h-4 [&>svg]:w-4">
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke="#3B71CA">
<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" />
</svg>
</button>`,
};
}),
},
{ hover: true }
);
const customDatatable = document.getElementById("datatable");
const setActions = () => {
document.querySelectorAll(".call-btn").forEach((btn) => {
btn.addEventListener("click", () => {
console.log(`call ${btn.attributes["data-twe-number"].value}`);
});
});
document.querySelectorAll(".message-btn").forEach((btn) => {
btn.addEventListener("click", () => {
console.log(
`send a message to ${btn.attributes["data-twe-email"].value}`
);
});
});
};
customDatatable.addEventListener("render.twe.datatable", setActions);
new twe.Datatable(
customDatatable,
{
columns: [
{ label: "Name", field: "name" },
{ label: "Position", field: "position" },
{ label: "Office", field: "office" },
{ label: "Contact", field: "contact", sort: false },
],
rows: [
{
name: "Tiger Nixon",
position: "System Architect",
office: "Edinburgh",
phone: "+48000000000",
email: "tiger.nixon@gmail.com",
},
{
name: "Sonya Frost",
position: "Software Engineer",
office: "Edinburgh",
phone: "+53456123456",
email: "sfrost@gmail.com",
},
{
name: "Tatyana Fitzpatrick",
position: "Regional Director",
office: "London",
phone: "+42123432456",
email: "tfitz@gmail.com",
},
].map((row) => {
return {
...row,
contact: `
<button
type="button"
data-twe-ripple-init
data-twe-ripple-color="dark"
data-twe-number=${row.phone}
class="call-btn inline-block rounded-full border border-primary p-1.5 me-1 uppercase leading-normal text-primary transition duration-150 ease-in-out hover:border-primary-accent-300 hover:bg-primary-50/50 hover:text-primary-accent-300 focus:border-primary-600 focus:bg-primary-50/50 focus:text-primary-600 focus:outline-none focus:ring-0 active:border-primary-700 active:text-primary-700 dark:text-primary-500 dark:hover:bg-blue-950 dark:focus:bg-blue-950 [&>svg]:h-4 [&>svg]:w-4">
<svg xmlns="http://www.w3.org/2000/svg" fill="#3B71CA" viewBox="0 0 24 24" stroke-width="1.3" stroke="#3B71CA">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 002.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106c-.44-.11-.902.055-1.173.417l-.97 1.293c-.282.376-.769.542-1.21.38a12.035 12.035 0 01-7.143-7.143c-.162-.441.004-.928.38-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 00-1.091-.852H4.5A2.25 2.25 0 002.25 4.5v2.25z" />
</svg>
</button>
<button
type="button"
data-twe-ripple-init
data-twe-ripple-color="light"
data-twe-email=${row.email}
class="message-btn inline-block rounded-full border border-primary bg-primary text-white p-1.5 uppercase leading-normal shadow-primary-3 transition duration-150 ease-in-out hover:bg-primary-accent-300 hover:shadow-primary-2 focus:bg-primary-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-primary-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong [&>svg]:h-4 [&>svg]:w-4">
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke="#3B71CA">
<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" />
</svg>
</button>`,
};
}),
},
{ hover: true }
);
Cell formating
Use cell formatting to color individual cells.
<div id="datatable" data-twe-sort-field="purchases" data-twe-sort-order="desc"></div>
// Initialization for ES Users
import { Datatable } from "tw-elements";
const rows = [
["Product 1", 10, 103],
["Product 2", 45, 110],
["Product 3", 76, 56],
["Product 4", 89, 230],
["Product 5", 104, 240],
["Product 6", 97, 187],
["Product 7", 167, 130],
["Product 8", 50, 199],
["Product 9", 4, 206],
["Product 10", 120, 88],
["Product 11", 22, 100],
];
const maxValue = Math.max(...rows.map((row) => row[2]));
const minValue = Math.min(...rows.map((row) => row[2]));
const colors = [
"bg-[#E3F2FD] dark:bg-[#70a4fa]",
"bg-[#BBDEFB] dark:bg-[#5e93eb]",
"bg-[#90CAF9] dark:bg-[#4878c7]",
"bg-[#64B5F6] dark:bg-[#2463c9]",
"bg-[#42A5F5] dark:bg-[#124fb3]",
];
const step = (maxValue - minValue) / (colors.length - 1);
const formatCell = (cell, value) => {
const colorIndex = Math.floor((value - minValue) / step);
const colorClass = colors[colorIndex].split(" ");
colorClass.forEach((className) => {
cell.classList.add(className);
});
};
const columns = [
{ label: "Product", field: "product" },
{ label: "Quantity", field: "quantity" },
{ label: "Purchases", field: "purchases", format: formatCell },
];
new Datatable(document.getElementById("datatable"), {
columns,
rows,
});
const rows = [
["Product 1", 10, 103],
["Product 2", 45, 110],
["Product 3", 76, 56],
["Product 4", 89, 230],
["Product 5", 104, 240],
["Product 6", 97, 187],
["Product 7", 167, 130],
["Product 8", 50, 199],
["Product 9", 4, 206],
["Product 10", 120, 88],
["Product 11", 22, 100],
];
const maxValue = Math.max(...rows.map((row) => row[2]));
const minValue = Math.min(...rows.map((row) => row[2]));
const colors = [
"bg-[#E3F2FD] dark:bg-[#70a4fa]",
"bg-[#BBDEFB] dark:bg-[#5e93eb]",
"bg-[#90CAF9] dark:bg-[#4878c7]",
"bg-[#64B5F6] dark:bg-[#2463c9]",
"bg-[#42A5F5] dark:bg-[#124fb3]",
];
const step = (maxValue - minValue) / (colors.length - 1);
const formatCell = (cell, value) => {
const colorIndex = Math.floor((value - minValue) / step);
const colorClass = colors[colorIndex].split(" ");
colorClass.forEach((className) => {
cell.classList.add(className);
});
};
const columns = [
{ label: "Product", field: "product" },
{ label: "Quantity", field: "quantity" },
{ label: "Purchases", field: "purchases", format: formatCell },
];
new twe.Datatable(document.getElementById("datatable"), {
columns,
rows,
});
Clickable rows
Click on the row to preview the message.
Note: To prevent this action with other clickable
elements within the row, call stopPropagation()
method.
Note: This feature cannot be used simultaneously with
edit
option.
Modal title
<div
id="datatable-clickable-rows"
data-twe-clickable-rows="true"
data-twe-selectable="true"
data-twe-multi="true"></div>
<!-- Modal -->
<div
class="fixed left-0 top-0 z-[1055] hidden h-full w-full overflow-y-auto overflow-x-hidden outline-none"
id="modal-clickable-rows"
tabindex="-1"
aria-labelledby="exampleModalLabel"
aria-hidden="true"
data-twe-modal-init>
<div
data-twe-modal-dialog-ref
class="pointer-events-none relative w-auto translate-y-[-50px] opacity-0 transition-all duration-300 ease-in-out min-[576px]:mx-auto min-[576px]:mt-7 min-[576px]:max-w-[500px]">
<div
class="min-[576px]:shadow-[0_0.5rem_1rem_rgba(#000, 0.15)] pointer-events-auto relative flex w-full flex-col rounded-md border-none bg-white bg-clip-padding text-current shadow-lg outline-none dark:bg-neutral-600">
<div
id="modal-header-clickable-rows"
class="flex flex-shrink-0 items-center justify-between rounded-t-md border-b-2 border-neutral-100 border-opacity-100 p-4 dark:border-opacity-50">
<!--Modal title-->
<h5
class="text-xl font-medium leading-normal text-neutral-800 dark:text-neutral-200"
id="exampleModalLabel">
Modal title
</h5>
</div>
<!--Modal body-->
<div
id="modal-body-clickable-rows"
class="relative flex-auto p-4"
data-twe-modal-body-ref>
Modal body text goes here.
</div>
<!--Modal footer-->
<div
class="flex flex-shrink-0 flex-wrap items-center justify-end rounded-b-md border-t-2 border-neutral-100 border-opacity-100 p-4 dark:border-opacity-50">
<button
type="button"
class="flex items-center rounded border border-primary px-5 pb-1.5 pt-2 text-xs font-medium uppercase leading-normal text-primary-700 transition duration-150 ease-in-out focus:outline-none focus:ring-0 dark:border-primary-400 dark:text-primary-400"
data-twe-ripple-init
data-twe-ripple-color="dark">
Reply
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="ms-2 h-5 w-5">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5" />
</svg>
</button>
<button
type="button"
class="ms-1 flex items-center rounded border border-primary px-5 pb-1.5 pt-2 text-xs font-medium uppercase leading-normal text-primary-700 transition duration-150 ease-in-out dark:border-primary-400 dark:text-primary-400"
data-twe-ripple-init
data-twe-ripple-color="dark">
Forward
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="ms-2 h-5 w-5">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3" />
</svg>
</button>
</div>
</div>
</div>
</div>
// Initialization for ES Users
import {
Datatable,
Modal,
Ripple,
initTWE,
} from "tw-elements";
initTWE({ Ripple });
const table = document.getElementById("datatable-clickable-rows");
const modal = document.getElementById("modal-clickable-rows");
const modalBody = document.getElementById("modal-body-clickable-rows");
const modalHeader = document.getElementById(
"modal-header-clickable-rows"
);
const modalInstance = new Modal(modal);
const setupButtons = (action) => {
document
.querySelectorAll(`.${action}-email-button`)
.forEach((button) => {
button.addEventListener("click", (e) => {
e.stopPropagation();
const index = button.getAttribute("data-twe-index");
console.log(`${action} message: ${index}`, messages[index]);
});
});
};
const columnsClickable = [
{ label: "Actions", field: "actions", sort: false },
{ label: "From", field: "from" },
{ label: "Title", field: "title" },
{ label: "Message", field: "preview", sort: false },
{ label: "Date", field: "date" },
];
const messages = [
{
from: "admin@mdbootstrap.com",
title: "TW elements spring sale",
message:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sed metus ultricies, sollicitudin est nec, blandit turpis. Fusce venenatis nisi volutpat, pharetra elit eu, ullamcorper metus. Vestibulum dapibus laoreet aliquam. Maecenas sed magna ut libero consequat elementum. Maecenas euismod pellentesque pulvinar. Morbi sit amet turpis eget dolor rutrum eleifend. Sed bibendum diam nec diam posuere pulvinar. Cras ac bibendum arcu.",
date: "11/12/2019",
},
{
from: "user@mdbootstrap.com",
title: "How to use TW elements?",
message:
"Quisque tempor ligula eu lobortis scelerisque. Mauris tristique mi a erat egestas, quis dictum nibh iaculis. Sed gravida sodales egestas. In tempus mollis libero sit amet lacinia. Duis non augue sed leo imperdiet efficitur faucibus vitae elit. Mauris eu cursus ligula. Praesent posuere efficitur cursus.",
date: "10/12/2019",
},
{
from: "user@mdbootstrap.com",
title: "Licence renewal",
message:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sed metus ultricies, sollicitudin est nec, blandit turpis. Fusce venenatis nisi volutpat, pharetra elit eu, ullamcorper metus. Vestibulum dapibus laoreet aliquam. Maecenas sed magna ut libero consequat elementum. Maecenas euismod pellentesque pulvinar. Morbi sit amet turpis eget dolor rutrum eleifend. Sed bibendum diam nec diam posuere pulvinar. Cras ac bibendum arcu.",
date: "09/12/2019",
},
{
from: "admin@mdbootstrap.com",
title: "Black friday offer",
message:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sed metus ultricies, sollicitudin est nec, blandit turpis. Fusce venenatis nisi volutpat, pharetra elit eu, ullamcorper metus. Vestibulum dapibus laoreet aliquam. Maecenas sed magna ut libero consequat elementum. Maecenas euismod pellentesque pulvinar. Morbi sit amet turpis eget dolor rutrum eleifend. Sed bibendum diam nec diam posuere pulvinar. Cras ac bibendum arcu.",
date: "08/12/2019",
},
];
const rowsClickable = messages.map((email, i) => {
const getPreview = (message, length) => {
if (message.length <= length) return message;
return `${message.slice(0, length)}...`;
};
return {
...email,
preview: getPreview(email.message, 20),
actions: `
<div class="flex">
<a role="button" class="star-email-button text-warning" data-twe-index="${i}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z" />
</svg>
</a>
<a role="button" class="delete-email-button text-neutral-300 ms-2" data-twe-index="${i}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
</svg>
</a>
</div>
`,
};
});
table.addEventListener("rowClicked.twe.datatable", (e) => {
const { index } = e;
const { message, title, from } = messages[index];
modalHeader.innerHTML = `
<h5 class="text-xl font-medium leading-normal text-surface dark:text-white">${title}</h5>
<button
id="close-button"
type="button"
class="box-content rounded-none border-none focus:text-neutral-800 text-neutral-500 hover:text-neutral-800 hover:no-underline focus:opacity-100 focus:shadow-none focus:outline-none dark:text-neutral-400 dark:focus:text-neutral-300 dark:hover:text-neutral-300 [&>svg]:h-6 [&>svg]:w-6"
data-twe-modal-dismiss
aria-label="Close">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6 18L18 6M6 6l12 12" />
</svg>
</button>`;
modalBody.innerHTML = `
<h6 class="mb-4">From: <strong>${from}</strong></h6>
<p>${message}</p>
`;
modalInstance.show();
document
.getElementById("close-button")
.addEventListener("click", () => {
modalInstance.hide();
});
});
table.addEventListener("render.twe.datatable", () => {
setupButtons("star");
setupButtons("delete");
});
const datatableInstance = new Datatable(table, {
columns: columnsClickable,
rows: rowsClickable,
});
const table = document.getElementById("datatable-clickable-rows");
const modal = document.getElementById("modal-clickable-rows");
const modalBody = document.getElementById("modal-body-clickable-rows");
const modalHeader = document.getElementById(
"modal-header-clickable-rows"
);
const modalInstance = new twe.Modal(modal);
const setupButtons = (action) => {
document
.querySelectorAll(`.${action}-email-button`)
.forEach((button) => {
button.addEventListener("click", (e) => {
e.stopPropagation();
const index = button.getAttribute("data-twe-index");
console.log(`${action} message: ${index}`, messages[index]);
});
});
};
const columnsClickable = [
{ label: "Actions", field: "actions", sort: false },
{ label: "From", field: "from" },
{ label: "Title", field: "title" },
{ label: "Message", field: "preview", sort: false },
{ label: "Date", field: "date" },
];
const messages = [
{
from: "admin@mdbootstrap.com",
title: "TW elements spring sale",
message:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sed metus ultricies, sollicitudin est nec, blandit turpis. Fusce venenatis nisi volutpat, pharetra elit eu, ullamcorper metus. Vestibulum dapibus laoreet aliquam. Maecenas sed magna ut libero consequat elementum. Maecenas euismod pellentesque pulvinar. Morbi sit amet turpis eget dolor rutrum eleifend. Sed bibendum diam nec diam posuere pulvinar. Cras ac bibendum arcu.",
date: "11/12/2019",
},
{
from: "user@mdbootstrap.com",
title: "How to use TW elements?",
message:
"Quisque tempor ligula eu lobortis scelerisque. Mauris tristique mi a erat egestas, quis dictum nibh iaculis. Sed gravida sodales egestas. In tempus mollis libero sit amet lacinia. Duis non augue sed leo imperdiet efficitur faucibus vitae elit. Mauris eu cursus ligula. Praesent posuere efficitur cursus.",
date: "10/12/2019",
},
{
from: "user@mdbootstrap.com",
title: "Licence renewal",
message:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sed metus ultricies, sollicitudin est nec, blandit turpis. Fusce venenatis nisi volutpat, pharetra elit eu, ullamcorper metus. Vestibulum dapibus laoreet aliquam. Maecenas sed magna ut libero consequat elementum. Maecenas euismod pellentesque pulvinar. Morbi sit amet turpis eget dolor rutrum eleifend. Sed bibendum diam nec diam posuere pulvinar. Cras ac bibendum arcu.",
date: "09/12/2019",
},
{
from: "admin@mdbootstrap.com",
title: "Black friday offer",
message:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sed metus ultricies, sollicitudin est nec, blandit turpis. Fusce venenatis nisi volutpat, pharetra elit eu, ullamcorper metus. Vestibulum dapibus laoreet aliquam. Maecenas sed magna ut libero consequat elementum. Maecenas euismod pellentesque pulvinar. Morbi sit amet turpis eget dolor rutrum eleifend. Sed bibendum diam nec diam posuere pulvinar. Cras ac bibendum arcu.",
date: "08/12/2019",
},
];
const rowsClickable = messages.map((email, i) => {
const getPreview = (message, length) => {
if (message.length <= length) return message;
return `${message.slice(0, length)}...`;
};
return {
...email,
preview: getPreview(email.message, 20),
actions: `
<div class="flex">
<a role="button" class="star-email-button text-warning" data-twe-index="${i}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z" />
</svg>
</a>
<a role="button" class="delete-email-button text-neutral-300 ms-2" data-twe-index="${i}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
</svg>
</a>
</div>
`,
};
});
table.addEventListener("rowClicked.twe.datatable", (e) => {
const { index } = e;
const { message, title, from } = messages[index];
modalHeader.innerHTML = `
<h5 class="text-xl font-medium leading-normal text-surface dark:text-white">${title}</h5>
<button
id="close-button"
type="button"
class="box-content rounded-none border-none text-neutral-500 hover:text-neutral-800 focus:text-neutral-800 hover:no-underline focus:opacity-100 focus:shadow-none focus:outline-none dark:text-neutral-400 dark:hover:text-neutral-300 dark:focus:text-neutral-300 [&>svg]:h-6 [&>svg]:w-6"
data-twe-modal-dismiss
aria-label="Close">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6 18L18 6M6 6l12 12" />
</svg>
</button>`;
modalBody.innerHTML = `
<h6 class="mb-4">From: <strong>${from}</strong></h6>
<p>${message}</p>
`;
modalInstance.show();
document
.getElementById("close-button")
.addEventListener("click", () => {
modalInstance.hide();
});
});
table.addEventListener("render.twe.datatable", () => {
setupButtons("star");
setupButtons("delete");
});
const datatableInstance = new twe.Datatable(table, {
columns: columnsClickable,
rows: rowsClickable,
});
Related resources
If you are looking for more advanced options, try Bootstrap Datatables from MDBootstrap.