SearchForm: fix inconsistent/mixed search results
- SearchForm.vue: route Enter key through submit() (native form submit interception) so it behaves like the button; fix autocomplete array mutation during iteration in the typed watcher. - Base.vue: parse terms consistently in beforeRouteUpdate; render items via displayedItems (drops sparse holes); only mount infinite-loading after the first page and reset it per search via :identifier. - search store: guard against stale async responses (searchId); place each page at its offset instead of concatenating in arrival order, which mixed pages when a later page resolved first.
This commit is contained in:
@@ -25,6 +25,9 @@ export default {
|
||||
infos: null,
|
||||
count: 0,
|
||||
noresults: false,
|
||||
// Incremented on every new search. Async responses (MA + GraphQL) carrying an
|
||||
// outdated id are discarded to avoid mixing results from concurrent searches.
|
||||
searchId: 0,
|
||||
// infinteState will come from vue-infinite-loading plugin
|
||||
// implemented in vuejs/components/Content/Base.vue
|
||||
infiniteLoadingState: null
|
||||
@@ -35,23 +38,38 @@ export default {
|
||||
|
||||
// mutations
|
||||
mutations: {
|
||||
setUuids (state, uuids) {
|
||||
state.uuids = state.uuids.concat(uuids)
|
||||
setUuids (state, { uuids, offset }) {
|
||||
// Place the page at its offset position instead of concatenating in
|
||||
// response-arrival order. With infinite-loading firing several pages
|
||||
// concurrently, concat mixed pages when a later page resolved first.
|
||||
if (!uuids) {
|
||||
return
|
||||
}
|
||||
const newUuids = state.uuids.slice()
|
||||
uuids.forEach((uuid, i) => {
|
||||
newUuids[offset + i] = uuid
|
||||
})
|
||||
state.uuids = newUuids
|
||||
},
|
||||
resetUuids (state) {
|
||||
state.uuids = []
|
||||
},
|
||||
setResults (state, items) {
|
||||
// console.log('setResults, items', items)
|
||||
if (items) { // avoid bug like items = [null]
|
||||
state.items = state.items.concat(items)
|
||||
setResults (state, { items, offset }) {
|
||||
// Same as setUuids: position each page by its offset so the display order
|
||||
// always matches the backend relevance order, whatever the network race.
|
||||
if (!items) { // avoid bug like items = [null]
|
||||
return
|
||||
}
|
||||
// console.log('setResults, state.items', state.items)
|
||||
const newItems = state.items.slice()
|
||||
items.forEach((item, i) => {
|
||||
newItems[offset + i] = item
|
||||
})
|
||||
state.items = newItems
|
||||
},
|
||||
updateItem (state, item) {
|
||||
// state.items[data.index] = data.item // this is not triggering vuejs refresh
|
||||
// find the right index
|
||||
const index = state.items.findIndex(i => i.id === item.id)
|
||||
const index = state.items.findIndex(i => i && i.id === item.id)
|
||||
// update the array
|
||||
if (index !== -1) {
|
||||
Vue.set(state.items, index, item)
|
||||
@@ -107,6 +125,9 @@ export default {
|
||||
},
|
||||
setInfiniteState (state, infiniteLoadingstate) {
|
||||
state.infiniteLoadingState = infiniteLoadingstate
|
||||
},
|
||||
incrementSearchId (state) {
|
||||
state.searchId++
|
||||
}
|
||||
},
|
||||
|
||||
@@ -120,6 +141,8 @@ export default {
|
||||
commit('resetNoresults')
|
||||
commit('resetOffset')
|
||||
commit('resetInfos')
|
||||
// Invalidate any in-flight response from a previous search.
|
||||
commit('incrementSearchId')
|
||||
if (state.keys || state.terms.length) {
|
||||
this.commit('Common/setPagetitle', state.keys.join(', '))
|
||||
} else {
|
||||
@@ -134,10 +157,15 @@ export default {
|
||||
dispatch('getResults')
|
||||
},
|
||||
getResults ({ dispatch, commit, state }) {
|
||||
// Capture the search token; a newSearch bumps it and makes this run stale.
|
||||
const searchId = state.searchId
|
||||
// Capture the offset of THIS request so the response is placed correctly
|
||||
// even if a later page (higher offset) resolves before it.
|
||||
const offset = state.offset
|
||||
const params = {
|
||||
keys: state.keys.join(', '),
|
||||
terms: JSON.stringify(state.terms),
|
||||
offset: state.offset,
|
||||
offset: offset,
|
||||
limit: state.limit
|
||||
}
|
||||
console.log('search store getResults, params', params)
|
||||
@@ -153,11 +181,15 @@ export default {
|
||||
return MA.get('/materio_sapi/getresults?' + q)
|
||||
.then(({ data }) => {
|
||||
console.log('search MA getresults data', data, state.terms)
|
||||
// Discard the response if another search was started meanwhile.
|
||||
if (searchId !== state.searchId) {
|
||||
return
|
||||
}
|
||||
// commit('setItems', data.items)
|
||||
commit('setInfos', data.infos)
|
||||
commit('setCount', data.count)
|
||||
if (data.count) {
|
||||
commit('setUuids', data.uuids)
|
||||
commit('setUuids', { uuids: data.uuids, offset })
|
||||
// loadMaterials is on mixins
|
||||
// https://github.com/huybuidac/vuex-extensions
|
||||
// dispatch('loadMaterialsGQL', {
|
||||
@@ -175,7 +207,11 @@ export default {
|
||||
MGQ.post('', { query: print(ast) })
|
||||
.then(resp => {
|
||||
console.log('loadSearchResultsGQL resp', resp)
|
||||
dispatch('loadSearchResultsCallBack', { items: resp.data.data.searchresults })
|
||||
// Discard if a newer search was started while GraphQL was in flight.
|
||||
if (searchId !== state.searchId) {
|
||||
return
|
||||
}
|
||||
dispatch('loadSearchResultsCallBack', { items: resp.data.data.searchresults, offset })
|
||||
})
|
||||
.catch(error => {
|
||||
console.warn('Issue with loadSearchResults', error)
|
||||
@@ -191,9 +227,9 @@ export default {
|
||||
Promise.reject(error)
|
||||
})
|
||||
},
|
||||
loadSearchResultsCallBack ({ commit, state }, { items, callBackArgs }) {
|
||||
loadSearchResultsCallBack ({ commit, state }, { items, offset, callBackArgs }) {
|
||||
console.log('search loadSearchResultsCallBack', items)
|
||||
commit('setResults', items)
|
||||
commit('setResults', { items, offset })
|
||||
if (state.infiniteLoadingState) {
|
||||
if (state.offset + state.limit > state.count) {
|
||||
console.log('Search infinite completed')
|
||||
|
||||
Reference in New Issue
Block a user