Input: simplify el-input implementation (#13471)

* input: simplify internal implementation

remove currentValue, strictly follow one-way data flow
hack for MSIE input event when placeholder is set
remove isKorean hack (#11665, #10648)

* input-number: fix for new el-input

* test: input, fix vue warning

* date-time-range: fix for new el-input

* pagination: fix for new el-input, simplify internals

* input: fix input event on compositionend

* input-number: fix for new el-input

* input-number: nuke userInput on change
pull/13574/head
Jiewei Qian 2018-11-26 18:05:46 +11:00 committed by hetech
parent dd4a496940
commit e2c5573c1f
7 changed files with 132 additions and 133 deletions

View File

@ -28,18 +28,19 @@
:placeholder="t('el.datepicker.startDate')" :placeholder="t('el.datepicker.startDate')"
class="el-date-range-picker__editor" class="el-date-range-picker__editor"
:value="minVisibleDate" :value="minVisibleDate"
@input.native="handleDateInput($event, 'min')" @input="val => handleDateInput(val, 'min')"
@change.native="handleDateChange($event, 'min')" /> @change="val => handleDateChange(val, 'min')" />
</span> </span>
<span class="el-date-range-picker__time-picker-wrap" v-clickoutside="handleMinTimeClose"> <span class="el-date-range-picker__time-picker-wrap" v-clickoutside="handleMinTimeClose">
<el-input <el-input
size="small" size="small"
class="el-date-range-picker__editor"
:disabled="rangeState.selecting" :disabled="rangeState.selecting"
:placeholder="t('el.datepicker.startTime')" :placeholder="t('el.datepicker.startTime')"
class="el-date-range-picker__editor"
:value="minVisibleTime" :value="minVisibleTime"
@focus="minTimePickerVisible = true" @focus="minTimePickerVisible = true"
@change.native="handleTimeChange($event, 'min')" /> @input="val => handleTimeInput(val, 'min')"
@change="val => handleTimeChange(val, 'min')" />
<time-picker <time-picker
ref="minTimePicker" ref="minTimePicker"
@pick="handleMinTimePick" @pick="handleMinTimePick"
@ -54,25 +55,25 @@
<span class="el-date-range-picker__time-picker-wrap"> <span class="el-date-range-picker__time-picker-wrap">
<el-input <el-input
size="small" size="small"
class="el-date-range-picker__editor"
:disabled="rangeState.selecting" :disabled="rangeState.selecting"
:placeholder="t('el.datepicker.endDate')" :placeholder="t('el.datepicker.endDate')"
class="el-date-range-picker__editor"
:value="maxVisibleDate" :value="maxVisibleDate"
:readonly="!minDate" :readonly="!minDate"
@input.native="handleDateInput($event, 'max')" @input="val => handleDateInput(val, 'max')"
@change.native="handleDateChange($event, 'max')" /> @change="val => handleDateChange(val, 'max')" />
</span> </span>
<span class="el-date-range-picker__time-picker-wrap" v-clickoutside="handleMaxTimeClose"> <span class="el-date-range-picker__time-picker-wrap" v-clickoutside="handleMaxTimeClose">
<el-input <el-input
size="small" size="small"
:disabled="rangeState.selecting"
ref="maxInput"
:placeholder="t('el.datepicker.endTime')"
class="el-date-range-picker__editor" class="el-date-range-picker__editor"
:disabled="rangeState.selecting"
:placeholder="t('el.datepicker.endTime')"
:value="maxVisibleTime" :value="maxVisibleTime"
@focus="minDate && (maxTimePickerVisible = true)"
:readonly="!minDate" :readonly="!minDate"
@change.native="handleTimeChange($event, 'max')" /> @focus="minDate && (maxTimePickerVisible = true)"
@input="val => handleTimeInput(val, 'max')"
@change="val => handleTimeChange(val, 'max')" />
<time-picker <time-picker
ref="maxTimePicker" ref="maxTimePicker"
@pick="handleMaxTimePick" @pick="handleMaxTimePick"
@ -263,19 +264,27 @@
}, },
minVisibleDate() { minVisibleDate() {
return this.minDate ? formatDate(this.minDate, this.dateFormat) : ''; if (this.dateUserInput.min !== null) return this.dateUserInput.min;
if (this.minDate) return formatDate(this.minDate, this.dateFormat);
return '';
}, },
maxVisibleDate() { maxVisibleDate() {
return (this.maxDate || this.minDate) ? formatDate(this.maxDate || this.minDate, this.dateFormat) : ''; if (this.dateUserInput.max !== null) return this.dateUserInput.max;
if (this.maxDate || this.minDate) return formatDate(this.maxDate || this.minDate, this.dateFormat);
return '';
}, },
minVisibleTime() { minVisibleTime() {
return this.minDate ? formatDate(this.minDate, this.timeFormat) : ''; if (this.timeUserInput.min !== null) return this.timeUserInput.min;
if (this.minDate) return formatDate(this.minDate, this.timeFormat);
return '';
}, },
maxVisibleTime() { maxVisibleTime() {
return (this.maxDate || this.minDate) ? formatDate(this.maxDate || this.minDate, this.timeFormat) : ''; if (this.timeUserInput.max !== null) return this.timeUserInput.max;
if (this.maxDate || this.minDate) return formatDate(this.maxDate || this.minDate, this.timeFormat);
return '';
}, },
timeFormat() { timeFormat() {
@ -330,12 +339,22 @@
maxTimePickerVisible: false, maxTimePickerVisible: false,
format: '', format: '',
arrowControl: false, arrowControl: false,
unlinkPanels: false unlinkPanels: false,
dateUserInput: {
min: null,
max: null
},
timeUserInput: {
min: null,
max: null
}
}; };
}, },
watch: { watch: {
minDate(val) { minDate(val) {
this.dateUserInput.min = null;
this.timeUserInput.min = null;
this.$nextTick(() => { this.$nextTick(() => {
if (this.$refs.maxTimePicker && this.maxDate && this.maxDate < this.minDate) { if (this.$refs.maxTimePicker && this.maxDate && this.maxDate < this.minDate) {
const format = 'HH:mm:ss'; const format = 'HH:mm:ss';
@ -354,6 +373,8 @@
}, },
maxDate(val) { maxDate(val) {
this.dateUserInput.max = null;
this.timeUserInput.max = null;
if (val && this.$refs.maxTimePicker) { if (val && this.$refs.maxTimePicker) {
this.$refs.maxTimePicker.date = val; this.$refs.maxTimePicker.date = val;
this.$refs.maxTimePicker.value = val; this.$refs.maxTimePicker.value = val;
@ -433,8 +454,8 @@
this.rangeState = val.rangeState; this.rangeState = val.rangeState;
}, },
handleDateInput(event, type) { handleDateInput(value, type) {
const value = event.target.value; this.dateUserInput[type] = value;
if (value.length !== this.dateFormat.length) return; if (value.length !== this.dateFormat.length) return;
const parsedValue = parseDate(value, this.dateFormat); const parsedValue = parseDate(value, this.dateFormat);
@ -444,19 +465,22 @@
return; return;
} }
if (type === 'min') { if (type === 'min') {
this.minDate = new Date(parsedValue); this.minDate = modifyDate(this.minDate || new Date(), parsedValue.getFullYear(), parsedValue.getMonth(), parsedValue.getDate());
this.leftDate = new Date(parsedValue); this.leftDate = new Date(parsedValue);
if (!this.unlinkPanels) {
this.rightDate = nextMonth(this.leftDate); this.rightDate = nextMonth(this.leftDate);
}
} else { } else {
this.maxDate = new Date(parsedValue); this.maxDate = modifyDate(this.maxDate || new Date(), parsedValue.getFullYear(), parsedValue.getMonth(), parsedValue.getDate());
this.leftDate = prevMonth(parsedValue);
this.rightDate = new Date(parsedValue); this.rightDate = new Date(parsedValue);
if (!this.unlinkPanels) {
this.leftDate = prevMonth(parsedValue);
}
} }
} }
}, },
handleDateChange(event, type) { handleDateChange(value, type) {
const value = event.target.value;
const parsedValue = parseDate(value, this.dateFormat); const parsedValue = parseDate(value, this.dateFormat);
if (parsedValue) { if (parsedValue) {
if (type === 'min') { if (type === 'min') {
@ -473,8 +497,23 @@
} }
}, },
handleTimeChange(event, type) { handleTimeInput(value, type) {
const value = event.target.value; this.timeUserInput[type] = value;
if (value.length !== this.timeFormat.length) return;
const parsedValue = parseDate(value, this.timeFormat);
if (parsedValue) {
if (type === 'min') {
this.minDate = modifyTime(this.minDate, parsedValue.getHours(), parsedValue.getMinutes(), parsedValue.getSeconds());
this.$nextTick(_ => this.$refs.minTimePicker.adjustSpinners());
} else {
this.maxDate = modifyTime(this.maxDate, parsedValue.getHours(), parsedValue.getMinutes(), parsedValue.getSeconds());
this.$nextTick(_ => this.$refs.maxTimePicker.adjustSpinners());
}
}
},
handleTimeChange(value, type) {
const parsedValue = parseDate(value, this.timeFormat); const parsedValue = parseDate(value, this.timeFormat);
if (parsedValue) { if (parsedValue) {
if (type === 'min') { if (type === 'min') {

View File

@ -28,7 +28,7 @@
</span> </span>
<el-input <el-input
ref="input" ref="input"
:value="currentInputValue" :value="displayValue"
:placeholder="placeholder" :placeholder="placeholder"
:disabled="inputNumberDisabled" :disabled="inputNumberDisabled"
:size="inputNumberSize" :size="inputNumberSize"
@ -40,6 +40,7 @@
@keydown.down.native.prevent="decrease" @keydown.down.native.prevent="decrease"
@blur="handleBlur" @blur="handleBlur"
@focus="handleFocus" @focus="handleFocus"
@input="handleInput"
@change="handleInputChange"> @change="handleInputChange">
</el-input> </el-input>
</div> </div>
@ -102,7 +103,8 @@
}, },
data() { data() {
return { return {
currentValue: 0 currentValue: 0,
userInput: null
}; };
}, },
watch: { watch: {
@ -121,6 +123,7 @@
if (newVal >= this.max) newVal = this.max; if (newVal >= this.max) newVal = this.max;
if (newVal <= this.min) newVal = this.min; if (newVal <= this.min) newVal = this.min;
this.currentValue = newVal; this.currentValue = newVal;
this.userInput = null;
this.$emit('input', newVal); this.$emit('input', newVal);
} }
} }
@ -156,7 +159,10 @@
inputNumberDisabled() { inputNumberDisabled() {
return this.disabled || (this.elForm || {}).disabled; return this.disabled || (this.elForm || {}).disabled;
}, },
currentInputValue() { displayValue() {
if (this.userInput !== null) {
return this.userInput;
}
const currentValue = this.currentValue; const currentValue = this.currentValue;
if (typeof currentValue === 'number' && this.precision !== undefined) { if (typeof currentValue === 'number' && this.precision !== undefined) {
return currentValue.toFixed(this.precision); return currentValue.toFixed(this.precision);
@ -208,7 +214,6 @@
}, },
handleBlur(event) { handleBlur(event) {
this.$emit('blur', event); this.$emit('blur', event);
this.$refs.input.setCurrentValue(this.currentInputValue);
}, },
handleFocus(event) { handleFocus(event) {
this.$emit('focus', event); this.$emit('focus', event);
@ -220,19 +225,21 @@
} }
if (newVal >= this.max) newVal = this.max; if (newVal >= this.max) newVal = this.max;
if (newVal <= this.min) newVal = this.min; if (newVal <= this.min) newVal = this.min;
if (oldVal === newVal) { if (oldVal === newVal) return;
this.$refs.input.setCurrentValue(this.currentInputValue); this.userInput = null;
return;
}
this.$emit('input', newVal); this.$emit('input', newVal);
this.$emit('change', newVal, oldVal); this.$emit('change', newVal, oldVal);
this.currentValue = newVal; this.currentValue = newVal;
}, },
handleInput(value) {
this.userInput = value;
},
handleInputChange(value) { handleInputChange(value) {
const newVal = value === '' ? undefined : Number(value); const newVal = value === '' ? undefined : Number(value);
if (!isNaN(newVal) || value === '') { if (!isNaN(newVal) || value === '') {
this.setCurrentValue(newVal); this.setCurrentValue(newVal);
} }
this.userInput = null;
}, },
select() { select() {
this.$refs.input.select(); this.$refs.input.select();

View File

@ -28,7 +28,7 @@
:disabled="inputDisabled" :disabled="inputDisabled"
:readonly="readonly" :readonly="readonly"
:autocomplete="autoComplete || autocomplete" :autocomplete="autoComplete || autocomplete"
:value="currentValue" :value="nativeInputValue"
ref="input" ref="input"
@compositionstart="handleComposition" @compositionstart="handleComposition"
@compositionupdate="handleComposition" @compositionupdate="handleComposition"
@ -78,7 +78,7 @@
v-else v-else
:tabindex="tabindex" :tabindex="tabindex"
class="el-textarea__inner" class="el-textarea__inner"
:value="currentValue" :value="nativeInputValue"
@compositionstart="handleComposition" @compositionstart="handleComposition"
@compositionupdate="handleComposition" @compositionupdate="handleComposition"
@compositionend="handleComposition" @compositionend="handleComposition"
@ -102,7 +102,6 @@
import Migrating from 'element-ui/src/mixins/migrating'; import Migrating from 'element-ui/src/mixins/migrating';
import calcTextareaHeight from './calcTextareaHeight'; import calcTextareaHeight from './calcTextareaHeight';
import merge from 'element-ui/src/utils/merge'; import merge from 'element-ui/src/utils/merge';
import { isKorean } from 'element-ui/src/utils/shared';
export default { export default {
name: 'ElInput', name: 'ElInput',
@ -124,14 +123,10 @@
data() { data() {
return { return {
currentValue: this.value === undefined || this.value === null
? ''
: this.value,
textareaCalcStyle: {}, textareaCalcStyle: {},
hovering: false, hovering: false,
focused: false, focused: false,
isOnComposition: false, isOnComposition: false
valueBeforeComposition: null
}; };
}, },
@ -203,18 +198,24 @@
inputDisabled() { inputDisabled() {
return this.disabled || (this.elForm || {}).disabled; return this.disabled || (this.elForm || {}).disabled;
}, },
nativeInputValue() {
return this.value === null || this.value === undefined ? '' : this.value;
},
showClear() { showClear() {
return this.clearable && return this.clearable &&
!this.inputDisabled && !this.inputDisabled &&
!this.readonly && !this.readonly &&
this.currentValue !== '' && this.nativeInputValue &&
(this.focused || this.hovering); (this.focused || this.hovering);
} }
}, },
watch: { watch: {
value(val, oldValue) { value(val) {
this.setCurrentValue(val); this.$nextTick(this.resizeTextarea);
if (this.validateEvent) {
this.dispatch('ElFormItem', 'el.form.change', [val]);
}
} }
}, },
@ -240,7 +241,7 @@
this.focused = false; this.focused = false;
this.$emit('blur', event); this.$emit('blur', event);
if (this.validateEvent) { if (this.validateEvent) {
this.dispatch('ElFormItem', 'el.form.blur', [this.currentValue]); this.dispatch('ElFormItem', 'el.form.blur', [this.value]);
} }
}, },
select() { select() {
@ -266,38 +267,30 @@
this.$emit('focus', event); this.$emit('focus', event);
}, },
handleComposition(event) { handleComposition(event) {
if (event.type === 'compositionstart') {
this.isOnComposition = true;
}
if (event.type === 'compositionend') { if (event.type === 'compositionend') {
this.isOnComposition = false; this.isOnComposition = false;
this.currentValue = this.valueBeforeComposition;
this.valueBeforeComposition = null;
this.handleInput(event); this.handleInput(event);
} else {
const text = event.target.value;
const lastCharacter = text[text.length - 1] || '';
this.isOnComposition = !isKorean(lastCharacter);
if (this.isOnComposition && event.type === 'compositionstart') {
this.valueBeforeComposition = text;
}
} }
}, },
handleInput(event) { handleInput(event) {
const value = event.target.value;
this.setCurrentValue(value);
if (this.isOnComposition) return; if (this.isOnComposition) return;
this.$emit('input', value);
// hack for https://github.com/ElemeFE/element/issues/8548
// should remove the following line when we don't support IE
if (event.target.value === this.nativeInputValue) return;
this.$emit('input', event.target.value);
// set input's value, in case parent refuses the change
// see: https://github.com/ElemeFE/element/issues/12850
this.$nextTick(() => { this.$refs.input.value = this.value; });
}, },
handleChange(event) { handleChange(event) {
this.$emit('change', event.target.value); this.$emit('change', event.target.value);
}, },
setCurrentValue(value) {
if (this.isOnComposition && value === this.valueBeforeComposition) return;
this.currentValue = value;
if (this.isOnComposition) return;
this.$nextTick(this.resizeTextarea);
if (this.validateEvent && this.currentValue === this.value) {
this.dispatch('ElFormItem', 'el.form.change', [value]);
}
},
calcIconOffset(place) { calcIconOffset(place) {
let elList = [].slice.call(this.$el.querySelectorAll(`.el-input__${place}`) || []); let elList = [].slice.call(this.$el.querySelectorAll(`.el-input__${place}`) || []);
if (!elList.length) return; if (!elList.length) return;
@ -329,7 +322,6 @@
this.$emit('input', ''); this.$emit('input', '');
this.$emit('change', ''); this.$emit('change', '');
this.$emit('clear'); this.$emit('clear');
this.setCurrentValue('');
} }
}, },

View File

@ -215,56 +215,36 @@ export default {
Jumper: { Jumper: {
mixins: [Locale], mixins: [Locale],
components: { ElInput },
data() { data() {
return { return {
oldValue: null userInput: null
}; };
}, },
components: { ElInput },
watch: { watch: {
'$parent.internalPageSize'() { '$parent.internalCurrentPage'() {
this.$nextTick(() => { this.userInput = null;
this.$refs.input.$el.querySelector('input').value = this.$parent.internalCurrentPage;
});
} }
}, },
methods: { methods: {
handleFocus(event) {
this.oldValue = event.target.value;
},
handleBlur({ target }) {
this.resetValueIfNeed(target.value);
this.reassignMaxValue(target.value);
},
handleKeyup({ keyCode, target }) { handleKeyup({ keyCode, target }) {
if (keyCode === 13 && this.oldValue && target.value !== this.oldValue) { // Chrome, Safari, Firefox triggers change event on Enter
// Hack for IE: https://github.com/ElemeFE/element/issues/11710
// Drop this method when we no longer supports IE
if (keyCode === 13) {
this.handleChange(target.value); this.handleChange(target.value);
} }
}, },
handleInput(value) {
this.userInput = value;
},
handleChange(value) { handleChange(value) {
this.$parent.internalCurrentPage = this.$parent.getValidCurrentPage(value); this.$parent.internalCurrentPage = this.$parent.getValidCurrentPage(value);
this.$parent.emitChange(); this.$parent.emitChange();
this.oldValue = null; this.userInput = null;
this.resetValueIfNeed(value);
},
resetValueIfNeed(value) {
const num = parseInt(value, 10);
if (!isNaN(num)) {
if (num < 1) {
this.$refs.input.setCurrentValue(1);
} else {
this.reassignMaxValue(value);
}
}
},
reassignMaxValue(value) {
const { internalPageCount } = this.$parent;
if (+value > internalPageCount) {
this.$refs.input.setCurrentValue(internalPageCount || 1);
}
} }
}, },
@ -276,15 +256,12 @@ export default {
class="el-pagination__editor is-in-pagination" class="el-pagination__editor is-in-pagination"
min={ 1 } min={ 1 }
max={ this.$parent.internalPageCount } max={ this.$parent.internalPageCount }
value={ this.$parent.internalCurrentPage } value={ this.userInput !== null ? this.userInput : this.$parent.internalCurrentPage }
domPropsValue={ this.$parent.internalCurrentPage }
type="number" type="number"
ref="input"
disabled={ this.$parent.disabled } disabled={ this.$parent.disabled }
nativeOnKeyup={ this.handleKeyup } nativeOnKeyup={ this.handleKeyup }
onChange={ this.handleChange } onInput={ this.handleInput }
onFocus={ this.handleFocus } onChange={ this.handleChange }/>
onBlur={ this.handleBlur }/>
{ this.t('el.pagination.pageClassifier') } { this.t('el.pagination.pageClassifier') }
</span> </span>
); );
@ -380,7 +357,7 @@ export default {
currentPage: { currentPage: {
immediate: true, immediate: true,
handler(val) { handler(val) {
this.internalCurrentPage = val; this.internalCurrentPage = this.getValidCurrentPage(val);
} }
}, },
@ -393,24 +370,8 @@ export default {
internalCurrentPage: { internalCurrentPage: {
immediate: true, immediate: true,
handler(newVal, oldVal) { handler(newVal) {
newVal = parseInt(newVal, 10);
/* istanbul ignore if */
if (isNaN(newVal)) {
newVal = oldVal || 1;
} else {
newVal = this.getValidCurrentPage(newVal);
}
if (newVal !== undefined) {
this.internalCurrentPage = newVal;
if (oldVal !== newVal) {
this.$emit('update:currentPage', newVal); this.$emit('update:currentPage', newVal);
}
} else {
this.$emit('update:currentPage', newVal);
}
this.lastEmittedPage = -1; this.lastEmittedPage = -1;
} }
}, },

View File

@ -2159,16 +2159,16 @@ describe('DatePicker', () => {
triggerEvent(rightCell, 'click', true); triggerEvent(rightCell, 'click', true);
setTimeout(_ => { setTimeout(_ => {
triggerEvent(input2, 'input');
input2.value = '1988-6-4'; input2.value = '1988-6-4';
triggerEvent(input2, 'input');
triggerEvent(input2, 'change'); triggerEvent(input2, 'change');
setTimeout(_ => { setTimeout(_ => {
triggerEvent(input, 'input');
input.value = '1989-6-4'; input.value = '1989-6-4';
triggerEvent(input, 'input');
triggerEvent(input, 'change', true); triggerEvent(input, 'change', true);
setTimeout(_ => { setTimeout(_ => {
expect(vm.picker.maxDate > vm.picker.minDate).to.true; expect(vm.picker.maxDate >= vm.picker.minDate).to.true;
done(); done();
}, DELAY); }, DELAY);
}, DELAY); }, DELAY);
@ -2213,7 +2213,7 @@ describe('DatePicker', () => {
input.focus(); input.focus();
setTimeout(_ => { setTimeout(_ => {
// simulate user input of invalid date // simulate user input of invalid date
vm.$refs.compo.picker.handleDateChange({ target: { value: '2000-09-01'} }, 'min'); vm.$refs.compo.picker.handleDateChange('2000-09-01', 'min');
setTimeout(_ => { setTimeout(_ => {
expect(vm.$refs.compo.picker.btnDisabled).to.equal(true); // invalid input disables button expect(vm.$refs.compo.picker.btnDisabled).to.equal(true); // invalid input disables button
vm.$refs.compo.picker.handleConfirm(); vm.$refs.compo.picker.handleConfirm();

View File

@ -194,7 +194,8 @@ describe('Input', () => {
`, `,
data() { data() {
return { return {
value: '1234' value: '1234',
select: '1'
}; };
} }
}, true); }, true);

View File

@ -249,7 +249,6 @@ describe('Pagination', () => {
const input = vm.inputer; const input = vm.inputer;
const changeValue = (value) => { const changeValue = (value) => {
input.$emit('input', value); input.$emit('input', value);
input.setCurrentValue(value);
input.$emit('change', value); input.$emit('change', value);
}; };