I don't quite get how to move the v-slot data into a component.
Let's say I want to refactor the follwoing code:
<template v-slot:item="data">
<template v-if="typeof data.item !== 'object'">
<v-list-item-content v-text="data.item"></v-list-item-content>
</template>
<template v-else>
<v-list-item-avatar>
<img :src="data.item.avatar">
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-html="data.item.name"></v-list-item-title>
<v-list-item-subtitle v-html="data.item.group"></v-list-item-subtitle>
</v-list-item-content>
</template>
How do I pass the data inside a new component? I tried it with props but the component wouldn't show up:
<ListElementAvatar
:item="data.item"
:imgSrc="data.item.avatar"
:title="data.item.name"
:subtitle="data.item.group"
:source="data" />
ListElementAvatar:
<template>
<div>
<template v-if="typeof item !== 'object'">
<v-list-item-content :v-text="item"></v-list-item-content>
</template>
<template v-else>
<ListItemAvatar :imgSrc="imgSrc" />
<v-list-item-content>
<v-list-item-title :v-html="title"></v-list-item-title>
<v-list-item-subtitle :v-html="subtitle"></v-list-item-subtitle>
</v-list-item-content>
</template>
</div>
</template>
<script>
export default {
name: "ListElementAvatar",
props: {
item: {
type: Object,
default: () => {},
},
imgSrc: {
type: String,
default: '',
},
title: {
type: String,
default: '',
},
subtitle: {
type: String,
default: '',
},
source: {
type: Object,
default: () => {},
},
},
};
</script>
What I want to achieve:
I'm trying to refactor the code, i.e. creating small components. The code in the first listing should be put inside a vue component, called ListElementAvatar. Because I want to reuse it later. When I want to reuse it, I just call instead of the long code in the first listing.
Context:
https://codepen.io/thadeuszlay/pen/gOYevRZ?editors=1010
You need to put your component inside template with scoped-slot:
<template v-slot:item="data">
<ListElementAvatar
:item="data.item"
:imgSrc="data.item.avatar"
:title="data.item.name"
:subtitle="data.item.group"
:source="data"
/>
</template>
This should do the trick.
Related
I'm not sure why this isn't working... should be straight forward. I'm unable to bind a simple object to a named slot in one of my components:
I should be able to do the following:
Create a named slot and then bind a property to it:
<slot name="actions" :item="item" />
data(){
return {
item: {val1: 1, val2: 2}
}
}
Use it in this fashion:
<template #actions="{ item }">
<pre>{{ item }}</pre>
</template>
However, when I do this, this slot will not even render...
Below is my entire component code:
<template>
<v-dialog v-model="dialog" :persistent="persistent" :width="width">
<template #activator="{ on: dialogActivator, attrs: dialogAttrs }">
<v-tooltip bottom :disabled="!tooltipText">
<template #activator="{ on: tooltipActivator, tooltipAttrs }">
<v-btn
v-bind="{ ...dialogAttrs, ...tooltipAttrs, ...$attrs }"
v-on="{ ...dialogActivator, ...tooltipActivator }"
#click="$emit('handle-dialog-open-click')"
>
<slot name="activator"> Open </slot>
</v-btn>
</template>
<span>{{ tooltipText }}</span>
</v-tooltip>
</template>
<v-card :height="cardHeight">
<v-card-title
v-if="hasTitleSlot"
class="d-flex justify-space-between"
>
<slot name="title" />
</v-card-title>
<slot v-if="dialog" />
<v-card-actions v-if="hasActionsSlot" class="d-flex justify-end">
<slot name="actions" :item="obj" />
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
inheritAttrs: false,
props: {
width: {
type: String,
default: '500',
},
tooltipText: {
type: String,
default: '',
},
persistent: {
type: Boolean,
default: false,
},
cardHeight: {
type: String,
default: '',
},
},
data() {
return {
dialog: false,
obj: {
val1: 1,
val2: 2,
},
};
},
computed: {
hasTitleSlot() {
return !!this.$slots.title;
},
hasActionsSlot() {
return !!this.$slots.actions;
},
},
created() {
this.$root.$on('close-dialog', this.closeModal);
},
beforeDestroy() {
this.$root.$off('close-dialog', this.closeModal);
},
methods: {
closeModal() {
this.dialog = false;
},
},
};
</script>
It feels like its a simple typo somewhere...
EDIT:
Confirming that it works fine if I do not try to extract the prop like this:
<template #actions>
Some awesome action goes here
</template>
Problem is is in v-if="hasActionsSlot"
hasActionsSlot() {
return !!this.$slots.actions;
},
This method always returns false as your <slot name="actions" :item="obj" /> is not a regular slot, it is a scoped slot! And because this is not Vue 3 (where all slots, scoped or not are part of $slots), you need to use:
hasActionsSlot() {
return !!this.$scopedSlots.actions;
},
See $slots VS $scopedSlots
since 2.6.0+: All $slots are now also exposed on $scopedSlots as functions. If you work with render functions, it is now recommended to always access slots via $scopedSlots, whether they currently use a scope or not. This will not only make future refactors to add a scope simpler, but also ease your eventual migration to Vue 3, where all slots will be functions.
So in Vue 2.6+ it is always safer to work with $scopedSlots (as it contains all the slots)
So I have a vue project with a dashboard that contains many tests and i want to pass the test name as a parameter in an axios request when the user clicks on a button and gets redirected on another page.
I already did the first part and passed the name of the test in route as a parameter name and now i'm trying to fetch the corresponding item in the collection.When using $route.params.name I get an error that says $route is not defined
here's my code so far
<template>
<v-app>
<app-navbar />
<v-main>
<div class="text-center">
<h3>
test {{ $route.params.name }}, {{ $route.query.status }},{{
$route.query.tag
}}
</h3>
<h3 v-if="this.loadAPI">{{failreason()}}</h3>
</div>
<v-data-table
:headers="headers"
:items="imagesref"
:items-per-page="5"
class="elevation-1"
>
<template v-slot:[`item.index`]="{ index }">
{{index+1}}
</template>
<template v-slot:[`item.status`]="{ index }">
{{imagesresult[index][2]}}
</template>
<template v-slot:[`item.ref`]="{ index }">
<v-img :src="imagesref[index]" max-width="750" max-height="750" #click="expref[index] = !expref[index]"/>
<v-overlay :value="expref[index]"><v-img :src="imagesref[index]" max-width="1300" max-height="900" #click="expref[index] = !expref[index]"/> </v-overlay>
</template>
<template v-slot:[`item.test`]="{ index }">
<v-img :src="imagestest[index]" max-width="750" max-height="750" #click="exptest[index] = !exptest[index]"/>
<v-overlay :value="exptest[index]"><v-img :src="imagestest[index]" max-width="1300" max-height="900" #click="exptest[index] = !exptest[index]"/> </v-overlay>
</template>
<template v-slot:[`item.res`]="{ index }">
<v-img :src="imagesresult[index][0]" max-width="750" max-height="750" #click="expres[index] = !expres[index]"/>
<v-overlay :value="expres[index]"><v-img :src="imagesresult[index][0]" max-width="1300" max-height="900" #click="expres[index] = !expres[index]"/> </v-overlay>
</template>
<template #[`item.mis`]="{ index }">
{{Math.round(imagesresult[index][1]*100)/100}}
</template>
<template #[`item.Scrubber`]="{ index }">
<nuxt-link :to="{ path: 'scrubber', query: { imageref: imagesref[index],imagetest:imagestest[index],imageres:imagesresult[index] }}">Show Scrubber</nuxt-link>
</template>
</v-data-table>
</v-main>
</v-app>
</template>
<script>
import appNavbar from "../../../components/appNavbar.vue"
import axios from "axios"
export default {
components: { appNavbar },
name: "App",
data() {
return {
loadAPI:false,
dialog:false,
expref:[],
exptest:[],
expres:[],
items: [],
imagesref: [],
imagestest: [],
imagesresult: [],
headers: [
{ text: 'index',value: 'index',sortable:false},
{ text: 'Status',value: 'status',sortable:false},
{ text: 'Imagesref', value: 'ref',sortable:false },
{ text: 'Imagestest', value: 'test',sortable:false },
{ text: 'Imagesresult', value: 'res',sortable:false },
{ text: 'Mismatch percent', value: 'mis',sortable:false },
{ text: 'Scrubber', value: 'Scrubber',sortable:false },
]
}
},
async created() {
try {
const res = await axios({
method: 'post',
url: 'http://localhost:3002/backend/gettestbyname',
data: {name: $route.params.name}
})
this.items = res.data.data;
this.imagesref = res.data.data[0].refimages;
this.imagestest = res.data.data[0].testimages;
this.imagesresult = res.data.data[0].testresults;
for (let i of this.imagesref){
this.expref.push(false);
this.exptest.push(false);
this.expres.push(false);
}
this.loadAPI=true;
} catch (error) {
console.log(error);
}
},
methods:{
failreason()
{
if (this.items[0].status=="failed"){
let index=0;
for (let i of this.items[0].testresults)
{ console.log(i);
index++;
if (i[2]=="failed")
{
return 'Visual test failed at step number '+index;
}
}
return 'Test set missing screenshots';
}
}
}
}
</script>
<style scoped>
</style>
Globally injected properties in Vue are available from the Vue context, not the global javascript context (like window).
So, in the <script> tag, you have to use this.$router to access it.
In your created hook:
// replace
data: {name: $route.params.name}
//by
data: {name: this.$route.params.name}
From the vue-router docs:
By calling app.use(router), we get access to it as this.$router as well as the current route as this.$route inside of any component:
I know that a similar question has already been dealt with on stackoverflow. But I could not put together a solution from the proposed one. I am very ashamed.
The essence is this: I have a component and another one inside it.
The child component-VClipboardTextField is a ready-made configured text-area. I couldn't get the input out of there and I don't get an error when I try to enter it.
Avoid mutating a prop directly since the value will be overwritten
whenever the parent component re-renders. Instead, use a data or
computed property based on the prop's value. Prop being mutated:
"message"
code tabs-item.vue
<template>
<v-container fluid>
<v-row align="center">
<v-col cols="9">
<v-card flat>
<v-card-text>
<h1>Request</h1>
<v-container>
<v-textarea v-model="message"
placeholder="Placeholder"
label="Request"
auto-grow
clear-icon="mdi-close-circle-outline"
clearable
rows="10"
row-height="5"
#click:clear="clearMessage"
></v-textarea>
<v-textarea v-model="response"
placeholder="Placeholder"
label="Request2"
auto-grow
counter
rows="10"
row-height="5"
color="success"
></v-textarea>
<VClipboardTextField ></VClipboardTextField>
<VClipboardTextField isReadOnly></VClipboardTextField>
</v-container>
<v-row>
<v-btn
dark
color="primary"
elevation="12"
justify="end"
float-right
#click="sendRequest"
>
Send Request
</v-btn>
</v-row>
</v-card-text>
</v-card>
</v-col>
<v-col cols="3">
<schema-selector #changeSchema="onChangeSchema"></schema-selector>
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
name: 'tabs-item',
props: ['response'],
data() {
return {
schema: String,
message: '',
}
},
methods: {
sendRequest() {
const message = {
message: this.message,
method: this.schema.state
}
this.$emit('sendRequest', message)
},
clearMessage() {
this.message = ''
},
onChangeSchema(selectS) {
console.log("get schema: ", selectS.state)
this.schema = selectS
}
},
}
</script>
and child VClipboardTextField.vue
<template>
<v-container>
<v-tooltip bottom
v-model="show">
<template v-slot:activator="{ on, attrs }">
<v-textarea
v-model="message"
:append-outer-icon="'mdi-content-copy'"
:readonly="isReadOnly"
auto-grow
filled
counter
clear-icon="mdi-close-circle-outline"
clearable
label="Response message"
type="text"
#click:append-outer="copyToBuffer"
#click:clear="clearMessage"
></v-textarea>
</template>
<span>Tooltip</span>
</v-tooltip>
</v-container>
</template>
<script>
export default {
name: 'VClipboardTextField',
props: {
isReadOnly: Boolean,
message : { type :String, default: "msg"}
},
data() {
return {
show: false,
// messageLocal: 'Response!',
iconIndex: 0,
}
},
methods: {
copyToBuffer() {
console.log("this: ", this)
navigator.clipboard.writeText(this.message);
this.toolTipChange()
setTimeout(() => this.toolTipChange(), 1000)
},
clearMessage() {
this.message = ''
},
toolTipChange() {
if (this.show)
this.show = false
}
}
}
</script>
I will be glad to see an example of the code that will explain how to correctly solve this problem without crutches!
Thanks.
you cannot modify props in components, if the initial value of message is needed as placeholder (meaning the input might not be empty at the begining), you can store the data in message prop to another data variable and use that as v-model to the textarea.
if there is no initial value for message, just use another data variable for textarea and emit an update for message in a watcher.
in code tabs-item.vue
<VClipboardTextField :message.sync="message"></VClipboardTextField>
and child VClipboardTextField.vue
<template>
<v-container>
<v-tooltip bottom
v-model="show">
<template v-slot:activator="{ on, attrs }">
<v-textarea
v-model="message_slug"
:append-outer-icon="'mdi-content-copy'"
:readonly="isReadOnly"
auto-grow
filled
counter
clear-icon="mdi-close-circle-outline"
clearable
label="Response message"
type="text"
#click:append-outer="copyToBuffer"
#click:clear="clearMessage"
></v-textarea>
</template>
<span>Tooltip</span>
</v-tooltip>
</v-container>
</template>
<script>
export default {
name: 'VClipboardTextField',
props: {
isReadOnly: Boolean,
message : { type :String, default: "msg"}
},
data() {
return {
show: false,
// messageLocal: 'Response!',
iconIndex: 0,
message_slug: '',
}
},
watch: {
message_slug(x) {
this.$emit('update:message', x)
}
},
}
</script>
It will bind the value to message_slug and update the message on parent component when it's value changes.
Instead of watching for change every time, you can only emit when there are changes.
In your tabs-item.vue
<VClipboardTextField v-model="message"></VClipboardTextField>
In your VClipboardTextField.vue component, you can receive the v-model input prop as a "VALUE" prop and assign it to a local data property. That way u can manipulate the local property and emit only when in it is changed!
<template>
<v-container>
<v-tooltip bottom v-model="show">
<template v-slot:activator="{ on, attrs }">
<v-textarea
v-model="message"
:append-outer-icon="'mdi-content-copy'"
:readonly="isReadOnly"
v-on="on"
v-bind="attrs"
auto-grow
filled
counter
clear-icon="mdi-close-circle-outline"
clearable
label="Response message"
type="text"
#click:append-outer="copyToBuffer"
#click:clear="clearMessage"
#change="$emit('input', v)"
></v-textarea>
</template>
<span>Tooltip</span>
</v-tooltip>
</v-container>
</template>
<script>
export default {
name: "VClipboardTextField",
props: {
isReadOnly: { type: Boolean },
value: {
type: String,
},
},
data() {
return {
show: false,
message: this.value,
};
},
};
</script>
I got the following two components:
Parent:
<template>
<v-container>
<v-row class="text-center">
<v-col cols="12" class="parent">
<p>Ich bin der Parent component</p>
<button #click="changeDetail" :name.sync="name">Change Details</button>
<Child v-bind:name="name"></Child>
</v-col>
</v-row>
</v-container>
</template>
<script>
import Child from "./Child";
export default {
name: "Parent",
data: () => ({
name: "test"
}),
methods: {
changeDetail() {
this.name = "Updated from Parent";
}
},
components: {
Child
}
};
</script>
Child:
<template>
<v-container>
<v-row class="text-center">
<v-col cols="12">
<p>My name is: {{ name}}</p>
<button #click="resetname">Reset the name</button>
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
// props: ["name"],
props: {
name: {
type: String,
required: true
}
},
data: () => ({
newname: "Updated from Child"
}),
methods: {
resetname() {
this.$emit("update:name", this.newname);
}
}
};
</script>
As far as I read here: https://v2.vuejs.org/v2/guide/components-custom-events.html#sync-Modifier, I should use update and sync to pass props from the child back to the parent. However it does not work. I don´t understand what´s wrong here. What am I missing?
It is usually best to not bind your template to the prop but a computed property instead to ensure the data is accessed and modified externally. It will also simplify your code a bit so that you don't have to trigger updates.
Parent:
<template>
<v-container>
<v-row class="text-center">
<v-col cols="12" class="parent">
<p>Ich bin der Parent component</p>
<button #click="changeDetail">Change Details</button>
<Child v-bind:name.sync="name"></Child>
</v-col>
</v-row>
</v-container>
</template>
<script>
import Child from "./Child";
export default {
name: "Parent",
data: () => ({
name: "test"
}),
methods: {
changeDetail() {
this.name = "Updated from Parent";
}
},
components: {
Child
}
};
</script>
Child:
<template>
<v-container>
<v-row class="text-center">
<v-col cols="12">
<p>My name is: {{ currentName }}</p>
<button #click="resetname">Reset the name</button>
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
// props: ["name"],
props: {
name: {
type: String,
required: true
}
},
data: () => ({
//Be careful with fat arrow functions for data
//Because the value of *this* isn't the component,
//but rather the parent scope.
}),
computed: {
currentName: {
get() { return this.name },
set(value) { this.$emit("update:name", value); }
}
},
methods: {
resetname() {
this.currentName = "updated from child";
}
}
};
</script>
I'm starting a project in which I had to use Vue. I'm actually really new to this, so I'm learning on the go. I do apologize in advance since this question have answered before, however, I didn't really understand the solutions provided, which is why I'm here asking myself.
Well, I was trying to display some data on my Data Table (more specifically, v-data-table from Vuetify). I was able to get the data from the API, but, for some reason it doesn't show me anything. Thanks to Vuex I can see that the mutation worked because on the console on Google Chrome I can see the Array of objects. But as I said, it still does't show me a single thing on the table, it even says 'no data available'. Some errors that I get are things like '[Vue warn]: Invalid prop: type check failed for prop "items". Expected Array, got Object' and 'TypeError: this.items.slice is not a function'.
Here is the code from List.vue
<template>
<v-container id="data-tables" tag="section">
<div class="text-right">
<v-btn class="mx-2" fab dark color="primary" :to="{ name: 'UserCreate' }">
<v-icon dark>mdi-plus</v-icon>
</v-btn>
</div>
<base-material-card
color="indigo"
icon="mdi-vuetify"
inline
class="px-5 py-3"
>
<template v-slot:after-heading>
<div class="display-2 font-weight-light">
Lista de Empleados
</div>
</template>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
class="ml-auto"
label="Search"
hide-details
single-line
style="max-width: 250px;"
/>
<v-divider class="mt-3" />
<v-data-table
:headers="headers"
:items="users"
:search.sync="search"
:sort-by="['name', 'office']"
:sort-desc="[false, true]"
multi-sort
>
<template v-slot:item.actions="{ item }">
<v-icon small class="mr-2" #click="editItem(item)">
mdi-eye
</v-icon>
<v-icon
small
class="mr-2"
#click="editItem(item)"
:to="{ name: 'UserUpdate' }"
>
mdi-pencil
</v-icon>
<v-icon small #click="deleteItem(item)">
mdi-delete
</v-icon>
</template>
</v-data-table>
</base-material-card>
</v-container>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'UsersTable',
data() {
return {
headers: [
{
text: 'Nombre',
value: 'empleado.nombre',
},
{
text: 'Apellido',
value: 'empleado.apellido',
},
{
text: 'Dirección',
value: 'empleado.direccion',
},
{
text: 'Correo Electrónico',
value: 'email',
},
{
text: 'Teléfono',
value: 'empleado.telefono',
},
{
sortable: false,
text: 'Actions',
value: 'actions',
},
],
loader: true,
search: undefined,
}
},
created() {
this.$store.dispatch('users/fetchUsers')
},
computed: {
...mapState(['users']),
},
methods: {},
mounted() {},
}
</script>
And the code from user.js, where the fetchUsers it's coming from.
import auth from '#/api/auth'
export const namespaced = true
export const state = {
users: [],
}
export const mutations = {
SET_USERS(state, users) {
state.users = users
},
}
export const actions = {
fetchUsers({ commit, dispatch }) {
auth
.getAllAccounts()
.then((response) => {
commit('SET_USERS', response.data)
})
.catch((error) => {
const notification = {
type: 'error',
message: 'There was a problem fetching users: ' + error.message,
}
dispatch('notification/add', notification, { root: true })
})
},
}
Thanks in advance.
You are not getting the correct user from vuex, because is namespaced, change to:
computed: {
...mapState('users',['users']),
},
MapState helper dosen't work the same way like the other helpers because the state module isn't registred in the global namespace. So namespacing your module will help or you do it in this way:
computed: {
...mapState({
users: state => state.FilenameOfYourModule.users
})
}