I had this problem and I discovered that it's caused by declaring multiple ConfirmDialog components in DOM markup. For example if you add a confirm dialog to every component that uses it and then you happen to load 2+ components on the page at the same time, you will see 1 dialog for every <ConfirmDialog /> that exists on that page.
The solution is to only declare the ConfirmDialog once in your root Vue component and then every time you call it, only import the useConfirm function and then call the dialog with that.
For example:
App.vue
<script setup>
import ConfirmDialog from 'primevue/confirmdialog';
</script>
<template>
<router-view></router-view>
<ConfirmDialog />
</template>
Every other component:
<script setup>
import { useConfirm } from 'primevue/useconfirm';
const confirm = useConfirm();
const handleRemoveThing = () => {
// bye
};
const onRemoveThing = () => {
confirm.require({
message: 'Are you sure you want to remove some thing?',
header: 'Remove Thing',
icon: 'icon-delete',
rejectLabel: 'Cancel',
acceptLabel: 'Remove',
acceptClass: 'p-button-danger',
accept: handleRemoveThing,
});
};
</script>
<template>
<div>
<button @click="onRemoveThing">Remove</button>
</div>
</template>
Answer from agm1984 on Stack OverflowConfirmPopup: misplaced / missing popup on stopped click event
ConfirmPopup also shows ConfirmDialog
Primevue Dialog component.
PrimeVue ConfirmDialog Opens Multiple Times
Hey everyone, Is it possible to add confirmation dialog based on conditions before closing a dialog? I have tried to add custom function but still the cancel button works.
<template>
<Dialog v-model:visible="visible" modal :draggable="false" :style="{ width: '45rem' }">
<template #header>
<div class="flex justify-between items-center ml-5">
<p class="font-semibold text-xl flex justify-center text-center">Item options</p>
</div>
</template>
<template #closeicon>
<i class="pi pi-times cursor-pointer p-dialog-header-icon" u/click="handleCancel" />
</template>
<div class="px-5 my-5">
<div class="flex flex-col align-items-center gap-3 mb-5">
<label for="listname" class="font-semibold w-6rem text-lg">Item name <span class="text-red-400">*</span></label>
<InputText id="listname" class="flex-auto" v-model="listItemName" placeholder="list Item Name" autocomplete="off" />
</div>
</div>
<div v-if="props.editableItem?.level != 3" class="flex align-items-center px-5 mt-5 ">
<Checkbox v-model="containsublist" inputId="containsublist" name="sublist" value="sublist" :binary="true"/>
<label for="containsublist" class="ml-2">Contains sublist</label>
</div>
<!-- -->
<div v-if="containsublist" class="mt-5">
<div class="px-5">
<div class="flex flex-col align-items-center gap-2 mb-3">
<label for="sublistitems" class="font-semibold w-6rem text-lg">Sublist items <span class="text-red-400">*</span></label>
<span class="text-sm text-surface-500">Multiple entries are allowed <br/> (Comma separated entries)</span>
<span v-if="addClicked && sublistItems.length === 0" class="text-sm text-error"><i class="pi pi-exclamation-triangle text-error mr-2"></i>You should add items</span>
<Textarea id="sublistItems" v-model="sublistItem" rows="10" cols="30" placeholder="List item" :invalid="addClicked && sublistItem === ''"/>
</div>
<Button
label="Add"
icon="pi pi-plus"
u/click="handleAdd"
class="bg-success text-white hover:bg-success hover:border-success my-2" />
<!-- -->
<div class="flex justify-center mt-5 mr-5">
<Button label="Save" icon="pi pi-check" class="bg-success text-white hover:bg-success hover:border-success w-28" @click="handleEditItem" />
</div>
<ConfirmDialog group="headless">
<template #container="{ message }">
<div class="rounded-full p-3 mt-2">
<i class="pi pi-exclamation-triangle = mr-2"></i>
<span class="mt-2 font-poppins text-base text-surface-500">{{ message.message }}</span>
<!-- <div class="flex justify-end gap-2 mt-3">
<Button label="No" outlined @click="rejectCallback" severity="secondary" size="small" text></Button>
<Button label="Yes" @click="acceptCallback" severity="success" size="small" ></Button>
</div> -->
</div>
</template>
</ConfirmDialog>
</Dialog>
</template>
<script setup>
import { watch, ref } from "vue";
import { useConfirm } from "primevue/useconfirm";
import { useToast } from "primevue/usetoast";
const confirm = useConfirm();
const toast = useToast();
const emit = defineEmits();
const props= defineProps({
editableItem: {
type: Object,
required: true
},
containsublist: {
type: Boolean,
required: true
},
});
const sublistName = ref('');
const sublistItem = ref('');
const sublistItems = ref([]);
const addClicked = ref(false);
// const containsublist = ref(false);
const containsublist = ref(props.containsublist);
const visible = ref(false);
const listItemName = ref(props.editableItem.title);
watch(() => props.editableItem, (newVal) => {
listItemName.value = newVal.title;
});
const handleAdd = () => {
addClicked.value = true;
const items = sublistItem.value.split(/[\n,]+/)
.map(item => item.trim())
.filter(item => item !== '')
.map(item => ({ name: item }));
sublistItems.value = sublistItems.value.concat(items);
};
const handleEditItem = () => {
addClicked.value = true;
if (sublistItems.value.length > 0){
sublistItems.value = sublistItems.value.map((item, index) => {
return {
id: index,
title: item.name,
isHovered: false,
level: props.editableItem?.level + 1,
sublists: [],
}
});
const editedData = {
'id':props.editableItem.id,
'title':listItemName.value,
'sublists':sublistItems.value,
};
emit('editItem',editedData)
listItemName.value = '';
sublistItem.value = '';
sublistItems.value = [];
containsublist.value = false;
emit('cancel');
};
};
const requireConfirmation = () => {
confirm.require({
group: 'headless',
header: 'Are you sure you want to close the modal? Unsaved changes will be lost.',
message: 'Please confirm to proceed.',
});
};
const handleCancel = () => {
// Check if there are unsaved changes before closing
// if (hasUnsavedChanges()) {
// if (confirm("Are you sure you want to close the dialog? Unsaved changes will be lost.")) { // Use native confirm dialog
// emit('cancel');
// visible.value = false;
// }
// } else {
// emit('cancel');
// visible.value = false;
// }
requireConfirmation();
// emit('cancel');
console.log('cancel hereeee')
}
const deleteItem = (data) => {
sublistItems.value = sublistItems.value.filter(item => item.name !== data.name);
};
const emitOpenCreateListModal = () => {
containsublist.value = false;
emit('openCreateListModal', props.editableItem);
};
const onRowReorder = (event) => {
sublistItems.value = event.value;
};
</script>Any inputs would be helpful. Thank you.
I had this problem and I discovered that it's caused by declaring multiple ConfirmDialog components in DOM markup. For example if you add a confirm dialog to every component that uses it and then you happen to load 2+ components on the page at the same time, you will see 1 dialog for every <ConfirmDialog /> that exists on that page.
The solution is to only declare the ConfirmDialog once in your root Vue component and then every time you call it, only import the useConfirm function and then call the dialog with that.
For example:
App.vue
<script setup>
import ConfirmDialog from 'primevue/confirmdialog';
</script>
<template>
<router-view></router-view>
<ConfirmDialog />
</template>
Every other component:
<script setup>
import { useConfirm } from 'primevue/useconfirm';
const confirm = useConfirm();
const handleRemoveThing = () => {
// bye
};
const onRemoveThing = () => {
confirm.require({
message: 'Are you sure you want to remove some thing?',
header: 'Remove Thing',
icon: 'icon-delete',
rejectLabel: 'Cancel',
acceptLabel: 'Remove',
acceptClass: 'p-button-danger',
accept: handleRemoveThing,
});
};
</script>
<template>
<div>
<button @click="onRemoveThing">Remove</button>
</div>
</template>
As mentioned by others, the issue comes from having multiple instances rendered at the same time. Each instance listens globally, so if several are present in the DOM, they all open together.
To avoid this, you can use the group property on ConfirmDialog to target the specific dialog you want to open. This is especially useful if, like me, you have custom templates for each dialog.
You can see an example of this in the documentation: https://primevue.org/confirmdialog/#template
Here’s the basic idea:
<template>
<div>
<ConfirmDialog group="confirm-delete-stuff" />
<button @click="deleteStuff">Delete</button>
</div>
</template>
<script setup>
import { useConfirm } from 'primevue/useconfirm';
const confirm = useConfirm();
const deleteStuff = () => {
confirm.require({
group: 'confirm-delete-stuff',
message: 'Are you sure you want to delete this stuff?',
header: 'Delete stuff',
rejectLabel: 'Cancel',
acceptLabel: 'Remove',
accept: () => {
console.log('Remove stuff');
},
});
};
</script>
I found the problem!
app.use(router, PrimeVue)
it should be
app.use(router).use(PrimeVue)
app.use() expects the plugin as the first argument, and optional plugin options as the second argument.
https://vuejs.org/api/application.html#app-use
The code is ok and works. Check your builder and node setup.
Here is the working playground with your code.
I have only deleted the vue router, since it is not used and a couple of css files
import './assets/main.css'
import '/node_modules/primeflex/primeflex.css';
To customize the appearance of the p-dialog component from PrimeVue while maintaining its functionality, you can use a feature in PrimeVue called "Pass Through" to override or add classes and styles to the components.
More information here: https://primevue.org/passthrough/
Found this answer in the PrimeVue Discord.
In the styling section of the documentation, it has the built in css classes. I can access these for this specific p-dialog if used in my wrapper class as seen below:
.deletion-dialog {
...
...
.p-dialog-header {
...
...
}
}