The Message component

Preface: just began to learn the source code of element-UI, if there is an analysis of the wrong place, welcome everyone to point out the message
Knowledge:
  • hasOwnProperty:
  1. HasOwnProperty indicates whether an object has a specified property in its properties. It determines whether a property is defined in the object itself and not inherited from the prototype chain.

  2. Why use the use Object. The prototype. The hasOwnProperty. Call (myObj, prop) instead of myObj. HasOwnProperty (prop)?

    Obj.hasownproperty (prop) can be used to determine whether this property is present

    Such as:

    o = new Object(a); o.prop ='exists';
    o.hasOwnProperty('prop');             / / return true
    o.hasOwnProperty('toString');         / / returns false
    o.hasOwnProperty('hasOwnProperty');   / / returns false
    Copy the code

    Use hasOwnProperty as the property name of an object

    Since javaScript does not use hasOwnProperty as a sensitive word, it is possible to name one of the object’s properties hasOwnProperty, so that the object’s prototype hasOwnProperty method can no longer be used to determine if the property is from the prototype chain.

    var foo = {
        hasOwnProperty: function() {
            return false;
        },
        bar: 'Here be dragons'
    };
     
    foo.hasOwnProperty('bar'); // Always return false
    Copy the code

    Can’t use the object. HasOwnProperty method, how to solve this problem? We need to use the real hasOwnProperty method on the prototype chain:

    ({}).hasOwnProperty.call(foo, 'bar'); // true
    / / or:
    Object.prototype.hasOwnProperty.call(foo, 'bar'); // true
    Copy the code

    Element – UI source code:

    //vdom.js
    export function isVNode(node) {
      returnnode ! = =null && typeof node === 'object' && hasOwn(node, 'componentOptions');
    }
    
    // util.js 
    const hasOwnProperty = Object.prototype.hasOwnProperty;
    export function hasOwn(obj, key) {
      return hasOwnProperty.call(obj, key);
    }
    Copy the code
  • Vue.extend:

    Vue.extend returns an “extended instance constructor,” which is the Vue instance constructor with partial options preset. Vue.com Ponent is often used to generate components, which can be simply interpreted as that when a custom element with the component name as a label is encountered in a template, the “Extended Instance Constructor” is automatically invoked to produce component instances and mount them to the custom element.

Source:

The main.vue component part

<template>
  <transition name="el-message-fade" @after-leave="handleAfterLeave">
    <div
      :class="[ 'el-message', type && !iconClass ? `el-message--${ type }` : '', center ? 'is-center' : '', showClose ? 'is-closable' : '', customClass ]"
      :style="positionStyle"
      v-show="visible"
      @mouseenter="clearTimer"
      @mouseleave="startTimer"
      role="alert">
      <i :class="iconClass" v-if="iconClass"></i>
      <i :class="typeClass" v-else></i>
      <slot>
        <p v-if=! "" dangerouslyUseHTMLString" class="el-message__content">{{ message }}</p>
        <p v-else v-html="message" class="el-message__content"></p>
      </slot>
      <i v-if="showClose" class="el-message__closeBtn el-icon-close" @click="close"></i>
    </div>
  </transition>
</template>

<script>
  const typeMap = {
    success: 'success'.info: 'info'.warning: 'warning'.error: 'error'
  };

  export default {
    data() {
      return {
        visible: false.// Show hide
        message: ' '.// Message text
        duration: 3000.// Display time in milliseconds. If this parameter is set to 0, it will not be automatically closed
        type: 'info'./ / theme
        iconClass: ' '.// The class name of the custom icon overrides the type
        customClass: ' '.// Define the class name
        onClose: null.// Close the callback function
        showClose: false.// Whether to display the close button
        closed: false.// Whether to close
        verticalOffset: 20.// Distance to the top
        timer: null./ / timer
        dangerouslyUseHTMLString: false.// Whether to treat the message attribute as an HTML fragment
        center: false // Whether the text is centered
      };
    },

    computed: {
      typeClass() {
        return this.type && !this.iconClass
          ? `el-message__icon el-icon-${ typeMap[this.type] }`
          : ' ';
      },
      // Control the distance at the top
      positionStyle() {
        return {
          'top': `The ${this.verticalOffset }px`}; }},watch: {
      closed(newVal) {
        if (newVal) {
          this.visible = false; }}},methods: {
      handleAfterLeave() {
        this.$destroy(true);
        this.$el.parentNode.removeChild(this.$el);
      },
      / / close
      close() {
        this.closed = true;
        if (typeof this.onClose === 'function') {
          this.onClose(this); }},clearTimer() {
        clearTimeout(this.timer);
      },
      // How long after message appears to close
      startTimer() {
        if (this.duration > 0) {
          this.timer = setTimeout(() = > {
            if (!this.closed) {
              this.close(); }},this.duration); }},// Esc closes the message
      keydown(e) {
        if (e.keyCode === 27) { 
          if (!this.closed) {
            this.close(); }}}},mounted() {
      this.startTimer();
      document.addEventListener('keydown'.this.keydown);
    },
    beforeDestroy() {
      document.removeEventListener('keydown'.this.keydown); }};</script>
Copy the code

The main.js call part (the PopupManager used here will be analyzed later)

import Vue from 'vue';
import Main from './main.vue';
import { PopupManager } from '@/utils/popup';
import { isVNode } from '@/utils/vdom';
let MessageConstructor = Vue.extend(Main);

let instance;
let instances = [];
let seed = 1;
// Message initialization method
const Message = function(options) {
  if (Vue.prototype.$isServer) return;
  options = options || {};
  if (typeof options === 'string') {
    options = {
      message: options
    };
  }
   // The closing method passed in from the outside world
  let userOnClose = options.onClose;
  let id = 'message_' + seed++; // Popover ID is incremented each time to ensure uniqueness

  // Register the close event and trigger the close event in main.vue to call onClos here
  options.onClose = function() {
    Message.close(id, userOnClose);
  };
  instance = new MessageConstructor({  // Create a message object, data in main.vue, overridden by options
    data: options
  });
  instance.id = id;
    //isVNode is used to receive a VNode as an argument in the following format:
    // const h = this.$createElement;
    // this.$message({
    // message: h('p', null, [
    // h('span', null, 'content can be '),
    // h('i', { style: 'color: teal' }, 'VNode')
    / /])
    / /});
  if (isVNode(instance.message)) {
    // Assign this to the default slot
    instance.$slots.default = [instance.message];
    instance.message = null;
  }
  // Mount the instance into the body
  instance.$mount();
  document.body.appendChild(instance.$el); // Add a pop-up message to the body
  let verticalOffset = options.offset || 20;
  instances.forEach(item= > {
    verticalOffset += item.$el.offsetHeight + 16; //// calculates the actual location based on how many messages pop up now
  });
  instance.verticalOffset = verticalOffset; // Assign the instance to the top offset
  instance.visible = true;
  instance.$el.style.zIndex = PopupManager.nextZIndex();
  instances.push(instance);
  return instance;
};
// Bind the initialization event for a separate call. Such as: Message. Success (options)
['success'.'warning'.'info'.'error'].forEach(type= > {
  Message[type] = options= > {
    if (typeof options === 'string') {
      options = {
        message: options
      };
    }
    options.type = type;
    return Message(options);
  };
});
// Popover close event
Message.close = function(id, userOnClose) {
  let len = instances.length;
  let index = -1;
  let removedHeight;
  for (let i = 0; i < len; i++) {
    if (id === instances[i].id) {
      removedHeight = instances[i].$el.offsetHeight;
      index = i;
      if (typeof userOnClose === 'function') {
        userOnClose(instances[i]);
      }
       // Delete this instance
      instances.splice(i, 1);
      break; }}if (len <= 1 || index === -1 || index > instances.length - 1) return;
  for (let i = index; i < len - 1 ; i++) {
    let dom = instances[i].$el;
    dom.style['top'] =
      parseInt(dom.style['top'].10) - removedHeight - 16 + 'px'; }};// Manually close all instances
Message.closeAll = function() {
  for (let i = instances.length - 1; i >= 0; i--) { instances[i].close(); }};export default Message;

Copy the code