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

View File

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

View File

@ -28,7 +28,7 @@
:disabled="inputDisabled"
:readonly="readonly"
:autocomplete="autoComplete || autocomplete"
:value="currentValue"
:value="nativeInputValue"
ref="input"
@compositionstart="handleComposition"
@compositionupdate="handleComposition"
@ -78,7 +78,7 @@
v-else
:tabindex="tabindex"
class="el-textarea__inner"
:value="currentValue"
:value="nativeInputValue"
@compositionstart="handleComposition"
@compositionupdate="handleComposition"
@compositionend="handleComposition"
@ -102,7 +102,6 @@
import Migrating from 'element-ui/src/mixins/migrating';
import calcTextareaHeight from './calcTextareaHeight';
import merge from 'element-ui/src/utils/merge';
import { isKorean } from 'element-ui/src/utils/shared';
export default {
name: 'ElInput',
@ -124,14 +123,10 @@
data() {
return {
currentValue: this.value === undefined || this.value === null
? ''
: this.value,
textareaCalcStyle: {},
hovering: false,
focused: false,
isOnComposition: false,
valueBeforeComposition: null
isOnComposition: false
};
},
@ -203,18 +198,24 @@
inputDisabled() {
return this.disabled || (this.elForm || {}).disabled;
},
nativeInputValue() {
return this.value === null || this.value === undefined ? '' : this.value;
},
showClear() {
return this.clearable &&
!this.inputDisabled &&
!this.readonly &&
this.currentValue !== '' &&
this.nativeInputValue &&
(this.focused || this.hovering);
}
},
watch: {
value(val, oldValue) {
this.setCurrentValue(val);
value(val) {
this.$nextTick(this.resizeTextarea);
if (this.validateEvent) {
this.dispatch('ElFormItem', 'el.form.change', [val]);
}
}
},
@ -240,7 +241,7 @@
this.focused = false;
this.$emit('blur', event);
if (this.validateEvent) {
this.dispatch('ElFormItem', 'el.form.blur', [this.currentValue]);
this.dispatch('ElFormItem', 'el.form.blur', [this.value]);
}
},
select() {
@ -266,38 +267,30 @@
this.$emit('focus', event);
},
handleComposition(event) {
if (event.type === 'compositionstart') {
this.isOnComposition = true;
}
if (event.type === 'compositionend') {
this.isOnComposition = false;
this.currentValue = this.valueBeforeComposition;
this.valueBeforeComposition = null;
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) {
const value = event.target.value;
this.setCurrentValue(value);
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) {
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) {
let elList = [].slice.call(this.$el.querySelectorAll(`.el-input__${place}`) || []);
if (!elList.length) return;
@ -329,7 +322,6 @@
this.$emit('input', '');
this.$emit('change', '');
this.$emit('clear');
this.setCurrentValue('');
}
},

View File

@ -215,56 +215,36 @@ export default {
Jumper: {
mixins: [Locale],
components: { ElInput },
data() {
return {
oldValue: null
userInput: null
};
},
components: { ElInput },
watch: {
'$parent.internalPageSize'() {
this.$nextTick(() => {
this.$refs.input.$el.querySelector('input').value = this.$parent.internalCurrentPage;
});
'$parent.internalCurrentPage'() {
this.userInput = null;
}
},
methods: {
handleFocus(event) {
this.oldValue = event.target.value;
},
handleBlur({ target }) {
this.resetValueIfNeed(target.value);
this.reassignMaxValue(target.value);
},
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);
}
},
handleInput(value) {
this.userInput = value;
},
handleChange(value) {
this.$parent.internalCurrentPage = this.$parent.getValidCurrentPage(value);
this.$parent.emitChange();
this.oldValue = 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);
}
this.userInput = null;
}
},
@ -276,15 +256,12 @@ export default {
class="el-pagination__editor is-in-pagination"
min={ 1 }
max={ this.$parent.internalPageCount }
value={ this.$parent.internalCurrentPage }
domPropsValue={ this.$parent.internalCurrentPage }
value={ this.userInput !== null ? this.userInput : this.$parent.internalCurrentPage }
type="number"
ref="input"
disabled={ this.$parent.disabled }
nativeOnKeyup={ this.handleKeyup }
onChange={ this.handleChange }
onFocus={ this.handleFocus }
onBlur={ this.handleBlur }/>
onInput={ this.handleInput }
onChange={ this.handleChange }/>
{ this.t('el.pagination.pageClassifier') }
</span>
);
@ -380,7 +357,7 @@ export default {
currentPage: {
immediate: true,
handler(val) {
this.internalCurrentPage = val;
this.internalCurrentPage = this.getValidCurrentPage(val);
}
},
@ -393,24 +370,8 @@ export default {
internalCurrentPage: {
immediate: true,
handler(newVal, oldVal) {
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) {
handler(newVal) {
this.$emit('update:currentPage', newVal);
}
} else {
this.$emit('update:currentPage', newVal);
}
this.lastEmittedPage = -1;
}
},

View File

@ -2159,16 +2159,16 @@ describe('DatePicker', () => {
triggerEvent(rightCell, 'click', true);
setTimeout(_ => {
triggerEvent(input2, 'input');
input2.value = '1988-6-4';
triggerEvent(input2, 'input');
triggerEvent(input2, 'change');
setTimeout(_ => {
triggerEvent(input, 'input');
input.value = '1989-6-4';
triggerEvent(input, 'input');
triggerEvent(input, 'change', true);
setTimeout(_ => {
expect(vm.picker.maxDate > vm.picker.minDate).to.true;
expect(vm.picker.maxDate >= vm.picker.minDate).to.true;
done();
}, DELAY);
}, DELAY);
@ -2213,7 +2213,7 @@ describe('DatePicker', () => {
input.focus();
setTimeout(_ => {
// 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(_ => {
expect(vm.$refs.compo.picker.btnDisabled).to.equal(true); // invalid input disables button
vm.$refs.compo.picker.handleConfirm();

View File

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

View File

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