Often used in the project to choose, choose color, choose brand… A variety of choices, as shown in the figure below, are usually made using Radio or checkbox, which usually requires a lot of logic and native style modification, which is time-consuming and costly. We simply use the most primitive way to package into components, only pay attention to the rendering of each Item, and put the logic such as selection into the components to complete. Just focus on event callbacks externally.

Again in terms of component requirements, we expect the following usage:

 <CheckGroup class="selector" @onSelect="onSelect" deriction="x | y">
    <CheckItem :renderItem="renderColorItem"/>.</CheckGroup>

onSelect:function(ids){
    // List of project ids selected by the user
    console.log(ids)
}
Copy the code

CheckGroup and CheckItem are used similarly to iView or Element forms. Yes, I borrowed the Form implementation. I put FromItem event handling (dependent collection, validation, etc.) on the top layer of the Form, injected data into the FormItem by providing, passed Form element events to the FormItem through eventBus, and finally reached From. In the Form layer to implement various functions, so as to achieve the effect of abstract logic, each component does its own job.

Architecture design

implementation

The CheckGroup implementation is simple and does the following things.

  • 1 collect all items (my project is only collected to the parent container, not used, for future advanced functions).
  • 2 Inject data into the top layer of child components by providing.
  • 3 Handle events via eventBus, receiving child element clicks and other event data

First of all, in the Template layer, we usually put multiple choices into the scrollView (based on the battery-scroll), so we need to render different styles in different directions so that the outer scrollView can smoothly roll the battery-scroll document portal

<template>
  <div class="multiCheck-container" :style="direction=='x' ? xStyle : yStyle">
    <slot name="default"></slot>
  </div>
</template>
Copy the code
export default {
  props: {
    multiCheck: {
      type: Boolean.required: false.default: true
    }
  },
  data () {
     return {
      itemsGroup: [].userSelect: {
        id: [].item: []},// Item selected by the user
      xStyle: {
        // So that the inside of the container can be stretched horizontally
        display: 'inline-block'.width: 'auto'.whiteSpace: 'nowrap'
      },
      yStyle: {
        display: 'flex'.'flex-direction': 'row'.'flex-wrap': 'wrap'.width: '100%'}}},// Inject data into child components
  provide () {
    return {
      choseData: this.userSelect,
      chooseForm: this}},created: function () {
    // Initializes the add item
    this.$on('on-add-item', item => {
      this.itemsGroup.push(item)
    })
  },
  mounted: function () {
    this.$on('on-click-item', item => {
      if (this.multiCheck) {
        / / multi-select
        const hasChecked = this.userSelect.id.some(v= > v === item.id)
        if (hasChecked) {
          // Double click to delete the item
          let index = this.userSelect.id.findIndex(v= > v === item.id)
          this.userSelect.id.splice(index, 1)
          this.userSelect.item.splice(index, 1)}else {
          // Add item with one click
          this.userSelect.id.push(item.id)
          this.userSelect.item.push(item)
        }
      } else {
        / / radio
        this.userSelect.id = item.id
        this.userSelect.item = item
      }
      // Notify externally selected projects via emit
      this.$emit('onSelect'.this.userSelect)
    })
  }
}
Copy the code

Implement CheckItem

The things we need to do in CheckItem are

  • 1 Add child elements to parent dependencies via eventBus
  • 2 Notify the parent component that the child element is clicked, and pass the item information to the parent checkGroup component

checkItem.js

<template>
  <div class="multicheckItem" :style="{display:FormParent.multiCheck? 'inline-block':'block'}">
    <Render :render="renderItem" :bindData="{onItemClick,choseData,FormParent}" />
  </div>
</template>
Copy the code
import Render from '.. /render'
export default {
  components: { Render },
  props: {
    renderItem: {
      type: Function.required: true}},inject: {
    FormParent: {
      from: 'chooseForm',},choseData: {
      from: 'choseData',}},// Notify the parent component to collect child element instances
  created: function () {
    this.FormParent.$emit('on-add-item'.this)},methods: {
    onItemClick: function (item) {
      this.FormParent.$emit('on-click-item', item)
    }
  }
}
Copy the code

To make it easier for us to scale, we hand over the rendering of the child element to a functional component, so that when we call the checkout component, we just pass in a method called renderItem and return any element. He will carry onItemClick choseData, FormParent these three parameters in the past, we only need to apply colours to a drawing of the item in the renderItem method component processing click event as well as the style of the selected will be ok

Render.js

export default {
  name: 'Render'.functional: true.props: {
    render: [Function.String].bindData: [Object]},render: function (h, ctx) {
    return ctx.props.render(h, ctx.props.bindData)
  }
}

Copy the code

Take a chestnut


<CheckGroup class="selector" @onSelect="onSelect">
    <CheckItem
    v-for="(item,index) in ColorList"
    :renderItem="(h,bindData)=>renderItem(h,bindData,item,index)"
    />
</CheckGroup>. Omit some codeconst ColorList =
[
    {
        id:1.color:'# 222'.name:'Light black'
    },
    {
         id:1.color:'#F00'.name:'red'
    },
    {
         id:1.color:'#0F0'.name:'green'
    }
]
renderItem: function (h, bindData, item,index) {
  return <ColorItem bindData={bindData} item={item} />
},
Copy the code

So in our ColorItem component:

ColorItem.js

<template>
  <div class="brand_item" @click="onBrandClick">
    <div
      class="itemimg"// Add a background color to match the id, indicating that :style="{background:bindData.choseData.id.some(v=>item.id == v) ? '# 555' : ' '}"
    ></div>
    <p>{{item.name}}</p>
  </div>
</template>

<script>
export default {
  props: {
    item: {
      type: Object,
      required: true
    },
    bindData: {
      type: Object
    }
  },
  mounted: function () {},
  methods: {
    onBrandClick: function () {
      this.bindData.onItemClick(this.item)
    }
  }
}
</script>
Copy the code