Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vuejs on blur for custom select element not working

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>
like image 255
Indrajeet Latthe Avatar asked May 13 '18 07:05

Indrajeet Latthe


1 Answers

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

like image 51
ittus Avatar answered Sep 28 '22 14:09

ittus