Skip to content

Commit

Permalink
Feature: Mobile - Added possibility changeField prop together with th…
Browse files Browse the repository at this point in the history
…e form updater and automatic handling of initial relation value from the entity object.
  • Loading branch information
dominikklein committed Nov 7, 2022
1 parent 447b904 commit 25b7a86
Show file tree
Hide file tree
Showing 35 changed files with 310 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import type {
EnumObjectManagerObjects,
ObjectAttributeValue,
} from '@shared/graphql/types'
import type {
FormFieldValue,
FormSchemaField,
} from '@shared/components/Form/types'
import { MutationHandler } from '@shared/server/apollo/handler'
import type { ObjectLike } from '@shared/types/utils'
import Form from '@shared/components/Form/Form.vue'
Expand All @@ -26,6 +30,7 @@ export interface Props {
object?: ObjectLike
type: EnumObjectManagerObjects
formUpdaterId?: EnumFormUpdaterId
formChangeFields?: Record<string, Partial<FormSchemaField>>
errorNotificationMessage?: string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mutation: UseMutationReturn<any, any>
Expand All @@ -36,6 +41,12 @@ const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'success', data: unknown): void
(e: 'error'): void
(
e: 'changedField',
fieldName: string,
newValue: FormFieldValue,
oldValue: FormFieldValue,
): void
}>()

const updateQuery = new MutationHandler(props.mutation, {
Expand Down Expand Up @@ -72,6 +83,14 @@ const cancelDialog = async () => {
closeDialog(props.name)
}

const changedFormField = (
fieldName: string,
newValue: FormFieldValue,
oldValue: FormFieldValue,
) => {
emit('changedField', fieldName, newValue, oldValue)
}

const saveObject = async (formData: FormData) => {
const objectAttributeValues = objectAttributes.value
.filter(({ isInternal }) => !isInternal)
Expand Down Expand Up @@ -116,7 +135,7 @@ const saveObject = async (formData: FormData) => {
class="text-blue"
:disabled="isDisabled"
:class="{ 'opacity-50': isDisabled }"
@click="cancelDialog()"
@click="cancelDialog"
>
{{ $t('Cancel') }}
</button>
Expand All @@ -126,7 +145,7 @@ const saveObject = async (formData: FormData) => {
class="text-blue"
:disabled="isDisabled"
:class="{ 'opacity-50': isDisabled }"
@click="formSubmit()"
@click="formSubmit"
>
{{ $t('Save') }}
</button>
Expand All @@ -137,9 +156,11 @@ const saveObject = async (formData: FormData) => {
class="w-full p-4"
:schema="schema"
:initial-entity-object="initialFlatObject"
:change-fields="formChangeFields"
use-object-attributes
:form-updater-id="formUpdaterId"
@submit="saveObject($event)"
@changed="changedFormField"
@submit="saveObject"
/>
</CommonDialog>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const renderForm = () => {
id: 'faked-id',
name: 'Some Organization',
shared: true,
domain_assignment: false,
domainAssignment: false,
domain: '',
note: '',
active: false,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
// Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/

import type { FormFieldValue } from '@shared/components/Form/types'
import { useDialog } from '@shared/composables/useDialog'
import type { EnumObjectManagerObjects } from '@shared/graphql/types'
import type { Props } from './CommonDialogObjectForm.vue'

interface ObjectDescription extends Omit<Props, 'name' | 'type'> {
onSuccess?(data: unknown): void
onError?(): void
onChangedField?(
fieldName: string,
newValue: FormFieldValue,
oldValue: FormFieldValue,
): void
}

export const useDialogObjectForm = (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/

import { reactive } from 'vue'
import { useDialogObjectForm } from '@mobile/components/CommonDialogObjectForm/useDialogObjectForm'
import { defineFormSchema } from '@mobile/form/defineFormSchema'
import type { OrganizationQuery } from '@shared/graphql/types'
import type { FormSchemaField } from '@shared/components/Form/types'
import {
EnumFormUpdaterId,
EnumObjectManagerObjects,
Expand Down Expand Up @@ -39,10 +41,26 @@ export const useOrganizationEdit = () => {
const openEditOrganizationDialog = async (
organization: ConfidentTake<OrganizationQuery, 'organization'>,
) => {
const formChangeFields = reactive<Record<string, Partial<FormSchemaField>>>(
{
domain: {
required: !!organization.domainAssignment,
},
},
)

dialog.openDialog({
object: organization,
schema,
mutation,
formChangeFields,
onChangedField: (fieldName, newValue) => {
if (fieldName === 'domain_assignment') {
// TODO: Can be changed when we have the new toggle field (currently the value can also be a string).
formChangeFields.domain.required =
(typeof newValue === 'boolean' && newValue) || newValue === 'true'
}
},
formUpdaterId: EnumFormUpdaterId.FormUpdaterUpdaterOrganizationEdit,
errorNotificationMessage: __('Organization could not be updated.'),
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
EnumObjectManagerObjects,
} from '@shared/graphql/types'
import type { ConfidentTake } from '@shared/types/utils'
import { edgesToArray } from '@shared/utils/helpers'

export const useUserEdit = () => {
const dialog = useDialogObjectForm('user-edit', EnumObjectManagerObjects.User)
Expand All @@ -30,13 +29,7 @@ export const useUserEdit = () => {

const openEditUserDialog = async (user: ConfidentTake<UserQuery, 'user'>) => {
dialog.openDialog({
object: {
...user,
organization_id: user.organization?.internalId,
organization_ids: edgesToArray(user.secondaryOrganizations).map(
(item) => item.internalId,
),
},
object: user,
mutation,
schema,
formUpdaterId: EnumFormUpdaterId.FormUpdaterUpdaterUserEdit,
Expand Down
114 changes: 82 additions & 32 deletions app/frontend/shared/components/Form/Form.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!-- Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/ -->

<script setup lang="ts">
import { isEqual, cloneDeep } from 'lodash-es'
import { isEqual, cloneDeep, merge } from 'lodash-es'
import type { ConcreteComponent, Ref } from 'vue'
import {
computed,
Expand Down Expand Up @@ -31,6 +31,7 @@ import type { Stoppable } from '@vueuse/shared'
import { useTimeoutFn, watchOnce } from '@vueuse/shared'
import getUuid from '@shared/utils/getUuid'
import log from '@shared/utils/log'
import { camelize } from '@shared/utils/formatter'
import UserError from '@shared/errors/UserError'
import type {
EnumObjectManagerObjects,
Expand All @@ -43,6 +44,7 @@ import { QueryHandler } from '@shared/server/apollo/handler'
import { useObjectAttributeLoadFormFields } from '@shared/entities/object-attributes/composables/useObjectAttributeLoadFormFields'
import { useObjectAttributeFormFields } from '@shared/entities/object-attributes/composables/useObjectAttributeFormFields'
import testFlags from '@shared/utils/testFlags'
import { edgesToArray } from '@shared/utils/helpers'
import type { FormUpdaterTrigger } from '@shared/types/form'
import type { ObjectLike } from '@shared/types/utils'
import { useFormUpdaterQuery } from './graphql/queries/formUpdater.api'
Expand All @@ -69,7 +71,6 @@ export interface Props {
id?: string
schema?: FormSchemaNode[]
formUpdaterId?: EnumFormUpdaterId
// TODO: Add possibility to have an "initial" changeFields as a combination with the the formUpdater...
changeFields?: Record<string, Partial<FormSchemaField>>
formKitPlugins?: FormKitPlugin[]
formKitSectionsSchema?: Record<
Expand Down Expand Up @@ -128,7 +129,12 @@ if (!hasSchema.value) {
const localClass = toRef(props, 'class')

const emit = defineEmits<{
(e: 'changed', newValue: unknown, fieldName: string): void
(
e: 'changed',
fieldName: string,
newValue: FormFieldValue,
oldValue: FormFieldValue,
): void
(e: 'node', node: FormKitNode): void
(e: 'settled'): void
}>()
Expand Down Expand Up @@ -201,6 +207,7 @@ const values = computed<FormValues>(() => {
})

const relationFields: FormUpdaterRelationField[] = []
const relationFieldBelongsToObjectField: Record<string, string> = {}

const formUpdaterProcessing = computed(
() => formNode.value?.context?.state.formUpdaterProcessing || false,
Expand Down Expand Up @@ -284,29 +291,60 @@ const schemaData = reactive<ReactiveFormSchemData>({
fields: {},
})

const lookupSchemaFieldNames = computed(() => {
if (staticSchema.value.length === 0) return []
const internalFieldCamelizeName: Record<string, string> = {}

return Object.keys(schemaData.fields) || []
})
const getInitialEntityObjectValue = (fieldName: string): FormFieldValue => {
if (!props.initialEntityObject) return undefined

const localInitialValues = computed<Maybe<FormValues>>(() => {
if (props.initialValues) return props.initialValues
let value: FormFieldValue
if (relationFieldBelongsToObjectField[fieldName]) {
const belongsToObject =
props.initialEntityObject[relationFieldBelongsToObjectField[fieldName]]

if (props.initialEntityObject) {
const { initialEntityObject } = props
if ('edges' in belongsToObject) {
value = edgesToArray(
belongsToObject as { edges?: { node: { internalId: number } }[] },
).map((item) => item.internalId)
} else {
value = belongsToObject?.internalId
}
}

return lookupSchemaFieldNames.value.reduce(
(initialValues: FormValues, fieldName) => {
initialValues[fieldName] = initialEntityObject[fieldName]
return initialValues
},
{},
)
if (!value) {
value =
props.initialEntityObject[
internalFieldCamelizeName[fieldName] || fieldName
]
}

return null
})
return value
}

const localInitialValues: FormValues = props.initialValues || {}

const initializeFieldRelation = (
fieldName: string,
relation: FormSchemaField['relation'],
belongsToObjectField?: string,
) => {
if (relation) {
relationFields.push({
name: fieldName,
relation: relation.type,
filterIds: relation.filterIds,
})
}

if (belongsToObjectField) {
relationFieldBelongsToObjectField[fieldName] = belongsToObjectField
}
}

const setInternalField = (fieldName: string, internal: boolean) => {
if (!internal) return

internalFieldCamelizeName[fieldName] = camelize(fieldName)
}

const updateSchemaDataField = (
field: FormSchemaField | SetRequired<Partial<FormSchemaField>, 'name'>,
Expand Down Expand Up @@ -337,21 +375,27 @@ const updateSchemaDataField = (
),
}
} else {
if (relation) {
relationFields.push({
name: field.name,
relation: relation.type,
filterIds: relation.filterIds,
})
}
initializeFieldRelation(
field.name,
relation,
specificProps?.belongsToObjectField,
)

setInternalField(field.name, Boolean(fieldProps.internal))

const combinedFieldProps = Object.assign(fieldProps, specificProps)

// Select the correct initial value (at this time localInitialValues has not already the information
// from the initial entity object, so we need to check it manually).
combinedFieldProps.value =
localInitialValues.value?.[field.name] ??
props.initialEntityObject?.[field.name] ??
localInitialValues[field.name] ??
getInitialEntityObjectValue(field.name) ??
combinedFieldProps.value

// Save current initial value for later usage, when not already exists.
if (!(field.name in localInitialValues))
localInitialValues[field.name] = combinedFieldProps.value

schemaData.fields[field.name] = {
show: showField,
updateFields: updateFields || false,
Expand Down Expand Up @@ -488,7 +532,7 @@ const changedInputValueHandling = (inputNode: FormKitNode) => {
)
}

emit('changed', newValue, node.name)
emit('changed', node.name, newValue, oldValue)

previousValues.set(node, cloneDeep(newValue))
updaterChangedFields.delete(node.name)
Expand Down Expand Up @@ -654,7 +698,7 @@ const initializeFormSchema = () => {
formUpdaterVariables.value = markRaw({
id: props.initialEntityObject?.id,
formUpdaterId: props.formUpdaterId,
data: localInitialValues.value || {},
data: localInitialValues,
meta: {
initial: true,
formId,
Expand All @@ -673,12 +717,18 @@ const initializeFormSchema = () => {

formUpdaterQueryHandler.watchOnResult((queryResult) => {
if (queryResult?.formUpdater) {
updateChangedFields(queryResult.formUpdater)
updateChangedFields(
props.changeFields
? merge(queryResult.formUpdater, props.changeFields)
: queryResult.formUpdater,
)
}

if (!formSchemaInitialized.value) formSchemaInitialized.value = true
})
} else {
if (props.changeFields) updateChangedFields(props.changeFields)

formSchemaInitialized.value = true
}
}
Expand Down
Loading

0 comments on commit 25b7a86

Please sign in to comment.