Element from form source code analysis, involving input, SELECT, checkbox, picker, radio and other components of the verification. Forms are validated using the Async-Validator plugin. Take a look.

First, the Form component consists of two parts

  • Form: Manages form-items in a unified manner.
  • Form-item: is responsible for completing validation, etc.

form

structure

  <form class="el-form" :class="[ labelPosition ? 'el-form--label-' + labelPosition : '', { 'el-form--inline': inline } ]">
    <slot></slot>
  </form>
Copy the code

The structure is simple. The form element wraps around the slot, the Form-item

Script part

1. It is convenient to associate with form-item and inject form instance

    // Inject the component instance
    provide() {
      return {
        elForm: this
      };
    }
Copy the code

2. After the instance is created, Fields collects the instance of the form-item

    created() {
      // Listen for the el.form.addField event, which triggers: push the form-item instance to Fields
      this.$on('el.form.addField', (field) => {
        if (field) {
          this.fields.push(field); }});// Listen for the el.form.removeField event and trigger: Form-item instance has a prop rule attribute to remove form-item instance from Fields
      this.$on('el.form.removeField', (field) => {
        if (field.prop) {
          this.fields.splice(this.fields.indexOf(field), 1); }}); }Copy the code

3. Some methods for form validation,Does the form component manage the specific execution or is it in the form-item

  • Validate the entire form

          // Validate the entire form
          validate(callback) {
            // No form data throw warning pops up
            if (!this.model) {
              console.warn('[Element Warn][Form]model is required for validate to work! ');
              return;
            }
    
            let promise;
            // No callback and the browser supports Promise return Promise
            if (typeofcallback ! = ='function' && window.Promise) {
              promise = new window.Promise((resolve, reject) = > {
                callback = function(valid) {
                  valid ? resolve(valid) : reject(valid);
                };
              });
            }
              
            let valid = true;
            let count = 0;
            // If the fields to be validated are empty, callback is immediately returned when validation is called
            if (this.fields.length === 0 && callback) {
              callback(true);
            }
            let invalidFields = {};
            // Iterate through all instances, one by one
            this.fields.forEach(field= > {
              // Validate is the form-item method
              field.validate(' ', (message, field) => {
                // If a message is displayed, the authentication fails
                if (message) {
                  valid = false;
                }
                // Copy the error object to invalidFields
                invalidFields = objectAssign({}, invalidFields, field);
                // 调callback
                if (typeof callback === 'function' && ++count === this.fields.length) { callback(valid, invalidFields); }}); });if (promise) {
              returnpromise; }}Copy the code

    ObjectAssign method In utils/merge, the method used to merge objects

  • Validate part of the form

          // Validate some forms
          validateField(prop, cb) {
            let field = this.fields.filter(field= > field.prop === prop)[0];
            if(! field) {throw new Error('must call validateField with valid prop string! '); }
            // Validate the form corresponding to the form rule
            field.validate(' ', cb);
          }
    Copy the code
  • Removes validation results for form entries

    Pass in an array of prop properties for the form item to be removed, or remove the validation result for the entire form if not passed

          clearValidate(props = []) {
            const fields = props.length
              ? this.fields.filter(field= > props.indexOf(field.prop) > - 1)
              : this.fields;
            fields.forEach(field= > {
            // The form-item instance method clearValidate (clears validation status and hints)
              field.clearValidate();
            });
          }
    Copy the code
  • Reset the entire form, resetting all field values to their original values and removing validation results

          resetFields() {
            // No form data return
            if (!this.model) {
              // Environment variable, non-production environment throw warning and returnprocess.env.NODE_ENV ! = ='production' &&
              console.warn('[Element Warn][Form]model is required for resetFields to work.');
              return;
            }
            this.fields.forEach(field= > {
              field.resetField();
            });
          }
    Copy the code

4. Listen for authentication rules

    // Listen for form validation rules
    watch: {
      rules() {
        // validateOnRuleChange is emitted immediately if no false is passed
        if (this.validateOnRuleChange) {
          / / verification
          this.validate((a)= >{}); }}}Copy the code

form-item

structure

  <div class="el-form-item" :class="[{ 'el-form-item--feedback': elForm && elForm.statusIcon, 'is-error': validateState === 'error', 'is-validating': validateState === 'validating', 'is-success': validateState === 'success', 'is-required': isRequired || required }, sizeClass ? 'el-form-item--' + sizeClass : '' ]">
    <! -- Form field tag text -->
    <label :for="labelFor" class="el-form-item__label" :style="labelStyle" v-if="label || $slots.label">
      <slot name="label">{{label + form.labelSuffix}}</slot>
    </label>
    <div class="el-form-item__content" :style="contentStyle">
      <! -- slots receive form validation elements, input boxes, checkboxes, checkboxes, etc. -->
      <slot></slot>
      <! -- invalid message -->
      <transition name="el-zoom-in-top">
        <div
          v-if="validateState === 'error' && showMessage && form.showMessage"
          class="el-form-item__error"
          :class="{ 'el-form-item__error--inline': typeof inlineMessage === 'boolean' ? inlineMessage : (elForm && elForm.inlineMessage || false) }"
        >
          {{validateMessage}}
        </div>
      </transition>
    </div>
  </div>
Copy the code

The outer div controls the overall style and is divided into two parts

  • Part 1: You can display labels or concatenate uniform suffixes (attributes from)
  • Part 2: Slot receives input,select and other components, and displays messages that are not validated

The variable elForm here is the FROM instance provided by the Form component

Script part

1. Inject receives Form instances and injects Form-item instances to descendants

    provide() { // Inject the form-item instance
      return {
        elFormItem: this
      };
    },
    // Receive the form instance
    inject: ['elForm']
Copy the code

2. After the component $EL is mounted to the instance

The el.form.blur and el.form.change events with validation rules are listened on, waiting for validation to be triggered.

    mounted() {
      // There is a form to validate
      if (this.prop) {
        // Look up the Form component and publish el.form.addField to expose the form-item instance
        // Let the Form component collect the form-item instance that needs validation
        this.dispatch('ElForm'.'el.form.addField'[this]);
        // Form data to validate
        let initialValue = this.fieldValue;
        / / array
        if (Array.isArray(initialValue)) {
          initialValue = [].concat(initialValue);
        }
        // The response property becomes a normal property
        Object.defineProperty(this.'initialValue', {
          value: initialValue
        });
        // This validates the rule
        let rules = this.getRules();

        if (rules.length || this.required ! = =undefined) {
          // Listen on el.form.blur and call back bluer event validation
          // Listen for the el.form.change event and call back to validate the change event
          this.$on('el.form.blur'.this.onFieldBlur);
          this.$on('el.form.change'.this.onFieldChange); }}}Copy the code
  • The dispatch method used here is borrowed from mixins: find the specified component and publish the specified event.

3. Verification method

      validate(trigger, callback = noop) {
        this.validateDisabled = false;
        // Trigger that conforms to the rule
        const rules = this.getFilteredRule(trigger);
        // No rule and not required returns true
        if((! rules || rules.length ===0) && this.required === undefined) {
          // Perform the callback
          callback();
          return true;
        }
        / / verification
        this.validateState = 'validating';

        const descriptor = {};
        // In order to match the format required by the AsyncValidator plugin, we need to do something with the rule data
        if (rules && rules.length > 0) {
          rules.forEach(rule= > {
            delete rule.trigger;
          });
        }
        // AsyncValidator requires validation rules
        descriptor[this.prop] = rules;
        // Validation rule AsyncValidator instance object
        const validator = new AsyncValidator(descriptor);
        const model = {};
        // AsyncValidator requires validation data
        model[this.prop] = this.fieldValue;
        / / verification
        validator.validate(model, { firstFields: true }, (errors, invalidFields) => {
          // Verify status
          this.validateState = ! errors ?'success' : 'error';
          // Verify prompts
          this.validateMessage = errors ? errors[0].message : ' ';
          // Perform the callback
          callback(this.validateMessage, invalidFields);
          // The form component publishes the validate event
          this.elForm && this.elForm.$emit('validate'.this.prop, ! errors); }); }Copy the code

4. Clear the authentication status and reset the form authentication method

      // Clear the authentication status and message
      clearValidate() {
        // Verify the status
        this.validateState = ' ';
        / / validate the message
        this.validateMessage = ' ';
        this.validateDisabled = false;
      },
      / / reset
      resetField() {
        this.validateState = ' ';
        this.validateMessage = ' ';

        // Get the initial data
        let model = this.form.model;// So form data
        let value = this.fieldValue; // The form data
        let path = this.prop; / / the
        if (path.indexOf(':')! = =- 1) {
          path = path.replace(/ :.'. ');
        }
        // The form data
        let prop = getPropByPath(model, path, true);

        this.validateDisabled = true;
        // Reset to the original form data
        if (Array.isArray(value)) {
          prop.o[prop.k] = [].concat(this.initialValue);
        } else {
          prop.o[prop.k] = this.initialValue;
        }
        // Look down at the Select component and publish the fieldReset event to expose the initial form data
        this.broadcast('ElTimeSelect'.'fieldReset'.this.initialValue);
      }
Copy the code

5. Listen for error and verify status

Error is received by props. If it is passed, the status becomes error and an error message is displayed

    watch: {
      / / to monitor the error
      error: {
        // Execute handler immediately
        immediate: true, 
        handler(value) {
          // The validation status changes to Error and an error message is displayed
          this.validateMessage = value;
          this.validateState = value ? 'error' : ' '; }},// Listen for validation status
      validateStatus(value) {
        this.validateState = value; }}Copy the code
6. Publish the Form to remove the collected form-item instance before instance destruction
    beforeDestroy() {
      // Look up the Form component and publish the el.form.removeField event to expose the current instance
      this.dispatch('ElForm'.'el.form.removeField'[this]);
    }

Copy the code

The association of a form component to a component such as input

How does from validation get triggered

Echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo echo

this.$on('el.form.blur'.this.onFieldBlur);
this.$on('el.form.change'.this.onFieldChange);
Copy the code

In the case of the input

// The form form publishes the el.form.blur event
this.dispatch('ElFormItem'.'el.form.blur'[this.currentValue]);
Copy the code

Take checkbox multiple selection as an example

// Listen for value to look up the form component publish the el.form.change event expose value (array)
this.dispatch('ElFormItem'.'el.form.change', [value]);
Copy the code

, etc… , these times trigger the checksum

$on is used to listen for the specified event and specify a callback function. $emit is used to publish an event and pass some data. If the name of the event is the same as the name of the published event, the callback function will be triggered and the parameters will be the same as those passed by $emit.

Official website direct instance method$onAs well as$emit