Reputation: 339
I am working on converting an existing theme into reusable components.
I currently have a button component like so:
<template>
<a :href="link" class="button" :class="styling"><slot></slot></a>
</template>
<script>
export default {
props: {
link: {},
styling: {
default: ''
}
}
}
</script>
And, in my HTML, I use it like so:
<vue-button link="#" styling="tiny bg-aqua">Button 1</vue-button>
Now, I am attempting to create a "button group" using the existing button component.
What I would like to be able to do is something like this:
<vue-button-group styling="radius tiny">
<vue-button link="#" styling="tiny bg-aqua">Button 1</vue-button>
<vue-button link="#" styling="tiny bg-aqua">Button 2</vue-button>
<vue-button link="#" styling="tiny bg-aqua">Button 3</vue-button>
<vue-button link="#" styling="tiny bg-aqua">Button 4</vue-button>
</vue-button-group>
I am very new to VueJS, and am a bit confused on the proper way to handle such a thing. I would like to be able to pass as many button components into the group as is needed.
Here is what I have so far for the button group:
<template>
<ul class="button-group" :class="styling">
<li><slot></slot></li>
</ul>
</template>
<script>
export default {
props: {
styling: {
default: ''
}
}
}
</script>
This would, of course, work with a single button being passed in, but I cannot seem to figure out how to allow for more than that, while encasing each button within its own list item.
Any suggestions or corrections to the way I am going about this would be very much appreciated. Thank you.
Upvotes: 3
Views: 2230
Reputation: 5676
A possible solution for that is to use v-for.
<button v-for="button in buttons" :key="button">
Button{{button}}
</button>
Here is the fiddle.
From there you could build your <buttongroup>
-component yourself; passing "meta-infos" into your <buttongroup>
as props (in my fiddle the array in the data
-part).
Slots would make sense, if you want to render something else besides the buttons and you want to inject components for that. Otherwise you would gain nothing with slots.
Upvotes: 0
Reputation: 7661
You can also try to use a named slot
<template>
<ul class="button-group">
<li><slot name="buttons"></slot></li>
</ul>
</template>
And than:
<vue-button-group class="radius tiny">
<template slot="buttons">
<vue-button link="#" styling="tiny bg-aqua">Button 1</vue-button>
<vue-button link="#" styling="tiny bg-aqua">Button 2</vue-button>
<vue-button link="#" styling="tiny bg-aqua">Button 3</vue-button>
<vue-button link="#" styling="tiny bg-aqua">Button 4</vue-button>
</template>
</vue-button-group>
Upvotes: 0
Reputation: 18824
Since you want to do advanced things with the output of your component, this might be the time to go to render functions, as they allow far more flexibility:
Vue.component('button-group', {
props: {
styling: {
default: ''
}
},
render(createElement) { // createElement is usually called `h`
// You can also do this in 1 line, but that is more complex to explain...
// const children = this.$slots.default.filter(slot => slot.tag).map(slot => createElement('li', {}, [slot]))
const children = [];
for(let i = 0; i < this.$slots.default.length; i++) {
if(!this.$slots.default[i].tag) {
// Filter out "text" nodes, so we don't make li elements
// for the enters between the buttons
continue;
}
children.push(createElement('li', {}, [
this.$slots.default[i]
]));
}
return createElement('ul', {staticClass: "button-group",class: this.styling}, children);
}
})
var app = new Vue({
el: '#app',
})
.rainbow-background {
/* todo: implement rainbow background */
border: red 1px solid;
}
.button-group {
border-color: blue;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<div id="app">
<button-group styling="rainbow-background">
<button>hi</button>
<button>How are you?</button>
<button>I'm fine</button>
<button>Have a nice day!</button>
</button-group>
</div>
A render function works by returning an virtual html structure, this structure is generated by repeated calls to a createElement
functions. createElement
accepts 3 parameters, a tag name (like ul
or li
), an options object, and an list of children.
We first start by generating a list of children with our incoming slots, who are stored inside this.$slots.default
.
Then we loop over this list, filtering out incoming slot data that is basically text, this is because the way HTML considers whitespace between tags as text.
We are now almost complete with our final structure, we now wrap the slot element in a freshly generated li
tag, and then we finish the generating of with wrapping everything in a new ul
tag with the proper class names.
Upvotes: 3
Reputation: 29071
The Vue way to do this is use names slots, and provide the data the child uses to the parent. The data will propagate to the child via the slot-scope
.
The whole key to this is the data flows through the parent to the child.
Here's a working codepen of the process: https://codepen.io/Flamenco/pen/ZMOdYz
This example uses the default slot, so name
attribute is not needed on parent or child.
<template>
<ul class="button-group" :class="styling">
<li v-for='item in items'><slot :item='item'></slot></li>
</ul>
</template>
<script>
...
props:{
items: Array
}
</script>
<vue-button-group class="radius tiny" :items='items'>
<template slot-scope='scope'>
<vue-button link="#" styling="tiny bg-aqua">{{scope.item.text}}</vue-button>
</template>
</vue-button-group>
<script>
...
data:{
items:[{text:'Button 1'},{text:'Button 2'},{text:'Button 3}]
}
</script>
Upvotes: 1