UNPKG

admin-on-rest-fr05t1k

Version:

A frontend Framework for building admin applications on top of REST services, using ES6, React and Material UI

310 lines (284 loc) 11.3 kB
import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { push as pushAction } from 'react-router-redux'; import { Card } from 'material-ui/Card'; import compose from 'recompose/compose'; import inflection from 'inflection'; import { change as changeFormValueAction, getFormValues } from 'redux-form'; import debounce from 'lodash.debounce'; import queryReducer, { SET_SORT, SET_PAGE, SET_FILTER, SORT_DESC } from '../../reducer/resource/list/queryReducer'; import ViewTitle from '../layout/ViewTitle'; import Title from '../layout/Title'; import DefaultPagination from './Pagination'; import DefaultActions from './Actions'; import { crudGetList as crudGetListAction } from '../../actions/dataActions'; import { changeListParams as changeListParamsAction } from '../../actions/listActions'; import translate from '../../i18n/translate'; const filterFormName = 'filterForm'; /** * List page component * * The <List> component renders the list layout (title, buttons, filters, pagination), * and fetches the list of records from the REST API. * It then delegates the rendering of the list of records to its child component. * Usually, it's a <Datagrid>, responsible for displaying a table with one row for each post. * * In Redux terms, <List> is a connected component, and <Datagrid> is a dumb component. * * Props: * - title * - perPage * - sort * - filter (the permanent filter to apply to the query) * - actions * - filters (a React Element used to display the filter form) * - pagination * * @example * const PostFilter = (props) => ( * <Filter {...props}> * <TextInput label="Search" source="q" alwaysOn /> * <TextInput label="Title" source="title" /> * </Filter> * ); * export const PostList = (props) => ( * <List {...props} * title="List of posts" * sort={{ field: 'published_at' }} * filter={{ is_published: true }} * filters={<PostFilter />} * > * <Datagrid> * <TextField source="id" /> * <TextField source="title" /> * <EditButton /> * </Datagrid> * </List> * ); */ export class List extends Component { constructor(props) { super(props); this.debouncedSetFilters = debounce(this.setFilters.bind(this), 500); this.state = { key: 0 }; } componentDidMount() { this.updateData(); if (Object.keys(this.props.query).length > 0) { this.props.changeListParams(this.props.resource, this.props.query); } } componentWillReceiveProps(nextProps) { if (nextProps.resource !== this.props.resource || nextProps.query.sort !== this.props.query.sort || nextProps.query.order !== this.props.query.order || nextProps.query.page !== this.props.query.page || nextProps.query.filter !== this.props.query.filter) { this.updateData(Object.keys(nextProps.query).length > 0 ? nextProps.query : nextProps.params); } if (nextProps.data !== this.props.data && this.fullRefresh) { this.fullRefresh = false; this.setState({ key: this.state.key + 1 }); } if (Object.keys(nextProps.filterValues).length === 0 && Object.keys(this.props.filterValues).length === 0) { return; } if (nextProps.filterValues !== this.props.filterValues) { const nextFilters = nextProps.filterValues; Object.keys(nextFilters).forEach(filterName => { if (nextFilters[filterName] === '') { // remove empty filter from query delete nextFilters[filterName]; } }); this.debouncedSetFilters(nextFilters); } } shouldComponentUpdate(nextProps, nextState) { if ( nextProps.isLoading === this.props.isLoading && nextProps.width === this.props.width && nextState === this.state) { return false; } return true; } componentWillUnmount() { this.debouncedSetFilters.cancel(); } getBasePath() { return this.props.location.pathname; } refresh = (event) => { event.stopPropagation(); this.fullRefresh = true; this.updateData(); } /** * Merge list params from 3 different sources: * - the query string * - the params stored in the state (from previous navigation) * - the props passed to the List component */ getQuery() { const query = Object.keys(this.props.query).length > 0 ? this.props.query : { ...this.props.params }; if (!query.sort) { query.sort = this.props.sort.field; query.order = this.props.sort.order; } if (!query.perPage) { query.perPage = this.props.perPage; } return query; } updateData(query) { const params = query || this.getQuery(); const { sort, order, page, perPage, filter } = params; const permanentFilter = this.props.filter; this.props.crudGetList(this.props.resource, { page, perPage }, { field: sort, order }, { ...filter, ...permanentFilter }); } setSort = sort => this.changeParams({ type: SET_SORT, payload: sort }); setPage = page => this.changeParams({ type: SET_PAGE, payload: page }); setFilters = filters => this.changeParams({ type: SET_FILTER, payload: filters }); showFilter = (filterName, defaultValue) => { this.setState({ [filterName]: true }); if (typeof defaultValue !== 'undefined') { this.props.changeFormValue(filterFormName, filterName, defaultValue); this.setFilters({ ...this.props.filterValues, [filterName]: defaultValue }); } } hideFilter = (filterName) => { this.setState({ [filterName]: false }); this.props.changeFormValue(filterFormName, filterName, ''); this.setFilters({ ...this.props.filterValues, [filterName]: undefined }); } changeParams(action) { const newParams = queryReducer(this.getQuery(), action); this.props.push({ ...this.props.location, query: { ...newParams, filter: JSON.stringify(newParams.filter) } }); this.props.changeListParams(this.props.resource, newParams); } render() { const { filters, pagination = <DefaultPagination />, actions = <DefaultActions />, resource, hasCreate, title, data, ids, total, children, isLoading, translate } = this.props; const { key } = this.state; const query = this.getQuery(); const filterValues = query.filter; const basePath = this.getBasePath(); const resourceName = translate(`resources.${resource}.name`, { smart_count: 2, _: inflection.humanize(inflection.pluralize(resource)), }); const defaultTitle = translate('aor.page.list', { name: `${resourceName}` }); const titleElement = <Title title={title} defaultTitle={defaultTitle} />; return ( <div> <Card style={{ opacity: isLoading ? 0.8 : 1 }} key={key}> {actions && React.cloneElement(actions, { resource, filters, filterValues, basePath, hasCreate, displayedFilters: this.state, showFilter: this.showFilter, refresh: this.refresh, })} <ViewTitle title={titleElement} /> {filters && React.cloneElement(filters, { resource, hideFilter: this.hideFilter, filterValues, displayedFilters: this.state, context: 'form', })} {React.cloneElement(children, { resource, ids, data, currentSort: { field: query.sort, order: query.order }, basePath, isLoading, setSort: this.setSort, })} {pagination && React.cloneElement(pagination, { total, page: parseInt(query.page, 10), perPage: parseInt(query.perPage, 10), setPage: this.setPage, })} </Card> </div> ); } } List.propTypes = { // the props you can change title: PropTypes.any, filter: PropTypes.object, filters: PropTypes.element, pagination: PropTypes.element, actions: PropTypes.element, perPage: PropTypes.number.isRequired, sort: PropTypes.shape({ field: PropTypes.string, order: PropTypes.string, }), children: PropTypes.element.isRequired, // the props managed by admin-on-rest changeFormValue: PropTypes.func.isRequired, changeListParams: PropTypes.func.isRequired, crudGetList: PropTypes.func.isRequired, data: PropTypes.object, // eslint-disable-line react/forbid-prop-types filterValues: PropTypes.object, // eslint-disable-line react/forbid-prop-types hasCreate: PropTypes.bool.isRequired, hasEdit: PropTypes.bool.isRequired, ids: PropTypes.array, isLoading: PropTypes.bool.isRequired, location: PropTypes.object.isRequired, path: PropTypes.string, params: PropTypes.object.isRequired, push: PropTypes.func.isRequired, query: PropTypes.object.isRequired, resource: PropTypes.string.isRequired, total: PropTypes.number.isRequired, translate: PropTypes.func.isRequired, }; List.defaultProps = { filter: {}, filterValues: {}, perPage: 10, sort: { field: 'id', order: SORT_DESC, }, }; function mapStateToProps(state, props) { const resourceState = state.admin[props.resource]; const query = props.location.query; if (query.filter && typeof query.filter === 'string') { // if the List has no filter component, the filter is always "{}" // avoid deserialization and keep identity by using a constant query.filter = props.filters ? JSON.parse(query.filter) : resourceState.list.params.filter; } return { query, params: resourceState.list.params, ids: resourceState.list.ids, total: resourceState.list.total, data: resourceState.data, isLoading: state.admin.loading > 0, filterValues: props.filters ? getFormValues(filterFormName)(state) : resourceState.list.params.filter, }; } const enhance = compose( connect( mapStateToProps, { crudGetList: crudGetListAction, changeFormValue: changeFormValueAction, changeListParams: changeListParamsAction, push: pushAction, }, ), translate, ); export default enhance(List);