Vulcan/packages/vulcan-core/lib/modules/components/Datatable.jsx

250 lines
7.2 KiB
React
Raw Normal View History

2018-02-24 18:26:56 +09:00
import { registerComponent, Components, getCollection, Utils } from 'meteor/vulcan:lib';
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import withCurrentUser from '../containers/withCurrentUser.js';
import withList from '../containers/withList.js';
2017-08-02 18:56:29 +09:00
import { FormattedMessage, intlShape } from 'meteor/vulcan:i18n';
import Button from 'react-bootstrap/lib/Button';
import { getFieldValue } from './Card.jsx';
/*
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);
};
})();
class Datatable extends PureComponent {
constructor() {
super();
this.updateQuery = this.updateQuery.bind(this);
this.state = {
value: '',
query: ''
}
}
updateQuery(e) {
e.persist()
e.preventDefault();
this.setState({
value: e.target.value
});
delay(() => {
this.setState({
query: e.target.value
});
}, 700 );
}
render() {
if (this.props.data) { // static JSON datatable
return <Components.DatatableContents {...this.props} results={this.props.data}/>;
} else { // dynamic datatable with data loading
const collection = this.props.collection || getCollection(this.props.collectionName);
const options = {
collection,
...this.props.options
}
const DatatableWithList = withList(options)(Components.DatatableContents);
const canInsert = collection.options && collection.options.mutations && collection.options.mutations.new && collection.options.mutations.new.check(this.props.currentUser);
return (
<div className={`datatable datatable-${collection.options.collectionName}`}>
2018-02-24 18:26:56 +09:00
<Components.DatatableAbove {...this.props} collection={collection} canInsert={canInsert} value={this.state.value} updateQuery={this.updateQuery} />
<DatatableWithList {...this.props} collection={collection} terms={{query: this.state.query}} currentUser={this.props.currentUser}/>
</div>
)
}
}
}
Datatable.propTypes = {
collection: PropTypes.object,
columns: PropTypes.array,
data: PropTypes.array,
2017-08-02 18:56:29 +09:00
options: PropTypes.object,
showEdit: PropTypes.bool,
2018-01-03 15:13:50 +09:00
showNew: PropTypes.bool,
2017-10-05 09:12:53 +09:00
showSearch: PropTypes.bool,
}
Datatable.defaultProps = {
2018-01-20 11:02:11 +09:00
showNew: true,
2017-10-05 09:12:53 +09:00
showEdit: true,
showSearch: true,
}
registerComponent('Datatable', Datatable, withCurrentUser);
/*
DatatableAbove Component
*/
const DatatableAbove = (props) => {
2018-02-15 12:04:51 +09:00
const { showSearch, showNew, canInsert, value, updateQuery, options } = props;
return (
<div className="datatable-above">
{showSearch && <input className="datatable-search form-control" placeholder="Search…" type="text" name="datatableSearchQuery" value={value} onChange={updateQuery} />}
2018-02-15 12:04:51 +09:00
{showNew && canInsert && <Components.NewButton {...props} mutationFragmentName={options && options.fragmentName} />}
</div>
)
}
registerComponent('DatatableAbove', DatatableAbove);
/*
2017-08-02 17:18:49 +09:00
DatatableHeader Component
*/
const DatatableHeader = ({ collection, column }, { intl }) => {
const columnName = typeof column === 'string' ? column : column.label || column.name;
if (collection) {
const schema = collection.simpleSchema()._schema;
/*
2017-08-02 17:18:49 +09:00
use either:
2017-08-02 17:18:49 +09:00
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.
*/
2018-02-24 18:26:56 +09:00
const defaultMessage = schema[columnName] ? schema[columnName].label : Utils.camelToSpaces(columnName);
const formattedLabel = intl.formatMessage({ id: `${collection._name}.${columnName}`, defaultMessage });
return <th>{formattedLabel}</th>;
} else {
2018-02-03 07:06:56 +00:00
const formattedLabel = intl.formatMessage({ id: columnName, defaultMessage: columnName });
return <th className={`datatable-th-${columnName.toLowerCase().replace(/\s/g,'-')}`}>{formattedLabel}</th>;
}
2017-08-02 17:18:49 +09:00
}
DatatableHeader.contextTypes = {
intl: intlShape
};
registerComponent('DatatableHeader', DatatableHeader);
/*
DatatableContents Component
*/
const DatatableContents = (props) => {
2017-11-09 10:00:56 +09:00
const {collection, columns, results, loading, loadMore, count, totalCount, networkStatus, showEdit, currentUser, emptyState} = props;
if (loading) {
return <div className="datatable-list datatable-list-loading"><Components.Loading /></div>;
2017-11-09 10:00:56 +09:00
} else if (!results.length) {
return emptyState || null;
}
const isLoadingMore = networkStatus === 2;
const hasMore = totalCount > results.length;
return (
<div className="datatable-list">
2017-11-09 10:00:56 +09:00
<table className="table">
<thead>
<tr>
{_.sortBy(columns, column => column.order).map((column, index) => <Components.DatatableHeader key={index} collection={collection} column={column}/>)}
{showEdit ? <th><FormattedMessage id="datatable.edit"/></th> : null}
</tr>
</thead>
<tbody>
{results.map((document, index) => <Components.DatatableRow {...props} collection={collection} columns={columns} document={document} key={index} showEdit={showEdit} currentUser={currentUser}/>)}
2017-11-09 10:00:56 +09:00
</tbody>
</table>
{hasMore &&
<div className="datatable-list-load-more">
{isLoadingMore ?
<Components.Loading/> :
<Button bsStyle="primary" onClick={e => {e.preventDefault(); loadMore();}}>Load More ({count}/{totalCount})</Button>
}
</div>
}
</div>
)
}
registerComponent('DatatableContents', DatatableContents);
/*
2017-08-02 17:18:49 +09:00
DatatableRow Component
*/
const DatatableRow = (props, { intl }) => {
2018-02-15 12:04:51 +09:00
const { collection, columns, document, showEdit, currentUser, options } = props;
const canEdit = collection && collection.options && collection.options.mutations && collection.options.mutations.edit && collection.options.mutations.edit.check(currentUser, document);
return (
<tr className="datatable-item">
2017-08-02 18:56:29 +09:00
2017-09-29 07:40:59 +09:00
{_.sortBy(columns, column => column.order).map((column, index) => <Components.DatatableCell key={index} column={column} document={document} currentUser={currentUser} />)}
{showEdit && canEdit ?
2017-08-02 18:56:29 +09:00
<td>
2018-02-15 12:04:51 +09:00
<Components.EditButton {...props} mutationFragmentName={options && options.fragmentName}/>
2017-08-02 18:56:29 +09:00
</td>
: null}
</tr>
)
}
2017-08-02 17:18:49 +09:00
registerComponent('DatatableRow', DatatableRow);
2017-08-02 18:56:29 +09:00
DatatableRow.contextTypes = {
intl: intlShape
};
2017-08-02 17:18:49 +09:00
/*
DatatableCell Component
*/
2017-09-29 07:40:59 +09:00
const DatatableCell = ({ column, document, currentUser }) => {
2017-08-02 17:18:49 +09:00
const Component = column.component || Components[column.componentName] || Components.DatatableDefaultCell;
2017-09-04 17:40:01 +09:00
const columnName = column.name || column;
2017-08-02 17:18:49 +09:00
return (
<td className={`datatable-item-${columnName.toLowerCase().replace(/\s/g,'-')}`}><Component column={column} document={document} currentUser={currentUser} /></td>
2017-08-02 17:18:49 +09:00
)
}
registerComponent('DatatableCell', DatatableCell);
/*
DatatableDefaultCell Component
*/
const DatatableDefaultCell = ({ column, document }) =>
<div>{typeof column === 'string' ? getFieldValue(document[column]) : getFieldValue(document[column.name])}</div>
2017-08-02 17:18:49 +09:00
registerComponent('DatatableDefaultCell', DatatableDefaultCell);