i want to use a dynamic slot name(v-slot="{ hover }"), when using this hover component
<v-hover v-slot="{ hover }">
<v-chip v-if = "visibleChip">
Abc
</v-chip>
<script>
export default Component({
computed: {
visibleChip() {
return this.hover && this.someAction && this.someOtherAction
}
},
})
</script>
Where should i define this hover slot in order to use it in computed property and how ?
Related
Vuetify v-data-table supports several types of slots: v-slot:body, v-slot:item and v-slot:item.<name>.
We have been using v-slot:item.<name> extensively, as these provides a flexible way to style and process content in individual columns AND allow the table headers to be programmatically changed.
I'd like to add draggability to my v-data-table rows and have got this working using Vue.Draggable.
However the draggable component requires use of the v-data-table v-slot:body i.e. taking control of the full body of the table and thereby losing the flexibility of v-slot:item.<name>.
Is there a way these two components can be used together and provide v-slot:item.<name> support?
I have created a DataTableRowHandler component which allows v-slot:item.<name> support.
This is placed inside the draggable component, inserts the table <tr> element and feeds off the same "headers" array to insert <td> elements and v-slot:item.<name> entries. If no v-slot:item.<name> is defined then the cell value is output, in the same way that v-data-table works.
Here is the example component usage:
<v-data-table
ref="myTable"
v-model="selected"
:headers="headers"
:items="desserts"
item-key="name"
class="elevation-1"
>
<template v-slot:body="props">
<draggable
:list="props.items"
tag="tbody"
:disabled="!allowDrag"
:move="onMoveCallback"
:clone="onCloneCallback"
#end="onDropCallback"
>
<data-table-row-handler
v-for="(item, index) in props.items"
:key="index"
:item="item"
:headers="headers"
:item-class="getClass(item)"
>
<template v-slot:item.lock="{ item }">
<v-icon #click="item.locked = item.locked ? false : true">{{
item.locked ? "mdi-pin-outline" : "mdi-pin-off-outline"
}}</v-icon>
</template>
<template v-slot:item.carbs="{ item }">
{{ item.carbs }}
<v-icon>{{
item.carbs > 80
? "mdi-speedometer"
: item.carbs > 45
? "mdi-speedometer-medium"
: "mdi-speedometer-slow"
}}</v-icon>
</template>
</data-table-row-handler>
</draggable>
</template>
</v-data-table>
Here is the DataTableRowHandler component code
<template>
<tr :class="getClass">
<td v-for="(header, index) in headers" :key="index">
<slot :item="item" :name="columnName(header)">
<div :style="getAlignment(header)">
{{ getNonSlotValue(item, header) }}
</div>
</slot>
</td>
</tr>
</template>
<script>
export default {
name: "DataTableRowHandler",
components: {},
props: {
itemClass: {
type: String,
default: "",
},
item: {
type: Object,
default: () => {
return {};
},
},
headers: {
type: Array,
default: () => {
return [];
},
},
},
data() {
return {};
},
computed: {
getClass() {
return this.itemClass;
}
},
methods: {
columnName(header) {
return `item.${header.value}`;
},
getAlignment(header) {
const align = header.align ? header.align : "right";
return `text-align: ${align}`;
},
getNonSlotValue(item, header) {
const val = item[header.value];
if (val) {
return val;
}
return "";
},
},
};
</script>
An example of it's use is in this codesandbox link
I checked the source code of VDataTable and found that the content in tbody is generated by genItems().
The following example is implemented with functional components and is fully compatible with v-slot:item*:
<v-data-table ref="table" ...>
<template #body="props">
<draggable
v-if="$refs.table"
tag="tbody"
:list="props.items"
>
<v-nodes :vnodes="$refs.table.genItems(props.items, props)" />
</draggable>
</template>
<template #item.name="{ item }">
...
</template>
</v-data-table>
Here is the definition of the VNodes component:
components: {
VNodes: {
functional: true,
render: (h, ctx) => ctx.props.vnodes,
}
}
If component A is a parent to component B, where B has a slot. If A uses the slot with a button, where do you place the clickHandler, in A or in B?
Is both possible as long as you don't have both the same time? And if so, are there a best practice or does it depend on the situation?
You place it in A.
Slots are just cues for Vue to 'put the template here'. B doesn't know that you're rendering a button in that template. A determines that it's a button being rendered, and thus determines what happens when that button is pressed.
Technically, you could check what is being rendered in the slot from Component B, and apply a clickHandler to the slot if it's a button, as it's all just a render function under the hood. But for the sake of 'where do I put my function', that's generally too complex and rarely useful.
The child component could expose data to the parent one using scoped slot, in the parent we put some element which could have some events that have handlers defined in the parent component.
Example :
List
<template>
<div>
<ul>
<li v-for="(item, i) in items" :key="i">
<slot name="item" :item="item"></slot>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "list",
props: ["items"],
};
</script>
parent :
<template>
<list :items="items">
<template #item="{ item }">
<h3>{{ item }}</h3>
<span #click="deleteItem(item)">delete</span>
</template>
</list>
</template>
<script>
import List from "./List.vue";
export default {
data() {
return {
items: ["item 1", "item 2", "item 3"],
};
},
methods: {
deleteItem(item) {
console.log(item);
this.items = this.items.filter((_item) => _item !== item);
},
},
components: {
List,
},
};
</script>
LIVE DEMO
I have a view that will have a section with components that are already built. I want to take that information captured in each component and put it into a json object. The view looks like this:
<template>
<v-row>
<v-flex>
<v-btn
:click="prevStep"
v-if="counter != 0"
>
Back
</v-btn>
<v-btn
:click="nextStep"
v-if="counter != component.length"
>
Next
</v-btn>
<keep-alive>
<component :is="component[counter]"></component>
</keep-alive>
<v-btn
:click="submit"
v-if="counter == component.length"
>
Submit
</v-btn>
...
</template>
<script>
import ALL THE COMPONENTS (3 total)
export default {
name: "formSubmit",
components: {
comp1,
comp2,
comp3
},
data() {
return {
component: [
'comp1',
'comp2',
'comp3'
],
counter: 0,
}
},
methods: {
nextStep: function() {
this.counter++;
},
prevStep: function() {
this.counter--;
}
}
}
</script>
So if I hardcode counter to the correct array slot, it loads that component like I expect, but clicking my buttons does nothing and does not change which component is active. I have tried adding a console.log to each button method to display the current value of the counter, but nothing shows up - no entry in the console for counter at all.
Am I over-complicating this or have I just made a dumb error somewhere?
You use click like a prop and not like an event. Replace both :click with #click:
<v-btn
#click="prevStep"
v-if="counter != 0"
>
...
<v-btn
#click="nextStep"
v-if="counter != component.length"
>
I have a component that renders navigation links. It's based on scopedSlots feature. The component is working fine, but I'd like to improve it. This is how it's currently used:
<horizontal-navigation :items="items" #default="{ item }">
<navigation-item :item="item"></navigation-item>
</horizontal-navigation>
Array of items looks like this:
[
{ path: '/about', title: 'About' },
{ path: '/', title: 'Home' }
]
Here's HorizontalNavigation component template:
<template>
<div class="horizontal-navigation">
<slot v-for="(item, index) in items" :key="index" :item="item"></slot>
</div>
</template> -->
Here's NavigationItem component template
<template>
<router-link class="navigation-item" :path="item.path">{{ item.title }}</router-link>
</template>
I'm trying to replace HorizontalNavigation component's template with a render function, because I want to make this component to render links with a default styling when no slot content is provided.
Here's where I'm stuck:
render () {
const options = {
class: 'horizontal-navigation'
}
let children = []
if (this.$slots.default) {
children = this.$slots.default({ items: this.items }) // NEED TO ITERATE ITEMS TO CREATE INSTANCES OF A COMPONENT PASSED TO DEFAULT SLOT
}
return h('div', options, children)
}
The closest thing I found in the docs is this:
render() {
if (this.items.length) {
return Vue.h('ul', this.items.map((item) => {
return Vue.h('li', item.name)
}))
} else {
return Vue.h('p', 'No items found.')
}
}
Any advice?
In the template try to use conditional rendering to render the slot or the fallback content :
<div class="horizontal-navigation">
<template v-for="(item, index) in items" >
<template v-if="$slots.default">
<slot :item="item"></slot>
</template>
<template v-else>
<router-link class="navigation-item" :path="item.path">{{ item.title }}</router-link>
</template>
</template>
</div>
I'm trying to create buttons dynamically whose names are fetched from keys of an Object. And I need to pass the corresponding value to the method to get my code work. How do you do both of these tasks?
<template>
<v-app>
<router-view></router-view>
<v-app-bar app dense id="abc" dark>
<v-icon>note_add</v-icon>
<v-toolbar-title>Title</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn
v-for="(value, key) in button_redirect_to"
:key="key"
#click="render_comp()"
>{{ key }}</v-btn>
<v-btn depressed fab small>
<v-icon>notifications</v-icon>
</v-btn>
</v-app-bar>
</v-app>
</template>
<script>
export default {
name: "Navbar",
data() {
return {
button_redirect_to: {
Projects: "/projects",
Requests: "/requests",
Reports: "/reports",
Resources: "/resources",
temp: "/temp"
},
pers_actions: ["Profile", "LogOut"]
};
},
methods: {
render_comp() {
this.$router.push();
}
}
};
</script>
In your for-loop, value is the route and key is the button label. Just pass the route (value) as an argument:
#click="render_comp(value)"
methods: {
render_comp(to) {
this.$router.push(to);
}
}
Pass the value variable from the for loop (which in the first case will be "/projects") as an argument of #click="render_comp(value)" handler so it can be used inside the function.
<v-btn
v-for="(value, key) in button_redirect_to"
:key="key"
#click="render_comp(value)"
>
{{ key }}
</v-btn>
render_comp will then have access to it so it can be passed into this.$router.push(), making the function this.$router.push("/projects").
methods: {
render_comp(value) {
this.$router.push(value);
}
}
Hope this helps.