Select: refactor and bug fix

pull/1401/head
Leopoldthecoder 2016-11-27 20:12:59 +08:00
parent 568df8058d
commit ce3bed6cb5
7 changed files with 132 additions and 175 deletions

View File

@ -81,15 +81,15 @@
value: 'Guangzhou', value: 'Guangzhou',
label: '广州' label: '广州'
}], }],
value: '', value: '选项2',
value2: '', value2: '',
value3: '', value3: '',
value4: '', value4: '',
value5: [], value5: [],
value6: '', value6: '',
value7: [], value7: '',
value8: '', value8: '',
value9: [], value9: '',
loading: false, loading: false,
states: ["Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware", "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming"] states: ["Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware", "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming"]
}; };
@ -97,9 +97,20 @@
mounted() { mounted() {
this.list = this.states.map(item => { return { value: item, label: item }; }); this.list = this.states.map(item => { return { value: item, label: item }; });
// this.options4 = this.states.map(item => { return { value: item, label: item }; });
// this.value9 = ['Vermont'];
}, },
methods: { methods: {
empty() {
this.value9 = ['New York'];
},
aa(val) {
console.log('change', val)
},
bb() {
this.options.splice(0, 1, { label: 'haha', value: 'haha' });
},
remoteMethod(query) { remoteMethod(query) {
if (query !== '') { if (query !== '') {
this.loading = true; this.loading = true;
@ -129,11 +140,11 @@
### 基础用法 ### 基础用法
适用广泛的基础单选 适用广泛的基础单选
<el-button @click="bb">bb</el-button>
:::demo `v-model`的值为当前被选中的`el-option`的 value 属性值 :::demo `v-model`的值为当前被选中的`el-option`的 value 属性值
```html ```html
<template> <template>
<el-select v-model="value" placeholder="请选择"> <el-select v-model="value" placeholder="请选择" @change="aa">
<el-option <el-option
v-for="item in options" v-for="item in options"
:label="item.label" :label="item.label"
@ -499,13 +510,13 @@
### 远程搜索 ### 远程搜索
从服务器搜索数据,输入关键字进行查找 从服务器搜索数据,输入关键字进行查找
<el-button @click="empty">aa</el-button>
:::demo 为了启用远程搜索,需要将`filterable`和`remote`设置为`true`,同时传入一个`remote-method`。`remote-method`为一个`Function`,它会在输入值发生变化时调用,参数为当前输入值。需要注意的是,如果`el-option`是通过`v-for`指令渲染出来的,此时需要为`el-option`添加`key`属性,且其值需具有唯一性,比如此例中的`item.value`。 :::demo 为了启用远程搜索,需要将`filterable`和`remote`设置为`true`,同时传入一个`remote-method`。`remote-method`为一个`Function`,它会在输入值发生变化时调用,参数为当前输入值。需要注意的是,如果`el-option`是通过`v-for`指令渲染出来的,此时需要为`el-option`添加`key`属性,且其值需具有唯一性,比如此例中的`item.value`。
```html ```html
<template> <template>
<el-select <el-select
v-model="value9" v-model="value9"
multiple clearable
filterable filterable
remote remote
placeholder="请输入关键词" placeholder="请输入关键词"

View File

@ -140,10 +140,8 @@ export default {
return ( return (
<span class="el-pagination__sizes"> <span class="el-pagination__sizes">
<el-select <el-select
size="small"
value={ this.$parent.internalPageSize } value={ this.$parent.internalPageSize }
on-change={ this.handleChange } on-change={ this.handleChange }>
width={ 110 }>
{ {
this.pageSizes.map(item => this.pageSizes.map(item =>
<el-option <el-option

View File

@ -4,7 +4,11 @@
@click.stop="selectOptionClick" @click.stop="selectOptionClick"
class="el-select-dropdown__item" class="el-select-dropdown__item"
v-show="visible" v-show="visible"
:class="{ 'selected': itemSelected, 'is-disabled': disabled || groupDisabled, 'hover': parent.hoverIndex === index }"> :class="{
'selected': itemSelected,
'is-disabled': disabled || groupDisabled,
'hover': parent.hoverIndex === index
}">
<slot> <slot>
<span>{{ currentLabel }}</span> <span>{{ currentLabel }}</span>
</slot> </slot>
@ -63,23 +67,11 @@
}, },
itemSelected() { itemSelected() {
if (Object.prototype.toString.call(this.parent.selected) === '[object Object]') { if (!this.parent.multiple) {
return this === this.parent.selected; return this.value === this.parent.value;
} else if (Array.isArray(this.parent.selected)) { } else {
return this.parent.value.indexOf(this.value) > -1; return this.parent.value.indexOf(this.value) > -1;
} }
},
currentSelected() {
return this.selected || (this.parent.multiple ? this.parent.value.indexOf(this.value) > -1 : this.parent.value === this.value);
}
},
watch: {
currentSelected(val) {
if (val === true) {
this.dispatch('ElSelect', 'addOptionToValue', this);
}
} }
}, },
@ -122,10 +114,6 @@
this.parent.filteredOptionsCount++; this.parent.filteredOptionsCount++;
this.index = this.parent.options.indexOf(this); this.index = this.parent.options.indexOf(this);
if (this.currentSelected === true) {
this.dispatch('ElSelect', 'addOptionToValue', [this, true]);
}
this.$on('queryChange', this.queryChange); this.$on('queryChange', this.queryChange);
this.$on('handleGroupDisabled', this.handleGroupDisabled); this.$on('handleGroupDisabled', this.handleGroupDisabled);
this.$on('resetIndex', this.resetIndex); this.$on('resetIndex', this.resetIndex);

View File

@ -1,19 +1,26 @@
<template> <template>
<div <div
class="el-select" class="el-select"
v-clickoutside="handleClose" v-clickoutside="handleClose">
:class="{ 'is-multiple': multiple, 'is-small': size === 'small' }"> <div
<div class="el-select__tags" v-if="multiple" @click.stop="toggleMenu" ref="tags" :style="{ 'max-width': inputWidth - 32 + 'px' }"> class="el-select__tags"
v-if="multiple"
@click.stop="toggleMenu"
ref="tags"
:style="{ 'max-width': inputWidth - 32 + 'px' }">
<transition-group @after-leave="resetInputHeight"> <transition-group @after-leave="resetInputHeight">
<el-tag <el-tag
v-for="item in selected" v-for="item in selected"
:key="item" :key="item.value"
closable closable
:hit="item.hitState" :hit="item.hitState"
type="primary" type="primary"
@close="deleteTag($event, item)" @close="deleteTag($event, item)"
close-transition>{{ item.currentLabel }}</el-tag> close-transition>
{{ item.currentLabel }}
</el-tag>
</transition-group> </transition-group>
<input <input
type="text" type="text"
class="el-select__input" class="el-select__input"
@ -40,7 +47,7 @@
:disabled="disabled" :disabled="disabled"
:readonly="!filterable || multiple" :readonly="!filterable || multiple"
@focus="toggleMenu" @focus="toggleMenu"
@click="toggleMenu" @click="handleIconClick"
@mousedown.native="handleMouseDown" @mousedown.native="handleMouseDown"
@keyup.native="debouncedOnInputChange" @keyup.native="debouncedOnInputChange"
@keydown.native.down.prevent="navigateOptions('next')" @keydown.native.down.prevent="navigateOptions('next')"
@ -94,22 +101,18 @@
}, },
showCloseIcon() { showCloseIcon() {
let criteria = this.clearable && this.inputHovering && !this.multiple && this.options.indexOf(this.selected) > -1; let criteria = this.clearable &&
this.inputHovering &&
!this.multiple &&
this.value !== undefined &&
this.value !== '';
if (!this.$el) return false; if (!this.$el) return false;
this.$nextTick(() => { this.$nextTick(() => {
let icon = this.$el.querySelector('.el-input__icon'); let icon = this.$el.querySelector('.el-input__icon');
if (icon) { if (icon) {
if (criteria) { criteria ? addClass(icon, 'is-show-close') : removeClass(icon, 'is-show-close');
icon.addEventListener('click', this.deleteSelected);
addClass(icon, 'is-show-close');
} else {
icon.removeEventListener('click', this.deleteSelected);
removeClass(icon, 'is-show-close');
}
} }
}); });
return criteria; return criteria;
}, },
@ -143,7 +146,6 @@
props: { props: {
name: String, name: String,
value: {}, value: {},
size: String,
disabled: Boolean, disabled: Boolean,
clearable: Boolean, clearable: Boolean,
filterable: Boolean, filterable: Boolean,
@ -163,18 +165,16 @@
data() { data() {
return { return {
options: [], options: [],
selected: {}, selected: this.multiple ? [] : {},
isSelect: true, isSelect: true,
inputLength: 20, inputLength: 20,
inputWidth: 0, inputWidth: 0,
valueChangeBySelected: false,
cachedPlaceHolder: '', cachedPlaceHolder: '',
optionsCount: 0, optionsCount: 0,
filteredOptionsCount: 0, filteredOptionsCount: 0,
dropdownUl: null, dropdownUl: null,
visible: false, visible: false,
selectedLabel: '', selectedLabel: '',
selectInit: false,
hoverIndex: -1, hoverIndex: -1,
query: '', query: '',
voidRemoteQuery: false, voidRemoteQuery: false,
@ -191,79 +191,24 @@
}, },
value(val) { value(val) {
if (this.valueChangeBySelected) {
this.valueChangeBySelected = false;
return;
}
this.$nextTick(() => {
if (this.multiple && Array.isArray(val)) {
this.$nextTick(() => {
this.resetInputHeight();
});
this.selectedInit = true;
this.selected = [];
this.currentPlaceholder = this.cachedPlaceHolder;
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];
if (option) {
this.$emit('addOptionToValue', option);
} else {
this.selected = {};
this.selectedLabel = '';
}
}
this.resetHoverIndex();
});
},
selected(val, oldVal) {
if (this.multiple) { if (this.multiple) {
if (this.selected.length > 0) { this.resetInputHeight();
if (val.length > 0) {
this.currentPlaceholder = ''; this.currentPlaceholder = '';
} else { } else {
this.currentPlaceholder = this.cachedPlaceHolder; this.currentPlaceholder = this.cachedPlaceHolder;
} }
this.$nextTick(() => {
this.resetInputHeight();
});
if (this.selectedInit) {
this.selectedInit = false;
return;
} }
this.valueChangeBySelected = true; this.selected = this.getSelected();
const result = val.map(item => item.value);
this.$emit('input', result);
this.$emit('change', result);
this.dispatch('ElFormItem', 'el.form.change', val);
if (this.filterable) { if (this.filterable) {
this.query = '';
this.hoverIndex = -1;
this.$refs.input.focus();
this.inputLength = 20; this.inputLength = 20;
} }
} else { this.$emit('change', val);
if (this.selectedInit) { this.dispatch('ElFormItem', 'el.form.change', val);
this.selectedInit = false;
return;
}
if (val.value === oldVal.value) return;
this.$emit('input', val.value);
this.$emit('change', val.value);
}
}, },
query(val) { query(val) {
this.$nextTick(() => {
this.broadcast('ElSelectDropdown', 'updatePopper'); this.broadcast('ElSelectDropdown', 'updatePopper');
});
if (this.multiple && this.filterable) { if (this.multiple && this.filterable) {
this.resetInputHeight(); this.resetInputHeight();
} }
@ -283,16 +228,18 @@
visible(val) { visible(val) {
if (!val) { if (!val) {
this.$refs.reference.$el.querySelector('input').blur(); this.$refs.reference.$el.querySelector('input').blur();
if (this.$el.querySelector('.el-input__icon')) { let icon = this.$el.querySelector('.el-input__icon');
removeClass(this.$el.querySelector('.el-input__icon'), 'is-reverse'); if (icon) {
removeClass(icon, 'is-reverse');
} }
this.broadcast('ElSelectDropdown', 'destroyPopper'); this.broadcast('ElSelectDropdown', 'destroyPopper');
if (this.$refs.input) { if (this.$refs.input) {
this.$refs.input.blur(); this.$refs.input.blur();
} }
this.query = '';
this.resetHoverIndex(); this.resetHoverIndex();
if (!this.multiple) { if (!this.multiple) {
if (this.dropdownUl && this.selected.$el) { if (this.dropdownUl && this.selected && this.selected.$el) {
this.bottomOverflowBeforeHidden = this.selected.$el.getBoundingClientRect().bottom - this.$refs.popper.$el.getBoundingClientRect().bottom; this.bottomOverflowBeforeHidden = this.selected.$el.getBoundingClientRect().bottom - this.$refs.popper.$el.getBoundingClientRect().bottom;
} }
if (this.selected && this.selected.value) { if (this.selected && this.selected.value) {
@ -302,7 +249,7 @@
} else { } else {
let icon = this.$el.querySelector('.el-input__icon'); let icon = this.$el.querySelector('.el-input__icon');
if (icon && !hasClass(icon, 'el-icon-circle-close')) { if (icon && !hasClass(icon, 'el-icon-circle-close')) {
addClass(this.$el.querySelector('.el-input__icon'), 'is-reverse'); addClass(icon, 'is-reverse');
} }
this.broadcast('ElSelectDropdown', 'updatePopper'); this.broadcast('ElSelectDropdown', 'updatePopper');
if (this.filterable) { if (this.filterable) {
@ -327,10 +274,47 @@
options(val) { options(val) {
this.optionsAllDisabled = val.length === val.filter(item => item.disabled === true).length; this.optionsAllDisabled = val.length === val.filter(item => item.disabled === true).length;
// remote !multiple
// this.selected = this.getSelected();
if (this.multiple) {
this.resetInputHeight();
}
} }
}, },
methods: { methods: {
getSelected() {
if (this.multiple) {
let result = [];
this.value.forEach(value => {
let option = this.options.filter(option => option.value === value)[0];
if (option) {
result.push(option);
} else {
result.push({
value: this.value,
currentLabel: value,
hitState: false
});
}
});
return result;
} else {
let option = this.options.filter(option => option.value === this.value)[0] ||
{ value: this.value, currentLabel: this.value };
this.selectedLabel = option.currentLabel;
return option;
}
},
handleIconClick(event) {
if (this.iconClass === 'circle-close') {
this.deleteSelected(event);
} else {
this.toggleMenu();
}
},
handleMouseDown(event) { handleMouseDown(event) {
if (event.target.tagName !== 'INPUT') return; if (event.target.tagName !== 'INPUT') return;
if (this.visible) { if (this.visible) {
@ -363,22 +347,7 @@
deletePrevTag(e) { deletePrevTag(e) {
if (e.target.value.length <= 0 && !this.toggleLastOptionHitState()) { if (e.target.value.length <= 0 && !this.toggleLastOptionHitState()) {
this.selected.pop(); this.value.pop();
}
},
addOptionToValue(option, init) {
if (this.multiple) {
if (this.selected.indexOf(option) === -1 && (this.remote ? this.value.indexOf(option.value) === -1 : true)) {
this.selectedInit = !!init;
this.selected.push(option);
this.resetHoverIndex();
}
} else {
this.selectedInit = !!init;
this.selected = option;
this.selectedLabel = option.currentLabel;
this.hoverIndex = option.index;
} }
}, },
@ -418,20 +387,19 @@
handleOptionSelect(option) { handleOptionSelect(option) {
if (!this.multiple) { if (!this.multiple) {
this.selected = option; this.$emit('input', option.value);
this.selectedLabel = option.currentLabel;
this.visible = false; this.visible = false;
} else { } else {
let optionIndex = -1; let optionIndex = -1;
this.selected.forEach((item, index) => { this.value.forEach((item, index) => {
if (item === option || item.currentValue === option.currentValue) { if (item === option.value) {
optionIndex = index; optionIndex = index;
} }
}); });
if (optionIndex > -1) { if (optionIndex > -1) {
this.selected.splice(optionIndex, 1); this.value.splice(optionIndex, 1);
} else { } else {
this.selected.push(option); this.value.push(option.value);
} }
} }
}, },
@ -497,8 +465,6 @@
deleteSelected(event) { deleteSelected(event) {
event.stopPropagation(); event.stopPropagation();
this.selected = {};
this.selectedLabel = '';
this.$emit('input', ''); this.$emit('input', '');
this.$emit('change', ''); this.$emit('change', '');
this.visible = false; this.visible = false;
@ -507,7 +473,7 @@
deleteTag(event, tag) { deleteTag(event, tag) {
let index = this.selected.indexOf(tag); let index = this.selected.indexOf(tag);
if (index > -1) { if (index > -1) {
this.selected.splice(index, 1); this.value.splice(index, 1);
} }
event.stopPropagation(); event.stopPropagation();
}, },
@ -535,9 +501,11 @@
created() { created() {
this.cachedPlaceHolder = this.currentPlaceholder = this.placeholder; this.cachedPlaceHolder = this.currentPlaceholder = this.placeholder;
if (this.multiple) { if (this.multiple && !Array.isArray(this.value)) {
this.selectedInit = true; this.$emit('input', []);
this.selected = []; }
if (!this.multiple && !this.value) {
this.$emit('input', '');
} }
if (this.remote) { if (this.remote) {
this.voidRemoteQuery = true; this.voidRemoteQuery = true;
@ -547,20 +515,15 @@
this.onInputChange(); this.onInputChange();
}); });
this.$on('addOptionToValue', this.addOptionToValue);
this.$on('handleOptionClick', this.handleOptionSelect); this.$on('handleOptionClick', this.handleOptionSelect);
this.$on('onOptionDestroy', this.onOptionDestroy); this.$on('onOptionDestroy', this.onOptionDestroy);
}, },
mounted() { mounted() {
addResizeListener(this.$el, this.resetInputWidth); addResizeListener(this.$el, this.resetInputWidth);
if (this.remote && this.multiple && Array.isArray(this.value)) { this.selected = this.getSelected();
this.selected = this.options.reduce((prev, curr) => { if (this.remote && this.multiple) {
return this.value.indexOf(curr.value) > -1 ? prev.concat(curr) : prev;
}, []);
this.$nextTick(() => {
this.resetInputHeight(); this.resetInputHeight();
});
} }
this.$nextTick(() => { this.$nextTick(() => {
if (this.$refs.reference.$el) { if (this.$refs.reference.$el) {

View File

@ -23,6 +23,8 @@
width: 110px; width: 110px;
input { input {
padding-right: 25px; padding-right: 25px;
border-radius: var(--border-radius-small);
height: 28px;
} }
} }

View File

@ -12,13 +12,6 @@
display: block; display: block;
position: relative; position: relative;
@when small {
& input {
border-radius: var(--border-radius-small);
height: 28px;
}
}
&:hover { &:hover {
.el-input__inner { .el-input__inner {
border-color: var(--select-border-color-hover); border-color: var(--select-border-color-hover);

View File

@ -387,6 +387,7 @@ describe('Select', () => {
remoteMethod remoteMethod
}); });
const select = vm.$children[0]; const select = vm.$children[0];
vm.$nextTick(() => {
select.query = '面'; select.query = '面';
setTimeout(() => { setTimeout(() => {
expect(select.filteredOptionsCount).to.equal(1); expect(select.filteredOptionsCount).to.equal(1);
@ -404,4 +405,5 @@ describe('Select', () => {
}); });
}, 250); }, 250);
}); });
});
}); });