@mezereon/ui-shopify
Version:
Shopify UI components for Mezereon Xperience
551 lines (528 loc) • 14.7 kB
JavaScript
/* global mz */
/* eslint-disable eqeqeq */
import { isArray, isEmpty, toRelativeUrl, pathCombine } from '@/helpers'
export const namespaced = true
export const state = () => ({
hits: [],
queryId: '',
segments: [],
keyword: '',
pageId: 0,
aggregations: [],
pagination: {},
selections: [],
filters: [],
sorting: [],
paging: [],
banners: [],
autocompleteBanners: [],
layout: null,
toggleLayout: false,
sort: undefined,
pageSize: undefined,
page: 1,
overlay: false,
primaryKey: null,
events: [],
complete: '',
suggestResult: {},
suggestItems: {},
currentSuggestion: null,
context: {},
gridItem: '',
listItem: '',
css: '',
config: {},
queryParam: 'q',
translation: undefined,
filterLayout: 'vertical',
redirect: undefined,
infiniteLoad: false
})
export const mutations = {
LOAD_BANNERS(state, data) {
state.banners = data
},
LOAD_CONFIG(state, data) {
state.config = data.autocomplete
state.translation = data.translation
if (data.gridItem) {
state.gridItem = data.gridItem
}
if (data.listItem) {
state.listItem = data.listItem
}
},
RUN_AUTOCOMPLETE(state, payload) {
if (payload.includeSuggestResult) {
state.suggestResult = payload.data.suggestResult
state.currentSuggestion = payload.data.suggestion
state.complete = payload.data.complete
}
if (
state.context &&
state.context.root &&
payload.data.searchResult &&
payload.data.searchResult.items
) {
payload.data.searchResult.items.forEach((single) => {
if (single.inner && single.inner.url) {
single.inner.url = pathCombine(
state.context.root,
toRelativeUrl(single.inner.url)
)
}
if (single.item && single.item.url) {
single.item.url = pathCombine(
state.context.root,
toRelativeUrl(single.item.url)
)
}
})
}
state.suggestItems = payload.data.searchResult
state.primaryKey = payload.primaryKey
state.redirect = payload.data.searchResult.redirect
state.autocompleteBanners = payload.data.searchResult.banners
},
RUN_QUERY(state, data) {
const pagingType = data.pagination.pagingType
if (state.context && state.context.root && data.hits && data.hits.items) {
data.hits.items.forEach((single) => {
if (single.inner && single.inner.url) {
single.inner.url = pathCombine(
state.context.root,
toRelativeUrl(single.inner.url)
)
}
if (single.item && single.item.url) {
single.item.url = pathCombine(
state.context.root,
toRelativeUrl(single.item.url)
)
}
})
}
if (data.pagination.current_page == 1) {
state.hits = data.hits
} else {
if (pagingType === 'more' || pagingType === 'infinite') {
state.hits.items.push(...data.hits.items)
} else {
state.hits = data.hits
}
}
state.queryId = data.queryId
state.segments = data.segments
state.primaryKey = data.primaryKey
// add .state and .open properties
data.aggregations.forEach((agg) => {
agg.state = {}
agg.open = {}
})
// flatten tree aggregations to simplify value lookup
const flat = {}
const flatten = (name, item) => {
flat[name] = flat[name] || {}
flat[name][item.key] = item
if (item.children) {
item.children.forEach((child) => flatten(name, child))
}
}
data.aggregations
.filter((y) => y.control === 'tree')
.forEach((tree) => tree.items.forEach((item) => flatten(tree.name, item)))
// helper function to get updated selection
const getSelection = (agg, x, v) => {
const item =
agg.control === 'tree'
? flat[agg.name][v]
: agg.items.find((a) => a.key == v)
const value = item ? item.value : v
return {
...x,
action: 'add',
key: v,
label: agg.label,
value
}
}
// replace initial selections with new objects that contain updated action, label, key and value
state.selections
.filter((x) => x.action === 'init')
.forEach((x) => {
const agg = data.aggregations.find((y) => y.name === x.name)
if (!agg) {
return
}
if (agg.control === 'slider') {
state.selections.push({
...x,
action: 'add',
label: agg.label
})
} else if (isArray(x.value)) {
x.value.forEach((v) => state.selections.push(getSelection(agg, x, v)))
} else {
state.selections.push(getSelection(agg, x, x.value))
}
})
state.selections = state.selections.filter((x) => x.action !== 'init')
state.aggregations = data.aggregations
state.pagination = data.pagination
if (data.banners) {
state.banners = data.banners
}
if (data.paging) {
state.paging = data.paging
}
if (data.sorting) {
state.sorting = data.sorting
}
if (data.grid) {
state.gridItem = data.grid
}
if (data.list) {
state.listItem = data.list
}
if (data.css) {
state.css = data.css
}
if (data.filterLayout) {
state.filterLayout = data.filterLayout
}
if (data.redirect) {
state.redirect = data.redirect
}
},
SET_LAYOUT(state, data) {
state.layout = data
},
SET_SORT(state, data) {
state.sort = data
},
SET_PAGE_SIZE(state, data) {
state.pageSize = data
},
SET_PAGE(state, data) {
state.page = data
},
SET_OVERLAY(state, data) {
state.overlay = data
},
SET_KEYWORD(state, data) {
state.keyword = data
},
SET_TOGGLE_LAYOUT(state, data) {
state.toggleLayout = data
},
SET_PAGE_ID(state, data) {
state.pageId = data
},
SET_INFINITE_LOAD(state, data) {
state.infiniteLoad = data
},
SET_COMPLETE(state, data) {
state.complete = data
},
SET_CURRENT_SUGGESTION(state, data) {
state.currentSuggestion = data
},
CLEAR_AUTOCOMPLETE(state) {
state.complete = ''
state.currentSuggestion = ''
state.suggestItems = {}
state.suggestResult = {}
},
SET_STATE(state, data) {
state.page = data.page
state.sort = data.sort
state.pageSize = data.pageSize
state.keyword = data.keyword
state.filters = data.filters
state.selections = data.selections
},
SET_QUERY_PARAM(state, data) {
state.queryParam = data
},
SET_CONTEXT(state, data) {
state.context = data
},
UPDATE_AGGREGATION(state, agg) {
const aggregation = state.aggregations.find((x) => x.name === agg.name)
if (aggregation) {
aggregation.selected = agg.value
}
},
UPDATE_SELECTIONS(state, last) {
switch (last.action) {
case 'remove-all':
state.selections = state.selections.filter((x) => x.name !== last.name)
if (state.filters && state.filters[last.name]) {
delete state.filters[last.name]
}
break
case 'replace':
state.selections = state.selections.filter((x) => x.name !== last.name)
state.selections.push(last)
if (last.key == '_Keyword') {
break
}
state.filters = state.filters || {}
if (last.value && isArray(last.value)) {
state.filters[last.name] = last.value
} else {
state.filters[last.name] = [last.key]
}
break
case 'add':
state.selections.push(last)
if (last.key == '_Keyword') {
break
}
state.filters = state.filters || {}
state.filters[last.name] = state.filters[last.name] || []
if (last.value && isArray(last.value)) {
state.filters[last.name] = last.value
} else {
state.filters[last.name].push(last.key)
}
break
default:
state.selections = state.selections.filter(
(x) => x.name !== last.name || x.key !== last.key
)
if (state.filters && state.filters[last.name]) {
if (last.value == null) {
delete state.filters[last.name]
} else {
state.filters[last.name] = state.filters[last.name].filter(
(x) => x !== last.key
)
if (state.filters[last.name].length === 0) {
delete state.filters[last.name]
}
}
}
break
}
},
CLEAR_ONE_SELECTION(state, toClear) {
state.selections = state.selections.filter(
(x) => x.name !== toClear.name || x.key !== toClear.key
)
if (state.filters && state.filters[toClear.name]) {
if (toClear.value && isArray(toClear.value)) {
delete state.filters[toClear.name]
} else {
state.filters[toClear.name] = state.filters[toClear.name].filter(
(x) => x !== toClear.key
)
if (state.filters[toClear.name].length === 0) {
delete state.filters[toClear.name]
}
}
}
// special case for keyword selection
if (toClear.key === '_Keyword') {
state.keyword = ''
// clear autocomplete
state.complete = ''
state.currentSuggestion = ''
state.suggestItems = {}
state.suggestResult = {}
}
const aggregation = state.aggregations.find((x) => x.name === toClear.name)
if (aggregation) {
if (aggregation.control === 'slider') {
aggregation.selected = []
} else {
aggregation.selected = aggregation.selected.filter(
(x) => x !== toClear.key
)
}
}
},
CLEAR_ALL_SELECTIONS(state, resetKeyword) {
if (resetKeyword) {
state.keyword = ''
}
state.selections = []
state.aggregations.forEach((agg) => {
agg.selected = []
})
state.filters = undefined
},
TRACK_EVENT(state, payload) {
state.events.push(payload)
},
CLEAR_EVENTS(state) {
state.events = []
}
}
export const actions = {
setLayout({ commit }, payload) {
commit('SET_LAYOUT', payload)
},
setSort({ commit }, payload) {
commit('SET_SORT', payload)
},
setPageSize({ commit }, payload) {
commit('SET_PAGE_SIZE', payload)
},
setPage({ commit }, payload) {
commit('SET_PAGE', payload)
},
setOverlay({ commit }, payload) {
commit('SET_OVERLAY', payload)
},
setKeyword({ commit }, payload) {
commit('SET_KEYWORD', payload)
},
setToggleLayout({ commit }, payload) {
commit('SET_TOGGLE_LAYOUT', payload)
},
setComplete({ commit }, payload) {
commit('SET_COMPLETE', payload)
},
setCurrentSuggestion({ commit }, payload) {
commit('SET_CURRENT_SUGGESTION', payload)
},
clearAutocomplete({ commit }, payload) {
commit('CLEAR_AUTOCOMPLETE', payload)
},
setPageId({ commit }, payload) {
commit('SET_PAGE_ID', payload)
},
setState({ commit }, payload) {
commit('SET_STATE', payload)
},
setQueryParam({ commit }, payload) {
commit('SET_QUERY_PARAM', payload)
},
setContext({ commit }, payload) {
commit('SET_CONTEXT', payload)
},
updateAggregation({ commit }, payload) {
commit('UPDATE_AGGREGATION', payload)
},
updateSelections({ commit }, payload) {
commit('UPDATE_SELECTIONS', payload)
},
clearOneSelection({ commit }, payload) {
commit('CLEAR_ONE_SELECTION', payload)
},
clearAllSelections({ commit }, resetKeyword) {
commit('CLEAR_ALL_SELECTIONS', resetKeyword)
},
trackEvent({ commit }, payload) {
commit('TRACK_EVENT', payload)
},
getEvents({ commit, state }) {
const events = state.events
commit('CLEAR_EVENTS')
return events
},
async loadConfig({ commit }, payload) {
if (mz && mz.autocomplete && mz.translation) {
commit('LOAD_CONFIG', {
autocomplete: mz.autocomplete,
translation: mz.translation,
gridItem: mz.gridItem,
listItem: mz.listItem
})
}
},
async runAutocomplete({ commit }, payload) {
const { data } = await this.$axios.post('/search/autocomplete/', payload)
commit('RUN_AUTOCOMPLETE', {
includeSuggestResult: payload.autocompleteConfig.includeSuggestResult,
primaryKey: data.searchResult.primaryKey,
data: data
})
},
async runQuery({ commit, state }, payload) {
commit('SET_PAGE_ID', payload.query.pageId || 0)
commit('SET_INFINITE_LOAD', true)
const res = await this.$axios.post('/search/query', payload)
commit('SET_INFINITE_LOAD', false)
// set default pagination
for (const paging of res.data.paging) {
if (paging.default && !state.pageSize) {
commit('SET_PAGE_SIZE', paging.value)
break
}
}
// set default sorting
for (const sorting of res.data.sorting) {
if (sorting.default && !state.sort) {
commit('SET_SORT', sorting.key)
break
}
}
const skip = (payload.query.page - 1) * state.pageSize
const total = res.data.total || 0
const pagination = {
pagingType: res.data.pagingType || 'standard',
current_page: payload.query.page,
per_page: state.pageSize,
total: total,
from: skip + 1,
to: Math.min(skip + Math.min(state.pageSize, total), total),
last_page: Math.ceil(total / state.pageSize)
}
const data = {
hits: res.data,
primaryKey: res.data.primaryKey,
queryId: res.data.queryId,
segments: res.data.segments,
pagination: pagination,
aggregations: res.data.aggregations,
banners: res.data.banners,
paging: res.data.paging,
sorting: res.data.sorting,
grid: res.data.grid,
list: res.data.list,
css: res.data.css,
filterLayout: res.data.filterLayout,
redirect: res.data.redirect
}
commit('RUN_QUERY', data)
}
}
export const getters = {
getFacetBanners: (state) => {
const banners = { ...state.banners }
for (const banner in banners) {
if (!banner.startsWith('facet')) {
delete banners[banner]
}
if (isEmpty(banners[banner])) {
delete banners[banner]
}
}
return banners
},
getBannerByZone: (state) => (zone) => {
return state.banners[zone]
},
getAutocompleteBannerByZone: (state) => (zone) => {
return state.autocompleteBanners[zone]
},
getQuery: (state) => () => {
return {
page: state.page,
sort: state.sort,
pageSize: state.pageSize,
keyword: state.keyword,
filters: state.filters
}
}
}
export const search = {
namespaced,
state,
getters,
mutations,
actions
}