<template> <DocSectionText v-bind="$attrs"> <p>Place the Popover outside of the data iteration components to avoid rendering it multiple times.</p> </DocSectionText> <div class="card"> <DataTable :value="products" :rows="5" paginator tableStyle="min-width: 50rem"> <Column field="id" header="Id" class="w-1/6"></Column> <Column field="code" header="Code" class="w-1/6"></Column> <Column field="name" header="Name" class="w-1/6" bodyClass="whitespace-nowrap"></Column> <Column field="price" header="Price" sortable class="w-1/6"> <template #body="slotProps"> $ {{ slotProps.data.price }} </template> </Column> <Column header="Image" class="w-1/6"> <template #body="slotProps"> <img :src="`https://primefaces.org/cdn/primevue/images/product/${slotProps.data.image}`" :alt="slotProps.data.image" class="w-16 shadow-sm" /> </template> </Column> <Column header="Details" class="w-1/6"> <template #body="slotProps"> <Button type="button" @click="displayProduct($event, slotProps.data)" icon="pi pi-search" severity="secondary" rounded></Button> </template> </Column> </DataTable> <Popover ref="op"> <div v-if="selectedProduct" class="rounded flex flex-col"> <div class="flex justify-center rounded"> <div class="relative mx-auto"> <img class="rounded w-44 sm:w-64" :src="`https://primefaces.org/cdn/primevue/images/product/${selectedProduct.image}`" :alt="selectedProduct.name" /> <Tag :value="selectedProduct.inventoryStatus" :severity="getSeverity(selectedProduct)" class="absolute dark:!bg-surface-900" style="left: 4px; top: 4px"></Tag> </div> </div> <div class="pt-4"> <div class="flex flex-row justify-between items-start gap-2 mb-4"> <div> <span class="font-medium text-surface-500 dark:text-surface-400 text-sm">{{ selectedProduct.category }}</span> <div class="text-lg font-medium mt-1">{{ selectedProduct.name }}</div> </div> <div class="bg-surface-100 p-1" style="border-radius: 30px"> <div class="bg-surface-0 flex items-center gap-2 justify-center py-1 px-2" style="border-radius: 30px; box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.04), 0px 1px 2px 0px rgba(0, 0, 0, 0.06)"> <span class="text-surface-900 font-medium text-sm">{{ selectedProduct.rating }}</span> <i class="pi pi-star-fill text-yellow-500"></i> </div> </div> </div> <div class="flex gap-2"> <Button icon="pi pi-shopping-cart" :label="`Buy Now | \$${selectedProduct.price}`" :disabled="selectedProduct.inventoryStatus === 'OUTOFSTOCK'" class="flex-auto whitespace-nowrap" @click="hidePopover"></Button> <Button icon="pi pi-heart" outlined @click="hidePopover"></Button> </div> </div> </div> </Popover> </div> <DocSectionCode :code="code" :service="['ProductService']" /> </template> <script> import { ProductService } from '@/service/ProductService'; export default { data() { return { products: null, selectedProduct: null, code: { basic: ` <DataTable :value="products" :rows="5" paginator tableStyle="min-width: 50rem"> <Column field="id" header="Id" class="w-1/6"></Column> <Column field="code" header="Code" class="w-1/6"></Column> <Column field="name" header="Name" class="w-1/6" bodyClass="whitespace-nowrap"></Column> <Column field="price" header="Price" sortable class="w-1/6"> <template #body="slotProps"> $ {{ slotProps.data.price }} </template> </Column> <Column header="Image" class="w-1/6"> <template #body="slotProps"> <img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="w-16 shadow-sm" /> </template> </Column> <Column header="Details" class="w-1/6"> <template #body="slotProps"> <Button type="button" @click="displayProduct($event, slotProps.data)" icon="pi pi-search" severity="secondary" rounded></Button> </template> </Column> </DataTable> <Popover ref="op"> <div v-if="selectedProduct" class="rounded flex flex-col"> <div class="flex justify-center rounded"> <div class="relative mx-auto"> <img class="rounded w-44 sm:w-64" :src="\`https://primefaces.org/cdn/primevue/images/product/\${selectedProduct.image}\`" :alt="selectedProduct.name" /> <Tag :value="selectedProduct.inventoryStatus" :severity="getSeverity(selectedProduct)" class="absolute dark:!bg-surface-900" style="left: 4px; top: 4px"></Tag> </div> </div> <div class="pt-4"> <div class="flex flex-row justify-between items-start gap-2 mb-4"> <div> <span class="font-medium text-surface-500 dark:text-surface-400 text-sm">{{ selectedProduct.category }}</span> <div class="text-lg font-medium mt-1">{{ selectedProduct.name }}</div> </div> <div class="bg-surface-100 p-1" style="border-radius: 30px"> <div class="bg-surface-0 flex items-center gap-2 justify-center py-1 px-2" style="border-radius: 30px; box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.04), 0px 1px 2px 0px rgba(0, 0, 0, 0.06)"> <span class="text-surface-900 font-medium text-sm">{{ selectedProduct.rating }}</span> <i class="pi pi-star-fill text-yellow-500"></i> </div> </div> </div> <div class="flex gap-2"> <Button icon="pi pi-shopping-cart" :label="\`Buy Now | \\$\${selectedProduct.price}\`" :disabled="selectedProduct.inventoryStatus === 'OUTOFSTOCK'" class="flex-auto whitespace-nowrap" @click="hidePopover"></Button> <Button icon="pi pi-heart" outlined @click="hidePopover"></Button> </div> </div> </div> </Popover> `, options: ` <template> <div class="card"> <DataTable :value="products" :rows="5" paginator tableStyle="min-width: 50rem"> <Column field="id" header="Id" class="w-1/6"></Column> <Column field="code" header="Code" class="w-1/6"></Column> <Column field="name" header="Name" class="w-1/6" bodyClass="whitespace-nowrap"></Column> <Column field="price" header="Price" sortable class="w-1/6"> <template #body="slotProps"> $ {{ slotProps.data.price }} </template> </Column> <Column header="Image" class="w-1/6"> <template #body="slotProps"> <img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="w-16 shadow-sm" /> </template> </Column> <Column header="Details" class="w-1/6"> <template #body="slotProps"> <Button type="button" @click="displayProduct($event, slotProps.data)" icon="pi pi-search" severity="secondary" rounded></Button> </template> </Column> </DataTable> <Popover ref="op"> <div v-if="selectedProduct" class="rounded flex flex-col"> <div class="flex justify-center rounded"> <div class="relative mx-auto"> <img class="rounded w-44 sm:w-64" :src="\`https://primefaces.org/cdn/primevue/images/product/\${selectedProduct.image}\`" :alt="selectedProduct.name" /> <Tag :value="selectedProduct.inventoryStatus" :severity="getSeverity(selectedProduct)" class="absolute dark:!bg-surface-900" style="left: 4px; top: 4px"></Tag> </div> </div> <div class="pt-4"> <div class="flex flex-row justify-between items-start gap-2 mb-4"> <div> <span class="font-medium text-surface-500 dark:text-surface-400 text-sm">{{ selectedProduct.category }}</span> <div class="text-lg font-medium mt-1">{{ selectedProduct.name }}</div> </div> <div class="bg-surface-100 p-1" style="border-radius: 30px"> <div class="bg-surface-0 flex items-center gap-2 justify-center py-1 px-2" style="border-radius: 30px; box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.04), 0px 1px 2px 0px rgba(0, 0, 0, 0.06)"> <span class="text-surface-900 font-medium text-sm">{{ selectedProduct.rating }}</span> <i class="pi pi-star-fill text-yellow-500"></i> </div> </div> </div> <div class="flex gap-2"> <Button icon="pi pi-shopping-cart" :label="\`Buy Now | \\$\${selectedProduct.price}\`" :disabled="selectedProduct.inventoryStatus === 'OUTOFSTOCK'" class="flex-auto whitespace-nowrap" @click="hidePopover"></Button> <Button icon="pi pi-heart" outlined @click="hidePopover"></Button> </div> </div> </div> </Popover> </div> </template> <script> import { ProductService } from '@/service/ProductService'; export default { data() { return { products: null, selectedProduct: null }; }, mounted() { ProductService.getProductsSmall().then((data) => (this.products = data)); }, methods: { displayProduct(event, product) { this.$refs.op.hide(); if (this.selectedProduct?.id === product.id) { this.selectedProduct = null; } else { this.selectedProduct = product; this.$nextTick(() => { this.$refs.op.show(event); }); } }, hidePopover() { this.$refs.op.hide(); }, getSeverity(product) { switch (product.inventoryStatus) { case 'INSTOCK': return 'success'; case 'LOWSTOCK': return 'warn'; case 'OUTOFSTOCK': return 'danger'; default: return null; } } } }; <\/script> `, composition: ` <template> <div class="card"> <DataTable :value="products" :rows="5" paginator tableStyle="min-width: 50rem"> <Column field="id" header="Id" class="w-1/6"></Column> <Column field="code" header="Code" class="w-1/6"></Column> <Column field="name" header="Name" class="w-1/6" bodyClass="whitespace-nowrap"></Column> <Column field="price" header="Price" sortable class="w-1/6"> <template #body="slotProps"> $ {{ slotProps.data.price }} </template> </Column> <Column header="Image" class="w-1/6"> <template #body="slotProps"> <img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="w-16 shadow-sm" /> </template> </Column> <Column header="Details" class="w-1/6"> <template #body="slotProps"> <Button type="button" @click="displayProduct($event, slotProps.data)" icon="pi pi-search" severity="secondary" rounded></Button> </template> </Column> </DataTable> <Popover ref="op"> <div v-if="selectedProduct" class="rounded flex flex-col"> <div class="flex justify-center rounded"> <div class="relative mx-auto"> <img class="rounded w-44 sm:w-64" :src="\`https://primefaces.org/cdn/primevue/images/product/\${selectedProduct.image}\`" :alt="selectedProduct.name" /> <Tag :value="selectedProduct.inventoryStatus" :severity="getSeverity(selectedProduct)" class="absolute dark:!bg-surface-900" style="left: 4px; top: 4px"></Tag> </div> </div> <div class="pt-4"> <div class="flex flex-row justify-between items-start gap-2 mb-4"> <div> <span class="font-medium text-surface-500 dark:text-surface-400 text-sm">{{ selectedProduct.category }}</span> <div class="text-lg font-medium mt-1">{{ selectedProduct.name }}</div> </div> <div class="bg-surface-100 p-1" style="border-radius: 30px"> <div class="bg-surface-0 flex items-center gap-2 justify-center py-1 px-2" style="border-radius: 30px; box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.04), 0px 1px 2px 0px rgba(0, 0, 0, 0.06)"> <span class="text-surface-900 font-medium text-sm">{{ selectedProduct.rating }}</span> <i class="pi pi-star-fill text-yellow-500"></i> </div> </div> </div> <div class="flex gap-2"> <Button icon="pi pi-shopping-cart" :label="\`Buy Now | \\$\${selectedProduct.price}\`" :disabled="selectedProduct.inventoryStatus === 'OUTOFSTOCK'" class="flex-auto whitespace-nowrap" @click="hidePopover"></Button> <Button icon="pi pi-heart" outlined @click="hidePopover"></Button> </div> </div> </div> </Popover> </div> </template> <script setup> import { ref, onMounted, nextTick } from "vue"; import { useToast } from "primevue/usetoast"; import { ProductService } from '@/service/ProductService'; onMounted(() => { ProductService.getProductsSmall().then((data) => (products.value = data)); }); const op = ref(); const products = ref(); const selectedProduct = ref(); const displayProduct = (event, product) => { op.value.hide(); if (selectedProduct.value?.id === product.id) { selectedProduct.value = null; } else { selectedProduct.value = product; nextTick(() => { op.value.show(event); }); } } const hidePopover = () => { op.value.hide(); } const getSeverity = (product) => { switch (product.inventoryStatus) { case 'INSTOCK': return 'success'; case 'LOWSTOCK': return 'warn'; case 'OUTOFSTOCK': return 'danger'; default: return null; } } <\/script> `, data: ` /* ProductService */ { id: '1000', code: 'f230fh0g3', name: 'Bamboo Watch', description: 'Product Description', image: '/bamboo-watch.jpg', price: 65, category: 'Accessories', quantity: 24, inventoryStatus: 'INSTOCK', rating: 5 }, ... ` } }; }, mounted() { ProductService.getProductsSmall().then((data) => (this.products = data)); }, methods: { displayProduct(event, product) { this.$refs.op.hide(); if (this.selectedProduct?.id === product.id) { this.selectedProduct = null; } else { this.selectedProduct = product; this.$nextTick(() => { this.$refs.op.show(event); }); } }, hidePopover() { this.$refs.op.hide(); }, getSeverity(product) { switch (product.inventoryStatus) { case 'INSTOCK': return 'success'; case 'LOWSTOCK': return 'warn'; case 'OUTOFSTOCK': return 'danger'; default: return null; } } } }; </script>