How do I make dynamic item slots in Vuetify datatable? - vue.js

I'm learning to code and have a Vuetify datatable where I want to change boolean values in the table to icons. I want to make the table dynamic so I can reuse it as a component, passing props to it depending on page. I can get headers and other stuff to pass as props fine but passing props into the v-slot item in child table is problematic.
I am currently passing a 'booleans' prop from parent which has an array of objects including names of the columns that I want to change to icons and the true/false icons.
Here is the 'boolean' prop array from parent. I've stripped everything else out for readability.
booleans: [
{
name: "wo",
iconTrue: "mdi-account",
iconFalse: "mdi-office-building",
},
{
name: "ep",
iconTrue: "mdi-account",
iconFalse: "mdi-office-building",
}]
The child component is a vuetify datatable.
<template>
<div>
<v-data-table
:headers="headers"
:items="gotData"
:items-per-page="25"
:sort-by="sort"
dense
:search="search"
:loading="loading"
loading-text="Loading... Please wait"
#click:row="handleClick"
class="elevation-1"
>
<template v-for="(bool, i) in booleans" v-slot:[boolNames[i]]="{ item }">
<v-icon :key="i" class="mr-3">
{{ boolNames[i] ? bool.iconTrue : bool.iconFalse }}</v-icon>
</template>
</v-data-table>
</div>
</template>
Script
<script>
export default {
data: () => ({
search: "",
loading: "true",
dialog: false,
gotData: [],
}),
props: {
dataSource: {
type: String,
},
headers: {
type: Array,
},
tableTitle: {
type: String,
},
pageURL: {
type: String,
},
sort: {
type: String,
},
booleans: {
type: Array,
},
},
created() {
this.initialize();
},
computed: {
formTitle() {
return this.editedIndex === -1 ? "New Item" : "Edit Item";
},
boolNames: function () {
return this.booleans.map(function (bool) {
return "item." + bool.name;
});
},
},
methods: {
async initialize() {
const response = await this.$axios
.$get(this.dataSource)
.then((response) => {
this.gotData = response.data;
})
.catch(function (error) {
console.log(error);
});
this.loading = false;
},
async addItem(item) {
this.editedIndex = this.dataSource.indexOf(item);
this.editedItem = Object.assign({}, item);
this.dialog = true;
this.$axios({
method: "post",
url: this.dataSource,
data: this.item,
})
.then((response) => {
this.gotData = response.data;
})
.catch(function (error) {
console.log(error);
});
},
handleClick(item) {
this.$router.push(this.pageURL + item.id);
},
},
};
</script>
boolNames[i] returns item.wo and item.ep as I want it to but vuetify sees these as strings not props and so the ternary always reads true and all cells in table are the true icon.
If I hard code item.wo into the template it will work properly, e.g.
<template v-for="(bool, i) in booleans" v-slot:[boolNames[i]]="{ item }">
<v-icon :key="i" class="mr-3">
{{ item.wo ? bool.iconTrue : bool.iconFalse }}</v-icon>
</template>
I've tried all sorts of other ways to get this to work but can't figure out.
How do I get this to work?

Ok, figured it out. I got rid of the computed property and went back to how I started. Just had a syntax error. Here is code that works.
<template
v-for="(bool, i) in booleans"
v-slot:[`item.${bool.name}`]="{ item }"
>
<v-icon :key="i" class="mr-3">
{{ item[bool.name] ? bool.iconTrue : bool.iconFalse }}</v-icon
>
</template>

Related

How can I change selected item in qselect on page load

I use a QSelect Dropdown with some options on my page header, like the following:
<q-select
filled
v-model="model"
use-input
hide-selected
fill-input
input-debounce="0"
:options="options"
#filter="filterFn"
hint="Basic autocomplete"
style="width: 250px; padding-bottom: 32px"
emit-value
map-options
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
No results
</q-item-section>
</q-item>
</template>
</q-select>
const stringOptions = [
{label:'Google', value:'g1111111111'}, {label:'Facebook', value:'f2222222222'}, {label:'Twitter', value:'t3333333'}, {label:'Apple', value:'a44444444'}, {label:'Oracle', value:'o555555555'}
]
new Vue({
el: '#q-app',
data () {
return {
model: 'f2222222222',
options: stringOptions
}
},
methods: {
filterFn (val, update, abort) {
update(() => {
const needle = val.toLowerCase()
this.options = stringOptions.filter(v => v.label.toLowerCase().indexOf(needle) > -1)
})
}
}
})
How can I use a method to change the selected value on pageload for example from facebook to google?
I think with something like the following but cant get it working:
mounted: function () {
this.model = 'g1111111111'
},
codepen: https://codepen.io/aquadk/pen/JQbbKw
Thanks
You can use updated method, it called after the data changed and virtual dom is created for that component. Then you can update the value of the model.
const stringOptions = [
{label:'Google', value:'g1111111111'}, {label:'Facebook', value:'f2222222222'}, {label:'Twitter', value:'t3333333'}, {label:'Apple', value:'a44444444'}, {label:'Oracle', value:'o555555555'}
]
new Vue({
el: '#q-app',
data () {
return {
model: 'f2222222222',
options: stringOptions
}
},
methods: {
filterFn (val, update, abort) {
update(() => {
const needle = val.toLowerCase()
this.options = stringOptions.filter(v => v.label.toLowerCase().indexOf(needle) > -1)
})
}
},
updated(){
// Update the value of model
this.model = 'g1111111111';
}
})
The mounted should work, if it's not working the way you expect, try inside-mounted nextTick().
Here is an example with your code:
mounted () {
this.$nextTick(() => {
this.model = 'a44444444'
})
},

Render named scopedSlot programmatically

I want to move the following template into the render function of my component, but I don't understand how.
This is my template:
<template>
<div>
<slot name="item" v-for="item in filteredItems" :item="item">
{{ item.title }}
</slot>
</div>
</template>
This is my component:
export default {
props: {
items: {
type: Array,
required: true,
},
search: {
type: String,
default: ""
}
},
methods: {
filterByTitle(item) {
if (!("title" in item)) { return false; }
return item.title.includes(this.search);
}
},
computed: {
filteredItems() {
if (this.search.length === "") {
return this.items;
}
return this.items.filter(this.filterByTitle);
}
},
render: function(h) {
// How can I transform the template so that it finds its place here?
return h('div', ...);
}
};
I thank you in advance.
To render scoped slots you can use $scopedSlots. See more here.
Example Code:
...
render(h) {
return h(
'div',
this.filteredItems.map(item => {
let slot = this.$scopedSlots[item.title]
return slot ? slot(item) : item.title
})
)
}
...
JSFiddle

VueJS - Auto Fill Selected Dropdown List Relate Field

I have Related data between Province and District, Province hasMany District.
I have Object something like this.
{
"id": 1,
"name_eng": "Banteay Meanchey",
"name_kh": "បន្ទាយមានជ័យ",
"district": [
{
"id": 1,
"name_eng": "Mongkol Borei",
"name_kh": "មង្គលបុរី",
"province": 1
}
]
},
I have form like above image, All i want is when select Province the field District will Auto Filled to related relationship.
Between i use Vuetify v-autocomplete
<label
>Pls Select Province/District</label
>
<v-autocomplete
:items="provinces"
item-id="id"
item-text="name_kh"
label="Pls Select Province"
solo
v-model="form.province"
return-object
>
</v-autocomplete>
<v-autocomplete
:items="provinces"
item-id="id"
item-text="name_kh"
label="Pls Select District"
solo
v-model="form.province"
return-object
>
</v-autocomplete>
In script Tag
<script>
import axios from "axios";
export default {
data() {
return {
menu: false,
loading: false,
form: {},
provinces: [],
};
},
created() {
this.getProvince();
},
computed() {},
methods: {
getProvince() {
axios
.get(`api/v1/province/`)
.then((res) => {
console.log(res);
this.provinces = res.data;
})
.catch((err) => {
console.log(err.response);
});
},
},
};
</script>
I'll appreciate of all ur help :) Thanks...
On district dropdown, you can be looping by selected province like below
<v-autocomplete
:items="getDistrictsByProvince(form.province)"
item-id="id"
item-text="name_kh"
label="Pls Select District"
solo
v-model="form.province"
return-object
>
</v-autocomplete>
In script Tag under method:
getDistrictsByProvince(province) {
if(province){
return this.provinces.find(item => item.id === province.id).district
} else {
return []
}
}

Error: [vuex] do not mutate vuex store state outside mutation handlers

Why do I get error 「Error: [vuex] do not mutate vuex store state outside mutation handlers.」
・Error procedure
1.Input form data
2.Click submit(dispatch "setFormData")
3.Input form data
4.error
When input form data,I set data into tmpFormData.
tmpFormData is not vuex state.
It is components data.
Maybe,when exec 「this.$store.dispatch('ranks/setFormData', this.tmpFormData)」
Is tmpFormData and formData connected?
pages/search.vue
<template>
<v-container>
<v-form ref="form" v-on:submit.prevent="search">
<v-list width=100%>
<v-list-item v-for="(criteria, name, i) in searchCriterias" :key="i">
<v-subheader>{{criteria.name}}</v-subheader>
<template v-if="criteria.type=='checkbox'">
<v-checkbox
v-for="(item, j) in criteria.items" :key="j"
v-model="tmpFormData[name]"
:label="item"
:value="j + 1"
></v-checkbox>
</template>
</v-list-item>
</v-list>
<v-btn color="success" class="mr-4" type="submit">Search</v-btn>
</v-form>
</v-container>
</template>
<script>
export default {
data () {
return {
tmpFormData: {
search_1: null,
search_2: null,
search_3: null,
search_4: null,
},
searchCriterias: {
search_1: {
name: "sex",
items: ["male", "female"],
},
search_2: {
name: "price",
items: [
{label: "not selected", value: 0},
{label: "under 8000yen", value: 1}
],
},
},
}
},
methods: {
async search() {
await this.$store.dispatch('ranks/setFormData', this.tmpFormData)
}
}
}
</script>
store/search.js
export const state = () => ({
formData: [],
})
export const mutations = {
setFormData(state, formData) {
state.formData = formData
},
}
export const actions = {
async setFormData({ commit, getters }, formData) {
commit('setFormData', formData)
},
}
export const getters = {
formData (state) {
return state.formData
},
}
The error disappeared when I fixed it as follows, but I don't know why
setFormData(state, formData) {
state.formData.search_1 = formData.search_1
state.formData.search_2 = formData.search_2
state.formData.search_3 = formData.search_3
state.formData.search_4 = formData.search_4
},
I guess you have set strict: true on vuex.
In this mode, vuex warns you, when you manipulate any state without using a mutation. With this statement v-model="tmpFormData[name]" you do exactly that.
You can either disable strict mode or use something like vuex-map-fields

Getting Error in render: "TypeError: Cannot read property 'title' of undefined" when rendering CourseDescriptionPageComponent

Here is how CourseDescriptionPage.vue looks
import CourseCover from './CourseDescription/CourseCover.vue'
import WhyJoin from './CourseDescription/WhyJoin.vue'
import CourseStructure from './CourseDescription/CourseStructure.vue'
export default {
props: ['id'],
data () {
return {
hasDetails: false
}
},
created () {
this.$store.dispatch('loadCourseDetails', this.id).then(() => {
this.hasDetails = true
})
},
computed: {
course () {
return this.$store.state.courseDetails[this.id]
}
},
components: {
CourseCover,
WhyJoin,
CourseStructure
},
name: 'CourseDescriptionPage'
}
<template>
<div v-if="hasDetails">
<course-cover :courseTitle="course.title" :courseDuration="course.duration"></course-cover>
<why-join :courseTitle="course.title" :courseJobs="course.jobs"></why-join>
<course-structure :lectureList="course.lectureList"></course-structure>
</div>
</template>
Here is how my store looks
import Vuex from 'vuex'
import * as firebase from 'firebase'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
courseDetails: {},
loading: false
},
mutations: {
setCourseDetails (state, payload) {
const { id, data } = payload
state.courseDetails[id] = data
},
setLoading (state, payload) {
state.loading = payload
}
},
actions: {
loadCourseDetails ({commit}, payload) {
commit('setLoading', true)
firebase.database().ref(`/courseStructure/${payload}`).once('value')
.then((data) => {
commit('setCourseDetails', {
id: payload,
data: data.val()
})
commit('setLoading', false)
})
.catch(
(error) => {
console.log(error)
commit('setLoading', false)
}
)
}
}
Here is how my CourseCover.vue looks
export default {
props: {
courseTitle: {
type: String,
required: true
},
courseDuration: {
type: String,
required: true
}
},
name: 'CourseCover'
}
<template>
<v-jumbotron
src="./../../../static/img/course_cover_background.png">
<v-container fill-height>
<v-layout align-center>
<v-flex>
<h3>{{ courseTitle }}</h3>
<span>{{ courseDuration }}</span>
<v-divider class="my-3"></v-divider>
<v-btn large color="primary" class="mx-0" #click="">Enroll</v-btn>
</v-flex>
</v-layout>
</v-container>
</v-jumbotron>
</template>
I think there is something wrong with the way I am using props here but I couldn't figure out.
The data is loaded in store by the firebase that I know for sure because it shows in Vue dev tools but I just couldn't understand why Vue is complaining about that.
Thanks in advance.
course is undefined on component initialize ,so then you should return an empty object:
computed: {
course () {
return this.$store.state.courseDetails[this.id] || {}
}
},