Skip to content

Instantly share code, notes, and snippets.

@IlCallo
Created December 22, 2022 16:34
Show Gist options
  • Save IlCallo/107c901b45ac8a470d9b06b7ec7b46ef to your computer and use it in GitHub Desktop.
Save IlCallo/107c901b45ac8a470d9b06b7ec7b46ef to your computer and use it in GitHub Desktop.
Quasar Dialog helpers, which differentiate between create and edit mode
export interface MyEntity {
id?: number;
title: string;
}
<template>
<q-dialog ref="dialogRef" @hide="onDialogHide">
<q-card>
<!-- ... your content -->
<q-card-section>
{{ myEntity }}
</q-card-section>
<q-card-actions align="evenly">
<q-btn v-close-popup label="Cancel" color="primary" />
<q-btn label="Save" color="primary" @click="onDialogOK(myEntity)" />
</q-card-actions>
</q-card>
</q-dialog>
</template>
<script lang="ts" setup>
import { useDialogPluginComponent } from "quasar";
import { ref } from "vue";
import { openFormDialog } from "src/helpers/open-form-dialog";
const { onDialogHide, dialogRef, onDialogOK } = useDialogPluginComponent();
const props = defineProps({
...openFormDialog.props,
// ... your props
});
const myEntity = ref(props.initialValue);
</script>
<template>
<q-dialog v-bind="dialogProps" v-on="dialogEvents">
<q-card>
<!-- ... your content -->
<q-card-section>
{{ myEntity }}
</q-card-section>
<q-card-actions align="evenly">
<q-btn v-close-popup label="Cancel" color="primary" />
<q-btn label="Save" color="primary" @click="onDialogOK(myEntity)" />
</q-card-actions>
</q-card>
</q-dialog>
</template>
<script>
import { defineComponent, ref } from "vue";
import { useDialogWithDefaults } from "src/composables/use-dialog-with-defaults";
import { openFormDialog } from "src/helpers/open-form-dialog";
export default defineComponent({
name: "MyFormDialog",
props: openFormDialog.props,
setup(props) {
const { onDialogHide, dialogRef, onDialogOK } = useDialogPluginComponent();
const myEntity = ref(props.initialValue);
return { dialogProps, dialogEvents, onDialogOK };
},
});
</script>
<template>
<div>
<q-btn label="Create" @click="showForm()" />
<q-btn label="Edit" @click="showForm({ id: 10, title: 'test' })" />
</div>
</template>
<script lang="ts" setup>
import { openFormDialog } from "src/helpers/open-form-dialog";
import { MyEntity } from "./model";
import MyFormDialog from "./my-form-dialog.vue";
function showForm(initialValue?: MyEntity) {
openFormDialog<MyEntity>({
component: MyFormDialog,
initialValue,
defaultValue: { title: "prova" },
// eslint-disable-next-line no-console, @typescript-eslint/restrict-plus-operands
onSave: (payload: MyEntity) => console.log("onSave " + payload),
// eslint-disable-next-line no-console, @typescript-eslint/restrict-plus-operands
onCreate: (payload: MyEntity) => console.log("onCreate " + payload),
// eslint-disable-next-line no-console
onCancel: () => console.log("onCancel"),
// eslint-disable-next-line no-console
onDismiss: () => console.log("onDismiss"),
});
}
</script>
import { Dialog } from "quasar";
import { Component } from "vue";
const DEFAULT_INITIAL_VALUE_PROP_NAME = "initialValue";
/* eslint-disable @typescript-eslint/no-explicit-any */
interface OpenFormDialogOptions<T> {
component: Component;
initialValuePropName?: string;
initialValue?: T;
defaultValue?: T;
onSave?: (payload?: any) => Promise<void> | void;
onCreate?: (payload?: any) => Promise<void> | void;
onCancel?: () => Promise<void> | void;
onDismiss?: () => Promise<void> | void;
props?: Record<string, any>;
}
/* eslint-enable @typescript-eslint/no-explicit-any */
/**
* Wrap `Dialog.create()` to provide a better DX, with specialized hooks based on the assumptions
* that dialog forms can be used either in creation or edit mode of a particular entity or array of entities.
*
* Use `openFormDialog.props` to define the `initialValue` prop into forms which will be opened by this helper.
*
* @param {object} options
* @param {import('vue').Component} options.component
* The component to open as a custom dialog
* @param {string} [options.initialValuePropName]
* Changes the name of the prop used to pass the initial value to the custom dialog component, which is `initialValue` by default.
* @param {*} [options.initialValue]
* If defined, will be provided to the component via `initialValue` prop.
* The form component is expected to create a local empty model for its purposes and populate it with the initial value.
* @param {*} [options.defaultValue]
* If `initialValue` option isn't defined, this option will be provided to the component via `initialValue` prop to set .
* @param {Function} [options.onSave]
* Runs when the dialog is closed via Quasar `onOk` hook, even when the dialog has been opened in creation mode.
* The dialog result is provided as parameter: if you're not interested into it, always define the hook as `() => {}` to avoid side effects.
* @param {Function} [options.onCreate]
* Runs when the dialog is closed via Quasar `onOk` hook and has been opened in creation mode, meaning when `initialValue` option isn't falsy.
* It is assured to run after `onSave` hook completes.
* The dialog result is provided as parameter: if you're not interested into it, always define the hook as `() => {}` to avoid side effects.
* @param {Function} [options.onCancel]
* Same as QDialog's hook with the same name.
* @param {Function} [options.onDismiss]
* Same as QDialog's hook with the same name.
* @param {object} [options.props]
* Add any custom dialog component prop. Avoid `component` prop name as it would overwrite `component` option.
*
* @example
* // Into the component that will be opened by the helper, when using Composition API and JS
* export default defineComponent({
* name: 'UserFormDialog',
* props: {
* ...openFormDialog.props
* // ... your props
* }
* })
*
* // OR, with script setup
*
* const props = defineProps({
* ...openFormDialog.props
* // ... your props
* })
*
* // Into the component using the helper
* const user = { id: 5, name: 'test', role: 'admin' }
*
* // Create
* async function showCreateForm() {
* openFormDialog({
* component: UserFormDialog,
* defaultValue: { role: 'user' },
* onCreate: () => {
* // ...
* },
* })
* }
*
* // Update
* async function showUpdateForm() {
* openFormDialog({
* component: UserFormDialog,
* initialValue: user,
* onSave: () => {
* // ...
* },
* })
* }
*
* // Either create or update
* async function showForm(maybeUser) {
* openFormDialog({
* component: UserFormDialog,
* initialValue: maybeUser,
* defaultValue: { role: 'user' },
* onSave: () => {
* // ...
* },
* onCreate: () => {
* // ...
* },
* })
* }
*
* // Use a custom initialValue prop name
* async function showUserForm() {
* openFormDialog({
* component: CustomUserFormDialog,
* initialValuePropName: 'initialUserModel',
* initialValue: user,
* onSave: () => {
* // ...
* },
* })
* }
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function openFormDialog<T = any>({
component,
initialValuePropName = DEFAULT_INITIAL_VALUE_PROP_NAME,
initialValue = undefined,
defaultValue = undefined,
onSave = () => undefined,
onCreate = () => undefined,
onCancel = () => undefined,
onDismiss = () => undefined,
props = {},
}: OpenFormDialogOptions<T>) {
const isCreate = !initialValue;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
props[initialValuePropName] = initialValue ?? defaultValue;
Dialog.create({
component,
componentProps: props,
})
.onOk(async (result) => {
await onSave(result);
isCreate && (await onCreate(result));
})
.onCancel(onCancel)
.onDismiss(onDismiss);
}
openFormDialog.props = {
[DEFAULT_INITIAL_VALUE_PROP_NAME]: {
type: Object,
default: undefined,
},
};
import { useDialogPluginComponent } from "quasar";
/**
* Wrap `useDialogPluginComponent` helper to also set appearance & behaviour common to all modals.
* Props and listeners must then be applied to a q-dialog component.
*
* @example
* // You can also destructure and use any method/property from `useDialogPluginComponent`
* const { dialogProps, dialogEvents } = useDialogWithDefaults()
*
* <q-dialog v-bind="dialogProps" v-on="dialogEvents" />
*/
export function useDialogWithDefaults() {
const dialogComposable = useDialogPluginComponent();
const dialogProps = {
// Always make sure this name match the dialogComposable property name returned by the composable
// This is used to dynamically bind the ref to q-dialog component
ref: "dialogRef",
// ... add your own defaults, es.:
// persistent: true,
// maximized: true,
};
const dialogEvents = {
hide: dialogComposable.onDialogHide,
};
return { ...dialogComposable, dialogProps, dialogEvents };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment