@mezereon/ui-shopify
Version:
Shopify UI components for Mezereon Search & Filter
394 lines (387 loc) • 10.8 kB
JavaScript
/* global mz */
import { debounce, indexOf } from 'lodash'
import { mapState, mapGetters } from 'vuex'
import {
getKeyFromComplex,
getValueFromComplex,
isAbsoluteUrl,
pathCombine
} from '@/helpers'
import Fa from 'vue-fa'
import {
faSearch,
faBars,
faTimes,
faThLarge
} from '@fortawesome/free-solid-svg-icons'
export default {
components: {
Fa
},
props: {
searchUrl: {
type: String,
required: false,
default: undefined
},
queryParam: {
type: String,
required: false,
default: 'q'
},
placement: {
type: String,
required: false,
default: undefined
},
delay: {
type: Number,
required: false,
default: 0
},
queryOnHover: {
type: Boolean,
required: false,
default: true
}
},
data() {
return {
search: '',
internalView: false,
autocomplete: false,
faSearch,
faBars,
faTimes,
faThLarge
}
},
computed: {
...mapGetters('history', ['recentSearches']),
...mapState('search', [
'suggestHidden',
'hits',
'primaryKey',
'pagination',
'pageSize',
'keyword',
'queryId',
'complete',
'suggestResult',
'suggestItems',
'currentSuggestion',
'config',
'translation',
'redirect'
]),
resultsCount() {
const topItems = this.config.mainAreaSections.find(
(x) => x.modal === 'modal-top-items'
)
if (!topItems || !topItems.config) {
return 10
}
return topItems.config.count || 10
},
suggestRecent() {
if (!this.recentSearches) {
return []
}
const count = this.config.recentCount || 5
return this.recentSearches(count)
}
},
methods: {
isHidden(hit) {
if (!this.suggestHidden || !hit.item || !hit.item[this.primaryKey]) {
return false
}
return (
indexOf(this.suggestHidden, hit.item[this.primaryKey].toString()) !== -1
)
},
addSearch(payload) {
this.$store.dispatch('history/addSearch', payload, { root: true })
},
deleteSearch(payload) {
this.$store.dispatch('history/deleteSearch', payload, { root: true })
},
trackEvent(event) {
const final = {
...event,
context: {
visitId: this.$cookies ? this.$cookies.get('mz_visitId') : '',
visitorId: this.$cookies ? this.$cookies.get('mz_visitorId') : ''
}
}
if (mz && mz.track) {
mz.track('autocomplete', final)
}
},
onItemClick(event) {
const final = {
...event,
keyword: this.query(),
complete: this.complete,
suggestion: this.currentSuggestion
}
this.trackEvent(final)
},
changeView(view) {
this.view = view
this.$emit('change-autocomplete-view')
},
keyHandler(event) {
if (event.key === 'Escape') {
this.autocomplete = false
return
}
// arrow down
if (event.key === 'ArrowDown') {
const nextElement = event.target.nextElementSibling
if (nextElement) {
nextElement.focus()
}
event.preventDefault()
}
// arrow up
if (event.key === 'ArrowUp') {
const previousElement = event.target.previousElementSibling
if (previousElement) {
previousElement.focus()
}
event.preventDefault()
}
},
setCurrentSuggestion(sug) {
if (sug !== this.currentSuggestion) {
this.$store.dispatch('search/setCurrentSuggestion', sug, { root: true })
this.getAutocomplete(
this,
this.currentSuggestion,
true,
this.resultsCount
)
}
},
emitChange(value) {
this.$emit('change', value)
this.search = value
},
clearKeyword() {
this.emitChange('')
this.$store.dispatch('search/setComplete', '', { root: true })
this.$store.dispatch('search/setCurrentSuggestion', '', { root: true })
this.$store.dispatch('search/clearAutocomplete', '', { root: true })
this.doSearch(false)
},
processClick(event, keyword) {
this.trackEvent(event)
this.addSearch(event)
this.$store.dispatch('search/setComplete', '', { root: true })
this.$store.dispatch('search/setCurrentSuggestion', '', { root: true })
this.emitChange(keyword)
this.doSearch(true)
},
clickRecent(keyword) {
// clear autocomplete to run autocomplete query on focus
this.$store.dispatch('search/clearAutocomplete', '', { root: true })
// track suggestion click event
const event = {
type: 'recent',
keyword: '',
complete: '',
suggestion: keyword
}
this.processClick(event, keyword)
},
clickSearch(keyword) {
if (!this.queryOnHover) {
this.setCurrentSuggestion(keyword)
return
}
// track suggestion click event
const event = {
type: 'suggestion',
keyword: this.query(),
complete: '',
suggestion: keyword
}
this.processClick(event, keyword)
},
clickFacet(facet, original) {
this.trackEvent({
type: 'facet',
keyword: this.query(),
complete: this.complete,
suggestion: this.currentSuggestion,
name: facet.name,
value: original
})
const key = getKeyFromComplex(original)
const value = getValueFromComplex(original)
if (this.searchUrl) {
if (window.location.pathname !== this.searchUrl) {
window.location = `${this.searchUrl}?mz.${facet.name}=${key}`
return
}
}
const last = {
name: facet.name,
label: facet.label,
key: key,
value: value,
action: 'add'
}
this.emitChange('')
this.$store.dispatch('search/setComplete', '', { root: true })
this.$store.dispatch('search/setCurrentSuggestion', '', { root: true })
this.$store.dispatch('search/clearAutocomplete', '', { root: true })
this.$store.dispatch('search/clearAllSelections', {}, { root: true })
this.$store.dispatch('search/updateSelections', last, { root: true })
this.$store.dispatch('search/setPage', 1, { root: true })
// close autocomplete
this.autocomplete = false
// run query
if (this.$bus) this.$bus.emit('run-query')
},
doEnter() {
// track complete event
if (this.complete && this.complete !== this.query()) {
const event = {
type: 'complete',
complete: this.complete,
keyword: this.query(),
suggestion: this.currentSuggestion
}
this.trackEvent(event)
this.addSearch(event)
this.doSearch(true)
return
}
// track suggest event
if (this.currentSuggestion && this.currentSuggestion !== this.query()) {
const event = {
type: 'suggestion',
complete: this.complete,
keyword: this.query(),
suggestion: this.currentSuggestion
}
this.trackEvent(event)
this.addSearch(event)
this.doSearch(true)
return
}
const event = {
type: 'search',
complete: this.complete,
keyword: this.query(),
suggestion: this.currentSuggestion
}
this.addSearch(event)
this.doSearch(true)
},
doSearch(clearSelections) {
this.autocomplete = false
if (this.redirect) {
let redirect = this.redirect
if (isAbsoluteUrl(redirect)) {
window.location.replace(redirect)
return
}
if (mz && mz.context && mz.context.root) {
redirect = pathCombine(mz.context.root, redirect)
}
window.location.replace(redirect)
return
}
this.$emit(
'change',
this.complete || this.currentSuggestion || this.query()
)
if (this.searchUrl) {
if (
this.input.form &&
this.input.form.action.includes(this.searchUrl)
) {
this.input.form.submit()
return
}
if (window.location.pathname !== this.searchUrl) {
window.location = `${this.searchUrl}?${
this.queryParam
}=${encodeURIComponent(this.query())}`
return
}
}
const last = {
name: 'Keyword',
label: this.$t('Keyword'),
key: '_Keyword',
value: this.query(),
action: this.query() === '' ? 'remove-all' : 'replace'
}
if (clearSelections) {
this.$store.dispatch('search/clearAllSelections', {}, { root: true })
}
this.$store.dispatch('search/updateSelections', last, { root: true })
this.$store.dispatch('search/setKeyword', this.query(), { root: true })
this.$store.dispatch('search/setPage', 1, { root: true })
if (this.$bus) this.$bus.emit('run-query')
},
doSuggest(event) {
if (event.key === 'Escape' || event.key === 'Enter') {
this.autocomplete = false
}
if (event.key === 'Enter') {
if (this.complete || this.currentSuggestion) {
this.emitChange(this.complete || this.currentSuggestion)
this.$store.dispatch('search/setComplete', '', {
root: true
})
}
}
},
getAutocomplete: debounce((vm, keyword, skipSuggestions, resultsCount) => {
vm.runAutocomplete(keyword, skipSuggestions, resultsCount)
}, 250),
runAutocomplete(keyword, skipSuggestions, resultsCount) {
if (this.$bus) {
this.$bus.emit(
'run-autocomplete',
keyword,
skipSuggestions,
resultsCount,
this.includeBanners
)
}
},
changeSuggest() {
this.$store.dispatch('search/setComplete', '', { root: true })
this.$store.dispatch('search/setCurrentSuggestion', '', { root: true })
this.autocomplete = true
this.getAutocomplete(this, this.query(), false, this.resultsCount)
},
keyDown() {
if (event.key === 'Escape') {
this.autocomplete = false
return
}
if (
(event.key === 'Tab' && event.shiftKey === false) ||
event.key === 'ArrowDown' ||
event.key === 'ArrowUp'
) {
let active = ''
// find current suggestion and set focus
active = document.querySelector('.mz-suggest-item.mz-active')
// set focus on first suggestion
active = active || document.querySelector('.mz-suggest-item')
if (active) {
active.focus()
event.preventDefault()
}
}
}
}
}