preface

The previous TypeAhead was written too early, which did not meet the current business requirements

And some flaws, there are also inconvenient incoming data and response data..

So I went back to the drawing board and wrote a version of V2

Look at the picture, more details to consider; Simplified implementation logic code

rendering

Implemented functions

  1. Click the area outside the drop-down box to close the drop-down box
  2. You can select up and down keys on the keyboard and mouse
  3. Support list filtering search
  4. Support external incoming list mapping in JSON format
  5. Support for incoming to placeholder
  6. The response of the selected object (.syncSyntax sugar for component communication in VUe2.3)
  7. Arrow icon mapping, feeling not very useful, removed

What can you learn

Different people have different opinions

usage


<select-search 
style="max-width:195px" 
placeholder="Please select the advertiser" 
:asyncData.sync="adHostData" 
:mapData="adHostDataList" 
:mapDataFormat="{label:'userName',value:'userId'}">
</select-search>

Copy the code
  • asyncData: Data for the response, that is, the selected.. Back is an object
  • mapData: Search list data, must have been passed in from outside…
  • mapDataFormat: List value mapping

code

  • selectSearch.vue

  <template>
  <div class="select-search" v-if="typeaheadData" ref="selectSearch" @click.native="showHideMenu($event)">
    <div class="select-header">
      <input type="text" autocomplete="off" readonly :placeholder="placeholder" :value="placeholderValue" @keydown.down.prevent="selectChildWidthArrowDown" @keydown.up.prevent="selectChildWidthArrowUp" @keydown.enter="selectChildWidthEnter">
      <i class="fzicon " :class="isExpand? 'fz-ad-jiantou1':'fz-ad-jiantou'"></i>
    </div>
    <div class="select-body" v-if="isExpand && typeaheadData">
      <input type="text" placeholder="Keywords" v-model="searchVal" autocomplete="off" @keydown.esc="resetDefaultStatus" @keydown.down.prevent="selectChildWidthArrowDown" @keydown.up.prevent="selectChildWidthArrowUp" @keydown.enter="selectChildWidthEnter">
      <transition name="el-fade-in-linear" mode="out-in">
        <div class="typeahead-filter">
          <transition-group tag="ul" name="el-fade-in-linear" v-show="typeaheadData.length>0">
            <li v-for="(item,index) in typeaheadData" :key="index" :class="item.active ? 'active':''" @mouseenter="setActiveClass(index)" @mouseleave="setActiveClass(index)" @click="selectChild(index)">
              <a href="javascript:;">
                {{item[mapDataFormat.label]}}
              </a>
            </li>
          </transition-group>
          <p class="noFound" v-show="typeaheadData && typeaheadData.length === 0">Could not find, please re-enter!</p>
        </div>
      </transition>
    </div>
  </div>
</template>

<script>
  export default {
    name: 'selectSearch'.data: function () {
      return {
        placeholderValue: ' '.// To see the selection
        isExpand: false.searchVal: ' '.// Search for the keyword
        resultVal: ' '.// Save the searched value
        searchList: [], // Save the filtered result set
        currentIndex: - 1.// Index is currently selected by default,}},computed: {
      mapFormatData () { // Map mapData when an external format is passed in
        return this.mapData.map(item= > {
          item[this.mapDataFormat.value] = item[this.mapDataFormat.value];
          return item;
        });
      },
      typeaheadData () {
        let temp = [];
        if (this.searchVal && this.searchVal === ' ') {
          return this.mapFormatData;
        } else {
          this.currentIndex = - 1;  // Reset the index in a special case
          this.mapFormatData.map(item= > {
            if (item[this.mapDataFormat.label].indexOf(this.searchVal.toLowerCase().trim()) ! = =- 1) {
              temp.push(item)
            }
            return item;
          })
          returntemp; }}},props: {
      placeholder: {
        type: String.default: '-- please select --'
      },
      emptyText: {
        type: String.default: 'No data at present'
      },
      mapData: { // External incoming list data
        type: Array.default: function () {
          return[]}},mapDataFormat: { // Map the format of the incoming data
        type: Object.default: function () {
          return {
            label: 'text'.value: 'value'.extraText: 'extraText'}}},asyncData: { // The value of real-time response
        type: [Object.String].default: function () {
          return{}}}},methods: {
      showHideMenu (e) { // Click on other areas to close the drop-down list
        if (e) {
          if (this.$refs.selectSearch && this.$refs.selectSearch.contains(e.target)) {
            this.isExpand = true;
          } else {
            this.isExpand = false;
          }
        }

      },
      resetDefaultStatus () { // Clear all selected states
        this.searchVal = ' ';
        this.currentIndex = - 1;
        this.typeaheadData.map(item= > {
          this. $delete(item, 'active');
        })
      },
      setActiveClass (index) { // Set the style activity class
        this.typeaheadData.map((item, innerIndex) = > {
          if (index === innerIndex) {
            this.$set(item, 'active'.true);
            this.currentIndex = index;  // This sentence is used to correct index, which is the index of the up and down keys on the keyboard, otherwise it will jump bit
          } else {
            this.$set(item, 'active'.false)
          }
        })
      },
      selectChildWidthArrowDown () {
        // determine index to select the child item
        if (this.currentIndex < this.typeaheadData.length) {
          this.currentIndex++;
          this.typeaheadData.map((item, index) = > {
            this.currentIndex === index ? this.$set(item, 'active'.true) : this.$set(item, 'active'.false);
          })
        }
      },
      selectChildWidthArrowUp () {
        // determine index to select the child item
        if (this.currentIndex > 0) {
          this.currentIndex--;
          this.typeaheadData.map((item, index) = > {
            this.currentIndex === index ? this.$set(item, 'active'.true) : this.$set(item, 'active'.false);
          })
        }
      },
      selectChildWidthEnter () {
        // If there is only one result set, it is selected by default
        if (this.typeaheadData.length === 1) {
          this.$emit('update:asyncData'.this.typeaheadData[0]); // emit the value of the response
          this.placeholderValue = this.typeaheadData[0] [this.mapDataFormat.label];

        } else {
          // If the search content matches exactly the content in the item, it is selected by default
          this.typeaheadData.map(item= > {
            if (this.searchVal === item[this.mapDataFormat.label] || item.active === true) {
              this.$emit('update:asyncData', item); // emit the value of the response
              this.placeholderValue = item[this.mapDataFormat.label]; }})}this.isExpand = false;
      },
      selectChild (index) {
        // Mouse click to select a child
        this.typeaheadData.map((item, innerIndex) = > {
          if (index === innerIndex || item.active) {
            this.placeholderValue = item[this.mapDataFormat.label];
            this.$emit('update:asyncData', item); // emit the value of the response}});this.isExpand = false;
      },
    },
    mounted () {
      window.addEventListener('click'.this.showHideMenu);
    },
    beforeDestroy () {
      window.removeEventListener('click'.this.showHideMenu);
    },
    watch: {
      'isExpand' (newValue) {
        if (newValue === false) {
          this.resetDefaultStatus(); }}}}</script>

<style scoped lang="scss">.el-fade-in-linear-enter-active, .el-fade-in-linear-leave-active, .fade-in-linear-enter-active, .fade-in-linear-leave-active { transition: opacity .2s linear; } .el-fade-in-enter, .el-fade-in-leave-active, .el-fade-in-linear-enter, .el-fade-in-linear-leave, .el-fade-in-linear-leave-active, .fade-in-linear-enter, .fade-in-linear-leave, .fade-in-linear-leave-active { opacity: 0; } .noFound { text-align: center; } .select-search { position: relative; z-index: 1000; a { color: #333; text-decoration: none; padding: 5px; } ul { list-style: none; padding: 6px 0; margin: 0; max-height: 200px; overflow-x: hidden; overflow-y: auto; li { display: block; width: 100%; padding: 5px; font-size: 14px; padding: 8px 10px; position: relative; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #48576a; height: 36px; The line - height: 1.5; box-sizing: border-box; cursor: pointer; &.active { background-color: #20a0ff; a { color: #fff; } } } } .select-header { position: relative; border-radius: 4px; border: 1px solid #bfcbd9; outline: 0; padding: 0 8px; >input { border: none; -webkit-appearance: none; -moz-appearance: none; appearance: none; width: 100%; outline: 0; box-sizing: border-box; color: #1f2d3d; font-size: inherit; height: 36px; line-height: 1; } >i { transition: all .3s linear; display: inline-block; position: absolute; right: 3%; top: 50%; transform: translateY(-50%); } } .select-body { position: absolute; border-radius: 2px; background-color: #fff; box-sizing: border-box; margin: 5px 0; padding: 8px; width: 100%; box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04); >input { -webkit-appearance: none; -moz-appearance: none; appearance: none; background-color: #fff; background-image: none; border-radius: 4px; border: 1px solid #bfcbd9; box-sizing: border-box; color: #1f2d3d; font-size: inherit; height: 36px; line-height: 1; outline: 0; padding: 3px 10px; transition: border-color .2s cubic-bezier(.645, .045, .355, 1); width: 100%; display: inline-block; &:focus { outline: 0; border-color: #20a0ff; }}}}</style>


Copy the code

conclusion

If there is some help for some small partners, it is the greatest value of this article;

There is a better way to do this, you can leave a comment below, thank you