element/packages/select/src/select.vue

563 lines
17 KiB
Vue
Raw Normal View History

2016-07-27 06:15:02 +00:00
<template>
<div
class="el-select"
v-clickoutside="handleClose"
:class="{ 'is-multiple': multiple, 'is-small': size === 'small' }">
2016-08-29 11:01:42 +00:00
<div class="el-select__tags" v-if="multiple" @click.stop="toggleMenu" ref="tags" :style="{ 'max-width': inputWidth - 32 + 'px' }">
2016-08-19 10:20:54 +00:00
<transition-group @after-leave="resetInputHeight">
<el-tag
v-for="item in selected"
:key="item"
closable
:hit="item.hitState"
type="primary"
@click.native="deleteTag($event, item)"
2016-08-24 10:52:07 +00:00
close-transition>{{ item.currentLabel }}</el-tag>
2016-08-19 10:20:54 +00:00
</transition-group>
2016-07-27 06:15:02 +00:00
<input
type="text"
class="el-select__input"
@focus="visible = true"
2016-07-27 06:15:02 +00:00
@keyup="managePlaceholder"
@keydown="resetInputState"
@keydown.down.prevent="navigateOptions('next')"
@keydown.up.prevent="navigateOptions('prev')"
@keydown.enter.prevent="selectOption"
@keydown.esc.prevent="visible = false"
@keydown.delete="deletePrevTag"
v-model="query"
:debounce="remote ? 300 : 0"
v-if="filterable"
:style="{ width: inputLength + 'px', 'max-width': inputWidth - 42 + 'px' }"
2016-07-28 10:37:14 +00:00
ref="input">
2016-07-27 06:15:02 +00:00
</div>
<el-input
2016-07-28 10:37:14 +00:00
ref="reference"
v-model="selectedLabel"
2016-07-27 06:15:02 +00:00
type="text"
2016-07-28 10:37:14 +00:00
:placeholder="currentPlaceholder"
2016-07-27 06:15:02 +00:00
:name="name"
:disabled="disabled"
:readonly="!filterable || multiple"
2016-07-28 10:37:14 +00:00
@click.native="toggleMenu"
@keyup.native="debouncedOnInputChange"
@keydown.native.down.prevent="navigateOptions('next')"
@keydown.native.up.prevent="navigateOptions('prev')"
@keydown.native.enter.prevent="selectOption"
@keydown.native.esc.prevent="visible = false"
@keydown.native.tab="visible = false"
@mouseenter.native="inputHovering = true"
@mouseleave.native="inputHovering = false"
:icon="iconClass">
2016-07-27 06:15:02 +00:00
</el-input>
<transition name="md-fade-bottom" @after-leave="doDestroy">
2016-08-11 04:20:29 +00:00
<el-select-menu
ref="popper"
2016-10-25 10:00:39 +00:00
v-show="visible && emptyText !== false">
2016-08-20 07:00:01 +00:00
<ul class="el-select-dropdown__list" v-show="options.length > 0 && filteredOptionsCount > 0 && !loading">
2016-08-11 04:20:29 +00:00
<slot></slot>
</ul>
2016-10-27 12:14:31 +00:00
<p class="el-select-dropdown__empty" v-if="emptyText">{{ emptyText }}</p>
2016-08-11 04:20:29 +00:00
</el-select-menu>
</transition>
2016-07-27 06:15:02 +00:00
</div>
</template>
<script type="text/babel">
2016-10-27 09:31:22 +00:00
import Emitter from 'element-ui/src/mixins/emitter';
import Locale from 'element-ui/src/mixins/locale';
2016-10-25 13:35:41 +00:00
import ElInput from 'element-ui/packages/input';
2016-10-13 09:51:14 +00:00
import ElSelectMenu from './select-dropdown.vue';
2016-10-25 13:35:41 +00:00
import ElTag from 'element-ui/packages/tag';
2016-07-27 06:15:02 +00:00
import debounce from 'throttle-debounce/debounce';
2016-10-13 03:12:24 +00:00
import Clickoutside from 'element-ui/src/utils/clickoutside';
2016-10-13 05:55:07 +00:00
import { addClass, removeClass, hasClass } from 'wind-dom/src/class';
2016-10-20 10:43:37 +00:00
import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event';
2016-10-27 09:31:22 +00:00
import { $t } from 'element-ui/src/locale';
2016-07-27 06:15:02 +00:00
export default {
2016-10-27 09:31:22 +00:00
mixins: [Emitter, Locale],
2016-07-27 06:15:02 +00:00
name: 'ElSelect',
componentName: 'select',
computed: {
2016-08-19 10:20:54 +00:00
iconClass() {
return this.showCloseIcon ? 'circle-close' : (this.remote && this.filterable ? '' : 'caret-top');
},
2016-07-27 06:15:02 +00:00
debounce() {
return this.remote ? 300 : 0;
},
showCloseIcon() {
let criteria = this.clearable && this.inputHovering && !this.multiple && this.options.indexOf(this.selected) > -1;
2016-07-28 10:37:14 +00:00
if (!this.$el) return false;
2016-07-27 06:15:02 +00:00
let icon = this.$el.querySelector('.el-input__icon');
if (icon) {
if (criteria) {
icon.addEventListener('click', this.deleteSelected);
2016-10-11 08:39:54 +00:00
addClass(icon, 'is-show-close');
2016-07-27 06:15:02 +00:00
} else {
icon.removeEventListener('click', this.deleteSelected);
2016-10-11 08:39:54 +00:00
removeClass(icon, 'is-show-close');
2016-07-27 06:15:02 +00:00
}
}
return criteria;
},
2016-10-25 10:00:39 +00:00
emptyText() {
2016-07-27 06:15:02 +00:00
if (this.loading) {
2016-10-27 09:31:22 +00:00
return this.$t('el.select.loading');
2016-07-27 06:15:02 +00:00
} else {
if (this.voidRemoteQuery) {
this.voidRemoteQuery = false;
return false;
}
2016-10-27 12:14:31 +00:00
if (this.filterable && this.filteredOptionsCount === 0) {
2016-10-27 09:31:22 +00:00
return this.$t('el.select.noMatch');
2016-07-27 06:15:02 +00:00
}
if (this.options.length === 0) {
2016-10-27 09:31:22 +00:00
return this.$t('el.select.noData');
2016-07-27 06:15:02 +00:00
}
}
return null;
}
},
components: {
ElInput,
ElSelectMenu,
ElTag
},
directives: { Clickoutside },
2016-07-27 06:15:02 +00:00
props: {
name: String,
value: {},
size: String,
disabled: Boolean,
clearable: Boolean,
filterable: Boolean,
loading: Boolean,
remote: Boolean,
remoteMethod: Function,
filterMethod: Function,
multiple: Boolean,
placeholder: {
type: String,
2016-10-27 09:31:22 +00:00
default: $t('el.select.placeholder')
2016-07-27 06:15:02 +00:00
}
},
data() {
return {
options: [],
selected: {},
isSelect: true,
inputLength: 20,
2016-09-12 13:04:20 +00:00
inputWidth: 0,
2016-07-27 06:15:02 +00:00
valueChangeBySelected: false,
cachedPlaceHolder: '',
optionsCount: 0,
filteredOptionsCount: 0,
dropdownUl: null,
visible: false,
selectedLabel: '',
selectInit: false,
hoverIndex: -1,
query: '',
voidRemoteQuery: false,
bottomOverflowBeforeHidden: 0,
optionsAllDisabled: false,
2016-07-28 10:37:14 +00:00
inputHovering: false,
currentPlaceholder: ''
2016-07-27 06:15:02 +00:00
};
},
watch: {
2016-07-28 10:37:14 +00:00
placeholder(val) {
this.currentPlaceholder = val;
},
2016-07-27 06:15:02 +00:00
value(val) {
if (this.valueChangeBySelected) {
this.valueChangeBySelected = false;
return;
}
2016-09-07 10:38:51 +00:00
this.$nextTick(() => {
if (this.multiple && Array.isArray(val)) {
this.$nextTick(() => {
this.resetInputHeight();
});
this.selectedInit = true;
this.selected = [];
2016-09-12 05:53:45 +00:00
this.currentPlaceholder = this.cachedPlaceHolder;
2016-09-07 10:38:51 +00:00
val.forEach(item => {
let option = this.options.filter(option => option.value === item)[0];
if (option) {
this.$emit('addOptionToValue', option);
}
});
}
if (!this.multiple) {
let option = this.options.filter(option => option.value === val)[0];
2016-07-27 06:15:02 +00:00
if (option) {
this.$emit('addOptionToValue', option);
2016-09-07 10:38:51 +00:00
} else {
this.selected = {};
this.selectedLabel = '';
2016-07-27 06:15:02 +00:00
}
}
2016-09-07 10:38:51 +00:00
this.resetHoverIndex();
});
2016-07-27 06:15:02 +00:00
},
selected(val) {
if (this.multiple) {
2016-10-10 06:42:01 +00:00
if (this.selected.length > 0) {
this.currentPlaceholder = '';
} else {
this.currentPlaceholder = this.cachedPlaceHolder;
}
2016-07-27 06:15:02 +00:00
if (this.selectedInit) {
this.selectedInit = false;
return;
}
this.valueChangeBySelected = true;
2016-07-29 09:46:16 +00:00
const result = val.map(item => item.value);
this.$emit('input', result);
this.$emit('change', result);
2016-07-27 06:15:02 +00:00
this.$nextTick(() => {
this.resetInputHeight();
});
if (this.filterable) {
this.query = '';
this.hoverIndex = -1;
2016-07-28 10:37:14 +00:00
this.$refs.input.focus();
2016-07-27 06:15:02 +00:00
this.inputLength = 20;
}
} else {
this.valueChangeBySelected = true;
2016-09-05 12:11:11 +00:00
this.$emit('input', val.value);
this.$emit('change', val.value);
2016-07-27 06:15:02 +00:00
}
},
query(val) {
2016-07-28 10:37:14 +00:00
this.$nextTick(() => {
this.broadcast('select-dropdown', 'updatePopper');
});
2016-07-27 06:15:02 +00:00
if (this.multiple && this.filterable) {
this.resetInputHeight();
}
if (this.remote && typeof this.remoteMethod === 'function') {
this.hoverIndex = -1;
if (!this.multiple) {
this.selected = {};
}
this.remoteMethod(val);
this.voidRemoteQuery = val === '';
2016-07-27 06:15:02 +00:00
} else if (typeof this.filterMethod === 'function') {
this.filterMethod(val);
} else {
this.filteredOptionsCount = this.optionsCount;
this.broadcast('option', 'queryChange', val);
}
},
visible(val) {
if (!val) {
2016-08-15 08:10:12 +00:00
this.$refs.reference.$el.querySelector('input').blur();
2016-08-19 10:20:54 +00:00
if (this.$el.querySelector('.el-input__icon')) {
2016-10-11 08:39:54 +00:00
removeClass(this.$el.querySelector('.el-input__icon'), 'is-reverse');
2016-08-19 10:20:54 +00:00
}
2016-07-27 06:15:02 +00:00
this.broadcast('select-dropdown', 'destroyPopper');
2016-07-28 10:37:14 +00:00
if (this.$refs.input) {
this.$refs.input.blur();
2016-07-27 06:15:02 +00:00
}
this.resetHoverIndex();
if (!this.multiple) {
if (this.dropdownUl && this.selected.$el) {
2016-07-28 10:37:14 +00:00
this.bottomOverflowBeforeHidden = this.selected.$el.getBoundingClientRect().bottom - this.$refs.popper.$el.getBoundingClientRect().bottom;
2016-07-27 06:15:02 +00:00
}
if (this.selected && this.selected.value) {
2016-08-24 10:52:07 +00:00
this.selectedLabel = this.selected.currentLabel;
2016-07-27 06:15:02 +00:00
}
}
} else {
2016-10-13 05:55:07 +00:00
let icon = this.$el.querySelector('.el-input__icon');
if (icon && !hasClass(icon, 'el-icon-circle-close')) {
2016-10-11 08:39:54 +00:00
addClass(this.$el.querySelector('.el-input__icon'), 'is-reverse');
2016-08-19 10:20:54 +00:00
}
2016-07-27 06:15:02 +00:00
this.broadcast('select-dropdown', 'updatePopper');
if (this.filterable) {
this.query = this.selectedLabel;
if (this.multiple) {
2016-07-28 10:37:14 +00:00
this.$refs.input.focus();
2016-07-27 06:15:02 +00:00
} else {
this.broadcast('input', 'inputSelect');
}
}
if (!this.dropdownUl) {
2016-07-28 10:37:14 +00:00
let dropdownChildNodes = this.$refs.popper.$el.childNodes;
2016-07-27 06:15:02 +00:00
this.dropdownUl = [].filter.call(dropdownChildNodes, item => item.tagName === 'UL')[0];
}
if (!this.multiple && this.dropdownUl) {
if (this.bottomOverflowBeforeHidden > 0) {
this.dropdownUl.scrollTop += this.bottomOverflowBeforeHidden;
}
}
}
},
options(val) {
this.optionsAllDisabled = val.length === val.filter(item => item.disabled === true).length;
}
},
methods: {
doDestroy() {
this.$refs.popper.doDestroy();
},
2016-07-28 10:37:14 +00:00
handleClose() {
this.visible = false;
},
2016-07-27 06:15:02 +00:00
toggleLastOptionHitState(hit) {
if (!Array.isArray(this.selected)) return;
const option = this.selected[this.selected.length - 1];
if (!option) return;
if (hit === true || hit === false) {
option.hitState = hit;
return hit;
}
option.hitState = !option.hitState;
return option.hitState;
},
deletePrevTag(e) {
if (e.target.value.length <= 0 && !this.toggleLastOptionHitState()) {
this.selected.pop();
}
},
2016-10-10 06:42:01 +00:00
addOptionToValue(option, init) {
2016-07-27 06:15:02 +00:00
if (this.multiple) {
if (this.selected.indexOf(option) === -1 && (this.remote ? this.value.indexOf(option.value) === -1 : true)) {
2016-10-10 06:42:01 +00:00
this.selectedInit = !!init;
2016-07-27 06:15:02 +00:00
this.selected.push(option);
this.resetHoverIndex();
}
} else {
this.selected = option;
2016-08-24 10:52:07 +00:00
this.selectedLabel = option.currentLabel;
2016-07-27 06:15:02 +00:00
this.hoverIndex = option.index;
}
},
managePlaceholder() {
2016-07-28 10:37:14 +00:00
if (this.currentPlaceholder !== '') {
this.currentPlaceholder = this.$refs.input.value ? '' : this.cachedPlaceHolder;
2016-07-27 06:15:02 +00:00
}
},
resetInputState(e) {
if (e.keyCode !== 8) this.toggleLastOptionHitState(false);
this.inputLength = this.$refs.input.value.length * 15 + 20;
2016-07-27 06:15:02 +00:00
},
resetInputHeight() {
this.$nextTick(() => {
2016-07-28 10:37:14 +00:00
let inputChildNodes = this.$refs.reference.$el.childNodes;
2016-07-27 06:15:02 +00:00
let input = [].filter.call(inputChildNodes, item => item.tagName === 'INPUT')[0];
2016-07-28 10:37:14 +00:00
input.style.height = Math.max(this.$refs.tags.clientHeight + 6, this.size === 'small' ? 28 : 36) + 'px';
2016-07-27 06:15:02 +00:00
this.broadcast('select-dropdown', 'updatePopper');
});
},
resetHoverIndex() {
setTimeout(() => {
if (!this.multiple) {
this.hoverIndex = this.options.indexOf(this.selected);
} else {
if (this.selected.length > 0) {
this.hoverIndex = Math.min.apply(null, this.selected.map(item => this.options.indexOf(item)));
} else {
this.hoverIndex = -1;
}
}
}, 300);
},
handleOptionSelect(option) {
if (!this.multiple) {
this.selected = option;
2016-08-24 10:52:07 +00:00
this.selectedLabel = option.currentLabel;
2016-07-27 06:15:02 +00:00
this.visible = false;
} else {
let optionIndex = -1;
this.selected.forEach((item, index) => {
2016-08-24 10:52:07 +00:00
if (item === option || item.currentLabel === option.currentLabel) {
2016-07-27 06:15:02 +00:00
optionIndex = index;
}
});
if (optionIndex > -1) {
this.selected.splice(optionIndex, 1);
} else {
this.selected.push(option);
}
}
},
toggleMenu() {
2016-08-15 08:10:12 +00:00
if (this.filterable && this.query === '' && this.visible) {
return;
}
2016-07-27 06:15:02 +00:00
if (!this.disabled) {
this.visible = !this.visible;
2016-08-04 11:56:54 +00:00
if (this.remote && this.visible) {
this.selectedLabel = '';
}
2016-07-27 06:15:02 +00:00
}
},
navigateOptions(direction) {
if (!this.visible) {
this.visible = true;
return;
}
if (!this.optionsAllDisabled) {
if (direction === 'next') {
this.hoverIndex++;
if (this.hoverIndex === this.options.length) {
this.hoverIndex = 0;
}
this.resetScrollTop();
2016-10-25 10:00:39 +00:00
if (this.options[this.hoverIndex].disabled === true ||
this.options[this.hoverIndex].groupDisabled === true ||
!this.options[this.hoverIndex].queryPassed) {
2016-07-27 06:15:02 +00:00
this.navigateOptions('next');
}
}
if (direction === 'prev') {
this.hoverIndex--;
if (this.hoverIndex < 0) {
this.hoverIndex = this.options.length - 1;
}
this.resetScrollTop();
2016-10-25 10:00:39 +00:00
if (this.options[this.hoverIndex].disabled === true ||
this.options[this.hoverIndex].groupDisabled === true ||
!this.options[this.hoverIndex].queryPassed) {
2016-07-27 06:15:02 +00:00
this.navigateOptions('prev');
}
}
}
},
resetScrollTop() {
2016-07-28 10:37:14 +00:00
let bottomOverflowDistance = this.options[this.hoverIndex].$el.getBoundingClientRect().bottom - this.$refs.popper.$el.getBoundingClientRect().bottom;
let topOverflowDistance = this.options[this.hoverIndex].$el.getBoundingClientRect().top - this.$refs.popper.$el.getBoundingClientRect().top;
2016-07-27 06:15:02 +00:00
if (bottomOverflowDistance > 0) {
this.dropdownUl.scrollTop += bottomOverflowDistance;
}
if (topOverflowDistance < 0) {
this.dropdownUl.scrollTop += topOverflowDistance;
}
},
selectOption() {
2016-08-15 08:10:12 +00:00
if (this.options[this.hoverIndex]) {
this.handleOptionSelect(this.options[this.hoverIndex]);
}
2016-07-27 06:15:02 +00:00
},
deleteSelected(event) {
event.stopPropagation();
this.selected = {};
this.selectedLabel = '';
2016-08-04 11:56:54 +00:00
this.$emit('input', '');
2016-08-17 06:08:54 +00:00
this.$emit('change', '');
2016-07-27 06:15:02 +00:00
this.visible = false;
},
deleteTag(event, tag) {
if (event.target.tagName === 'I') {
let index = this.selected.indexOf(tag);
if (index > -1) {
this.selected.splice(index, 1);
}
event.stopPropagation();
}
},
onInputChange() {
if (this.filterable && this.selectedLabel !== this.value) {
2016-07-27 06:15:02 +00:00
this.query = this.selectedLabel;
}
2016-08-20 07:00:01 +00:00
},
onOptionDestroy(option) {
this.optionsCount--;
this.filteredOptionsCount--;
let index = this.options.indexOf(option);
if (index > -1) {
this.options.splice(index, 1);
}
this.broadcast('option', 'resetIndex');
2016-10-20 10:43:37 +00:00
},
resetInputWidth() {
this.inputWidth = this.$refs.reference.$el.getBoundingClientRect().width;
2016-07-27 06:15:02 +00:00
}
},
created() {
2016-07-28 10:37:14 +00:00
this.cachedPlaceHolder = this.currentPlaceholder = this.placeholder;
2016-07-27 06:15:02 +00:00
if (this.multiple) {
this.selectedInit = true;
this.selected = [];
}
if (this.remote) {
this.voidRemoteQuery = true;
}
2016-07-28 10:37:14 +00:00
2016-07-27 06:15:02 +00:00
this.debouncedOnInputChange = debounce(this.debounce, () => {
this.onInputChange();
});
this.$on('addOptionToValue', this.addOptionToValue);
this.$on('handleOptionClick', this.handleOptionSelect);
2016-08-20 07:00:01 +00:00
this.$on('onOptionDestroy', this.onOptionDestroy);
2016-08-29 11:01:42 +00:00
},
mounted() {
2016-10-20 10:43:37 +00:00
addResizeListener(this.$el, this.resetInputWidth);
if (this.remote && this.multiple && Array.isArray(this.value)) {
this.selected = this.options.reduce((prev, curr) => {
return this.value.indexOf(curr.value) > -1 ? prev.concat(curr) : prev;
}, []);
this.$nextTick(() => {
this.resetInputHeight();
});
}
2016-08-29 11:01:42 +00:00
this.$nextTick(() => {
if (this.$refs.reference.$el) {
this.inputWidth = this.$refs.reference.$el.getBoundingClientRect().width;
}
});
2016-10-20 10:43:37 +00:00
},
destroyed() {
if (this.resetInputWidth) removeResizeListener(this.$el, this.resetInputWidth);
2016-07-27 06:15:02 +00:00
}
};
</script>