labo-components
Version:
386 lines (343 loc) • 15.3 kB
JSX
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import IDUtil from '../../util/IDUtil';
import ReactTooltip from 'react-tooltip';
import debounce from 'debounce';
class AggregationBox extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
sortMode: this.determineSortMode(),
showAll: this.determineShowAll(),
currentOptions: this.props.data.facets,
showExtraOptions: false,
};
this.CLASS_PREFIX = "agb";
this.minToShow = this.props.minToShow || 5;
this.maxToShow = this.props.maxToShow || 10;
this.SORT_BUTTON_TITLES = { alpha: "Alphanumeric", numeric: "Numeric" };
this.updateKeywordFilter = debounce(this.updateKeywordFilter, 150)
}
determineSortMode = () => {
//load the sort mode from the desired facet corresponding
//to this facet in the query, otherwise use the default
const desiredFacet = this.props.desiredFacets[this.props.desiredFacets.findIndex(desiredFacet => desiredFacet.field === this.props.data.field)]
let sortMode = {
alpha: "asc",
numeric: "asc",
active: "alpha"
}
if(desiredFacet!=null && "sortMode" in desiredFacet)
{
sortMode = desiredFacet["sortMode"]
}
return sortMode;
}
determineShowAll = currentFacet => {
//load the showAll value from the desired facet corresponding
//to this facet in the query, otherwise use the default
const desiredFacet = this.props.desiredFacets[this.props.desiredFacets.findIndex(desiredFacet => desiredFacet.field === this.props.data.field)]
let showAll = false;
if(desiredFacet!=null && "showAll" in desiredFacet)
{
showAll = desiredFacet["showAll"]
}
return showAll;
}
saveSortMode = (newSortMode) => {
//save the changed sort mode to the equivalent desired facet
//so that it will be stored in the query
const desiredFacet = this.props.desiredFacets[this.props.desiredFacets.findIndex(desiredFacet => desiredFacet.field === this.props.data.field)]
desiredFacet["sortMode"] = newSortMode
}
saveShowAllValue = (newShowAllValue) => {
//save the changed show all value to the equivalent desired facet
//so that it will be stored in the query
const desiredFacet = this.props.desiredFacets[this.props.desiredFacets.findIndex(desiredFacet => desiredFacet.field === this.props.data.field)]
desiredFacet["showAll"] = newShowAllValue
}
sortData = (data, sortMode) => {
let newSortedList = [...data];
const disabledFields = 'Empty field';
if (sortMode.active === 'alpha') {
if (sortMode['alpha'] === 'desc') {
newSortedList.sort((a, b) => {
if(a.key === disabledFields) {
return -2;
} else if(typeof(a.key) == 'number') {
return b.key - a.key
} else if(typeof(a.key) == 'string') {
return a.key.toLowerCase() < b.key.toLowerCase() ? 1 : (b.key.toLowerCase() < a.key.toLowerCase() ? -1 : 0);
}
});
} else { //asc
newSortedList.sort((a, b) => {
if(a.key === disabledFields) {
return -2;
} else if(typeof(a.key) == 'number') {
return a.key - b.key;
} else if(typeof(a.key) == 'string') {
return a.key.toLowerCase() > b.key.toLowerCase() ? 1 : (b.key.toLowerCase() > a.key.toLowerCase() ? -1 : 0);
}
});
}
return newSortedList;
} else if (sortMode.active === 'numeric') {
if (sortMode['numeric'] === 'desc') {
newSortedList.sort((a, b) => {
if(a.key === disabledFields){
return -2;
}
return b.count - a.count;
});
} else { //asc
newSortedList.sort((a, b) => {
if(a.key === disabledFields){
return -2;
}
return a.count - b.count;
});
}
return newSortedList;
} else {
return null;
}
};
// puts selected elements on top
getSelectedAggregation = aggr => {
const selectedFirst = [];
aggr.forEach(item => {
if (item.selected) {
selectedFirst.unshift(item)
} else {
selectedFirst.push(item)
}
});
return selectedFirst;
};
toggleSelectedFacet = e => this.props.onToggleSelectedFacet(
this.props.data.field,
e.currentTarget.getAttribute('value')
);
renderAggregationBlock = data => {
const sortedData = this.sortData(data, this.state.sortMode);
const selectedAggregation = this.getSelectedAggregation(sortedData);
const nrOfSelectedTerms = this.props.selectedFacets[this.props.data.field] ?
this.props.selectedFacets[this.props.data.field].length :
0
;
const disabledFields = 'Empty field';
const sortedFacets = selectedAggregation.map((f, index) => {
return (
<li
key={"facet__" + data.index + "__" + index}
id={this.props.data.field}
value={f.key}
title={f.key}
hidden={this.state.showAll ?
index > this.state.currentOptions.length :
index > this.minToShow + nrOfSelectedTerms
}
className={classNames(
{selected: f.selected, exclude: this.props.data.exclude, disabled : f.key === disabledFields},
IDUtil.cssClassName('facet-list-item', this.CLASS_PREFIX)
)}
onClick={f.key !== disabledFields ? this.toggleSelectedFacet : null}
>
{f.key !== disabledFields ?
<span className={IDUtil.cssClassName("checkbox", this.CLASS_PREFIX)}/> :
null
}
<span className="elem-label">{f.key}</span>
<span className={IDUtil.cssClassName('count', this.CLASS_PREFIX)}>{f.count}</span>
</li>
)
});
return (
<ul className={IDUtil.cssClassName('facet-list', this.CLASS_PREFIX)}>
{sortedFacets}
</ul>
);
};
toggleShow = () => {
this.setState({ showAll: !this.state.showAll })
this.saveShowAllValue(!this.state.showAll); //save the changed value in the desired facet so it will be saved in the query
this.props.onOutput(this.props.desiredFacets, this.props.selectedFacets);
};
renderToggleShowMore = () => {
const currentStatus = this.state.showAll
? {
text: "Show Less",
symbol: "switchIcon fas fa-minus"
}
: {
text: "Show More",
symbol: "switchIcon fas fa-plus"
};
if(this.state.currentOptions.length <= this.minToShow + (this.props.selectedFacets[this.props.data.field]
? this.props.selectedFacets[this.props.data.field].length
: 0 )
) {
return null;
}
return (
<a className={IDUtil.cssClassName('switch-view', this.CLASS_PREFIX)} onClick={this.toggleShow}>
<span className="switchViewText">{currentStatus.text}</span>
<span className={currentStatus.symbol} aria-hidden="true" />
</a>
);
};
setFacetSortMode = (sortType, direction) => {
const sortModes = this.state.sortMode;
const newSortModes = {
alpha : sortType === 'alpha' ? direction : sortModes.alpha,
numeric : sortType === 'numeric' ? direction : sortModes.numeric,
active : sortType
}
//save new sort mode in the query
this.setState({ sortMode: newSortModes }, () => this.sortData(this.state.currentOptions, this.state.sortMode));
this.saveSortMode(newSortModes); //save the changed value in the desired facet so it will be saved in the query
this.props.onOutput(this.props.desiredFacets, this.props.selectedFacets);
};
renderSortButton = (aggr, sortType) => {
//alpha || numeric
const sortMode = this.state.sortMode;
const title = sortMode[sortType] === "desc"
? this.SORT_BUTTON_TITLES[sortType] + " descending"
: this.SORT_BUTTON_TITLES[sortType] + " ascending";
const newDirection = sortMode[sortType] === "desc" ? "asc" : "desc";
const classNames = [
"fa",
"fa-lg",
"fa-sort-" + sortType + "-" + sortMode[sortType]
];
if (sortMode.active === sortType) {
classNames.push(
IDUtil.cssClassName("sort-active", this.CLASS_PREFIX)
);
}
return (
<div
className={IDUtil.cssClassName("sort-btn", this.CLASS_PREFIX)}
title={title}
onClick={() => this.setFacetSortMode(sortType, newDirection)}
>
<i aria-hidden="true" className={classNames.join(" ")}/>
</div>
);
};
toggleExcludeFacets = () => {
this.props.desiredFacets[this.props.data.index]["exclude"] =
!this.props.desiredFacets[this.props.data.index]["exclude"];
this.props.onOutput(this.props.desiredFacets, this.props.selectedFacets);
};
showRemoveDialog = () => this.props.showRemoveDialog(this.props.data.field, this.props.data.index);
toggleExtraOptions = () => this.setState({ showExtraOptions: !this.state.showExtraOptions });
renderHamburgerMenu = aggr => {
const extraOptions = this.state.showExtraOptions ? (
<div className={IDUtil.cssClassName('extra-options', this.CLASS_PREFIX)}>
<div className={IDUtil.cssClassName('exclude-btn', this.CLASS_PREFIX)}>
<input
type="checkbox"
id={this.props.data.field}
checked={this.props.desiredFacets[this.props.data.index]['exclude'] || false}
onChange={this.toggleExcludeFacets}
/>
<label htmlFor={this.props.data.field} title="Exclude selection from search results">Exclude</label>
</div>
<button
className={classNames('btn', IDUtil.cssClassName('remove-button', this.CLASS_PREFIX))}
onClick={this.showRemoveDialog}
title="Remove Facet"
>
Remove
</button>
<div className={IDUtil.cssClassName('sort-btn-wrapper', this.CLASS_PREFIX)}>
{this.renderSortButton(this.props.data.facets, 'numeric')}
{this.renderSortButton(this.props.data.facets, 'alpha')}
</div>
</div>
) : null;
return (
<div className={IDUtil.cssClassName('facet-header', this.CLASS_PREFIX)}>
<div className={IDUtil.cssClassName('facet-title-bar', this.CLASS_PREFIX)} onClick={this.toggleExtraOptions}>
<span className={IDUtil.cssClassName('facet-title', this.CLASS_PREFIX)}>
<i
className="fas fa-info-circle"
data-for={"tooltip__" + this.props.data.index}
data-tip={this.props.data.field}
data-html={true}
/>
<span>{this.props.data.title}{" "}</span>
</span>
<div className={IDUtil.cssClassName('menu-icon', this.CLASS_PREFIX)}>⋮</div>
</div>
{extraOptions}
<ReactTooltip id={"tooltip__" + this.props.data.index} />
</div>
);
};
// Returns items that have the searched term or that have already been selected.
filterFields = (arr, str) => arr.filter(item => item.key && (item.key.toLowerCase().includes(str.toLowerCase()) || item.selected));
onKeywordFilter = (e) => {
this.updateKeywordFilter(e.target.value);
};
updateKeywordFilter = (value)=>{
const newCategorySet = this.filterFields(this.props.data.facets, value);
this.setState({
currentOptions: newCategorySet
});
}
render() {
if (this.state.currentOptions) {
const selectedTerms = this.props.selectedFacets[this.props.data.field]
? this.props.selectedFacets[this.props.data.field].length
: 0;
const nrOfTerms = this.state.currentOptions.length - selectedTerms;
const renderedBlock = this.renderAggregationBlock(this.state.currentOptions);
return (
<div className={IDUtil.cssClassName('aggregation-box')} id={"index__" + this.props.data.index}>
{this.renderHamburgerMenu(this.props.data.facets)}
<div className={IDUtil.cssClassName('search-box', this.CLASS_PREFIX)}>
<input
className={IDUtil.cssClassName('search-input', this.CLASS_PREFIX)}
type="text"
placeholder="Search facet ..."
name="search"
onChange={this.onKeywordFilter}
/>
<div className={IDUtil.cssClassName('search-count', this.CLASS_PREFIX)}>
{nrOfTerms} terms
</div>
</div>
{renderedBlock}
{this.renderToggleShowMore()}
</div>
);
} else {
return <div>loading ...</div>
}
}
}
AggregationBox.propTypes = {
clientId: PropTypes.string,
user: PropTypes.shape({
id: PropTypes.string.isRequired
}),
data: PropTypes.shape({
empty: PropTypes.boolean,
field: PropTypes.string.isRequired,
exclude : PropTypes.boolean,
facets : PropTypes.array,
guid : PropTypes.string,
index: PropTypes.number.isRequired,
title: PropTypes.string
}).isRequired,
desiredFacets : PropTypes.array,
onOutput: PropTypes.func,
onToggleSelectedFacet : PropTypes.func,
selectedFacets : PropTypes.object,
showRemoveDialog : PropTypes.func
};
export default AggregationBox;