@aibsweb/faceted-search-biccn
Version:
A generalized faceted search application. Used for BICCN
381 lines (311 loc) • 15.8 kB
JavaScript
"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'
};