Sfoglia il codice sorgente

update select and input

qingwei.li 9 anni fa
parent
commit
a733f7835e

+ 29 - 36
examples/docs/select.md

@@ -1,8 +1,5 @@
 <script>
   export default {
-    created() {
-      this.template = '<span>label: {{ label }}, value: {{ value }}</span>';
-    },
     data() {
       return {
         options: [{
@@ -128,13 +125,13 @@
 
 ## 基本用法
 
-<el-select :value.sync="value">
+<el-select v-model="value">
   <el-option v-for="item in options" :label="item.label" :value="item.value"></el-option>
 </el-select>
 
 ```html
 <template>
-<el-select :value.sync="value">
+<el-select v-model="value">
   <el-option
     v-for="item in options"
     :label="item.label"
@@ -172,13 +169,13 @@
 
 ## 禁用状态
 
-<el-select :value.sync="value2" disabled>
+<el-select v-model="value2" disabled>
   <el-option v-for="item in options" :label="item.label" :value="item.value"></el-option>
 </el-select>
 
 ```html
 <template>
-  <el-select :value.sync="value2" disabled>
+  <el-select v-model="value2" disabled>
     <el-option
       v-for="item in options"
       :label="item.label"
@@ -216,13 +213,13 @@
 
 ## 有禁用选项
 
-<el-select :value.sync="value3">
+<el-select v-model="value3">
   <el-option v-for="item in options2" :label="item.label" :value="item.value" :disabled="item.disabled"></el-option>
 </el-select>
 
 ```html
 <template>
-  <el-select :value.sync="value3">
+  <el-select v-model="value3">
     <el-option
       v-for="item in options2"
       :label="item.label"
@@ -262,13 +259,13 @@
 
 ## 可清空单选
 
-<el-select :value.sync="value4" clearable>
+<el-select v-model="value4" clearable>
   <el-option v-for="item in options" :label="item.label" :value="item.value"></el-option>
 </el-select>
 
 ```html
 <template>
-<el-select :value.sync="value4" clearable>
+<el-select v-model="value4" clearable>
   <el-option
     v-for="item in options"
     :label="item.label"
@@ -306,7 +303,7 @@
 
 ## 指定初始被选项
 
-<el-select :value.sync="value5">
+<el-select v-model="value5">
   <el-option v-for="item in options" :label="item.label" :value="item.value" :selected="item.value === '选项2'"></el-option>
 </el-select>
 
@@ -314,7 +311,7 @@
 
 ```html
 <template>
-  <el-select :value.sync="value5">
+  <el-select v-model="value5">
     <el-option
       v-for="item in options"
       :label="item.label"
@@ -355,7 +352,7 @@
 
 ```html
 <template>
-  <el-select :value.sync="value5">
+  <el-select v-model="value5">
     <el-option
       v-for="item in options"
       :label="item.label"
@@ -393,32 +390,29 @@
 
 ## 自定义模板
 
-<el-select :value.sync="value6">
+<el-select v-model="value6">
   <el-option
     v-for="item in options"
     :label="item.label"
-    :value="item.value"
-    :template="template">
+    :value="item.value">
+    <span>label: {{ item.label }}, value: {{ item.value }}</span>
   </el-option>
 </el-select>
 
 ```html
 <template>
-  <el-select :value.sync="value6">
+  <el-select v-model="value6">
     <el-option
       v-for="item in options"
       :label="item.label"
-      :value="item.value"
-      :template="optionTemplate">
+      :value="item.value">
+      <span>label: {{ item.label }}, value: {{ item.value }}</span>
     </el-option>
   </el-select>
 </template>
 
 <script>
   export default {
-    cerated () {
-      this.optionTemplate = '<span>label: {{ label }}, value: {{ value }}</span>';
-    },
     data() {
       return {
         options: [{
@@ -446,7 +440,7 @@
 
 ## 多选
 
-<el-select :value.sync="value7" multiple>
+<el-select v-model="value7" multiple>
   <el-option
     v-for="item in options"
     :label="item.label"
@@ -456,7 +450,7 @@
 
 ```html
 <template>
-  <el-select :value.sync="value7" multiple>
+  <el-select v-model="value7" multiple>
     <el-option
       v-for="item in options"
       :label="item.label"
@@ -494,13 +488,13 @@
 
 ## 自定义宽度
 
-<el-select :value.sync="value8" :width="300" :dropdown-width="350">
+<el-select v-model="value8" :width="300" :dropdown-width="350">
   <el-option v-for="item in options" :label="item.label" :value="item.value"></el-option>
 </el-select>
 
 ```html
 <template>
-  <el-select :value.sync="value8" :width="300" :dropdown-width="350">
+  <el-select v-model="value8" :width="300" :dropdown-width="350">
     <el-option
       v-for="item in options"
       :label="item.label"
@@ -538,7 +532,7 @@
 
 ## 分组
 
-<el-select :value.sync="value9">
+<el-select v-model="value9">
   <el-option-group v-for="group in options3" :label="group.label">
     <el-option v-for="item in group.options" :label="item.label" :value="item.value"></el-option>
   </el-option-group>
@@ -546,7 +540,7 @@
 
 ```html
 <template>
-  <el-select :value.sync="value9">
+  <el-select v-model="value9">
     <el-option-group v-for="group in options3" :label="group.label">
       <el-option
         v-for="item in group.options"
@@ -592,13 +586,13 @@
 
 ## 选项较多时的交互
 
-<el-select :value.sync="value10">
+<el-select v-model="value10">
   <el-option v-for="item in options4" :label="item.label" :value="item.value"></el-option>
 </el-select>
 
 ```html
 <template>
-  <el-select :value.sync="value10">
+  <el-select v-model="value10">
     <el-option
       v-for="item in options4"
       :label="item.label"
@@ -654,13 +648,13 @@
 
 ## 可搜索
 
-<el-select :value.sync="value11" filterable>
+<el-select v-model="value11" filterable>
   <el-option v-for="item in options" :label="item.label" :value="item.value"></el-option>
 </el-select>
 
 ```html
 <template>
-  <el-select :value.sync="value11" filterable>
+  <el-select v-model="value11" filterable>
     <el-option
       v-for="item in options"
       :label="item.label"
@@ -698,14 +692,14 @@
 
 ## 服务端搜索
 
-<el-select :value.sync="value12" multiple filterable remote :remote-method="remoteMethod" :loading="loading">
+<el-select v-model="value12" multiple filterable remote :remote-method="remoteMethod" :loading="loading">
   <el-option v-for="item in options5" :label="item.label" :value="item.value"></el-option>
 </el-select>
 
 ```html
 <template>
   <el-select
-    :value.sync="value12"
+    v-model="value12"
     multiple
     filterable
     remote
@@ -776,4 +770,3 @@
 | label | 选项的标签,若不设置则默认与 `value` 相同 | string/number | | |
 | disabled | 是否禁用该选项 | boolean | | false |
 | selected | 选项是否被初始选中 | boolean | | false |
-| template | 选项的自定义模板 | String | | `<span>{{ label }}</span>`  |

+ 31 - 16
packages/input/src/input.vue

@@ -12,20 +12,20 @@
         :name="name"
         class="el-input__inner"
         :placeholder="placeholder"
-        v-model="value"
+        v-model="currentValue"
         :disabled="disabled"
         :readonly="readonly"
-        @focus="$emit('onfocus', value)"
+        @focus="$emit('onfocus', currentValue)"
         @blur="handleBlur"
         :number="number"
         :maxlength="maxlength"
         :minlength="minlength"
         :autocomplete="autoComplete"
-        v-el:input
+        ref="input"
       >
     </template>
     <!-- 写成垂直的方式会导致 placeholder 失效, 蜜汁bug -->
-    <textarea v-else v-model="value" class="el-textarea__inner" :name="name" :placeholder="placeholder" :disabled="disabled" :readonly="readonly" @focus="$emit('onfocus', value)" @blur="handleBlur"></textarea>
+    <textarea v-else v-model="currentValue" class="el-textarea__inner" :name="name" :placeholder="placeholder" :disabled="disabled" :readonly="readonly" @focus="$emit('onfocus', val)" @blur="handleBlur"></textarea>
   </div>
 </template>
 <script>
@@ -37,9 +37,7 @@
     mixins: [emitter],
 
     props: {
-      value: {
-        required: true
-      },
+      value: {},
       placeholder: {
         type: String,
         default: ''
@@ -79,26 +77,43 @@
       maxlength: Number,
       minlength: Number
     },
-    events: {
-      inputSelect() {
-        this.$els.input.select();
-      }
-    },
+
     methods: {
       handleBlur(event) {
-        this.$emit('onblur', this.value);
-        this.dispatch('form-item', 'el.form.blur', [this.value]);
+        this.$emit('onblur', this.currentValue);
+        this.dispatch('form-item', 'el.form.blur', [this.currentValue]);
+      },
+
+      inputSelect() {
+        this.$refs.input.select();
       }
     },
+
+    data() {
+      return {
+        currentValue: ''
+      };
+    },
+
+    created() {
+      this.$on('inputSelect', this.inputSelect);
+    },
+
     computed: {
       validating() {
         return this.$parent.validating;
       }
     },
+
     watch: {
       'value'(val) {
-        this.$emit('onchange', this.value);
-        this.dispatch('form-item', 'el.form.change', this.value);
+        this.currentValue = val;
+      },
+
+      'currentValue'(val) {
+        this.$emit('input', val);
+        this.$emit('onchange', val);
+        this.dispatch('form-item', 'el.form.change', val);
       }
     }
   };

+ 10 - 8
packages/select-dropdown/src/select-dropdown.vue

@@ -28,14 +28,16 @@
         if (this.popper) {
           this.popper.update();
         } else {
-          this.popper = new Popper(this.$parent.$els.reference, this.$el, {
-            gpuAcceleration: false,
-            placement: 'bottom-start',
-            boundariesPadding: 0,
-            forceAbsolute: true
-          });
-          this.popper.onCreate(popper => {
-            this.resetTransformOrigin(popper);
+          this.$nextTick(() => {
+            this.popper = new Popper(this.$parent.$refs.reference.$el, this.$el, {
+              gpuAcceleration: false,
+              placement: 'bottom-start',
+              boundariesPadding: 0,
+              forceAbsolute: true
+            });
+            this.popper.onCreate(popper => {
+              this.resetTransformOrigin(popper);
+            });
           });
         }
       },

+ 8 - 6
packages/select/src/option-group.vue

@@ -1,10 +1,12 @@
 <template>
-  <li class="el-select-group__title">{{ label }}</li>
-  <li>
-    <ul class="el-select-group">
-      <slot></slot>
-    </ul>
-  </li>
+  <ul class="el-select-group__wrap">
+    <li class="el-select-group__title">{{ label }}</li>
+    <li>
+      <ul class="el-select-group">
+        <slot></slot>
+      </ul>
+    </li>
+  </ul>
 </template>
 
 <script type="text/babel">

+ 11 - 16
packages/select/src/option.vue

@@ -5,7 +5,9 @@
     class="el-select-dropdown__item"
     v-show="queryPassed"
     :class="{ 'selected': itemSelected(), 'is-disabled': disabled, 'hover': parent.hoverIndex === index }">
-    <include></include>
+    <slot>
+      <span>{{ label }}</span>
+    </slot>
   </li>
 </template>
 
@@ -32,10 +34,6 @@
       disabled: {
         type: Boolean,
         default: false
-      },
-      template: {
-        type: String,
-        default: '<span>{{ label }}</span>'
       }
     },
 
@@ -48,18 +46,20 @@
       };
     },
 
+    computed: {
+      currentSelected() {
+        return this.selected || (this.$parent.multiple ? this.$parent.value.indexOf(this.value) > -1 : this.$parent.value === this.value);
+      }
+    },
+
     watch: {
-      selected(val) {
+      currentSelected(val) {
         if (val === true) {
           this.dispatch('select', 'addOptionToValue', this);
         }
       }
     },
 
-    partials: {
-      'el-selectmenu-default': '<span>{{ label }}</span>'
-    },
-
     methods: {
       disableOptions() {
         this.disabled = true;
@@ -94,22 +94,17 @@
     },
 
     created() {
-      // Vue 1.x 需要禁用缓存;2.0 不需要配置
-      this.$options._linkerCachable = false;
       this.parent = this.$parent;
       while (!this.parent.isSelect) {
         this.parent = this.parent.$parent;
       }
       this.label = this.label || ((typeof this.value === 'string' || typeof this.value === 'number') ? this.value : '');
-      this.selected = this.selected || (this.parent.multiple ? this.parent.value.indexOf(this.value) > -1 : this.parent.value === this.value);
       this.parent.options.push(this);
       this.parent.optionsCount++;
       this.parent.filteredOptionsCount++;
       this.index = this.parent.options.indexOf(this);
 
-      this.$options.template = this.$options.template.replace(/<include><\/include>/g, this.template);
-
-      if (this.selected === true) {
+      if (this.currentSelected === true) {
         this.dispatch('select', 'addOptionToValue', this);
       }
 

+ 64 - 42
packages/select/src/select.vue

@@ -1,12 +1,12 @@
 <template>
   <div class="el-select" :class="{ 'is-multiple': multiple, 'is-small': size === 'small' }">
-    <div class="el-select__tags" v-if="multiple" @click.stop="toggleMenu" v-el:tags :style="{ 'max-width': width - 36 + 'px' }">
+    <div class="el-select__tags" v-if="multiple" @click.stop="toggleMenu" ref="tags" :style="{ 'max-width': inputWidth - 36 + 'px' }">
       <el-tag
       v-for="item in selected"
       closable
       :hit="item.hitState"
       type="primary"
-      @click="deleteTag($event, item)"
+      @click.native="deleteTag($event, item)"
       close-transition>{{ item.label }}</el-tag>
 
       <input
@@ -23,33 +23,33 @@
         :debounce="remote ? 300 : 0"
         v-if="filterable"
         :style="{ width: inputLength + 'px' }"
-        v-el:input>
+        ref="input">
     </div>
     <el-input
-      v-el:reference
-      :value.sync="selectedLabel"
+      ref="reference"
+      v-model="selectedLabel"
       type="text"
-      :placeholder="placeholder"
+      :placeholder="currentPlaceholder"
       :name="name"
       :disabled="disabled"
       :readonly="!filterable || multiple"
-      @click="toggleMenu"
-      @keyup="debouncedOnInputChange"
-      @keydown.down.prevent="navigateOptions('next')"
-      @keydown.up.prevent="navigateOptions('prev')"
-      @keydown.enter.prevent="selectOption"
-      @keydown.esc.prevent="visible = false"
-      @keydown.tab="visible = false"
-      @mouseenter="inputHovering = true"
-      @mouseleave="inputHovering = false"
+      @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="showCloseIcon ? 'circle-close' : 'caret-top'"
-      :style="{ 'width': width + 'px' }"
-      v-element-clickoutside="visible = false">
+      :style="{ 'width': inputWidth + 'px' }"
+      v-element-clickoutside="handleClose">
     </el-input>
     <el-select-menu
-      v-el:popper
+      ref="popper"
       v-show="visible && nodataText !== false"
-      transition="md-fade-bottom"
+      transition="fade"
       :style="{ 'width': dropdownWidth ? dropdownWidth + 'px' : '100%' }">
       <ul class="el-select-dropdown__list" v-show="options.length > 0 && filteredOptionsCount > 0">
         <slot></slot>
@@ -65,6 +65,7 @@
   import ElSelectMenu from 'packages/select-dropdown/index.js';
   import ElTag from 'packages/tag/index.js';
   import debounce from 'throttle-debounce/debounce';
+  import Clickoutside from 'main/utils/clickoutside';
 
   export default {
     mixins: [emitter],
@@ -80,6 +81,8 @@
 
       showCloseIcon() {
         let criteria = this.clearable && this.inputHovering && !this.multiple && this.options.indexOf(this.selected) > -1;
+        if (!this.$el) return false;
+
         let icon = this.$el.querySelector('.el-input__icon');
         if (icon) {
           if (criteria) {
@@ -109,6 +112,14 @@
           }
         }
         return null;
+      },
+
+      inputWidth() {
+        if (!this.width) {
+          return this.multiple ? 220 : 180;
+        }
+
+        return this.width;
       }
     },
 
@@ -119,7 +130,7 @@
     },
 
     directives: {
-      ElementClickoutside: require('vue-clickoutside')
+      ElementClickoutside: Clickoutside
     },
 
     props: {
@@ -161,7 +172,8 @@
         voidRemoteQuery: false,
         bottomOverflowBeforeHidden: 0,
         optionsAllDisabled: false,
-        inputHovering: false
+        inputHovering: false,
+        currentPlaceholder: ''
       };
     },
 
@@ -172,7 +184,12 @@
         }
       },
 
+      placeholder(val) {
+        this.currentPlaceholder = val;
+      },
+
       value(val) {
+        this.$emit('input', val);
         this.$emit('change', val);
         if (this.valueChangeBySelected) {
           this.valueChangeBySelected = false;
@@ -211,11 +228,11 @@
             return;
           }
           this.valueChangeBySelected = true;
-          this.value = val.map(item => item.value);
+          this.$emit('input', val.map(item => item.value));
           if (this.selected.length > 0) {
-            this.placeholder = '';
+            this.currentPlaceholder = '';
           } else {
-            this.placeholder = this.cachedPlaceHolder;
+            this.currentPlaceholder = this.cachedPlaceHolder;
           }
           this.$nextTick(() => {
             this.resetInputHeight();
@@ -223,17 +240,20 @@
           if (this.filterable) {
             this.query = '';
             this.hoverIndex = -1;
-            this.$els.input.focus();
+            this.$refs.input.focus();
             this.inputLength = 20;
           }
         } else {
           if (val.value) {
-            this.value = val.value;
+            this.$emit('input', val.value);
           }
         }
       },
 
       query(val) {
+        this.$nextTick(() => {
+          this.broadcast('select-dropdown', 'updatePopper');
+        });
         if (this.multiple && this.filterable) {
           this.resetInputHeight();
         }
@@ -258,13 +278,13 @@
         if (!val) {
           this.$el.querySelector('.el-input__icon').classList.remove('is-reverse');
           this.broadcast('select-dropdown', 'destroyPopper');
-          if (this.$els.input) {
-            this.$els.input.blur();
+          if (this.$refs.input) {
+            this.$refs.input.blur();
           }
           this.resetHoverIndex();
           if (!this.multiple) {
             if (this.dropdownUl && this.selected.$el) {
-              this.bottomOverflowBeforeHidden = this.selected.$el.getBoundingClientRect().bottom - this.$els.popper.getBoundingClientRect().bottom;
+              this.bottomOverflowBeforeHidden = this.selected.$el.getBoundingClientRect().bottom - this.$refs.popper.$el.getBoundingClientRect().bottom;
             }
             if (this.selected && this.selected.value) {
               this.selectedLabel = this.selected.label;
@@ -282,13 +302,13 @@
           if (this.filterable) {
             this.query = this.selectedLabel;
             if (this.multiple) {
-              this.$els.input.focus();
+              this.$refs.input.focus();
             } else {
               this.broadcast('input', 'inputSelect');
             }
           }
           if (!this.dropdownUl) {
-            let dropdownChildNodes = this.$els.popper.childNodes;
+            let dropdownChildNodes = this.$refs.popper.$el.childNodes;
             this.dropdownUl = [].filter.call(dropdownChildNodes, item => item.tagName === 'UL')[0];
           }
           if (!this.multiple && this.dropdownUl) {
@@ -305,6 +325,10 @@
     },
 
     methods: {
+      handleClose() {
+        this.visible = false;
+      },
+
       toggleLastOptionHitState(hit) {
         if (!Array.isArray(this.selected)) return;
         const option = this.selected[this.selected.length - 1];
@@ -340,21 +364,21 @@
       },
 
       managePlaceholder() {
-        if (this.placeholder !== '') {
-          this.placeholder = this.$els.input.value ? '' : this.cachedPlaceHolder;
+        if (this.currentPlaceholder !== '') {
+          this.currentPlaceholder = this.$refs.input.value ? '' : this.cachedPlaceHolder;
         }
       },
 
       resetInputState(e) {
         if (e.keyCode !== 8) this.toggleLastOptionHitState(false);
-        this.inputLength = this.$els.input.value.length * 12 + 20;
+        this.inputLength = this.$refs.input.value.length * 12 + 20;
       },
 
       resetInputHeight() {
         this.$nextTick(() => {
-          let inputChildNodes = this.$els.reference.childNodes;
+          let inputChildNodes = this.$refs.reference.$el.childNodes;
           let input = [].filter.call(inputChildNodes, item => item.tagName === 'INPUT')[0];
-          input.style.height = Math.max(this.$els.tags.clientHeight + 6, this.size === 'small' ? 28 : 36) + 'px';
+          input.style.height = Math.max(this.$refs.tags.clientHeight + 6, this.size === 'small' ? 28 : 36) + 'px';
           this.broadcast('select-dropdown', 'updatePopper');
         });
       },
@@ -429,8 +453,8 @@
       },
 
       resetScrollTop() {
-        let bottomOverflowDistance = this.options[this.hoverIndex].$el.getBoundingClientRect().bottom - this.$els.popper.getBoundingClientRect().bottom;
-        let topOverflowDistance = this.options[this.hoverIndex].$el.getBoundingClientRect().top - this.$els.popper.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;
         }
@@ -468,14 +492,12 @@
     },
 
     created() {
-      this.cachedPlaceHolder = this.placeholder;
+      this.cachedPlaceHolder = this.currentPlaceholder = this.placeholder;
       if (this.multiple) {
         this.selectedInit = true;
         this.selected = [];
       }
-      if (!this.width) {
-        this.width = this.multiple ? 220 : 180;
-      }
+
       this.debouncedOnInputChange = debounce(this.debounce, () => {
         this.onInputChange();
       });

+ 6 - 0
packages/theme-default/src/option-group.css

@@ -7,6 +7,12 @@
     margin: 0;
     padding: 0;
 
+    @e wrap {
+      list-style: none;
+      margin: 0;
+      padding: 0;
+    }
+
     @e title {
       padding-left: 10px;
       font-size: 12px;

+ 4 - 4
src/utils/vue-popper.js

@@ -1,8 +1,8 @@
 import PopperJS from './popper';
 
 /**
- * @param {HTMLElement} [reference=$els.reference] - The reference element used to position the popper.
- * @param {HTMLElement} [popper=$els.popper] - The HTML element used as popper, or a configuration used to generate the popper.
+ * @param {HTMLElement} [reference=$refs.reference] - The reference element used to position the popper.
+ * @param {HTMLElement} [popper=$refs.popper] - The HTML element used as popper, or a configuration used to generate the popper.
  * @param {String} [placement=button] - Placement of the popper accepted values: top(-start, -end), right(-start, -end), bottom(-start, -right), left(-start, -end)
  * @param {Number} [offset=0] - Amount of pixels the popper will be shifted (can be negative).
  * @param {Boolean} [visible=false] Visibility of the popup element.
@@ -47,8 +47,8 @@ export default {
         return;
       }
 
-      this.popper = this.popper || this.$els.popper;
-      this.reference = this.reference || this.$els.reference;
+      this.popper = this.popper || this.$refs.popper;
+      this.reference = this.reference || this.$refs.reference;
 
       if (!this.popper || !this.reference) {
         return;