admin-on-rest-fr05t1k
Version:
A frontend Framework for building admin applications on top of REST services, using ES6, React and Material UI
220 lines (203 loc) • 7.29 kB
JavaScript
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import debounce from 'lodash.debounce';
import Labeled from './Labeled';
import { crudGetOne as crudGetOneAction, crudGetMatching as crudGetMatchingAction } from '../../actions/dataActions';
import { getPossibleReferences } from '../../reducer/references/possibleValues';
const referenceSource = (resource, source) => `${resource}@${source}`;
const noFilter = () => true;
/**
* An Input component for choosing a reference record. Useful for foreign keys.
*
* This component fetches the possible values in the reference resource
* (using the `CRUD_GET_MATCHING` REST method), then delegates rendering
* to a subcomponent, to which it passes the possible choices
* as the `choices` attribute.
*
* Use it with a selector component as child, like `<AutocompleteInput>`,
* `<SelectInput>`, or `<RadioButtonGroupInput>`.
*
* @example
* export const CommentEdit = (props) => (
* <Edit {...props}>
* <SimpleForm>
* <ReferenceInput label="Post" source="post_id" reference="posts">
* <AutocompleteInput optionText="title" />
* </ReferenceInput>
* </SimpleForm>
* </Edit>
* );
*
* @example
* export const CommentEdit = (props) => (
* <Edit {...props}>
* <SimpleForm>
* <ReferenceInput label="Post" source="post_id" reference="posts">
* <SelectInput optionText="title" />
* </ReferenceInput>
* </SimpleForm>
* </Edit>
* );
*
* By default, restricts the possible values to 25. You can extend this limit
* by setting the `perPage` prop.
*
* @example
* <ReferenceInput
* source="post_id"
* reference="posts"
* perPage={100}>
* <SelectInput optionText="title" />
* </ReferenceInput>
*
* By default, orders the possible values by id desc. You can change this order
* by setting the `sort` prop (an object with `field` and `order` properties).
*
* @example
* <ReferenceInput
* source="post_id"
* reference="posts"
* sort={{ field: 'title', order: 'ASC' }}>
* <SelectInput optionText="title" />
* </ReferenceInput>
*
* Also, you can filter the query used to populate the possible values. Use the
* `filter` prop for that.
*
* @example
* <ReferenceInput
* source="post_id"
* reference="posts"
* filter={{ is_published: true }}>
* <SelectInput optionText="title" />
* </ReferenceInput>
*
* The enclosed component may filter results. ReferenceInput passes a `setFilter`
* function as prop to its child component. It uses the value to create a filter
* for the query - by default { q: [searchText] }. You can customize the mapping
* searchText => searchQuery by setting a custom `filterToQuery` function prop:
*
* @example
* <ReferenceInput
* source="post_id"
* reference="posts"
* filterToQuery={searchText => ({ title: searchText })}>
* <SelectInput optionText="title" />
* </ReferenceInput>
*/
export class ReferenceInput extends Component {
constructor(props) {
super(props);
const { perPage, sort, filter } = props;
// stored as a property rather than state because we don't want redraw of async updates
this.params = { pagination: { page: 1, perPage }, sort, filter };
this.debouncedSetFilter = debounce(this.setFilter.bind(this), 500);
}
componentDidMount() {
this.fetchReferenceAndOptions();
}
componentWillReceiveProps(nextProps) {
if (this.props.record.id !== nextProps.record.id) {
this.fetchReferenceAndOptions(nextProps);
}
}
setFilter = (filter) => {
if (filter !== this.params.filter) {
this.params.filter = this.props.filterToQuery(filter);
this.fetchReferenceAndOptions();
}
}
setPagination = (pagination) => {
if (pagination !== this.param.pagination) {
this.param.pagination = pagination;
this.fetchReferenceAndOptions();
}
}
setSort = (sort) => {
if (sort !== this.params.sort) {
this.params.sort = sort;
this.fetchReferenceAndOptions();
}
}
fetchReferenceAndOptions({ input, reference, source, resource } = this.props) {
const { pagination, sort, filter } = this.params;
const id = input.value;
if (id) {
this.props.crudGetOne(reference, id, null, false);
}
this.props.crudGetMatching(reference, referenceSource(resource, source), pagination, sort, filter);
}
render() {
const { input, resource, label, source, reference, referenceRecord, allowEmpty, matchingReferences, basePath, onChange, children, meta } = this.props;
if (!referenceRecord && !allowEmpty) {
return <Labeled
label={typeof label === 'undefined' ? `resources.${resource}.fields.${source}` : label}
source={source}
resource={resource}
/>;
}
return React.cloneElement(children, {
allowEmpty,
input,
label: typeof label === 'undefined' ? `resources.${resource}.fields.${source}` : label,
resource,
meta,
source,
choices: matchingReferences,
basePath,
onChange,
filter: noFilter, // for AutocompleteInput
setFilter: this.debouncedSetFilter,
setPagination: this.setPagination,
setSort: this.setSort,
});
}
}
ReferenceInput.propTypes = {
addField: PropTypes.bool.isRequired,
allowEmpty: PropTypes.bool.isRequired,
basePath: PropTypes.string,
children: PropTypes.element.isRequired,
crudGetMatching: PropTypes.func.isRequired,
crudGetOne: PropTypes.func.isRequired,
filter: PropTypes.object,
filterToQuery: PropTypes.func.isRequired,
input: PropTypes.object.isRequired,
label: PropTypes.string,
matchingReferences: PropTypes.array,
meta: PropTypes.object,
onChange: PropTypes.func,
perPage: PropTypes.number,
reference: PropTypes.string.isRequired,
referenceRecord: PropTypes.object,
resource: PropTypes.string.isRequired,
sort: PropTypes.shape({
field: PropTypes.string,
order: PropTypes.oneOf(['ASC', 'DESC']),
}),
source: PropTypes.string,
};
ReferenceInput.defaultProps = {
allowEmpty: false,
filter: {},
filterToQuery: searchText => ({ q: searchText }),
matchingReferences: [],
perPage: 25,
sort: { field: 'id', order: 'DESC' },
referenceRecord: null,
};
function mapStateToProps(state, props) {
const referenceId = props.input.value;
return {
referenceRecord: state.admin[props.reference].data[referenceId],
matchingReferences: getPossibleReferences(state, referenceSource(props.resource, props.source), props.reference, referenceId),
};
}
const ConnectedReferenceInput = connect(mapStateToProps, {
crudGetOne: crudGetOneAction,
crudGetMatching: crudGetMatchingAction,
})(ReferenceInput);
ConnectedReferenceInput.defaultProps = {
addField: true,
};
export default ConnectedReferenceInput;