From daf0d51960f1c8e1facb961272a50ba77e6c52f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Mon, 1 Jun 2026 23:51:59 +0300 Subject: [PATCH] FormDevexpress, Grid ve Tree setReadOnly --- .../Seeds/ListFormSeeder_Administration.cs | 8 +- ui/public/version.json | 2 +- ui/src/utils/editorScriptRuntime.ts | 29 ++ ui/src/views/form/FormDevExpress.tsx | 249 ++++++++++++------ ui/src/views/list/Grid.tsx | 45 +++- ui/src/views/list/Tree.tsx | 45 +++- 6 files changed, 285 insertions(+), 93 deletions(-) create mode 100644 ui/src/utils/editorScriptRuntime.ts diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs index 5e7dba8..c8732c0 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs @@ -1540,7 +1540,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep }), InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(), 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[] { new() { @@ -1638,8 +1638,8 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep DisplayExpr = "name", ValueExpr = "key", LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] { - new () { Key="Aktif", Name="Aktif" }, - new () { Key="Pasif", Name="Pasif" }, + new () { Key= "active", Name= "Aktif" }, + new () { Key= "passive", Name= "Pasif" }, }), }), ValidationRuleJson = DefaultValidationRuleRequiredJson, @@ -3044,7 +3044,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep ValueExpr = "key", LookupQuery = JsonSerializer.Serialize(new LookupDataDto[] { new () { Key= "active", Name= "Aktif" }, - new () { Key= "passive", Name= "Kapalı" }, + new () { Key= "passive", Name= "Pasif" }, }), }), ValidationRuleJson = DefaultValidationRuleRequiredJson, diff --git a/ui/public/version.json b/ui/public/version.json index 5bb593f..8bb90a9 100644 --- a/ui/public/version.json +++ b/ui/public/version.json @@ -137,4 +137,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/ui/src/utils/editorScriptRuntime.ts b/ui/src/utils/editorScriptRuntime.ts new file mode 100644 index 0000000..9c9af48 --- /dev/null +++ b/ui/src/utils/editorScriptRuntime.ts @@ -0,0 +1,29 @@ +export type EditorScriptRuntimeContext = { + formData: Record + 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) +} diff --git a/ui/src/views/form/FormDevExpress.tsx b/ui/src/views/form/FormDevExpress.tsx index 88906f9..bd8ae48 100644 --- a/ui/src/views/form/FormDevExpress.tsx +++ b/ui/src/views/form/FormDevExpress.tsx @@ -1,4 +1,5 @@ import { DX_CLASSNAMES } from '@/constants/app.constant' +import { executeEditorScript } from '@/utils/editorScriptRuntime' import { Form as FormDx, FormRef, @@ -6,7 +7,7 @@ import { SimpleItem as SimpleItemDx, } from 'devextreme-react/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 { ImageUploadEditorComponent } from './editors/ImageUploadEditorComponent' import { TagBoxEditorComponent } from './editors/TagBoxEditorComponent' @@ -157,6 +158,8 @@ const FormDevExpress = (props: { const formItemsRef = useRef(formItems) const formInstanceRef = useRef() const lastContentReadyScriptKeyRef = useRef() + const [runtimeReadOnlyFields, setRuntimeReadOnlyFields] = useState>({}) + const runtimeReadOnlyFieldsRef = useRef>({}) useEffect(() => { formDataRef.current = formData @@ -166,6 +169,143 @@ const FormDevExpress = (props: { formItemsRef.current = 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 useEffect(() => { if (!refForm.current?.instance()) return @@ -250,9 +390,14 @@ const FormDevExpress = (props: { value: getValueByField(currentFormData, formItem.dataField), } 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) { console.error('Script execution failed on contentReady for', formItem.name, err) } @@ -313,6 +458,7 @@ const FormDevExpress = (props: { }) if (hasChanges) { + formDataRef.current = newFormData setFormData(newFormData) } @@ -321,35 +467,6 @@ const FormDevExpress = (props: { updateCascadeDisabledStates() }, 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) => { formInstanceRef.current = e.component @@ -383,7 +500,7 @@ const FormDevExpress = (props: { return formItem.editorType2 === PlatformEditorTypes.dxTagBox ? ( ( { - setFormData({ ...formData, [formItem.dataField!]: e }) - }} - editorOptions={{ - ...formItem.editorOptions, - ...(mode === 'view' ? { readOnly: true } : {}), + const newData = { ...formDataRef.current, [formItem.dataField!]: e } + formDataRef.current = newData + setFormData(newData) + runEditorScript(formItem, e, formInstanceRef.current) }} + editorOptions={getEditorOptions(formItem)} > )} label={{ @@ -409,7 +526,7 @@ const FormDevExpress = (props: { ) : formItem.editorType2 === PlatformEditorTypes.dxGridBox ? ( ( { - setFormData({ ...formData, [formItem.dataField!]: e }) - }} - editorOptions={{ - ...formItem.editorOptions, - ...(mode === 'view' ? { readOnly: true } : {}), + const newData = { ...formDataRef.current, [formItem.dataField!]: e } + formDataRef.current = newData + setFormData(newData) + runEditorScript(formItem, e, formInstanceRef.current) }} + editorOptions={getEditorOptions(formItem)} > )} label={{ @@ -434,7 +551,7 @@ const FormDevExpress = (props: { ) : formItem.editorType2 === PlatformEditorTypes.dxImageUpload ? ( { - setFormData({ ...formData, [formItem.dataField!]: val }) - }} - editorOptions={{ - ...formItem.editorOptions, - ...(mode === 'view' ? { readOnly: true } : {}), + const newData = { ...formDataRef.current, [formItem.dataField!]: val } + formDataRef.current = newData + setFormData(newData) + runEditorScript(formItem, val, formInstanceRef.current) }} + editorOptions={getEditorOptions(formItem)} /> )} label={{ @@ -460,37 +577,9 @@ const FormDevExpress = (props: { ) : ( { - 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 - }), - }} + editorOptions={getEditorOptions(formItem, i)} label={{ text: translate('::' + formItem.colData?.captionName) }} /> ) diff --git a/ui/src/views/list/Grid.tsx b/ui/src/views/list/Grid.tsx index cee682e..7f96aa7 100644 --- a/ui/src/views/list/Grid.tsx +++ b/ui/src/views/list/Grid.tsx @@ -16,6 +16,7 @@ import { } from '@/services/list-form-customization.service' import { useLocalization } from '@/utils/hooks/useLocalization' import useResponsive from '@/utils/hooks/useResponsive' +import { executeEditorScript } from '@/utils/editorScriptRuntime' import { Template } from 'devextreme-react/core/template' import DataGrid, { ColumnChooser, @@ -211,6 +212,7 @@ const setFormEditorReadOnly = (form: any, field: string, readOnly: boolean) => { } apply() + setTimeout(apply, 0) return true } @@ -826,7 +828,13 @@ const Grid = (props: GridProps) => { } } - eval(formItem.editorScript!) + executeEditorScript(formItem.editorScript!, { + formData, + e, + editor, + runtimeSetEditorReadOnly, + setFormData, + }) } catch (err) { console.error('Script exec error', formItem.dataField, err) } @@ -1550,9 +1558,19 @@ const Grid = (props: GridProps) => { } const runReadOnlyScripts = () => { + const editorValues = gridDto.gridOptions.editingFormDto + .flatMap((group) => flattenEditingFormItems([group])) + .reduce>((values, formItem) => { + const editorInstance = form?.getEditor?.(formItem.dataField) + if (editorInstance?.option) { + values[formItem.dataField] = editorInstance.option('value') + } + return values + }, {}) const formData = { ...editingFormDataRef.current, ...(form?.option?.('formData') || {}), + ...editorValues, } editingFormDataRef.current = { ...formData } @@ -1563,6 +1581,10 @@ const Grid = (props: GridProps) => { ) .forEach((formItem) => { try { + const editorInstance = form?.getEditor?.(formItem.dataField) + const editorValue = + editorInstance?.option?.('value') ?? + getValueByField(formData, formItem.dataField) const editor = { dataField: formItem.dataField, component: grid, @@ -1570,10 +1592,16 @@ const Grid = (props: GridProps) => { const e = { component: form, dataField: formItem.dataField, - value: getValueByField(formData, formItem.dataField), + value: editorValue, } - eval(formItem.editorScript!) + executeEditorScript(formItem.editorScript!, { + formData, + e, + editor, + runtimeSetEditorReadOnly, + setFormData, + }) } catch (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) { console.error('Script exec error', err) } diff --git a/ui/src/views/list/Tree.tsx b/ui/src/views/list/Tree.tsx index e42c31f..a475d23 100644 --- a/ui/src/views/list/Tree.tsx +++ b/ui/src/views/list/Tree.tsx @@ -1,6 +1,7 @@ import Container from '@/components/shared/Container' import { Dialog, Notification, toast } from '@/components/ui' import { APP_NAME, DX_CLASSNAMES } from '@/constants/app.constant' +import { executeEditorScript } from '@/utils/editorScriptRuntime' import { DbTypeEnum, EditingFormItemDto, @@ -199,6 +200,7 @@ const setFormEditorReadOnly = (form: any, field: string, readOnly: boolean) => { } apply() + setTimeout(apply, 0) return true } @@ -784,7 +786,13 @@ const Tree = (props: TreeProps) => { } } - eval(formItem.editorScript!) + executeEditorScript(formItem.editorScript!, { + formData, + e, + editor, + runtimeSetEditorReadOnly, + setFormData, + }) } catch (err) { console.error('Script exec error', formItem.dataField, err) } @@ -1212,9 +1220,19 @@ const Tree = (props: TreeProps) => { } const runReadOnlyScripts = () => { + const editorValues = gridDto.gridOptions.editingFormDto + .flatMap((group) => flattenEditingFormItems([group])) + .reduce>((values, formItem) => { + const editorInstance = form?.getEditor?.(formItem.dataField) + if (editorInstance?.option) { + values[formItem.dataField] = editorInstance.option('value') + } + return values + }, {}) const formData = { ...editingFormDataRef.current, ...(form?.option?.('formData') || {}), + ...editorValues, } editingFormDataRef.current = { ...formData } @@ -1225,6 +1243,10 @@ const Tree = (props: TreeProps) => { ) .forEach((formItem) => { try { + const editorInstance = form?.getEditor?.(formItem.dataField) + const editorValue = + editorInstance?.option?.('value') ?? + getValueByField(formData, formItem.dataField) const editor = { dataField: formItem.dataField, component: grid, @@ -1232,10 +1254,16 @@ const Tree = (props: TreeProps) => { const e = { component: form, dataField: formItem.dataField, - value: getValueByField(formData, formItem.dataField), + value: editorValue, } - eval(formItem.editorScript!) + executeEditorScript(formItem.editorScript!, { + formData, + e, + editor, + runtimeSetEditorReadOnly, + setFormData, + }) } catch (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) { console.error('Script exec error', err) }