UNPKG

@aibsweb/faceted-search

Version:
391 lines (312 loc) 16.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var _react = _interopRequireDefault(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _escapeRegExp = _interopRequireDefault(require("lodash/escapeRegExp")); var _isEmpty = _interopRequireDefault(require("lodash/isEmpty")); var _get = _interopRequireDefault(require("lodash/get")); var _reduce = _interopRequireDefault(require("lodash/reduce")); var _mapValues = _interopRequireDefault(require("lodash/mapValues")); var _difference = _interopRequireDefault(require("lodash/difference")); var _reactCustomScrollbars = require("react-custom-scrollbars"); var _textButton = _interopRequireDefault(require("./text-button")); var _filterTextSearch = _interopRequireDefault(require("../filter-text-search/filter-text-search")); var _categoryFilter = _interopRequireDefault(require("../category-filter/category-filter")); var _enums = require("../../enums"); require("../../../scss/filter-box.scss"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); } function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } var FilterBox = /*#__PURE__*/ function (_React$Component) { _inherits(FilterBox, _React$Component); _createClass(FilterBox, null, [{ key: "getDefaultCollapsedListItems", value: function getDefaultCollapsedListItems(data) { return data.reduce(function (acc, category) { acc[category.name] = _categoryFilter["default"].CollapsedState.SHOW_ALL; return acc; }, {}); } /** * Creates a default selected filters object from initial data. * @param {Array} data - data from API. Array of category objects * @param {Object} store - state store */ }, { key: "getDefaultSelectedFilters", value: function getDefaultSelectedFilters(data, store) { var selectedFilters = data.reduce(function (categoryAcc, category) { categoryAcc[category.name] = category.values.reduce(function (valueAcc, value) { valueAcc[value.name] = false; return valueAcc; }, {}); return categoryAcc; }, {}); var filtersFromStore = store.filter; // apply filters from store filtersFromStore.forEach(function (filterItem) { selectedFilters[filterItem.category][filterItem.value] = filterItem.checked; }); return selectedFilters; } }]); function FilterBox(props) { var _this; _classCallCheck(this, FilterBox); _this = _possibleConstructorReturn(this, _getPrototypeOf(FilterBox).call(this, props)); // initialize state _this.componentDidUpdate = function (prevProps) { // If data has not already been loaded from Apollo Query // initialize state for collapsed list items and selected filters after data is loaded // For instance, this does not run if switching tabs in Faceted Seach because data was already loaded. if (prevProps.data !== _this.props.data && (0, _isEmpty["default"])(_this.state.collapsedListItems) && (0, _isEmpty["default"])(_this.state.selectedFilters)) { _this.setState({ collapsedListItems: FilterBox.getDefaultCollapsedListItems(_this.props.data), selectedFilters: FilterBox.getDefaultSelectedFilters(_this.props.data, _this.props.store) }); } // set local state for selected filters when filters change if (prevProps.store.filter.length !== _this.props.store.filter.length) { // Set all filters to false. var nextSelectedFilters = (0, _reduce["default"])(_this.state.selectedFilters, function (categories, categoryValues, categoryName) { categories[categoryName] = (0, _reduce["default"])(categoryValues, function (categoryValues, _, categoryValueName) { categoryValues[categoryValueName] = false; return categoryValues; }, {}); return categories; }, {}); // Set filters based on state store filters _this.props.store.filter.forEach(function (filterItem) { nextSelectedFilters[filterItem.category][filterItem.value] = filterItem.checked; }); _this.setState({ selectedFilters: nextSelectedFilters }); } }; _this.collapseAll = function () { _this.setState({ collapsedListItems: (0, _mapValues["default"])(_this.state.collapsedListItems, function () { return _categoryFilter["default"].CollapsedState.SHOW_MATCHED_OR_SELECTED; }), showExpandAll: true }); }; _this.expandAll = function () { _this.setState({ collapsedListItems: (0, _mapValues["default"])(_this.state.collapsedListItems, function () { return _categoryFilter["default"].CollapsedState.SHOW_ALL; }), showExpandAll: false }); }; _this.serializeState = function () { var _this$state = _this.state, collapsedListItems = _this$state.collapsedListItems, selectedFilters = _this$state.selectedFilters; return { collapsedListItems: collapsedListItems, selectedFilters: selectedFilters }; }; _this.filterTextSearchChangeHandler = function (filterText) { _this.setState({ filterText: filterText }); }; _this.textFilterReducer = function (acc, category) { // create special character escaped regex var filterTextRegex = new RegExp((0, _escapeRegExp["default"])(_this.state.filterText), 'i'); // first, test the category display name if (filterTextRegex.test(category.displayName)) { // add the entire category to the filtered data acc.push(category); return acc; } // if category is not a match, then check values in the category var filteredValues = category.values.filter( // test value against regex function (value) { return filterTextRegex.test(value.name) || // OR keep any selected filter values _this.state.selectedFilters[category.name][value.name]; }); if (filteredValues.length) { // keep category data but overwrite with filtered values acc.push(_objectSpread({}, category, { values: filteredValues })); } return acc; }; _this.filterChangeCB = function (filterItem) { var filter = { category: filterItem.category, value: filterItem.valueName, checked: filterItem.valueState }; var event = new CustomEvent(_enums.EVENT_KEYS.FILTER, { detail: filter }); document.dispatchEvent(event); }; _this.collapseChangeCB = function (collapsedItem) { var nextCollapsedListItems = _objectSpread({}, _this.state.collapsedListItems, _defineProperty({}, collapsedItem.category, collapsedItem.collapsed)); _this.setState({ collapsedListItems: nextCollapsedListItems }); }; _this.renderFilterTextSearch = function () { if (!_this.props.showFilterTextSearch) { return null; } return _react["default"].createElement(_filterTextSearch["default"], { searchTextChangeCB: _this.filterTextSearchChangeHandler }); }; _this.renderCategoryFilter = function (category) { var _this$state2 = _this.state, collapsedListItems = _this$state2.collapsedListItems, filterText = _this$state2.filterText, selectedFilters = _this$state2.selectedFilters; var categoryFilterProps = { key: category.name, data: category, selected: selectedFilters[category.name], collapsed: collapsedListItems[category.name], match: filterText, filterChangeCB: _this.filterChangeCB, collapseChangeCB: _this.collapseChangeCB, collapseEnabled: true }; return _react["default"].createElement(_categoryFilter["default"], categoryFilterProps); }; _this.clearAllFiltersCB = function () { var filtersForRemoval = _this.props.store.filter.map(function (filterItem) { return _objectSpread({}, filterItem, { checked: false }); }); // tell StateStore to remove the filters from the query via an event var event = new CustomEvent(_enums.EVENT_KEYS.FILTER, { detail: filtersForRemoval }); document.dispatchEvent(event); }; _this.renderClearAllFilters = function () { var attributes = { clickHandler: _this.clearAllFiltersCB, className: 'filter-box__text-button text-button--clear-filters', buttonText: 'Clear All Filters' }; return _react["default"].createElement(_textButton["default"], attributes); }; _this.renderExpandCollapseFilters = function (showExpandAll) { var attributes = { clickHandler: showExpandAll ? _this.expandAll : _this.collapseAll, className: 'filter-box__text-button text-button--expand-collapse-filters', buttonText: showExpandAll ? 'Expand All' : 'Collapse All' }; return _react["default"].createElement(_textButton["default"], attributes); }; _this.state = { collapsedListItems: FilterBox.getDefaultCollapsedListItems(props.data), // Object, { [category: String]: CollapsedState } filterText: '', // String selectedFilters: FilterBox.getDefaultSelectedFilters(props.data, props.store), // Object, { [category: String]: { [value: String]: Boolean } } showExpandAll: false // Boolean }; return _this; } _createClass(FilterBox, [{ key: "appendAnySelectedFilters", /** * selected filter check box values with a count of zero will be appended to the list of values for a category * @param {object} data - for category check boxes */ value: function appendAnySelectedFilters(data) { var _this2 = this; var nextData = data.map(function (categoryData) { // get string name for category var categoryName = categoryData.name; // get the selected filter object for this category var categorySelectedFilters = (0, _get["default"])(_this2.state.selectedFilters, categoryName, {}); // get the string values that are selected for this category var categorySelectedFilterValues = Object.keys(categorySelectedFilters).filter(function (key) { return categorySelectedFilters[key]; }); // get the string value names for this category. based on a filter being applied, some selected values maybe missing var categoryValuesByName = categoryData.values.map(function (value) { return value.name; }); // from the list of selected filter values, find out which were filtered out in the data var missingSelectedFilterValues = (0, _difference["default"])(categorySelectedFilterValues, categoryValuesByName); // create new zero count filter objects var missingSelectedFilterObjects = missingSelectedFilterValues.map(function (value) { return { name: value, count: 0 }; }); // combine all the values. categoryData.values = [].concat(_toConsumableArray(categoryData.values), _toConsumableArray(missingSelectedFilterObjects)); return categoryData; }); return nextData; } /** * Renders clickable text element to clear all filter selections. * * @returns {object} React <TextButton/> element */ }, { key: "render", /** * React * @ignore */ value: function render() { var data = this.props.data; var dataWithSelectedFilters = this.appendAnySelectedFilters(data); var _this$state3 = this.state, filterText = _this$state3.filterText, showExpandAll = _this$state3.showExpandAll; var filteredData = filterText.length ? dataWithSelectedFilters.reduce(this.textFilterReducer, []) : dataWithSelectedFilters; return _react["default"].createElement("div", { className: "filter-box" }, _react["default"].createElement("div", { className: "filter-box__top" }, this.renderFilterTextSearch()), _react["default"].createElement("div", { className: "filter-box__middle" }, this.renderClearAllFilters(), this.renderExpandCollapseFilters(showExpandAll)), _react["default"].createElement("div", { className: "filter-box__filters" }, _react["default"].createElement(_reactCustomScrollbars.Scrollbars, { className: "filter-box__filter-scrollbar", renderThumbVertical: function renderThumbVertical() { return _react["default"].createElement("div", { className: "filter-box__filter-scroll-thumb" }); } }, filteredData.map(this.renderCategoryFilter)))); } }]); return FilterBox; }(_react["default"].Component); exports["default"] = FilterBox; FilterBox.propTypes = { data: _propTypes["default"].arrayOf(_propTypes["default"].shape({ displayName: _propTypes["default"].string, name: _propTypes["default"].string, values: _propTypes["default"].arrayOf(_propTypes["default"].shape({ count: _propTypes["default"].oneOfType([_propTypes["default"].number, _propTypes["default"].string]), name: _propTypes["default"].string })) })).isRequired, store: _propTypes["default"].object.isRequired, showFilterTextSearch: _propTypes["default"].bool };