FormDevexpress, Grid ve Tree setReadOnly

This commit is contained in:
Sedat Öztürk 2026-06-01 23:51:59 +03:00
parent 5e6d2f518b
commit daf0d51960
6 changed files with 285 additions and 93 deletions

View file

@ -1540,7 +1540,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
}), }),
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(), InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] { FormFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
new() { FieldName = "Status", FieldDbType = DbType.String, Value = "Aktif", CustomValueType = FieldCustomValueTypeEnum.Value }, new() { FieldName = "Status", FieldDbType = DbType.String, Value = "active", CustomValueType = FieldCustomValueTypeEnum.Value },
}), }),
CommandColumnJson = JsonSerializer.Serialize(new CommandColumnDto[] { CommandColumnJson = JsonSerializer.Serialize(new CommandColumnDto[] {
new() { new() {
@ -1638,8 +1638,8 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
DisplayExpr = "name", DisplayExpr = "name",
ValueExpr = "key", ValueExpr = "key",
LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] { LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] {
new () { Key="Aktif", Name="Aktif" }, new () { Key= "active", Name= "Aktif" },
new () { Key="Pasif", Name="Pasif" }, new () { Key= "passive", Name= "Pasif" },
}), }),
}), }),
ValidationRuleJson = DefaultValidationRuleRequiredJson, ValidationRuleJson = DefaultValidationRuleRequiredJson,
@ -3044,7 +3044,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
ValueExpr = "key", ValueExpr = "key",
LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] { LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] {
new () { Key= "active", Name= "Aktif" }, new () { Key= "active", Name= "Aktif" },
new () { Key= "passive", Name= "Kapalı" }, new () { Key= "passive", Name= "Pasif" },
}), }),
}), }),
ValidationRuleJson = DefaultValidationRuleRequiredJson, ValidationRuleJson = DefaultValidationRuleRequiredJson,

View file

@ -0,0 +1,29 @@
export type EditorScriptRuntimeContext = {
formData: Record<string, any>
e: any
editor: any
runtimeSetEditorReadOnly?: (field: string, readOnly: boolean) => void
setFormData?: (newData: any) => void
}
export const executeEditorScript = (
script: string,
{
formData,
e,
editor,
runtimeSetEditorReadOnly,
setFormData,
}: EditorScriptRuntimeContext,
) => {
const executor = new Function(
'formData',
'e',
'editor',
'runtimeSetEditorReadOnly',
'setFormData',
script,
)
return executor(formData, e, editor, runtimeSetEditorReadOnly, setFormData)
}

View file

@ -1,4 +1,5 @@
import { DX_CLASSNAMES } from '@/constants/app.constant' import { DX_CLASSNAMES } from '@/constants/app.constant'
import { executeEditorScript } from '@/utils/editorScriptRuntime'
import { import {
Form as FormDx, Form as FormDx,
FormRef, FormRef,
@ -6,7 +7,7 @@ import {
SimpleItem as SimpleItemDx, SimpleItem as SimpleItemDx,
} from 'devextreme-react/form' } from 'devextreme-react/form'
import { FieldDataChangedEvent, GroupItem } from 'devextreme/ui/form' import { FieldDataChangedEvent, GroupItem } from 'devextreme/ui/form'
import { Dispatch, RefObject, useEffect, useRef } from 'react' import { Dispatch, RefObject, useEffect, useRef, useState } from 'react'
import { GridBoxEditorComponent } from './editors/GridBoxEditorComponent' import { GridBoxEditorComponent } from './editors/GridBoxEditorComponent'
import { ImageUploadEditorComponent } from './editors/ImageUploadEditorComponent' import { ImageUploadEditorComponent } from './editors/ImageUploadEditorComponent'
import { TagBoxEditorComponent } from './editors/TagBoxEditorComponent' import { TagBoxEditorComponent } from './editors/TagBoxEditorComponent'
@ -157,6 +158,8 @@ const FormDevExpress = (props: {
const formItemsRef = useRef(formItems) const formItemsRef = useRef(formItems)
const formInstanceRef = useRef<any>() const formInstanceRef = useRef<any>()
const lastContentReadyScriptKeyRef = useRef<string>() const lastContentReadyScriptKeyRef = useRef<string>()
const [runtimeReadOnlyFields, setRuntimeReadOnlyFields] = useState<Record<string, boolean>>({})
const runtimeReadOnlyFieldsRef = useRef<Record<string, boolean>>({})
useEffect(() => { useEffect(() => {
formDataRef.current = formData formDataRef.current = formData
@ -166,6 +169,143 @@ const FormDevExpress = (props: {
formItemsRef.current = formItems formItemsRef.current = formItems
}, [formItems]) }, [formItems])
useEffect(() => {
runtimeReadOnlyFieldsRef.current = runtimeReadOnlyFields
}, [runtimeReadOnlyFields])
const setRuntimeEditorReadOnly = (field: string, readOnly: boolean) => {
const resolvedField = findFormFieldKey(formItemsRef.current, field)
const key = String(resolvedField || field || '').toLowerCase()
if (!key || runtimeReadOnlyFieldsRef.current[key] === readOnly) {
return
}
runtimeReadOnlyFieldsRef.current = {
...runtimeReadOnlyFieldsRef.current,
[key]: readOnly,
}
setRuntimeReadOnlyFields(runtimeReadOnlyFieldsRef.current)
}
const getRuntimeEditorReadOnly = (formItem: SimpleItemWithColData) => {
const field = formItem.dataField || formItem.name
const resolvedField = findFormFieldKey(formItemsRef.current, field || '')
const key = String(resolvedField || field || '').toLowerCase()
return Object.prototype.hasOwnProperty.call(runtimeReadOnlyFields, key)
? runtimeReadOnlyFields[key]
: undefined
}
const applyEditorReadOnly = (form: any, field: string, readOnly: boolean) => {
setRuntimeEditorReadOnly(field, readOnly)
setFormEditorReadOnly(form, field, readOnly)
setTimeout(() => setFormEditorReadOnly(formInstanceRef.current ?? form, field, readOnly), 0)
}
const runEditorScript = (
formItem: SimpleItemWithColData,
eventValue: any,
component?: any,
) => {
if (!formItem?.editorScript) {
return
}
const form = formInstanceRef.current ?? component
const dataField = formItem.dataField
const nextFormData = {
...(formDataRef.current || {}),
...(form?.option?.('formData') || {}),
...(dataField ? { [dataField]: eventValue } : {}),
}
formDataRef.current = nextFormData
try {
const editor = {
dataField,
component: form,
}
const formData = nextFormData
const e = {
component: form,
dataField,
value: eventValue,
}
const runtimeSetEditorReadOnly = (field: string, readOnly: boolean) =>
applyEditorReadOnly(form, field, readOnly)
executeEditorScript(formItem.editorScript, {
formData,
e,
editor,
runtimeSetEditorReadOnly,
})
} catch (err) {
console.error('Script execution failed for', formItem.name, err)
}
}
const getEditorOptions = (formItem: SimpleItemWithColData, index?: number) => {
const runtimeReadOnly = getRuntimeEditorReadOnly(formItem)
const prevOnValueChanged = formItem.editorOptions?.onValueChanged
return {
...(index !== undefined && mode !== 'view' ? { autoFocus: index === 1 } : {}),
...(formItem.editorType === 'dxDateBox'
? {
useMaskBehavior: true,
openOnFieldClick: true,
showClearButton: true,
}
: {}),
...(formItem.colData?.placeHolder
? { placeholder: translate('::' + formItem.colData.placeHolder) }
: {}),
...formItem.editorOptions,
...(runtimeReadOnly !== undefined ? { readOnly: runtimeReadOnly } : {}),
...(mode === 'view' ? { readOnly: true } : {}),
...(formItem.editorScript
? {
onValueChanged: (e: any) => {
if (typeof prevOnValueChanged === 'function') {
prevOnValueChanged(e)
}
if (formItem.dataField) {
const nextFormData = {
...(formDataRef.current || {}),
...(formInstanceRef.current?.option?.('formData') || {}),
[formItem.dataField]: e?.value,
}
formDataRef.current = nextFormData
formInstanceRef.current?.option?.('formData', nextFormData)
setFormData(nextFormData)
}
runEditorScript(formItem, e?.value, formInstanceRef.current)
},
}
: {}),
buttons: (formItem.editorOptions?.buttons || []).map((btn: any) => {
if (btn?.options?.onClick && typeof btn.options.onClick === 'string') {
const origClick = eval(`(${btn.options.onClick})`)
btn.options.onClick = (e: any) => {
origClick({
...e,
formData: formDataRef.current,
fieldName: formItem.dataField,
mode,
})
}
}
return btn
}),
}
}
const getFormItemKey = (formItem: SimpleItemWithColData, index: number) => {
const runtimeReadOnly = getRuntimeEditorReadOnly(formItem)
return `formItem-${formItem.dataField || formItem.name || index}-${String(runtimeReadOnly)}`
}
// formItems değiştiğinde (özellikle cascading alanlar için) editörlerin dataSource'larını güncelle // formItems değiştiğinde (özellikle cascading alanlar için) editörlerin dataSource'larını güncelle
useEffect(() => { useEffect(() => {
if (!refForm.current?.instance()) return if (!refForm.current?.instance()) return
@ -250,9 +390,14 @@ const FormDevExpress = (props: {
value: getValueByField(currentFormData, formItem.dataField), value: getValueByField(currentFormData, formItem.dataField),
} }
const runtimeSetEditorReadOnly = (field: string, readOnly: boolean) => const runtimeSetEditorReadOnly = (field: string, readOnly: boolean) =>
setFormEditorReadOnly(form, field, readOnly) applyEditorReadOnly(form, field, readOnly)
eval(formItem.editorScript!) executeEditorScript(formItem.editorScript!, {
formData,
e,
editor,
runtimeSetEditorReadOnly,
})
} catch (err) { } catch (err) {
console.error('Script execution failed on contentReady for', formItem.name, err) console.error('Script execution failed on contentReady for', formItem.name, err)
} }
@ -313,6 +458,7 @@ const FormDevExpress = (props: {
}) })
if (hasChanges) { if (hasChanges) {
formDataRef.current = newFormData
setFormData(newFormData) setFormData(newFormData)
} }
@ -321,35 +467,6 @@ const FormDevExpress = (props: {
updateCascadeDisabledStates() updateCascadeDisabledStates()
}, 0) }, 0)
//Dinamik script
const changeItem = formItemsRef.current
.flatMap((group) => flattenFormItems([group]))
.find(
(i: SimpleItemWithColData) =>
String(i.dataField || '').toLowerCase() === String(e.dataField || '').toLowerCase(),
)
if (changeItem?.editorScript) {
try {
const form = e.component
const editor = {
dataField: changeItem.dataField,
component: form,
}
const formData = newFormData
formDataRef.current = newFormData
const runtimeSetEditorReadOnly = (field: string, readOnly: boolean) =>
setFormEditorReadOnly(formInstanceRef.current ?? form, field, readOnly)
//setFormData({...formData, Path: e.value});
//UiEvalService.ApiGenerateBackgroundWorkers();
//setFormData({ ...formData, Path: (v => v === '1' ? '1-deneme' : v === '0' ? '0-deneme' : '')(e.value) })
eval(changeItem.editorScript)
} catch (err) {
console.error('Script execution failed for', changeItem.name, err)
}
}
}} }}
onContentReady={(e) => { onContentReady={(e) => {
formInstanceRef.current = e.component formInstanceRef.current = e.component
@ -383,7 +500,7 @@ const FormDevExpress = (props: {
return formItem.editorType2 === PlatformEditorTypes.dxTagBox ? ( return formItem.editorType2 === PlatformEditorTypes.dxTagBox ? (
<SimpleItemDx <SimpleItemDx
cssClass="font-semibold" cssClass="font-semibold"
key={'formItem-' + i} key={getFormItemKey(formItem, i)}
{...formItem} {...formItem}
render={() => ( render={() => (
<TagBoxEditorComponent <TagBoxEditorComponent
@ -393,12 +510,12 @@ const FormDevExpress = (props: {
options={formItem.tagBoxOptions} options={formItem.tagBoxOptions}
col={formItem.colData} col={formItem.colData}
onValueChanged={(e: any) => { onValueChanged={(e: any) => {
setFormData({ ...formData, [formItem.dataField!]: e }) const newData = { ...formDataRef.current, [formItem.dataField!]: e }
}} formDataRef.current = newData
editorOptions={{ setFormData(newData)
...formItem.editorOptions, runEditorScript(formItem, e, formInstanceRef.current)
...(mode === 'view' ? { readOnly: true } : {}),
}} }}
editorOptions={getEditorOptions(formItem)}
></TagBoxEditorComponent> ></TagBoxEditorComponent>
)} )}
label={{ label={{
@ -409,7 +526,7 @@ const FormDevExpress = (props: {
) : formItem.editorType2 === PlatformEditorTypes.dxGridBox ? ( ) : formItem.editorType2 === PlatformEditorTypes.dxGridBox ? (
<SimpleItemDx <SimpleItemDx
cssClass="font-semibold" cssClass="font-semibold"
key={'formItem-' + i} key={getFormItemKey(formItem, i)}
{...formItem} {...formItem}
render={() => ( render={() => (
<GridBoxEditorComponent <GridBoxEditorComponent
@ -418,12 +535,12 @@ const FormDevExpress = (props: {
options={formItem.gridBoxOptions} options={formItem.gridBoxOptions}
col={formItem.colData} col={formItem.colData}
onValueChanged={(e: any) => { onValueChanged={(e: any) => {
setFormData({ ...formData, [formItem.dataField!]: e }) const newData = { ...formDataRef.current, [formItem.dataField!]: e }
}} formDataRef.current = newData
editorOptions={{ setFormData(newData)
...formItem.editorOptions, runEditorScript(formItem, e, formInstanceRef.current)
...(mode === 'view' ? { readOnly: true } : {}),
}} }}
editorOptions={getEditorOptions(formItem)}
></GridBoxEditorComponent> ></GridBoxEditorComponent>
)} )}
label={{ label={{
@ -434,7 +551,7 @@ const FormDevExpress = (props: {
) : formItem.editorType2 === PlatformEditorTypes.dxImageUpload ? ( ) : formItem.editorType2 === PlatformEditorTypes.dxImageUpload ? (
<SimpleItemDx <SimpleItemDx
cssClass="font-semibold" cssClass="font-semibold"
key={'formItem-' + i} key={getFormItemKey(formItem, i)}
dataField={formItem.dataField} dataField={formItem.dataField}
name={formItem.name} name={formItem.name}
colSpan={formItem.colSpan} colSpan={formItem.colSpan}
@ -444,12 +561,12 @@ const FormDevExpress = (props: {
value={formData[formItem.dataField!]} value={formData[formItem.dataField!]}
options={formItem.imageUploadOptions} options={formItem.imageUploadOptions}
onValueChanged={(val: any) => { onValueChanged={(val: any) => {
setFormData({ ...formData, [formItem.dataField!]: val }) const newData = { ...formDataRef.current, [formItem.dataField!]: val }
}} formDataRef.current = newData
editorOptions={{ setFormData(newData)
...formItem.editorOptions, runEditorScript(formItem, val, formInstanceRef.current)
...(mode === 'view' ? { readOnly: true } : {}),
}} }}
editorOptions={getEditorOptions(formItem)}
/> />
)} )}
label={{ label={{
@ -460,37 +577,9 @@ const FormDevExpress = (props: {
) : ( ) : (
<SimpleItemDx <SimpleItemDx
cssClass="font-semibold" cssClass="font-semibold"
key={'formItem-' + i} key={getFormItemKey(formItem, i)}
{...formItem} {...formItem}
editorOptions={{ editorOptions={getEditorOptions(formItem, i)}
...(mode === 'view' ? {} : { autoFocus: i === 1 }),
...(formItem.editorType === 'dxDateBox'
? {
useMaskBehavior: true,
openOnFieldClick: true,
showClearButton: true,
}
: {}),
...(formItem.colData?.placeHolder
? { placeholder: translate('::' + formItem.colData.placeHolder) }
: {}),
...formItem.editorOptions,
...(mode === 'view' ? { readOnly: true } : {}),
buttons: (formItem.editorOptions?.buttons || []).map((btn: any) => {
if (btn?.options?.onClick && typeof btn.options.onClick === 'string') {
const origClick = eval(`(${btn.options.onClick})`)
btn.options.onClick = (e: any) => {
origClick({
...e,
formData: formDataRef.current,
fieldName: formItem.dataField,
mode,
})
}
}
return btn
}),
}}
label={{ text: translate('::' + formItem.colData?.captionName) }} label={{ text: translate('::' + formItem.colData?.captionName) }}
/> />
) )

View file

@ -16,6 +16,7 @@ import {
} from '@/services/list-form-customization.service' } from '@/services/list-form-customization.service'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import useResponsive from '@/utils/hooks/useResponsive' import useResponsive from '@/utils/hooks/useResponsive'
import { executeEditorScript } from '@/utils/editorScriptRuntime'
import { Template } from 'devextreme-react/core/template' import { Template } from 'devextreme-react/core/template'
import DataGrid, { import DataGrid, {
ColumnChooser, ColumnChooser,
@ -211,6 +212,7 @@ const setFormEditorReadOnly = (form: any, field: string, readOnly: boolean) => {
} }
apply() apply()
setTimeout(apply, 0)
return true return true
} }
@ -826,7 +828,13 @@ const Grid = (props: GridProps) => {
} }
} }
eval(formItem.editorScript!) executeEditorScript(formItem.editorScript!, {
formData,
e,
editor,
runtimeSetEditorReadOnly,
setFormData,
})
} catch (err) { } catch (err) {
console.error('Script exec error', formItem.dataField, err) console.error('Script exec error', formItem.dataField, err)
} }
@ -1550,9 +1558,19 @@ const Grid = (props: GridProps) => {
} }
const runReadOnlyScripts = () => { const runReadOnlyScripts = () => {
const editorValues = gridDto.gridOptions.editingFormDto
.flatMap((group) => flattenEditingFormItems([group]))
.reduce<Record<string, any>>((values, formItem) => {
const editorInstance = form?.getEditor?.(formItem.dataField)
if (editorInstance?.option) {
values[formItem.dataField] = editorInstance.option('value')
}
return values
}, {})
const formData = { const formData = {
...editingFormDataRef.current, ...editingFormDataRef.current,
...(form?.option?.('formData') || {}), ...(form?.option?.('formData') || {}),
...editorValues,
} }
editingFormDataRef.current = { ...formData } editingFormDataRef.current = { ...formData }
@ -1563,6 +1581,10 @@ const Grid = (props: GridProps) => {
) )
.forEach((formItem) => { .forEach((formItem) => {
try { try {
const editorInstance = form?.getEditor?.(formItem.dataField)
const editorValue =
editorInstance?.option?.('value') ??
getValueByField(formData, formItem.dataField)
const editor = { const editor = {
dataField: formItem.dataField, dataField: formItem.dataField,
component: grid, component: grid,
@ -1570,10 +1592,16 @@ const Grid = (props: GridProps) => {
const e = { const e = {
component: form, component: form,
dataField: formItem.dataField, dataField: formItem.dataField,
value: getValueByField(formData, formItem.dataField), value: editorValue,
} }
eval(formItem.editorScript!) executeEditorScript(formItem.editorScript!, {
formData,
e,
editor,
runtimeSetEditorReadOnly,
setFormData,
})
} catch (err) { } catch (err) {
console.error('Script exec error on contentReady', formItem.dataField, err) console.error('Script exec error on contentReady', formItem.dataField, err)
} }
@ -1619,7 +1647,16 @@ const Grid = (props: GridProps) => {
} }
} }
eval(formItem.editorScript) executeEditorScript(formItem.editorScript, {
formData,
e,
editor: {
dataField: e.dataField,
component: grid,
},
runtimeSetEditorReadOnly,
setFormData,
})
} catch (err) { } catch (err) {
console.error('Script exec error', err) console.error('Script exec error', err)
} }

View file

@ -1,6 +1,7 @@
import Container from '@/components/shared/Container' import Container from '@/components/shared/Container'
import { Dialog, Notification, toast } from '@/components/ui' import { Dialog, Notification, toast } from '@/components/ui'
import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant' import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant'
import { executeEditorScript } from '@/utils/editorScriptRuntime'
import { import {
DbTypeEnum, DbTypeEnum,
EditingFormItemDto, EditingFormItemDto,
@ -199,6 +200,7 @@ const setFormEditorReadOnly = (form: any, field: string, readOnly: boolean) => {
} }
apply() apply()
setTimeout(apply, 0)
return true return true
} }
@ -784,7 +786,13 @@ const Tree = (props: TreeProps) => {
} }
} }
eval(formItem.editorScript!) executeEditorScript(formItem.editorScript!, {
formData,
e,
editor,
runtimeSetEditorReadOnly,
setFormData,
})
} catch (err) { } catch (err) {
console.error('Script exec error', formItem.dataField, err) console.error('Script exec error', formItem.dataField, err)
} }
@ -1212,9 +1220,19 @@ const Tree = (props: TreeProps) => {
} }
const runReadOnlyScripts = () => { const runReadOnlyScripts = () => {
const editorValues = gridDto.gridOptions.editingFormDto
.flatMap((group) => flattenEditingFormItems([group]))
.reduce<Record<string, any>>((values, formItem) => {
const editorInstance = form?.getEditor?.(formItem.dataField)
if (editorInstance?.option) {
values[formItem.dataField] = editorInstance.option('value')
}
return values
}, {})
const formData = { const formData = {
...editingFormDataRef.current, ...editingFormDataRef.current,
...(form?.option?.('formData') || {}), ...(form?.option?.('formData') || {}),
...editorValues,
} }
editingFormDataRef.current = { ...formData } editingFormDataRef.current = { ...formData }
@ -1225,6 +1243,10 @@ const Tree = (props: TreeProps) => {
) )
.forEach((formItem) => { .forEach((formItem) => {
try { try {
const editorInstance = form?.getEditor?.(formItem.dataField)
const editorValue =
editorInstance?.option?.('value') ??
getValueByField(formData, formItem.dataField)
const editor = { const editor = {
dataField: formItem.dataField, dataField: formItem.dataField,
component: grid, component: grid,
@ -1232,10 +1254,16 @@ const Tree = (props: TreeProps) => {
const e = { const e = {
component: form, component: form,
dataField: formItem.dataField, dataField: formItem.dataField,
value: getValueByField(formData, formItem.dataField), value: editorValue,
} }
eval(formItem.editorScript!) executeEditorScript(formItem.editorScript!, {
formData,
e,
editor,
runtimeSetEditorReadOnly,
setFormData,
})
} catch (err) { } catch (err) {
console.error('Script exec error on contentReady', formItem.dataField, err) console.error('Script exec error on contentReady', formItem.dataField, err)
} }
@ -1281,7 +1309,16 @@ const Tree = (props: TreeProps) => {
} }
} }
eval(formItem.editorScript) executeEditorScript(formItem.editorScript, {
formData,
e,
editor: {
dataField: e.dataField,
component: grid,
},
runtimeSetEditorReadOnly,
setFormData,
})
} catch (err) { } catch (err) {
console.error('Script exec error', err) console.error('Script exec error', err)
} }