skip to Main Content

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


  1. You need to do some minor updation inside your displayName function

    I have updated your function, you can try from this below code:

    function displayName(itemElement) {
      const _primaryName = primaryName(itemElement);
      const _yearPublished = yearPublished(itemElement);
    
      if(_yearPublished !== null) {
    
        const names = itemElement.getElementsByTagName("name");
        for (let i = 0; i < names.length; i++) {
    
          const nameValue = names[i].getAttribute("value");
    
          const year = nameValue.match(/((d{4}))$/);
          if (year) {
            _yearPublished = year[1];
    
            break;
          }
        }
    
        return `${_primaryName} (${_yearPublished})`;
      } else {
        return _primaryName;
      }
    }
    Login or Signup to reply.
  2. 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 all item 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 set value to the game’s unique id

    this.items = localItems.map(item => {
      return { text: displayName(item), value: bggId(item), raw: item };
    })
    

    updated 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)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search