UNPKG

@aibsweb/faceted-search-biccn

Version:

A generalized faceted search application. Used for BICCN

381 lines (311 loc) 15.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; require("@babel/polyfill"); require("custom-event-polyfill"); require("whatwg-fetch"); var _lodash = require("lodash"); require("../../scss/layout/stacked.scss"); require("../../scss/faceted-search.scss"); var _react = _interopRequireDefault(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _reactApollo = require("react-apollo"); var _apolloBoost = require("apollo-boost"); var _stateStore = _interopRequireDefault(require("../classes/state-store")); var _componentDefinitionValidationHelper = _interopRequireDefault(require("../helpers/component-definition-validation-helper")); var _enums = require("../enums"); var _loadIndicator = _interopRequireDefault(require("./status/load-indicator")); var _errorIndicator = _interopRequireDefault(require("./status/error-indicator")); var _filterBox = _interopRequireDefault(require("./filter-box/filter-box")); var _countsRatioBox = _interopRequireDefault(require("./counts-ratio-box/counts-ratio-box")); var _dataTable = _interopRequireDefault(require("./data-table/data-table")); 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 _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } 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 _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 _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 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 FacetedSearch = /*#__PURE__*/ function (_React$Component) { _inherits(FacetedSearch, _React$Component); /*** * The faceted search component is the entry point for the facetedSearch application. This component is responsible for loading * and rendering the components provided in the config. * * @param {Object} props * @param {json} props.config - Application configuration containing props for each component. * @param {string} props.configUrl - The application config url when the config is hosted elsewhere. * @param {Object} props.componentDefinitions - Contains a query builder, a data transform function, and layout position property * @param {string} props.layout - The name of the layout scss to use, defaults to 'stacked' */ function FacetedSearch(props) { var _this; _classCallCheck(this, FacetedSearch); _this = _possibleConstructorReturn(this, _getPrototypeOf(FacetedSearch).call(this, props)); _this.state = { readyState: _enums.loadState.INITIALIZATION, config: props.config, store: {} }; // map component type names to render methods of those components _this.componentRenderers = { 'counts-ratio-box': _this.renderCountsRatioBox.bind(_assertThisInitialized(_this)), 'filter-box': _this.renderFilterBox.bind(_assertThisInitialized(_this)), 'data-table': _this.renderDataTable.bind(_assertThisInitialized(_this)) }; return _this; } /*eslint brace-style: ["warn", "stroustrup"]*/ _createClass(FacetedSearch, [{ key: "componentDidMount", value: function componentDidMount() { var _this2 = this; var componentDefinitionsAreNotValid = this.componentDefinitionsAreNotValid(); var shouldFetchConfig = this.state.readyState === _enums.loadState.INITIALIZATION && this.props.configUrl && !this.state.config; var configCannotBeFound = !this.state.config && !this.props.configUrl; // check the component definition if (componentDefinitionsAreNotValid) { console.error('Provided component definitions are missing one or more properties.'); this.setState({ readyState: _enums.loadState.ERROR }); } // check if config should be fetched from a url else if (shouldFetchConfig) { this.setState({ readyState: _enums.loadState.FETCHING }, function () { return fetch(_this2.props.configUrl, { credentials: 'same-origin' }).then(function (response) { return response.json(); }).then(function (config) { _this2.setState({ config: config }, _this2.initialize); })["catch"](function (e) { console.error('Error while fetching config.', e.message); _this2.setState({ readyState: _enums.loadState.ERROR }); }); }); } // check if both a config is not provided and there is no url to get a config else if (configCannotBeFound) { this.setState({ readyState: _enums.loadState.ERROR }); } // config was provided else { this.initialize(); } } /*eslint brace-style: ["warn", "stroustrup"]*/ /** * When a hosting application provides a set componentDefinitions validate that all necessary properties are present. */ }, { key: "componentDefinitionsAreNotValid", value: function componentDefinitionsAreNotValid() { return !_componentDefinitionValidationHelper["default"].validateDefinitions(this.props.componentDefinitions); } /** * Set up: * Apollo Client; * props for components; * State Store; */ }, { key: "initialize", value: function initialize() { // Set up Apollo Client this.client = new _apolloBoost.ApolloClient({ cache: new _apolloBoost.InMemoryCache(), link: new _apolloBoost.HttpLink({ uri: this.state.config.network.uri }) }); // get props for components this.componentPropsList = this.getComponentProps(); // Set up State Store var stateStoreConfig = this.state.config['state-store'] || {}; this.stateStore = new _stateStore["default"](this.updateStore.bind(this), stateStoreConfig); // app is ready to render data. this.setState({ readyState: _enums.loadState.READY, store: this.stateStore.store }); } /** * A callback used to update component store. Called by the StateStore * * @param {object[]} store the state store. */ }, { key: "updateStore", value: function updateStore(store) { this.setState({ store: store }); } /** * Combine component configuration with component definition. * @returns {Object[]} - List of props for each component */ }, { key: "getComponentProps", value: function getComponentProps() { var _this3 = this; var componentKeys = this.getSortedComponentKeys(); return componentKeys.map(function (componentKey) { // { query, transformer, position } var componentDefinition = _this3.props.componentDefinitions[componentKey]; // component props from config json var componentConfig = _this3.state.config[componentKey]; // Provide a unique id var id = (0, _lodash.uniqueId)('id:'); return _objectSpread({ componentType: componentKey }, componentDefinition, componentConfig, { id: id }); }); } /** * @returns {String[]} List of component types that are in both definition and config files. */ }, { key: "getSortedComponentKeys", value: function getSortedComponentKeys() { var componentDefinitions = this.props.componentDefinitions; var configKeys = Object.keys(this.state.config); var definitionKeys = Object.keys(componentDefinitions); return (0, _lodash.intersection)(configKeys, definitionKeys).sort(function (a, b) { return componentDefinitions[a].position - componentDefinitions[b].position; }); } }, { key: "render", value: function render() { var readyState = this.state.readyState; if (readyState === _enums.loadState.INITIALIZATION || readyState === _enums.loadState.FETCHING) { return _loadIndicator["default"]; } if (readyState === _enums.loadState.READY) { return _react["default"].createElement("div", { className: "faceted-search__container layout-".concat(this.props.layout) }, this.renderApplication()); } // default return _errorIndicator["default"]; } }, { key: "renderApplication", value: function renderApplication() { return _react["default"].createElement(_reactApollo.ApolloProvider, { client: this.client }, this.componentPropsList.map(this.renderComponent.bind(this))); } /** * Determine which faceted search sub-component to render * * @param {object} componentProps - props for a component: contains definition, config, component type string, and unique id * @param {QueryBuilder} componentConfig.query - A reference to the static querybuilder for this component * @param {DataTransform} componentConfig.transformer - A reference to the static data transform class used to reformat the data returned from apollo */ }, { key: "renderComponent", value: function renderComponent(componentProps) { var componentRenderer = this.componentRenderers[componentProps.componentType]; if (!(0, _lodash.isFunction)(componentRenderer)) { return null; } return _react["default"].createElement("div", { key: componentProps.id, className: "component__".concat(componentProps.className, " layout-").concat(this.props.layout, "__").concat(componentProps.position) }, componentRenderer(componentProps)); } /** * Render method for Counts Ratio Box * @param {object} componentProps - props for a component: contains definition, config, component type string, and unique id */ }, { key: "renderCountsRatioBox", value: function renderCountsRatioBox(componentProps) { var _componentProps$query = componentProps.query.buildQuery(this.state.store), variables = _componentProps$query.variables, baseQuery = _componentProps$query.baseQuery; return _react["default"].createElement(_reactApollo.Query, { query: baseQuery, variables: variables, notifyOnNetworkStatusChange: true }, function (_ref) { var data = _ref.data; var transformedData = componentProps.transformer.transform(data); return _react["default"].createElement(_countsRatioBox["default"], _extends({ data: transformedData }, componentProps)); }); } /** * Render method for Filter Box * @param {object} componentProps - props for a component: contains definition, config, component type string, and unique id */ }, { key: "renderFilterBox", value: function renderFilterBox(componentProps) { var _componentProps$query2 = componentProps.query.buildQuery(this.state.store), variables = _componentProps$query2.variables, baseQuery = _componentProps$query2.baseQuery; return _react["default"].createElement(_reactApollo.Query, { query: baseQuery, variables: variables, notifyOnNetworkStatusChange: true }, function (_ref2) { var data = _ref2.data; var transformedData = componentProps.transformer.transform(data); return _react["default"].createElement(_filterBox["default"], _extends({ data: transformedData }, componentProps)); }); } /** * Render method for Data Table * @param {object} componentProps - props for a component: contains definition, config, component type string, and unique id */ }, { key: "renderDataTable", value: function renderDataTable(componentProps) { var _this4 = this; var _componentProps$query3 = componentProps.query.buildQuery(this.state.store), variables = _componentProps$query3.variables, baseQuery = _componentProps$query3.baseQuery; return _react["default"].createElement(_reactApollo.Query, { query: baseQuery, variables: variables, notifyOnNetworkStatusChange: true }, function (_ref3) { var data = _ref3.data, loading = _ref3.loading; var transformedData = componentProps.transformer.transform(data); return _react["default"].createElement(_dataTable["default"], _extends({ data: transformedData, store: _this4.state.store, isLoading: loading }, componentProps)); }); } }]); return FacetedSearch; }(_react["default"].Component); exports["default"] = FacetedSearch; FacetedSearch.propTypes = { config: _propTypes["default"].object, configUrl: _propTypes["default"].string, layout: _propTypes["default"].string, componentDefinitions: _propTypes["default"].object.isRequired }; FacetedSearch.defaultProps = { layout: 'stacked' };