Tablefiniti
Tablefiniti is a table (ReactJS Component) that is adapted for the spefic needs of Datafiniti Web projects that use tables and require a variety of graphicial options, such as expandable rows, custom React Components on divisions and a form-agnostic paradigm.
Initial set up
The initial set up of Tablefiniti is quite easy. First import Tablefiniti into your React Project:
import React, { Component } from 'react'
import Tablefiniti from 'tablefiniti'
After that the component itself only need 3 obligatory props to work:
rows
an array of rows in the Tablefiniti formatheaders
a simple array of stringstotalRecords
an integer, most of the time returned by the server, that states the total amount of records that can be fetched
A simple set up of the table looks like this:
import React, { Component } from 'react'
import Tablefiniti from 'tablefiniti'
class SimpleTable extends Component {
render () {
let headers = ['Id', 'Firstname', 'Lastname']
let rows = [{
Id: '1',
Firstname: 'Francisco',
Lastname: 'Jimenez'
}, {
Id: '2',
Firstname: 'Vincent',
Lastname: 'Zierigen'
}]
return (
<Tablefiniti
rows={rows}
headers={headers}
totalRecords={rows.length}
/>
)
}
}
export default SimpleTable
Notice that for totalRecords the length of the rows
is being used, but most of the time will be a number higher than the amount of the present data, as it can represent the total amount of records a query inside the server holds
Data format and types
The data that Tablefiniti consumes must be in a specific format. Let's take a look at following data:
let headers = ['Id', 'Firstname', 'Lastname']
let rows = [{
Id: '1',
Firstname: 'Juan',
Lastname: 'Valdez'
}, {
Id: '2',
Firstname: 'Jane',
Lastname: 'Doe'
}]
Notice that the keys
on each object that represent a row, match exactly the name of a header
. Tablefiniti will not render a column that does not have matching header. (Credits to: Trevor Pace for the idea of using objects to represent the rows).
So far, only strings are represented on data, but tablefiniti support various types of data. Take a look a this more complex data model:
let headers = ['Id', 'Firstname', 'Lastname', 'Link']
let rows = [{
Id: '1',
Firstname: 'Juan',
Lastname: 'Valdez',
Link: {
type: 'link',
data: 'Go!',
url: '#here'
}
}, {
Id: '2',
Firstname: 'Jane',
Lastname: {
type: 'string',
data: 'Doe'
},
Link: {
type: 'link',
data: 'Download!',
url: '#download'
}
}]
Link is a type of data that generates an <a>
tag with a text inside, being the data
the text and the url the href
used address.
Data Types
There are 3 types of data available for Tablefiniti:
String
Strings can be declared in the complex form using type and data, but it is not necesary, as strings can be interpreted by Tablefiniti in ther simple regular JS form.
Lastname: {
type: 'string',
data: 'Doe'
}
OR
Lastname: 'Doe'
Link
Link: {
type: 'link',
data: 'Download!',
url: 'https://myawesomedomain.com/tablefinitirules:1337'
}
Compound
Compound is a group of 2 text represented as simple strings that can be displayed using the following format:
{
type: 'compound',
title: 'Some Title',
subTitle: `This is a subtitle`
}
Custom
Custom is the most complex of the 3 types as it allows you to create a custom React generator
that returns a React Component.
Sample of a generator function
const editButton = myID => {
return (
<a onClick={alert(myID)} className='btn btn-info btn-sm'>
<span className='icon-pencil' />
</a>
)
}
let rows = [
...
{
'ID': '248148',
'Status': {
type: 'custom',
generator: editButton('Some Text or ID')
},
'Num of Records': '300',
'Date/TimeStarted': 'May 4, 2016 at 3:15pm'
}
...
]
Hidden Rows
Tablefiniti allows you set up hidden rows that are toggable by clicking the first division of the parent
object.
Row with a child element
...
{
'ID': '248148',
'Num of Records': '300',
'Date/TimeStarted': 'May 4, 2016 at 3:15pm',
'Status': 'In Progress',
child: {
title: 'Criteria Selected',
data: {
'Source': '*walmart*',
'SourceUrls': 'Reviews',
'DateUpdated': '*2017-01-30*',
'Brand': 'dell'
}
}
}
...
The data keys of the child object are rendered inside of a single divison ( td
) with a colSpan equal to the amount of columns in the table.
IMPORTANT
The father object's first column corresponding value HAS to be a string for this feature to work correctly, otherwise, Tablefiniti won't process the data and will dispatch an error.
Props
Besides the 3 needed props for operation, Tablefiniti has 2 other props that can be used:
currentPage
currentPage
lets the user ser manually which page should the paginator indicate the table is in. It must be a Number
. This is an optional prop.
sorting
sorting
lets the user override or manually place a sorting object, which should always be in the following format:
sorting: {
header: '',
order: ''
}
This is an optional prop.
onQueryChange(query)
onQueryChange(query)
is a function prop that can be passed to Tablefiniti, this function will be triggered everytime a part of Tablefiniti regarding the order of view (sorting, paging or records per page) is changed.
Example
...
onQueryChange (query) {
}
...
<Tablefiniti onQueryChange={this.onQueryChange} config={config} rows={rows} headers={headers} totalRecords={totalRecords} />
query {
"currentPage": 4,
"rowsPerPage": 10,
"sorting": {
"header": "Type",
"order": "asc"
}
}
config {}
The config
prop allows the user set up several configuration options to make Tablefiniti more suitable to the user's needs.
Sample of a complete configuration object
let config = {
noDataMessage: 'There is no data available.',
rowsPerPageVisibility: {
up: true,
down: false,
expandCollapse: false
},
customCSS: {
table: 'separation anxiety',
rows: 'spiderman venom mary jane watson',
paginator: 'maximum carnage'
},
nonSortableHeaders: ['Edit'],
childSorting: (propA, propB) => {
if (propA === 'Rank') {
return -1
} else {
return 1
}
}
}
The `childSorting` is the only inner object function of the `config` object. It has a default value of `null` and takes a function as an acceptable value. The function itself is the main param of`Array.sort()`, for which is the reason the function has 2 props that has to be compared and return 1 of 3 possible values, `[0,1,-1]`. The `childSorting()` is a function that is run over an iteration made by `Array.sort()` therefore `propA` and `propB` will mutate is value on each iteration and if the results (depends on how the user wants to sort the properties inside the child element of the row) is equal then a `0` is returned, if it is less (will go first in `desc` order) then a `-1` is returned and it is is greater a `1` is returned.
Example (taken from MDN):
function compare(a, b) {
if (a is less than b by some ordering criterion) {
return -1;
}
if (a is greater than b by the ordering criterion) {
return 1;
}
return 0;
}
Note: rowsPerPageVisibility
allows the user to hide the rowsPerPage
on the paginator modules and includes a option called expandCollapse
that allows a expand/collapse
button to appear, this button will either expand of collapse all availble childen row (if any, if there's no children rows, nothing will happen)
This is Tablefiniti's default config inner JSON
{
rowsPerPageVisibility: {
up: true,
down: true,
expandCollapse: false
},
customCSS: {
table: '',
rows: '',
paginator: ''
},
nonSortableHeaders: [],
childSorting: null
}
No data provided
When Tablefiniti recives no data or the data was not fetched, Tablefiniti will display the message inside noDataMessage
on the configuration object. It will render a simple table with the provided headers and the message.
If no headers are provided, then Id and Name will appear as default headers.
Examples
Simple Table
This is a simple example of how to set up Tablefiniti with the bare minimum using the 3 data types:
import React, { Component } from 'react'
import Tablefiniti from '../Tablefiniti.jsx'
class SimpleTable extends Component {
constructor (props) {
super(props)
this.state = {
rows: [],
headers: []
}
this.editButtonGenerator = this.editButtonGenerator.bind(this)
}
componentDidMount () {
let headers = ['ID', 'Num of Records', 'Date/TimeStarted', 'Status']
let rows = [
{
'ID': '248148',
'Num of Records': '300',
'Date/TimeStarted': 'May 4, 2016 at 3:15pm',
'Status': 'In Progress',
child: {
title: 'Criteria Selected',
data: {
'Source': '*walmart*',
'SourceUrls': 'Reviews',
'DateUpdated': '*2017-01-30*',
'Brand': 'dell'
}
}
},
{
'ID': '248148',
'Status': {
type: 'link',
data: 'Download Records',
url: '#downloads_records'
},
'Num of Records': '300',
'Date/TimeStarted': 'May 4, 2016 at 3:15pm'
},
{
'ID': '248148',
'Status': {
type: 'custom',
generator: this.editButtonGenerator('Hey hey')
},
'Num of Records': '300',
'Date/TimeStarted': 'May 4, 2016 at 3:15pm'
}
]
this.setState({rows, headers})
}
editButtonGenerator (id) {
return (
<a onClick={e => alert(`This would edit id: ${id}`)} className='btn btn-info btn-sm'>
<span className='icon-pencil' />
</a>
)
}
render () {
let { rows, headers } = this.state
if (rows.length === 0 || headers === 0) {
return (<div />)
}
return (
<div style={{margin: 100}}>
<Tablefiniti
rows={rows}
headers={headers}
totalRecords={rows.length}
/>
</div>
)
}
}
export default SimpleTable
Explicit Example
An explicit example that implements all Tablefiniti's options, server simulation and a search form can be seeing here.