This article is published by the Cloud + community

1. The demand

Recent projects, the need to implement dynamic rendering with prompt box in the frame of the vue radio/alternative text box, the effect of the concrete as shown in the figure below, focusing in the input box, the front-end components by receiving kv parameters of rendering options, users click on options, can choose the option key assembled in the input box, at the same time allows the user to input freely.

Because of the element-UI used in the project, the input and SELECT components of the component were preferred, but in practice it was found that the components provided by the framework did not meet this requirement very well. For example, using an input component with input suggestions makes it possible to implement prompt boxes and radio selections, but not easy to implement multiple selections (repeated selections overwrite the contents of the input box).

The remote search function of the select selector provided by the framework can realize the prompt box, and also can easily realize single and multiple choices, but the content of the SELECT component can only be selected by the user (the text box content must be included in the prompt options), and does not allow the user to freely input text content.

In addition, the design draft needs to achieve a three-column layout, and the final result needs to dynamically assemble the option key value. If it is too expensive to transform the existing Element component, we try to package the radio/multi-choice text box component with a prompt box, and record the problems encountered in component interaction during the packaging process.

2. Design interface parameters

The component supports passing in six parameters, respectively

  1. Size (size, String, medium/small/mini)
  2. Value (Input value, String, bidirectional binding with sync modifier)
  3. {key:1, value: XXX}}
  4. Seperator, separator, the String, such as the ‘, ‘, ‘|’, ‘-‘)
  5. Multiple (whether multiple selection is supported, Boolean)
  6. Placeholder (hint, String)

Call as follows:

<cs-select
  size="mini" / / size
  :value.sync="value" // value
  :opt="optParams.kv" / / options
  seperator="," / / delimiter
  :multiple="true">
</cs-select>
Copy the code

3. The hidden interaction implementation is displayed in the dialog box

To refine the above requirements, you need to display a prompt when the user clicks on an input box (to get focus), hide the prompt when the user clicks on a blank area, and do nothing when the component itself is clicked. The template structure of the component is as follows. The show variable controls the display and hiding of the prompt box, and binds the focus and out-of-focus events to the input box of the component: @focus=”onfocus” and @blur=”onblur”. Set this. Show to true for focus and false for blur. The key to all this is how to click the prompt option without hiding the prompt box.

<template> <div> <! - input box - > < el - input @ focus = "an onfocus @ the blur =" onblur > < / el - input > <! - prompt box - > < div v - if = "show && opt. Length > 0" > < el - row > < el - col: span = "8" v - for = "(item, index) in opt" :key="index"> {{item.value}} </el-col> </el-row> </div> </div> </template>Copy the code

3.1 Try scheme 1: Active focus of click event

Based on the above requirements, it is no surprise that you can bind the click event to the option and call the el-Input focus() method for active focus, as follows: here we use vue ref to find dom elements by $ref.

clickEvent () {
  this.show = true // Set the prompt box display
  this.$refs.input.$el.querySelector('input').focus() // Set active focus
}
Copy the code

Problem: In the actual development process, it was found that the prompt box would blink once every time after clicking the prompt option, because of the js event mechanism. The blur event was executed before the click event, which led to the hidden and then displayed prompt box, causing the blink.

3.2 Try scheme 2: Blur event add delay timer + switch variable

Since the blur event is executed before the click event in scheme 1, the setTimeout delay is considered to change the execution time as follows.

blurEvent () {
  setTimeout((a)= > {
    this.show = false
  }, 200)}Copy the code

** Problem: In the actual development process, it was found that the delater delayed the closing operation, which led to the active closing of the prompt box after the input box acquired the focus and no longer opened automatically, which did not meet the requirements. Therefore, it was considered to use the switch variable canClose to determine whether the current closing operation was needed. The implementation is as follows.

focusEvent () {
  this.show = true
  this.canClose = true // Turn on the switch when focusing
},
blurEvent () {
  if (this.canClose) {
    setTimeout((a)= > {
      this.show = false // Execute shutdown only when the switch is on
    }, 200)
  }
},
clickEvent (key) {
  this.canClose = false // Click the prompt option to turn off the switch
  this.show = true. }Copy the code

Problem: the actual development process, found that in most cases, the prompt dialog box to display and hide, but when the action faster, occasionally prompted box cannot be closed or shut down in advance, analysis the reason is that any delay during may cause components to the operation of the switch switch state changes, the state of disorder.

3.3 Try scheme 3: Do not use blur, switch off the method to event delegate, and dynamically bind the class

If the closure is triggered by click events instead of blur, the above timing problems will not exist. Therefore, it is considered to use event delegate globally to monitor the user’s click events and close the prompt box by judging the node’s special class, as follows.

$('body').on('click', (event) => {
  this.show = false$(})'body').on('click', className, (event) => {
  this.show = true
})
Copy the code

Problem 1: Event delegate, using a fixed class, when rendering multiple components at the same time, there is no way to implement a separate management prompt box switch, so there is no way to render multiple components, so classes use dynamic binding, each component uses a different class, as follows.

** Problem 2: ** prevents bubbling. If the component’s parent prevents bubbling, the closure method bound on the body cannot be triggered and needs to be handled separately for the parent.

let randId = Math.round(Math.random()*100000)
this.className = `cs-select-${randId}`
// Handle the parent separately, binding the close event to the parent.Copy the code

After the transformation, the components appear to be basically usable, but there are many problems in practice:

** Problem 1: ** components bind events to parent components, which violates Demeter’s law of design pattern and increases coupling between components, which is not conducive to later maintenance.

** Problem 2: ** The above operation only considers the closing of the click event, ignoring other possible closing situations, such as using the TAB key to switch the input box also need to be able to display the hidden prompt box.

** Problem 3: Too many ** binding events can cause performance problems and even unexpected problems.

3.4 Scheme 4: OnFocus + onblur + mouseDown + Switch

Since the focus event is executed before the click event, the above problems in scheme 1 and 2 are caused. According to the data, the Mousedown event is executed before the focus event. Therefore, The onfocus + onblur + mouseDown + switch can be used to solve the above execution timing problem, as follows.

focusEvent () {
  this.show = true
  this.canClose = true // Turn on the switch when focusing
},
blurEvent () {
  if (this.canClose) {
    this.show = false // Execute shutdown only when the switch is on
  }
},
mousedownEvent (key) {
  this.canClose = false // Click the prompt option to turn off the switch
  this.show = true
  this.$refs.input.$el.querySelector('input').focus()
  ...
}
Copy the code

This.$refs. XXX dom element of the current object cannot be directly retrieved from mousedownEvent because the component is dynamically rendered.

3.5 Implementation Scheme

On the basis of Scheme 4, the nextTick asynchronous update queue can solve the dom rendering timing problem, and the specific implementation can be slightly modified for Scheme 4.

$nextTick: In vUE’s official in-depth responsive principle, it is explained that vUE implements responsive not immediately after data changes in the DOM, but after the next DOM update cycle ends, the delayed callback is used after data changesnextTick(() => { this.el.querySelector(‘input’).focus() }) … }4. Component data bidirectional binding

In order to facilitate the processing of the data in the component, the input value of the component will be split into key array first, then add watcher observer, listen to the change of the input value, update the check status of the prompt box, and synchronize to the parent component through $emit method, realize the two-way binding of the data. Enter the value of watch as follows:

watch: {
  inputVal: {
    handler () {
      let selectArray = this.inputFilter()
      this.inputVal = selectArray.join(this.seperator)
      // Update the selected status
      this.updateActive()
      // Synchronize data
      this.$emit('update:value'.this.inputVal) // Can be changed to V-model
    },
    immediate: true}}Copy the code

5. Component application and improvement

There are many application scenarios of single or multiple text box components with prompt boxes. Typical scenarios include a selector that encapsulates enterprise contacts. When users enter user name keywords, the prompt box displays relevant contacts and allows users to enter user names freely.

There are a number of improvements that can be made to the component, such as:

  1. The current design listens to mousedown to prevent the prompt box from closing, which is obviously not compatible with mobile terminals. You can consider adding touch events.
  2. The CSS layout is not judged to be user-friendly, and in extreme cases may be off-screen;
  3. Slot slots and dynamic class Settings are not supported.

With the overall project iterations can be gradually improved.

This article has been published by Tencent Cloud + community authorized by the author