Here is a codepen: https://codepen.io/spierepf/pen/ExdvXYY?editors=101
<div id="app">
<v-app id="inspire">
<v-card>
<v-container fluid>
<v-row
align="center"
>
<v-col cols="12">
<v-autocomplete
:items='items'
:search-input.sync='search'
v-model="selectedItem"
@change="$emit('itemSelected', selectedItem)"
></v-autocomplete>
</v-col>
<v-col cols="12">
</v-col>
<v-col cols="12">
</v-col>
</v-row>
</v-container>
</v-card>
</v-app>
</div>
function bggId(itemElement) {
return itemElement.getAttribute('id');
}
function primaryName(itemElement) {
var retval = null;
Array.prototype.slice.call(itemElement.getElementsByTagName("name")).forEach(nameElement => {
if(retval === null || nameElement.getAttribute('type') === 'primary') {
retval = nameElement.getAttribute('value');
}
})
return retval;
}
function yearPublished(itemElement) {
const yearpublishedElements = itemElement.getElementsByTagName("yearpublished");
return (yearpublishedElements.length > 0) ? yearpublishedElements[0].getAttribute("value") : null;
}
function displayName(itemElement) {
const _primaryName = primaryName(itemElement);
const _yearPublished = yearPublished(itemElement);
return _yearPublished === null ? _primaryName : `${_primaryName} (${_yearPublished})`
}
function createItemWithPrimaryName(primaryName) {
return new window.DOMParser().parseFromString("<item type='boardgame'><name type='primary' value='" + primaryName + "'/></item>", 'text/xml').documentElement
}
async function searchBgg(searchInput) {
return await fetch('https://boardgamegeek.com/xmlapi2/search?type=boardgame,boardgameaccessory,boardgameexpansion&query=' + encodeURIComponent(searchInput.replace(/s/g, '+')) + '')
.then(response => response.text())
.then(text => new window.DOMParser().parseFromString(text, 'text/xml'))
.then(xml => (Array.prototype.slice.call(xml.documentElement.getElementsByTagName('item'))))
}
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
search: '',
items: [],
selectedItem: null
}),
watch: {
async search(localSearch) {
if(this.search && this.search !== '') {
if(!this.selectedItem || this.search !== this.selectedItemDisplayName) {
console.log(localSearch) // eslint-disable-line no-console
const localItems = await searchBgg(localSearch).then(items => items.concat([createItemWithPrimaryName(localSearch)]))
if(this.search === localSearch) {
this.items = localItems.map(item => Object.fromEntries([['text', displayName(item)], ['value', item]]))
}
}
} else {
this.items = []
}
}
}
})
I am trying to develop an autocomplete component backed by BoardGameGeeks api.
Since a multiple items may have the same title, I’ve appended the year of publication, so ‘Crossbows and Catapults’ is either ‘Crossbows and Catapults (1983)’ or ‘Crossbows and Catapults (2022)’.
However, for some reason, if one types Crossbows and Catapults
into the field and clicks on Crossbows and Catapults (1983)
; the field will instead display Battleground: Crossbows & Catapults (2007)
2
Answers
You need to do some minor updation inside your displayName function
I have updated your function, you can try from this below code:
You’ll actually notice no matter what option you pick you always get the first option. That’s because v-autocomplete’s options
value
must be primitive, whereas yours options are allitem
objects. v-autocomplete can’t compare non-primitive values so when you select an option it just defaults to the first one.What I would do then when mapping your results into
this.items
is to setvalue
to the game’s unique idupdated codepen
By the way, I recommend a debounce function for your searching so you don’t trigger an API fetch every keystroke (I added one to the codepen)