react-datable
Version:
react-datable is a lightweight, fast and extendable datagrid built for React
606 lines (540 loc) • 20.8 kB
JavaScript
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
import React from "react";
import ReactDOM from "react-dom";
import "./datatable.css";
import Pagination from "../Pagination";
import PropTypes from "prop-types";
import Utils, { isEmpty } from "../utils";
import cx from "classnames";
const isEqual = require("react-fast-compare");
export default class DataTable extends React.Component {
constructor(props) {
super(props);
_defineProperty(this, "_preSearchData", null);
_defineProperty(this, "renderTableHeaderTitle", (title, cleanTitle, index, sortIcon = null, style) => {
if (typeof title === "object") {
const icon = React.createElement("span", {
"data-col": cleanTitle,
"data-index": index,
key: "icon",
style: {
cursor: "pointer"
}
}, sortIcon);
const childrenWithProps = React.Children.map(title.props.children, (child, i) => {
if (typeof child === "object") {
return React.cloneElement(child, {
"data-col": cleanTitle,
"data-index": index
});
} else {
return child;
}
});
const titleElement = React.cloneElement(title, {
"data-col": cleanTitle,
"data-index": index,
className: "header-cell",
key: "title-custom" + index,
style
}, [childrenWithProps]);
return React.cloneElement( /*#__PURE__*/React.createElement("div", {
style: {
display: "flex"
}
}), {
"data-col": cleanTitle,
"data-index": index,
className: "header-cell"
}, [titleElement, icon]);
} else {
if (sortIcon) {
return /*#__PURE__*/React.createElement("span", {
draggable: true,
"data-col": cleanTitle,
"data-index": index,
className: "header-cell",
style: style
}, title, " ", sortIcon);
} else {
return /*#__PURE__*/React.createElement("span", {
draggable: true,
"data-col": cleanTitle,
"data-index": index,
className: "header-cell",
style: style
}, title);
}
}
});
_defineProperty(this, "renderTableHeader", () => {
let {
headers
} = this.state;
headers.sort((a, b) => {
if (a.index > b.index) return 1;
return -1;
});
let sortIcon = null;
let headerView = headers.map((header, index) => {
let title = header.title;
let cleanTitle = typeof header.accessor === "function" ? typeof header.title === "object" ? Utils.onlyText(header.title.props.children) : header.title : header.accessor;
let width = header.width;
if (this.state.sortby === index) {
// title += this.state.descending ? "\u2193" : "\u2191";
sortIcon = this.state.descending ? " ▾" : " ▴";
} else {
sortIcon = null;
}
return /*#__PURE__*/React.createElement("th", {
key: cleanTitle + index,
ref: th => this[cleanTitle] = th,
style: {
width: width + "px"
},
"data-col": cleanTitle,
"data-index": index
}, this.renderTableHeaderTitle(title, cleanTitle, index, sortIcon, header.style));
});
return headerView;
});
_defineProperty(this, "renderNoData", () => {
return /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("td", {
colSpan: this.props.headers.length
}, this.noData));
});
_defineProperty(this, "onUpdate", e => {
e.preventDefault();
let input = e.target.firstChild;
let header = this.state.headers[this.state.edit.cell];
let rowId = this.state.edit.rowId;
this.setState({
edit: null
});
this.props.onUpdate && this.props.onUpdate(header.accessor, Number(rowId), input.value);
});
_defineProperty(this, "onFormReset", e => {
if (e.keyCode === 27) {
// ESC key
this.setState({
edit: null
});
}
});
_defineProperty(this, "renderContent", () => {
let {
headers
} = this.state;
let data = this.state.data;
let contentView = data.map((row, rowIdx) => {
let id = row[this.keyField];
let edit = this.state.edit;
let tds = headers.map((header, index) => {
let content = "";
if (typeof header.accessor === "function") {
content = header.accessor(row);
} else {
if (header.accessor.includes(".")) {
let splitedcolAccessor = header.accessor.split(".");
splitedcolAccessor.forEach((title, index) => {
if (index === 0) {
content = row[title];
} else {
content = content[title];
}
});
} else {
content = row[header.accessor];
}
}
let cell = header.cell;
if (cell) {
if (typeof cell === "object") {
if (cell.type === "image" && content) {
content = /*#__PURE__*/React.createElement("img", {
style: cell.style,
src: content,
alt: ""
});
}
} else if (typeof cell === "function") {
content = cell(row, index);
}
}
if (this.props.edit) {
if (header.dataType && (header.dataType === "number" || header.dataType === "string") && header.accessor !== this.keyField) {
if (edit && edit.row === rowIdx && edit.cell === index) {
content = /*#__PURE__*/React.createElement("form", {
onSubmit: this.onUpdate
}, /*#__PURE__*/React.createElement("input", {
type: "text",
defaultValue: content,
onKeyUp: this.onFormReset
}));
}
}
}
return /*#__PURE__*/React.createElement("td", {
key: index,
"data-id": id,
"data-row": rowIdx
}, content);
});
return /*#__PURE__*/React.createElement("tr", {
key: rowIdx
}, tds);
});
return contentView;
});
_defineProperty(this, "onSort", e => {
let data = this.state.data.slice(); // Give new array
let colIndex = Number(ReactDOM.findDOMNode(e.target).dataset.index);
let colAccessor = this.state.headers[colIndex].accessor;
let sortable = this.state.headers[colIndex].sortable;
if (!sortable) {
return;
}
let descending = !this.state.descending;
data.sort((a, b) => {
let sortVal = 0;
let aValue = "",
bValue = "";
if (typeof colAccessor === "function") {
aValue = colAccessor(a);
bValue = colAccessor(b);
if (!isEmpty(aValue) && typeof aValue === "object") {
aValue = Utils.onlyText(aValue.props.children);
bValue = Utils.onlyText(bValue.props.children);
}
} else {
if (colAccessor.includes(".")) {
let splitedcolAccessor = colAccessor.split(".");
splitedcolAccessor.forEach((title, index) => {
if (index === 0) {
aValue = a[title];
bValue = b[title];
} else {
aValue = aValue[title];
bValue = bValue[title];
}
});
} else {
aValue = a[colAccessor];
bValue = b[colAccessor];
}
}
aValue = typeof aValue === "string" ? aValue.toLowerCase() : aValue;
bValue = typeof bValue === "string" ? bValue.toLowerCase() : bValue;
if (aValue < bValue) {
sortVal = -1;
} else if (aValue > bValue) {
sortVal = 1;
}
if (descending) {
sortVal = sortVal * -1;
}
return sortVal;
});
this.setState({
data,
sortby: colIndex,
Startsorting: true,
descending
});
});
_defineProperty(this, "onSearch", e => {
let {
headers
} = this.state; // Grab the index of the target column
let idx = e.target.dataset.idx;
let fieldName = "",
fieldValue = "",
inputId = ""; // Get the target column
fieldName = headers[idx].accessor;
let data = this._preSearchData; // Filter the records
let searchData = this.state._preSearchData.filter(row => {
if (typeof fieldName === "function") {
fieldValue = fieldName(row);
if (!isEmpty(fieldValue) && typeof fieldValue === "object") {
fieldValue = Utils.onlyText(fieldValue.props.children);
}
if (typeof headers[idx].title === "object") {
inputId = "inp" + Utils.onlyText(headers[idx].title.props.children).replace(/\s/g, "");
} else {
inputId = "inp" + headers[idx].title.replace(/\s/g, "");
}
} else {
if (fieldName.includes(".")) {
let splitedcolAccessor = fieldName.split(".");
splitedcolAccessor.forEach((title, index) => {
if (index === 0) {
fieldValue = row[title];
} else {
fieldValue = fieldValue[title];
}
});
} else {
fieldValue = row[fieldName];
}
inputId = "inp" + fieldName;
}
let input = this[inputId];
if (isEmpty(input.value)) {
return true;
}
if (!isEmpty(fieldValue) || isEmpty(fieldValue) && fieldValue == 0) {
return fieldValue.toString().toLowerCase().indexOf(input.value.toString().toLowerCase()) !== -1;
}
}); //UPdate the state
this.setState({
data: searchData,
pagedData: searchData,
search: this.props.searchable,
totalItemsCount: searchData.length
}, () => {
if (this.pagination.enabled) {//this.onGotoPage(1);
}
});
});
_defineProperty(this, "renderSearch", () => {
let {
search,
headers
} = this.state;
let {
searchable
} = this.props;
if (!searchable) {
return /*#__PURE__*/React.createElement("td", {
key: "emptytr1"
});
}
let searchInputs = headers.map((header, idx) => {
if (!header.searchable) {
return /*#__PURE__*/React.createElement("td", {
key: `emptytr${idx}`
});
} // Get the header ref.
let hdr = this[header.accessor];
let inputId = "";
if (typeof header.accessor === "function") {
if (typeof header.title === "object") {
inputId = "inp" + Utils.onlyText(header.title.props.children).replace(/\s/g, "");
} else {
inputId = "inp" + header.title.replace(/\s/g, "");
}
} else {
inputId = "inp" + header.accessor;
}
return /*#__PURE__*/React.createElement("td", {
key: idx
}, /*#__PURE__*/React.createElement("input", {
type: "text",
ref: input => this[inputId] = input,
style: {
//width: hdr && hdr.clientWidth - 17 + "px",
width: header.width - 17 + "px"
},
"data-idx": idx
}));
});
return /*#__PURE__*/React.createElement("tr", {
onChange: this.onSearch
}, searchInputs);
});
_defineProperty(this, "onShowEditor", e => {
let id = e.target.dataset.id;
this.setState({
edit: {
row: parseInt(e.target.dataset.row, 10),
rowId: id,
cell: e.target.cellIndex
}
});
});
_defineProperty(this, "renderTable", () => {
let title = this.props.title || "DataTable";
let headerView = this.renderTableHeader();
let contentView = this.state.data && this.state.data.length > 0 ? this.renderContent() : this.renderNoData();
return /*#__PURE__*/React.createElement("table", {
className: "data-inner-table"
}, /*#__PURE__*/React.createElement("thead", {
onClick: this.onSort
}, /*#__PURE__*/React.createElement("tr", null, headerView)), /*#__PURE__*/React.createElement("tbody", null, this.renderSearch(), contentView));
});
_defineProperty(this, "onitemsCountPerPageChange", itemsCountPerPage => {
this.setState({
itemsCountPerPage: parseInt(itemsCountPerPage, 10)
}, () => {
this.onGotoPage(this.state.currentPage);
});
});
_defineProperty(this, "onGotoPage", pageNo => {
this.setState({
currentPage: pageNo,
search: false,
Startsorting: false,
sortby: null
}, () => this.props.pagination.onPageChange(pageNo));
});
this.state = {
headers: props.headers,
data: props.data,
pagedData: props.data,
_preSearchData: props.data,
sortby: null,
Startsorting: false,
descending: null,
search: false,
itemsCountPerPage: props.pagination.itemsCountPerPage,
currentPage: props.pagination.currentPage
};
this.keyField = props.keyField || "id"; // TODO: revisit this logic
this.noData = props.noData || "No records found!";
this.width = props.width || "100%"; // Add pagination support
this.pagination = !isEmpty(this.props.pagination) && this.props.pagination || DataTable.defaultProps.pagination; //{ enabled: true };
}
/**
* Render Table Header Title
*/
render() {
return /*#__PURE__*/React.createElement("div", {
className: cx(this.props.className, "data-table")
}, this.pagination.enabled && this.pagination.position.join(", ").includes("top") && /*#__PURE__*/React.createElement(Pagination, {
activePage: this.state.currentPage,
itemsCountPerPage: this.props.pagination.itemsCountPerPage,
totalItemsCount: this.props.totalItemsCount,
onChange: this.onGotoPage.bind(this),
pageRangeDisplayed: this.props.pagination.pageRangeDisplayed,
position: this.pagination.position[0].includes("top") ? this.pagination.position[0] : this.pagination.position[1],
innerClass: cx(this.props.pagination.innerClass, "pagination"),
activeClass: this.props.pagination.activeClass ? this.props.pagination.activeClass : "active",
disabledClass: cx(this.props.pagination.innerClass, "disabled"),
itemClass: this.props.pagination.itemClass,
itemClassFirst: this.props.pagination.itemClassFirst,
itemClassPrev: this.props.pagination.itemClassPrev,
itemClassNext: this.props.pagination.itemClassNext,
itemClassLast: this.props.pagination.itemClassLast,
activeLinkClass: this.props.pagination.activeLinkClass,
disabledClass: this.props.pagination.disabledClass,
prevPageText: this.props.pagination.prevPageText,
firstPageText: this.pagination.firstPageText,
lastPageText: this.props.pagination.lastPageText,
nextPageText: this.props.pagination.nextPageText,
getPageUrl: this.props.pagination.getPageUrl,
activeLinkClass: this.props.pagination.activeLinkClass,
hideDisabled: this.props.pagination.hideDisabled,
hideNavigation: this.props.pagination.hideNavigation,
hideFirstLastPages: this.props.pagination.hideFirstLastPages,
linkClass: this.props.pagination.linkClass,
linkClassFirst: this.props.pagination.linkClassFirst,
linkClassPrev: this.props.pagination.linkClassPrev,
linkClassNext: this.props.pagination.linkClassNext,
linkClassLast: this.props.pagination.linkClassLast
}), this.renderTable(), this.pagination.enabled && this.pagination.position.join(", ").includes("bottom") && /*#__PURE__*/React.createElement(Pagination, {
activePage: this.state.currentPage,
itemsCountPerPage: this.props.pagination.itemsCountPerPage,
totalItemsCount: this.props.totalItemsCount,
onChange: this.onGotoPage.bind(this),
pageRangeDisplayed: this.props.pagination.pageRangeDisplayed,
position: this.pagination.position[0].includes("bottom") ? this.pagination.position[0] : this.pagination.position[1],
innerClass: cx(this.props.pagination.innerClass, "pagination"),
activeClass: this.props.pagination.activeClass ? this.props.pagination.activeClass : "active",
disabledClass: cx(this.props.pagination.innerClass, "disabled"),
itemClass: this.props.pagination.itemClass,
itemClassFirst: this.props.pagination.itemClassFirst,
itemClassPrev: this.props.pagination.itemClassPrev,
itemClassNext: this.props.pagination.itemClassNext,
itemClassLast: this.props.pagination.itemClassLast,
activeLinkClass: this.props.pagination.activeLinkClass,
disabledClass: this.props.pagination.disabledClass,
prevPageText: this.props.pagination.prevPageText,
firstPageText: this.pagination.firstPageText,
lastPageText: this.props.pagination.lastPageText,
nextPageText: this.props.pagination.nextPageText,
getPageUrl: this.props.pagination.getPageUrl,
activeLinkClass: this.props.pagination.activeLinkClass,
hideDisabled: this.props.pagination.hideDisabled,
hideNavigation: this.props.pagination.hideNavigation,
hideFirstLastPages: this.props.pagination.hideFirstLastPages,
linkClass: this.props.pagination.linkClass,
linkClassFirst: this.props.pagination.linkClassFirst,
linkClassPrev: this.props.pagination.linkClassPrev,
linkClassNext: this.props.pagination.linkClassNext,
linkClassLast: this.props.pagination.linkClassLast
}));
}
}
_defineProperty(DataTable, "defaultProps", {
searchable: true,
pagination: {
enabled: true,
position: ["bottom left", "top left"],
currentPage: 1,
itemsCountPerPage: 10,
pageRangeDisplayed: 5
}
});
_defineProperty(DataTable, "getDerivedStateFromProps", (nextProps, prevState) => {
// eslint-disable-next-line
if (!isEqual(nextProps.data, prevState.data) && !prevState.Startsorting && !prevState.search) {
return {
headers: nextProps.headers,
data: nextProps.data,
sortby: prevState.sortby,
descending: prevState.descending,
//search: prevState.search,
pagedData: nextProps.data,
_preSearchData: nextProps.data
};
}
return {
Startsorting: false
};
});
DataTable.propTypes = {
edit: PropTypes.bool,
width: PropTypes.string,
headers: PropTypes.arrayOf(PropTypes.object).isRequired,
data: PropTypes.array.isRequired,
noData: PropTypes.string,
onUpdate: PropTypes.func,
totalItemsCount: PropTypes.number.isRequired,
searchable: PropTypes.bool,
pagination: PropTypes.shape({
enabled: PropTypes.bool,
currentPage: PropTypes.number.isRequired,
itemsCountPerPage: function (props, propName, componentName) {
// eslint-disable-next-line
if ( // eslint-disable-next-line
props["enabled"] == true && ( // eslint-disable-next-line
props[propName] == undefined || typeof props[propName] != "number")) {
const error = console.error;
console.error = function (warning, ...args) {
if (/Please provide itemsCountPerPage paginate Proprty/.test(warning)) {
throw new Error(warning);
}
error.apply(console, [warning, ...args]);
};
throw new Error("Please provide itemsCountPerPage paginate Proprty");
}
},
position: function (props, propName, componentName) {
// eslint-disable-next-line
if ( // eslint-disable-next-line
props["enabled"] == true && ( // eslint-disable-next-line
props[propName] == undefined || !Array.isArray(props[propName]))) {
const error = console.error;
console.error = function (warning, ...args) {
if (/Please provide paginate position Property with type array/.test(warning)) {
throw new Error(warning);
}
error.apply(console, [warning, ...args]);
};
throw new Error("Please provide paginate position Property with type array");
}
},
pageRangeDisplayed: PropTypes.number,
onPageChange: PropTypes.func.isRequired
})
};