element/packages/autocomplete/src/autocomplete.vue

167 lines
4.6 KiB
Vue

<template>
<div class="el-autocomplete" v-clickoutside="handleClickoutside">
<el-input
ref="input"
:value="value"
:disabled="disabled"
:placeholder="placeholder"
:name="name"
:size="size"
@change="handleChange"
@focus="handleFocus"
@blur="handleBlur"
@keydown.up.native="highlight(highlightedIndex - 1)"
@keydown.down.native="highlight(highlightedIndex + 1)"
@keydown.enter.stop.native="handleKeyEnter"
>
<template slot="prepend" v-if="$slots.prepend">
<slot name="prepend"></slot>
</template>
<template slot="append" v-if="$slots.append">
<slot name="append"></slot>
</template>
</el-input>
<el-autocomplete-suggestions
:class="[popperClass ? popperClass : '']"
ref="suggestions"
:suggestions="suggestions"
>
</el-autocomplete-suggestions>
</div>
</template>
<script>
import ElInput from 'element-ui/packages/input';
import Clickoutside from 'element-ui/src/utils/clickoutside';
import ElAutocompleteSuggestions from './autocomplete-suggestions.vue';
import Emitter from 'element-ui/src/mixins/emitter';
export default {
name: 'ElAutocomplete',
mixins: [Emitter],
componentName: 'ElAutocomplete',
components: {
ElInput,
ElAutocompleteSuggestions
},
directives: { Clickoutside },
props: {
popperClass: String,
placeholder: String,
disabled: Boolean,
name: String,
size: String,
value: String,
autofocus: Boolean,
fetchSuggestions: Function,
triggerOnFocus: {
type: Boolean,
default: true
},
customItem: String
},
data() {
return {
isFocus: false,
suggestions: [],
loading: false,
highlightedIndex: -1
};
},
computed: {
suggestionVisible() {
const suggestions = this.suggestions;
let isValidData = Array.isArray(suggestions) && suggestions.length > 0;
return (isValidData || this.loading) && this.isFocus;
}
},
watch: {
suggestionVisible(val) {
this.broadcast('ElAutocompleteSuggestions', 'visible', [val, this.$refs.input.$refs.input.offsetWidth]);
}
},
methods: {
getData(queryString) {
this.loading = true;
this.fetchSuggestions(queryString, (suggestions) => {
this.loading = false;
if (Array.isArray(suggestions)) {
this.suggestions = suggestions;
} else {
console.error('autocomplete suggestions must be an array');
}
});
},
handleChange(value) {
this.$emit('input', value);
if (!this.triggerOnFocus && !value) {
this.suggestions = [];
return;
}
this.getData(value);
},
handleFocus() {
this.isFocus = true;
if (this.triggerOnFocus) {
this.getData(this.value);
}
},
handleBlur() {
// 因为 blur 事件处理优先于 select 事件执行
setTimeout(_ => {
this.isFocus = false;
}, 100);
},
handleKeyEnter() {
if (this.suggestionVisible) {
this.select(this.suggestions[this.highlightedIndex]);
}
},
handleClickoutside() {
this.isFocus = false;
},
select(item) {
this.$emit('input', item.value);
this.$emit('select', item);
this.$nextTick(_ => {
this.suggestions = [];
});
},
highlight(index) {
if (!this.suggestionVisible || this.loading) { return; }
if (index < 0) index = 0;
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;
if (offsetTop + highlightItem.scrollHeight > (scrollTop + suggestion.clientHeight)) {
suggestion.scrollTop += highlightItem.scrollHeight;
}
if (offsetTop < scrollTop) {
suggestion.scrollTop -= highlightItem.scrollHeight;
}
this.highlightedIndex = index;
}
},
mounted() {
this.$on('item-click', item => {
this.select(item);
});
},
beforeDestroy() {
this.$refs.suggestions.$destroy();
}
};
</script>