I am trying to create an interface for a support ticket system in my Vue 3 / Quasar / TypeScript app.
It consists of tickets and message threads.
When viewing a single ticket I want to display each thread of the ticket as a q-expansion-item
. The expansion item shows a preview of the thread, and when clicked the preview disappears and the full message thread is shown.
I have a working component for this. The problem is that all thread previews disappear when opening a q-expansion-item
. But I only want the thread preview of the q-expansion-item
that was clicked to disappear.
Any idea how I can fix this?
This is what it looks like before opening:
This is what it looks like after opening. Note that ALL thread previews have gone, but I only want the preview for the top thread that was clicked to disappear:
I tried to create a minimal reproduction in codepen.io and codesandbox.io but I couldn't get it working with Vue 3 / Quasar / TypeScript.
So instead I have pasted the code below.
<template>
<q-card flat style="max-width: 1200px">
<q-card-section>
<q-list bordered >
<div v-for="(thread, index) in threads" :key="thread.threadId">
<q-expansion-item
clickable
:content-inset-level="1"
@update:model-value="handleOpenClose"
>
<template #header>
<q-item-section top>
<q-item-label lines="1">
<span >
{{ thread.author.name }}
</span>
<span >
{{
date.formatDate(
new Date(thread.createdAt.seconds * 1000),
'Do MMMM hh:mmA'
)
}}
</span>
</q-item-label>
<q-item-label v-if="!isOpen" caption lines="1">
{{ stripHTML(thread.content) }}
</q-item-label>
</q-item-section>
</template>
<template #default>
<q-card>
<!-- eslint-disable-next-line vue/no-v-html -->
<q-card-section v-html="thread.content" />
</q-card>
</template>
</q-expansion-item>
<q-separator v-if="threads && index != threads?.length - 1" />
</div>
</q-list>
</q-card-section>
</q-card>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { date } from 'quasar';
// Fake Data
const threads = ref([
{
threadId: '6bXb0tfCZWoNVfFEztIS',
ticketId: '9rgz013ahc2Aqx9C2UxG',
author: {
name: 'Ben Bob',
firstName: 'Ben',
lastName: 'Bob',
photoURL: '__vue_devtool_undefined__',
type: 'END_USER',
},
createdAt: { seconds: 1656573148, nanoseconds: 38000000 },
content: 'The first thread',
},
{
threadId: 'MTM4qvzcRKQ2eZ2crpQB',
ticketId: '9rgz013ahc2Aqx9C2UxG',
author: {
name: 'Ben Bob',
firstName: 'Ben',
lastName: 'Bob',
photoURL: '__vue_devtool_undefined__',
type: 'END_USER',
},
createdAt: { seconds: 1656573666, nanoseconds: 250000000 },
content: 'The second thread',
},
{
threadId: 'Q9xf9PFmTzs6X4LYSXXh',
ticketId: '9rgz013ahc2Aqx9C2UxG',
author: {
name: 'Ben Bob',
firstName: 'Ben',
lastName: 'Bob',
photoURL: null,
type: 'END_USER',
},
createdAt: { seconds: 1656573990, nanoseconds: 262000000 },
content: 'The third thread',
},
]);
// Normal vue script code
const isOpen = ref(false);
const handleOpenClose = (isShowing: boolean) => {
isOpen.value = isShowing;
};
const stripHTML = (html: string) => {
let doc = new DOMParser().parseFromString(html, 'text/html');
doc.body
.querySelectorAll('br, li, div') // Get all <br>, <li>, and <div> elements
.forEach((br) => br.after(doc.createTextNode(' '))); // And add spaces after them
return doc.body.textContent || '';
};
</script>
CodePudding user response:
I'm not too familiar with Quasar, but it looks like your issue is that you're using <q-item-label v-if="!isOpen" caption lines="1">
, which will always resolve true when something is open. You want to check that isOpen.value
exists and equals the value of the model (which I think would be the threadId
).
EDIT - added suggested answer (just a guess!):
Swap
@update:model-value="handleOpenClose"
with
@update:model-value="handleOpenClose(thread.threadId)"
and then swap
const handleOpenClose = (isShowing: boolean) => {
isOpen.value = isShowing;
};
with
const handleOpenClose = (threadId) => {
isOpen = threadId;
};
then swap
<q-item-label v-if="!isOpen" caption lines="1">
with
<q-item-label v-if="isOpen !== thread.threadId" caption lines="1">