data table template as prop vuejs - javascript

I have a component that I use in various parts of my application to render tables.
What I want to do now in view of the fact that every time I need to add more custom fields to the table (templates), is to be able to pass these templates as a prop to the parent component so that I do not have to always be editing this component.
As you can see I have tried in many ways and I have not been able to find the solution.
Father component
<template>
<section>
<data-table-search
:titleFilter="titleFilter"
:selectFilter="selectFilter"
:srchfunction="srchfunction"
:clkfunction="clkfunction"
:filterable="filterable"
:itemsFilter="itemsFilter"
:clickSelect="clickSelect"
:btnew="btnew"
:elevationNew="elevationNew"
:btnNewClass="btnNewClass"
:btsmall="btsmall">
</data-table-search>
<div :is="processedHtml"></div>
<v-data-table
class="tableBackground"
:dense="dense"
:headers="headers"
:items="items.data"
:server-items-length="items.recordsFiltered"
:options.sync="options"
:loading="loading"
:footer-props="footerProps"
no-data-text="No hay datos disponibles"
:loading-text="$t('comunes.general.cargando')">
<template v-slot:progress>
<v-progress-linear
height="2"
:color="colorProgress"
indeterminate
></v-progress-linear>
</template>
<template v-if="Object.keys(spanState).length > 0 && spanState.state == true" v-slot:item.state="{ item }">
<div class="">
<span v-if="item.state === 1" color="red">Abierto</span>
<span v-else-if="item.state === 2" color="green">En bodega</span>
<span v-else-if="item.state === 3" color="green">Revisado</span>
<span v-else-if="item.state === 4" color="green">En correo</span>
<span v-else-if="item.state === 5" color="green">Aceptada</span>
<span v-else-if="item.state === 6" color="green">Parcial</span>
<span v-else-if="item.state === 7" color="green">Rechazada</span>
<span v-else-if="item.state === 8" color="green">Orden de trabajo</span>
<span v-else-if="item.state === 9" color="green">Incompleto</span>
<span v-else-if="item.state === 10" color="green">Facturado</span>
<span v-else color="green"></span>
</div>
</template>
<template v-slot:item.activo="{ item }">
<v-icon :small="btsmall" color="green" v-if="item.activo">check_circle</v-icon>
<v-icon :small="btsmall" color="red" v-else>cancel</v-icon>
</template>
<template v-if="actions.length>1" v-slot:item.opciones="{ item }">
<v-menu offset-y small>
<template v-slot:activator="{ on }">
<v-btn :small="btsmall" class="btn-opciones" dark v-on="on">{{ $t('comunes.general.opciones') }}</v-btn>
</template>
<v-list class="dt-actions">
<v-list-item-group v-for="(action, index) in actions" :key="index">
<v-list-item #click="_onClick(action.action, item.id)">
<v-list-item-icon :class="action.color">
<v-icon :small="btsmall" class="text-white" v-text="action.icon"></v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title v-text="action.text"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-menu>
</template>
<template v-else v-slot:item.opciones="{ item }">
<v-btn :small="btsmall" class="btn-opciones" #click="_onClick(actions[0].action, item.id)" dark v-on="on">{{ btnSeleccionar ? btnSeleccionar : $t('comunes.general.seleccionar') }}</v-btn>
</template>
</v-data-table>
</section>
</template>
<script>
import datatableMixin from '#/mixins/datatable'
import dataTableSearch from '#/components/utilidades/dataTableSearch'
export default {
name: 'datatable',
mixins: [ datatableMixin ],
components: { dataTableSearch },
data () {
return {
on: false,
template: '<h1>Hola</h1>'
}
},
props: {
searchFunction: Function,
clkfunction: Function,
clickSelect: Function,
endpoint: String,
dense: Boolean,
btsmall: Boolean,
// initialData: {
// type: Boolean,
// default: true
// },
re: Function,
initialData: {
type: Boolean,
default () {
return true
}
},
headers: {
type: Array,
default: { }
},
spanState: {
type: Object,
default () {
return {}
}
},
itemsFilter: {
type: Array,
default: Array
},
templates: {},
filterable: {
type: Boolean,
default: false
},
selectFilter: {
type: Boolean,
default: false
},
titleFilter: {
type: String,
default: 'Filtro'
},
btnew: {
type: Boolean,
default: false
},
btnActionClass: {
type: String,
default: ''
},
btnNewClass: {
type: String,
default: ''
},
elevationAction: {
type: Number,
default: 2
},
elevationNew: {
type: Number,
default: 2
},
colorProgress: {
type: String,
default: 'primary'
},
btnSeleccionar: {
type: String,
default: null
},
actions: Array,
endManual: Boolean
},
computed: {
processedHtml () {
// let html = this.html.replace('[Placeholder]', '<my-component></my-component>')
return {
template: this.template
}
}
},
mounted () {
this.getData()
if (Object.keys(this.spanState).length > 0) {
console.log('fds', this.items)
}
},
methods: {
_onClick (event, id) {
this.clkfunction(event, id)
},
async srchfunction (text) {
this.search = text
this.options.page = 1
this.getData()
}
}
}
</script>
Child component:
<template>
<div>
<v-dialog v-if="dialogModalBuscador" v-model="dialogModalBuscador" scrollable max-width="600px">
<v-card>
<v-card-title>
<span class="h2" >{{title}}</span>
</v-card-title>
<v-card-text class="pb-0">
<data-table
:btsmall="true"
:endpoint="endpoint"
:headers="headers"
:filterable="true"
:actions="actions"
:clkfunction="clickFunction"
:initialData="initialDataL"
v-on:changeInitialDatastateEmit="emm"
:showCountOrdenesTecnico="True"
:customTemplate="re"
:endManual="true">
<template
v-for="header in headers"
v-slot:[`item.${header.value}`]="{ item }"
>
<slot :name="[`item.${header.value}`]" :item="item">
{{ getVal(item, header.value) }}
</slot>
</template>
<template v-slot:item="{ item }">
<div class="text-center">
<span v-if="item.countOrdenNoFacturada === 0" color="red">Sin Entregar</span>
<span v-else-if="item.countOrdenNoFacturada === 2" color="green">Solicitado</span>
<span v-else color="green"></span>
</div>
</template>
</data-table>
</v-card-text>
<v-card-actions class="d-block" >
<v-btn color="red" #click="dialogModalBuscador = false" outlined >{{$t('comunes.general.cancelar')}}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import configMixin from '#/mixins/config'
import dataTable from '#/components/utilidades/dataTable'
export default {
mixins: [ configMixin ],
components: { dataTable },
data () {
return {
dialogModalBuscador: false,
loading: false,
initialDataL: this.initialData
// templates: `1.0/cda/serviciosproductosobtener/${id}/`
}
},
props: {
title: {
type: String,
default: 'Listado default'
},
endpoint: {
type: String,
default: {}
},
headers: {
type: Array
},
hidden: {
type: Boolean,
default: true
},
initialData: {
type: Boolean,
default: true
},
actions: {
type: Array,
default: () => [ { 'text': 'Seleccionar', 'icon': 'check_box', 'action': 'ver', 'color': 'primary' } ]
}
// endpoint: String
},
mounted () {
},
methods: {
re () {
return { template: `<template v-slot:item.countOrdenNoFacturada="{ item }">
<div class="">
<span v-if="item.countOrdenNoFacturada === 0" color="red">Abiertoooo</span>
<span v-else color="green"></span>
</div>
</template>
` }
},
async clickFunction (event, id) {
if (event === 'ver') this.$emit('getId', id)
if (event === 'editar') this.$router.push({ name: this.openRoute(`cda/formulario/${id}/`) })
if (event === 'crear') this.$router.push({ name: this.openRoute(`cda/formulario/${id}/`) })
},
emm (e) {
console.log('EMMIT', e)
this.initialDataL = e
}
}
}
</script>

You can use v-bind="$attrs". which means that all the attributed passed to the parent component will pass on to the child component.
so in your case use it like so:
parent component:
<data-table-search v-bind="$attrs">
and now you don't need to declare all of those props in the parent component.

Related

Vue warn: Avoid mutating a prop directly when use vuetify <v-dialog>

Vue v2. Can't fix this warn, i'm use v-dialog in child element.Child element have prop - dialog for toggle v-dialog. Try to use for v-dialog v-bind:value and #input instead of v-model but with this don't open dialog. Try to use computed dialogLocal - dialog can't open with computed prop.
Parent
<template>
<div>
<p class='subtitle_text'>Поставщик</p>
<v-autocomplete
clearable
dense
v-model='supplier'
:items='suppliers'
:item-text='item => item.supplierName'
return-object
>
<template v-slot:item="{ item }">
<v-list-item-content>
<v-list-item-title v-text="item.supplierName"></v-list-item-title>
</v-list-item-content>
<v-list-item-action>
<v-btn
fab
x-small
dark
color='primary'
#click="openEditDialog(item)"
>
<v-icon
x-small
dark
>
mdi-pencil
</v-icon>
</v-btn>
</v-list-item-action>
<v-list-item-action>
<v-btn
style='margin-left: 15px'
fab
x-small
dark
color='red'
#click="openDeleteDialog(item)"
>
<v-icon
x-small
dark
>
mdi-delete
</v-icon>
</v-btn>
</v-list-item-action>
<DeleteDialog
message = 'Удалить поставщика'
v-bind:info = currentSupplier.supplierName
v-bind:id = currentSupplier.id
v-bind:isActive = 'isDeleteDialogActive'
#confirmAction = 'confirmDeleteSupplier'
#cancelAction = 'cancelDeleteSupplier'
ref='deleteDialog'
/>
</template>
<template v-slot:append-item>
<div style="padding-left: 0px; max-height: 2rem">
<v-btn
#click='openCreateDialog'
block
text
large
style='font-size: 1em'
>
<v-icon
left
large
color='green'
>
mdi-plus-circle
</v-icon>
Создать
</v-btn>
</div>
<SupplierDialog
v-bind:dialog='isSupplierDialogActive'
v-bind:title='supplierDialogTitle'
v-model:supplier='currentSupplier'
#save='confirmSupplierDialog()'
#cancel='cancelSupplierDialog'
/>
</template>
</v-autocomplete>
</div>
</template>
<script>
imports ...
export default {
components: {
DeleteDialog,
SupplierDialog
},
data() {
return {
suppliers: null,
currentSupplier: {id: null, name: null},
supplier: {id: null, name: null},
supplierDialogTitle: null,
isSupplierDialogActive: false,
isDeleteDialogActive: false
}
},
methods: {
getAllSuppliers() {
...
},
openEditDialog(supplier) {
this.isSupplierDialogActive = true
this.currentSupplier = supplier
this.supplierDialogTitle = 'Редактирование поставщика'
},
openDeleteDialog(supplier) {
...
},
confirmDeleteSupplier(id) {
...
},
cancelDeleteSupplier() {
...
},
openCreateDialog() {
this.currentSupplier = {id: null, name: null}
this.isSupplierDialogActive = true
this.supplierDialogTitle = 'Создание поставщика'
},
cancelSupplierDialog() {
this.currentSupplier = {id: null, name: null}
this.getAllSuppliers()
this.isSupplierDialogActive = false
},
confirmSupplierDialog(supplier) {
this.getAllSuppliers()
this.supplier = supplier
this.isSupplierDialogActive = false
}
},
mounted() {
this.getAllSuppliers()
}
}
</script>
<style>
...
</style>
Child
<template>
<div>
<v-dialog
v-model='dialog'
max-width="50vw"
persistent
>
<v-card class='dialog_card'>
<v-card-title class="text-h5">
{{ title }}
</v-card-title>
<v-form
>
<v-text-field
label='Наименование поставщика'
required
v-model='supplier.supplierName'
></v-text-field>
</v-form>
<v-card-actions>
<v-btn
#click='addSupplier'
>
Сохранить
</v-btn>
<v-spacer></v-spacer>
<v-btn
#click='cancelDialog'
>
Отмена
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import ...
export default {
props: ['dialog','title', 'supplier'],
model: {
prop: 'supplier',
event: 'change'
},
computed: {
supplierLocal: {
get: function () {
return this.supplier
},
set: function (value) {
this.$emit('change', value)
}
},
},
methods: {
cancelDialog() {
this.supplierLocal = {id: null, supplierName: null}
this.$emit('cancel')
},
addSupplier() {
if (this.supplierLocal.supplierName != null && this.supplierLocal.supplierName != '') {
if(this.supplierLocal.id == null) {
RestService.postSuppliers(this.supplierLocal).then((response) =>
{
this.$emit('save', response.data)
},
error => {
this.content =
(error.response && error.response.data && error.response.data.message) ||
error.message ||
error.toString();
this.isLoading = false;
if (error.response && error.response.status === 403) {
EventBus.dispatch("logout");
}
}
)
} else {
RestService.putSuppliers(this.supplierLocal).then((response) =>
{
this.$emit('save', response.data)
},
error => {
this.content =
(error.response && error.response.data && error.response.data.message) ||
error.message ||
error.toString();
this.isLoading = false;
if (error.response && error.response.status === 403) {
EventBus.dispatch("logout");
}
}
)
}
}
}
}
}
</script>
<style>
.dialog_card {
padding: 4% 10%;
}
</style>
[Vue warn]: 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: "dialog"
found in
---> <SupplierDialog> at src/components/main_container/letter_of_authorization/SupplierDialog.vue
<VList>
<VSelectList>
<VThemeProvider>
<VMenu>
<VAutocomplete>
<SupplierForm>
<VCard>
<CreateLetterOfAuthorization> at src/views/app/CreateLetterOfAuthorization.vue
<MainContainer> at src/components/main_container/MainContainer.vue
<VApp>
<Application> at src/views/app/Application.vue
<App> at src/App.vue
<Root>

How to correctly pass paramters to vuex mapActions

I have a ProjectPage.vue page that displays issues in a v-data-table. Projects are retrieved from a server api call in the sidebar and displayed there. After I choose a project, I would like to use that project's id to get its issues from the server. Is it possible to do so using Vuex.
Is it possible using Vuex, that each project can use the same .js file to retreive its issues, since there could be any number of projects.
What I am trying to do is from VerticalNavMenu.vue, pass an id and project as props to ProjectPage, so I can pass the id as a parameter to mapAction inside ProjectPage to retreive its issues.
The I'm way doing it now is not working. The table has no data available when I open a project's table
I hope Peoject_Pages.js helps understand what I'm asking about.
VerticalNavMenu.vue (related template lines are 38 -> 48)
<template>
<v-navigation-drawer
:value="isDrawerOpen"
app
expand-on-hover
mini-variant-width="60px"
floating
width="260"
class="app-navigation-menu"
:right="$vuetify.rtl"
#input="val => $emit('update:is-drawer-open', val)"
>
<!-- Navigation Header -->
<div class="vertical-nav-header d-flex items-center ps-6 pe-5 pt-5 pb-2">
<router-link to="/" class="d-flex align-center text-decoration-none">
<v-slide-x-transition>
<h2 class="app-title text--primary">ITrackerHub</h2>
</v-slide-x-transition>
</router-link>
</div>
<!-- Navigation Items -->
<v-list expand shaped class="vertical-nav-menu-items pr-5">
<nav-menu-link
title="Dashboard"
:to="{ name: 'dashboard' }"
:icon="icons.mdiHomeOutline"
></nav-menu-link>
<v-list>
<v-list-group :prepend-icon="icons.mdiTelevisionGuide">
<template v-slot:activator>
<v-list-item-content>
<v-list-item-title v-text="'My Projects'"></v-list-item-title>
</v-list-item-content>
</template>
<v-list-item v-for="(project, index) in ProjectList" :key="index">
<v-icon class="mx-2">{{ icons.mdiAccountGroup }}</v-icon>
<v-list-item-content>
<router-link
class="d-flex align-center text-decoration-none black--text"
:to="{ name: 'ProjectPage', params: { id: project.id, project} }"
>
{{ project.title }}
</router-link>
</v-list-item-content>
</v-list-item>
</v-list-group>
</v-list>
<nav-menu-link title="My Issues" :to="{ name: 'MyIssues' }" :icon="icons.mdiBookEditOutline"></nav-menu-link>
<nav-menu-link
style="position:relative; top:70px;"
title="Account Settings"
:to="{ name: 'pages-account-settings' }"
:icon="icons.mdiAccountCogOutline"
></nav-menu-link>
<nav-menu-link
style="position: relative; top: 200px"
title="Create Project"
:to="{ name: 'CreateProject' }"
:icon="icons.mdiPlusMinus"
></nav-menu-link>
</v-list>
</v-navigation-drawer>
</template>
<script>
// eslint-disable-next-line object-curly-newline
import {
mdiHomeOutline,
mdiAlphaTBoxOutline,
mdiEyeOutline,
mdiCreditCardOutline,
mdiTable,
mdiFileOutline,
mdiFormSelect,
mdiAccountCogOutline,
mdiAccountGroup,
mdiAccountMultiple,
mdiTelevisionGuide,
mdiBookEditOutline,
mdiPlusMinus,
} from '#mdi/js'
// import NavMenuSectionTitle from './components/NavMenuSectionTitle.vue'
import NavMenuGroup from './components/NavMenuGroup.vue'
import NavMenuLink from './components/NavMenuLink.vue'
import { mapGetters, mapActions } from 'vuex'
export default {
components: {
// NavMenuSectionTitle,
NavMenuGroup,
NavMenuLink,
},
computed: {
...mapGetters(['ProjectList'])
},
methods: {
...mapActions(['fetchProjects'])
},
created() {
// this.getProjectList()
this.fetchProjects()
},
props: {
isDrawerOpen: {
type: Boolean,
default: null,
},
},
setup() {
return {
icons: {
mdiHomeOutline,
mdiAlphaTBoxOutline,
mdiEyeOutline,
mdiCreditCardOutline,
mdiTable,
mdiFileOutline,
mdiFormSelect,
mdiAccountCogOutline,
mdiAccountGroup,
mdiAccountMultiple,
mdiTelevisionGuide,
mdiBookEditOutline,
mdiPlusMinus,
},
}
},
}
</script>
<style lang="scss" scoped>
.app-title {
font-size: 1.25rem;
font-weight: 700;
font-stretch: normal;
font-style: normal;
line-height: normal;
letter-spacing: 0.3px;
}
// ? Adjust this `translateX` value to keep logo in center when vertical nav menu is collapsed (Value depends on your logo)
.app-logo {
transition: all 0.18s ease-in-out;
.v-navigation-drawer--mini-variant & {
transform: translateX(-10px);
}
}
#include theme(app-navigation-menu) using ($material) {
background-color: map-deep-get($material, 'background');
}
.app-navigation-menu {
.v-list-item {
&.vertical-nav-menu-link {
::v-deep .v-list-item__icon {
.v-icon {
transition: none !important;
}
}
}
}
}
</style>
NavBar.js
import axios from 'axios'
const state = {
Projects: [],
}
const getters = {
ProjectList: (state) => state.Projects
}
const actions = {
async fetchProjects({ commit }) {
const response = await axios.get('https://fadiserver.herokuapp.com/api/v1/my-projects/')
commit('setProjects', response.data)
}
}
const mutations = {
setProjects: (state, Projects) => (state.Projects = Projects)
}
export default {
state,
getters,
actions,
mutations
}
ProjectPage.vue
<template>
<v-card>
<v-card-title class="text-center justify-center py-6">
<h1 class="font-weight-bold text-h2 basil--text">
{{ project.title }}
</h1>
</v-card-title>
<v-tabs v-model="tab" background-color="primary" dark centered>
<v-tab v-for="item in items" :key="item.tab">{{ item.tab }}</v-tab>
</v-tabs>
<v-tabs-items v-model="tab">
<v-tab-item v-for="item in items" :key="item.tab">
<v-card flat>
<v-card v-if="item.tab == 'Issues'">
<template>
<div class="text-center">
<v-dialog v-model="dialog" width="500">
<template v-slot:activator="{ on }">
<v-btn class="success" dark v-on="on">
<v-icon align-self: left>
mdi-plus-thick
</v-icon>
Add Issue
</v-btn>
</template>
<v-card>
<v-card-title>
<h2>Add Issue</h2>
</v-card-title>
<v-card-text>
<v-form class="px-3">
<v-text-field v-model="title" label="Title"></v-text-field>
<v-textarea v-model="description" label="Description"></v-textarea>
<v-select
item-text="text"
item-value="value"
:items="time_est"
v-model="time_estimate"
label="Time Estimate"
></v-select>
<v-select
item-text="title"
item-value="id"
:items="issueType"
v-model="issue_type"
label="Issue Type"
></v-select>
<v-select
item-text="title"
item-value="id"
v-model="issue_status"
label="Issue Status"
:items="issueStatus"
></v-select>
<v-select
item-text="title"
item-value="id"
:items="issueSeverity"
v-model="issue_severity"
label="Issue Severity"
></v-select>
<v-spacer></v-spacer>
<v-btn
flat
#click="
postIssue()
reloadPage()
"
class="success mx-0 mt-3"
>
<v-icon align-self:left>mdi-content-save-check-outline</v-icon> Save</v-btn
>
</v-form>
</v-card-text>
</v-card>
</v-dialog>
</div>
</template>
<v-card>
<v-data-table
:headers="headers"
:items="Project_Issues"
item-key="full_name"
class="table-rounded"
hide-default-footer
enable-sort
#click:row="handleClick"
>
</v-data-table>
</v-card>
</v-card>
</v-card>
</v-tab-item>
</v-tabs-items>
</v-card>
</template>
<script>
import axios from 'axios'
import { mapGetters, mapActions } from 'vuex'
export default {
props: ['id', 'project'],
computed: {
...mapGetters(['Project_Issues']),
},
data() {
return {
tab: null,
items: [{ tab: 'Issues' }, { tab: 'Calender' }, { tab: 'About' }],
title: '',
description: '',
time_estimate: '',
issue_type: '',
issue_status: '',
issue_severity: '',
time_est: [
{ value: '1', text: '1' },
{ value: '2', text: '2' },
{ value: '3', text: '3' },
{ value: '4', text: '4' },
{ value: '5', text: '5' },
{ value: '6', text: '6' },
{ value: '7', text: '7' },
{ value: '8', text: '8' },
],
}
},
setup() {
return {
headers: [
{ text: 'Title', value: 'title' },
{ text: 'Description', value: 'description' },
{ text: 'Estimate', value: 'time_estimate' },
{ text: 'Assignees', value: 'user' },
{ text: 'Type', value: 'issueType' },
{ text: 'Status', value: 'issueStatus' },
{ text: 'Severity', value: 'issueSeverity' },
],
}
},
methods: {
...mapActions(['fetchProjectIssueList']),
handleClick(issue) {
this.$router.push({
name: 'IssuePage',
params: { id: issue.id, issue },
})
},
postIssue() {
axios
.post('https://fadiserver.herokuapp.com/api/v1/my-issues/', {
title: this.title,
description: this.description,
time_estimate: this.time_estimate,
userid: 'f3260d22-8b5b-4c40-be1e-d93ba732c576',
projectid: this.id,
issueTypeId: this.issue_type,
issueStatusId: this.issue_status,
issueSeverityId: this.issue_severity,
})
.then(response => {
console.log(response)
})
.catch(error => {
console.log(error)
})
},
reloadPage() {
window.location.reload()
},
},
mounted() {
this.fetchProjectIssueList(this.id)
},
}
</script>
<style scoped>
.v-btn {
left: 43%;
}
</style>
Project_Page.js
import axios from 'axios'
const state = {
issuesList: [],
}
const getters = {
Project_Issues: (state) => state.issuesList
}
const actions = {
async fetchProjectIssueList({ commit }, {projectid}) {
const response = await axios.get('https://fadiserver.herokuapp.com/api/v1/my-issues-titles/?projectid=' + projectid)
commit('setProjectIssues', response.data)
},
}
const mutations = {
setProjectIssues: (state, issuesList) => (state.issuesList = issuesList)
}
export default {
state,
getters,
actions,
mutations
}
There are 2 ways of solving your problem.
Or you pass an object with the key projectid in the fetchProjectIssueList method
Or you do not need to destructure the object in the fetchProjectIssueList method
this.fetchProjectIssueList({ projectid: this.id })
...
async fetchProjectIssueList({ commit }, {projectid}) { ... }
or
this.fetchProjectIssueList(this.id)
...
async fetchProjectIssueList({ commit }, projectid) { ... }

Vuetify dialog component to correctly delete item from parent component

I have a delete dialog component that when clicking Cancel or Delete nothing happens, not even errors. What am I missing to correctly delete or cancel
<template>
<v-dialog
v-bind="$attrs"
v-on="$listeners"
max-width="500px"
persistent
>
<v-card>
<v-card-title
class="headline"
>
{{ title }}
</v-card-title>
<v-card-text>
{{ message }}
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
color="grey darken-1"
text
#click="$emit('closeDeleteDialog')"
>
Cancel
</v-btn>
<v-btn
color="primary darken-1"
text
#click="$emit('deleteItem')"
>
Delete
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
name: 'DeleteDialog',
props: {
title: {
type: String,
required: true
},
message: {
type: String,
default: ''
}
},
emits: ['closeDeleteDialog', 'deleteItem']
}
</script>
This is how I use my component:
<DeleteDialog
v-model="dialogDelete"
title="Delete location"
message="Are you sure you want to delete this location?"
/>
On the same view as where I import my component I have my methods.
export default {
components: {
DeleteDialog: () => import('#/components/Shared/DeleteDialog'),
},
data: () => ({
locationId: null,
dialog: false,
dialogDelete: false,
})
},
methods: {
deleteItem () {
this.$store.dispatch('deleteFirebaseDoc', { docId: this.locationId, collection: 'locations' })
this.locationId = null
this.dialogDelete = false
},
deleteItemConfirm (item) {
this.locationId = item.docId
},
closeDeleteDialog () {
this.dialogDelete = false
}
}
}
</script>
How can I correctly access my component to delete and item or cancel the dialog?
In the parent component you need to to listen to those events you emit
<DeleteDialog
v-model="dialogDelete"
title="Delete location"
message="Are you sure you want to delete this location?"
#close-delete-dialog="dialogDelete = false" // or call closeDeleteDialog ()
#delete-item="deleteItem()"
/>
I've never used camelCase for events emitting so I'd rather write it like $emit('close-delete-dialog') and $emit('delete-item')

Vue.js: Managing multiple buttons in Dropdown

I have the following Dropdown in a Vue.js Project.
<template>
<v-menu close-on-click transition="slide-y-transition">
<template v-slot:activator="{ on, attrs }">
<v-btn color="primary" v-bind="attrs" v-on="on">
Menu
</v-btn>
</template>
<v-list>
<v-list-item v-for="(item, index) in menuItemsMisc" :key="index">
<v-list-item-title>
<v-btn block color="white">{{ item.title }}</v-btn>
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</template>
<script>
export default {
name: 'MenuBar',
data: () => ({
menuItemsMisc: [
{ title: 'Visit Website' },
{ title: 'Logout' },
{ title: 'Purchase' },
]
}),
}
</script>
I want all these buttons to have different functions like:
Visit Website -> Link to a Website
Logout -> To Call a function
Purchase -> A Purchase Modal to appear
That's how I have been handling the drop-down buttons before using page routing.
<v-list-item v-for="(item, index) in menuItemsPages" :key="index">
<v-list-item-title>
<v-btn :to= "'/' + item.url" >{{ item.title }}</v-btn>
</v-list-item-title>
</v-list-item>
But I think Page routing isn't the best way to go if we have buttons Drastically different in functionality. How should I go about it?
(not vuetify)I am not sure if this is the best practice but you can try this:
In my router there is an About page and it works ! Also when we select other options I can see the outputs in console. If you can adapt this code into vuetify, it can work.
<template>
<div class="home">
<select v-model="selectedValue">
<template v-for="(item, index) in menuItemsMisc">
<option :value="item.title" :key="index"> {{item.title }} </option>
</template>
</select>
</div>
</template>
<script>
// # is an alias to /src
export default {
name: 'Home',
data() {
return {
selectedValue : "",
menuItemsMisc: [
{ title: 'Visit Website' },
{ title: 'Logout' },
{ title: 'Purchase' },
]
}
},
watch: {
"selectedValue": function() {
if (this.selectedValue === "Visit Website") {
this.$router.push({name: "About"})
}
else if (this.selectedValue === "Logout") {
this.doSomething()
}
else {
this.purchase()
}
}
},
methods: {
doSomething() {
console.log("I AM DOING SOMETHING")
},
purchase() {
console.log("hello purchase")
}
}
}
</script>
Another way is to define a function to the menuItemsMisc's elements, then pass it to #click of v-btn.
<template>
<v-menu close-on-click transition="slide-y-transition">
<template v-slot:activator="{ on, attrs }">
<v-btn color="primary" v-bind="attrs" v-on="on">Menu</v-btn>
</template>
<v-list>
<v-list-item v-for="(item, index) in menuItemsMisc" :key="index">
<v-list-item-title>
<!-- Pass `item.click` to `#click` -->
<v-btn block color="white" #click="item.click">{{ item.title }}</v-btn>
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</template>
export default {
name: "Home",
data: () => ({
menuItemsMisc: [
{
title: "Visit Website",
click: () => {
// Go to a route
this.$router.push({ name: "About" });
}
},
{
title: "Logout",
click: () => {
// Call a function
console.log("Logging out...");
}
},
{
title: "Purchase",
click: () => {
// Show modal
console.log("Showing purchase modal");
}
}
]
})
};
Here is a sample demo.
You can add a function to each object in your array like so :
menuItemsMisc: [
{ title: 'Visit Website', fn: () => { this.$router.push('/') }},
{ title: 'Logout' , fn: () => { /* Your logic */ }},
{ title: 'Purchase' , fn: () => { /* different logic */ }},
]
and use it with an event listener on click :
<v-btn #click="item.fn" >{{ item.title }}</v-btn>
A combination of both answers worked for me!
data: () => ({
menuItemsMisc: [
{ title: "Vorlage speichern" },
{ title: "Anwesenheit bearbeiten" },
],
)}
watch: {
selectedValue: function () {
if (this.selectedValue === "Vorlage speichern") {
this.saveAsVorlage(this.topListWithName);
} else if (this.selectedValue === "Anwesenheit bearbeiten") {
this.updateAnwesenheit = true;
}
},
},

Two vue components are sharing instance

I have a component named concepts, with its data and methods.
In another view I have two instance of this component (I'm using vuetify too):
// Index.vue
<template>
<v-card>
<v-toolbar>
<v-toolbar-title>Conceptos</v-toolbar-title>
</v-toolbar>
<v-tabs class="elevation-2" color="primary">
<v-tab>Ordinarios</v-tab>
<v-tab>Extraordinarios</v-tab>
<v-tab-item :key="1">
<concepts :is_extra="false" :key="1"></concepts>
</v-tab-item>
<v-tab-item :key="2">
<concepts :is_extra="true" :key="2"></concepts>
</v-tab-item>
</v-tabs>
</v-card>
</template>
<script>
import concepts from './Concepts.vue'
export default {
components: {
concepts,
},
data() {
return {
}
}
}
</script>
In the concepts component the is_extra property is used as a parameter, In the method created() of the vue instance, the function fetchAll retrieve from API the data correctly, the param is sent with the correct value:
// Concepts.vue
// this works correctly
created() {
const isExtra = this.is_extra //true or false depends of the property value
// GET /concepts?is_extra=true ...or false
this.server
.setParams({ is_extra: isExtra })
.fetchAll()
}
I have a method named upload (to upload a file) where I use this property again, but the value always is false, Always of the first component
...
// Concepts.vue
methods: {
upload() {
const isExtra = this.is_extra // always false
this.server.setParams({is_extra: isExtra}).upload()
}
}
The full Concepts.vue component here:
<template>
<v-data-table
:divider="true"
:headers="headers"
:items="concepts">
<template v-slot:top>
<v-toolbar flat color="white">
<v-spacer></v-spacer>
<template v-if="me.caps.canUploadCsv()">
<v-btn color="secondary" class="mr-2" dark>
Agregar concepto
</v-btn>
<input type="file" class="d-none" id="csv_file" #change="confirm" accept=".csv">
<v-btn #click="selectFile" :loading="uploading" color="green" dark>
<v-icon left>mdi-file-upload</v-icon> Cargar CSV
</v-btn>
</template>
</v-toolbar>
<v-dialog v-model="confirmDialog"
#click:outside="closeConfirm" #keydown="closeConfirm" max-width="320">
<v-card>
<v-card-title>
<span class="headline">Confirmar</span>
</v-card-title>
<v-card-text>
¿Desea continuar con la carga del archivo?
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text #click="closeConfirm">
No
</v-btn>
<v-btn color="primary" dark #click="uploadFile">Si</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<template v-slot:item.description="{ item }">
<span :title="item.description">
{{ item.description.substr(0, 60) }}...
</span>
</template>
<template v-slot:item.amount_formated="{ item }">
$ {{ item.amount_formated }}
</template>
<template v-slot:item.iva_formated="{ item }">
$ {{ item.iva_formated }}
</template>
<template v-slot:item.total_formated="{ item }">
$ {{ item.total_formated }}
</template>
</v-data-table>
</template>
<script>
import { bus } from '../../app'
import User from '../../models/User'
import Concept from '../../models/Concept'
export default {
props: ['is_extra'],
data() {
return {
me: null,
headers: [
{ text: 'Clave', value: 'code' },
{ text: 'Categoría', value: 'category' },
{ text: 'Descripción', value: 'description' },
{ text: 'Unidad', value: 'unity' },
{ text: 'Total', value: 'total_formated', align: 'end' },
],
concepts: [],
model: null,
loaded: false,
inputFile: null,
confirmDialog: false,
uploading: false,
}
},
created() {
this.me = (new User()).getMe()
const params = this.$route.params
this.model = new Concept(params.companyId, params.contractId)
const isExtra = this.is_extra
this.model.server
.setParams({ is_extra: isExtra })
.fetchAll(this.onSuccess)
},
mounted() {
this.inputFile = document.getElementById('csv_file')
console.log('1', this)
},
methods: {
selectFile() {
if (this.uploading) {
return
}
this.inputFile.click()
},
confirm() {
this.confirmDialog = true
},
closeConfirm() {
this.inputFile.value = null
this.confirmDialog = false
},
uploadFile() {
console.log(this)
this.confirmDialog = false
this.uploading = true
const isExtra = this.is_extra
bus.$emit('snackbar', { color: 'info', text: 'Subiendo archivo...' })
this.model.server
.setParams({ is_extra: isExtra })
.upload(null, 'csv', this.inputFile, this.onSuccess)
},
onSuccess(resp) {
if (this.uploading) {
bus.$emit('snackbar', {
color: 'success', text: 'Conceptos actualizados correctamente'
})
}
if (this.inputFile) {
this.inputFile.value = null
}
this.uploading = false
this.concepts = resp.data
},
}
}
</script>
I put a console.log(this) inside of the created and upload methods, when I change the tab (vuetify tabs), in the first case I get:
VueComponent {_uid: 43, _isVue: true, ...} // created(), is_extra=false
VueComponent {_uid: 71, _isVue: true, ...} // created(), is_extra=true
but in the upload method (when upload the file) I get the same instance:
VueComponent {_uid: 43, _isVue: true, ...} // upload() is_extra=false
VueComponent {_uid: 43, _isVue: true, ...} // upload() is_extra=false
I added :key property but it didn't work.
Vue devtools show the two components with its correct data.
Before using vuetify tabs I already had the issue
Can someone help me please?

Categories