vdda

A seriously advanced Vue Dropdown tool with many custom options.

Usage no npm install needed!

<script type="module">
  import vdda from 'https://cdn.skypack.dev/vdda';
</script>

README

An advanced Vue Dropdown Component

An advanced pure Vue Dropdown control with many customisable options.

Key features

  • caters for different types of dropdown items (action, checkbox, radio, header, seperator)
  • lazily calls back for dropdown items (synchronously or asynchronously!)
  • images are supported on left and on the right (multiple)
  • dropdown items are removed from the markup when dropdown is closed
  • dropdown location is configurable (left/right/up/down)
  • the dropdown can be created through code using any html element as source
  • and much more!

Demo

Check it out at demo-page!

Typescript

The code snippets below are extracts from the demo project. An index.d.ts file providing intellisense will be included soon.

Setup

import Vue from 'vue';
import { DropDownMenu, DropDownInfo, DropDownItemBase, HeaderItem, ActionItem, CheckboxItem, RadioboxItem, SeperatorItem } from "vue-dropdown-advanced";

import 'vue-dropdown-advanced/dist/vue-dropdown-advanced.css';

import '@mdi/font/css/materialdesignicons.css';

include the vue-dropdown-advanced.css

Make sure you include the vue-dropdown-advanced.css file in your main imports.

import 'vue-dropdown-advanced/dist/vue-dropdown-advanced.css

include the materialdesignicons.css

If you wish to use images then also include the scoped @mdi/font/css/materialdesignicons.css (you must have '@mdi/font' as a dependency in your project)

import '@mdi/font/css/materialdesignicons.css'

Using the Vue DropDown

A basic 'fixed' dropdown items

down-right.png

We 'attach' a DropDownMenu menu to the 'Example Down Right' div simply by nesting it within its parent element as shown in the Vue template below. This is imporant since a 'click' handler is attached to this parent so the dropdown can be toggled when clicked.

There is also a programmatic way of attaching this dropdown to any DOM element - more on this later.

/// The 'template' part of the Vue file
<div class='button example-dr'>
    Example Down Right
    <drop-down-menu :items="fixedItems" @click="this.onClick">  </drop-down-menu>
</div>

Notice we have bound the 'items' property to the 'fixedItems' data property of the vue model. When the parent is 'clicked' the onClick handler is called with the item itself but also any other dropdown state.

/// The 'javascript' part of the Vue file		
export default Vue.extend({
  name: "app",
  data: () => {
    const fixedItems: DropDownItemBase[] = [];
    return {
      fixedItems
    }
  },
  methods: {
      onClick(info: DropDownInfo) {
        let msg = `Item '${info.item.text}' was clicked. [key: ${info.item.key}] `;
        console.log(msg);
      },
  },
  components: {
    DropDownMenu
  },
  created() {
    this.fixedItems.push(new ActionItem("A", "Holiday in France", "", false, _ => alert(_.key)));
    this.fixedItems.push(new ActionItem("B", "Go to California"));
    this.fixedItems.push(new ActionItem("C", "Visit London"));
  }
});

Bit boring, let's see what else we can do.

Show-casing the dropddown advanced

down-right.png

That is more like it!

Again, we 'attach' a DropDownMenu menu to the 'Example Down Right' div by nesting it within its div as shown in the Vue template below.

/// The 'template' part of the Vue file
<div class='button example-dr'>
    Showcase
    <drop-down-menu :items="fixedItems" @click="this.onClick">  </drop-down-menu>
</div>

Again, we are binding the 'items' property to the 'fixedItems' data property of the vue model. When the parent is 'clicked' the onClick handler is called with the item itself but also any other dropdown state.

/// The 'javascript' part of the Vue file		
var item = new ActionItem("booknow", "Book now!", "mdi-airplane-takeoff");
item.data = { pos: context };                            // save some random data with this item..
item.addRightImage("mdi-cogs", "settings");
item.addRightImage("mdi-exit-to-app", "exit the application");

fixedItems.push(item);
fixedItems.push(new SeperatorItem());
fixedItems.push(new HeaderItem("Choose your destination:"));
fixedItems.push(new RadioboxItem("california", "California and Santa Monica", "A"));
fixedItems.push(new RadioboxItem("newyork", "New York", "A"));
fixedItems.push(new RadioboxItem("miami", "Miami", "A"));
fixedItems.push(new SeperatorItem());
fixedItems.push(new HeaderItem("Mode of transport:"));
fixedItems.push(new RadioboxItem("car", "By car", "B"));
fixedItems.push(new RadioboxItem("boat", "By boat", "B", true));
fixedItems.push(new RadioboxItem("plane", "By plane", "B"));
fixedItems.push(new SeperatorItem());
fixedItems.push(new HeaderItem("Choose your activities:"));
fixedItems.push(new CheckboxItem("beach", "Visit the beach"));
fixedItems.push(new CheckboxItem("town", "Walk through town"));
fixedItems.push(new CheckboxItem("park", "Visit Parks"));
fixedItems.push(new CheckboxItem("hirecar", "Hire a car"));
fixedItems.push(new CheckboxItem("nothing", "Do absolutely nothing !"));

onClick(info: DropDownInfo) {
    // info.item		-> the selected item
    // info.items		-> all the source items that are shown
    // info.imageOnRight	-> (RightImageInfo) details regarding the right image if it was clicked
},

Note the groupBy property available on Radiobox items. Identical groupBy values will force a mutually exclusive group to be created, ie only a single radiobox can be 'checked' within a group. Hence in the example above we have two groups; one with groupBy marker 'A', the other with value 'B'. These identifiers could be any unique string.

Right placed Images

Images placed on the right of the text are selectable and therefore can be 'listened' for.

down-right.png

Use the addRightImage method to add a 'right aligned' image to an ActionItem. The first param is a materialdesignicons identifier, the second is an optional tooltip.

...
var item = new ActionItem("booknow", "Book now!", "mdi-airplane-takeoff");
item.data = { pos: context };                            // save some random data with this item..
item.addRightImage("mdi-cogs", "settings");              
item.addRightImage("mdi-exit-to-app", "exit the application");
...

Dropdown Direction

The dropdown doesn't necessarily has to go 'down' :) It can also go up as well as be left or right aligned. This is controlled through the direction property.
The default is down-right but it can be set to down-left, up-left or up-right - see examples below.

down-right.png

/// The 'template' part of the Vue file (NOTE the 'direction' property)
<div class='button'>
   Example Up Left
   <drop-down-menu :items="myitems_ul" @click="this.onClick" direction="up-left"></drop-down-menu>
</div>

Retrieving dropdown items asynchronously

An asynchronous request for data can be made by binding a function to the itemsAsync prop of the DropDownMenu. This function is expected to return an array of DropDown items. This is extremely useful when dropdown items can only be determined at runtime.

/// The 'template' part of the Vue file
<div class='button'>
   Example Up Left
   <drop-down-menu :itemsAsync="getAsyncItems" @click="this.onClick">  </drop-down-menu>
</div>
  methods: {
      onClick(info: DropDownInfo) {
        ...
      },
      async getAsyncItems() {
        await delay(1000);      // call an api for (dropdown) data (async)
        // .. convert the data to DropDownItems ...
        return this.myitems_dr; // return these items
      },
  },

Max/Min dropdown sizing

A certain minimum or maximum of the dropdown can be enforced by providing a min-width, max-width or max-height property as shown below. Specify the value including the unit ('px' in this case).

/// The 'template' part of the Vue file
<div class='button'>
   Example Up Left
   <drop-down-menu :itemsAsync="getAsyncItems" @click="this.onClick" min-width="240px">  </drop-down-menu>
</div>

How to use this programmatically

In cases where you just want to attach the dropdown to an DOM existing element anywhere on your form without altering your template you can use the DropDownControl class.

  1. Simply create a new DropDownControl object passing in the element you wish to attach the dropdown to. This could be any html element.
  2. Assign the dropdown items to show
  3. Attach an event handler(s)
  4. Set some specific properties (like minWidth or OpenOnCreate)
  5. Once configured call 'createMenu()' which will create and prepare the dropdown control

In its simplest form it would look something like this:

static demo1 = () => {
    
    let el = document.getElementsByClassName("example-programmatically");

    // specify the dropdown items
    let items = [];
    items.push(new ActionItem("logout", "Logout", "mdi-exit-run", false, _ => alert(_.key)));
        items.push(new SeperatorItem());
        items.push(new ActionItem("profile", "Show Profile", "mdi-face"));
        items.push(new ActionItem("shortcuts", "Show Shortcuts", "mdi-access-point"));
        items.push(new ActionItem("setting", "System Settings and a whole lot more and stuff", "mdi-cogs"));

    // 1. create a new DropDownControl for this element
    var dd: DropDownControl = new DropDownControl(el[0]);

    // 2. assign the items to diplay when clicked
    dd.items = items;

    // 3. set a handler when the user selects an item
    dd.onClick = (info: DropDownInfo) => {
      debugger;
      console.log(info.item.key);  // etc...
    }

    // 4. set some more specific properties
    dd.openOnCreate = true;
    dd.minWidth = "300px"
    dd.maxHeight = "150px";
    
    // 5. create the vue dropdown control
    dd.createMenu();
}

History

version Notes
v 0.1.4 security risk package updates
v 0.1.3 css updates
v 0.1.2 introduction of the DropDownControl allowing full programmatic control
v 0.1.1 dropdown now supports min-width, max-width and max-height properties
v 0.1.0 introduced @click event handler in addition to :click callback
v 0.0.11 cleaned package - separated css and materialdesignicons.css
v 0.0.10 exporting more helper objects (like getTestItems, delay, createGuidRight5)
v 0.0.9 small fixes
v 0.0.1..8 Initial release - work in progress