Marcus Turnbo
Senior Software Engineer
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.
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:
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.
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.
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);
},
},
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