Entering expressions in input boxes is a common scenario in mid-stage projects. Usually, when entering a specific character (such as $character), a drop-down list is displayed. The user selects variables in the drop-down list, and then continues to enter other operators in the input box to form an expression. The effect is as follows:

In previous projects, React + TypeScrpt implemented variable expression selectors in React. In this article, we will show you how to implement variable expression selectors using Vue.

This variable expression will do the following:

  1. Displays a drop-down list when a specific character is entered in the input box;

  2. Click the drop-down option or press Enter to display the selected content in the input box.

  3. Move the cursor to any position of the entered character and select the content in the drop-down list. The selected content is displayed at the position of the current cursor.

  4. When the drop-down list is displayed but the contents of the drop-down list are not selected, input is prohibited in the input box. Only after the selected contents are allowed to continue to be entered in the input box

  5. Disable the display of input box history;

Defining HTML structure

Since the existing SELECT selector doesn’t do what we want, we need to implement a select selector ourselves. Use the input tag as the input box for our select selector and the UL Li tag as the drop down list for the select selector. The basic HTML structure is as follows:

<div class="expressionContainer">
  <input />
  <div v-if="visible" class="expressionListBox">
    <ul class="expressionUlBox">
      <li></li>
    </ul>
  </div>
</div>
Copy the code

The INPUT tag binds attributes

In Vue, if you want to register reference information for an element or child component, you need to add the ref attribute. References registered with ref will be registered with the refs object of the parent component, through the refs object, through the refs object to find the element or child component that needs to be manipulated. Therefore, we add the ref attribute to the input tag.

<input ref="inputRef" />
Copy the code

In addition to adding the REF attribute to the input tag, you also need to bind events to the input tag. In Vue, events are usually bound using V-ON (abbreviated: @). We bind the input tag with blur, keyDown, and input events.

<input
  ref="inputRef"
  v-model="expressValue"
  class="ant-input"
  :readOnly="readonly ? true : false"
  autoComplete="off"
  :placeholder="placeholder"
  @blur="inputOnBlurHandle"
  @keydown="inputOnKeyDownHandle"
  @change="onchangeHandle"
  @input="onInputHandle"
/>
Copy the code

The input tag listens for keyDown events

Bind a keyDown event to the input tag, listen for the keyboard keys being pressed, and if shift + $is pressed at the same time, display the drop-down list, save the current value of the input field, and make the input tag uneditable, selecting only from the drop-down list. The keyDown binding has the following event handlers:

InputOnKeyDownHandle (e) {shift + $if (e.keycode === 52&&e.shiftKey) {// Get Input Const expressValue = e.target.value; this.setInputValue(expressValue); // Get the current cursor position const position = getPositionForInput(this.$refs.inputref); Const cursorIndex = position.start; // Set the cursor position to the input range property // updateRange(this.$refs.inputref); This.savecursorindex ({start: cursorIndex}); / / set the drop-down select box shows this. SetSelectedBoxVisible (true); This.setinputisreadonly (true); }},Copy the code

The li tag binds properties

The SELECT selector is implemented using ul Li tags. When we select the drop-down option, we need to operate on the Li tag, so we need to add the REF attribute and click, keyDown events to the Li tag, and use HTML5 data-* attribute to store the option value.

<ul ref="ulRef" class="expressionUlBox">
  <li
    v-for="(item, index) in options"
    ref="liRef"
    :key="item.field"
    :data-set="item"
    :data-index="index"
    :data-value="item.field"
    tabindex="0"
    @click="liClickHandle"
    @keydown="liKeyDownHandle"
  >
    {{ item.name }}
  </li>
</ul>
Copy the code

The LI tag gets focus

For ordinary elements such as div/ SPAN/LI, you cannot get focus directly. If you want to trigger the onfocus and onblur events of element nodes such as div/ SPAN /li, add the TabIndex attribute to them. The tabIndex property specifies where the cursor moves when the computer “Tab” key is clicked. The smaller the tabIndex value (minimum 0) is when the computer “Tab” key is clicked, the more focused the Tab is.

In our implementation of the Select selector, we need to use the up and down keys to switch option values, so we need to add a tabIndex attribute to the Li tag so that Li can trigger the onfocus event and onblur event.

<li tabindex="0"></li>
Copy the code

Listen for keyboard events globally

We’re listening for keyboard events globally, and we’re actually binding the event to the document. We listen for keyboard events in the Created lifecycle hook. If the current keys are up and down, you can use the up and down keys to switch the drop-down options.

created() { const _this = this; document.onkeydown = function () { const key = window.event.keyCode; / / upper and lower health care if (key = = = 38 | | key = = = 40) {_this. UpAndDownKeySwitch (key); } / / left, right healthy else if (key = = = 39 | | key = = = 37) {/ / _this leftAndRightKeySwitch (key); }}; },Copy the code

The processing logic of the drop-down options is as follows:

// upAndDownKeySwitch(key) {const liNodes = this.$refs.liRef; const liLength = liNodes.length; If (liNodes && liLength && key === 40) {const count = this.arrowCount. DwArrow === lilength-1? 0 : this.arrowCount.dwArrow + 1; // set the padding, If (liLength > 1) {if (count === 0) {this.$refs.ulref.style. padding = "40px 0 10px 0"; } else { this.$refs.ulRef.style.padding = "10px 0"; } // The current li element gets the focus liLength && liNodes[count].focus(); // Set ul scrollTop, If (count === lilength-1) {this.$refs.ulref.scrollTop = count * 40;  } else { this.$refs.ulRef.scrollTop = count * 10; } this.arrowCount = {upArrow: count, dwArrow: count}; } // Up Arrow key if (liNodes && liLength && key === 38) {const count = this.arrowCount. UpArrow <= 0? liLength - 1 : this.arrowCount.upArrow - 1; // set the padding, If (liLength > 1) {if (count === lilength-1) {this.$refs.ulref.style. padding = "10px 0" 40px 0"; } else { this.$refs.ulRef.style.padding = "10px 0"; LiNodes [count].focus(); $refs.ulref.scrollTop = count * 60; $refs.ulref.scrollTop = count * 60; This. arrowCount = {upArrow: count, dwArrow: count}; }},Copy the code

Sets the value of the input tag

By default the cursor to the entered characters in any position, select a drop-down option, the value of the option will be inserted into the entered characters of the back, and we want to effect is the value of the option is inserted into the current cursor location, so we need to calculate the cursor location, add the value of the option to the right place. Cursor position and text Selection are involved here. You can go to Selection for further understanding.

/** * Insert text * @param CTRL input element object (input, textarea, etc.) * @param inputValue Input box value */ export function insertAtCursor(ctrl, inputValue) { // IE support if (document.selection) { ctrl.focus(); / / document. Selection. CreateRange () / / according to the current text choose return TextRange object, Or choose according to the control returns the ControlRange const sel = document. The selection. The createRange (); // Set sel.text = inputValue for the current TextRange object; } else if (CTRL. SelectionStart | | CTRL. SelectionStart = = = 0) {/ / selectionStart text selected area start/end position/selectionEnd text selected area  // MOZILLA and others const startPos = ctrl.selectionStart; const endPos = ctrl.selectionEnd; // Insert ctrl.value = // Ctrl.value. Substring (0, StartPos) + // The value to be inserted inputValue + // the value after the cursor position Ctrl.value. substring(endPos, Ctrl.value.length); // After inserting the value, the cursor position should be at the end of the string. // The blinking cursor in the page is a special selection of width 0. CTRL. SelectionStart = startPos + inputValue.length; ctrl.selectionEnd = startPos + inputValue.length; } else { ctrl.value += inputValue; } // The input element objects (input, textarea) regain focus, and the cursor position should be at the end of the entered character ctrl.focus(); }Copy the code

Selector complete code and effect