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