preface

We already know how to create a tag component by creating the first vUE component air-UI (5) — Button. In this section we continue to look at how to create service-type components.

Create a component of service type

Take the notification component as an example. It is a typical built-in service component. It is bound to the prototype of Vue and used as a global method.

The global method of setting up a VUE is quite common, such as using AXIos to mount a Vue prototype when making ajax requests, as follows:

// 1 introduces vue and axios
import Vue from 'vue'
import axios from 'axios'
// 2 Some encapsulation of AXIos
// code ...

// 3 Then mount it to the prototype
Vue.prototype.$axios = axios
Copy the code

Use this.$axios

// The axios.get() method works like this
this.$axios.get()
Copy the code

It’s really convenient that you don’t have to import every component that uses Axios. This approach is quite simple, and the built-in services work much the same way, but with some DOM manipulation involved. Take notification for example: Element-UI notification notifications

There are three steps, and the directory structure is as follows:

components/
|    |--- notification/
|    |     |--- src/
|    |     |     |--- main.js
|    |     |     |--- main.vue
|    |     |--- index.js
Copy the code

Create a VUE component

Because involves the dom manipulation, so the first step is to create a notification corresponding vue component, is the notification/SRC/main vue:

<template> <transition name="air-notification-fade"> <div :class="['air-notification', customClass, horizontalClass]" v-show="visible" :style="positionStyle" @mouseenter="clearTimer()" @mouseleave="startTimer()" @click="click" role="alert" > <i class="air-notification__icon" :class="[ typeClass, iconClass ]" v-if="type || iconClass"> </i> <div class="air-notification__group" :class="{ 'is-with-icon': typeClass || iconClass }"> <h2 class="air-notification__title" v-text="title"></h2> <div class="air-notification__content" v-show="message"> <slot> <p v-if="! dangerouslyUseHTMLString">{{ message }}</p> <p v-else v-html="message"></p> </slot> </div> <div class="air-notification__closeBtn air-icon-close" v-if="showClose" @click.stop="close"></div> </div> </div> </transition> </template> <script type="text/babel"> let typeMap = { success: 'success', info: 'info', warning: 'warning', error: 'error' }; export default { data() { return { visible: false, title: '', message: '', duration: 4500, type: '', showClose: true, customClass: '', iconClass: '', onClose: null, onClick: null, closed: false, verticalOffset: 0, timer: null, dangerouslyUseHTMLString: false, position: 'top-right' }; }, computed: { typeClass() { return this.type && typeMap[this.type] ? `air-icon-${ typeMap[this.type] }` : ''; }, horizontalClass() { return this.position.indexOf('right') > -1 ? 'right' : 'left'; }, verticalProperty() { return /^top-/.test(this.position) ? 'top' : 'bottom'; }, positionStyle() { return { [this.verticalProperty]: `${ this.verticalOffset }px` }; } }, watch: { closed(newVal) { if (newVal) { this.visible = false; this.$el.addEventListener('transitionend', this.destroyElement); } } }, methods: { destroyElement() { this.$el.removeEventListener('transitionend', this.destroyElement); this.$destroy(true); this.$el.parentNode.removeChild(this.$el); }, click() { if (typeof this.onClick === 'function') { this.onClick(); } }, close() { this.closed = true; if (typeof this.onClose === 'function') { this.onClose(); } }, clearTimer() { clearTimeout(this.timer); }, startTimer() { if (this.duration > 0) { this.timer = setTimeout(() => { if (! this.closed) { this.close(); } }, this.duration); } }, keydown(e) { if (e.keyCode === 46 || e.keyCode === 8) { this.clearTimer(); Else if (e.keycode === 27) {// Esc closes message if (! this.closed) { this.close(); } } else { this.startTimer(); }}, mounted() {if (this.duration > 0) {this.timer = setTimeout(() => {if (! this.closed) { this.close(); } }, this.duration); } document.addEventListener('keydown', this.keydown); }, beforeDestroy() { document.removeEventListener('keydown', this.keydown); }}; </script>Copy the code

The specific logic is not detailed, it is an ordinary VUE component, nothing more than a DOM structure, and the triggering and monitoring of various events and parameters, it is not difficult to understand, can be understood as the DOM rendering of notification component.

Constructs the structure of notification

Now that the DOM is ready, it’s just a matter of building the Notification structure and letting the various methods of that structure manipulate the DOM. So the notification/SRC/main. Js:

import Vue from 'vue';
import Main from './main.vue';
import merge from '.. /.. /.. /.. /src/utils/merge';
import { PopupManager } from '.. /.. /.. /.. /src/utils/popup';
import { isVNode } from '.. /.. /.. /.. /src/utils/vdom';
const NotificationConstructor = Vue.extend(Main);

let instance;
let instances = [];
let seed = 1;

const Notification = function(options) {
  if (Vue.prototype.$isServer) return;
  options = merge({}, options);
  const userOnClose = options.onClose;
  const id = 'notification_' + seed++;
  const position = options.position || 'top-right';

  options.onClose = function() {
    Notification.close(id, userOnClose);
  };

  instance = new NotificationConstructor({
    data: options
  });

  if (isVNode(options.message)) {
    instance.$slots.default = [options.message];
    options.message = 'REPLACED_BY_VNODE';
  }
  instance.id = id;
  instance.$mount();
  document.body.appendChild(instance.$el);
  instance.visible = true;
  instance.dom = instance.$el;
  instance.dom.style.zIndex = PopupManager.nextZIndex();

  let verticalOffset = options.offset || 0;
  instances.filter(item= > item.position === position).forEach(item= > {
    verticalOffset += item.$el.offsetHeight + 16;
  });
  verticalOffset += 16;
  instance.verticalOffset = verticalOffset;
  instances.push(instance);
  return instance;
};

['success'.'warning'.'info'.'error'].forEach(type= > {
  Notification[type] = options= > {
    if (typeof options === 'string' || isVNode(options)) {
      options = {
        message: options
      };
    }
    options.type = type;
    return Notification(options);
  };
});

Notification.close = function(id, userOnClose) {
  let index = - 1;
  const len = instances.length;
  const instance = instances.filter((instance, i) = > {
    if (instance.id === id) {
      index = i;
      return true;
    }
    return false; }) [0];
  if(! instance)return;

  if (typeof userOnClose === 'function') {
    userOnClose(instance);
  }
  instances.splice(index, 1);

  if (len <= 1) return;
  const position = instance.position;
  const removedHeight = instance.dom.offsetHeight;
  for (let i = index; i < len - 1; i++) {
    if (instances[i].position === position) {
      instances[i].dom.style[instance.verticalProperty] =
        parseInt(instances[i].dom.style[instance.verticalProperty], 10) - removedHeight - 16 + 'px'; }}}; Notification.closeAll =function() {
  for (let i = instances.length - 1; i >= 0; i--) { instances[i].close(); }};export default Notification;
Copy the code

This logic is not difficult to understand, mainly divided into several steps:

  1. Previously created vUE components, throughVue.extendTo generate aA subclass
const NotificationConstructor = Vue.extend(Main);
Copy the code

This can be understood as a constructor for Notification. As long as new initializes this function, it can generate an instantiation object for Notification:

instance = new NotificationConstructor({
data: options
});
Copy the code

So the Notification object is essentially a factory function that, whenever called, instantiates a brand new object with a Notification Vue component.

  1. Instantiate the object, mount it, and add it to the body to display
instance.$mount();  // Without el, an unmounted instance will be mounted and the template will be rendered as an element outside the document
document.body.appendChild(instance.$el); // You must use the native DOM API to insert it into the document
Copy the code
  1. And then we have thisNotificationOther methods on this object, for examplecloseAnd so on, with a global object in a closure stateinstancesTo hold these instantiated objects and operate on these instances by id

In general, the logic is not complicated.

The final index. Js

Notification /index.js: notification/index.js:

import Notification from './src/main.js';
export default Notification;
Copy the code

Mount to the Vue prototype

The next step is to mount it, again in the components/index.js install method, but instead of using component syntax, mount it directly to the vue prototype:

import Button from './button'
import ButtonGroup from './button-group'
import Notification from './notification'

const components = {
  Button,
  ButtonGroup
}

const install = function (Vue) {
  Object.keys(components).forEach(key= > {
    Vue.component(components[key].name, components[key])
  })
  Vue.prototype.$notify = Notification
}

export default {
  install
}
Copy the code

SRC /styles/index.scss SRC /styles/index.scss

@import "./base.scss";
@import "./button.scss";
@import "./button-group.scss";
@import "./notification.scss";
Copy the code

Write down the example

Home. Vue:

<air-button plain @click="open1">Automatic closing</air-button>
<air-button plain @click="open2">Does not automatically shut down</air-button>
<air-button plain @click="open3">successful</air-button>
<air-button plain @click="open4">warning</air-button>
<air-button plain @click="open5">The message</air-button>
<air-button plain @click="open6">error</air-button>
Copy the code

Script fills the corresponding trigger method:

<script> export default {data () {return {MSG: 'air-ui - based on vue2.x, reusable UI components'}}, methods: { open1() { const h = this.$createElement; This.$notify({title: 'title ', message: h(' I ', {style: 'color: teal'},' teal')}); }, open2() {this.$notify({title: 'warning ', message:' this is a message that does not close automatically ', duration: 0}); }, open3() {this.$notify({title: 'success', message: 'success', type: 'success'}); }, open4() {this.$notify({title: 'warning', message: 'warning', type: 'warning'}); }, open5() {this.$notify.info({title: 'message ', message:' this is a message '}); }, open6() {this.$notify.error({title: 'error ', message:' error '}); } } } </script>Copy the code

Notice how this.$nofify is called: this.$nofify

In this way, a component of the built-in service is created.

Pay attention to

Note that the components of the built-in service cannot be called with vue.use (), otherwise the project will run once by default, even if they are not used. For example, I changed SRC /main.js to show a reference to notification mount and use:

import Notification from './components/notification'

Vue.prototype.$notify = Notification
Vue.use(Notification)
Copy the code

In this case, compilation is fine, but when I load the page, it will default to an empty popup box.

This is because when I call use, I execute a Notification object by default to instantiate a Notification object, but because no arguments are passed in, it is an empty popup. So for built-in service components such as Notification, Message, MessageBox, as long as they are bound to the VUE prototype object, do not use to introduce.

conclusion

The creation of built-in service components, and that’s about it. In the next section we’ll see how to create a directive component.


Series of articles:

  • Air-ui (1) — Why do I need to build an Element UI component
  • Self-built VUE component AIR-UI (2) — Take a look at the Element UI project
  • Self-built VUE component AIR-UI (3) – CSS development specification
  • Air-ui (4) — Air-UI environment setup and directory structure
  • Air-ui (5) — Create the first vUE component, Button
  • Self-built VUE component AIR-UI (6) – Creates built-in service components
  • Build vUE component AIR-UI (7) – Create command component
  • Self-built VUE component AIR-UI (8) — Implementation part introduces components
  • Build your own VUE component air-UI (9) — document with Vuepress
  • Air-ui (10) — Vuepress Documentation (Advanced version)
  • Vue Component Air-UI (11) — Vuepress Documentation (Crawl version)
  • Self-built VUE component AIR-UI (12) — Internationalization mechanism
  • Self-built VUE Component AIR-UI (13) — Internationalization Mechanism (Advanced Version)
  • Self-built VUE component AIR-UI (14) — Packaged Build (Dev and Dist)
  • Self-built VUE component AIR-UI (15) — Theme customization
  • Self-built VUE component AIR-UI (16) – Packages to build pub tasks
  • Build your own VUE component AIR-UI (17) – Develop a pit crawl and summary