A detailed analysis of the element-UI source code and what you can learn from it. (If you have any questions, you are welcome to comment and discuss. You can also visit the station.)
First of all, what does the life cycle do
created() {
// Associated with the Select component (selected if the Select component has published the inputSelect event)
this.$on('inputSelect'.this.select);
},
mounted() {
// Dynamic text field (height)
this.resizeTextarea();
// Set element offset before and after (style)
this.updateIconOffset();
},
updated() {
// The view is redrawn before and after offset (style)
this.$nextTick(this.updateIconOffset);
}
Copy the code
Some classes for the outer DIV binding
The socket and some props parameters are passed to control the outer style
<div :class="[ type === 'textarea' ? 'el-textarea' : 'el-input', inputSize ? 'el-input--' + inputSize : '', { 'is-disabled': inputDisabled, 'el-input-group': $slots.prepend || $slots.append, 'el-input-group--append': $slots.append, 'el-input-group--prepend': $slots.prepend, 'el-input--prefix': $slots.prefix || prefixIcon, 'el-input--suffix': $slots.suffix || suffixIcon || clearable } ]"
@mouseenter="hovering = true"
@mouseleave="hovering = false"
>
<! The interior is divided into input and textarea.
</div>
<! $slots.prepend: $slots.append: Suffix: prefix slot $slots.prefix: prefix icon slot $slots.suffix: prefix icon slot without slot icon prefixIcon: prefix icon suffixIcon: Back icon clearable: Whether the back is clear -->
Copy the code
The instance attribute $slots is used to access theSlot distributedThe content of the
- Vm.$slot. foo accesses named slot foo
- Vm.$slot.default Does not contain nodes in named slots
When there are more than one class condition:
- You can write an array with an object
Inner input structure
<! -- Input box structure -->
<template v-if="type ! == 'textarea'">
<! -- Prefix element -->
<div class="el-input-group__prepend" v-if="$slots.prepend">.</div>
<input
:tabindex="tabindex"
v-if="type ! == 'textarea'"
class="el-input__inner"
v-bind="$attrs"
:type="type"
:disabled="inputDisabled"
:readonly="readonly"
:autocomplete="autoComplete"
:value="currentValue"
ref="input"
@compositionstart="handleComposition"
@compositionupdate="handleComposition"
@compositionend="handleComposition"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
@change="handleChange"
:aria-label="label"
>
<! -- Front content -->
<span class="el-input__prefix" v-if="$slots.prefix || prefixIcon">.</span>
<! -- Post-content -->
<span
class="el-input__suffix"
v-if="$slots.suffix || suffixIcon || showClear || validateState && needStatusIcon">.</span>
<! -- After element -->
<div class="el-input-group__append" v-if="$slots.append">.</div>
</template>
Copy the code
Front and back contents and slots: Basically, these are variables that are received via props or slots to control the style and position offset. the
Events related to Chinese input methods
- compositionstart
- compositionupdate
- compositionend
You’ll first see the three events bound to the input (which I haven’t seen yet), so try the trigger timing
- Input to the Input box triggers an input event
- The change event is triggered when the content changes after losing focus
- The ** compositionStart ** event is detected when you start using Chinese input method
- The ** compositionUpdate ** event is raised in the input without completion
- The ComPositionEnd event is emitted when the input is complete (that is, when we enter or select the appropriate text to insert into the input box).
After consulting the data, it is found that these three events include not only Chinese input method but also speech recognition.
Here’s the explanation on MDN
Similar to the keyDown event, but only before the input of several visible characters, which may require a series of keyboard operations, speech recognition, or clicking alternatives to the input method
So the question is why use these events
Because input components often come with form forms, form validation is required
In order to solve the Chinese input method input content has not inserted Chinese into the input box on the verification problem
We hope that the Chinese input is complete before verification
Unused properties
In particular, this slag 눈.눈
- $attrs: gets attributes for all parent components, except for style and class, that the child component props is not registered with. (It feels so good!)
- Tabindex: native property, the tab-controlled order of elements (for details)
- **readonly ** : native property, read-only. (Input box cannot be modified if true)
- AutoComplete: Native property When the user starts typing in a field, the browser displays the option to fill in the field based on the previously typed value.
- Aria-label: native property. When a TAB is placed in the input box, the screen reader reads the text in the corresponding label.
Inner textarea structure
<! -- Text field structure -->
<textarea
v-else
:tabindex="tabindex"
class="el-textarea__inner"
:value="currentValue"
@compositionstart="handleComposition"
@compositionupdate="handleComposition"
@compositionend="handleComposition"
@input="handleInput"
ref="textarea"
v-bind="$attrs"
:disabled="inputDisabled"
:readonly="readonly"
:style="textareaStyle"
@focus="handleFocus"
@blur="handleBlur"
@change="handleChange"
:aria-label="label"
>
</textarea>
Copy the code
The events and attributes of the binding are similar to those of the input, except that the textarea dynamically controls the height of the style
Textarea is highly adaptive
props
- Autosize Indicates the autosize height
- Resize Whether to scale
computed: {
textareaStyle() {
// merge introduces methods to merge objects from SRC /utils/merge.js
return merge({}, this.textareaCalcStyle, { resize: this.resize }); },}, methods: {resizeTextarea() {// Whether to run on the server (server rendering)
if (this.$isServer) return;
const { autosize, type } = this;
if(type ! = ='textarea') return;
if(! autosize) {this.textareaCalcStyle = {
minHeight: calcTextareaHeight(this.$refs.textarea).minHeight
};
return;
}
const minRows = autosize.minRows;
const maxRows = autosize.maxRows;
this.textareaCalcStyle = calcTextareaHeight(this.$refs.textarea, minRows, maxRows); }}Copy the code
CalcTextareaHeight is a method in calcTextareaheight.js that calculates the height of the text field and sets the style
I’ll just post comments for the code and analysis
let hiddenTextarea;
// Some preset styles
const HIDDEN_STYLE = ` height:0 ! important; visibility:hidden ! important; overflow:hidden ! important; position:absolute ! important; z-index:-1000 ! important; top:0 ! important; right:0 ! important `;
// Some style attributes expected to be used
const CONTEXT_STYLE = [
'letter-spacing'.'line-height'.'padding-top'.'padding-bottom'.'font-family'.'font-weight'.'font-size'.'text-rendering'.'text-transform'.'width'.'text-indent'.'padding-left'.'padding-right'.'border-width'.'box-sizing'
];
// Get some styles to use
function calculateNodeStyling(targetElement) {
// Get all the final styles applied to the element (return CSSStyleDeclaration object)
const style = window.getComputedStyle(targetElement);
// getPropertyValue gets the specific style for the method on the CSSStyleDeclaration stereotype
const boxSizing = style.getPropertyValue('box-sizing');
// Upper and lower inner margins
const paddingSize = (
parseFloat(style.getPropertyValue('padding-bottom')) +
parseFloat(style.getPropertyValue('padding-top')));// Upper and lower border width
const borderSize = (
parseFloat(style.getPropertyValue('border-bottom-width')) +
parseFloat(style.getPropertyValue('border-top-width')));// Take the expected attribute name and value and concatenate it with a semicolon into a string
const contextStyle = CONTEXT_STYLE
.map(name= > `${name}:${style.getPropertyValue(name)}`)
.join('; ');
// Returns the default style string, upper and lower margins, border, and boxSizing properties
return { contextStyle, paddingSize, borderSize, boxSizing };
}
export default function calcTextareaHeight(
targetElement,
minRows = 1,
maxRows = null
) {
// If hiddenTextarea does not exist, create a Textarea element append in the body
if(! hiddenTextarea) { hiddenTextarea =document.createElement('textarea');
document.body.appendChild(hiddenTextarea);
}
// Retrieve the following attribute values
let {
paddingSize,
borderSize,
boxSizing,
contextStyle
} = calculateNodeStyling(targetElement);
// Add inline style to the created hiddenTextarea and assign value or palceHolder, otherwise ""
hiddenTextarea.setAttribute('style'.`${contextStyle};${HIDDEN_STYLE}`);
hiddenTextarea.value = targetElement.value || targetElement.placeholder || ' ';
// Get the height of the element itself
let height = hiddenTextarea.scrollHeight;
const result = {};
// boxSizing is calculated differently depending on the height
if (boxSizing === 'border-box') {
// border-box: height = the height of the element + the width of the top and bottom borders
height = height + borderSize;
} else if (boxSizing === 'content-box') {
// content-box: height = height - upper and lower inner margins sum
height = height - paddingSize;
}
hiddenTextarea.value = ' ';
// The height of a single line
let singleRowHeight = hiddenTextarea.scrollHeight - paddingSize;
// minRows minimum exists
if(minRows ! = =null) {
// Minimum height = single line height * number of lines
let minHeight = singleRowHeight * minRows;
if (boxSizing === 'border-box') {
// border-box adds an inner margin and border
minHeight = minHeight + paddingSize + borderSize;
}
// set minHeight to the maximum value of height
height = Math.max(minHeight, height);
result.minHeight = `${ minHeight }px`;
}
// The maximum row exists
if(maxRows ! = =null) {
// same logic as above
let maxHeight = singleRowHeight * maxRows;
if (boxSizing === 'border-box') {
maxHeight = maxHeight + paddingSize + borderSize;
}
// maxHeight and height take the minimum value to assign height
height = Math.min(maxHeight, height);
}
result.height = `${ height }px`;
// Remove the hiddenTextarea element after calculation
hiddenTextarea.parentNode && hiddenTextarea.parentNode.removeChild(hiddenTextarea);
hiddenTextarea = null;
// Expose the object containing minHeight and height
return result;
};
Copy the code
A few things to be aware of
formWhen an input component is nested within a component, the style is also controlled by some of the injected properties of the form.
// Receives the properties injected by the Form component
inject: {
elForm: {
default: ' '
},
elFormItem: {
default: ' '}}Copy the code
-
Size (Input size)
-
Enclosing elFormItem. ValidateState: associated with forms authentication, control form validation when the style of the icon (the red x)
computed: {
// Form validation is relevant
validateState() {
return this.elFormItem ? this.elFormItem.validateState : ' ';
},
needStatusIcon() {
return this.elForm ? this.elForm.statusIcon : false;
},
// Form validation style
validateIcon() {
return {
validating: 'el-icon-loading'.success: 'el-icon-circle-check'.error: 'el-icon-circle-close'} [this.validateState]; }}Copy the code
The propsvalidateEventProperty: The time selector will pass false and the other default true, which is used herevalidateEventThe methods of
handleBlur(event) {
this.focused = false;
// Expose the blur event
this.$emit('blur', event);
if (this.validateEvent) {
// Go up to the ElFormItem component and publish the el.form.blur event and pass the value
this.dispatch('ElFormItem'.'el.form.blur'[this.currentValue]);
}
},
setCurrentValue(value) {
// Still typing and the content is the same as before return
if (this.isOnComposition && value === this.valueBeforeComposition) return;
// Input content assignment
this.currentValue = value;
// Still typing return
if (this.isOnComposition) return;
this.$nextTick(_= > {
this.resizeTextarea();
});
// Use the default true for all components except the time selector
if (this.validateEvent) {
// The method in mixin means going up to the ElFormItem component and publishing the el.form.change event and passing the current input content
this.dispatch('ElFormItem'.'el.form.change', [value]); }}Copy the code
dispatchThis method started out as what I thought was a vuex trigger method and ended up beingmixinIn the
Path: the SRC/mixins/emitter. Js
// Receive component name, event name, parameters
dispatch(componentName, eventName, params) {
var parent = this.$parent || this.$root;
var name = parent.$options.componentName;
// Find the parent. If the parent does not match the component name, loop up
while(parent && (! name || name ! == componentName)) { parent = parent.$parent;if(parent) { name = parent.$options.componentName; }}// After finding a parent that matches the component name, publish the incoming event.
if(parent) { parent.$emit.apply(parent, [eventName].concat(params)); }}Copy the code
The import of Migrating
Refer to the getMigratingConfig event in methos and ** SRC /mixins/migrating. Js **
doubt
// How to judge Hangul (unclear why)
import { isKorean } from 'element-ui/src/utils/shared';
methods: {
// Trigger when Chinese or voice input starts see ↑
handleComposition(event) {
// When the input is complete
if (event.type === 'compositionend') {
// The input is identified as false
this.isOnComposition = false;
// The value before Chinese or voice input is assigned to the current value
this.currentValue = this.valueBeforeComposition;
// Clear the previous values
this.valueBeforeComposition = null;
// Assign and expose the input method to the parent component
this.handleInput(event);
// Not complete
} else {
const text = event.target.value;
const lastCharacter = text[text.length - 1] | |' ';
// The last character is either in Korean or in the input (not sure why the last character is Korean)
this.isOnComposition = ! isKorean(lastCharacter);// Before input begins
if (this.isOnComposition && event.type === 'compositionstart') {
this.valueBeforeComposition = text; }}}}Copy the code