UNPKG

@appbaseio/reactivesearch-vue

Version:

A Vue UI components library for building search experiences

586 lines (578 loc) 21.4 kB
import { Actions, helper } from '@appbaseio/reactivecore'; import '@appbaseio/reactivecore/lib/utils/constants'; import { _ as _taggedTemplateLiteralLoose, a as _extends } from './_rollupPluginBabelHelpers-5e8399d7.js'; import { h, createVNode, isVNode } from 'vue'; import VueTypes from 'vue-types'; import { isEqual as isEqual$1, transformRequestUsingEndpoint } from '@appbaseio/reactivecore/lib/utils/helper'; import { styled, createCache } from '@appbaseio/vue-emotion'; import { t as types } from './vueTypes-5d575822.js'; import 'redux'; import { c as connect, e as composeThemeObject, X as X_SEARCH_CLIENT } from './index-3af85a74.js'; import configureStore, { Actions as Actions$1 } from '@appbaseio/reactivecore/lib'; import { updateAnalyticsConfig } from '@appbaseio/reactivecore/lib/actions/analytics'; import Appbase from 'appbase-js'; import AppbaseAnalytics from '@appbaseio/analytics'; import 'url-search-params-polyfill'; var Provider = { name: 'Provider', props: { store: { type: Object, required: true, validator: function validator(store) { if (!store.dispatch && !store.subscribe && !store.getState) { throw new Error('[reactivesearch-vue] - store provided is not a valid redux store'); } return true; } }, analyticsRef: { type: Object, required: false } }, provide: function provide() { return { $$store: this.store, $analytics: this.analyticsRef }; }, render: function render() { if (this.$slots["default"]().length > 1) { return h('div', this.$slots["default"]()); } return this.$slots["default"]()[0]; } }; var _templateObject; function _isSlot(s) { return typeof s === 'function' || Object.prototype.toString.call(s) === '[object Object]' && !isVNode(s); } var Base = function Base(_ref) { var _ref$data$attrs$as = _ref.data.attrs.as, T = _ref$data$attrs$as === void 0 ? 'div' : _ref$data$attrs$as, props = _ref.data, children = _ref.children; var propsVar = props; delete propsVar.attrs.as; return createVNode(T, props, _isSlot(children) ? children : { "default": function _default() { return [children]; } }); }; var Base$1 = styled(Base)(_templateObject || (_templateObject = _taggedTemplateLiteralLoose(["\n\t", "\n\twidth: 100%;\n\n\tinput,\n\tbutton,\n\ttextarea,\n\tselect {\n\t\tfont-family: ", ";\n\t}\n\n\t*,\n\t*:before,\n\t*:after {\n\t\tbox-sizing: border-box;\n\t}\n"])), function (_ref2) { var userThemeProp = _ref2.userThemeProp, theme = _ref2.theme; return userThemeProp === false ? '' : "font-family: " + (theme && theme.typography ? theme.typography.fontFamily : 'unset') + ";\n\tfont-size: " + (theme && theme.typography ? theme.typography.fontSize : 'unset') + ";\n\tcolor: " + (theme && theme.colors ? theme.colors.textColor : 'unset') + ";"; }, function (_ref3) { var theme = _ref3.theme; return theme && theme.typography ? theme.typography.fontFamily : 'unset'; }); function _isSlot$1(s) { return typeof s === 'function' || Object.prototype.toString.call(s) === '[object Object]' && !isVNode(s); } var setHeaders = Actions.setHeaders, setValue = Actions.setValue; var isEqual = helper.isEqual; var URLParamsProvider = { name: 'URLParamsProvider', props: { className: types.string, headers: types.headers, getSearchParams: types.func, setSearchParams: types.func, as: VueTypes.string.def('div'), userThemeProp: VueTypes.oneOf([VueTypes.bool, VueTypes.object.def({})]) }, mounted: function mounted() { var _this = this; this.init(); window.onpopstate = function () { _this.init(); var activeComponents = Array.from(_this.params.keys()); // remove inactive components from selectedValues Object.keys(_this.currentSelectedState).filter(function (item) { return !activeComponents.includes(item); }).forEach(function (component) { _this.setValue(component, null, undefined, undefined, undefined, undefined, undefined, undefined, 'URL'); }); // update active components in selectedValues Array.from(_this.params.entries()).forEach(function (item) { try { var component = item[0], value = item[1]; var _ref = _this.selectedValues[component] || { label: component }, label = _ref.label, showFilter = _ref.showFilter, URLParams = _ref.URLParams; _this.setValue(component, JSON.parse(value), label, showFilter, URLParams, undefined, undefined, undefined, 'URL'); } catch (e) { // Do not set value if JSON parsing fails. } }); }; }, watch: { $route: function $route() { // this ensures the url params change are handled // when the url changes, which enables us to // make `onpopstate` event handler work with history.pushState updates this.checkForURLParamsChange(); }, selectedValues: function selectedValues(newVal, oldVal) { var _this2 = this; if (!isEqual(newVal, oldVal)) { this.searchString = this.$props.getSearchParams ? this.$props.getSearchParams() : window.location.search; this.params = new URLSearchParams(this.searchString); var currentComponents = Object.keys(newVal); var urlComponents = Array.from(this.params.keys()); var shouldPushHistory = false; currentComponents.filter(function (component) { return newVal[component].URLParams; }).forEach(function (component) { var selectedValues = newVal[component]; // prevents empty history pollution on initial load if (_this2.hasValidValue(newVal[component]) || _this2.hasValidValue(oldVal[component])) { if (selectedValues.URLParams) { if (selectedValues.category) { var shouldUpdateHistory = _this2.setURL(component, _this2.getValue({ category: selectedValues.category, value: selectedValues.value })); if (shouldUpdateHistory) { shouldPushHistory = true; } } else { var _shouldUpdateHistory = _this2.setURL(component, _this2.getValue(selectedValues.value)); if (_shouldUpdateHistory) { shouldPushHistory = true; } } } else { _this2.params["delete"](component); shouldPushHistory = true; } } else if (!_this2.hasValidValue(newVal[component]) && urlComponents.includes(component)) { // doesn't have a valid value, but the url has a (stale) valid value set _this2.params["delete"](component); shouldPushHistory = true; } }); // remove unmounted components Object.keys(newVal).filter(function (component) { return !currentComponents.includes(component); }).forEach(function (component) { _this2.params["delete"](component); shouldPushHistory = true; }); if (!currentComponents.length) { Array.from(this.params.keys()).forEach(function (item) { if (_this2.searchComponents && _this2.searchComponents.includes(item)) { _this2.params["delete"](item); shouldPushHistory = true; } }); } if (shouldPushHistory) { this.pushToHistory(); } } }, headers: function headers(newVal, oldVal) { if (!isEqual(oldVal, newVal)) { this.setHeaders(newVal); } } }, methods: { init: function init() { this.searchString = this.$props.getSearchParams ? this.$props.getSearchParams() : window.location.search; this.params = new URLSearchParams(this.searchString); this.currentSelectedState = this.selectedValues || {}; }, checkForURLParamsChange: function checkForURLParamsChange() { // we only compare the search string (window.location.search by default) // to see if the route has changed (or) not. This handles the following usecase: // search on homepage -> route changes -> search results page with same search query if (window) { var searchString = this.$props.getSearchParams ? this.$props.getSearchParams() : window.location.search; if (searchString !== this.searchString) { var event; if (typeof Event === 'function') { event = new Event('popstate'); } else { // Correctly fire popstate event on IE11 to prevent app crash. event = document.createEvent('Event'); event.initEvent('popstate', true, true); } window.dispatchEvent(event); } } }, hasValidValue: function hasValidValue(component) { if (!component) return false; if (Array.isArray(component.value)) return !!component.value.length; return !!component.value; }, getValue: function getValue(value) { var _this3 = this; if (Array.isArray(value) && value.length) { return value.map(function (item) { return _this3.getValue(item); }); } if (value && typeof value === 'object') { // TODO: support for NestedList if (value.location) return value; if (value.category) return value; return value.label || value.key || null; } return value; }, setURL: function setURL(component, value) { if (!value || typeof value === 'string' && value.trim() === '' || Array.isArray(value) && value.length === 0) { this.params["delete"](component); return true; } var data = JSON.stringify(value); if (data !== this.params.get(component)) { this.params.set(component, data); return true; } return false; }, pushToHistory: function pushToHistory() { var paramsSting = this.params.toString() ? "?" + this.params.toString() : ''; var base = window.location.href.split('?')[0]; var newURL = "" + base + paramsSting; if (this.$props.setSearchParams) { this.$props.setSearchParams(newURL); } else if (window.history.pushState) { window.history.pushState({ path: newURL }, '', newURL); } this.init(); } }, render: function render() { var children = this.$slots["default"](); return createVNode(Base$1, { "as": this.$props.as, "class": this.$props.className, "userThemeProp": this.$props.userThemeProp }, _isSlot$1(children) ? children : { "default": function _default() { return [children]; } }); } }; var mapStateToProps = function mapStateToProps(state) { return { selectedValues: state.selectedValues, searchComponents: state.components }; }; var mapDispatchtoProps = { setHeaders: setHeaders, setValue: setValue }; URLParamsProvider.install = function (Vue) { Vue.component(URLParamsProvider.name, URLParamsProvider); }; var URLParamsProvider$1 = connect(mapStateToProps, mapDispatchtoProps)(URLParamsProvider); var typography = { fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Noto Sans", "Ubuntu", "Droid Sans", "Helvetica Neue", sans-serif', fontSize: '16px' }; var light = { typography: typography, colors: { textColor: '#424242', primaryTextColor: '#fff', primaryColor: '#0B6AFF', titleColor: '#424242', alertColor: '#d9534f', borderColor: '#595959' } }; var dark = { typography: typography, colors: { textColor: '#ffffffcf', backgroundColor: '#212121', primaryTextColor: '#ffffffcf', primaryColor: '#2196F3', titleColor: '#ffffffcf', alertColor: '#d9534f', borderColor: '#666' } }; function getTheme(preset) { if (preset === 'light') { return light; } return dark; } function _isSlot$2(s) { return typeof s === 'function' || Object.prototype.toString.call(s) === '[object Object]' && !isVNode(s); } var setValues = Actions$1.setValues; var ReactiveBase = { name: 'ReactiveBase', data: function data() { this.state = { key: '__REACTIVE_BASE__' }; return this.state; }, created: function created() { this.setStore(this.$props); }, props: { app: types.string, analytics: VueTypes.bool, reactivesearchAPIConfig: types.reactivesearchAPIConfig, credentials: types.string, headers: types.headers, queryParams: types.string, theme: VueTypes.oneOf([VueTypes.bool, VueTypes.object.def({})]), themePreset: VueTypes.string.def('light'), type: types.string, url: types.string, mapKey: types.string, initialQueriesSyncTime: types.number, className: types.string, initialState: VueTypes.object.def({}), contextCollector: VueTypes.func, transformRequest: types.func, transformResponse: types.func, as: VueTypes.string.def('div'), getSearchParams: types.func, setSearchParams: types.func, mongodb: types.mongodb, endpoint: types.endpointConfig, preferences: VueTypes.object, httpRequestTimeout: VueTypes.number.def(30) }, provide: function provide() { var createCacheFn = createCache; if (createCache["default"]) { createCacheFn = createCache["default"]; } return { theme_reactivesearch: composeThemeObject(getTheme(this.$props.themePreset), this.$props.theme), store: this.store, $searchPreferences: this.preferences, $emotionCache: this.$parent && this.$parent.$emotionCache || createCacheFn({ key: 'css' }) }; }, watch: { app: function app() { this.updateState(this.$props); }, url: function url() { this.updateState(this.$props); }, type: function type() { this.updateState(this.$props); }, credentials: function credentials() { this.updateState(this.$props); }, mapKey: function mapKey() { this.updateState(this.$props); }, headers: function headers() { this.updateState(this.$props); }, reactivesearchAPIConfig: function reactivesearchAPIConfig(newVal, oldVal) { if (!isEqual$1(newVal, oldVal)) { if (this.store) { this.store.dispatch(updateAnalyticsConfig(newVal)); } } }, mongodb: function mongodb() { this.updateState(this.$props); }, httpRequestTimeout: function httpRequestTimeout() { this.updateState(this.$props); } }, computed: { getHeaders: function getHeaders() { var _this$$props = this.$props, headers = _this$$props.headers, reactivesearchAPIConfig = _this$$props.reactivesearchAPIConfig, mongodb = _this$$props.mongodb, endpoint = _this$$props.endpoint; var _ref = reactivesearchAPIConfig || {}, enableTelemetry = _ref.enableTelemetry; return _extends({}, !mongodb && _extends({ 'X-Search-Client': X_SEARCH_CLIENT }, enableTelemetry === false && { 'X-Enable-Telemetry': false }), headers, endpoint && endpoint.headers && _extends({}, endpoint.headers)); } }, methods: { updateState: function updateState(props) { this.setStore(props); this.key = this.state.key + "-0"; }, setStore: function setStore(props) { var credentials = props.url && props.url.trim() !== '' && !props.credentials ? null : props.credentials; var url = props.url && props.url.trim() !== '' ? props.url : ''; if (props.endpoint) { if (props.endpoint.url) { // eslint-disable-next-line prefer-destructuring url = props.endpoint.url; } else { throw Error('Error(ReactiveSearch): The `endpoint` prop object requires `url` property.'); } } var config = { url: url, app: props.app, credentials: credentials, type: props.type ? props.type : '*', transformRequest: props.transformRequest, transformResponse: props.transformResponse, enableAppbase: true, analytics: props.reactivesearchAPIConfig ? props.reactivesearchAPIConfig.recordAnalytics : props.analytics, analyticsConfig: props.reactivesearchAPIConfig, mongodb: props.mongodb, endpoint: props.endpoint, httpRequestTimeout: (props.httpRequestTimeout || 0) * 1000 || 30000 }; var queryParams = ''; if (typeof window !== 'undefined') { queryParams = window.location.search; } else { queryParams = props.queryParams || ''; } var params = new URLSearchParams(queryParams); var selectedValues = {}; var urlValues = {}; Array.from(params.keys()).forEach(function (key) { try { var _extends2, _extends3; var parsedParams = JSON.parse(params.get(key)); var selectedValue = {}; if (parsedParams.value) { selectedValue.value = parsedParams.value; } else { selectedValue.value = parsedParams; } if (parsedParams.category) selectedValue.category = parsedParams.category; selectedValue.reference = 'URL'; selectedValues = _extends({}, selectedValues, (_extends2 = {}, _extends2[key] = selectedValue, _extends2)); urlValues = _extends({}, urlValues, (_extends3 = {}, _extends3[key] = selectedValue.value, _extends3)); } catch (e) { // Do not add to selectedValues if JSON parsing fails. } }); var themePreset = props.themePreset, endpoint = props.endpoint; var appbaseRef = Appbase(config); appbaseRef.transformRequest = function (request) { var modifiedRequest = transformRequestUsingEndpoint(request, endpoint); if (props.transformRequest) return props.transformRequest(modifiedRequest); return modifiedRequest; }; if (this.$props.transformResponse) { appbaseRef.transformResponse = this.$props.transformResponse; } var analyticsInitConfig = { url: url && url.replace(/\/\/.*@/, '//'), credentials: appbaseRef.credentials, // When endpoint prop is used index is not defined, so we use _default index: appbaseRef.app || '_default', globalCustomEvents: this.$props.reactivesearchAPIConfig && this.$props.reactivesearchAPIConfig.customEvents }; try { if (this.$props.endpoint && this.$props.endpoint.url) { // Remove parts between '//' and first '/' in the url analyticsInitConfig.url = this.$props.endpoint.url.replace(/\/\/(.*?)\/.*/, '//$1'); var headerCredentials = this.$props.endpoint.headers && this.$props.endpoint.headers.Authorization; analyticsInitConfig.credentials = headerCredentials && headerCredentials.replace('Basic ', ''); // Decode the credentials analyticsInitConfig.credentials = analyticsInitConfig.credentials && atob(analyticsInitConfig.credentials); } } catch (e) { console.error('Endpoint not set correctly for analytics'); console.error(e); } var analyticsRef = null; if (config.analytics) { analyticsRef = AppbaseAnalytics.init(analyticsInitConfig); } var initialState = _extends({ config: _extends({}, config, { initialQueriesSyncTime: props.initialQueriesSyncTime, initialTimestamp: new Date().getTime(), mapKey: props.mapKey, themePreset: themePreset }), appbaseRef: appbaseRef, analyticsRef: analyticsRef, selectedValues: selectedValues, urlValues: urlValues, headers: this.getHeaders }, this.initialState); var storeFn = configureStore; if (configureStore["default"]) { storeFn = configureStore["default"]; } this.store = storeFn(initialState); // server side rendered app to collect context if (typeof window === 'undefined' && props.contextCollector) { var res = props.contextCollector({ ctx: this.store }); // necessary for supporting SSR of queryParams this.store.dispatch(setValues(res.selectedValues)); } this.analyticsRef = analyticsRef; } }, render: function render() { var _this = this; var _slot; var children = this.$slots["default"]; var _this$$props2 = this.$props, style = _this$$props2.style, className = _this$$props2.className; return createVNode(Provider, { "store": this.store, "analyticsRef": this.analyticsRef }, { "default": function _default() { return [createVNode(URLParamsProvider$1, { "as": _this.$props.as, "headers": _this.getHeaders, "style": style, "className": className, "getSearchParams": _this.getSearchParams, "setSearchParams": _this.setSearchParams, "userThemeProp": _this.$props.theme }, _isSlot$2(_slot = children()) ? _slot : { "default": function _default() { return [_slot]; } })]; } }); } }; ReactiveBase.install = function (Vue) { Vue.component(ReactiveBase.name, ReactiveBase); }; export default ReactiveBase;