Vue.js How to use <template v-slot> on Props - vue.js

thanks for reading my question. I've a dynamic table and I'm trying to use a prop in v-slot, like bellow:
<v-data-table :headers="chooseHeader" :items="chooseItem" :search="search">
<template v-slot:[`item.iconValue`]>
<v-icon> mdi-clipboard-edit-outline </v-icon>
</template>
</v-data-table>
<script>
export default {
name: 'EasyHrPerformanceEvaluationListsNewSolicitation',
props: {
iconValue: {
type: Object,
required: false,
},
chooseHeader: {
type: Array,
required: true,
},
chooseItem: {
type: Array,
required: true,
},
},
I call the component EasyHrPerformanceEvaluationListsNewSolicitation to pass the prop values:
<EasyHrPerformanceEvaluationListsNewSolicitation
:title="$t('Novas Avaliações')"
:chooseHeader="selfEvaluationsHeader"
:chooseItem="selfEvaluations"
:iconValue="makeEvaluation" //v-slot value
>
</EasyHrPerformanceEvaluationListsNewSolicitation>
Bellow is my page, if you have a look you can se the column "avaliar" isn't working (my v-slot)
enter image description here
Is possible to use the prop value bellow in my v-slot?
iconValue: {
type: Object,
required: false,
},
Can you help me?

I think from the syntax you are using vuetify data table. And as per the doc's, slots are to be written like this.
<template v-slot:[`item.iconValue`]="{item}">
Refer -> https://vuetifyjs.com/en/components/data-tables/#item

Related

ComboBox template component with VueJS

I want to make a Combobox template Component with vuejs v3 and to do so I have the following code:
<template>
<select name={{selector_name}} class= "combobox" id={{selector_id}}>
v-for=opt in {{selector_options}}
<option value=opt>
opt
</option>
</select>
</template>
<script>
export default {
name: 'ComboBox',
data() {
return {
selector_name: null,
selector_id: null,
selector_options : null,
}
},
props: {
selector_name: {
type: String,
required: true
},
selector_id: {
type: String,
default: "combobox"
},
selector_options: {
type: Array,
required: true
}
},
methods: {
onChange: (event) => {
console.log(event.target.value);
},
},
computed: {},
}
</script>
But the way that I use v-for does not seem to be correct to me, can you please tell me how can I correct that? thanks in advance.
I see a lot of things, to be clear and answer your questions, v-for is used on a element.
<template>
// For mor readability i recommend you do bind your property:
// - name={{selector_name}} become :name="selector_name"
<select :name="selector_name" class= "combobox" :id="selector_id">
<!-- v-for is required with a unique key, you can take the index and use it -->
<option v-for="(opt, index) in selector_options" :key="`opt ${index}`" value=opt>
{{ opt }}
</option>
</select>
</template>
You can't define a props and data with the same name. Props, inject inside a data the property and value.
It's exactly the same, but the data come from a parent where you pass a value to the child.
So use props if you need to pass data, but not define it too inside data.
There is a work example of all of that (i use data and not props for readability).

Two v-data-tables, second table has v-select that uses the data from the first table in the dropdown. Somehow is bound to newest data

I have two v-data-tables that are being rendered in a component. The first one is imported from another file as the matching-game-crud-table. This is the answer table. This first table manipulates information for the answerObject array, which is also used in the second tables to populate the v-select dropdown element. The first table (answer table) is set up like this below. Its items are updating the answerObjects on the main file.
<template>
<v-container>
<v-data-table
:headers="tableHeaders"
:items="items"
class="elevation-1"
>
<template #[`item.display`]="{item}">
<v-text-field
v-model="item.display"
:hide-details="true"
dense
outlined
:autofocus="true"
label="Display"
>
<!-- <v-icon v-if="header.draggable" slot="prepend"
>mdi-drag-vertical</v-icon
> -->
</v-text-field>
</template>
</v-data-table>
</v-container>
</template>
<script>
import draggable from 'vuedraggable'
import _ from 'lodash'
export default {
components: { draggable },
props: {
headers: { type: Array, required: true, default: [] },
defaultItem: { type: Object, required: true, default: {} },
allowDrag: { type: Boolean, required: false, default: false },
useModal: { type: Boolean, required: false, default: false },
data: { type: Array, required: true, default: [] },
},
data: () => ({
dialog: false,
dialogDelete: false,
items: [],
editedIndex: -1,
editedItem: {},
}),
computed: {
tableHeaders() {
let new_headers = JSON.parse(JSON.stringify(this.headers))
new_headers.push({
text: 'Actions',
value: 'actions',
sortable: false,
})
return new_headers
},
},
mounted() {
this.items = _.cloneDeep(this.data)
this.editedItem = _.cloneDeep(this.defaultItem)
},
created() {},
methods: {
addNew() {
console.log('add new')
const addObj = _.cloneDeep(this.defaultItem)
addObj.id = this.items.length + 1
this.items.push(addObj)
this.$emit('input', this.items)
},
},
}
The main page is set up like this. This is the page where you can see the second v-data-table with the v-select that is using the answerObjects array. The matching-game-crud-table is the file that utilizes the first table. There is a watch set up for the answerObject on the main file so that it is updated with the newest empty answer object from the matching-game-crud-table component.
<matching-game-crud-table
:headers="headers"
:default-item="default_item"
:data="answerObjects"
allow-drag
v-model="answerObjects"
></matching-game-crud-table>
<v-data-table
:headers="promptHeaders"
:items="prompts"
class="elevation-1"
>
<template #[`item.answer`]="{item, index}">
<v-select
:hide-details="true"
v-model="item.answer"
:items="answerObjects"
item-text="display"
item-value="display"
outlined
dense
label="Choose an answer value"
#input="change($event, true)"
></v-select>
</template>
</v-data-table>
<script>
import draggable from 'vuedraggable'
import _ from 'lodash'
import MatchingGameCrudTable from '../../MatchingGameCrudTable.vue'
export default {
name: 'MatchingGame',
components: { MatchingGameCrudTable, draggable },
props: {
value: { type: String, required: true },
data: { type: Object, required: true },
},
watch: {
answerObjects(newData, oldData) {
console.log(newData)
this.items = [newData]
console.log(this.items)
// this.mainBucket = this.setMainBucket()
},
},
data(){
return{
default_item: {
id: 0,
display: '',
feedback: '',
},
prompts: [],
answerObjects: [],
items: [],
}
}
</script>
The issue that seems to be happening is that if I add a new Answer to the answer table, then the addNew function is called and updates the answerObjects array with an empty answer object. For some reason if I also have an empty prompt object on the second table that has not been filled in yet, so the prompt.answer key is "". Then the v-select will take on the empty answer objects as the prompts answer. So as I fill in an answer in the empty answer object. The prompt answer is linked and is updated along with it. But the prompt answer is still "", it is just updating the v-select dropdown. I want the v-select to remain unchosen. And then the dropdown should populate with the answer value, but the user should be manually selecting it. I am unsure of why this link is occuring
I would suggest u fetch data from a central state like vuex, if the v-select is triggered on the first table it alters the state, then the second table uses the altered state

Pass component as prop in Vue JS

Intro: I am exploring Vue Js and got stuck while trying to make a dynamic data table component the problem I am facing is that I cannot pass a component via props and render it inside a table.
Problem: So basically what I am trying to do is to pass some custom component from headers prop in v-data-table such as:
headers = [
{ text: 'Name', value: 'name' },
{
text: 'Phone Number',
value: 'phone_number',
render: () => (
<div>
<p>Custom Render</p>
</div>
)
},
{ text: 'Actions', value: 'actions' }
]
So from the code above we can see that I want to render that paragraph from the render function inside Phone Number header, I did this thing in React Js before, but I cannot find a way to do it in Vue Js if someone can point me in the right direction would be fantastic. Thank you in advance.
You have 2 options - slots and dynamic components.
Let's first explore slots:
<template>
<v-data-table :items="dataItems" :headers="headerItems">
<template slot="item.phone_number" slot-scope="{item}">
<v-chip>{{ item.phone_number }}</v-chip>
</template>
<template slot="item.company_name" slot-scope="{item}">
<v-chip color="pink darken-4" text-color="white">{{ item.company_name }}</v-chip>
</template>
</v-data-table>
</template>
The data table provides you slots where you can customize the content. If you want to make your component more reusable and want to populate these slots from your parent component - then you need to re-expose these slots to the parent component:
<template>
<v-data-table :items="dataItems" :headers="headerItems">
<template slot="item.phone_number" slot-scope="props">
<slot name="phone" :props="props" />
</template>
<template slot="item.company_name" slot-scope="props">
<slot name="company" :props="props" />
</template>
</v-data-table>
</template>
If you don't know which slots will be customized - you can re-expose all of the data-table slots:
<template>
<v-data-table
:headers="headers"
:items="items"
:search="search"
hide-default-footer
:options.sync="pagination"
:expanded="expanded"
class="tbl_manage_students"
height="100%"
fixed-header
v-bind="$attrs"
#update:expanded="$emit('update:expanded', $event)"
>
<!-- https://devinduct.com/blogpost/59/vue-tricks-passing-slots-to-child-components -->
<template v-for="(index, name) in $slots" v-slot:[name]>
<slot :name="name" />
</template>
<template v-for="(index, name) in $scopedSlots" v-slot:[name]="data">
<slot :name="name" v-bind="data" />
</template>
<v-alert slot="no-results" color="error" icon="warning">
{{ $t("no_results", {term: search}) }}"
</v-alert>
<template #footer="data">
<!-- you can safely skip the "footer" slot override here - so it will be passed through to the parent component -->
<table-footer :info="data" #size="pagination.itemsPerPage = $event" #page="pagination.page = $event" />
</template>
</v-data-table>
</template>
<script>
import tableFooter from '#/components/ui/TableFooter'; // you can safely ignore this component in your own implementation
export default
{
name: 'TeacherTable',
components:
{
tableFooter,
},
props:
{
search:
{
type: String,
default: ''
},
items:
{
type: Array,
default: () => []
},
sort:
{
type: String,
default: ''
},
headers:
{
type: Array,
required: true
},
expanded:
{
type: Array,
default: () => []
}
},
data()
{
return {
pagination:
{
sortDesc: [false],
sortBy: [this.sort],
itemsPerPageOptions: [25, 50, 100],
itemsPerPage: 25,
page: 1,
},
};
},
watch:
{
items()
{
this.pagination.page = 1;
},
sort()
{
this.pagination.sortBy = [this.sort];
this.pagination.sortDesc = [false];
},
}
};
</script>
Dynamic components can be provided by props:
<template>
<v-data-table :items="dataItems" :headers="headerItems">
<template slot="item.phone_number" slot-scope="{item}">
<component :is="compPhone" :phone="item.phone_number" />
</template>
<template slot="item.company_name" slot-scope="{item}">
<component :is="compCompany" :company="item.company_name" />
</template>
</v-data-table>
</template>
<script>
export default
{
name: 'MyTable',
props:
{
compPhone:
{
type: [Object, String], // keep in mind that String type allows you to specify only the HTML tag - but not its contents
default: 'span'
},
compCompany:
{
type: [Object, String],
default: 'span'
},
}
}
</script>
Slots are more powerful than dynamic components as they (slots) use the Dependency Inversion principle. You can read more in the Markus Oberlehner's blog
Okay, I don't believe this is the best way possible but it works for me and maybe it will work for someone else.
What I did was I modified the headers array like this:
headers = [
{ text: 'Name', align: 'start', sortable: false, value: 'name' },
{
text: 'Phone Number',
key: 'phone_number',
value: 'custom_render',
render: Vue.component('phone_number', {
props: ['item'],
template: '<v-chip>{{item}}</v-chip>'
})
},
{ text: 'Bookings', value: 'bookings_count' },
{
text: 'Company',
key: 'company.name',
value: 'custom_render',
render: Vue.component('company_name', {
props: ['item'],
template:
'<v-chip color="pink darken-4" text-color="white">{{item}}</v-chip>'
})
},
{ text: 'Actions', value: 'actions', sortable: false }
]
And inside v-data-table I reference the slot of custom_render and render that component there like this:
<template v-slot:[`item.custom_render`]="{ item, header }">
<component
:is="header.render"
:item="getValue(item, header.key)"
></component>
</template>
To go inside the nested object like company.name I made a function which I called getValue that accepts 2 parametes, the object and the path to that value we need which is stored in headers array as key (ex. company.name) and used loadash to return the value.
getValue function:
getValue (item: any, path: string): any {
return loadash.get(item, path)
}
Note: This is just the initial idea, which worked for me. If someone has better ideas please engage with this post. Take a look at the props that I am passing to those dynamic components, note that you can pass more variables in that way.

vue.js v-select default value

I want to make a default value of the select option using vue.js
here is my code
<v-select
v-model="contact.title"
:options="['Mr','Mrs','Ms']">
</v-select>
and
export default {
props: {
contact: {
type: Object,
required: true,
},
titles: {
type: Array,
required: true,
},
},
};
thanks
Try this.I think this will work.
<v-select
v-model="selected"
:options="options">
</v-select>
data: () {
return {
selected: 'Option 1',
options: ["Option 1","Option 2","Option 3"]
}
},
Mutating a prop is not best practice in vue.
You could do it like:
<v-select
v-model="selected"
:options="['Mr','Mrs','Ms']">
</v-select>
data: function () {
return {
selected: '' || 'defaultValue'
}
},
This way you are not mutating the prop and you easily can set a default value.
If you want to pass the data to the parent look at:
Pass data to parent

Dynamic form problem in vue2: [TypeError: Cannot read property '_withTask' of undefined]

I have to create a dynamic form in vue2. I want to save the values of the dynamic fields in an named object so that I can pass them along on submit.
The following code is working fine except I get an error in the console when I change the input value the first time (value will be propagated correctly though):
[TypeError: Cannot read property '_withTask' of undefined]
Here is how I define the props:
props: {
fields: {
type: Object,
default: {startWord: 'abc'}
},
},
And this is how I populate the model from the input field:
v-model="fields[field.id]"
Here is the entire code:
<template>
<div>
<!-- Render dynamic form -->
<div v-for="(field, key) in actionStore.currentAction.manifest.input.fields">
<!-- Text -->
<template v-if="field.type == 'string'">
<label>
<span>{{key}} {{field.label}}</span>
<input type="text" v-bind:placeholder="field.placeholder"
v-model="fields[field.id]"/>
</label>
</template>
<!-- Footer -->
<footer class="buttons">
<button uxp-variant="cta" v-on:click="done">Done</button>
</footer>
</div>
</template>
<script>
const Vue = require("vue").default;
const {Bus, Notifications} = require('../../Bus.js');
module.exports = {
props: {
fields: {
type: Object,
default: {startWord: 'abc'}
},
},
computed: {
actionStore() {
return this.$store.state.action;
},
},
methods: {
done() {
console.log('fields', this.fields);
Bus.$emit(Notifications.ACTION_INPUT_DONE, {input: this.fields});
}
},
}
</script>
So again, everything is working just fine (showing initial value in input, propagating the new values to the model etc.). But I get this '_withTask' error when I first enter a new character (literally only on the first keystroke). After that initial error it doesn't pop up again.
-- Appendix --
This is what the manifest/fields look like:
manifest.input = {
fields: [
{ id: 'startWord', type: 'string', label: 'Start word', placeholder: 'Enter start word here...' },
{ id: 'startWordDummy', type: 'string', label: 'Start word dummy', placeholder: 'Enter start word here...' },
{ id: 'wordCount', type: 'integer', label: 'Word count' },
{ id: 'clean', type: 'checkbox', label: 'Clean up before' },
]
}
-- Update --
I just discovered that if I set the dynamic field values initially with static values I don't get the error for those fields set this way:
created() {
this.fields.startWord = 'abc1';
},
But this is not an option since it will be a dynamic list of fields. So what is the best way to handle scenarios like this?
From documentation: Due to the limitations of modern JavaScript (and the abandonment of Object.observe), Vue cannot detect property addition or deletion. Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the data object in order for Vue to convert it and make it reactive.
As I understand it's bad idea create keys of your object by v-model.
What would I do in HTML:
<input type="text"
:placeholder="field.placeholder"
#input="inputHandler(event, field.id)" />
Then in JS:
methods: {
// ...
inputHandler({ target }, field) {
this.fields[field] = target.value;
}
},