vue-typeahead icon indicating copy to clipboard operation
vue-typeahead copied to clipboard

Update method firing when input field blurs

Open bencarter78 opened this issue 7 years ago • 1 comments

Hi there

First off, thanks for making a great package! 👍

I'm doing a Vue 1 -> Vue 2 upgrade and I'm coming across an issue where by when I search for a resource, I can select it from the dropdown that appears, it populates the input field but when I exit the field it makes another request and the dropdown appears again.

It seems that it the update method is being fired when I leave the input field. Any ideas what I could be doing wrong?

<template>
    <div>
        <input :name="fieldName + '_id'" type="hidden" v-bind:value="fieldNameId">

        <input :id="fieldName"
               :name="fieldName"
               type="text"
               class="form-control"
               placeholder="Search..."
               autocomplete="off"
               v-model="query"
               @keydown.down="down"
               @keydown.up="up"
               @keydown.enter.prevent
               @keydown.enter="hit"
               @keydown.esc="reset"
               @input="update"/>

        <div id="autocomplete-results" v-if="hasItems">
            <ul class="list-group results">
                <li class="list-group-item results-item" v-for="(item, index) in items" :class="activeClass(index)" @mousedown="hit" @mousemove="setActive(index)">
                   {{ item.display }}
                </li>
            </ul>
        </div>
    </div>
</template>

<script>
    import VueTypeahead from 'vue-typeahead'

    export default {
        extends: VueTypeahead,

        props: ['fieldName', 'value', 'endpoint', 'attributes', 'relationships'],

        data () {
            return {
                query: '',
                src: this.endpoint,
                limit: 5,
                minChars: 3,
                queryParamName: 'q',
                fieldNameId: ''
            }
        },

        mounted () {
            this.query = this.value
        },

        methods: {
            prepareResponseData (items) {
                for (let i = 0; i < items.length; i++) {
                    var fields = this.transformAttributes(items[i])
                    var relationships = this.transformRelationships(items[i])
                    items[i].display = fields + ' - ' + relationships
                }
                return items
            },

            transformAttributes (item) {
                let q = []
                for (let i = 0; i < this.attributes.length; i++) {
                    q.push(item[this.attributes[i]])
                }
                return q.join(' ')
            },

            transformRelationships (item) {
                let r = []
                for (let [key, value] of Object.entries(this.relationships)) {
                    if (this.relationships.hasOwnProperty(key)) {
                        for (let data of item[key]) {
                            r.push(data[value])
                        }
                    }
                }
                return r.join(', ')
            },

            onHit (item) {
                console.log(item)
                this.query = this.transformAttributes(item)
                this.fieldNameId = item.id
                this.value = this.query
                this.items = []
            },

            isValid () {
                if (this.value != this.query) {
                    this.fieldNameId = ''
                }
            }
        }
    }
</script>

bencarter78 avatar May 08 '17 09:05 bencarter78

@bencarter78 Scratched my head on this one for some time. Here's what worked:

export default {
  extends: VueTypeahead,
  data () {
    return {
      src: '/guided_search/keyword_search',
      limit: 5,
      minChars: 3,
      showItems: true // for some reason, directly manipulating the items array is buggy; i.e. items = [] is not reliable
    }
  },
  methods: {
    doUpdate(event) {
      this.showItems = true
      this.update()
    },
    onHit(item) {
      this.query = item
      $("input[name='disease']").val(this.query) // firefox needs this ¯\_(ツ)_/¯
      this.softReset()
    },
    softReset() {
      this.loading = false
      this.showItems = false
    }
  }
}

and in the template:

<input type="text"
   name="disease"
    :class="{'Typeahead__input': true}"
    placeholder="Example: Leukemia"
    autocomplete="off"
    v-model="query"
    v-validate="'required'"           
    @keydown.down="down"
    @keydown.up="up"
    @keydown.enter="hit"
    @keydown.esc="reset"
    @blur="softReset"
    @input="doUpdate"/>

<ul v-show="hasItems && showItems">
...
</ul>

In my case I wanted the query to stay in the input on blur, even if no results matched. Clearing out the items via items = [] was giving me mixed results. I would type 3 characters (the default to trigger the data to load) and according to the Chrome vue inspector, I could see the items var get populated with a few results. In softReset() I was calling items = []. Logging that in the console, items was empty. However, in the vue inspector it was still populated and the dropdown with options was still appearing. Not sure what's going on there; something with data and extends maybe? I ended up adding showItems to data as a workaround.

It would be nice to have a "selecting item populates the input box" as a core feature as this is a pretty common use case. Would be happy to submit a pull req, but my solution above is a bit hacky and I'm stumped why manipulating the items array isn't working.

I should also mention that this does not fix the update event from firing on blur - this could be the expected Vue behavior, but I'm not certain.

cgs avatar May 16 '17 20:05 cgs