Select: add allow-create doc and test

This commit is contained in:
Leopoldthecoder 2016-11-30 23:17:53 +08:00
parent 1942e63453
commit 2d07391d3c
4 changed files with 193 additions and 48 deletions

View File

@ -62,6 +62,16 @@
}]
}],
options4: [],
options5: [{
value: 'HTML',
label: 'HTML'
}, {
value: 'CSS',
label: 'CSS'
}, {
value: 'JavaScript',
label: 'JavaScript'
}],
cities: [{
value: 'Beijing',
label: 'Beijing'
@ -87,9 +97,10 @@
value4: '',
value5: [],
value6: '',
value7: [],
value7: '',
value8: '',
value9: [],
value10: [],
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"]
};
@ -569,6 +580,47 @@ Enter keywords and search data from server.
```
:::
### Create new items
Create and select new items that are not included in select options
:::demo By using the `allow-create` attribute, users can create new items by typing in the input box. Note that for `allow-create` to work, `filterable` must be `true`.
```html
<template>
<el-select
v-model="value10"
multiple
filterable
allow-create
placeholder="Choose tags for your article">
<el-option
v-for="item in options5"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</template>
<script>
export default {
data() {
return {
options5: [{
value: 'HTML',
label: 'HTML'
}, {
value: 'CSS',
label: 'CSS'
}, {
value: 'JavaScript',
label: 'JavaScript'
}],
value10: []
}
}
}
</script>
```
:::
### Select Attributes
| Attribute | Description | Type | Accepted Values | Default |
|---------- |-------------- |---------- |-------------------------------- |-------- |
@ -579,6 +631,7 @@ Enter keywords and search data from server.
| name | the name attribute of select input | string | — | — |
| placeholder | placeholder | string | — | Select |
| filterable | whether Select is filterable | boolean | — | false |
| allow-create | whether creating new items is allowed. To use this, `filterable` must be true | boolean | — | false |
| filter-method | custom filter method | function | — | — |
| remote | whether options are loaded from server | boolean | — | false |
| remote-method | custom remote search method | function | — | — |

View File

@ -62,6 +62,16 @@
}]
}],
options4: [],
options5: [{
value: 'HTML',
label: 'HTML'
}, {
value: 'CSS',
label: 'CSS'
}, {
value: 'JavaScript',
label: 'JavaScript'
}],
cities: [{
value: 'Beijing',
label: '北京'
@ -90,6 +100,7 @@
value7: '',
value8: '',
value9: '',
value10: [],
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"]
};
@ -585,6 +596,47 @@
```
:::
### 创建条目
可以创建并选中选项中不存在的条目
:::demo 使用`allow-create`属性即可通过在输入框中输入文字来创建新的条目。注意此时`filterable`必须为真。
```html
<template>
<el-select
v-model="value10"
multiple
filterable
allow-create
placeholder="请选择文章标签">
<el-option
v-for="item in options5"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</template>
<script>
export default {
data() {
return {
options5: [{
value: 'HTML',
label: 'HTML'
}, {
value: 'CSS',
label: 'CSS'
}, {
value: 'JavaScript',
label: 'JavaScript'
}],
value10: []
}
}
}
</script>
```
:::
### Select Attributes
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|---------- |-------------- |---------- |-------------------------------- |-------- |
@ -595,6 +647,7 @@
| name | select input 的 name 属性 | string | — | — |
| placeholder | 占位符 | string | — | 请选择 |
| filterable | 是否可搜索 | boolean | — | false |
| allow-create | 是否允许用户创建新条目,需配合 `filterable` 使用 | boolean | — | false |
| filter-method | 自定义过滤方法 | function | — | — |
| remote | 是否为远程搜索 | boolean | — | false |
| remote-method | 远程搜索方法 | function | — | — |

View File

@ -85,6 +85,7 @@
import Locale from 'element-ui/src/mixins/locale';
import ElInput from 'element-ui/packages/input';
import ElSelectMenu from './select-dropdown.vue';
import ElOption from './option.vue';
import ElTag from 'element-ui/packages/tag';
import debounce from 'throttle-debounce/debounce';
import Clickoutside from 'element-ui/src/utils/clickoutside';
@ -143,7 +144,8 @@
},
showNewOption() {
let hasExistingOption = this.options.filter(option => !option.created).some(option => option.currentLabel === this.query);
let hasExistingOption = this.options.filter(option => !option.created)
.some(option => option.currentLabel === this.query);
return this.filterable && this.allowCreate && this.query !== '' && !hasExistingOption;
}
},
@ -151,6 +153,7 @@
components: {
ElInput,
ElSelectMenu,
ElOption,
ElTag
},
@ -248,10 +251,7 @@
visible(val) {
if (!val) {
this.$refs.reference.$el.querySelector('input').blur();
let icon = this.$el.querySelector('.el-input__icon');
if (icon) {
removeClass(icon, 'is-reverse');
}
this.handleIconHide();
this.broadcast('ElSelectDropdown', 'destroyPopper');
if (this.$refs.input) {
this.$refs.input.blur();
@ -259,21 +259,13 @@
this.query = '';
this.resetHoverIndex();
if (!this.multiple) {
if (this.dropdownUl && this.selected && this.selected.$el) {
let selectedRect = this.selected.$el.getBoundingClientRect();
let popperRect = this.$refs.popper.$el.getBoundingClientRect();
this.bottomOverflowBeforeHidden = selectedRect.bottom - popperRect.bottom;
this.topOverflowBeforeHidden = selectedRect.top - popperRect.top;
}
this.getOverflows();
if (this.selected && this.selected.value) {
this.selectedLabel = this.selected.currentLabel;
}
}
} else {
let icon = this.$el.querySelector('.el-input__icon');
if (icon && !hasClass(icon, 'el-icon-circle-close')) {
addClass(icon, 'is-reverse');
}
this.handleIconShow();
this.broadcast('ElSelectDropdown', 'updatePopper');
if (this.filterable) {
this.query = this.selectedLabel;
@ -288,15 +280,7 @@
this.dropdownUl = [].filter.call(dropdownChildNodes, item => item.tagName === 'UL')[0];
}
if (!this.multiple && this.dropdownUl) {
if (this.bottomOverflowBeforeHidden > 0) {
this.$nextTick(() => {
this.dropdownUl.scrollTop += this.bottomOverflowBeforeHidden;
});
} else if (this.topOverflowBeforeHidden < 0) {
this.$nextTick(() => {
this.dropdownUl.scrollTop += this.topOverflowBeforeHidden;
});
}
this.setOverflow();
}
}
},
@ -312,30 +296,64 @@
},
methods: {
handleIconHide() {
let icon = this.$el.querySelector('.el-input__icon');
if (icon) {
removeClass(icon, 'is-reverse');
}
},
handleIconShow() {
let icon = this.$el.querySelector('.el-input__icon');
if (icon && !hasClass(icon, 'el-icon-circle-close')) {
addClass(icon, 'is-reverse');
}
},
getOverflows() {
if (this.dropdownUl && this.selected && this.selected.$el) {
let selectedRect = this.selected.$el.getBoundingClientRect();
let popperRect = this.$refs.popper.$el.getBoundingClientRect();
this.bottomOverflowBeforeHidden = selectedRect.bottom - popperRect.bottom;
this.topOverflowBeforeHidden = selectedRect.top - popperRect.top;
}
},
setOverflow() {
if (this.bottomOverflowBeforeHidden > 0) {
this.$nextTick(() => {
this.dropdownUl.scrollTop += this.bottomOverflowBeforeHidden;
});
} else if (this.topOverflowBeforeHidden < 0) {
this.$nextTick(() => {
this.dropdownUl.scrollTop += this.topOverflowBeforeHidden;
});
}
},
getSelected() {
if (this.multiple) {
let result = [];
if (Array.isArray(this.value)) {
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 {
if (!this.multiple) {
let option = this.options.filter(option => option.value === this.value)[0] ||
{ value: this.value, currentLabel: this.value };
{ value: this.value, currentLabel: this.value };
this.selectedLabel = option.currentLabel;
return option;
}
let result = [];
if (Array.isArray(this.value)) {
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;
},
handleIconClick(event) {
@ -484,8 +502,10 @@
},
resetScrollTop() {
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;
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;
if (bottomOverflowDistance > 0) {
this.dropdownUl.scrollTop += bottomOverflowDistance;
}
@ -541,7 +561,7 @@
if (this.multiple && !Array.isArray(this.value)) {
this.$emit('input', []);
}
if (!this.multiple && !this.value) {
if (!this.multiple && (!this.value || Array.isArray(this.value))) {
this.$emit('input', '');
}
if (this.remote) {

View File

@ -3,7 +3,7 @@ import Select from 'packages/select';
describe('Select', () => {
const getSelectVm = (configs = {}, options) => {
['multiple', 'clearable', 'filterable', 'remote'].forEach(config => {
['multiple', 'clearable', 'filterable', 'allowCreate', 'remote'].forEach(config => {
configs[config] = configs[config] || false;
});
configs.multipleLimit = configs.multipleLimit || 0;
@ -39,6 +39,7 @@ describe('Select', () => {
:multiple-limit="multipleLimit"
:clearable="clearable"
:filterable="filterable"
:allow-create="allowCreate"
:filterMethod="filterMethod"
:remote="remote"
:loading="loading"
@ -60,6 +61,7 @@ describe('Select', () => {
multipleLimit: configs.multipleLimit,
clearable: configs.clearable,
filterable: configs.filterable,
allowCreate: configs.allowCreate,
loading: false,
filterMethod: configs.filterMethod && configs.filterMethod(this),
remote: configs.remote,
@ -352,6 +354,23 @@ describe('Select', () => {
}, 100);
});
it('allow create', done => {
vm = getSelectVm({ filterable: true, allowCreate: true });
const select = vm.$children[0];
select.selectedLabel = 'new';
select.onInputChange();
select.visible = true;
setTimeout(() => {
const options = document.querySelectorAll('.el-select-dropdown__item span');
const target = [].filter.call(options, option => option.innerText === 'new');
target[0].click();
setTimeout(() => {
expect(select.value.indexOf('new') > -1).to.true;
done();
}, 50);
}, 50);
});
it('multiple select', done => {
vm = getSelectVm({ multiple: true });
const options = vm.$el.querySelectorAll('.el-select-dropdown__item');