Back to Blog

Building a Menu Using Popovers and List Groups

Exploring how to build layered components utilizing MDS elements

March 14, 2021—The Morningstar Design system built with Vue provides a library of elements that can be combined to create more complex components for reuse within an application. Here we will explore building a Vue component that combines multiple MDS elements. This tutorial assumes a basic understanding of Vue.

For guidance on how to get started with MDS Vue elements, see Getting Started for Engineers.

Overview of our Approach

We will combine the popover, and list group elements into a Vue component that accepts menu items as a prop and emits an event when clicked.

The component can be triggered by an element such as a button. When the trigger is clicked, a popover is displayed with a list of menu items organized using the list group element. When an item in the list is clicked an event is passed back to the container element for further action.

The combination of these MDS elements will produce a component that looks like this when attached to a button:

Final composition of Popover Menu component.

Starting with the popover

Let’s start by incorporating the popover element. In our Vue component we will import @mds/popover. For simplicity we will set the position as right-bottom. We will also add a prop to set the trigger for the popover.

<template>
    <div>
        <mds-popover :triggered-by="trigger" :position="['right-bottom']">
        Lorem ipsum...
        </mds-popover>
    </div>
</template>

<script>
    import { MdsPopover } from '@mds/popover';

    export default {
        name: 'PopoverMenu',
        components: {
            MdsPopover,
        },
        props: {
            trigger: {
                type: String,
                required: true,
            },
        },
    };
</script>



Next let’s add a data attribute for controlling the visibility of the popover. We will also add a method for toggling this attribute.

<template>
    <div>
        <mds-popover v-model="visible" :triggered-by="trigger" :position="['right-bottom']">
        Lorem ipsum...
        </mds-popover>
    </div>
</template>

<script>
    import { MdsPopover } from '@mds/popover';

    export default {
        name: 'PopoverMenu',
        components: {
            MdsPopover,
        },
        props: {
            trigger: {
                type: String,
                required: true,
            },
        },
        data() {
            return {
                visible: false;
            };         
        },
        methods: {
            menuToggle() {
                this.visible = !this.visible;
            },
        },
    };
</script>



We can see if our component is working by placing an element such as a button in our app template to be used as a trigger. Give it an id and use that as the trigger for the new component.

<template>
    <div>
        <mds-button type="button" id="menuTrigger">Menu</mds-button>
        <popover-menu trigger="menuTrigger"></popover-menu>
    </div>
</template>



To encapsulate as much functionality as possible within our component we will include the event handlers there to control the visibility toggle. Add a click event listener to the trigger when the component is mounted and remove it when the component is destroyed.

<script>
    ...
        mounted() {
            if (this.trigger) {
                document.getElementById(this.trigger).addEventListener('click', this.menuToggle);
            }
        },
        beforeDestroy() {
            if (this.trigger) {
                document.getElementById(this.trigger).removeEventListener('click', this.menuToggle);
            }
        },
    };
</script>



Now the popover should appear when clicking the trigger.

Adding the List Group

Now that we have the popover that links to a trigger, we can add a list group as the content for the popover.

<template>
    <div>
        <mds-popover v-model="visible" :triggered-by="trigger" :position="['right-bottom']">
            <mds-list-group>
                <mds-list-group-item text="Menu Item 1"></mds-list-group-item>
                <mds-list-group-item text="Menu Item 2"></mds-list-group-item>
                <mds-list-group-item text="Menu Item 3"></mds-list-group-item>
                ...
            </mds-list-group>
        </mds-popover>
    </div>
</template>



Now let’s configure the component to accept the menu items as a prop and handle when a menu item is clicked.

Using Data to Define List Group Items

We will use an array of objects for our menu items list. Each entry will contain the menu text along with an icon.

const menuItems = [
    {
        text: 'Sit cappuccino',
        icon: 'pencil',
    },
    {
        text: 'uis aute irure',
        icon: 'heart',
    },
    {
        text: 'Excepteur sint occaecat',
        icon: 'download',
    },
    {
        text: 'Nemo enim ipsam ',
        icon: 'question-circle',
    }
];



Add items to the props for our component:

props: {
    ...
    items: {
        type: Array,
        required: true,
    },
}



Now we can replace the static list in our component and iterate over the items being passed as a prop list to produce the list items in the list group.

<mds-list-group with-icon>
    <mds-list-group-item
        v-for="(item, index) in items"
        :text="item.text"
        :icon-left="item.icon"
        :key="index"
    ></mds-list-group-item>
</mds-list-group>



Pass the items list to our component:

<popover-menu trigger="menuTrigger" :items="menuItems"></popover-menu>



Now that we have a component that displays a menu in a popover when a trigger element is clicked, we need to capture which menu item is selected. The MDS List Item element has a custom click event - mds-list-item-clicked - that we can use to emit our own custom event that passes the value of the menu item selected.

First we add the event listener to our list items:

<mds-list-group with-icon>
    <mds-list-group-item
        v-for="(item, index) in items"
        :text="item.text"
        :icon-left="item.icon"
        :key="index"
        @mds-list-item-clicked="menuItemClicked(item)"
    ></mds-list-group-item>
</mds-list-group>



Now we add a method to emit our custom event along with the trigger id and menu item selected

methods: {
    ...
    menuItemClicked(item) {
        this.$emit('popover-menu-item-clicked', { trigger: this.trigger, item });
        this.menuToggle()
    },
},



And our implementation of this component can now respond to our custom event:

<popover-menu
    trigger="menuTrigger"
    :items="menuItems"
    @popover-menu-item-clicked="handlePopoverMenuClick"
></popover-menu>
methods: {
    handlePopoverMenuClick(item) {
        console.log(item);
    },
},

Wrapping Up

MDS elements can be combined in many ways to created new components for use in Morningstar applications. Be sure to use the custom events emitted from the MDS elements to customize your own events.

You can view the completed code for this component here: CodePen demo