@appbaseio/reactivesearch-vue
Version:
A Vue UI components library for building search experiences
593 lines (582 loc) • 22.1 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var reactivecore = require('@appbaseio/reactivecore');
require('@appbaseio/reactivecore/lib/utils/constants');
var _rollupPluginBabelHelpers = require('./_rollupPluginBabelHelpers-1a877b17.js');
var vue = require('vue');
var VueTypes = _interopDefault(require('vue-types'));
var helper = require('@appbaseio/reactivecore/lib/utils/helper');
var vueEmotion = require('@appbaseio/vue-emotion');
var vueTypes = require('./vueTypes-adf43075.js');
require('redux');
var index = require('./index-7ca9570e.js');
var configureStore = require('@appbaseio/reactivecore/lib');
var configureStore__default = _interopDefault(configureStore);
var analytics = require('@appbaseio/reactivecore/lib/actions/analytics');
var Appbase = _interopDefault(require('appbase-js'));
var AppbaseAnalytics = _interopDefault(require('@appbaseio/analytics'));
require('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 vue.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]' && !vue.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 vue.createVNode(T, props, _isSlot(children) ? children : {
"default": function _default() {
return [children];
}
});
};
var Base$1 = vueEmotion.styled(Base)(_templateObject || (_templateObject = _rollupPluginBabelHelpers._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]' && !vue.isVNode(s);
}
var setHeaders = reactivecore.Actions.setHeaders,
setValue = reactivecore.Actions.setValue;
var isEqual = reactivecore.helper.isEqual;
var URLParamsProvider = {
name: 'URLParamsProvider',
props: {
className: vueTypes.types.string,
headers: vueTypes.types.headers,
getSearchParams: vueTypes.types.func,
setSearchParams: vueTypes.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 vue.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 = index.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]' && !vue.isVNode(s);
}
var setValues = configureStore.Actions.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: vueTypes.types.string,
analytics: VueTypes.bool,
reactivesearchAPIConfig: vueTypes.types.reactivesearchAPIConfig,
credentials: vueTypes.types.string,
headers: vueTypes.types.headers,
queryParams: vueTypes.types.string,
theme: VueTypes.oneOf([VueTypes.bool, VueTypes.object.def({})]),
themePreset: VueTypes.string.def('light'),
type: vueTypes.types.string,
url: vueTypes.types.string,
mapKey: vueTypes.types.string,
initialQueriesSyncTime: vueTypes.types.number,
className: vueTypes.types.string,
initialState: VueTypes.object.def({}),
contextCollector: VueTypes.func,
transformRequest: vueTypes.types.func,
transformResponse: vueTypes.types.func,
as: VueTypes.string.def('div'),
getSearchParams: vueTypes.types.func,
setSearchParams: vueTypes.types.func,
mongodb: vueTypes.types.mongodb,
endpoint: vueTypes.types.endpointConfig,
preferences: VueTypes.object,
httpRequestTimeout: VueTypes.number.def(30)
},
provide: function provide() {
var createCacheFn = vueEmotion.createCache;
if (vueEmotion.createCache["default"]) {
createCacheFn = vueEmotion.createCache["default"];
}
return {
theme_reactivesearch: index.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 (!helper.isEqual(newVal, oldVal)) {
if (this.store) {
this.store.dispatch(analytics.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 _rollupPluginBabelHelpers._extends({}, !mongodb && _rollupPluginBabelHelpers._extends({
'X-Search-Client': index.X_SEARCH_CLIENT
}, enableTelemetry === false && {
'X-Enable-Telemetry': false
}), headers, endpoint && endpoint.headers && _rollupPluginBabelHelpers._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 = _rollupPluginBabelHelpers._extends({}, selectedValues, (_extends2 = {}, _extends2[key] = selectedValue, _extends2));
urlValues = _rollupPluginBabelHelpers._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 = helper.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 = _rollupPluginBabelHelpers._extends({
config: _rollupPluginBabelHelpers._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__default;
if (configureStore__default["default"]) {
storeFn = configureStore__default["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 vue.createVNode(Provider, {
"store": this.store,
"analyticsRef": this.analyticsRef
}, {
"default": function _default() {
return [vue.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);
};
exports.default = ReactiveBase;