2018-02-24 18:26:56 +09:00
import { registerComponent , Components , getCollection , Utils } from 'meteor/vulcan:lib' ;
2017-08-02 16:19:15 +09:00
import React , { PureComponent } from 'react' ;
import PropTypes from 'prop-types' ;
import withCurrentUser from '../containers/withCurrentUser.js' ;
2018-09-12 14:51:44 +09:00
import withMulti from '../containers/withMulti.js' ;
2017-08-02 18:56:29 +09:00
import { FormattedMessage , intlShape } from 'meteor/vulcan:i18n' ;
2017-09-22 12:24:15 +02:00
import { getFieldValue } from './Card.jsx' ;
2018-01-25 18:20:29 +09:00
2017-08-02 16:19:15 +09:00
/ *
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 : '' ,
2018-06-12 15:24:17 +09:00
query : '' ,
currentSort : { }
2017-08-02 16:19:15 +09:00
}
}
2018-06-12 15:24:17 +09:00
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 } ) ;
}
2017-08-02 16:19:15 +09:00
updateQuery ( e ) {
e . persist ( )
e . preventDefault ( ) ;
this . setState ( {
value : e . target . value
} ) ;
delay ( ( ) => {
this . setState ( {
query : e . target . value
} ) ;
} , 700 ) ;
}
render ( ) {
2018-02-02 18:43:35 +09:00
if ( this . props . data ) { // static JSON datatable
2017-08-02 16:19:15 +09:00
2018-07-11 08:34:01 +02:00
return < Components.DatatableContents columns = { Object . keys ( this . props . data [ 0 ] ) } { ...this.props } results = { this . props . data } showEdit = { false } showNew = { false } / > ;
2018-02-02 18:43:35 +09:00
} else { // dynamic datatable with data loading
2018-02-24 18:15:42 +09:00
const collection = this . props . collection || getCollection ( this . props . collectionName ) ;
2018-02-02 18:43:35 +09:00
const options = {
2018-02-24 18:15:42 +09:00
collection ,
2018-02-02 18:43:35 +09:00
... this . props . options
}
2017-08-02 16:19:15 +09:00
2018-09-14 22:17:08 -07:00
const DatatableWithMulti = withMulti ( options ) ( Components . DatatableContents ) ;
2018-01-25 18:20:29 +09:00
2018-02-24 18:15:42 +09:00
const canInsert = collection . options && collection . options . mutations && collection . options . mutations . new && collection . options . mutations . new . check ( this . props . currentUser ) ;
2018-10-29 18:52:47 +01:00
// 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 } ;
2018-02-02 18:43:35 +09:00
return (
2018-02-24 18:15:42 +09:00
< 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 } / >
2018-10-29 18:52:47 +01:00
< DatatableWithMulti { ...this.props } collection = { collection } terms = { { query : this . state . query , orderBy : orderBy } } currentUser = { this . props . currentUser } toggleSort = { this . toggleSort } currentSort = { this . state . currentSort } / >
2018-02-02 18:43:35 +09:00
< / div >
)
}
2017-08-02 16:19:15 +09:00
}
}
Datatable . propTypes = {
2018-07-11 08:34:01 +02:00
title : PropTypes . string ,
2017-08-02 16:19:15 +09:00
collection : PropTypes . object ,
columns : PropTypes . array ,
2018-02-02 18:43:35 +09:00
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 ,
2018-03-20 14:56:09 +09:00
newFormOptions : PropTypes . object ,
editFormOptions : PropTypes . object ,
2018-03-25 10:54:45 +09:00
emptyState : PropTypes . object ,
2017-10-05 09:12:53 +09:00
}
Datatable . defaultProps = {
2018-01-20 11:02:11 +09:00
showNew : true ,
2017-10-05 09:12:53 +09:00
showEdit : true ,
showSearch : true ,
2017-08-02 16:19:15 +09:00
}
registerComponent ( 'Datatable' , Datatable , withCurrentUser ) ;
2018-01-25 18:20:29 +09:00
/ *
DatatableAbove Component
* /
2018-10-11 10:42:37 +03:00
const DatatableAbove = ( props , { intl } ) => {
2018-03-22 16:54:50 +09:00
const { collection , currentUser , showSearch , showNew , canInsert , value , updateQuery , options , newFormOptions } = props ;
2018-02-15 09:32:41 +09:00
return (
< div className = "datatable-above" >
2018-10-11 10:42:37 +03:00
{ showSearch && (
< input
className = "datatable-search form-control"
placeholder = { ` ${ intl . formatMessage ( { id : 'datatable.search' , defaultMessage : 'Search' } )}… ` }
type = "text"
name = "datatableSearchQuery"
value = { value }
onChange = { updateQuery }
/ >
) }
2018-03-22 16:54:50 +09:00
{ showNew && canInsert && < Components.NewButton collection = { collection } currentUser = { currentUser } mutationFragmentName = { options && options . fragmentName } { ...newFormOptions } / > }
2018-02-15 09:32:41 +09:00
< / div >
)
}
2018-10-11 10:42:37 +03:00
DatatableAbove . contextTypes = {
intl : intlShape ,
} ;
2018-01-25 18:20:29 +09:00
registerComponent ( 'DatatableAbove' , DatatableAbove ) ;
2017-08-02 16:19:15 +09:00
/ *
2017-08-02 17:18:49 +09:00
DatatableHeader Component
* /
2018-06-12 15:24:17 +09:00
const DatatableHeader = ( { collection , column , toggleSort , currentSort } , { intl } ) => {
2018-02-02 18:43:35 +09:00
2018-02-12 18:49:22 +09:00
const columnName = typeof column === 'string' ? column : column . label || column . name ;
2018-02-02 18:43:35 +09:00
if ( collection ) {
const schema = collection . simpleSchema ( ) . _schema ;
/ *
2017-08-02 17:18:49 +09:00
2018-02-02 18:43:35 +09:00
use either :
2017-08-02 17:18:49 +09:00
2018-02-02 18:43:35 +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-01-06 17:52:52 +01:00
2018-02-02 18:43:35 +09:00
* /
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 } ) ;
2018-06-12 15:24:17 +09:00
// 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 ? < Components.DatatableSorter name = { sortPropertyName } label = { formattedLabel } toggleSort = { toggleSort } currentSort = { currentSort } sortable = { column . sortable } / > : < th > { formattedLabel } < / th > ;
2018-01-06 17:52:52 +01:00
2018-02-02 18:43:35 +09:00
} else {
2018-02-03 07:06:56 +00:00
2018-02-02 18:43:35 +09:00
const formattedLabel = intl . formatMessage ( { id : columnName , defaultMessage : columnName } ) ;
2018-02-03 06:59:39 +00:00
return < th className = { ` datatable-th- ${ columnName . toLowerCase ( ) . replace ( /\s/g , '-' ) } ` } > { formattedLabel } < / th > ;
2018-02-02 18:43:35 +09:00
}
2017-08-02 17:18:49 +09:00
}
DatatableHeader . contextTypes = {
intl : intlShape
} ;
registerComponent ( 'DatatableHeader' , DatatableHeader ) ;
2018-06-12 15:24:17 +09:00
const SortNone = ( ) =>
< svg width = '16' height = '16' viewBox = '0 0 438 438' fill = 'none' xmlns = 'http://www.w3.org/2000/svg' >
< path d = 'M25.7368 247.243H280.263C303.149 247.243 314.592 274.958 298.444 291.116L171.18 418.456C161.128 428.515 144.872 428.515 134.926 418.456L7.55631 291.116C-8.59221 274.958 2.85078 247.243 25.7368 247.243ZM298.444 134.884L171.18 7.54408C161.128 -2.51469 144.872 -2.51469 134.926 7.54408L7.55631 134.884C-8.59221 151.042 2.85078 178.757 25.7368 178.757H280.263C303.149 178.757 314.592 151.042 298.444 134.884Z' transform = 'translate(66 6)' fill = '#000' fillOpacity = '0.2' / >
< / svg >
const SortDesc = ( ) =>
< svg width = "16" height = "16" viewBox = "0 0 438 438" fill = "none" xmlns = "http://www.w3.org/2000/svg" >
< path d = "M25.7368 0H280.263C303.149 0 314.592 27.7151 298.444 43.8734L171.18 171.213C161.128 181.272 144.872 181.272 134.926 171.213L7.55631 43.8734C-8.59221 27.7151 2.85078 0 25.7368 0Z" transform = "translate(66 253.243)" fill = "black" fillOpacity = "0.7" / >
< path d = "M171.18 7.54408L298.444 134.884C314.592 151.042 303.149 178.757 280.263 178.757H25.7368C2.85078 178.757 -8.59221 151.042 7.55631 134.884L134.926 7.54408C144.872 -2.51469 161.128 -2.51469 171.18 7.54408Z" transform = "translate(66 6)" fill = "black" fillOpacity = "0.2" / >
< / svg >
const SortAsc = ( ) =>
< svg width = "16" height = "16" viewBox = "0 0 438 438" fill = "none" xmlns = "http://www.w3.org/2000/svg" >
< path d = "M298.444 134.884L171.18 7.54408C161.128 -2.51469 144.872 -2.51469 134.926 7.54408L7.55631 134.884C-8.59221 151.042 2.85078 178.757 25.7368 178.757H280.263C303.149 178.757 314.592 151.042 298.444 134.884Z" transform = "translate(66 6)" fill = "black" fillOpacity = "0.7" / >
< path d = "M280.263 0H25.7368C2.85078 0 -8.59221 27.7151 7.55631 43.8734L134.926 171.213C144.872 181.272 161.128 181.272 171.18 171.213L298.444 43.8734C314.592 27.7151 303.149 0 280.263 0Z" transform = "translate(66 253.243)" fill = "black" fillOpacity = "0.2" / >
< / svg >
const DatatableSorter = ( { name , label , toggleSort , currentSort } ) =>
< th >
< div className = "datatable-sorter" onClick = { ( ) => { toggleSort ( name ) } } >
< span className = "datatable-sorter-label" > { label } < / span >
< span className = "sort-icon" >
{ ! currentSort [ name ] ? (
< SortNone / >
) : currentSort [ name ] === 1 ? (
< SortDesc / >
) : (
< SortAsc / >
)
}
< / span >
< / div >
< / th >
registerComponent ( 'DatatableSorter' , DatatableSorter ) ;
2017-08-02 17:18:49 +09:00
/ *
2017-08-02 16:19:15 +09:00
DatatableContents Component
* /
const DatatableContents = ( props ) => {
2018-02-15 09:32:41 +09:00
2018-07-11 08:34:01 +02:00
// 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 } = props ;
2018-01-06 17:52:52 +01:00
2017-08-02 16:19:15 +09:00
if ( loading ) {
2018-02-09 11:30:15 +09:00
return < div className = "datatable-list datatable-list-loading" > < Components.Loading / > < / div > ;
2018-06-12 15:24:17 +09:00
} else if ( ! results || ! results . length ) {
2017-11-09 10:00:56 +09:00
return emptyState || null ;
2017-08-02 16:19:15 +09:00
}
const isLoadingMore = networkStatus === 2 ;
const hasMore = totalCount > results . length ;
return (
< div className = "datatable-list" >
2018-07-11 08:34:01 +02:00
{ title && < Components.DatatableTitle title = { title } / > }
< table className = "table" >
< thead >
< tr >
{ _ . sortBy ( columns , column => column . order ) . map ( ( column , index ) => < Components.DatatableHeader key = { index } collection = { collection } column = { column } toggleSort = { toggleSort } currentSort = { currentSort } / > ) }
{ 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 } / > ) }
< / tbody >
< / table >
{ hasMore &&
< div className = "datatable-list-load-more" >
{ isLoadingMore ?
< Components.Loading / > :
< Components.Button variant = "primary" onClick = { e => { e . preventDefault ( ) ; loadMore ( ) ; } } > Load More ( { count } / { totalCount } ) < / Components.Button >
}
< / div >
}
< / div >
2017-08-02 16:19:15 +09:00
)
}
registerComponent ( 'DatatableContents' , DatatableContents ) ;
/ *
2018-07-11 08:34:01 +02:00
DatatableTitle Component
* /
const DatatableTitle = ( { title } ) =>
< div className = "datatable-title" > { title } < / div >
registerComponent ( 'DatatableTitle' , DatatableTitle ) ;
/ *
2017-08-02 17:18:49 +09:00
DatatableRow Component
2017-08-02 16:19:15 +09:00
* /
2018-02-15 09:32:41 +09:00
const DatatableRow = ( props , { intl } ) => {
2018-01-25 18:20:29 +09:00
2018-04-06 17:59:24 +09:00
const { collection , columns , document , showEdit , currentUser , options , editFormOptions , rowClass } = props ;
2018-02-02 18:43:35 +09:00
const canEdit = collection && collection . options && collection . options . mutations && collection . options . mutations . edit && collection . options . mutations . edit . check ( currentUser , document ) ;
2018-01-25 18:20:29 +09:00
2018-04-06 17:59:24 +09:00
const row = typeof rowClass === 'function' ? rowClass ( document ) : rowClass || '' ;
2018-05-16 11:40:10 +09:00
const modalProps = { title : < code > { document . _id } < / code > } ;
2018-04-06 17:59:24 +09:00
2017-08-02 16:19:15 +09:00
return (
2018-04-06 17:59:24 +09:00
< tr className = { ` datatable-item ${ row } ` } >
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 } / > ) }
2018-01-06 17:52:52 +01:00
2018-01-25 18:20:29 +09:00
{ showEdit && canEdit ?
2017-08-02 18:56:29 +09:00
< td >
2018-05-16 11:40:10 +09:00
< Components.EditButton collection = { collection } documentId = { document . _id } currentUser = { currentUser } mutationFragmentName = { options && options . fragmentName } modalProps = { modalProps } { ...editFormOptions } / >
2017-08-02 18:56:29 +09:00
< / td >
: null }
2017-08-02 16:19:15 +09:00
< / 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 } ) => {
2018-05-04 11:56:49 +09:00
const Component = column . component || column . componentName && 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 (
2018-02-03 06:39:57 +00:00
< 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 } ) =>
2017-09-22 12:24:15 +02:00
< div > { typeof column === 'string' ? getFieldValue ( document [ column ] ) : getFieldValue ( document [ column . name ] ) } < / div >
2017-08-02 16:19:15 +09:00
2017-08-02 17:18:49 +09:00
registerComponent ( 'DatatableDefaultCell' , DatatableDefaultCell ) ;