How to merge 2 third party components in VueJS How to merge 2 third party components in VueJS vue.js vue.js

How to merge 2 third party components in VueJS


The following solution hacks together the two components to create an editable combobox with tag-pills.

According to the caveat docs of vue-simple-suggest, its custom input components must emit the input, focus and blur events, as well as have a value prop. In addition, there are a few undocumented events that are required from the component: click, keydown, and keyup.

b-form-tags has a value prop, but is missing several of the required events. However, you could access its internal input element to attach your own event handlers that forward-$emit the events:

export default {  async mounted() {    // wait a couple ticks to ensure the inner contents    // of b-form-tags are fully rendered    await this.$nextTick()    await this.$nextTick()    // <b-form-tags ref="tags">    const input = this.$refs.tags.getInput()    const events = [      'focus',      'blur',      'input',      'click',      'keydown',      'keyup'    ]    events.forEach(event =>      input.addEventListener(event, e => this.$refs.tags.$emit(event, e))    )  },}

The changes above alone will cause the vue-simple-suggest to properly appear/disappear when typing. However, it doesn't add/remove tags when interacting with the auto-suggestions. That behavior could be implemented by the following features:

  1. ENTER or TAB keypress causes hovered auto-suggestion to be added as a tag. If nothing hovered, the keypress adds the first auto-suggestion as a tag.
  2. Clicking auto-suggestion adds the auto-suggestion as a tag.
  3. BACKSPACE on an auto-suggestion tag deletes it.

Feature 1 implementation:

  1. Add refs to the vue-simple-suggest and b-form-tags so that we could access the components in JavaScript later:
<vue-simple-suggest ref="suggest">  <b-form-tags ref="tags" /></vue-simple-suggest>
  1. Add a keydown-handler on the inner input of b-form-tags:
export default {  mounted() {    //...    // <b-form-tags ref="tags">    const input = this.$refs.tags.getInput()    input.addEventListener('keydown', e => this.onKeyDown(e))  },}
  1. Implement the handler as follows:
export default {  methods: {    async onKeyDown(e) {      if (e.key === 'Enter' || e.key === 'Tab') {        // prevent default so that the auto-suggestion isn't also        // added as plaintext in b-form-tags        e.preventDefault()        // <vue-simple-suggest ref="suggest">        if (this.$refs.suggest.hovered) {          this.$refs.tags.addTag(this.$refs.suggest.hovered)        } else {          const suggestions = await this.$refs.suggest.getSuggestions(e.target.value)          if (suggestions.length > 0) {            this.$refs.tags.addTag(suggestions[0])          } else {            // no match, so clear chosen            this.chosen = ''          }        }      }    }  }}
  1. To prevent conflict with our handler, disable b-form-tag's automatic tag-adding upon ENTER by adding no-add-on-enter prop:
<b-form-tags no-add-on-enter />

Feature 2 implementation:

  1. Bind a suggestion-click-event handler:
<vue-simple-suggest @suggestion-click="onSuggestionClick">
  1. Implement the handler as follows:
export default {  methods: {    onSuggestionClick(suggestion) {      this.$refs.tags.addTag(suggestion);    },  }}

Feature 3 implementation:

  1. Add the remove-on-delete prop to b-form-tags:
<b-form-tags remove-on-delete />

full demo


As an aside, you might be better off with Vuetify's v-combobox, which supports the combination of the two components you're trying to merge, but I'll leave that to you to explore :)


Merging two component need to modified internal component of bootstrapvue and it take times.

Refer to documentation here https://bootstrap-vue.js.org/docs/components/form-tags.BootstrapVue already supporting searching tags. The available list saved in options:

data() {  return {    options: ['Apple', 'Orange', 'Banana', 'Lime', 'Peach', 'Chocolate', 'Strawberry'],    search: '',    value: []  }},

You can achieve the suggestion by make modification to update this options list.

Add @change event in b-form-input to trigger updateOptionsList() :

<!-- Add onChange event to update The List --><b-form-input      v-model="search"      id="tag-search-input"      type="search"      size="sm"     @change="updateOptionsList()"      autocomplete="off" ></b-form-input>

Also add updateOptionsList() methods :

// Get Data From Server From URLupdateOptionsList() {  console.log("update list");  this.options = ["Jakarta", "London", "Birmingham", "Rome"];  // Use  axios.get('...') then attach the result to this.options  /**  axios.get("your-url-here").then(response => {    // update options    this.options = response.data;  }); **/}

Note: You can use axios ( https://vuejs.org/v2/cookbook/using-axios-to-consume-apis.html) to get real data from your server ada update the options list.

Complete sample code :

<template>  <div>    <b-form-group label="Tagged input using dropdown">      <b-form-tags v-model="value" no-outer-focus class="mb-2">        <template v-slot="{ tags, disabled, addTag, removeTag }">          <ul v-if="tags.length > 0" class="list-inline d-inline-block mb-2">            <li v-for="tag in tags" :key="tag" class="list-inline-item">              <b-form-tag                @remove="removeTag(tag)"                :title="tag"                :disabled="disabled"                variant="info"              >{{ tag }}</b-form-tag>            </li>          </ul>          <b-dropdown size="sm" variant="outline-secondary" block menu-class="w-100">            <template v-slot:button-content>              <b-icon icon="tag-fill"></b-icon>Choose tags            </template>            <b-dropdown-form @submit.stop.prevent="() => {}">              <b-form-group                label-for="tag-search-input"                label="Search tags"                label-cols-md="auto"                class="mb-0"                label-size="sm"                :description="searchDesc"                :disabled="disabled"              >                <!-- Add onChange event to update The List -->                <b-form-input                  v-model="search"                  id="tag-search-input"                  type="search"                  size="sm"                  @change="updateOptionsList()"                  autocomplete="off"                ></b-form-input>              </b-form-group>            </b-dropdown-form>            <b-dropdown-divider></b-dropdown-divider>            <b-dropdown-item-button              v-for="option in availableOptions"              :key="option"              @click="onOptionClick({ option, addTag })"            >{{ option }}</b-dropdown-item-button>            <b-dropdown-text              v-if="availableOptions.length === 0"            >There are no tags available to select</b-dropdown-text>          </b-dropdown>        </template>      </b-form-tags>    </b-form-group>  </div></template><script>import axios from "axios";export default {  data() {    return {      options: [        "Apple",        "Orange",        "Banana",        "Lime",        "Peach",        "Chocolate",        "Strawberry"      ],      search: "",      value: []    };  },  computed: {    criteria() {      // Compute the search criteria      return this.search.trim().toLowerCase();    },    availableOptions() {      const criteria = this.criteria;      // Filter out already selected options      const options = this.options.filter(        opt => this.value.indexOf(opt) === -1      );      if (criteria) {        // Show only options that match criteria        return options.filter(opt => opt.toLowerCase().indexOf(criteria) > -1);      }      // Show all options available      return options;    },    searchDesc() {      if (this.criteria && this.availableOptions.length === 0) {        return "There are no tags matching your search criteria";      }      return "";    }  },  methods: {    onOptionClick({ option, addTag }) {      addTag(option);      this.search = "";    },    // Get Data From Server From URL    updateOptionsList() {      console.log("update list");      this.options = ["Jakarta", "London", "Birmingham", "Rome"];      // Use  axios.get('...') then attach the result to this.options      /**  axios.get("your-url-here").then(response => {        // update options        this.options = response.data;      }); **/    }  }};</script>

I tried it here https://codesandbox.io/s/vue-bootstrap-tags-search-ldsqx and it looks goods.


Apparently, b-form-tags doesn't emit the input event until a tag has been entered. This is not the ideal case for working with vue-simple-suggest, as it would require its input to be changed every time user hits a key. As such, what you can rather do is slot vue-simple-suggest inside of b-form-tags, instead of doing it other way around. You could use something like this:

<template>  <div>    <b-form-tags      size="lg"      tag-variant="success"      tag-pills      remove-on-delete      separator=","      class="my-3"      v-model="chosenTags"    >      <template v-slot="{tags, tagVariant, addTag, removeTag}">        <b-form-tag          v-for="tag in tags"          :key="tag"          :variant="tagVariant"          @remove="removeTag(tag)"          class="mr-1 mb-1"        >{{ tag }}</b-form-tag>        <b-form @submit.prevent="addSelectedWord(chosen)">          <vue-simple-suggest            placeholder="Enter Keyword"            @input="textInput"            :value="chosen"            mode="select"            @select="addSelectedWord"            :list="simpleSuggestionsList"            :filter-by-query="true"            :destyled="false"          ></vue-simple-suggest>          <b-btn v-if="!!chosen" type="submit" class="my-1" outline>Add</b-btn>        </b-form>      </template>    </b-form-tags>  </div></template><script>import VueSimpleSuggest from "vue-simple-suggest";import "vue-simple-suggest/dist/styles.css";export default {  name: "SeedWordsSuggestions",  data() {    return {      chosen: "",      chosenTags: [],      seedWords: []    };  },  components: {    VueSimpleSuggest  },  methods: {    simpleSuggestionsList() {      return ["Angular", "ReactJs", "VueJs"];    },    textInput(text) {      this.chosen = text;    },    addSelectedWord(word) {      console.log(word);      this.chosenTags.push(word);      this.chosen = "";    }  }};</script><style scoped></style>

You can further customize the slot or components by adding custom styling. If you do not like two input fields being displayed, you could try removing border of one by setting border property to none in the CSS.