Basic introduction

Now there are a lot of component library based on Vue, but look at the message has not been seen a lot of comments of components, this to want to do some articles information display class projects can seem difficult, because there are too many page need this function, do we need to repeat to write (copy and paste the code? For the modular system is now gradually perfect front-end engineering projects, one-time encapsulation of a common function of the component is very necessary, and now we go to encapsulate such a component!

Required Technology (Vue)

Since the packaged components are based on Vue, this requires us to master some knowledge of Vue (Vue is recommended to go to the official documentation for related knowledge :grinning:), Students with basic Vue skills can deepen their understanding of Vue componentized programming by encapsulating such a fully-functional component. Now let’s implement the encapsulation of this component

Essential text input box

We know that native HTML elements only form elements can enter text, but these elements are rendered differently by default in different browsers and do not support formatted text, so we need to use Div elements to simulate an input box with the ability to format text.

How to get divs to enter text is a new HTML5 property, “contenteditable”. The attribute value is a Boolean type, adding this attribute tag element and set its attributes value is true (the default is not set the browser parses to true), and now this element is a can edit the content of elements, the user can like use form elements to use it, now let us use code to demonstrate it

<! Only the main code is shown here

<style>
.inputBox{
  border:1px solid;
  height:200px;
  width:400px;
}
</style>


<div class="inputBox" contenteditable="true">I'm an editable element</div>
Copy the code

This is what the page actually looks like

So here we have a text field, and then you can add some other styles to it to make it better, which I won’t show you here.

Encapsulate input box components

For better code logic decoupling and later component maintainability, we separate parts of the editor input box into a component for encapsulation. With the previous knowledge, packaging such a component is very easy to use, specific is how to achieve we directly look at the code!

<template>
  <div class>
    <div type="text" class="input-box-wrapper">
      <div
        :class="['content',{focused},type]"
        ref="richText"
        v-on="listeners"
        v-bind="$attrs"
        :contenteditable="contenteditable"
      ></div>
      <div class="append-wrapper">
        <slot name="append"></slot>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: 'input-box'.data() {
    return {
      contenteditable: true}},computed: {
    listeners() {
      return Object.assign(
        {},
        this.$listeners,
        {
          input: function(e) {
            const inputContent =
              this.contentType === 'plain'
                ? e.target.textContent
                : e.target.innerHTML
            this.$emit('input', inputContent)
          }.bind(this)})}},props: {
    focused: {
      type: Boolean.default: false
    },
    contentType: {
      type: String.default: 'plain'.validator(value) {
        return ['plain'.'rich'].includes(value)
      }
    },
    type: {
      type: String.default: 'text'.validator(value) {
        return ['text'.'textarea'].includes(value)
      }
    },
    rows: Number
  },
  methods: {
    focus() {
      this.$refs.richText.focus()
    }
  }
}
</script>


Copy the code

The actual component looks like this

At this point, we have a text field component that is simulated with the Div element.

Note: The complete code for the input-box component comes from the open source project comment-message-Editor

Rich emoji picker component (: Smile:)

Mixing text and emoticons when we leave a comment on an article or a review can make our language more concise and expressive. So why encapsulate the symbol selector component? The answer is: in an increasingly complex front-end engineering environment, we need to remove all future functional modules that can be reused or extended to be maintained, so that we can maintain and commit them in the future, whether we change the code or add new functionality. Here incidentally also the component of the source code posted out total we usually do the same type of function can refer to.

The emoji-picker.vue code comes from the open source project comment-Editor

<template>
  <div
    @keyup.esc="hidePicker"
    ref="container"
    class="emoji-wrapper"
    hidefocus="true"
    v-on="handleMouse()"
  >
    <span class="emoji-button" @click.stop="togglePickerVisibility">
      <img
        :class="{inactive:! pickerVisible}"
        class="button-icon"
        src=".. /emoji/icon.svg"
        width="20"
        height="20"
        alt
      />
      <span v-if="buttonTextVisible" class="button-text">expression</span>
    </span>
    <ul :class="['emoji-picker',pickerPosition]" v-if="pickerVisible">
      <li v-for="(url,key) in files" :key="key" class="emoji-picker-item">
        <img class="emoji-icon" @click="handlerSelect" width="20" height="20" :src="url" alt />
      </li>
    </ul>
  </div>
</template>
<script type="text/javascript">
const path = require('path')
const requireEmoji = require.context('.. /emoji')
let files = requireEmoji.keys()
export default {
  data() {
    return {
      pickerVisible: false.files: files.map(url= > require(`.. /emoji/${url.slice(2)}`}})),props: {
    buttonTextVisible: {
      type: Boolean.default: true
    },
    triggerPick: {
      tyep: String.default: 'hover'.validator(value) {
        return ['hover'.'click'].includes(value)
      }
    },
    pickerPosition: {
      type: String.default: 'right'.validator(value) {
        return ['left'.'middle'.'right'].includes(value)
      }
    }
  },
  watch: {
    pickerVisible(newValue) {
      newValue ? this.$emit('activated') : this.$emit('inactivated')}},mounted() {
    const docHandleClick = (this.docHandleClick = e= > {
      if (!this.$refs.container.contains(e.target)) {
        this.hidePicker()
      }
    })
    const handleKeyup = (this.handleKeyup = e= > {
      if (e.key === 'Escape') {
        this.hidePicker()
      }
    })
    document.addEventListener('click', docHandleClick)
    document.addEventListener('keyup', handleKeyup)
  },
  destroyed() {
    document.removeEventListener('click'.this.docHandleClick)
    document.removeEventListener('click'.this.handleKeyup)
  },
  methods: {
    handlerSelect(e) {
      this.$emit('selected', e)
    },
    hidePicker() {
      this.pickerVisible = false
    },
    togglePickerVisibility() {
      if (this.triggerPick === 'click') {
        this.pickerVisible = !this.pickerVisible
      }
    },
    handleMouse() {
      const mouseenter = function() {
        this.pickerVisible = true
      }.bind(this)
      const mouseleave = function() {
        this.pickerVisible = false
      }.bind(this)
      if (this.triggerPick === 'hover') {
        return {
          mouseenter,
          mouseleave
        }
      } else {
        return{}}}}}</script>

Copy the code

Assemble the editor entry component

We have the input box component and the emoticon picker component, and we just need to combine them in a certain way, and our comment editor component is done. So without further ado let’s look at the code

The source code for main.vue comes from the open source project comment-message-Editor

<template>
  <div class="comment-editor" ref="container">
    <div class="input-wrapper" :class="{inline}">
      <input-box
        ref="inputBox"
        :type="inline? 'text':'textarea'"
        content-type="rich"
        :rows="2"
        @focus="onInputFocus"
        @blur="onInputBlur"
        @keyup.enter.ctrl.exact.native="handlerSubmit"
        v-model="inputContent"
        :placeholder="'placeholder'"
        :focused="showInlineButton"
        class="input-box"
      >
        <div v-if="inline" :class="['input-append',{hasbg:!showInlineButton}]" slot="append">
          <emoji-picker
            ref="emojiPicker"
            trigger-pick="click"
            @activated="inputBoxFocused=true"
            @selected="handlerEmojiSelected"
            picker-position="left"
            :button-text-visible="false"
          ></emoji-picker>
        </div>
      </input-box>
      <transition name="button" >
        <div
          @click="handlerSubmit"
          class="submit-button inline"
          :disabled=! "" inputContent"
          ref="button"
          v-show="showInlineButton && inline"
        >{{buttonText}}</div>
      </transition>
    </div>
    <div class="footer-action" v-if=! "" inline">
      <emoji-picker
        trigger-pick="click"
        @activated="$refs.inputBox.focus()"
        @selected="handlerEmojiSelected"
      ></emoji-picker>
      <span class="submit-tiptext">Ctrl + Enter</span>
      <div @click="handlerSubmit" class="submit-button" :disabled=! "" inputContent">{{buttonText}}</div>
    </div>
  </div>
</template>
<script>
import InputBox from './components/input-box'
import EmojiPicker from './components/emoji-picker'
export default {
  name: 'comment-editor'.components: { InputBox, EmojiPicker },
  data() {
    return {
      active: false.inputContent: ' '.inputBoxFocused: false}},props: {
    buttonText: {
      type: String.default: 'submit'
    },
    inline: {
      type: Boolean.default: false}},computed: {
    showInlineButton() {
      return!!!!! (this.inputBoxFocused || this.inputContent)
    }
  },
  destroyed() {
    document.removeEventListener('click'.this.hideButton)
  },
  mounted() {
    document.addEventListener('click'.this.hideButton)
  },
  methods: {
    focus(){
      this.$refs.inputBox.focus()
    },
    hideButton(e) {
      if (this.$refs.container.contains(e.target)) {
        return
      }
      if (!this.$refs.container.contains(e.target)) {
        this.inputBoxFocused = false}},onInputFocus(e) {
      this.inputBoxFocused = true
    },
    onInputBlur(e) {
      if (this.$refs.container.contains(e.target)) {
        return
      }
      this.inputBoxFocused = false
    },
    handlerSubmit(e) {
      if (e.target.hasAttribute('disabled')) {
        return
      }
      this.$emit('submit'.this.inputContent)
    },
    handlerEmojiSelected(e) {
      this.$refs.inputBox.focus()
      const clonedNode = e.target.cloneNode(true)
      clonedNode.style.verticalAlign = 'text-top'
      document.execCommand('insertHTML'.false, clonedNode.outerHTML)
    }
  }
}
</script>


Copy the code

Component preview

The warehouse address of this project is attached at the end of the article

Note: this article belongs to the author’s original, reprint please indicate the source, thank you! Author: Kong Lingwen