In the last chapter we talked about Element’s ICONS, buttons, and links. In this chapter we’ll talk about forms

The form

The form is a little bit more code, and I’ll parse it step by step

Form

The first is the Created method of the Form component, which binds two events, one to add a field and one to delete a field

  this.$on('el.form.addField'.(field) = > {
    if (field) {
      this.fields.push(field); }});/* istanbul ignore next */
  this.$on('el.form.removeField'.(field) = > {
    if (field.prop) {
      this.fields.splice(this.fields.indexOf(field), 1); }});Copy the code

When FormItem executes a Mounted callback, the FormItem instance is stored by the event that triggers this

The four main methods on the Form component validate rules, validateField validate partial rules, resetFields reset checksum data, and clearValidate reset validates

Each of the four methods iterates through the child FormItem (aka field) and triggers its logic to process it, ValidateField and clearValidate filter FormItem options through files.filter (field => ~props. IndexOf (field.prop))

FormItem

mounted() {
  if (this.prop) {
    this.dispatch('ElForm'.'el.form.addField'[this]);
    let initialValue = this.fieldValue;
    if (Array.isArray(initialValue)) {
      initialValue = [].concat(initialValue);
    }
    Object.defineProperty(this.'initialValue', {
      value: initialValue
    });
    this.addValidateEvents(); }},beforeDestroy() {
  this.dispatch('ElForm'.'el.form.removeField'[this]);
}
Copy the code

The FormItem performs the dispatch method, which is mixed in through mixins, and the more interesting way to trigger the parent Form

 methods: {
    dispatch(componentName, eventName, params) {
      var parent = this.$parent || this.$root;
      var name = parent.$options.componentName;

      while(parent && (! name || name ! == componentName)) { parent = parent.$parent;if(parent) { name = parent.$options.componentName; }}if(parent) { parent.$emit.apply(parent, [eventName].concat(params)); }}}Copy the code

By looking up through the while, if there is a component whose name value is ElForm, that is, the Form component, its bound add/remove field event is executed

After the current FormItem instance is stored in the Form, fieldValue is added to the instance with initialValue as the key, because it is immutable by default as defined through Object.defineProperty, Obtain the corresponding prop of the model attribute of the Form through the prop attribute, and then monitor the change of data through the calculation attribute. The writing method of prop can be XXX or XXX: XXX or XXX.xxx

The value of fieldValue is processed by getPropByPath, which transforms path

/\[(\w+)\]/g => '.$1'
Copy the code
/ / ^ \. = > 'Copy the code
// getPropByPath returns the value
return {
    o: tempObj, // Prop points to the object of the value
    k: keyArr[i], // The key that prop points to
    v: tempObj ? tempObj[keyArr[i]] : null // the value that prop points to
  };
Copy the code

After processing, split(‘.’) the path to get the children of the model, such as path=’input.el.text’ corresponds to model.input.el.text, so fieldValue returns the corresponding value

fieldValue() {
    const model = this.form.model;
    if(! model || !this.prop) { return; }
    let path = this.prop;
    if (path.indexOf(':')! = = -1) {
      path = path.replace(/ :.'. ');
    }
    return getPropByPath(model, path, true).v;
  }
Copy the code

This.addvalidateevents () specifies whether rules are required or whether the parent component has rules

If one of the criteria is met, two events are added to listen for changes to input fields, checkboxes, and other form elements

addValidateEvents() {
        const rules = this.getRules(); // Get the form rule
        if (rules.length || this.required ! = =undefined) {
          this.$on('el.form.blur'.this.onFieldBlur);
          this.$on('el.form.change'.this.onFieldChange); }}Copy the code

These events are emitted when child components such as Input and Radio lose focus or change

Triggered after these events, will perform this. Validate (‘ blur ‘| |’ change ‘), and validate according to the first parameter to filter rules, getFilteredRule function according to the trigger is’ blur ‘| |’ change ‘filter

As can be seen, trigger supports two writing methods: string and array. The filtered rules are being shallow copied, and objectAssign is the polyfill of Object.assign

getFilteredRule(trigger) {
        const rules = this.getRules();
        return rules.filter(rule= > {
          if(! rule.trigger || trigger ===' ') return true;
          if (Array.isArray(rule.trigger)) {
            return rule.trigger.indexOf(trigger) > -1;
          } else {
            return rule.trigger === trigger;
          }
        }).map(rule= > objectAssign({}, rule));
      }
Copy the code

Store filtered rules into an object descriptor = {[this.prop]: Rules}, Element uses the async-validator library. It takes a rule object, rules, and uses it to generate validators to verify data. It calls the validate function and returns an error message if it does not match the rules. If the component has a Form, it executes the validate method attached to the Form. Unfortunately, I don’t see this method. If you want to trigger a single Form item for validation, you can use this validate method

validate(trigger, callback = noop) {
        this.validateDisabled = false;
        const rules = this.getFilteredRule(trigger);
        if((! rules || rules.length ===0) && this.required === undefined) {
          callback();
          return true;
        }
        this.validateState = 'validating';
        const descriptor = {};
        if (rules && rules.length > 0) {
          rules.forEach(rule= > {
            delete rule.trigger;
          });
        }
        descriptor[this.prop] = rules;
        const validator = new AsyncValidator(descriptor);
        const model = {};
        model[this.prop] = this.fieldValue;
        validator.validate(model, { firstFields: true }, (errors, invalidFields) = > {
          this.validateState = ! errors ?'success' : 'error';
          this.validateMessage = errors ? errors[0].message : ' ';
          callback(this.validateMessage, invalidFields);
          this.elForm && this.elForm.$emit('validate'.this.prop, ! errors,this.validateMessage || null);
        });
      }
Copy the code

ClearValidate is also very simple to clean up state

clearValidate() {
        this.validateState = ' ';
        this.validateMessage = ' ';
        this.validateDisabled = false;
      }
Copy the code

ResetField gets the relevant object and key via the getPropByPath function, and restores the initialValue via obj[key] = this.initialValue

The TimeSelect component is special and needs to trigger its fieldReset event, which I’ll talk about later

LabelWrap

This component is the label of the FormItem, which is implemented with Render and gets the autoLabelWidth (left margin) of the parent Form using getComputedStyle(this.$el.firstelementChild). Continuously calculate the text width and adapt the label

  render() {
    const slots = this.$slots.default;
    if(! slots)return null;
    if (this.isAutoWidth) {
      const autoLabelWidth = this.elForm.autoLabelWidth;
      const style = {};
      if(autoLabelWidth && autoLabelWidth ! = ='auto') {
        const marginLeft = parseInt(autoLabelWidth, 10) - this.computedWidth;
        if (marginLeft) {
          style.marginLeft = marginLeft + 'px'; }}return (<div class="el-form-item__label-wrap" style={style}>
        { slots }
      </div>);
    } else {
      return slots[0]; }}Copy the code

Tired, I will write here