import { registerComponent, getCollection, Utils } from 'meteor/vulcan:lib'; import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import withCurrentUser from '../containers/withCurrentUser.js'; import withComponents from '../containers/withComponents'; import withMulti from '../containers/withMulti.js'; import { FormattedMessage, intlShape } from 'meteor/vulcan:i18n'; import { getFieldValue } from './Card.jsx'; import _sortBy from 'lodash/sortBy'; /* Datatable Component */ // see: http://stackoverflow.com/questions/1909441/jquery-keyup-delay const delay = (function(){ var timer = 0; return function(callback, ms){ clearTimeout (timer); timer = setTimeout(callback, ms); }; })(); const getColumnName = column => ( typeof column === 'string' ? column : column.label || column.name ); class Datatable extends PureComponent { constructor() { super(); this.updateQuery = this.updateQuery.bind(this); this.state = { value: '', query: '', currentSort: {} }; } toggleSort = column => { let currentSort; if (!this.state.currentSort[column]) { currentSort = { [column] : 1 }; } else if (this.state.currentSort[column] === 1) { currentSort = { [column] : -1 }; } else { currentSort = {}; } this.setState({ currentSort }); } updateQuery(e) { e.persist(); e.preventDefault(); this.setState({ value: e.target.value }); delay(() => { this.setState({ query: e.target.value }); }, 700 ); } render() { const { Components } = this.props; if (this.props.data) { // static JSON datatable return ; } else { // dynamic datatable with data loading const collection = this.props.collection || getCollection(this.props.collectionName); const options = { collection, ...this.props.options }; const DatatableWithMulti = withMulti(options)(Components.DatatableContents); const canInsert = collection.options && collection.options.mutations && collection.options.mutations.new && collection.options.mutations.new.check(this.props.currentUser); // add _id to orderBy when we want to sort a column, to avoid breaking the graphql() hoc; // see https://github.com/VulcanJS/Vulcan/issues/2090#issuecomment-433860782 // this.state.currentSort !== {} is always false, even when console.log(this.state.currentSort) displays {}. So we test on the length of keys for this object. const orderBy = Object.keys(this.state.currentSort).length == 0 ? {} : { ...this.state.currentSort, _id: -1 }; return ( ); } } } Datatable.propTypes = { title: PropTypes.string, collection: PropTypes.object, columns: PropTypes.array, data: PropTypes.array, options: PropTypes.object, showEdit: PropTypes.bool, showNew: PropTypes.bool, showSearch: PropTypes.bool, newFormOptions: PropTypes.object, editFormOptions: PropTypes.object, emptyState: PropTypes.object, Components: PropTypes.object.isRequired, }; Datatable.defaultProps = { showNew: true, showEdit: true, showSearch: true, }; registerComponent({ name: 'Datatable', component: Datatable, hocs: [withCurrentUser, withComponents] }); export default Datatable; const DatatableLayout = ({ collectionName, children }) => (
{children}
); registerComponent({ name: 'DatatableLayout', component: DatatableLayout }); /* DatatableAbove Component */ const DatatableAbove = (props, { intl }) => { const { collection, currentUser, showSearch, showNew, canInsert, value, updateQuery, options, newFormOptions, Components } = props; return ( {showSearch && ( )} {showNew && canInsert && } ); }; DatatableAbove.contextTypes = { intl: intlShape, }; DatatableAbove.propTypes = { Components: PropTypes.object.isRequired }; registerComponent('DatatableAbove', DatatableAbove); const DatatableAboveSearchInput = (props) => ( ); registerComponent({ name: 'DatatableAboveSearchInput', component: DatatableAboveSearchInput }); const DatatableAboveLayout = ({ children }) => (
{children}
); registerComponent({ name: 'DatatableAboveLayout', component: DatatableAboveLayout }); /* DatatableHeader Component */ const DatatableHeader = ({ collection, column, toggleSort, currentSort, Components }, { intl }) => { const columnName = getColumnName(column); if (collection) { const schema = collection.simpleSchema()._schema; /* use either: 1. the column name translation 2. the column name label in the schema (if the column name matches a schema field) 3. the raw column name. */ const defaultMessage = schema[columnName] ? schema[columnName].label : Utils.camelToSpaces(columnName); const formattedLabel = intl.formatMessage({ id: `${collection._name}.${columnName}`, defaultMessage }); // if sortable is a string, use it as the name of the property to sort by. If it's just `true`, use column.name const sortPropertyName = typeof column.sortable === 'string' ? column.sortable : column.name; return column.sortable ? : {formattedLabel}; } else { const formattedLabel = intl.formatMessage({ id: columnName, defaultMessage: columnName }); return ( {formattedLabel} ); } }; DatatableHeader.contextTypes = { intl: intlShape }; DatatableHeader.propTypes = { Components: PropTypes.object.isRequired }; registerComponent('DatatableHeader', DatatableHeader); const DatatableHeaderCellLayout = ({ children, ...otherProps }) => ( {children} ); registerComponent({ name: 'DatatableHeaderCellLayout', component: DatatableHeaderCellLayout }); const SortNone = () => ; const SortDesc = () => ; const SortAsc = () => ; const DatatableSorter = ({ name, label, toggleSort, currentSort }) =>
{toggleSort(name);}}> {label} {!currentSort[name] ? ( ) : currentSort[name] === 1 ? ( ) : ( ) }
; registerComponent('DatatableSorter', DatatableSorter); /* DatatableContents Component */ const DatatableContents = (props) => { // if no columns are provided, default to using keys of first array item const { title, collection, results, columns, loading, loadMore, count, totalCount, networkStatus, showEdit, currentUser, emptyState, toggleSort, currentSort, Components } = props; if (loading) { return
; } else if (!results || !results.length) { return emptyState || null; } const isLoadingMore = networkStatus === 2; const hasMore = totalCount > results.length; const sortedColumns = _sortBy(columns, column => column.order); return ( {title && } { sortedColumns .map((column, index) => ( ) ) } {showEdit ? : null} {results.map((document, index) => )} {hasMore && {isLoadingMore ? : ( { e.preventDefault(); loadMore(); }}> Load More ({count}/{totalCount}) ) } } ); }; DatatableContents.propTypes = { Components: PropTypes.object.isRequired }; registerComponent('DatatableContents', DatatableContents); const DatatableContentsLayout = ({ children }) => (
{children}
); registerComponent({ name: 'DatatableContentsLayout', component: DatatableContentsLayout }); const DatatableContentsInnerLayout = ({ children }) => ( {children}
); registerComponent({ name: 'DatatableContentsInnerLayout', component: DatatableContentsInnerLayout }); const DatatableContentsHeadLayout = ({ children }) => ( {children} ); registerComponent({ name: 'DatatableContentsHeadLayout', component: DatatableContentsHeadLayout }); const DatatableContentsBodyLayout = ({ children }) => ( {children} ); registerComponent({ name: 'DatatableContentsBodyLayout', component: DatatableContentsBodyLayout }); const DatatableContentsMoreLayout = ({ children }) => (
{children}
); registerComponent({ name: 'DatatableContentsMoreLayout', component: DatatableContentsMoreLayout }); const DatatableLoadMoreButton = ({ count, totalCount, Components, children, ...otherProps }) => ( {children} ); registerComponent({ name: 'DatatableLoadMoreButton', component: DatatableLoadMoreButton }); /* DatatableTitle Component */ const DatatableTitle = ({ title }) =>
{title}
; registerComponent('DatatableTitle', DatatableTitle); /* DatatableRow Component */ const DatatableRow = (props, { intl }) => { const { collection, columns, document, showEdit, currentUser, options, editFormOptions, rowClass, Components } = props; const canEdit = collection && collection.options && collection.options.mutations && collection.options.mutations.edit && collection.options.mutations.edit.check(currentUser, document); const row = typeof rowClass === 'function' ? rowClass(document) : rowClass || ''; const modalProps = { title: {document._id} }; const sortedColumns = _sortBy(columns, column => column.order); return ( { sortedColumns .map((column, index) => ( ))} {showEdit && canEdit ? : null} ); }; DatatableRow.propTypes = { Components: PropTypes.object.isRequired }; registerComponent('DatatableRow', DatatableRow); DatatableRow.contextTypes = { intl: intlShape }; const DatatableRowLayout = ({ children, ...otherProps }) => ( {children} ); registerComponent({ name: 'DatatableRowLayout', component: DatatableRowLayout }); /* DatatableCell Component */ const DatatableCell = ({ column, document, currentUser, Components }) => { const Component = column.component || column.componentName && Components[column.componentName] || Components.DatatableDefaultCell; const columnName = getColumnName(column); return ( ); }; DatatableCell.propTypes = { Components: PropTypes.object.isRequired }; registerComponent('DatatableCell', DatatableCell); const DatatableCellLayout = ({ children, ...otherProps }) => ( {children} ); registerComponent({ name: 'DatatableCellLayout', component: DatatableCellLayout }); /* DatatableDefaultCell Component */ const DatatableDefaultCell = ({ column, document }) =>
{typeof column === 'string' ? getFieldValue(document[column]) : getFieldValue(document[column.name])}
; registerComponent('DatatableDefaultCell', DatatableDefaultCell);