UNPKG

labo-components

Version:
386 lines (343 loc) 15.3 kB
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} /> &nbsp;<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;