Skip to content

Commit

Permalink
geosolutions-it#9706: handle the advanced filter in attribute table w…
Browse files Browse the repository at this point in the history
…ith different operators
  • Loading branch information
mahmoudadel54 committed Nov 22, 2023
1 parent ea70a86 commit 7f3c2e7
Show file tree
Hide file tree
Showing 16 changed files with 790 additions and 35 deletions.
8 changes: 8 additions & 0 deletions web/client/actions/featuregrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const SET_FEATURES = 'SET_FEATURES';
export const SORT_BY = 'FEATUREGRID:SORT_BY';
export const SET_LAYER = 'FEATUREGRID:SET_LAYER';
export const UPDATE_FILTER = 'QUERY:UPDATE_FILTER';
export const UPDATE_OPERATOR_QUICK_FILTER = 'UPDATE_OPERATOR_QUICK_FILTER';
export const CHANGE_PAGE = 'FEATUREGRID:CHANGE_PAGE';
export const GEOMETRY_CHANGED = 'FEATUREGRID:GEOMETRY_CHANGED';
export const DOCK_SIZE_FEATURES = 'DOCK_SIZE_FEATURES';
Expand Down Expand Up @@ -234,6 +235,13 @@ export function updateFilter(update, append = false) {
append
};
}
export function updateOperatorQuickFilter(operator, attribute) {
return {
type: UPDATE_FILTER,
operator,
attribute
};
}
export function toggleTool(tool, value) {
return {
type: TOGGLE_TOOL,
Expand Down
6 changes: 4 additions & 2 deletions web/client/components/data/featuregrid/enhancers/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ const featuresToGrid = compose(
focusOnEdit: false,
editors,
dataStreamFactory,
virtualScroll: true
virtualScroll: true,
isShownOperators: false
}),
withPropsOnChange("showDragHandle", ({showDragHandle = true} = {}) => ({
className: showDragHandle ? 'feature-grid-drag-handle-show' : 'feature-grid-drag-handle-hide'
Expand Down Expand Up @@ -170,7 +171,8 @@ const featuresToGrid = compose(
return props.editors(desc.localType, generalProps);
},
getFilterRenderer: getFilterRendererFunc,
getFormatter: (desc) => getFormatter(desc, (props.fields ?? []).find(f => f.name === desc.name), {dateFormats: props.dateFormats})
getFormatter: (desc) => getFormatter(desc, (props.fields ?? []).find(f => f.name === desc.name), {dateFormats: props.dateFormats}),
isShownOperators: props.isShownOperators
}))
});
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import PropTypes from 'prop-types';
import { getMessageById } from '../../../../utils/LocaleUtils';
import { Tooltip } from 'react-bootstrap';
import OverlayTrigger from '../../../misc/OverlayTrigger';
import ComboField from '../../query/ComboField';

class AttributeFilter extends React.PureComponent {
static propTypes = {
Expand All @@ -21,7 +22,10 @@ class AttributeFilter extends React.PureComponent {
value: PropTypes.any,
column: PropTypes.object,
placeholderMsgId: PropTypes.string,
tooltipMsgId: PropTypes.string
tooltipMsgId: PropTypes.string,
operator: PropTypes.string,
type: PropTypes.string,
isShownOperators: PropTypes.bool
};

static contextTypes = {
Expand All @@ -33,15 +37,62 @@ class AttributeFilter extends React.PureComponent {
valid: true,
onChange: () => {},
column: {},
placeholderMsgId: "featuregrid.filter.placeholders.default"
placeholderMsgId: "featuregrid.filter.placeholders.default",
operator: "=",
isShownOperators: false
};
constructor(props) {
super(props);
this.state = {
listOperators: ["="],
stringOperators: ["=", "<>", "like", "ilike", "isNull"],
arrayOperators: ["contains"],
booleanOperators: ["="],
defaultOperators: ["=", ">", "<", ">=", "<=", "<>", "isNull"],
timeDateOperators: ["=", ">", "<", ">=", "<=", "<>", "><", "isNull"],
operator: this.props.operator || "="
};
}
getOperator = (type) => {
switch (type) {
case "list": {
return this.state.listOperators;
}
case "string": {
return this.state.stringOperators;
}
case "boolean": {
return this.state.booleanOperators;
}
case "array": {
return this.state.arrayOperators;
}
case "date":
case "time":
case "date-time":
{
return this.state.timeDateOperators;
}
default:
return this.state.defaultOperators;
}
};
renderInput = () => {
if (this.props.column.filterable === false) {
return <span/>;
}
const placeholder = getMessageById(this.context.messages, this.props.placeholderMsgId) || "Search";
let inputKey = 'header-filter-' + this.props.column.key;
return (<input disabled={this.props.disabled} key={inputKey} type="text" className="form-control input-sm" placeholder={placeholder} value={this.state?.value ?? this.props.value} onChange={this.handleChange}/>);
return (<div className="rw-widget">
<input
disabled={this.props.disabled || this.state.operator === 'isNull'}
key={inputKey}
type="text"
className="form-control input-sm"
placeholder={placeholder}
value={this.state?.value ?? this.props.value}
onChange={this.handleChange}/>
</div>);
}
renderTooltip = (cmp) => {
if (this.props.tooltipMsgId && getMessageById(this.context.messages, this.props.tooltipMsgId)) {
Expand All @@ -51,19 +102,38 @@ class AttributeFilter extends React.PureComponent {
}
return cmp;
}

renderOperatorField = () => {
return (
<ComboField
style={{ width: '30%'}}
// dropUp={this.props.dropUp}
fieldOptions= {this.getOperator(this.props.type)}
fieldName="operator"
fieldRowId={1}
onSelect={(selectedOperator)=>{
this.setState({ operator: selectedOperator, value: selectedOperator === 'isNull' ? undefined : this.state?.value ?? this.props.value });
let isNullOperatorSelected = selectedOperator === 'isNull';
let isValueExist = this.state?.value ?? this.props.value;
let isOperatorChangedFromIsNullAndValueNotExist = this.state.operator === 'isNull' && this.state.operator !== selectedOperator && !isValueExist;
if (isValueExist || isNullOperatorSelected || isOperatorChangedFromIsNullAndValueNotExist ) this.props.onChange({value: this.state?.value ?? this.props.value, attribute: this.props.column && this.props.column.key, inputOperator: selectedOperator});
}}
fieldValue={this.state.operator}
onUpdateField={this.updateFieldElement}/>
);
};
render() {
let inputKey = 'header-filter--' + this.props.column.key;
return (
<div key={inputKey} className={`form-group${(this.props.valid ? "" : " has-error")}`}>
{this.props.isShownOperators ? this.renderOperatorField() : null}
{this.renderTooltip(this.renderInput())}
</div>
);
}
handleChange = (e) => {
const value = e.target.value;
this.setState({value});
this.props.onChange({value, attribute: this.props.column && this.props.column.key});
this.props.onChange({value, attribute: this.props.column && this.props.column.key, inputOperator: this.state.operator});
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import PropTypes from 'prop-types';
import {intlShape} from 'react-intl';
import {getContext} from 'recompose';
import DateTimePicker from '../../../misc/datetimepicker';
import CustomDateTimePickerWithRange from '../../../misc/datetimepicker/CustomDateTimePickerWithRange';
import {getMessageById} from '../../../../utils/LocaleUtils';
import { getDateTimeFormat } from '../../../../utils/TimeUtils';
import AttributeFilter from './AttributeFilter';
Expand All @@ -22,6 +23,12 @@ const UTCDateTimePicker = utcDateWrapper({
setDateProp: "onChange"
})(DateTimePicker);

const UTCDateTimePickerWithRange = utcDateWrapper({
dateProp: "value",
dateTypeProp: "type",
setDateProp: "onChange"
})(CustomDateTimePickerWithRange);


class DateFilter extends AttributeFilter {
static propTypes = {
Expand All @@ -45,6 +52,7 @@ class DateFilter extends AttributeFilter {
if (this.props.column.filterable === false) {
return <span />;
}
const operator = this.props.value && this.props.value.operator || this.state.operator;
const format = getDateTimeFormat(this.context.locale, this.props.type);
const placeholder = getMessageById(this.context.messages, this.props.placeholderMsgId) || "Insert date";
const toolTip = this.props.intl && this.props.intl.formatMessage({id: `${this.props.tooltipMsgId}`}, {format}) || `Insert date in ${format} format`;
Expand All @@ -58,7 +66,23 @@ class DateFilter extends AttributeFilter {
val = this.props.value && this.props.value.startDate || this.props.value;
}
const dateValue = this.props.value ? val : null;
const operator = this.props.value && this.props.value.operator;
if (operator === '><') {
return (
<UTCDateTimePickerWithRange
key={inputKey}
disabled={this.props.disabled}
format={format}
placeholder={placeholder}
value={dateValue}
toolTip={toolTip}
operator={operator}
type={this.props.type}
time={this.props.type === 'time'}
calendar={this.props.type === 'date-time' || this.props.type === 'date'}
onChange={(date, stringDate) => this.handleChange(date, stringDate)}
/>
);
}
return (<UTCDateTimePicker
key={inputKey}
disabled={this.props.disabled}
Expand All @@ -74,7 +98,7 @@ class DateFilter extends AttributeFilter {
/>);
}
handleChange = (value, stringValue) => {
this.props.onChange({ value, stringValue, attribute: this.props.column && this.props.column.name });
this.props.onChange({ value, stringValue, attribute: this.props.column && this.props.column.name, inputOperator: this.state.operator || this.props.operator });
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default compose(
value: null
}),
withHandlers({
onChange: props => ({ value, attribute, stringValue } = {}) => {
onChange: props => ({ value, attribute, stringValue, inputOperator } = {}) => {
const match = /\s*(!==|!=|<>|<=|>=|===|==|=|<|>)?(.*)/.exec(stringValue);
const operator = match[1];
let enhancedOperator = match[1] || '=';
Expand All @@ -28,7 +28,7 @@ export default compose(
props.onValueChange(value);
props.onChange({
value: { startDate: value, operator },
operator: enhancedOperator,
operator: inputOperator || enhancedOperator,
type: props.type,
attribute
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ export default compose(
onValueChange: () => {}
}),
withHandlers({
onChange: props => ({value, attribute} = {}) => {
onChange: props => ({value, attribute, inputOperator} = {}) => {
props.onValueChange(value);
props.onChange({
value: value,
operator: "=",
operator: inputOperator || "=",
type: props.type,
attribute
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default compose(
}),
withState("valid", "setValid", true),
withHandlers({
onChange: props => ({value, attribute} = {}) => {
onChange: props => ({value, attribute, inputOperator} = {}) => {
props.onValueChange(value);
if (!COMMA_REGEX.exec(value)) {
let {operator, newVal} = getOperatorAndValue(value, "number");
Expand All @@ -31,7 +31,7 @@ export default compose(
props.onChange({
value: isNaN(newVal) ? undefined : newVal,
rawValue: value,
operator,
operator: inputOperator || operator,
type: 'number',
attribute
});
Expand All @@ -48,7 +48,7 @@ export default compose(
isValid && props.onChange({
value,
rawValue: value,
operator: "=",
operator: inputOperator || "=",
type: 'number',
attribute
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ export default compose(
placeholderMsgId: "featuregrid.filter.placeholders.string"
}),
withHandlers({
onChange: props => ({value, attribute} = {}) => {
onChange: props => ({value, attribute, inputOperator} = {}) => {
props.onValueChange(value);
props.onChange({
rawValue: value,
value: trim(value) ? trim(value) : undefined,
operator: "ilike",
operator: inputOperator || "ilike", // need to read operator from redux beased on operator selected option
type: 'string',
attribute
});
Expand Down
18 changes: 9 additions & 9 deletions web/client/components/data/featuregrid/filterRenderers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ import NumberFilter from './NumberFilter';
import StringFilter from './StringFilter';

const types = {
"defaultFilter": (type) => withProps(() =>({type: type}))(DefaultFilter),
"string": () => StringFilter,
"number": () => NumberFilter,
"int": () => NumberFilter,
"date": () => withProps(() =>({type: "date"}))(DateTimeFilter),
"time": () => withProps(() =>({type: "time"}))(DateTimeFilter),
"date-time": () => withProps(() =>({type: "date-time"}))(DateTimeFilter),
"defaultFilter": (props) => withProps(() =>({type: props.type, isShownOperators: props.isShownOperators || false}))(DefaultFilter),
"string": (props) => withProps(() =>({type: 'string', isShownOperators: props.isShownOperators || false}))(StringFilter),
"number": (props) => withProps(() =>({type: 'number', isShownOperators: props.isShownOperators || false}))(NumberFilter),
"int": (props) => withProps(() =>({type: 'integer', isShownOperators: props.isShownOperators || false}))(NumberFilter),
"date": (props) => withProps(() =>({type: "date", isShownOperators: props.isShownOperators || false}))(DateTimeFilter),
"time": (props) => withProps(() =>({type: "time", isShownOperators: props.isShownOperators || false}))(DateTimeFilter),
"date-time": (props) => withProps(() =>({type: "date-time", isShownOperators: props.isShownOperators || false}))(DateTimeFilter),
"geometry": () => GeometryFilter
};

Expand All @@ -46,11 +46,11 @@ export const getFilterRendererByName = (name) => {
* @param {string} [params.type] the type of the filter renderer. The available types are: "defaultFilter", "string", "number", "int", "date", "time", "date-time", "geometry".
* @returns {React.Component} the filter renderer
*/
export const getFilterRenderer = ({name, type}) => {
export const getFilterRenderer = ({name, type, isShownOperators}) => {
if (name) {
return getFilterRendererByName(name);
}
return types[type] ? types[type](type) : types.defaultFilter(type);
return types[type] ? types[type]({type, isShownOperators}) : types.defaultFilter({type, isShownOperators});
};


Loading

0 comments on commit 7f3c2e7

Please sign in to comment.