Vuejs on blur for custom select element not workin

2019-08-20 16:29发布

Scenario

I've built custom autocomplete select component where it autocompletes but in addition, if the result is not found we can add new value.(I found it is not possible in vue-select module).

Codepen Link

Question

I tried using @blur but the menu gets closed if I click on the item. I've custom div as a list of items for selecting which is getting toggled if the focus is on text field. If I remove blur it works perfect but it doesn't get closed it I click outside of text field. I've copied functions for handling keyup,keydown,keyenter,scrolling,handling pointer for select from vue-select

CODE

    <template>
    <div class="vselect">
        <div class="form-group">
            <label>{{label}}</label>
            <p class="control has-icon has-icon-right">
                <input ref="selected" @keyup="filterSelect($event)" @focus="onFocus" v-model="mutableValue" type="text" class="form-control"
                    @keydown.up.prevent="onKeyUp" @keydown.down.prevent="onKeyDown" @keydown.enter.prevent="onKeyEnter" @blur="onBlur">
            </p>
        </div>
        <div ref="dropdown" class="my-dropdown" v-show="toggled" v-if="options">
            <div v-for="(item,index) in filterList" :class="{'my-dropdwon--item':true,active:index === pointer}" @click="handleItemClick(item)"
                @mouseover="pointer = index">{{item}}</div>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'VSelect',
        props: {
            'options': Array,
            'label': String,
            'value': String,
        },
        data() {
            return {
                selected: null,
                toggled: false,
                filterList: [],
                mutableValue: null,
                mutableOptions: [],
                pointer: 0
            }
        },
        methods: {
            filterSelect: function (key) {
                if (!this.toggled) this.toggled = !this.toggled;
                let oldArr = this.options;
                if (this.mutableValue && this.mutableValue.length <= 0)
                    this.filterList = this.mutableOptions;
                else if (key.key.length == 1 || key.key == 'Backspace') {
                    oldArr = oldArr.filter(item => {
                        if (item.toLowerCase().includes(this.mutableValue.toLowerCase()))
                            return true;
                    })
                    this.filterList = oldArr;
                    //console.log('type', this.filterList)
                }
                // if (key.key == 'Enter')
                //     this.toggled = !this.toggled;
                this.$emit('input', this.mutableValue);
            },
            handleItemClick: function (item) {                
                this.mutableValue = item;
                this.$emit('input', item);                                    
                this.toggled = !this.toggled;                               
            },
            onFocus: function () {
                this.$refs.dropdown.scrollTop = 0;
                this.toggled = !this.toggled;
            },
            onBlur: function () { 
                this.handleItemClick(this.mutableValue)                
                this.$refs.selected.blur();
            },
            onKeyUp: function () {
                if (this.pointer > 0) this.pointer--;
                if (this.maybeAdjustScroll) {
                    this.maybeAdjustScroll()
                }
            },
            onKeyDown: function () {            
                if (this.pointer < this.options.length && this.filterList.length) this.pointer++;
                if (this.pointer == this.options.length) this.pointer = 0;
                if (this.maybeAdjustScroll) {
                    this.maybeAdjustScroll()
                }
            },
            onKeyEnter: function () {
                //console.log(this.filterList.length> 0);
                if(this.filterList.length > 0)
                    this.handleItemClick(this.filterList[this.pointer])                
                this.$refs.selected.blur();
                this.$emit('input', this.mutableValue);
                this.toggled = false;
            },
            maybeAdjustScroll() {
                let pixelsToPointerTop = this.pixelsToPointerTop()
                let pixelsToPointerBottom = this.pixelsToPointerBottom()
                //console.log(pixelsToPointerTop,pixelsToPointerBottom);
                if (pixelsToPointerTop <= this.viewport().top) {
                    return this.scrollTo(pixelsToPointerTop)
                } else if (pixelsToPointerBottom >= this.viewport().bottom) {
                    return this.scrollTo(this.viewport().top + this.pointerHeight())
                }
            },

            pixelsToPointerTop() {
                let pixelsToPointerTop = 0
                if (this.$refs.dropdown && this.$refs.dropdown.children) {
                    for (let i = 0; i < this.pointer; i++) {
                        pixelsToPointerTop += this.$refs.dropdown.children[i].offsetHeight
                    }
                }
                return pixelsToPointerTop
            },

            pixelsToPointerBottom() {
                return this.pixelsToPointerTop() + this.pointerHeight()
            },

            pointerHeight() {
                let element = this.$refs.dropdown ? this.$refs.dropdown.children[this.pointer] : false
                return element ? element.offsetHeight : 0
            },

            viewport() {
                return {
                    top: this.$refs.dropdown ? this.$refs.dropdown.scrollTop : 0,
                    bottom: this.$refs.dropdown ? this.$refs.dropdown.offsetHeight + this.$refs.dropdown.scrollTop : 0
                }
            },
            scrollTo(position) {
                //console.log(position);
                return this.$refs.dropdown ? this.$refs.dropdown.scrollTop = position : null
            },

        },
        mounted() {
            this.filterList = this.options;
        },
        watch: {
            value(val) {
                this.mutableValue = val
            },
            options(val) {
                this.mutableOptions = val
            },
            pointer() {
                this.maybeAdjustScroll()
            }

        }
    }
</script>

<style scoped>
    .vselect {
        display: block;
        position: relative;
    }

    .my-dropdown {
        width: 100%;
        background: #f7f7f7;
        margin-top: 0.1rem;
        border: 1px solid #ced4da;
        border-radius: 3px;
        transition: all 0.5s;
        position: absolute;
        z-index: 1;
        max-height: 10rem;
        overflow: auto
    }

    .my-dropdwon--item {
        padding: 0.5rem;
        width: 100%;
        transition: all 0.5s;
    }

    .active {
        cursor: pointer;
        background-color: rgb(223, 221, 221);
    }

    /* .my-dropdwon--item:hover {
        cursor: pointer;
        background-color: rgb(223, 221, 221);
    } */

    .form-group {
        margin-bottom: 0px;
    }

    .control.has-icon has-icon-right {
        margin-bottom: 0px;
    }

    .form-group>p {
        margin-bottom: 0px;
    }
</style>

1条回答
一纸荒年 Trace。
2楼-- · 2019-08-20 17:20

I have same problem with you before.

The problem is blur event will be fired first, it will hide the list of selection. Then click event will not be fired.

My solution is replacing @click with @mousedown event

<div v-for="(item,index) in filterList" 
     :class="{'my-dropdwon--item':true,active:index === pointer}" 
     @mousedown="handleItemClick(item)"
     @mouseover="pointer = index">
    {{item}}
</div>

Check my demo at https://codepen.io/ittus/pen/qYKRPv

查看更多
登录 后发表回答