UNPKG

react-jquery-datatables

Version:

React, DataTables.net (aka jQuery DataTables), fork of https://github.com/carlosrocha/react-data-components

286 lines (260 loc) 9.81 kB
import React, { Component} from 'react'; import PropTypes from 'prop-types'; import ReactDOMServer from 'react-dom/server'; const $ = require('jquery'); const dt = require('datatables.net'); // eslint-disable-line no-unused-vars const buttons = require('datatables.net-buttons'); // eslint-disable-line no-unused-vars const responsive = require('datatables.net-responsive'); // eslint-disable-line no-unused-vars const responsiveBs = require('datatables.net-responsive'); // eslint-disable-line no-unused-vars // const buttonStyles = require('datatables.net-buttons-dt/css/buttons.dataTables.css'); // eslint-disable-line no-unused-vars // const defaultStyles = require('datatables.net-dt/css/jquery.dataTables.css'); // eslint-disable-line no-unused-vars // const responsiveStyles = require('datatables.net-responsive-dt/css/responsive.dataTables.css'); // eslint-disable-line no-unused-vars import {connect} from 'react-redux'; import {push} from 'react-router-redux'; import _ from 'lodash'; require('datatables.net-buttons/js/buttons.colVis.js'); // Column visibility require('datatables.net-buttons/js/buttons.flash.js'); // Flash file export require('datatables.net-buttons/js/buttons.html5.js'); // HTML 5 file export require('datatables.net-buttons/js/buttons.print.js'); // Print view button let simpleGet = key => data => data[key]; let keyGetter = keys => data => keys.map(key => data[key]); let isEmpty = value => value == null || value === ''; // eslint-disable-line eqeqeq let getCellValue = ({prop, defaultContent, render}, row) => // Return `defaultContent` if the value is empty. !isEmpty(prop) && isEmpty(row[prop]) ? defaultContent : // eslint-disable-line no-nested-ternary // Use the render function for the value. render ? render(row[prop], row) : // Add Commas for Number formatting // addCommas ? // Otherwise just return the value. row[prop]; let getCellClass = ({prop, className}, row) => !isEmpty(prop) && isEmpty(row[prop]) ? 'empty-cell' : // eslint-disable-line no-nested-ternary typeof className == 'function' ? className(row[prop], row) : // eslint-disable-line eqeqeq className; // This function accepts an integer, and produces a piece of HTML that shows it nicely with // some empty space at "thousand" markers. // Note, these space are not spaces, if you copy paste, they will not be visible. let valPrettyPrint = (orgVal) => { // Save after-comma text, if present const period = orgVal.indexOf('.'); const frac = period >= 0 ? orgVal.substr(period) : ''; // Work on input as an integer let val = '' + Math.trunc(orgVal); let res = ''; while (val.length > 0) { res = val.substr(Math.max(0, val.length - 3), 3) + res; val = val.substr(0, val.length - 3); if (val.length > 0) { res = "<span class='thousandsSeparator'></span>" + res; } } // Add the saved after-period information res += frac; return res; }; // 2056776401.50 = 2,056,776,401.50 let addThousandsCommas = (n) => { // eslint-disable-line no-unused-vars n = n.toString(); while (true) { // eslint-disable-line no-constant-condition let n2 = n.replace(/(\d)(\d{3})($|,|\.)/g, '$1,$2$3'); if (n === n2) break; n = n2; } return n; }; @connect( () => ({}), {pushState: push}) export default class DataTable extends Component { static propTypes = { keys: PropTypes.oneOfType([ PropTypes.arrayOf(PropTypes.string), PropTypes.string ]).isRequired, columns: PropTypes.arrayOf(PropTypes.shape({ title: PropTypes.string.isRequired, prop: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]), render: PropTypes.func, sortable: PropTypes.bool, defaultContent: PropTypes.string, width: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]), className: PropTypes.oneOfType([ PropTypes.string, PropTypes.func ]) })).isRequired, dataArray: PropTypes.arrayOf(PropTypes.oneOfType([ PropTypes.array, PropTypes.object ])).isRequired, buttons: PropTypes.array, buildRowOptions: PropTypes.func, pushState: PropTypes.func, // the id of the div into which the table will render targetId: PropTypes.string, // clickHandler: if this is present, datatable will look for any cells with the // class .dtClickAction. If the cell is clicked, then it will pass back // data to the clickHandler as an option. The way to pass data is as an // data-<foo> attribute on the html element. It's generally a good idea to // pass a data-action-name attribute so the clickHandler can check which action // it needs to fire (because there could be multiple types of handlers). clickHandler: PropTypes.func, sortBy: PropTypes.shape({ prop: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]), order: PropTypes.oneOf(['asc', 'desc']) }) }; static defaultProps = { buildRowOptions: () => ({}), sortBy: {}, }; constructor(props) { super(props); // console.log('Table constructor', this); this._headers = []; // bind event handlers in the constructor so they are only bound once for every instance this.postRender = this.postRender.bind(this); this.getDTMarkup = this.getDTMarkup.bind(this); } componentDidMount() { this.postRender(); } shouldComponentUpdate(nextProps) { // check if dataArray changes, if so implement componentDidMount code in componentDidUpdate const isEqualDataArray = _.isEqual(this.props.dataArray, nextProps.dataArray); return !isEqualDataArray; } componentDidUpdate() { // console.log('Table componentDidUpdate', this); this.postRender(); } componentWillUnmount() { // console.log('Table componentWillUnmount', this); } getDTMarkup() { let {columns, keys, buildRowOptions, dataArray, buttons, pushState, sortBy, targetId, clickHandler, ...rest } // eslint-disable-line no-shadow = this.props; let headers = columns.map( (col, idx) => <th ref={c => this._headers[idx] = c} key={idx} style={{width: col.width}} className={col.className} role="columnheader" scope="col"> <span>{col.title}</span> </th> ); let footers = columns.map( (col, idx) => <th key={idx}> </th> ); let getKeys = Array.isArray(keys) ? keyGetter(keys) : simpleGet(keys); // console.log('dataArray', dataArray); let rows = dataArray.map( row => <tr key={getKeys(row)} {...buildRowOptions(row)}> {columns.map( (col, i) => <td key={i} className={getCellClass(col, row)}> {getCellValue(col, row)} </td> )} </tr> ); return ( <table id="dt" {...rest}> <thead> <tr> {headers} </tr> </thead> <tfoot> <tr> {footers} </tr> </tfoot> <tbody> {rows.length ? rows : <tr> <td colSpan={columns.length} className="text-center">No data</td> </tr>} </tbody> </table> ); } postRender() { const targetId = this.props.targetId || 'dtContainer'; $(`#${targetId}`).empty(); // eslint-disable-line no-shadow // console.log('post-processing', this); let {columns, sortBy, buttons, pushState, clickHandler} = this.props; // eslint-disable-line no-shadow let table = this.getDTMarkup(); let container = this.refs.tableDiv; let renderedTable = ReactDOMServer.renderToStaticMarkup(table, container); if (buttons.indexOf('colvis')) { buttons[buttons.indexOf('colvis')] = { extend: 'colvis', text: 'Columns' }; } $(`#${targetId}`).append(renderedTable); let jqueryTable = $(`#${targetId} table`); jqueryTable.DataTable({ // eslint-disable-line new-cap dom: '<"html5buttons"B>lTfgitp', buttons: buttons, 'order': [[columns.findIndex((col) => col.prop === sortBy.prop), sortBy.order]], 'pagingType': 'numbers', 'bAutoWidth': false, 'bDestroy': true, responsive: true, 'initComplete': function formatTable() { this.api().cells('.pushState').every(function __pushState() { const cell = this; const anchor = $(cell.node()).find('a'); const pusher = () => pushState(anchor.attr('href')); anchor.click(function __pushTheState(event) { event.preventDefault(); pusher(); }); }); if (clickHandler && typeof clickHandler === 'function') { this.api().cells('.dtClickAction').every(function __triggerAction() { const cell = this; const anchor = $(cell.node()).find('a'); const cellData = anchor.data(); anchor.click(function __actionOnClick(event) { event.preventDefault(); clickHandler(cellData); }); }); } } }); } render() { const targetId = this.props.targetId || 'dtContainer'; // console.log('Table render', this); return ( <div> <div ref="tableDiv" id={targetId} className="dtContainer"></div> </div> ); } } module.exports = DataTable;