In the Vue app that I'm currently working on, I ran into a case where I had to use two tools that are, shall we say, not my strongest: render functions and slots. Fortunately, I was able to figure out how to do what I needed to do, and I'm here to share about it.
My use case is that I need to display an icon with a badge (yes, cue the classic movie line about how we don't need no stinking badges) that contains a number. If I could just do it with the default HTML tags, it wold be easy. Here is the HTML from the Vuetify docs:
<v-badge left>
<template v-slot:badge>
<span>6</span>
</template>
<v-icon
large
color="grey lighten-1"
>
shopping_cart
</v-icon>
</v-badge>
However, in this app, the bottom half of every page is a data table that uses a Vuetify v-data-table component to list data, and each column uses a functional component to render the content.
<script>
export default {
name: 'BaseTableColumn',
props: {
format: Function,
item: Object,
index: String,
},
methods: {
getText() {
return this.item[this.index];
},
},
render(createElement) {
if (this.$props.format) {
return this.$props.format(this.item, this.index, createElement);
}
return createElement('div', this.getText());
},
};
</script>
So in this case, where I need to generate the markup is in this.$props.format,
where format
is an object key.. As you can see from the arguments passed above, the signature is
format: (item, index, createElement) => {
// rendering code goes here.
});
As you can see at my initial Stackoverflow post, this got me pretty close:
format: (item, index, createElement) => {
const propsObj = {
attrs: {
color: 'blue',
},
props: {
overlap: true,
left: true,
},
slots: {
badge: 'dummy',
},
};
return createElement('v-badge', propsObj, [createElement('v-icon', { attrs: { color: 'success', large: true } }, 'account_circle')]);
}
but I still wasn't getting the badge or badge content, even though I could see in my markup that the outer <span>
for the badge was there around the icon elements. Thanks to my buddy - and fellow Views on Vue panelist - Austin Gil (aka Stegosource), I was able to get it working. Here's the final working code:
format: (item, index, createElement) => {
const propsObj = {
props: {
overlap: true,
left: true,
color: 'success',
},
};
const span = createElement('span', { slot: 'badge' }, '5');
const icon = createElement('v-icon', { props: { color: 'warning', large: true } }, 'account_circle');
return createElement('v-badge', propsObj, [span, icon]);
}
What we did was break the render functions in parts to match the desired markup. Looking at the markup, there are three parts:
- The outer
v-badge
- The inner
badge
slot - The inner
v-icon
element
So first, we use createElement
to generate the badge slot:
const span = createElement('span', { slot: 'badge' }, '5');
Next, we generate the markup for the icon:
const icon = createElement('v-icon', { props: { color: 'warning', large: true } }, 'account_circle');
And finally we put it together, passing the span
and icon
values created above, also passing in propsObj
with attributes for the v-badge
element.
return createElement('v-badge', propsObj, [span, icon]);
Here is the end result:
From a coding standpoint, now I just need to control the value that is displayed in the badge, but that's trivial (and outside the scope of this post).
Thanks for reading, and happy coding.