cspace-ui
Version:
CollectionSpace user interface for browsers
214 lines (175 loc) • 5.71 kB
JSX
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { defineMessages, FormattedMessage } from 'react-intl';
import Immutable from 'immutable';
import get from 'lodash/get';
import SearchConditionInput from './input/SearchConditionInput';
import { ConnectedPanel } from '../../containers/layout/PanelContainer';
import {
OP_AND,
OP_OR,
OP_RANGE,
OP_GTE,
OP_LTE,
} from '../../constants/searchOperators';
const messages = defineMessages({
title: {
id: 'advancedSearchBuilder.title',
defaultMessage: 'Advanced Search',
},
});
const findAdvancedSearchClause = (searchClauses, clause) => {
let index;
index = searchClauses.findKey(candidateClause =>
candidateClause.get('path') === clause.get('path') &&
candidateClause.get('op') === clause.get('op')
);
if (typeof index === 'undefined') {
if (clause.get('op') === OP_GTE || clause.get('op') === OP_LTE) {
// A one-sided range search might have been converted to a gte/lte search.
index = searchClauses.findKey(candidateClause =>
candidateClause.get('path') === clause.get('path') &&
candidateClause.get('op') === OP_RANGE
);
}
}
return index;
};
const propTypes = {
condition: PropTypes.instanceOf(Immutable.Map),
config: PropTypes.object,
inline: PropTypes.bool,
preferredBooleanOp: PropTypes.string,
readOnly: PropTypes.bool,
recordType: PropTypes.string,
onConditionCommit: PropTypes.func,
};
const childContextTypes = {
recordType: PropTypes.string,
};
export default class AdvancedSearchBuilder extends Component {
getChildContext() {
const {
recordType,
} = this.props;
return {
recordType,
};
}
componentDidMount() {
this.normalizeCondition();
}
componentDidUpdate() {
this.normalizeCondition();
}
normalizeCondition() {
const {
condition,
config,
preferredBooleanOp,
recordType,
onConditionCommit,
} = this.props;
// FIXME: Uncomment this once conditions may be added by the end user.
// if (!condition && onConditionCommit) {
// const defaultCondition = get(config, ['recordTypes', recordType, 'advancedSearch']);
//
// if (defaultCondition) {
// onConditionCommit(Immutable.fromJS(defaultCondition));
// }
// }
// FIXME: There will be no need for this once conditions may be added by the end user.
// For now do a quick and dirty merge of the (possibly normalized) condition into the
// default, so that fields aren't unrecoverable after normalization.
if (onConditionCommit) {
const defaultCondition = Immutable.fromJS(
get(config, ['recordTypes', recordType, 'advancedSearch']));
const op = condition && condition.get('op');
if (preferredBooleanOp && (op === OP_AND || op === OP_OR) && (op !== preferredBooleanOp)) {
onConditionCommit(condition.set('op', preferredBooleanOp));
} else if (defaultCondition && condition) {
if (
op !== defaultCondition.get('op') ||
!Immutable.List.isList(condition.get('value')) ||
condition.get('value').size !== defaultCondition.get('value').size
) {
let mergedCondition = defaultCondition;
let clauses;
if (op === OP_AND || op === OP_OR) {
mergedCondition = mergedCondition.set('op', op);
clauses = condition.get('value');
} else {
if (preferredBooleanOp) {
mergedCondition = mergedCondition.set('op', preferredBooleanOp);
}
clauses = Immutable.List([condition]);
}
const targetClauses = mergedCondition.get('value');
clauses.forEach((clause) => {
const index = findAdvancedSearchClause(targetClauses, clause);
if (typeof index !== 'undefined') {
const targetClause = targetClauses.get(index);
let value = clause.get('value');
if (targetClause.get('op') === OP_RANGE && !Immutable.List.isList(value)) {
value = (clause.get('op') === OP_GTE)
? Immutable.List([value])
: Immutable.List([undefined, value]);
}
mergedCondition = mergedCondition.setIn(['value', index, 'value'], value);
}
});
if (!mergedCondition.equals(condition)) {
onConditionCommit(mergedCondition);
}
}
} else if (defaultCondition) {
onConditionCommit(
preferredBooleanOp
? defaultCondition.set('op', preferredBooleanOp)
: defaultCondition
);
}
}
}
render() {
const {
condition,
config,
inline,
readOnly,
recordType,
onConditionCommit,
} = this.props;
if (!condition) {
return null;
}
const fieldDescriptor = get(config, ['recordTypes', recordType, 'fields']);
const searchConditionInput = (
<SearchConditionInput
condition={condition}
fields={fieldDescriptor}
inline={inline}
readOnly={readOnly}
onCommit={onConditionCommit}
/>
);
if (inline) {
return searchConditionInput;
}
const panelHeader = (
<h3><FormattedMessage {...messages.title} /></h3>
);
return (
<ConnectedPanel
collapsible
header={panelHeader}
name="advancedSearch"
recordType={recordType}
>
{searchConditionInput}
</ConnectedPanel>
);
}
}
AdvancedSearchBuilder.propTypes = propTypes;
AdvancedSearchBuilder.childContextTypes = childContextTypes;