Skip to content

Commit

Permalink
Maintenance: Refactor public interface for reactive data in forms.
Browse files Browse the repository at this point in the history
  • Loading branch information
fliebe92 authored and dominikklein committed Dec 6, 2023
1 parent 9a30203 commit 92300dd
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 46 deletions.
Expand Up @@ -42,21 +42,20 @@ export const useTicketDuplicateDetectionHandler = (

const handleTicketDuplicateDetection: FormHandlerFunction = async (
execution,
formNode,
values,
changeFields,
updateSchemaDataField,
schemaData,
changedField,
reactivity,
data,
) => {
const { changedField } = data
const { schemaData } = reactivity

if (!executeHandler(execution, schemaData, changedField)) return

const data =
const newFieldData =
changedField?.newValue as unknown as TicketDuplicateDetectionPayload

if (!data?.count) return
if (!newFieldData?.count) return

showTicketDuplicateDetectionDialog(data)
showTicketDuplicateDetectionDialog(newFieldData)
}

return {
Expand Down
Expand Up @@ -249,13 +249,12 @@ export const useTicketEditForm = (ticket: Ref<TicketById | undefined>) => {

const handleArticleType: FormHandlerFunction = (
execution,
formNode,
values,
changeFields,
updateSchemaDataField,
schemaData,
changedField,
reactivity,
data,
) => {
const { formNode, changedField } = data
const { schemaData } = reactivity

if (
!executeHandler(execution, schemaData, changedField) ||
!ticket.value ||
Expand Down
22 changes: 13 additions & 9 deletions app/frontend/shared/components/Form/Form.vue
Expand Up @@ -74,13 +74,13 @@ import FormGroup from './FormGroup.vue'
export interface Props {
id?: string
schema?: FormSchemaNode[]
formUpdaterId?: EnumFormUpdaterId
schemaData?: Except<ReactiveFormSchemData, 'fields'>
handlers?: FormHandler[]
changeFields?: Record<string, Partial<FormSchemaField>>
formUpdaterId?: EnumFormUpdaterId
// Maybe in the future this is no longer needed, when FormKit supports group
// without value grouping below group name (https://github.com/formkit/formkit/issues/461).
flattenFormGroups?: string[]
schemaData?: Except<ReactiveFormSchemData, 'fields'>
formKitPlugins?: FormKitPlugin[]
formKitSectionsSchema?: Record<
string,
Expand Down Expand Up @@ -774,13 +774,17 @@ const executeFormHandler = (
formHandlerExecution[execution].forEach((handler) => {
handler(
execution,
formNode.value,
currentValues,
changeFields,
updateSchemaDataField,
schemaData,
changedField,
props.initialEntityObject,
{
changeFields,
updateSchemaDataField,
schemaData,
},
{
formNode: formNode.value,
values: currentValues,
changedField,
initialEntityObject: props.initialEntityObject,
},
)
})
}
Expand Down
28 changes: 19 additions & 9 deletions app/frontend/shared/components/Form/types.ts
Expand Up @@ -196,17 +196,27 @@ export enum FormHandlerExecution {
FieldChange = 'fieldChange',
}

export type FormHandlerFunction = (
execution: FormHandlerExecution,
formNode: FormKitNode | undefined,
values: FormValues,
changeFields: Ref<Record<string, Partial<FormSchemaField>>>,
export interface FormHandlerFunctionData {
formNode: FormKitNode | undefined
values: FormValues
changedField?: ChangedField
initialEntityObject?: ObjectLike
}

export interface FormHandlerFunctionReactivity {
changeFields: Ref<Record<string, Partial<FormSchemaField>>>
schemaData: ReactiveFormSchemData
// This can be used to update the current schema data, but without remembering it inside
// the changeFields and schemaData objects (which means it's persistent).
updateSchemaDataField: (
field: FormSchemaField | SetRequired<Partial<FormSchemaField>, 'name'>,
) => void,
schemaData: ReactiveFormSchemData,
changedField?: ChangedField,
initialEntityObject?: ObjectLike,
) => void
}

export type FormHandlerFunction = (
execution: FormHandlerExecution,
reactivity: FormHandlerFunctionReactivity,
data: FormHandlerFunctionData,
) => void

export interface FormHandler {
Expand Down
10 changes: 4 additions & 6 deletions app/frontend/shared/composables/useTicketSignature.ts
Expand Up @@ -51,13 +51,11 @@ export const useTicketSignature = (ticket?: Ref<TicketById | undefined>) => {
const signatureHandling = (editorName: string): FormHandler => {
const handleSignature: FormHandlerFunction = (
execution,
formNode,
values,
changeFields,
updateSchemaDataField,
schemaData,
changedField,
reactivity,
data,
) => {
const { formNode, values, changedField } = data

if (
changedField?.name !== 'group_id' &&
changedField?.name !== 'articleSenderType'
Expand Down
Expand Up @@ -33,15 +33,13 @@ export const useTicketFormOganizationHandler = (): FormHandler => {

const handleOrganizationField: FormHandlerFunction = (
execution,
formNode,
values,
changeFields,
updateSchemaDataField,
schemaData,
changedField,
initialEntityObject,
reactivity,
data,
// eslint-disable-next-line sonarjs/cognitive-complexity
) => {
const { formNode, values, initialEntityObject, changedField } = data
const { schemaData, changeFields, updateSchemaDataField } = reactivity

if (!executeHandler(execution, schemaData, changedField)) return

const session = useSessionStore()
Expand Down
1 change: 1 addition & 0 deletions doc/developer_manual/index.md
Expand Up @@ -16,6 +16,7 @@ Welcome to the developer docs of Zammad. 👋 This is a work in progress, and yo
- [How to add an SVG Icon](standards/how-to-add-an-svg-icon.md)
- [How to handle localization & translations](standards/how-to-handle-localization.md)
- [How to rebuild the chat](standards/how-to-rebuild-the-chat.md)
- [How to use forms](standards/how-to-use-forms.md)

# Cookbook / Recipes

Expand Down
59 changes: 59 additions & 0 deletions doc/developer_manual/standards/how-to-use-forms.md
@@ -0,0 +1,59 @@
# How to Use Forms

## Basics

Forms in Zammad are based on [FormKit](https://formkit.com/) and the documentation is referenced in the following paragraphs.

They are defined by the `schema`. The schema data describes the form containing all needed form fields, e.g. [the ticket creation screen](https://github.com/zammad/zammad/blob/develop/app/frontend/apps/mobile/pages/ticket/views/TicketCreate.vue#L121). For more information, please see the [Formkit schema essentials description](https://formkit.com/essentials/schema).

## Usage of Reactivity

The forms provide reactivity to modify the form and form fields on events, e.g. user input or data manipulation.

### Schema Data

In addition to the static schema, the `Form` component can also include a `schemaData` prop. Values from the data object and properties can then be referenced, and your schema will maintain the reactivity of the original data object.

To reference a value from the data object, you simply use `$` followed by the property name from the data object. References can be used in the schema `attrs`, `props`, `conditionals`, and as children. Please have a look at the [FormKit references page](https://formkit.com/essentials/schema#references).

In our implementation, the current form values are always available as `$values`.

Example:

```ts
const schemaData = reactive({
securityIntegration: false,
})
```

Example (excerpt of static schema)

```ts
{
if: '$securityIntegration === true && $values.articleSenderType === "email-out"',
name: 'security',
label: __('Security'),
type: 'security',
}
```

### Change Fields

The `changeFields` is a reactive extension for the form implementation.

This is our preferred way of changing the state of fields after a user interacts with the form. This should be manipulated with the `changed` event of the form.

A simple use case is to mark some fields as mandatory after a user selects the value `Support Request` in the field `Category`.

### Handlers

This is the most powerful way to influence the behavior of the current form's reactivity.

If you need to share code between multiple forms that relate to reactivity, you have to use form handlers.

The form handler supports two execution types:

- `Initial`
- `FieldChange`

For a working example of a handler, please have a look at the [ticket signature code](https://github.com/zammad/zammad/blob/develop/app/frontend/shared/composables/useTicketSignature.ts).

0 comments on commit 92300dd

Please sign in to comment.