autocomplete源码解析
autocomplete中包含三个组件,input和suggestion/el-scrollbar
1.template
一个class:
el-autocomplete:相对定位和行内块;
一个自定义指令:v-clickoutside:点击此dom元素外触发事件
el-input
四个emit触发事件:input,focus,blur,clear调节显示隐藏高亮清除
四个键盘事件up,down,enter,tab
四个插槽:prepend,append,prefix,suffix
v-bind="[$props, $attrs]",v-bind可用数组/对象,说明接收所有跟input相关的属性
el-autocomplete-suggestions
visible-arrow:没见到这个属性发挥作用
四个属性:
:popper-options=“popperOptions”
默认无
:append-to-body=“popperAppendToBody”
默认插入到body
popperAppendToBody: {
type: Boolean,
default: true
},
:placement=“placement”:
默认正下方
placement: {
type: String,
default: 'bottom-start'
},
:id=“id”
生成id随机数
export const generateId = function(){
return Math.floor(Math.random() * 10000)
}
popperClass:
属于自定义autocomplete-suggestions的class
2.script
引入防抖函数,外部点击指令,
混入广播派发事件,获取焦点方法Focus(‘input’)
Migrating 生产中没有用处,开发中可以给提示警告
import debounce from 'throttle-debounce/debounce';
import Clickoutside from 'element-ui/src/utils/clickoutside';
import Emitter from 'element-ui/src/mixins/emitter';
import Migrating from 'element-ui/src/mixins/migrating';
import { generateId } from 'element-ui/src/utils/util';
import Focus from 'element-ui/src/mixins/focus';
2.1props
24个属性
- valueKey: 默认为value,取list中的key,输入建议对象中用于显示的键名
- popperClass:Autocomplete 下拉列表的类名
- popperOptions:暂时没用上,应该与vue-popper选项有关
- placeholoder: 输入框占位文本
- clearable:是否清除,默认不显示
- disabled:是否禁用,默认不禁用
- name:原生属性,input的name,在v-bind中实现
- size:没见着怎么用
- value:给默认值
- maxlength:最大数量
- minlength:最小数量
- autofocus:是否自动获取焦点
- fetchSuggestions:可以没有queryString,必须回调,回调必须带参数,参数就是suggestions的结果,用意自然是可以去根据queryString去做过滤,但其实直接传过滤后的数据不香吗,绕得不太好.
- triggerOnFocus:是否在输入框focus的时候给出建议列表,默认为true,获取焦点的时候去判断
- customItem:没发现用到
- selectWhenUnmatched:默认为false,enter的时候触发,不匹配,清空
- prefixIcon:el-input的属性
- suffixIcon:el-input的属性
- label:el-input属性
- debounce:防抖时间
- placement:建议列表位置
- hideLoading:是否隐藏loading
- popperAppendToBody:弹框是否依附于body,默认依附,否则插入
- highlightFirstItem:是否默认突出显示远程搜索建议中的第一项
handleFocus(event) {
this.activated = true;
this.$emit('focus', event);
if (this.triggerOnFocus) {
this.debouncedGetData(this.value);
}
},
props: {
valueKey: {
type: String,
default: 'value'
},
popperClass: String,
popperOptions: Object,
placeholder: String,
clearable: {
type: Boolean,
default: false
},
disabled: Boolean,
name: String,
size: String,
value: String,
maxlength: Number,
minlength: Number,
autofocus: Boolean,
fetchSuggestions: Function,
triggerOnFocus: {
type: Boolean,
default: true
},
customItem: String,
selectWhenUnmatched: {
type: Boolean,
default: false
},
prefixIcon: String,
suffixIcon: String,
label: String,
debounce: {
type: Number,
default: 300
},
placement: {
type: String,
default: 'bottom-start'
},
hideLoading: Boolean,
popperAppendToBody: {
type: Boolean,
default: true
},
highlightFirstItem: {
type: Boolean,
default: false
}
},
2.2.data
五个自定义变量
actived表示获取焦点时为true,激活搜索建议
suggestions建议列表的数据
loading默认没有加载
highlightedIndex:表示高亮的选项
suggestionDisabled:表示禁用
data() {
return {
activated: false,
suggestions: [],
loading: false,
highlightedIndex: -1,
suggestionDisabled: false
};
},
2.3.computed
计算属性:
如果数据发生变化,则根据当前判断是否激活状态,根据是否有数据来显示隐藏:
suggestionVisible() {
const suggestions = this.suggestions;
let isValidData = Array.isArray(suggestions) && suggestions.length > 0;
return (isValidData || this.loading) && this.activated;
},
id() {
return `el-autocomplete-${generateId()}`;
}
2.4.watch
监听,如果可见性发生变化,直接给子组件派发事件,告诉子组件值和宽度
watch: {
suggestionVisible(val) {
let $input = this.getInput();
if ($input) {
this.broadcast('ElAutocompleteSuggestions', 'visible', [val, $input.offsetWidth]);
}
}
},
2.5.mounted
this.debouncedGetData = debounce(this.debounce, this.getData);
this.$on('item-click', item => {
this.select(item);
});
let $input = this.getInput();
$input.setAttribute('role', 'textbox');
$input.setAttribute('aria-autocomplete', 'list');
$input.setAttribute('aria-controls', 'id');
$input.setAttribute('aria-activedescendant', `${this.id}-item-${this.highlightedIndex}`);
2.6.methods
2.6.1:getMigratingConfig开发中的警告
警告
getMigratingConfig() {
return {
props: {
'custom-item': 'custom-item is removed, use scoped slot instead.',
'props': 'props is removed, use value-key instead.'
}
};
},
2.6.2:getData获取列表数据
获取数据
// 获取数据
getData(queryString) {
// 如果禁止建议选项,那么无法继续执行
if (this.suggestionDisabled) {
return;
}
this.loading = true;
// 根据参数请求
this.fetchSuggestions(queryString, (suggestions) => {
this.loading = false;
if (this.suggestionDisabled) {
return;
}
// suggestions为数组,将回调函数的参数赋值给列表
if (Array.isArray(suggestions)) {
this.suggestions = suggestions;
this.highlightedIndex = this.highlightFirstItem ? 0 : -1;
} else {
console.error('[Element Error][Autocomplete]autocomplete suggestions must be an array');
}
});
},
2.6.3:handleChange,监听input触发
handleChange(value) {
// 触发input,将input的value传递给父组件
this.$emit('input', value);
// 不禁止input选项
this.suggestionDisabled = false;
// 如果没有值且不设置获取焦点触发,则设置选项为禁用,将列表置空
if (!this.triggerOnFocus && !value) {
this.suggestionDisabled = true;
this.suggestions = [];
return;
}
// 用防抖重新去获取数据
this.debouncedGetData(value);
}
2.6.4:handleFocus,监听focus
handleFocus(event) {
// 获取焦点,激活建议ToolTip
this.activated = true;
// 触发获取焦点事件,将事件传递给父元素
this.$emit('focus', event);
// 判断是否获取焦点就触发,触发就根据value获取数据
if (this.triggerOnFocus) {
this.debouncedGetData(this.value);
}
}
2.6.5:handleBlur,监听blur
handleBlur(event) {
// 将失焦事件和全部event传递给父组件,
this.$emit('blur', event);
}
2.6.6:handleClear,监听清除事件
handleClear() {
// 清除便设置建议列表隐藏
this.activated = false;
// 并且将clear事件传递给父组件
this.$emit('clear');
}
2.6.7:close,tab按钮触发
close(e) {
this.activated = false;
},
2.6.8:handleKeyEnter
handleKeyEnter(e) {
// 判断建议列表是否显示,判断是否选中建议,判断高亮是否小于长度,阻止默认事件,调用选中方法,如果不匹配,触发select事件,同时置空选中列表
if (this.suggestionVisible && this.highlightedIndex >= 0 && this.highlightedIndex < this.suggestions.length) {
e.preventDefault();
this.select(this.suggestions[this.highlightedIndex]);
} else if (this.selectWhenUnmatched) {
this.$emit('select', {value: this.value});
this.$nextTick(_ => {
this.suggestions = [];
this.highlightedIndex = -1;
});
}
},
2.6.9:select enter选中事件
select(item) {
// 触发input事件,传选中值出来
this.$emit('input', item[this.valueKey]);
// 触发select事件,传选中项出来
this.$emit('select', item);
// 将当前列表置空
this.$nextTick(_ => {
this.suggestions = [];
this.highlightedIndex = -1;
});
},
2.6.10:highlight 高亮
highlight(index) {
// 不可见,就没有下一步
if (!this.suggestionVisible || this.loading) { return; }
// 没有索引值,就没有下一步
if (index < 0) {
this.highlightedIndex = -1;
return;
}
// 只要超出,则一直选中最后项
if (index >= this.suggestions.length) {
index = this.suggestions.length - 1;
}
// 获取下拉窗口
const suggestion = this.$refs.suggestions.$el.querySelector('.el-autocomplete-suggestion__wrap');
// 获取所有列表元素
const suggestionList = suggestion.querySelectorAll('.el-autocomplete-suggestion__list li');
// 获取选中项
let highlightItem = suggestionList[index];
// 滚动距离
let scrollTop = suggestion.scrollTop;
// 距离父级高度
let offsetTop = highlightItem.offsetTop;
// scrollHeight代表包括当前不可见部分的元素的高度
if (offsetTop + highlightItem.scrollHeight > (scrollTop + suggestion.clientHeight)) {
// 比较当前选中项和整个列表项,如果前者大,则将列表的滚动距离 + 选中项距离
suggestion.scrollTop += highlightItem.scrollHeight;
}
// 如果选中项距离顶部高度小于列表框滚动距离,则让列表框的滚动减去选中项的高度
if (offsetTop < scrollTop) {
suggestion.scrollTop -= highlightItem.scrollHeight;
}
this.highlightedIndex = index;
let $input = this.getInput();
$input.setAttribute('aria-activedescendant', `${this.id}-item-${this.highlightedIndex}`);
},