Note: This is a work in progress. If you find any bugs, please report them on the GitHub repository.
The power of tables.js comes from the dynamic nature of the Table class. While jQuery Datatables is a great tool, and it's customizable, I want to expand on its functionality. You can combine datatables with this tool by just setting options.datatable = true
or options.datatable = { ...datatableOptions }
. I have added state management and independent cell/row/column rendering through the object the Table class creates. My goal is for you to have full customizability over your tables
<!-- Most recent version -->
<script src="https://cdn.jsdelivr.net/npm/tables-js/tables.min.js"></script>
<!-- Specific version -->
<script src="https://cdn.jsdelivr.net/npm/tables-js/1.1/tables.min.js"></script> <!-- Uses Table Class -->
<script src="https://cdn.jsdelivr.net/npm/tables-js/1.0/tables.min.js"></script> <!-- Uses setTable -->
It is recommended that tables.js be used with Bootstrap, jQuery, jQuery Datatables, and Material Icons, but it is not required. If you want to use tables.js with Bootstrap, jQuery, and jQuery Datatables, you can use the following code:
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.2.1/css/bootstrap.min.css">
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
<script src="https://cdn.datatables.net/1.10.24/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.10.24/js/dataTables.bootstrap4.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/js-tables/1.1/tables.js"></script>
Initialize the table with the following code:
const table = document.getElementById("my-table");
const headers = ["Name", "Age", "Favorite Color"]; // these can be objects as well
const data = [
{
Name: "John",
Age: 20,
"Favorite Color": "Blue"
},
{
Name: "Jane",
Age: 21,
"Favorite Color": "Red"
}
];
const options = { // optional
// options go here
};
const myTable = new Table(table, headers, data, options);
const myTable = Table.from(table, json, options, 'json'); // 'json' may not be necessary as the function attempts to detect the type of data.
// or
const myTable = Table.fromJSON(table, json, options);
const myTable = Table.from(table, csv, options, 'csv'); // 'csv' may not be necessary as the function attempts to detect the type of data.
// or
const myTable = Table.fromCSV(table, csv, options);
const myTable = Table.from(table, tsv, options, 'tsv'); // 'tsv' may not be necessary as the function attempts to detect the type of data.
// or
const myTable = Table.fromTSV(table, tsv, options);
const myTable = Table.from(table /* already populated table */, null /* Data is in the prerendered table */, options, 'html'); // 'html' may not be necessary as the function attempts to detect the type of data.
// or
const myTable = Table.fromHTML(table, options);
class Table {
/* properties */
el // the table element
headers // the headers
data // the data
originalData // data before any filters/sorting/inserting/deleting
options // the options
states // the Table_StateStack object
rows // the array of TableRow objects
tableHeaders // the array of TableHeader objects
columns // the array of TableColumn objects
/* public methods */
render() // renders the table
destroy(deleteData: Booean) // destroys the table (deleteData removes all data from the Table object)
/* public static methods */
static fromHTML(table: HTMLElement, options: Object) // creates a Table object from a populated html table
static fromJSON(table: HTMLElement, json: String, options: Object) // creates a Table object from a JSON string
static fromCSV(table: HTMLElement, csv: String, options: Object) // creates a Table object from a CSV string
static fromTSV(table: HTMLElement, tsv: String, options: Object) // creates a Table object from a TSV string
static from(table: HTMLElement, data: Object | null, options: Object | null, type: String | null) // autodetects the type of data and creates a Table object from your inputs
/* private methods */
showInsertRows(TableRow) // shows the insert rows above and below the TableRow (used with reorderable tables)
hideInsertRows() // hides the insert rows
renderRows() // renders the rows
renderHeaders() // renders the headers
sort() // used with sortable tables
update() // updates the table
renderFromContent() // renders the table from the content of the Table object (for use with editable tables)
/* getters */
get content() // returns the array of TableRow.content objects
get json() // returns JSON.stringify(this.content)
}
This class is an extension of js-state-stack and is used to keep track of the table's/cell's state. It is used to keep track of the table's state when editing, sorting, etc. It is not recommended to use this class directly.
class TableRow {
/* properties */
cells // the array of TableCell objects
el // the tr element
data // the data passed into header.getData()
/* private methods */
render() // renders the row
/* getters */
get content() // returns an object of {headerTitle: tableCell.content}
}
class TableHeader {
/* properties */
el // the th element
options // the options (currently unused)
/* public methods */
changeContent(content) // changes the content of the header and rerenders the <td> element
/* private methods */
render() // renders the header
}
An extension of TableHeader
that is used with the footer
option. This has all the same properties and methods as TableHeader
.
class TableColumn {
/* properties */
cells // the array of TableCell objects (includes the header cell)
header // the TableHeader object
/* getters */
get content() // returns an array of the content of each cell in the column (includes the header cell)
}
class TableCell {
/* properties */
el // the td or th element
colPos // the column position
rowPos // the row position
headerTitle // the header title
header // the TableHeader object
editable // the editable object in Table.options
stack // the Table_StateStack object
/* public methods */
onChange() // custom function that is called when the cell is changed (used with editable tables)
onCancel() // custom function that is called when the cell is cancelled (used with editable tables)
changeContent(content) // changes the content of the cell and rerenders the <td> element
/* private methods */
render() // renders the cell
}
This is only used with reorderable tables. These are the rows that hare hidden and shown when you drag over a row. They are used to insert a row above or below the row you are dragging over.
class ReorderInsertRow {
el // the tr element
row // the TableRow object that the insert row is above or below
/* private methods */
show() // shows the insert row
hide() // hides the insert row
}
All you need to do is pass in the table element you want to use. This can be done in two ways:
const myTable = new Table(document.getElementById("my-table"));
or
const myTable = new Table("#my-table"); // you can use any selector you wish :)
The headers parameter is either an object where each key is the header name, or an array of objects or strings that generate each header and subsequent column. If you pass in an array of strings, the headers will be generated with the string as the header text and the data will be generated by row[headerTitle]. If you pass in an array of objects, you must specify the following properties:
header.title || header.name || header.key
which will be the string that the<th>
is populated withheader.getData
this must be a function that returns either a string to populate inside of the<td>
element usingtd.innerHTML
, or an element to populate inside of the<td>
usingtd.appendChild()
. If noheader.getData
is specified, it will returnrow[header.title || header.name || header.key]
.
All of these examples will make this in HTML:
<table>
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr>
<td>John</th>
</tr>
</tbody>
</table>
Object Array Header Example:
const headers = [{
title: 'Name',
getData: (row) => {
return row.name;
}
}];
const data = [{
name: 'John'
}];
const myTable = new Table("#my-table", headers, data);
String Array Header Example:
const headers = ['Name'];
const data = [{
Name: 'John' // notice how this must be the same capitalization as the header
}];
const myTable = new Table("#my-table", headers, data);
Object Header Example:
const headers = {
Name: {
getData: (row) => {
return row.name;
}
}
};
const data = [{
name: 'John'
}];
const myTable = new Table("#my-table", headers, data);
The other properties of the header objects are used to customize the columns.
All the properties below are written as header.property
. These properties are the same whether you pass in an array of objects or an object.
header.th.classes
is an array of strings that will be added to the <th>
element.
header.td.classes
is an array of strings that will be added to each <td>
element in this column.
header.td.classTests
is an array of objects that will be used to add classes to the <td>
element. Each object must have the following properties:
test
is a function that returns a boolean. If the function returns true, the class will be added to the<td>
element.class
(OPTIONAL) is a string that will be added to the<td>
element if thetest
function returns true.classes
(OPTIONAL) is an array of strings that will be added to the<td>
element if thetest
function returns true.
Example:
const headers = [{
title: 'Name',
th: {
classes: ['my-class', 'my-other-class'] // applies to <th>
},
td: {
classes: ['my-td-class', 'my-other'], // applies to <td>
classTests: [{
test: (row) => {
return row.name === 'John';
},
class: 'my-class' // applies to <td>
}, {
test: (row) => {
return row.name === 'John';
},
classes: ['my-class', 'my-other-class'] // applies to <td>
}]
}
}];
header.td.attributes
is an array of objects that will be added to the <td>
element. Each object must have the following properties:
name
is a string that will be the name of the attribute.value
is a either a string that is the value of the attribute, or a function that returns a string that is the value of the attribute that passes in the row as a parameter.header.th.attributes
is an array of objects that will be added to the<th>
element.header.th.attrubutes[].value
can only be a string, not a function.
Example:
const headers = [{
title: 'Name',
td: {
attributes: [{
name: 'data-name',
value: 'John'
}, {
name: 'data-name',
value: (row) => { // row is a TableRow object
return row.name;
}
}]
},
td: {
attributes: [{
name: 'data-name',
value: 'John'
}]
}
}];
All lisener arrays are structured the same way. Each object must have the following properties:
type
is a string that is the name of the event.callback
(v1.1.x) is a function that is the listener that passes in the event as a parameter. (Uses a different data structure than v1.0.x)action
(v1.0.x) is a function that is the listener that passes in the event as a parameter.
header.th.listeners
is an array of objects that will be added to the <th>
element.
header.td.listeners
is an array of objects that will be added to each <td>
element in this column.
Example:
const headers = [{
title: 'Name',
th: {
listeners: [{
event: 'click', // runs when the `<th>` is clicked
callback: (event) => { // v1.1.x
/*
event is an event object with the following expanded property:
- event.__row = TableRow object
*/
console.log(event);
}
}]
},
td: {
listeners: [{
event: 'click',
action: ({
event, // event object
row, // data row (custom data)
tableRow // TableRow object
}) => { // v1.0.x
console.log(event);
}
}]
}
}];
header.sort
is a sorting function that runs when the header is clicked. It sorts through the data and rerenders the table. This does not change table.originalData
, only table.data
. Currently, if you click it once, it sorts up, click again, it sorts down, and click again, it sorts back to the original order. This will be changed in the future to allow for more customization.
Example:
const headers = [{
title: 'Name',
sort: (a, b) => { // a and b are TableRow objects
return a.data.name > b.data.name;
}
}];
<td>
Editing (to be used with options.editable
)
header.editable
is a boolean that determines if the <td>
element is editable. If true, each <td>
element will have a contenteditable attribute.
const headers = [{
title: 'Name',
editable: true
}, {
title: 'Age',
editable: false
}];
const data = [{
Name: 'John',
Age: 34
}];
const options = {
editable: true
};
Will create
<table>
<thead>
<tr>
<th>Name</th>
</tr>
<tr>
<th>Age</th>
</tr>
</thead>
<tbody>
<tr>
<td contenteditable="true">John</td>
</tr>
<tr>
<td>34</td>
</tr>
</tbody>
</table>
Note: Even if a user edits the html, it will not change TableCell.content
header.minimize
is a boolean that determines if the <th>
element is minimized. If true, the <th>
element will have a minimize
class.
const headers = [{
title: 'Name',
minimize: true
}, {
title: 'Age',
minimize: false
}];
Will create
<table>
<thead>
<tr>
<th>
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>Name</div>
<!-- This icon can change with options.minimize.open -->
<i class="material-icons">chevron_left</i>
</div>
</th>
</tr>
<tr>
<th>Age</th>
</tr>
</thead>
<tbody>
<tr>
<td>John</td>
</tr>
<tr>
<td>John</td>
</tr>
</tbody>
</table>
The header array is used to represent each row in the table. This is fully customizable because nothing from the Table
class reads this data, only your getData
does and other custom functions.
Each row's data will be in TableRow.data
.
options.tr.listeners
is the same as header.th.listeners
and header.td.listeners
except it applies to all <tr>
elements.
options.tr.attributes
is the same as header.th.attributes
except it applies to all <tr>
elements.
options.tr.classes
is the same as header.th.classes
except it applies to all <tr>
elements.
options.tr.classTests
is the same as header.td.classTests
except it applies to all <tr>
elements.
options.datatable = true
or options.dataTable = true
will create a Jquery DataTable. This will override all other options except options.editable
.
header.sortable
will make every <th>
element sortable. This will not override header.sort
. The sort function will be the same as the default header sort function ((a, b) => a.el.innerText.localCompare(b.el.innerText);
).
options.evenColumns = true
will set the width to be the same for every column using percentages.
minimize.open
is the icon that will be used for the minimize button. The default is <i class="material-icons">chevron_left</i>
.
minimize.minimized
is a boolean that determines if the table is minimized. The default is false
.
options.caption
is a caption you can add onto the table. This will be added to the <table>
element.
Example:
const options = {
caption: 'This is a caption'
}
// or
const options = {
caption: {
content: 'This is a caption',
attributes: [], // Attributes onto the <caption> element
classes: [], // Classes onto the <caption> element
listeners: [] // Event listeners onto the <caption> element
}
}
Will create
<table>
<caption>This is a caption</caption>
<thead>
<!-- Your headers -->
</thead>
<tbody>
<!-- Your rows -->
</tbody>
</table>
- Reorder: Drag and drop to reorder rows and columns. Currently working on this, you can try it out by setting
options.reorder = true
to see what is happening. If you have tips please let me know. - Search: Search through the table using fuzzy search
- Pagination: Different pagination options for the table (ex. using dots, arrows, select, etc.)
- Insertion: Insert a row or column
- Deletion: Delete a row or column
Note: This version is deprecated and will not be updated. Please use version 1.1.x. The basics are the same, but the options and headers parameters are different.
const headers = [{
title: 'Name',
getData: (row) => row.name,
listeners: [{ // applied only to <td> elements
event: 'click',
callback: (e) => {
console.log(e);
}
}],
classes: ['class1', 'class2'], // applied only to <th> elements
tdClassTests: [{ // applied only to <td> elements
test: (row) => row.name === 'John',
classes: ['class1', 'class2']
}],
tdClasses: ['class1', 'class2'], // applied only to <td> elements
tdAttributes: [{ // applied only to <td> elements
name: 'contenteditable',
value: 'true' // can be string
value: (row) => row.name === 'John' ? 'true' : 'false' // or function
}]
}]
const options = {
evenColumns: true,
trAttributes: [{
name: 'contenteditable',
value: 'true' // can be string
value: (row) => row.name === 'John' ? 'true' : 'false' // or function
}],
appendTest: (row) => row.name === 'John',
trClasses: ['class1', 'class2'],
trClassTests: [{
test: (row) => row.name === 'John',
classes: ['class1', 'class2']
}],
trListeners: [{
event: 'click',
callback: (e) => {
console.log(e);
}
}]
}
setTable(document.getElementById('my-table'), headers, data, options);