@aibsweb/faceted-search
Version:
A generalized faceted search application.
391 lines (312 loc) • 16.8 kB
JavaScript
;
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
};