Writing code is sometimes like working with springs. A seemingly simple item may not be easy to process

1. Introduction

Recently, when I was working on a project, I saw that there were two requirements with the same functions but different interaction styles. In order to facilitate maintenance, I encapsulated the components. I found that a seemingly simple component, if it is to be encapsulated in general, there are also many things to consider.

This article is just an example of where you can start to increase the versatility of components. And provide some ideas for encapsulation. The components mentioned still have a lot to do with the requirements of the project, and are more or less custom developed for the project. They may not be available out of the box on other projects, and they will need to be modified if they are to be used.

2. Look at components first

This component looks simple enough to write in a moment

For the sake of space, CSS, and some unrelated JS code is not provided, need to see the source code can be moved: article example source :HandleButtonOld, project complete code: project code

HandleButtonOld.vue

<template>
  <div class="ec-handle">
    <div
      class="ec-handle--item"
      v-for="(item,index) in value"
      :key="index"
      :class="{'cur':nowClickIndex===index}"
      @click="switchCur(item,index)"
    >
      <ec-text v-if="item.fileType==='text'" :src="item.fileUrl"></ec-text>
      <video :src="item.fileUrl" v-if="item.fileType==='video'"></video>
      <audio :src="item.fileUrl" controls="controls" v-if="item.fileType==='audio'"></audio>
      <ec-image :src="item.fileUrl" v-if="item.fileType==='image'" />
      <ul class="customer-form-view-action-box">
        <li class="iconfont icon-icon-cus-edit" @click.stop="handleEvent('edit',index)"></li>
        <li
          class="iconfont icon-icon-cus-up"
          @click.stop="handleEvent('up',index)"
          v-if="index! = = 0"
        ></li>
        <li
          class="iconfont icon-icon-cus-down"
          @click.stop="handleEvent('down',index)"
          v-if="index! ==value.length-1"
        ></li>
        <li class="iconfont icon-icon-cus-del" @click.stop="handleEvent('delete',index)"></li>
      </ul>
    </div>
  </div>
</template>
<script>
export default {
  name: 'HandleButton',
  componentName: 'HandleButton',
  props: {
    value: {
      type: Array,
      default () {
        return[]}}},data () {
    return {
      nowClickIndex: ' '}},mounted () {},
  methods: {
    handleEvent (type, index) {
      let _list = JSON.parse(JSON.stringify(this.value))
      let _nowItem = _list[index]
      switch (type) {
        case 'up':
          this.nowClickIndex--
          _list.splice(index, 1)
          _list.splice(index - 1, 0, _nowItem)
          break
        case 'down':
          this.nowClickIndex++
          _list.splice(index, 1)
          _list.splice(index + 1, 0, _nowItem)
          break
        case 'delete':
          _list.splice(index, 1)
      }
      this.$emit('input', _list)
      this.$emit(type, _nowItem, index)
    },
    switchCur (item, index) {
      this.nowClickIndex = index
    }
  }
}
</script>
<style lang="scss"Slightly scoped > / / < / style >Copy the code

3. Improvement and optimization

Components are also easy to use, with a single line of code

<handle-button-old v-model="sortData"/>

sortData

sortData: [
    {
      fileType: 'text',
      content: 'Front-end development',
      index: 2,
      size: 12
    },
    {
      fileNmae: '251bb6d882024b11a6051d604ac51fc3.jpeg',
      fileType: 'image',
      fileUrl:
        'https://file-cdn-china.wechatify.net/marketing/sms/mms_material/53ce422f14e516af0eb9a5c7251cc1ca.jpeg',
      index: 3,
      size: 101109,
      fileName: '53ce422f14e516af0eb9a5c7251cc1ca.jpeg'
    },
    {
      fileType: 'text',
      content: 'wait',
      index: 5,
      size: 12
    }
 ]
Copy the code

But if there is a requirement on the page, the function is the same, the style is different, such as the following figure

The component is then unusable.

At this point, it’s definitely not copying a file, changing the style and writing another component, but changing the original component to be more generic and suitable for more needs.

Copying a file and writing a component is not recommended for such a requirement. The next time this happens, you have to copy another file and write another component. This can result in a large number of component files, affecting maintenance

To make components more generic and suitable for more needs, the main point is to extract frequently changing factors and give them to users to customize. What can be improved and optimized? Here are some simple examples

3-1. Supports custom content

Home page, see two requirements, typesetting style and display field is not the same. I don’t know what the third or fourth typesetting style will be, or what fields will be displayed. Therefore, it is better for him to operate the button to extract, as a component package, how to layout, display the field, the component does not care, just provide a slot for the user to define.

HandleButtonOld.vue

<template>
  <div class="ec-handle">
    <div
      class="ec-handle--item"
      v-for="(item,index) in value"
      :key="index"
      :class="{'cur':nowClickIndex===index}"
      @click="switchCur(index)"> <! -- provide slot --> <slot :data="item"></slot>
      
      <ul class="customer-form-view-action-box"> <! </div> </div> </template>Copy the code

Page calls

<handle-button-old v-model="sortData"> <! --> <div slot-scope="item" class="view-item">
    <span v-if="item.data.fileType==='text'">{{item.data.content}}123</span>
    <video :src="item.data.fileUrl" v-if="item.data.fileType==='video'"></video>
    <audio :src="item.data.fileUrl" controls="controls" v-if="item.data.fileType==='audio'"></audio>
    <img :src="item.data.fileUrl" v-if="item.data.fileType==='image'" />
  </div>
</handle-button-old>
Copy the code

3-2. Support custom selected styles

So if I go over here and look at what I’ve selected,

In addition to displaying a few action buttons, there is also a blue border line, but depending on your needs, the effect of the selection may be different, such as a gray double solid line in one place, a white line in another, a 30px margin, etc. It is impossible to guess what style will be selected the next time you use this component. Therefore, the selected style cannot be written or determined inside the handle-button-old, and can only be customized by users. All we can provide is a field that tells the user which item is currently selected.

HandleButtonOld.vue

<template>
  <div class="ec-handle">
    <div
      class="ec-handle--item"
      v-for="(item,index) in value"
      :key="index"
      :class="{'cur':nowClickIndex===index}"
      @click="switchCur(item,index)"> <! --> <slot :data="getItem(item,index)"</div> </div> </template> <script>exportMethods: {getItem (item, index) {$index) and whether currently selected ($select// Here is the way to pass the index in the past, is for the future need, not expand herereturn Object.assign({}, item, { $index: index, $select: this.nowClickIndex === index})} </script>Copy the code

Page calls

<! - according to$select<handle-button-old V-model ="sortData">
  <div slot-scope="item" class="view-item" :class="{'cur':item.data.$select}"</div> </handle-button-old> <style lang= "box-sizing: border-box! Important"scss">
.view-item {
    padding: 10px;
    border:4px dashed transparent;
    &.cur{
      border:4px double #ccc;
    }
}
</style>
Copy the code

This allows the user to customize the selected style

3-2. Set the display position and direction of the operation button

Look again at the styles of the two requirements

First, the position and orientation of the buttons are different. The position of the button can be given the default value, but also to allow the user to customize. To determine the location of a button, the handle-button-old component needs to provide top, right, bottom, and left parameters. In order to facilitate positioning, in addition to the specific pixel, percentage can be set, but also support users to enter’ center’, so that users can set vertical or horizontal center.

The direction of the button needs to provide the direction parameter, the user input horizontal vertical display, input vertical horizontal display

handle-button-old

<template>
  <div class="ec-handle">
    <div
      class="ec-handle--item"
      v-for="(item,index) in value"
      :key="index"
      :class="{'cur':nowClickIndex===index}"
      @click="switchCur(item,index)"
    >
      <slot :data="getItem(item,index)"></slot> <! <ul class= > <ul class="customer-form-view-action-box"
          :style="ulPosition"
          :class="{'handle-vertical':direction==='vertical'}"</ul> </div> </div> </template> <script>exportDefault {// props: {// props: {type: [String, Number],
      default: '0'
    },
    bottom: {
      type: [String, Number],
      default: 'auto'
    },
    left: {
      type: [String, Number],
      default: 'auto'
    },
    right: {
      type: [String, Number],
      default: '-26px'
    },
    direction: {
      type: String,
      default: 'horizontal'
    }
  },
  computed: {
    ulPosition () {
      let obj = {
        left: this.left,
        right: this.right,
        top: this.top,
        bottom: this.bottom
      }
      let _x = '0'
      let _y = '0'
      if (this.top === 'center' || this.bottom === 'center') {
        obj.top = '50%'
        obj.bottom = 'auto'
        _y = '50%'
        obj.transform = `translate(${_x}.${_y})`
      }
      if (this.left === 'center' || this.right === 'center') {
        obj.left = '50%'
        obj.right = 'auto'
        _x = '50%'
        obj.transform = `translate(${_x}.${_y})`
      }
      return obj
    }
  }
}
</script>
<style lang="scss" scoped>
.ec-handle--item {
  position: relative;
  ul {
    position: absolute;
    right: -26px;
    top: 0;
    display: none;
    line-height: 24px;
    &.handle-vertical {
      li {
        display: inline-block;
        vertical-align: top;
      }
    }
  }
}
</style>

Copy the code

Page calls

<! --> <handle-button-old v-model="sortData"
      direction="vertical"
      right="6px"
      bottom="center"
    >
  <div slot-scope="item" class="handle-item"</div> </handle-button-old>export default {
  data () {
    return {
      iconByFileType: {
        text: 'icon-wenben',
        image: 'icon-tupian1',
        video: 'icon-shipin',
        audio: 'icon-yinpin',
        link: 'icon-duanlian'}, {formatSize (val) {}, {formatSize (val) {if (val === 0) {
        return '0B'
      }
      let sizeObj = {
        MB: 1048576,
        KB: 1024,
        B: 1
      }
      val = +val
      for (let key in sizeObj) {
        if (val >= sizeObj[key]) {
          return+(val/sizeObj[key]).tofixed (2) + key}} </script> <style lang="scss"Scoped > </style>Copy the code

And so the effect is achieved

3-3. Set the display mode of the operation button

You can already see the problem. [3-2] ended up with only one of the items having an action button, while [3-2] initially saw the requirement that all the results should be displayed. So what we’re going to do here is set a display property, set how the action button is going to look. Currently three values are provided: ‘default’- selected items are shown, ‘Visible ‘- all items are shown, and’ None ‘- none is shown.

handle-button-old

<template>
  <div class="ec-handle">
    <div
      class="ec-handle--item"
      v-for="(item,index) in value"
      :key="index"
      :class="{'cur':nowClickIndex===index || display==='visible'}"
      @click="switchCur(item,index)"
    >
      <slot :data="getItem(item,index)"></slot>
      <ul class="customer-form-view-action-box"
          :style="ulPosition"
          v-if="display! =='none'"
          :class="{'handle-vertical':direction==='vertical'}"</ul> </div> </div> </template> <script>export default {
  props: {
    display: {
      type: [String],
      default: 'default'</script> </script>Copy the code

Page calls

<handle-button-old
      v-model="sortData"
      direction="vertical"
      right="6px"
      display="visible"
      bottom="center"</handle-button-old>Copy the code

That’s how it works

3-4. Trigger action before clicking the operation button

Many people encounter the need to be careful with a pop-up or other notification before performing “dangerous actions” such as deleting or erasing. The action button of this component may also be “dangerous action”. So you need to allow the user to customize the callback before the operation.

Take the handle-button-old component mentioned in the article as an example. If the “delete” button is required, it needs to remind the popup window, and other buttons can be operated directly.

handle-button-old

<template> <! </template> <script>export default {
  props: {
    beforeDelete: {
      type: Function
    },
    beforeUp: {
      type: Function
    },
    beforeDown: {
      type: Function
    },
    beforeEdit: {
      type: Function
    }
  },
  data () {
    return {
      nowClickIndex: ' ',
      eventType: ' ',
      curHandleIndex: ' '}}, methods: {/** * @description Execution event */handle () {
      let _list = this.value
      let _nowItem = _list[this.curHandleIndex]
      switch (this.eventType) {
        case 'up':
          this.nowClickIndex--
          _list.splice(this.curHandleIndex, 1)
          _list.splice(this.curHandleIndex - 1, 0, _nowItem)
          break
        case 'down':
          this.nowClickIndex++
          _list.splice(this.curHandleIndex, 1)
          _list.splice(this.curHandleIndex + 1, 0, _nowItem)
          break
        case 'delete':
          _list.splice(this.curHandleIndex, 1)
      }
      this.$emit('input', _list)
      this.$forceUpdate()
      this.$emit(this.eventType, _nowItem, this.curhandleIndex)}, /** * @description handles events */ handleEvent (eventType, item, this.curhandleIndex) EventType = eventType // Record the index of the current operation item this.curHandleIndex = indexlet _type = eventType.substr(0, 1).toUpperCase() + eventType.substr(1)
      if (typeof this[`before${_type}`] = = ='function') {// Pass the current operation function, current item, index as arguments to the calling function this[' before${_type}`](this.handle, item, index)
      } else{this.handle()},} </script>Copy the code

Page calls

<template>
    <handle-button-old
      v-model="sortData"
      direction="vertical"
      right="6px"
      display="visible"
      bottom="center"
      :beforeDelete="handleBefore"> <! </handle-button-old> </template> <script> methods: {/** * @description * @augmentsdone- Used to perform the operation * @augments item - current item * @Augments index - current index */ handleBefore (done, item, index) {// Click ok to proceed, click cancel to do not process this.$confirm('Are you sure you want to delete? ')
        .then(() => {
          done()
        })
        .catch(() => {})
    }
  }
</script>
Copy the code

3-5. Switch the trigger action of the selected item

For example, if there is a demand, click to switch the selected item, you need to take the data of the current item as the parameter of the request. To achieve this requirement, a custom event needs to be provided in the handle-button-old component

handle-button-old

Methods :{switchCur (item, index) {this.nowClickIndex = index // Trigger the custom event this.$emit('change', item, index)
    }
}

Copy the code

Page calls

<handle-button-old
  style="margin-bottom:500px;"
  v-model="sortData"
  direction="vertical"
  right="6px"
  display="visible"
  bottom="center"
  @change="handleChange"
>
  
</handle-button-old>
Copy the code

3-6. Deselect

You may have noticed this problem earlier in the day, but if you select one of these terms, the following happens.

But what if the requirement is to deselect? Then we can’t do it. From the logic of the code, as long as it is selected, it has to be selected, there is no way to cancel it. Therefore, the switchCur function 3-5 needs to check and deselect the current item if it is clicked

handle-button-old

methods:{
    switchCur (item, index) {
      
      if (this.display === 'visible') {
        return} // If the current item is clicked, deselect this.nowClickIndex = this.nowClickIndex! == index ? index :' '
      this.$emit('change', item, index)
    }
}
Copy the code

3-7. Button folding display

As you can see in the image above, the buttons are arranged either horizontally or vertically. If one day the button occupies too much space and needs to fold the display button, this is also easy to be compatible. Add a type parameter to handle-button-old to determine the display mode.

handle-button-old

<template>
  <div class="ec-handle">
    <div
      class="ec-handle--item"
      v-for="(item,index) in value"
      :key="index"
      :class="{'cur':nowClickIndex===index || display==='visible'}"
      @click="switchCur(item,index)"
    >
      <slot :data="getItem(item,index)"></slot> <! -- If not dropdown and dispaly is not none--> <ul class="customer-form-view-action-box"
        :style="ulPosition"
        v-if="type! =='dropdown'&&display! =='none'"
        :class="{'handle-vertical':direction==='vertical'}"> <! </ul> <! -- if the type is dropdown --> <el-dropdown v-else-if="type==='dropdown'" class="customer-form-view-action-box" :style="ulPosition" style="position:absolute;">
        <span class="el-dropdown-link"> operation < I class="el-icon-arrow-down el-icon--right"></i>
        </span>
        <el-dropdown-menu>
          <el-dropdown-item><div @click.stop="handleEvent('edit',item,index)"> Edit </div></el-dropdown-item> <el-dropdown-item v-if="index! = = 0"><div @click.stop="handleEvent('up',item,index)"</div></el-dropdown-item> <el-dropdown-item v-if="index! ==value.length-1"><div @click.stop="handleEvent('down',item,index)"> scroll down </div></el-dropdown-item> <el-dropdown-item><div @click.stop="handleEvent('delete',item,index)"Delete > < / div > < / el - dropdown can - item > < / el - dropdown can - menu > < / el - dropdown can > < / div > < / div > < / template >export default {
  name: 'HandleButton',
  componentName: 'HandleButton',
  props: {
    type: {
      type: String} // code omitted} // code omitted}Copy the code

Page calls

The direction parameter is invalid after type=”dropdown”

<! --type="dropdown"< span style= "max-width: 100%; clear: both; min-height: 1em"margin-bottom:500px;"
  v-model="sortData"
  direction="vertical"
  type="dropdown"
  right="6px"
  display="visible"
  bottom="center"
  :beforeDelete="handleBefore"
>
  <div slot-scope="item" class="handle-item">
    <div class="message-item___box">
      <span class="message-item___icon iconfont" :class="iconByFileType[item.data.fileType]"></span>
    </div>
    <div class="message-item___info">
      <p v-if="item.data.fileType==='text'">
        <span>{{item.data.content}}</span>
      </p>
      <p v-else>{{item.data.fileName}}</p>
      <span class="message-item___info_size">{{formatSize(item.data.size)}}</span>
    </div>
  </div>
</handle-button-old>
Copy the code

3-8. Button display mode

Going back to this scenario, you might have thought during development that in order for an action button to appear, you have to click on something to appear. But a lot of times the need, need to mouse on the time to display the operation button, do not need to click. To do this, just add a trigger parameter. Triggle defaults to ‘click’- click to appear, and ‘hover’- mouse to appear

<template>
  <div class="ec-handle">
    <div
      class="ec-handle--item"
      v-for="(item,index) in value"
      :key="index"
      :class="{'cur':nowClickIndex===index || display==='visible'}"
      @click="switchCur(item,index,'click')"
      @mouseenter="switchCur(item,index,'hover')"
      @mouseleave="handleMouseLeave"> <! </div> </div> </template> <script>exportDefault {props: {// props: {type: String,
      default: 'click'}}, methods: {switchCur (item, index, eventType) {if (this.display === 'visible') {
        return} // If the current triggered event is different from triggle, no action is performedif(eventType ! == this.triggle) {return} this.nowClickIndex = this.nowClickIndex ! == index ? index :' '
      this.$emit('change', item, index)
    },
    handleMouseLeave() {// If triggle is hover, uncheck the current item when the mouse is removedif (this.triggle === 'hover') {
        this.nowClickIndex = ' '} </script>Copy the code

Page calls

<handle-button-old v-model="sortData" triggle="hover"> <! </handle-button-old>Copy the code

3-9. Regarding others

For the first time, the handle-button-old component is used as an example to list some improved and optimized functions. There are plenty of features you can fiddle with if you want, such as button style (icon color, shape, background color, size, etc.), spacing, customization buttons, etc. As for whether to toss, it depends on the demand is necessary, specific circumstances, specific analysis. This is a very simple component. If it is a complex component, there may be more points to optimize.

4. Summary

When encapsulating components, if the requirements for components are simple at the beginning or the time is urgent, you can also encapsulate components with basic functions that can meet the requirements. Later, if the component does not meet the business requirements, it can be improved and optimized.

— — — — — — — — — — — — — — — — — — — — — — — — — gorgeous line — — — — — — — — — — — — — — — — — — — —

To learn more and communicate with me, please add me on wechat. Or follow my wechat public number: Waiting book Pavilion