Fixed #2653 - New Component: DynamicDialog

pull/2665/head
mertsincan 2022-06-14 09:11:34 +01:00
parent cdf42edd14
commit 6e608144b9
24 changed files with 458 additions and 1 deletions

View File

@ -0,0 +1,15 @@
const DynamicDialogProps = [];
const DynamicDialogEvents = [];
const DynamicDialogSlots = [];
module.exports = {
dynamicdialog: {
name: "DynamicDialog",
description: "Dialogs can be created dynamically with any component as the content using a DialogService.",
props: DynamicDialogProps,
events: DynamicDialogEvents,
slots: DynamicDialogSlots
}
};

View File

@ -56,7 +56,7 @@
"gulp-uglify": "^3.0.2", "gulp-uglify": "^3.0.2",
"gulp-uglifycss": "^1.0.6", "gulp-uglifycss": "^1.0.6",
"sass": "^1.45.0", "sass": "^1.45.0",
"primeflex": "3.1.2", "primeflex": "3.2.1",
"primeicons": "5.0.0", "primeicons": "5.0.0",
"prismjs": "^1.15.0", "prismjs": "^1.15.0",
"quill": "^1.3.7", "quill": "^1.3.7",

View File

@ -20,9 +20,11 @@ let coreDependencies = {
'primevue/confirmationeventbus': 'primevue.confirmationeventbus', 'primevue/confirmationeventbus': 'primevue.confirmationeventbus',
'primevue/toasteventbus': 'primevue.toasteventbus', 'primevue/toasteventbus': 'primevue.toasteventbus',
'primevue/overlayeventbus': 'primevue.overlayeventbus', 'primevue/overlayeventbus': 'primevue.overlayeventbus',
'primevue/dynamicdialogeventbus': 'primevue.dynamicdialogeventbus',
'primevue/terminalservice': 'primevue.terminalservice', 'primevue/terminalservice': 'primevue.terminalservice',
'primevue/useconfirm': 'primevue.useconfirm', 'primevue/useconfirm': 'primevue.useconfirm',
'primevue/usetoast': 'primevue.usetoast', 'primevue/usetoast': 'primevue.usetoast',
'primevue/usedialog': 'primevue.usedialog',
'primevue/button': 'primevue.button', 'primevue/button': 'primevue.button',
'primevue/inputtext': 'primevue.inputtext', 'primevue/inputtext': 'primevue.inputtext',
'primevue/inputnumber': 'primevue.inputnumber', 'primevue/inputnumber': 'primevue.inputnumber',
@ -182,6 +184,9 @@ function addServices() {
addEntry('overlayeventbus', 'OverlayEventBus.js', 'overlayeventbus'); addEntry('overlayeventbus', 'OverlayEventBus.js', 'overlayeventbus');
addEntry('usetoast', 'UseToast.js', 'usetoast'); addEntry('usetoast', 'UseToast.js', 'usetoast');
addEntry('terminalservice', 'TerminalService.js', 'terminalservice'); addEntry('terminalservice', 'TerminalService.js', 'terminalservice');
addEntry('usedialog', 'UseDialog.js', 'usedialog');
addEntry('dialogservice', 'DialogService.js', 'dialogservice');
addEntry('dynamicdialogeventbus', 'DynamicDialogEventBus.js', 'dynamicdialogeventbus');
} }
addUtils(); addUtils();

View File

@ -580,6 +580,10 @@
"name": "Dialog", "name": "Dialog",
"to": "/dialog" "to": "/dialog"
}, },
{
"name": "DynamicDialog",
"to": "/dynamicdialog"
},
{ {
"name": "OverlayPanel", "name": "OverlayPanel",
"to": "/overlaypanel" "to": "/overlaypanel"

View File

@ -0,0 +1,47 @@
import { Plugin, VNode } from 'vue';
import { DynamicDialogOptions } from '../dynamicdialogoptions';
declare const plugin: Plugin;
export default plugin;
export interface DialogInstance {
/**
* Dynamic component for content template
*/
content: VNode | undefined;
/**
* Instance options
* @see DynamicDialogOptions
*/
options: DynamicDialogOptions;
/**
* Custom data object
*/
data: any;
/**
* Hides the dialog.
* @param {*} params - Parameters sent by the user to the root instance
*/
hide: (params?: any) => void;
}
export interface DialogServiceMethods {
/**
* Displays the dialog using the dynamic dialog object options.
* @param {VNode} content - Dynamic component for content template
* @param {DynamicDialogOptions} options - DynamicDialog Object
*/
open(content: VNode, options?: DynamicDialogOptions): DialogInstance;
}
declare module 'vue/types/vue' {
interface Vue {
$dialog: DialogServiceMethods;
}
}
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$dialog: DialogServiceMethods;
}
}

View File

@ -0,0 +1,28 @@
import { markRaw } from 'vue';
import { PrimeVueDialogSymbol } from 'primevue/usedialog';
import DynamicDialogEventBus from 'primevue/dynamicdialogeventbus';
export default {
install: (app) => {
const DialogService = {
open: (content, options) => {
const instance = {
content: content && markRaw(content),
options: options || {},
data: options && options.data,
hide: (params) => {
DynamicDialogEventBus.emit('close', { instance, params });
}
}
DynamicDialogEventBus.emit('open', { instance });
return instance;
}
};
app.config.unwrapInjectedRef = true; // Remove it after Vue 3.3. Details: https://vuejs.org/guide/components/provide-inject.html#working-with-reactivity
app.config.globalProperties.$dialog = DialogService;
app.provide(PrimeVueDialogSymbol, DialogService);
}
}

View File

@ -0,0 +1,6 @@
{
"main": "./dialogservice.cjs.js",
"module": "./dialogservice.esm.js",
"unpkg": "./dialogservice.min.js",
"types": "./DialogService.d.ts"
}

View File

@ -0,0 +1,26 @@
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
export interface DynamicDialogProps {}
export declare type DynamicDialogEmits = {}
export interface DynamicDialogSlots {}
declare class DynamicDialog extends ClassComponent<DynamicDialogProps, DynamicDialogSlots, DynamicDialogEmits> { }
declare module '@vue/runtime-core' {
interface GlobalComponents {
DynamicDialog: GlobalComponentConstructor<DynamicDialog>
}
}
/**
*
* DynamicDialogs can be created dynamically with any component as the content using a DialogService.
*
* Demos:
*
* - [DynamicDialog](https://www.primefaces.org/primevue/showcase/#/dynamicdialog)
*
*/
export default DynamicDialog;

View File

@ -0,0 +1,76 @@
<template>
<template v-for="(instance, key) in instanceMap" :key="key">
<DDialog :_instance="instance" v-bind="instance.options.props" v-model:visible="instance.visible" @hide="onDialogHide(instance)" @after-hide="onDialogAfterHide">
<template #header v-if="instance.options.templates && instance.options.templates.header">
<component :is="header" v-for="(header, index) in getTemplateItems(instance.options.templates.header)" :key="index + '_header'"></component>
</template>
<component :is="instance.content"></component>
<template #footer v-if="instance.options.templates && instance.options.templates.footer">
<component :is="footer" v-for="(footer, index) in getTemplateItems(instance.options.templates.footer)" :key="index + '_footer'"></component>
</template>
</DDialog>
</template>
</template>
<script>
import { ObjectUtils, UniqueComponentId } from 'primevue/utils';
import DynamicDialogEventBus from 'primevue/dynamicdialogeventbus';
import Dialog from 'primevue/dialog';
export default {
name: 'DynamicDialog',
inheritAttrs: false,
data() {
return {
instanceMap: {}
}
},
openListener: null,
closeListener: null,
currentInstance: null,
mounted() {
this.openListener = ({ instance }) => {
const key = UniqueComponentId() + '_dynamic_dialog';
instance.visible = true;
instance.key = key;
this.instanceMap[key] = instance;
};
this.closeListener = ({ instance, params }) => {
const key = instance.key;
const currentInstance = this.instanceMap[key];
if (currentInstance) {
currentInstance.visible = false;
currentInstance.options.onHide && currentInstance.options.onHide({ data: params, type: 'config-close' });
this.currentInstance = currentInstance;
}
};
DynamicDialogEventBus.on('open', this.openListener);
DynamicDialogEventBus.on('close', this.closeListener);
},
beforeUnmount() {
DynamicDialogEventBus.off('open', this.openListener);
DynamicDialogEventBus.off('close', this.closeListener);
},
methods: {
onDialogHide(instance) {
!this.currentInstance && instance.options.onHide && instance.options.onHide({ type: 'dialog-close' });
},
onDialogAfterHide() {
this.currentInstance && (delete this.currentInstance);
this.currentInstance = null;
},
getTemplateItems(template) {
const items = ObjectUtils.getItemValue(template);
return Array.isArray(items) ? items : [items];
}
},
components: {
'DDialog': Dialog
}
}
</script>

View File

@ -0,0 +1,9 @@
{
"main": "./dynamicdialog.cjs.js",
"module": "./dynamicdialog.esm.js",
"unpkg": "./dynamicdialog.min.js",
"types": "./DynamicDialog.d.ts",
"browser": {
"./sfc": "./DynamicDialog.vue"
}
}

View File

@ -0,0 +1,3 @@
import { EventBus } from 'primevue/utils';
export default EventBus();

View File

@ -0,0 +1,5 @@
{
"main": "./dynamicdialogeventbus.cjs.js",
"module": "./dynamicdialogeventbus.esm.js",
"unpkg": "./dynamicdialogeventbus.min.js"
}

View File

@ -0,0 +1,53 @@
import { VNode } from 'vue';
import { DialogProps } from '../dialog';
export type DynamicDialogHideType = 'config-close' | 'dialog-close' | undefined;
export interface DynamicDialogTemplates {
/**
* Custom header template.
*/
header: () => VNode[];
/**
* Custom footer template.
*/
footer: () => VNode[];
}
export interface DynamicDialogHideOptions {
/**
* Custom data object
*/
data?: any;
/**
* Hide type
* @see DynamicDialogHideType
*/
type: DynamicDialogHideType;
}
export interface DynamicDialogOptions {
/**
* Dialog Props
* @see DialogProps
*/
props?: DialogProps;
/**
* Dialog Slots
* @see DynamicDialogTemplates
*/
templates?: DynamicDialogTemplates;
/**
* Custom data object
*/
data?: any;
/**
* Hides the dialog.
* @see DynamicDialogHideOptions
*/
onHide?: (options?: DynamicDialogHideOptions) => void;
/**
* Optional
*/
[key: string]: any;
}

View File

@ -0,0 +1,3 @@
{
"types": "./DynamicDialogOptions.d.ts"
}

View File

@ -0,0 +1,6 @@
//import { DynamicDialogOptions } from '../dynamicdialogoptions';
export declare function useDialog(): {
open: (dialog: any) => void;
close: () => void;
}

View File

@ -0,0 +1,12 @@
import { inject } from 'vue';
export const PrimeVueDialogSymbol = Symbol();
export function useDialog() {
const PrimeVueDialog = inject(PrimeVueDialogSymbol);
if (!PrimeVueDialog) {
throw new Error('No PrimeVue Dialog provided!');
}
return PrimeVueDialog;
}

View File

@ -0,0 +1,6 @@
{
"main": "./usedialog.cjs.js",
"module": "./usedialog.esm.js",
"unpkg": "./usedialog.min.js",
"types": "./UseDialog.d.ts"
}

View File

@ -86,6 +86,10 @@ export default {
return !!(obj && obj.constructor && obj.call && obj.apply); return !!(obj && obj.constructor && obj.call && obj.apply);
}, },
getItemValue(obj, ...params) {
return this.isFunction(obj) ? obj(...params) : obj;
},
filter(value, fields, filterValue) { filter(value, fields, filterValue) {
var filteredItems = []; var filteredItems = [];

View File

@ -33,6 +33,8 @@ import DataView from './components/dataview/DataView';
import DataViewLayoutOptions from './components/dataviewlayoutoptions/DataViewLayoutOptions'; import DataViewLayoutOptions from './components/dataviewlayoutoptions/DataViewLayoutOptions';
import DeferredContent from './components/deferredcontent/DeferredContent'; import DeferredContent from './components/deferredcontent/DeferredContent';
import Dialog from './components/dialog/Dialog'; import Dialog from './components/dialog/Dialog';
import DynamicDialog from './components/dynamicdialog/DynamicDialog';
import DialogService from './components/dialogservice/DialogService';
import Divider from './components/divider/Divider'; import Divider from './components/divider/Divider';
import Dock from './components/dock/Dock'; import Dock from './components/dock/Dock';
import Dropdown from './components/dropdown/Dropdown'; import Dropdown from './components/dropdown/Dropdown';
@ -128,6 +130,7 @@ app.config.globalProperties.$appState = reactive({theme: 'lara-light-blue', dark
app.use(PrimeVue, {ripple: true}); app.use(PrimeVue, {ripple: true});
app.use(ToastService); app.use(ToastService);
app.use(ConfirmationService); app.use(ConfirmationService);
app.use(DialogService);
app.use(router); app.use(router);
app.directive('badge', BadgeDirective); app.directive('badge', BadgeDirective);
@ -163,6 +166,7 @@ app.component('DataView', DataView);
app.component('DataViewLayoutOptions', DataViewLayoutOptions); app.component('DataViewLayoutOptions', DataViewLayoutOptions);
app.component('DeferredContent', DeferredContent); app.component('DeferredContent', DeferredContent);
app.component('Dialog', Dialog); app.component('Dialog', Dialog);
app.component('DynamicDialog', DynamicDialog);
app.component('Divider', Divider); app.component('Divider', Divider);
app.component('Dock', Dock); app.component('Dock', Dock);
app.component('Dropdown', Dropdown); app.component('Dropdown', Dropdown);

View File

@ -349,6 +349,11 @@ const routes = [
name: 'dropdown', name: 'dropdown',
component: () => import('../views/dropdown/DropdownDemo.vue') component: () => import('../views/dropdown/DropdownDemo.vue')
}, },
{
path: '/dynamicdialog',
name: 'dynamicdialog',
component: () => import('../views/dynamicdialog/DynamicDialogDemo.vue')
},
{ {
path: '/editor', path: '/editor',
name: 'editor', name: 'editor',

View File

@ -0,0 +1,59 @@
<template>
<div>
<div class="content-section introduction">
<div class="feature-intro">
<h1>Dynamic Dialog</h1>
<p>Dialogs can be created dynamically with any component as the content using a DialogService.</p>
</div>
<AppDemoActions />
</div>
<div class="content-section implementation">
<div class="card">
<Button label="Show" @click="onShow" />
<DynamicDialog />
</div>
</div>
</div>
</template>
<script>
import ProductListDemo from './ProductListDemo';
import Button from 'primevue/button';
import { h } from 'vue';
export default {
methods:{
onShow() {
const dialogRef = this.$dialog.open(ProductListDemo, {
props: {
header: 'Product List',
style: {
width: '50%',
height: '550px',
},
modal: true
},
templates: {
footer: () => {
return [
h(Button, { label: "No", icon: "pi pi-times", onClick: () => dialogRef.hide({ buttonType: 'No' }), class: "p-button-text" }),
h(Button, { label: "Yes", icon: "pi pi-check", onClick: () => dialogRef.hide({ buttonType: 'Yes' }), autofocus: true })
]
}
},
onHide: (options) => {
const data = options.data;
if (data) {
const buttonType = data.buttonType;
const summary_and_detail = buttonType ? { summary: 'No Product Selected', detail: `Pressed '${buttonType}' button` } : { summary: 'Product Selected', detail: data.name };
this.$toast.add({ severity:'info', ...summary_and_detail, life: 3000 });
}
}
});
}
}
}
</script>

View File

@ -0,0 +1,25 @@
<template>
<div class="flex flex-column align-items-center justify-content-center row-gap-3">
<p>There are <strong>{{totalProducts}}</strong> products in total in this list.</p>
<Button type="button" label="Close" @click="closeDialog"></Button>
</div>
</template>
<script>
export default {
inject: ['dialogRef'],
data() {
return {
totalProducts: 0
}
},
mounted() {
this.totalProducts = this.dialogRef.data.totalProducts;
},
methods: {
closeDialog() {
this.dialogRef.hide();
}
}
}
</script>

View File

@ -0,0 +1,54 @@
<template>
<div>
<Button label="Info" icon="pi pi-info-circle" class="p-button-sm p-button-outlined p-button-info my-2" @click="onInfoShow" />
<DataTable :value="products" responsiveLayout="scroll">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
<Column>
<template #body="slotProps">
<Button type="button" icon="pi pi-cog" @click="closeDialog(slotProps.data)"></Button>
</template>
</Column>
</DataTable>
</div>
</template>
<script>
import ProductService from '../../service/ProductService';
import InfoDemo from './InfoDemo.vue';
export default {
inject: ['dialogRef'],
data() {
return {
products: null
}
},
productService: null,
created() {
this.productService = new ProductService();
},
mounted() {
this.productService.getProductsSmall().then(data => this.products = data);
},
methods: {
closeDialog(data) {
this.dialogRef.hide(data);
},
onInfoShow() {
this.$dialog.open(InfoDemo, {
props: {
header: 'Information',
modal: true,
dismissableMask: true
},
data: {
totalProducts: this.products ? this.products.length : 0
}
});
}
}
}
</script>

View File

@ -10,6 +10,7 @@ module.exports = {
'primevue/tooltip': path.resolve(__dirname, 'src/components/tooltip/Tooltip.js'), 'primevue/tooltip': path.resolve(__dirname, 'src/components/tooltip/Tooltip.js'),
'primevue/useconfirm': path.resolve(__dirname, 'src/components/useconfirm/UseConfirm.js'), 'primevue/useconfirm': path.resolve(__dirname, 'src/components/useconfirm/UseConfirm.js'),
'primevue/usetoast': path.resolve(__dirname, 'src/components/usetoast/UseToast.js'), 'primevue/usetoast': path.resolve(__dirname, 'src/components/usetoast/UseToast.js'),
'primevue/usedialog': path.resolve(__dirname, 'src/components/usedialog/UseDialog.js'),
'primevue/utils': path.resolve(__dirname, 'src/components/utils/Utils.js'), 'primevue/utils': path.resolve(__dirname, 'src/components/utils/Utils.js'),
'primevue/api': path.resolve(__dirname, 'src/components/api/Api.js'), 'primevue/api': path.resolve(__dirname, 'src/components/api/Api.js'),
'primevue/portal': path.resolve(__dirname, 'src/components/portal/Portal.vue'), 'primevue/portal': path.resolve(__dirname, 'src/components/portal/Portal.vue'),
@ -28,6 +29,7 @@ module.exports = {
'primevue/toasteventbus': path.resolve(__dirname, 'src/components/toasteventbus/ToastEventBus.js'), 'primevue/toasteventbus': path.resolve(__dirname, 'src/components/toasteventbus/ToastEventBus.js'),
'primevue/overlayeventbus': path.resolve(__dirname, 'src/components/overlayeventbus/OverlayEventBus.js'), 'primevue/overlayeventbus': path.resolve(__dirname, 'src/components/overlayeventbus/OverlayEventBus.js'),
'primevue/terminalservice': path.resolve(__dirname, 'src/components/terminalservice/TerminalService.js'), 'primevue/terminalservice': path.resolve(__dirname, 'src/components/terminalservice/TerminalService.js'),
'primevue/dynamicdialogeventbus': path.resolve(__dirname, 'src/components/dynamicdialogeventbus/DynamicDialogEventBus.js'),
'primevue/virtualscroller': path.resolve(__dirname, 'src/components/virtualscroller/VirtualScroller.vue') 'primevue/virtualscroller': path.resolve(__dirname, 'src/components/virtualscroller/VirtualScroller.vue')
}, },
}, },