Quasar: How to give props in q-table to parent component? - vue.js

I have created TableTop5 component using q-table, so that I can reuse it in VisitorSummary.vue, but I have no clue how to get props which should be written in v-slot:header and v-slot:body.
VisitorSummary.vue
...
<div class="row card-wrap row-sec">
<table-top-5 title="검색어 TOP5" titleWord="검색어 TOP5"
:data="data" :columns="columns" :rowFirst="rank" // <-- it gives me errors..
:rowSec="keyword" :rowThird="visitor" :props="props" // <-- i can feel it isn't right..
>
</table-top-5>
</div>
...
<script lang="ts">
import Vue from 'vue';
import axios from 'axios';
import VueApexCharts from 'vue-apexcharts'
import NumberCount from 'components/card/NumberCount.vue'
import NumberCountSet from 'components/card/NumberCountSet.vue';
import TitleWithTooltip from 'components/card/TitleWithTooltip.vue'
import TableTop5 from 'components/table/TableTop5'
Vue.use(VueApexCharts)
Vue.component('apexchart', VueApexCharts)
export default Vue.extend({
components: {
NumberCountSet,
NumberCount,
TitleWithTooltip,
TableTop5,
},
data() {
return {
columns: [
{
name: 'rank',
required: true,
label: '순위',
align: 'center',
field: row => row.rank,
format: val => `${val}`,
sortable: true,
style: 'width: 10%;'
},
{ name: 'keyword', align: 'left', label: '검색어', field: 'keyword', sortable: true,
style: 'width: 70%; background:RGBA(0,178,45,0.05) ' },
{ name: 'visitor', align: 'center', label: '방문자수', field: 'visitor', sortable: true, style: 'width: 20%' },
],
data: [
{
rank: 1,
keyword: 'slimplanet',
visitor: 25,
},
{
rank: 2,
keyword: 'test',
visitor: 58,
},
{
rank: 3,
keyword: 'test',
visitor: 64,
},
{
rank: 4,
keyword: 'Slimplanet',
visitor: 72,
},
{
rank: 5,
keyword: 'test',
visitor: 18,
},
],
};
},
});
</script>
TableTop5.vue
<template>
<q-card class="card-item">
<q-card-section class="q-pa-none">
<div>
<q-table
:data="data"
:columns="columns"
row-key="name"
class="no-shadow q-pb-md q-px-md"
:hide-pagination="true"
>
<template v-slot:header="props">
<q-tr :props="props">
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
style="font-weight: bold"
>
{{ col.label }}
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td key="rowFirst" :props="props">
{{ rowFirst }}
</q-td>
<q-td key="rowSec" :props="props">
{{ props.row.rowSec }}
</q-td>
<q-td key="rowThird" :props="props">
<q-badge color="black" outline>
{{ props.row.rowThird }}
</q-badge>
</q-td>
</q-tr>
</template>
</q-table>
</div>
</q-card-section>
</q-card>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
name: 'TableTop5',
props: {
title: {
type: String
},
titleWord: {
type: String
},
data: {
type: Array
},
columns: {
type: Array
},
rowFirst: {
type: String
},
rowSec: {
type: String
},
rowThird: {
type: String
},
props: {
type: Object
}
}
});
</script>
Or should i just not reuse and write q-table whenever I need it in a row?

This can be done in multiple ways. You can define it like this where the parent component takes care of rendering rows and columns. You can also define some columns that every table needs to have and have a slot for extra columns.
You could also iterate over the columns so you render them dynamically.
Either way, the advantage to this approach, is consistency and reusability, in case you have some complex logic inside the table for filtering and other stuff.
TableTop5
<template>
<q-table :data="data" :columns="columns">
<template #body="props">
<slot v-bind="props" name="columns"></slot>
</template>
</q-table>
</template>
<script>
export default {
name: 'TableTop5',
props: {
data: {
type: Array,
},
columns: {
type: Array,
},
},
};
</script>
VisitorSummary
<template>
<div>
...
<table-top5 :data="data" :columns="columns">
<template #columns="props">
<q-tr>
<q-td key="c1">
{{ props.row.c1 }}
</q-td>
<q-td key="c2">
{{ props.row.c2 }}
</q-td>
</q-tr>
</template>
</table-top5>
...
</div>
</template>
<script>
import TableTop5 from '#/TableTop5.vue';
export default {
name: 'VisitorSummary',
components: {
TableTop5,
},
data() {
return {
columns: [
{
name: 'c1',
label: 'Col 1',
field: 'c1',
align: 'left',
},
{
name: 'c1',
label: 'Col 2',
field: 'c2',
align: 'left',
},
],
data: [
{
c1: 'C1 data - row 1',
c2: 'C2 data - row 1',
},
{
c1: 'C1 data - row 2',
c2: 'C2 data - row 2',
},
],
};
},
};
</script>

Related

Child-modal is empty on every second click

I am using row-selected on Bootstrap table, so that when a user select a row in a table, the data of that table is passed to a new object which is then sent as a prop to a child component, which is a modal.
The issue is that when I first click on a row, the modal opens and shows the data correctly, but if I close the modal and click the same row again, the data object is empty and nothing gets shown. I need to click it again for a third time to see the data. Then if I click a fourth time, the data is gone again.
I don't understand it, because in my showModal method, I check first to see if selectedRows are empty or not, so it shouldn't open if there was no data.
Any idea why this happens?
Parent:
<template>
<b-container>
<b-card class="mt-4 mb-4">
<h5>{{ $t('errorLogs.errorLog') }}</h5>
<b-table
:items="completedTasks"
:fields="fields"
:per-page="[10, 25, 50]"
selectable
:select-mode="'single'"
#row-selected="onRowSelected"
#row-clicked="showModal"
include-actions
sort-desc
/>
</b-card>
<error-log-entry-modal ref="errorLogEntryModal" :selected-error-log="selectedRows"/>
</b-container>
</template>
<script>
import ErrorLogEntryModal from '#/components/error-log/ErrorLogEntryModal';
export default {
components: {
ErrorLogEntryModal,
},
data() {
return {
errors: null,
tasksCompleted: null,
selectedRows: []
};
},
computed: {
fields() {
return [
{
key: 'done',
label: '',
thStyle: 'width: 1%',
template: {
type: 'checkbox',
includeCheckAllCheckbox: true,
},
},
{
key: 'priority',
label: this.$t('errorLogs.priority'),
formatter: type => this.$t(`model.errors.types.${type}`),
sortable: true,
},
{
key: 'creationDateTime',
label: this.$t('creationDateTime'),
formatter: date => moment(date).locale(this.$i18n.locale).format('L'),
sortable: true,
},
{
key: 'stackTraceShort',
label: this.$t('errorLogs.stackTrace'),
sortable: true,
},
{
key: 'message',
label: this.$t('message'),
sortable: true
},
]
},
completedTasks(){
if(this.errors){
return this.errors.filter(log => log.done)
}
},
},
methods: {
load(){
errorService.getErrorLogs().then(result => {
result.data.forEach(log => log.stackTraceShort = log.stackTrace.substring(0,30));
this.errors = result.data
})
},
submit(){
this.$bvModalExt
.msgBoxYesNo(
this.$t('archiveErrorLog'),
{
title: this.$t('pleaseConfirm'),
okVariant: 'success'
}
).then(value => {
if (value) {
errorService.updateStatusOnErrorEntryLog(this.errors)
}
})
},
onRowSelected(fields){
this.selectedRows = fields
},
showModal(){
if (this.selectedRows) {
this.$refs.errorLogEntryModal.show()
}
},
},
created() {
this.load()
},
};
</script>
child:
<template>
<b-modal
modal-class="error-log-modal"
v-model="showModal"
ok-only
size="xl">
<b-row class="lg-12" v-for="log in selectedErrorLog">
<b-col class="ml-2 mr-2 mb-4">
<h4>{{ $t('errorLogs.errorMessage') }}</h4>
{{ log.message }}
</b-col>
</b-row>
<b-row class="lg-12">
<b-col class="ml-2 mr-2"><h4>{{ $t('errorLogs.stackTrace') }}</h4></b-col>
</b-row>
<b-row class="lg-12" v-for="log in selectedErrorLog">
<b-col class="ml-2 mr-2" style="word-break: break-word; background-color: #F5C9C1;">
{{ log.stackTrace }}
</b-col>
</b-row>
</b-modal>
</template>
<script>
export default {
props: {
selectedErrorLog: Array
},
data() {
return {
showModal: false,
};
},
methods: {
show(){
this.showModal = true
},
}
};
</script>

How to customize v-select in vue 2, options with icons or images

How to customize a select or another component like this design. I tried with v-select, BFormSelect, and others, the options are images or icons, and the output-
{
languages: {
en: {
title: "the text writted",
},
},
logo: "this logo in base64"
}
<template>
<div>
<div>
<b-form
ref="form"
class="repeater-form p-2"
#submit.prevent="repeateAgain"
>
<div>
<v-select
v-model="selected"
:options="options"
:multiple="false"
item-text="text"
item-value="value"
label="Select options"
>
<template slot="option" slot-scope="option">
<img :src="option.src" class="mr-2" style="width: 20px; height: 20px;"/>
{{ option.text }}
</template>
</v-select>
</div>
</b-form>
</div>
</div>
</template>
<script>
import {
BForm, BFormGroup, BFormInput, BRow, BCol, BButton, BFormCheckbox, BFormSelect, BDropdown
} from 'bootstrap-vue'
import vSelect from 'vue-select'
export default {
components: {
BForm,
BRow,
BCol,
BButton,
BFormGroup,
BFormInput,
BFormCheckbox,
BFormSelect,
vSelect,
BDropdown,
},
data() {
const typeOptions = ['include', 'not_include', 'important']
return {
typeOptions,
selected: [],
options: [
{
value: null,
text: "Please select some item",
src: ""
{
value: "b",
text: "Default Selected Option",
src: "../../../../assets/images/trip_icons/almuerzo.png"
},
{
value: "c",
text: "This is another option",
src: "../../../../assets/images/trip_icons/almuerzo.png"
},
{
value: "d",
text: "This one is disabled",
disabled: true,
src: "https://mdn.mozillademos.org/files/7693/catfront.png"
}
]
}
},
methods: {
select(option) {
console.log(option);
this.selected = option;
},
},
}
</script>
This is the result now but it is not enough, I hope to customize more.

Vuejs 3 using i18n translation inside "script setup>

I have a Laravel + Inertiajs + Vue 3 with Vite
I user Quasar framework so in one vue file I have a Quasar table.
Now I need to translate the column labels.
<template>
<head title="Elenco utenti"></head>
<AuthenticatedLayout>
<template #header>
<div>
<q-toolbar class="q-gutter-sm">
<Link :href="route('list-utenti')" type="button" class="float-right"><q-btn flat round dense icon="people" /></Link>
<div class="text-subtitle1">{{ $t('utenti.pagetitle')}}</div>
</q-toolbar>
</div>
</template>
<div class="q-py-md q-gutter-sm">
<Link :href="route('register')" type="button" class="float-right"><q-btn icon="add_box" color="white" text-color="text-grey-7" :label="$t('utenti.btn_new')"></q-btn></Link>
</div>
<div class="q-py-xl">
<q-table
title=""
:rows="users"
:columns="columns"
row-key="id"
>
<template v-slot:body-cell-actions="props">
<q-td :props="props">
<q-btn icon="mode_edit" #click="onEdit(props.row)"></q-btn>
<q-btn icon="person_off" #click="onDelete(props.row)"></q-btn>
</q-td>
</template>
<template v-slot:body-cell-email="props">
<q-td :props="props">
<strong>{{ props.value }} </strong>
</q-td>
</template>
</q-table>
</div>
</AuthenticatedLayout>
</template>
As you can see in the template code, I configured i18n and I can correctly translate with {{ $t('utenti.pagetitle') }} when in template.
now this is the script part
<script setup>
import AuthenticatedLayout from '#/Layouts/AuthenticatedLayout.vue';
import { Inertia } from '#inertiajs/inertia';
import { Link } from '#inertiajs/inertia-vue3';
import { computed, ref} from '#vue/reactivity';
import { Quasar, useQuasar } from 'quasar';
const props = defineProps({
page_description : String,
title: String,
numero_record: Number,
users: Array
});
const columns = [
{
name: 'id',
required: true,
label: 'id',
align: 'left',
field: row => row.id,
format: val => `${val}`,
sortable: false
},
{ name: 'name', align: 'left', label: translated_name, field: 'name', sortable: true},
{ name: 'email', align: 'left', label: 'e-mail', field: 'email', sortable: false},
{ name: 'ruolo', align: 'left', label: 'ruolo', field: 'ruolo', sortable: true},
{ name: 'listino_grossista_visible', align: 'center', label: 'list. grossista', field: 'listino_grossista_visible', sortable: true},
{ name: 'enabled', align: 'center', label: 'Attivo', field: 'enabled', sortable: true},
{ name: 'actions', align: 'center', label: 'Azioni'}
]
const onEdit = (row) => {
Inertia.post(route('edit-utente'),
{ row } ,
{ onBefore: () => confirm('Vuoi veramente modificarlo?')}
)
}
const onDelete = (row) => {
alert('DELETE ' + row.email);
}
</script>
I would like to use in some way $t to get the correct translation for the grid columns labels (for example 'translated_name' but I don' understand how I can do it as it is in the script part and I can not use same as in template.
I probably understood that I need to add some computed properties but I didn't exactly figured out how. Consider I'm using composition api.
const translated_name = computed(() => {
return $t('utenti.nome')
})
I checked also this link Vue-i18n not translating inside component script tags but I didn't understood how to adapt to my case.
kind regards,
Matt

Ant Design Vue3 Editable Table Value Does Not Change

I have used the table component from the ant design, and right now I'm stuck on why this cannot work,
The reference link is:
https://2x.antdv.com/components/table-cn#components-table-demo-edit-cell
Here's the source code from the ant design about editable table:
<template>
<a-table :columns="columns" :data-source="dataSource" bordered>
<template v-for="col in ['name', 'age', 'address']" #[col]="{ text, record }" :key="col">
<div>
<a-input
v-if="editableData[record.key]"
v-model:value="editableData[record.key][col]"
style="margin: -5px 0"
/>
<template v-else>
{{ text }}
</template>
</div>
</template>
<template #operation="{ record }">
<div class="editable-row-operations">
<span v-if="editableData[record.key]">
<a #click="save(record.key)">Save</a>
<a-popconfirm title="Sure to cancel?" #confirm="cancel(record.key)">
<a>Cancel</a>
</a-popconfirm>
</span>
<span v-else>
<a #click="edit(record.key)">Edit</a>
</span>
</div>
</template>
</a-table>
</template>
<script lang="ts">
import { cloneDeep } from 'lodash-es';
import { defineComponent, reactive, ref, UnwrapRef } from 'vue';
const columns = [
{
title: 'name',
dataIndex: 'name',
width: '25%',
slots: { customRender: 'name' },
},
{
title: 'age',
dataIndex: 'age',
width: '15%',
slots: { customRender: 'age' },
},
{
title: 'address',
dataIndex: 'address',
width: '40%',
slots: { customRender: 'address' },
},
{
title: 'operation',
dataIndex: 'operation',
slots: { customRender: 'operation' },
},
];
interface DataItem {
key: string;
name: string;
age: number;
address: string;
}
const data: DataItem[] = [];
for (let i = 0; i < 100; i++) {
data.push({
key: i.toString(),
name: `Edrward ${i}`,
age: 32,
address: `London Park no. ${i}`,
});
}
export default defineComponent({
setup() {
const dataSource = ref(data);
const editableData: UnwrapRef<Record<string, DataItem>> = reactive({});
const edit = (key: string) => {
editableData[key] = cloneDeep(dataSource.value.filter(item => key === item.key)[0]);
};
const save = (key: string) => {
Object.assign(dataSource.value.filter(item => key === item.key)[0], editableData[key]);
delete editableData[key];
};
const cancel = (key: string) => {
delete editableData[key];
};
return {
dataSource,
columns,
editingKey: '',
editableData,
edit,
save,
cancel,
};
},
});
</script>
<style scoped>
.editable-row-operations a {
margin-right: 8px;
}
</style>
However, I have the functionality that my name will be a hyperlink, so I need to put the name column and its value outside the v-for. Here's what I got:
<template>
<a-table :columns="columns" :data-source="dataSource" bordered>
<template v-for="col in ['age', 'address']" #[col]="{ text, record }" :key="col">
<div>
<a-input
v-if="editableData[record.key]"
v-model:value="editableData[record.key][col]"
style="margin: -5px 0"
/>
<template v-else>
{{ text }}
</template>
</div>
<template #name="{ text, record }">
<div>
<a-input v-if="editableData[record.key]" v-model:value="editableData[record.key][record.name]" style="margin:
-5px 0"></a-input>
<router-link v-else="v-else" to="/tables/123">{{ text }}</router-link>
</div>
</template>
</template>
<template #operation="{ record }">
<div class="editable-row-operations">
<span v-if="editableData[record.key]">
<a #click="save(record.key)">Save</a>
<a-popconfirm title="Sure to cancel?" #confirm="cancel(record.key)">
<a>Cancel</a>
</a-popconfirm>
</span>
<span v-else>
<a #click="edit(record.key)">Edit</a>
</span>
</div>
</template>
</a-table>
</template>
<script lang="ts">
import { cloneDeep } from 'lodash-es';
import { defineComponent, reactive, ref, UnwrapRef } from 'vue';
const columns = [
{
title: 'name',
dataIndex: 'name',
width: '25%',
slots: { customRender: 'name' },
},
{
title: 'age',
dataIndex: 'age',
width: '15%',
slots: { customRender: 'age' },
},
{
title: 'address',
dataIndex: 'address',
width: '40%',
slots: { customRender: 'address' },
},
{
title: 'operation',
dataIndex: 'operation',
slots: { customRender: 'operation' },
},
];
interface DataItem {
key: string;
name: string;
age: number;
address: string;
}
const data: DataItem[] = [];
for (let i = 0; i < 100; i++) {
data.push({
key: i.toString(),
name: `Edrward ${i}`,
age: 32,
address: `London Park no. ${i}`,
});
}
export default defineComponent({
setup() {
const dataSource = ref(data);
const editableData: UnwrapRef<Record<string, DataItem>> = reactive({});
const edit = (key: string) => {
editableData[key] = cloneDeep(dataSource.value.filter(item => key === item.key)[0]);
};
const save = (key: string) => {
Object.assign(dataSource.value.filter(item => key === item.key)[0], editableData[key]);
delete editableData[key];
};
const cancel = (key: string) => {
delete editableData[key];
};
return {
dataSource,
columns,
editingKey: '',
editableData,
edit,
save,
cancel,
};
},
});
</script>
<style scoped>
.editable-row-operations a {
margin-right: 8px;
}
</style>
However, when I changed my name through the editing mode, it seems that the name will not change based on the edited name. Is there anything I'm wrong? Any help will be greatly appreciated!
Ok, I finally got the answer - should be using a editableData[record.key].name when we are using v-model instead of editableData[record.key][record.name]

How to make a recursive menu using Quasar QexpansionItem

I want create a component that it can to scale with a nested object structure using the QExpansionItem from Quasar Framework.
I made a recursive component to try achieve this but doesn't shows like i hope. The items are repeated in a wrong way and I don't know why.
I am using Quasar V1.0.5, the component that i used QexpansionItem
Here the menu object
[
{
name: '1',
icon: 'settings',
permission: 'configuration',
description: '1',
url: '',
children: [
{
name: '1.1',
permission: 'configuration',
url: '/insuranceTypes',
icon: 'add',
description: '1.1'
},
{
name: '1.2',
permission: 'configuration',
url: '/insuranceTypes2',
icon: 'phone',
description: '1.2'
}
]
}, {
name: '2',
icon: 'person',
permission: 'configuration',
url: 'contacts',
description: '2'
}
]
MenuComponent.vue where i call side-tree-menu component
<q-list
bordered
class="rounded-borders q-pt-md"
>
<side-tree-menu :menu="menu"></side-tree-menu>
</q-list>
SideTreeMenuComponent.vue
<template>
<div>
<q-expansion-item
expand-separator
:icon="item.icon"
:label="item.name"
:caption="item.description"
header-class="text-primary"
:key="item.name"
:to="item.url"
v-for="(item) in menu"
>
<template>
<side
v-for="(subitem) in item.children"
:key="subitem.name"
:menu="item.children"
>
</side>
</template>
</q-expansion-item>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'side',
props: ['menu', 'children'],
data () {
return {
isOpen: false,
algo: 0
}
},
mounted () {
console.log('menu', this.menu)
},
computed: {
...mapGetters('generals', ['can'])
}
}
</script>
The elements 1.1 and 1.2 are repeated and I don't know fix it
I got stuck at the same problem and did not find any solution online. I managed to get it working with the below approach. This could be helpful for someone in the future :)
I am adding here the 2 most important code files that will get this working. Rest of my setup is nothing more than what is created by the quasar create [project-name] CLI command.
When you create the project with the above command, you get the MainLayout.vue and EssentialLink.vue file. I have modified those to achieve the required result.
**My MainLayout.vue file - the template **
EssentialLink below is the component that renders the menu recursively using q-expansion-item inside the drawer on the main layout page.
<template>
<q-layout view="hHh Lpr lFf">
<q-header elevated>
<q-toolbar>
<q-btn flat dense round icon="menu" aria-label="Menu"
#click="leftDrawerOpen = !leftDrawerOpen" />
<q-toolbar-title>
{{appTitle}}
</q-toolbar-title>
<div>Release {{ appVersion }}</div>
</q-toolbar>
</q-header>
<q-drawer
v-model="leftDrawerOpen" show-if-above bordered
content-class="bg-grey-1">
<q-list>
<q-item-label
header
class="text-grey-8">
Essential Links
</q-item-label>
<EssentialLink
v-for="link in essentialLinks"
:key="link.title"
v-bind="link">
</EssentialLink>
</q-list>
</q-drawer>
<q-page-container>
<router-view />
</q-page-container>
</q-layout>
</template>
script section of MainLayout.vue file. Key properties to note - children and level.
<script>
import EssentialLink from 'components/EssentialLink.vue'
export default {
name: 'MainLayout',
components: {
EssentialLink
},
data () {
return {
appTitle: 'Project Name',appVersion: 'v0.1',leftDrawerOpen: false,
essentialLinks: [
{
title: 'Search', caption: 'quasar.dev', icon: 'school',
link: 'https://quasar.dev',
level: 0,
children: [{
title: 'Documents', caption: 'quasar.dev',icon: 'school',
link: 'https://quasar.dev',
level: 1,
children: [{
title: 'Search (level 3)',
caption: 'quasar.dev',
icon: 'school',
link: 'https://quasar.dev',
level: 2,
children: []
}]
}]
},
{
title: 'Github',caption: 'github.com/quasarframework',
icon: 'code',link: 'https://github.com/quasarframework',
level: 0,
children: [{
title: 'Github Level 2',caption: 'quasar.dev',icon: 'school',
link: 'https://quasar.dev',level: 1,
children: []
}]
},
{
title: 'Forum',caption: 'forum.quasar.dev',
icon: 'record_voice_over',link: 'https://forum.quasar.dev',
level: 0,
children: [{
title: 'Forum Level 2',caption: 'quasar.dev',icon: 'school',
link: 'https://quasar.dev',
level: 1,
children: []
}]
}
]
}
}
}
</script>
Finally the EssentialLink.vue component
The code below recursively calls itself when it encounters more than 1 item in its children property. The level property is used to indent the menus as you drill down.
<template>
<div>
<div v-if="children.length == 0">
<q-item clickable v-ripple :inset-level="level">
<q-item-section>{{title}}</q-item-section>
</q-item>
</div>
<div v-else>
<div v-if="children.length > 0">
<!-- {{children}} -->
<q-expansion-item
expand-separator
icon="mail"
:label="title"
:caption="caption"
:header-inset-level="level"
default-closed>
<EssentialLink
v-for="child in children"
:key="child"
v-bind="child">
</EssentialLink>
</q-expansion-item>
</div>
<div v-else>
<q-item clickable v-ripple :inset-level="level">
<q-item-section>{{title}}</q-item-section>
</q-item>
</div>
</div>
</div>
</template>
*script section of the EssentialLink.vue component
<script>
export default {
name: 'EssentialLink',
props: {
title: {
type: String,
required: true
},
caption: {
type: String,
default: ''
},
link: {
type: String,
default: '#'
},
icon: {
type: String,
default: ''
},
level: {
type: String,
default: ''
},
children: []
}
}
</script>
Final output looks like this (image)