vue输入框联想词功能

3,705 阅读4分钟

1.实现的功能

输入框输入字符后,联想词列表出现,可以按“↓”或“↑”选择列表中的内容,也可以鼠标点选,且互相不影响选择样式,即只会出现一个被选中,“Enter”键发起检索。

2.DOM结构

<template>
    <div class="input-m">  
        <div class="search">    
            <input type="text" :placeholder=placeholder v-model="content" @keyup="input">  
        </div>  
    <ul class="associate-list" v-show="associateWords&&showList" @mouseout="selectedLi(0)">
        <li class="associate-item">{{inputContent}}</li>    
        <li class="associate-item" v-for="(item,index) of associateWords" :key="index" 
            @mouseover="selectedLi(index+1)">{{item}}</li>  
    </ul>
    </div>
</template>

3.变量

content: '',---双向绑定的数据,input输入框中的内容,用户选择联想词列表时同步变化
inputContent: '',---保存用户通过键盘输入的内容,不与用户选择的联想词列表同步变化
focusIndex: 0,---用户选择的联想词<li>列表的下标
associateWords: [],---联想词列表数组
showList: true---是否显示联想词列表由此变量和associateWords的长度共同控制

inputConent是用于记录用户通过键盘输入的内容,通过上下键选择或鼠标悬浮时选择的会通过双向绑定同步到content中,以百度搜索联想词列表为例,当用户一直按向下键时,超过联想词列表后,input框中的内容为用户最开始输入的内容。

focusIndex记录用户选择的<li>标签的下标,当一直按向上或向下键时,会出现focusIndex超出<li>列表长度的或小于0的清理,用focusIndex = (focusIndex+length)%length操作,可以实现fousIndex总是在0至length-1范围内。

当通过document.getElementsByClassName获取<li>数组时,数组下标从0开始,而foucusIndex初始值为0,当按下“↓”时,focusIndex+1,选中的就是<li>列表的下标为1的元素,即第2个<li>,这是不合理的。

如果将focusIndex的下标初始值设为-1,满足了上边的需求。那么当按下“↑”时,focusIndex-1,通过取余操作可以得到foucusIndex = length-2,即<li>列表的倒数第2项,也是不合理的。

故将用户输入的文字内容作为<li>列表的第一项,且隐藏,将focusIndex初始值设为0。这样就实现了按上下键选择,且超出显示的长度时,是用户通过键盘输入的内容。

用showList与associateWords一起控制列表的显示,没有相关联想词时肯定不显示,但用户点击输入框以外的位置时,联想词列表应该可以隐藏。如果采用将associateWords来隐藏的话,用户再次点击输入框时,会多向服务器发送一次搜索相关联想词的请求。

4.JavaScript部分

input (e) {  
    //keyup事件的event    
    e = e || window.event      
    this.showList = true      
    // 按“↑” 键     
    if (e.keyCode === 38) {        
        this.focusIndex--        
        this.selectedLi()      
    } else if (e.keyCode === 40) {        
        // 按“↓”,数组下标从0开始,list的[0]项内容为用户输入内容,不显示,从[1]项开始显示        
        this.focusIndex++        
        this.selectedLi()      
    } else if (e.keyCode === 13) {        
        // 按“Enter”调用搜索函数        
        this.doSomething() //----向后台发送搜索请求     
        this.associateWords = []      
    } else {        
        // 用户继续输入时,将inputContent置空----非常重要的一步       
        this.inputContent = ''        
        this.focusIndex = 0        
        // 搜索联想内容        
        this.getAssociateWords() //----向后台请求相关联想词列表       
        this.clearSelected()      
    }    
}

与样式相关的js操作

selectedLi (hoverIndex) {      
    // 当inputContent内容为空时,记录下搜索框中用户输入的内容,作为associate-item的第一项      
    if (this.inputContent === '') {        
    this.inputContent = this.content      
    }      
    let associateList = document.getElementsByClassName('associate-item')      
    // 移除已添加的.selected样式      
    for (var i = 0; i < associateList.length; i++) {        
        associateList[i].classList.remove('selected')      
    }      
    if (hoverIndex !== undefined) {        
        this.focusIndex = hoverIndex      
    }      
    // 一直按向下键超出联想内容li长度时,应回到联想内容第一个      
    this.focusIndex = (this.focusIndex + associateList.length) % associateList.length      
    // 为选中的li添加.selected样式      
    let selectedOne = associateList[this.focusIndex]      
    this.content = selectedOne.textContent      
    selectedOne.classList.add('selected')    
}
clearSelected () {      
    let associateList = document.getElementsByClassName('associate-item')      
    // 移除已添加的.selected样式      
    for (var i = 1; i < associateList.length; i++) {        
        associateList[i].classList.remove('selected')      
    }    
}

为除了input框以外的页面部分添加监听事件,点击input以外的部分时,隐藏联想词列表

// 点击input输入框以外的位置时 隐藏联想词列表    
document.body.addEventListener('click', e => {      
    if (e.target.nodeName === 'INPUT') {        
        this.showList = true      
    } else {        
        this.showList = false      
    }    
})

向后台服务器请求联想词列表

getAssociateWords () {      
    let self = this      
    axios.get('XX/data.json').then(function (res) {        
         self.associateWords = result.slice(0, 5) 
    })    
}