vue-listbox-multiselect

Vue Dual list-box multi-select drop-down component for enterprise applications.

Motivation

There are several good multi-select components available for Vue. However, none are suitable for enterprise app development. In a typical enterprise app, you are often challenged offering a simple drop-down which allows the user to filter through thousands of categorized items from the server, and allows the user to select hundreds. At Banner Edge Media, we have been using a similar component for multiple years. As we migrate to Vue, we wanted to share this component with the Vue community, and together make it even better.

Setup

Install:

# NPM
npm install @banneredge/vue-listbox-multiselect

Usage:

<script lang="ts">
import Vue from 'vue';
import VueListboxMultiselect from '@banneredge/vue-listbox-multiselect';
import dataSet from './usCities';

export default Vue.extend({
  name: 'ServeDev',
  components: {
    VueListboxMultiselect
  },
  data() {
    return {
      selectedList: [] as any[],
    };
  },
  methods: {
    async search(query: string): Promise<any[]> {
      const ids = this.selectedList.map((x) => x.id);
      let subset = dataSet.filter((x) => !ids.includes(`${x.state}-${x.city}`));

      if (!query) {
        subset = subset.slice(0, 100);
      } else {
        const q = query.toLowerCase();
        subset = subset.filter((x) => x.city.toLowerCase().includes(q) || x.state.toLowerCase().includes(q));
        subset = subset.slice(0, 100);
      }

      return subset.map((x: any) => {
        return {
          id: `${x.state}-${x.city}`,
          value: x.city,
          group: x.state
        }
      });
    },
  },
});
</script>

<template>
  <v-app id="app">
    <link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet">
    <vue-listbox-multiselect
        v-model="selectedList"
        :search-function="search"
        placeholder="Search Items"
        style="width: 300px; margin: 20px auto"
        size="medium"
        :hide-search="false"
    />
  </v-app>
</template>

Notes:

  • The current version is heavily dependent on Vuetify with mdi icons for the arrows
  • There is no direct way to pass in the items, everything must go through the async search function. The function will get called with a blank query on load.
  • You must implement your own limit, filtering and excluding selected items (See examples). This is set up because we assume most use-cases will be calling the server for data and that will need to be handled on the server.
  • There is lots of room for improvement, so please check out the Roadmap and contribute!
  • Current recommendation is to pull 100 results with each query. Users do not need to scroll through more, they can just search to get what they need.

Props

Prop Description
v-model Model to bind output to. You can set it initially to pre-select items. Note: you will need to exclude selected items from each query. Type: {id: string, value: string, group?: string}. The component will NOT watch for changes on the model and re-fresh the list.
:search-function Function that will get called when the component needs data. It should be async or return a Promise. A string query will be passed in as the only parameter. While the promise is not fulfilled, a loading indicator will show.
placeholder Text to show in the select field when no items are selected. Defaults to 'None'
size How wide to expand the menu. Options are, null, medium, large, x-large. They will fall back on a size that fits. Default size is 366px wide, it will look good in most modern mobile devices.
hide-search Hides the search bar. Search is on by default. If you have a small list and still want to use this component, you have the option to turn it off. You can also turn it off dynamically if items < 10 for example.

Roadmap:

  • Add demo and documentation static site.
  • Clean up the code a bit and add comments.
  • Add interfaces for parameters
    • We should add a few more features before making contracts
  • Speed it up
    • It gets a little slow when you have 1000 items, and you have to group and sort them
    • Keeping the limit to 100 makes it work well.
  • Test browser compatibility.
    • Seems to work well in Chrome, Firefox, Edge
    • We should establish what we support.
  • Remove Vuetify dependency
    • Vuetify provides some very cool pre-made components, but we want this to be usable with any library.
  • Figure out a smart way to do widths.
    • Sometimes if we have a large monitor and long item names, we want to stretch it very wide. If the item names are not long it does not look good.
  • Add a prop to Hide Search (May want to keep it for all use-cases?)
    • Sometimes we want to use this with a very small list and don't need the search box.
  • Add test coverage?
  • Add a way to reset it to original state dynamically
    • Reset selected list
    • Re-run blank query
    • Through Emitter?
  • Add slots to make it fully customizable
  • Add more input interactions
    • Currently Supported:
      • Click (Select)
      • Ctrl-Click (Select Multiple)
      • Double-Click (Move Item)
    • Needed:
      • Shift-Click
      • Ctrl-A ?
      • Move with arrow keys?
      • Shift-arrow keys?

GitHub