Notes on source code analysis of element UI switch components

Posted by the-botman on Sat, 07 Dec 2019 05:30:37 +0100

The source code is as follows:

<template>
  <div
    class="el-switch"
    :class="{ 'is-disabled': switchDisabled, 'is-checked': checked }"
    role="switch"
    :aria-checked="checked"
    :aria-disabled="switchDisabled"
    @click="switchValue"
  >
    <input
      class="el-switch__input"
      type="checkbox"
      @change="handleChange"
      ref="input"
      :id="id"
      :name="name"
      :true-value="activeValue"
      :false-value="inactiveValue"
      :disabled="switchDisabled"
      @keydown.enter="switchValue"
    >

    <span
      :class="['el-switch__label', 'el-switch__label--left', !checked ? 'is-active' : '']"
      v-if="inactiveIconClass || inactiveText">
      <i :class="[inactiveIconClass]" v-if="inactiveIconClass"></i>
      <span v-if="!inactiveIconClass && inactiveText" :aria-hidden="checked">{{ inactiveText }}</span>
    </span>

    <span class="el-switch__core" ref="core" :style="{ 'width': coreWidth + 'px' }"></span>

    <span
      :class="['el-switch__label', 'el-switch__label--right', checked ? 'is-active' : '']"
      v-if="activeIconClass || activeText">
      <i :class="[activeIconClass]" v-if="activeIconClass"></i>
      <span v-if="!activeIconClass && activeText" :aria-hidden="!checked">{{ activeText }}</span>
    </span>
  </div>
</template>
<script>
  import Focus from 'element-ui/src/mixins/focus';
  import Migrating from 'element-ui/src/mixins/migrating';

  export default {
    name: 'ElSwitch',
    mixins: [Focus('input'), Migrating],
    //  Inject the El form object to prevent the problem that the object does not exist when it is not used with El form.
    inject: {
      elForm: {
        default: ''
      }
    },
    props: {
      value: {
        type: [Boolean, String, Number],
        default: false
      },
      disabled: { //Is it forbidden?
        type: Boolean,
        default: false
      },
      width: { //Width of switch (pixels)
        type: Number,
        default: 40
      },
      activeIconClass: { //The class name of the icon displayed when switch is turned on. Setting this option will ignore active text
        type: String,
        default: ''
      },
      inactiveIconClass: { //Class name of the icon displayed when switch is turned off. Setting this option will ignore inactive text
        type: String,
        default: ''
      },
      activeText: String, //Text description when switch is turned on
      inactiveText: String, //Text description when switch is turned off
      activeColor: { //Background color when switch is on
        type: String,
        default: ''
      },
      inactiveColor: { //Background color when switch is off
        type: String,
        default: ''
      },
      activeValue: { //Value when switch is on
        type: [Boolean, String, Number],
        default: true
      },
      inactiveValue: { //switch off value
        type: [Boolean, String, Number],
        default: false
      },
      name: { //name attribute corresponding to switch
        type: String,
        default: ''
      },
      id: String
    },
    data() {
      return {
        coreWidth: this.width
      };
    },
    created() {
      if (!~[this.activeValue, this.inactiveValue].indexOf(this.value)) {
        this.$emit('input', this.inactiveValue);
      }
    },
    computed: {
        //Current switch component status
        checked() {
            //Whether the value of the v-model binding in the parent component is equal to the value when switch is opened
            return this.value === this.activeValue;
        },
        //Is the current component disabled
      switchDisabled() {
        return this.disabled || (this.elForm || {}).disabled;
      }
    },
    watch: {
      checked() {
        this.$refs.input.checked = this.checked;
        //When the user has set active color and inactive color, set the background color of the switch through setBackgroundColor
        if (this.activeColor || this.inactiveColor) {
          this.setBackgroundColor();
        }
      }
    },
    methods: {
      handleChange(event) {
        //! if this.checked is true, it means that this.value === this.inactiveValue is currently closed; if you need to switch to the open state, you need to return this.activeValue
        this.$emit('input', !this.checked ? this.activeValue : this.inactiveValue);
        this.$emit('change', !this.checked ? this.activeValue : this.inactiveValue);
        this.$nextTick(() => {
            //Modifying the value value does not take effect immediately, and in order to prevent the parent component from not modifying the value, repeated assignments are made here
          this.$refs.input.checked = this.checked;
        });
      },
      //When the user has set active color and inactive color, when clicking the toggle switch, set the background color of the switch according to the value of this.checked
      setBackgroundColor() {
        //If this.checked is true, that is, the current switch is on, and the switch returns the background color set when it is turned on
        let newColor = this.checked ? this.activeColor : this.inactiveColor;
        this.$refs.core.style.borderColor = newColor;
        this.$refs.core.style.backgroundColor = newColor;
      },
      switchValue() {
        //Click only when it is not disabled
        !this.switchDisabled && this.handleChange();
      },
      getMigratingConfig() {
        return {
          props: {
            'on-color': 'on-color is renamed to active-color.',
            'off-color': 'off-color is renamed to inactive-color.',
            'on-text': 'on-text is renamed to active-text.',
            'off-text': 'off-text is renamed to inactive-text.',
            'on-value': 'on-value is renamed to active-value.',
            'off-value': 'off-value is renamed to inactive-value.',
            'on-icon-class': 'on-icon-class is renamed to active-icon-class.',
            'off-icon-class': 'off-icon-class is renamed to inactive-icon-class.'
          }
        };
      }
    },
    mounted() {
      /* istanbul ignore if */
      this.coreWidth = this.width || 40;
      if (this.activeColor || this.inactiveColor) {
        this.setBackgroundColor();
      }
      this.$refs.input.checked = this.checked;
    }
  };
</script>

Analysis:
(1) html structure of components

<div class="el-switch">
        <input class="el-switch__input" type="checkbox">
        <!--Show left label-->
        <span class="el-switch__label el-switch__label--left">
             <i></i>
             <span></span>
        </span>
        <!--Middle switch-->
        <span class="el-switch__core"></span>
        <!--Show label on right-->
        <span class="el-switch__label el-switch__label--right">
            <i></i>
            <span></span>
         </span>
</div>

The input tag is hidden. The code of css is as follows:

.el-switch__input {
    position: absolute;
    width: 0;
    height: 0;
    opacity: 0;
    margin: 0;
}

If the above style code is commented out, as shown in the figure:

The text display and switch status are controlled by the checked attribute of < input type = "checkbox" >. The outer layer of the div is to be able to switch the status by clicking the text.

(2) mixins in: [focus ('input '), migrating]

It is mainly migration.js, which is mainly used to prompt some properties and methods to be migrated or modified in the development environment.
Example: the element UI v2.4.9 I use, I write as follows: the off text attribute has been changed to inactive text in my current version

< El switch V-model = "Value2" off text = "off" >

All migrated properties are in the getMigratingConfig() method of the component:

 getMigratingConfig() {
        return {
          props: {
            'on-color': 'on-color is renamed to active-color.',
            'off-color': 'off-color is renamed to inactive-color.',
            'on-text': 'on-text is renamed to active-text.',
            'off-text': 'off-text is renamed to inactive-text.',
            'on-value': 'on-value is renamed to active-value.',
            'off-value': 'off-value is renamed to inactive-value.',
            'on-icon-class': 'on-icon-class is renamed to active-icon-class.',
            'off-icon-class': 'off-icon-class is renamed to inactive-icon-class.'
          }
        };
      }

(3) created method

 created() {
    //If the value of the v-model passed in by the user is neither active value nor inactive value, the inactive value is passed out and the switch is off
      if (!~[this.activeValue, this.inactiveValue].indexOf(this.value)) {
        this.$emit('input', this.inactiveValue);
      }
    },

~Represents the bitwise non operator. If [this.activevalue, this.inactive value]. Indexof (this. Value) is - 1, then the bitwise non becomes 0.

Reference blog: https://juejin.im/post/5b861db0e51d4538aa1b5630
http://www.zhuyuntao.cn/2018/10/24/element-ui-focus-js and migration.js file source code learning

Topics: Javascript Attribute