diff --git a/judo-ui-react-itest/ActionGroupTest/model/ActionGroupTest-ui.model b/judo-ui-react-itest/ActionGroupTest/model/ActionGroupTest-ui.model
index c248a4d0..99fb5bad 100644
--- a/judo-ui-react-itest/ActionGroupTest/model/ActionGroupTest-ui.model
+++ b/judo-ui-react-itest/ActionGroupTest/model/ActionGroupTest-ui.model
@@ -217,7 +217,7 @@
-
+
@@ -283,8 +283,8 @@
-
+
@@ -328,7 +328,7 @@
-
+
@@ -349,7 +349,7 @@
-
+
@@ -364,7 +364,7 @@
-
+
@@ -438,7 +438,7 @@
-
+
@@ -493,7 +493,7 @@
-
+
@@ -530,7 +530,7 @@
-
+
@@ -549,7 +549,7 @@
-
+
@@ -610,7 +610,7 @@
-
+
@@ -657,7 +657,7 @@
-
+
@@ -699,7 +699,7 @@
-
+
@@ -979,8 +979,8 @@
-
+
@@ -2124,9 +2124,9 @@
-
+
-
+
@@ -2144,7 +2144,7 @@
-
+
@@ -2310,7 +2310,7 @@
-
+
@@ -2414,9 +2414,9 @@
-
+
-
+
@@ -2434,7 +2434,7 @@
-
+
@@ -2615,7 +2615,7 @@
-
+
@@ -2746,7 +2746,7 @@
-
+
@@ -2834,9 +2834,9 @@
-
+
-
+
@@ -2855,7 +2855,7 @@
-
+
@@ -2936,7 +2936,7 @@
-
+
@@ -2992,9 +2992,9 @@
-
+
-
+
@@ -3013,7 +3013,7 @@
-
+
@@ -3121,9 +3121,9 @@
-
+
-
+
@@ -3199,7 +3199,7 @@
-
+
@@ -3270,9 +3270,9 @@
-
+
-
+
@@ -3374,7 +3374,7 @@
-
+
@@ -3488,7 +3488,7 @@
-
+
@@ -3607,7 +3607,7 @@
-
+
@@ -3734,7 +3734,7 @@
-
+
@@ -3858,9 +3858,9 @@
-
+
-
+
@@ -3899,7 +3899,7 @@
-
+
@@ -4104,7 +4104,7 @@
-
+
@@ -4163,7 +4163,7 @@
-
+
@@ -4246,9 +4246,9 @@
-
+
-
+
@@ -4456,7 +4456,7 @@
-
+
@@ -4542,9 +4542,9 @@
-
+
-
+
@@ -4787,7 +4787,7 @@
-
+
@@ -5053,7 +5053,7 @@
-
+
@@ -5436,7 +5436,7 @@
-
+
@@ -5569,7 +5569,7 @@
-
+
@@ -5637,9 +5637,9 @@
-
+
-
+
@@ -5689,7 +5689,7 @@
-
+
@@ -5961,7 +5961,7 @@
-
+
@@ -6140,7 +6140,7 @@
-
+
@@ -6257,7 +6257,7 @@
-
+
@@ -6385,7 +6385,7 @@
-
+
@@ -6553,9 +6553,9 @@
-
+
-
+
@@ -6868,7 +6868,7 @@
-
+
@@ -7226,7 +7226,7 @@
-
+
@@ -7291,9 +7291,9 @@
-
+
-
+
diff --git a/judo-ui-react-itest/ActionGroupTestPro/model/ActionGroupTestPro-ui.model b/judo-ui-react-itest/ActionGroupTestPro/model/ActionGroupTestPro-ui.model
index c248a4d0..99fb5bad 100644
--- a/judo-ui-react-itest/ActionGroupTestPro/model/ActionGroupTestPro-ui.model
+++ b/judo-ui-react-itest/ActionGroupTestPro/model/ActionGroupTestPro-ui.model
@@ -217,7 +217,7 @@
-
+
@@ -283,8 +283,8 @@
-
+
@@ -328,7 +328,7 @@
-
+
@@ -349,7 +349,7 @@
-
+
@@ -364,7 +364,7 @@
-
+
@@ -438,7 +438,7 @@
-
+
@@ -493,7 +493,7 @@
-
+
@@ -530,7 +530,7 @@
-
+
@@ -549,7 +549,7 @@
-
+
@@ -610,7 +610,7 @@
-
+
@@ -657,7 +657,7 @@
-
+
@@ -699,7 +699,7 @@
-
+
@@ -979,8 +979,8 @@
-
+
@@ -2124,9 +2124,9 @@
-
+
-
+
@@ -2144,7 +2144,7 @@
-
+
@@ -2310,7 +2310,7 @@
-
+
@@ -2414,9 +2414,9 @@
-
+
-
+
@@ -2434,7 +2434,7 @@
-
+
@@ -2615,7 +2615,7 @@
-
+
@@ -2746,7 +2746,7 @@
-
+
@@ -2834,9 +2834,9 @@
-
+
-
+
@@ -2855,7 +2855,7 @@
-
+
@@ -2936,7 +2936,7 @@
-
+
@@ -2992,9 +2992,9 @@
-
+
-
+
@@ -3013,7 +3013,7 @@
-
+
@@ -3121,9 +3121,9 @@
-
+
-
+
@@ -3199,7 +3199,7 @@
-
+
@@ -3270,9 +3270,9 @@
-
+
-
+
@@ -3374,7 +3374,7 @@
-
+
@@ -3488,7 +3488,7 @@
-
+
@@ -3607,7 +3607,7 @@
-
+
@@ -3734,7 +3734,7 @@
-
+
@@ -3858,9 +3858,9 @@
-
+
-
+
@@ -3899,7 +3899,7 @@
-
+
@@ -4104,7 +4104,7 @@
-
+
@@ -4163,7 +4163,7 @@
-
+
@@ -4246,9 +4246,9 @@
-
+
-
+
@@ -4456,7 +4456,7 @@
-
+
@@ -4542,9 +4542,9 @@
-
+
-
+
@@ -4787,7 +4787,7 @@
-
+
@@ -5053,7 +5053,7 @@
-
+
@@ -5436,7 +5436,7 @@
-
+
@@ -5569,7 +5569,7 @@
-
+
@@ -5637,9 +5637,9 @@
-
+
-
+
@@ -5689,7 +5689,7 @@
-
+
@@ -5961,7 +5961,7 @@
-
+
@@ -6140,7 +6140,7 @@
-
+
@@ -6257,7 +6257,7 @@
-
+
@@ -6385,7 +6385,7 @@
-
+
@@ -6553,9 +6553,9 @@
-
+
-
+
@@ -6868,7 +6868,7 @@
-
+
@@ -7226,7 +7226,7 @@
-
+
@@ -7291,9 +7291,9 @@
-
+
-
+
diff --git a/judo-ui-react-itest/CRUDActionsTest/model/CRUDActionsTest-ui.model b/judo-ui-react-itest/CRUDActionsTest/model/CRUDActionsTest-ui.model
index 55503235..230f0eb8 100644
--- a/judo-ui-react-itest/CRUDActionsTest/model/CRUDActionsTest-ui.model
+++ b/judo-ui-react-itest/CRUDActionsTest/model/CRUDActionsTest-ui.model
@@ -9,7 +9,7 @@
-
+
@@ -40,8 +40,8 @@
-
+
@@ -102,8 +102,8 @@
-
+
@@ -131,7 +131,7 @@
-
+
@@ -248,7 +248,7 @@
-
+
@@ -300,7 +300,7 @@
-
+
@@ -586,8 +586,8 @@
-
-
+
+
@@ -639,7 +639,7 @@
-
+
@@ -675,8 +675,8 @@
-
-
+
+
@@ -698,9 +698,9 @@
-
+
-
+
@@ -713,8 +713,8 @@
-
-
+
+
@@ -748,9 +748,9 @@
-
+
-
+
@@ -759,8 +759,8 @@
-
-
+
+
@@ -802,9 +802,9 @@
-
+
-
+
@@ -835,8 +835,8 @@
-
-
+
+
@@ -890,8 +890,8 @@
-
-
+
+
@@ -913,9 +913,9 @@
-
+
-
+
@@ -928,8 +928,8 @@
-
-
+
+
@@ -963,9 +963,9 @@
-
+
-
+
@@ -974,8 +974,8 @@
-
-
+
+
@@ -1017,9 +1017,9 @@
-
+
-
+
@@ -1061,8 +1061,8 @@
-
-
+
+
@@ -1096,8 +1096,8 @@
-
-
+
+
@@ -1149,7 +1149,7 @@
-
+
@@ -1181,8 +1181,8 @@
-
-
+
+
@@ -1190,7 +1190,7 @@
-
+
@@ -1211,8 +1211,8 @@
-
-
+
+
@@ -1224,7 +1224,7 @@
-
+
@@ -1239,8 +1239,8 @@
-
-
+
+
@@ -1252,7 +1252,7 @@
-
+
@@ -1290,8 +1290,8 @@
-
-
+
+
@@ -1341,8 +1341,8 @@
-
-
+
+
@@ -1350,7 +1350,7 @@
-
+
@@ -1371,8 +1371,8 @@
-
-
+
+
@@ -1384,7 +1384,7 @@
-
+
@@ -1399,8 +1399,8 @@
-
-
+
+
@@ -1412,7 +1412,7 @@
-
+
@@ -1461,8 +1461,8 @@
-
-
+
+
@@ -1496,8 +1496,8 @@
-
-
+
+
@@ -1549,7 +1549,7 @@
-
+
@@ -1565,8 +1565,8 @@
-
-
+
+
@@ -1687,8 +1687,8 @@
-
-
+
+
@@ -1740,7 +1740,7 @@
-
+
@@ -1843,8 +1843,8 @@
-
-
+
+
@@ -1896,7 +1896,7 @@
-
+
@@ -1999,8 +1999,8 @@
-
-
+
+
@@ -2052,7 +2052,7 @@
-
+
@@ -2068,8 +2068,8 @@
-
-
+
+
@@ -2187,10 +2187,10 @@
-
+
-
-
+
+
@@ -2199,7 +2199,7 @@
-
+
@@ -2230,8 +2230,8 @@
-
+
@@ -2292,8 +2292,8 @@
-
+
@@ -2321,7 +2321,7 @@
-
+
@@ -2433,7 +2433,7 @@
-
+
@@ -2486,7 +2486,7 @@
-
+
@@ -2776,8 +2776,8 @@
-
-
+
+
@@ -2829,7 +2829,7 @@
-
+
@@ -2865,8 +2865,8 @@
-
-
+
+
@@ -2888,9 +2888,9 @@
-
+
-
+
@@ -2903,8 +2903,8 @@
-
-
+
+
@@ -2938,9 +2938,9 @@
-
+
-
+
@@ -2949,8 +2949,8 @@
-
-
+
+
@@ -2992,9 +2992,9 @@
-
+
-
+
@@ -3025,8 +3025,8 @@
-
-
+
+
@@ -3080,8 +3080,8 @@
-
-
+
+
@@ -3103,9 +3103,9 @@
-
+
-
+
@@ -3118,8 +3118,8 @@
-
-
+
+
@@ -3153,9 +3153,9 @@
-
+
-
+
@@ -3164,8 +3164,8 @@
-
-
+
+
@@ -3207,9 +3207,9 @@
-
+
-
+
@@ -3251,8 +3251,8 @@
-
-
+
+
@@ -3286,8 +3286,8 @@
-
-
+
+
@@ -3339,7 +3339,7 @@
-
+
@@ -3371,8 +3371,8 @@
-
-
+
+
@@ -3380,7 +3380,7 @@
-
+
@@ -3401,8 +3401,8 @@
-
-
+
+
@@ -3414,7 +3414,7 @@
-
+
@@ -3429,8 +3429,8 @@
-
-
+
+
@@ -3442,7 +3442,7 @@
-
+
@@ -3480,8 +3480,8 @@
-
-
+
+
@@ -3531,8 +3531,8 @@
-
-
+
+
@@ -3540,7 +3540,7 @@
-
+
@@ -3561,8 +3561,8 @@
-
-
+
+
@@ -3574,7 +3574,7 @@
-
+
@@ -3589,8 +3589,8 @@
-
-
+
+
@@ -3602,7 +3602,7 @@
-
+
@@ -3651,8 +3651,8 @@
-
-
+
+
@@ -3686,8 +3686,8 @@
-
-
+
+
@@ -3739,7 +3739,7 @@
-
+
@@ -3755,8 +3755,8 @@
-
-
+
+
@@ -3877,8 +3877,8 @@
-
-
+
+
@@ -3930,7 +3930,7 @@
-
+
@@ -4033,8 +4033,8 @@
-
-
+
+
@@ -4086,7 +4086,7 @@
-
+
@@ -4189,8 +4189,8 @@
-
-
+
+
@@ -4242,7 +4242,7 @@
-
+
@@ -4258,8 +4258,8 @@
-
-
+
+
@@ -4377,10 +4377,10 @@
-
+
-
-
+
+
@@ -4389,7 +4389,7 @@
-
+
@@ -4420,8 +4420,8 @@
-
+
@@ -4482,8 +4482,8 @@
-
+
@@ -4511,7 +4511,7 @@
-
+
@@ -4628,7 +4628,7 @@
-
+
@@ -4957,8 +4957,8 @@
-
-
+
+
@@ -5010,7 +5010,7 @@
-
+
@@ -5046,8 +5046,8 @@
-
-
+
+
@@ -5069,9 +5069,9 @@
-
+
-
+
@@ -5084,8 +5084,8 @@
-
-
+
+
@@ -5119,9 +5119,9 @@
-
+
-
+
@@ -5130,8 +5130,8 @@
-
-
+
+
@@ -5173,9 +5173,9 @@
-
+
-
+
@@ -5206,8 +5206,8 @@
-
-
+
+
@@ -5261,8 +5261,8 @@
-
-
+
+
@@ -5284,9 +5284,9 @@
-
+
-
+
@@ -5299,8 +5299,8 @@
-
-
+
+
@@ -5334,9 +5334,9 @@
-
+
-
+
@@ -5345,8 +5345,8 @@
-
-
+
+
@@ -5388,9 +5388,9 @@
-
+
-
+
@@ -5432,8 +5432,8 @@
-
-
+
+
@@ -5467,8 +5467,8 @@
-
-
+
+
@@ -5520,7 +5520,7 @@
-
+
@@ -5552,8 +5552,8 @@
-
-
+
+
@@ -5561,7 +5561,7 @@
-
+
@@ -5582,8 +5582,8 @@
-
-
+
+
@@ -5595,7 +5595,7 @@
-
+
@@ -5610,8 +5610,8 @@
-
-
+
+
@@ -5623,7 +5623,7 @@
-
+
@@ -5661,8 +5661,8 @@
-
-
+
+
@@ -5712,8 +5712,8 @@
-
-
+
+
@@ -5721,7 +5721,7 @@
-
+
@@ -5742,8 +5742,8 @@
-
-
+
+
@@ -5755,7 +5755,7 @@
-
+
@@ -5770,8 +5770,8 @@
-
-
+
+
@@ -5783,7 +5783,7 @@
-
+
@@ -5832,8 +5832,8 @@
-
-
+
+
@@ -5867,8 +5867,8 @@
-
-
+
+
@@ -5920,7 +5920,7 @@
-
+
@@ -5936,8 +5936,8 @@
-
-
+
+
@@ -6058,8 +6058,8 @@
-
-
+
+
@@ -6111,7 +6111,7 @@
-
+
@@ -6214,8 +6214,8 @@
-
-
+
+
@@ -6267,7 +6267,7 @@
-
+
@@ -6370,8 +6370,8 @@
-
-
+
+
@@ -6423,7 +6423,7 @@
-
+
@@ -6439,8 +6439,8 @@
-
-
+
+
@@ -6558,8 +6558,8 @@
-
+
-
-
+
+
diff --git a/judo-ui-react-itest/OperationParametersTest/model/OperationParametersTest-ui.model b/judo-ui-react-itest/OperationParametersTest/model/OperationParametersTest-ui.model
index 70f17949..93db98a2 100644
--- a/judo-ui-react-itest/OperationParametersTest/model/OperationParametersTest-ui.model
+++ b/judo-ui-react-itest/OperationParametersTest/model/OperationParametersTest-ui.model
@@ -15,7 +15,7 @@
-
+
@@ -42,7 +42,7 @@
-
+
@@ -62,7 +62,7 @@
-
+
@@ -91,7 +91,7 @@
-
+
@@ -191,7 +191,7 @@
-
+
@@ -365,7 +365,7 @@
-
+
@@ -536,7 +536,7 @@
-
+
@@ -635,7 +635,7 @@
-
+
@@ -1084,6 +1084,7 @@
DELETE
+
@@ -1093,6 +1094,7 @@
+
REFRESH
@@ -1162,8 +1164,8 @@
-
-
+
+
@@ -1197,9 +1199,9 @@
-
+
-
+
@@ -1213,7 +1215,7 @@
-
+
@@ -1239,8 +1241,8 @@
-
-
+
+
@@ -1291,7 +1293,7 @@
-
+
@@ -1316,7 +1318,7 @@
-
+
@@ -1339,8 +1341,8 @@
-
-
+
+
@@ -1374,9 +1376,9 @@
-
+
-
+
@@ -1412,8 +1414,8 @@
-
-
+
+
@@ -1447,8 +1449,8 @@
-
-
+
+
@@ -1487,7 +1489,7 @@
-
+
@@ -1537,7 +1539,7 @@
-
+
@@ -1559,8 +1561,8 @@
-
-
+
+
@@ -1583,8 +1585,8 @@
-
-
+
+
@@ -1665,7 +1667,7 @@
-
+
@@ -1716,7 +1718,7 @@
-
+
@@ -1785,9 +1787,9 @@
-
-
-
+
+
+
@@ -1840,7 +1842,7 @@
-
+
@@ -1856,9 +1858,9 @@
-
-
-
+
+
+
@@ -1946,7 +1948,7 @@
-
+
@@ -1997,7 +1999,7 @@
-
+
@@ -2096,8 +2098,8 @@
-
-
+
+
@@ -2149,7 +2151,7 @@
-
+
@@ -2221,8 +2223,8 @@
-
-
+
+
@@ -2244,9 +2246,9 @@
-
-
-
+
+
+
@@ -2281,7 +2283,7 @@
-
+
@@ -2301,11 +2303,11 @@
-
-
-
-
-
+
+
+
+
+
@@ -2325,8 +2327,8 @@
-
-
+
+
@@ -2348,7 +2350,7 @@
-
+
@@ -2374,11 +2376,11 @@
-
-
-
-
-
+
+
+
+
+
@@ -2433,7 +2435,7 @@
-
+
@@ -2469,7 +2471,7 @@
-
+
@@ -2482,8 +2484,8 @@
-
-
+
+
@@ -2506,7 +2508,7 @@
-
+
@@ -2527,7 +2529,7 @@
-
+
@@ -2548,9 +2550,9 @@
-
-
-
+
+
+
@@ -2586,8 +2588,8 @@
-
-
+
+
@@ -2636,9 +2638,9 @@
-
-
-
+
+
+
@@ -2674,7 +2676,7 @@
-
+
@@ -2708,7 +2710,7 @@
-
+
@@ -2768,8 +2770,8 @@
-
-
+
+
@@ -2821,7 +2823,7 @@
-
+
@@ -2880,7 +2882,7 @@
-
+
@@ -2892,8 +2894,8 @@
-
-
+
+
@@ -2927,9 +2929,9 @@
-
+
-
+
@@ -2989,11 +2991,11 @@
-
-
-
-
-
+
+
+
+
+
@@ -3048,7 +3050,7 @@
-
+
@@ -3064,11 +3066,11 @@
-
-
-
-
-
+
+
+
+
+
@@ -3110,7 +3112,7 @@
-
+
@@ -3122,8 +3124,8 @@
-
-
+
+
@@ -3157,9 +3159,9 @@
-
+
-
+
@@ -3243,8 +3245,8 @@
-
-
+
+
@@ -3267,8 +3269,8 @@
-
-
+
+
@@ -3291,8 +3293,8 @@
-
-
+
+
@@ -3321,7 +3323,7 @@
-
+
@@ -3338,8 +3340,8 @@
-
-
+
+
@@ -3380,7 +3382,7 @@
-
+
@@ -3401,9 +3403,9 @@
-
-
-
+
+
+
@@ -3454,7 +3456,7 @@
-
+
@@ -3513,8 +3515,8 @@
-
-
+
+
@@ -3549,7 +3551,7 @@
-
+
@@ -3583,7 +3585,7 @@
-
+
@@ -3617,9 +3619,9 @@
-
-
-
+
+
+
@@ -3654,13 +3656,13 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -3715,7 +3717,7 @@
-
+
@@ -3759,7 +3761,7 @@
-
+
@@ -3786,9 +3788,9 @@
-
-
-
+
+
+
@@ -3839,7 +3841,7 @@
-
+
@@ -3852,7 +3854,7 @@
-
+
@@ -3869,8 +3871,8 @@
-
-
+
+
@@ -3911,8 +3913,8 @@
-
-
+
+
@@ -3935,8 +3937,8 @@
-
-
+
+
@@ -3959,8 +3961,8 @@
-
-
+
+
@@ -4009,8 +4011,8 @@
-
-
+
+
@@ -4045,7 +4047,7 @@
-
+
@@ -4079,7 +4081,7 @@
-
+
@@ -4113,9 +4115,9 @@
-
-
-
+
+
+
@@ -4165,8 +4167,8 @@
-
-
+
+
@@ -4189,7 +4191,7 @@
-
+
@@ -4202,8 +4204,8 @@
-
-
+
+
@@ -4238,7 +4240,7 @@
-
+
@@ -4303,8 +4305,8 @@
-
-
+
+
@@ -4339,7 +4341,7 @@
-
+
@@ -4372,12 +4374,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
@@ -4427,7 +4429,7 @@
-
+
@@ -4467,7 +4469,7 @@
-
+
@@ -4484,7 +4486,7 @@
-
+
@@ -4497,8 +4499,8 @@
-
-
+
+
@@ -4533,8 +4535,8 @@
-
-
+
+
@@ -4599,7 +4601,7 @@
-
+
@@ -4650,7 +4652,7 @@
-
+
@@ -4734,13 +4736,13 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -4775,11 +4777,11 @@
-
-
-
-
-
+
+
+
+
+
@@ -4811,13 +4813,13 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -4868,7 +4870,7 @@
-
+
@@ -4919,7 +4921,7 @@
-
+
@@ -4982,7 +4984,7 @@
-
+
@@ -5033,7 +5035,7 @@
-
+
@@ -5091,8 +5093,8 @@
-
-
+
+
@@ -5126,9 +5128,9 @@
-
+
-
+
@@ -5140,7 +5142,7 @@
-
+
@@ -5202,11 +5204,11 @@
-
-
-
-
-
+
+
+
+
+
@@ -5260,7 +5262,7 @@
-
+
@@ -5276,11 +5278,11 @@
-
-
-
-
-
+
+
+
+
+
@@ -5346,7 +5348,7 @@
-
+
@@ -5359,8 +5361,8 @@
-
-
+
+
@@ -5394,9 +5396,9 @@
-
+
-
+
@@ -5451,7 +5453,7 @@
-
+
@@ -5502,7 +5504,7 @@
-
+
@@ -5520,6 +5522,9 @@
+
+
+
@@ -5544,5 +5549,5 @@
-
+
diff --git a/judo-ui-react/pom.xml b/judo-ui-react/pom.xml
index 6e134266..d74d03df 100644
--- a/judo-ui-react/pom.xml
+++ b/judo-ui-react/pom.xml
@@ -51,6 +51,13 @@
judo-ui-typescript-rest-commons
${judo-ui-typescript-rest-version}
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.5.1
+ test
+
diff --git a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiActionsHelper.java b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiActionsHelper.java
index ce50ba36..3c48e301 100644
--- a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiActionsHelper.java
+++ b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiActionsHelper.java
@@ -229,7 +229,11 @@ public static String getDialogOpenParameters(PageDefinition pageDefinition) {
result.add("data: " + classDataName(getReferenceClassType(pageDefinition), ""));
}
if (!pageDefinition.getContainer().isIsSelector()) {
- result.add("templateDataOverride?: Partial<" + classDataName(getReferenceClassType(pageDefinition), ">"));
+ if (pageDefinition.getContainer().isView()) {
+ result.add("templateDataOverride?: " + classDataName(getReferenceClassType(pageDefinition), "Stored"));
+ } else {
+ result.add("templateDataOverride?: Partial<" + classDataName(getReferenceClassType(pageDefinition), ">"));
+ }
} else if (pageDefinition.getContainer().isIsRelationSelector()) {
result.add("alreadySelected: " + classDataName(getReferenceClassType(pageDefinition), "Stored") + "[]");
}
@@ -267,6 +271,12 @@ public static String getFormOpenParameters(PageDefinition pageDefinition, Action
}
} else {
tokens.add("data");
+ if (isRelationOpenCreateActionOnEagerView(pageDefinition, action)) {
+ if (tokens.size() < 2) {
+ tokens.add("undefined");
+ }
+ tokens.add("true");
+ }
}
}
if (isRelationOpenCreateActionOnForm(pageDefinition, action)) {
@@ -386,6 +396,13 @@ public static boolean isRelationOpenCreateActionOnForm(PageDefinition pageDefini
&& relationType.isIsInlineCreatable();
}
+ public static boolean isRelationOpenCreateActionOnEagerView(PageDefinition pageDefinition, Action action) {
+ return pageDefinition.getContainer().isView()
+ && action.getIsOpenFormAction()
+ && action.getTargetDataElement() instanceof RelationType relationType
+ && relationType.isIsInlineCreatable();
+ }
+
public static String postCallOperationActionParams(PageDefinition page, ActionDefinition actionDefinition) {
List tokens = new ArrayList<>();
if (actionDefinition.getTargetType() != null) {
@@ -477,7 +494,7 @@ public static String actionTargetPageName(Action action) {
}
public static boolean createNestedValidation(RelationType relationType, PageDefinition pageDefinition) {
- return pageDefinition.getContainer().isForm() && relationType != null && (relationType.isIsInlineCreatable() || relationType.getIsCreateValidatable() || (relationType.getIsMemberTypeTransient() && !relationType.getTarget().isIsMapped()));
+ return relationType != null && !pageDefinition.getContainer().isTable() && (relationType.getIsCreateValidatable() || relationType.getIsUpdateValidatable());
}
public static boolean skipNestedValidationBody(PageDefinition pageDefinition) {
@@ -485,7 +502,7 @@ public static boolean skipNestedValidationBody(PageDefinition pageDefinition) {
}
public static boolean isRowActionCRUD(ActionDefinition actionDefinition) {
- return actionDefinition.getIsRemoveAction() || actionDefinition.getIsDeleteAction();
+ return actionDefinition.getIsRemoveAction() || actionDefinition.getIsRowDeleteAction();
}
public static boolean allowRefreshAfterOperationCall(Action action) {
@@ -514,4 +531,16 @@ public static Action getRowViewActionForCreateOpenAction(Action action) {
}
return null;
}
+
+ public static boolean isActionParentEagerElement(Action action) {
+ Table table = getTableParentForActionDefinition(action.getActionDefinition());
+ Link link = getLinkParentForActionDefinition(action.getActionDefinition());
+ if (table != null) {
+ return table.isIsEager();
+ }
+ if (link != null) {
+ return link.isIsEager();
+ }
+ return false;
+ }
}
diff --git a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPageContainerHelper.java b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPageContainerHelper.java
index a7029ed7..0cb22645 100644
--- a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPageContainerHelper.java
+++ b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPageContainerHelper.java
@@ -4,6 +4,7 @@
import hu.blackbelt.judo.meta.ui.*;
import hu.blackbelt.judo.meta.ui.data.ClassType;
import hu.blackbelt.judo.meta.ui.data.EnumerationType;
+import hu.blackbelt.judo.ui.generator.react.mask.MaskEntry;
import hu.blackbelt.judo.ui.generator.typescript.rest.commons.UiCommonsHelper;
import lombok.extern.java.Log;
@@ -149,32 +150,80 @@ public static String containerButtonAvailable(Button button) {
tokens.add("editMode");
} else if (button.getActionDefinition().getIsCancelAction() || button.getActionDefinition().getIsUpdateAction()) {
tokens.add("editMode");
+ } else if (button.getActionDefinition().getIsRefreshAction()) {
+ // In case of dialogs that are open in draft mode, we should consider checking actions
+ // because it could be possible that there are no valid use cases where these actions
+ // should be generated in the first place.
+ tokens.add("!isDraft");
} else {
tokens.add("!editMode");
}
return String.join(" && ", tokens);
}
- public static String getMaskForTable(Table table) {
+ public static MaskEntry getMaskForTable(Table table, PageDefinition pageDefinition, Integer counter) {
+ MaskEntry mask = new MaskEntry(Set.of(), null);
Set columnAttributeNames = table.getColumns().stream()
.map(c -> c.getAttributeType().getName())
.collect(Collectors.toSet());
columnAttributeNames.addAll(table.getAdditionalMaskAttributes().stream().map(NamedElement::getName).collect(Collectors.toSet()));
- String tableColumns = String.join(",", columnAttributeNames.stream().sorted().toList());
+ mask.addPrimitives(columnAttributeNames);
+ if (table.isIsEager() && counter < 5) {
+ // table items can be potentially opened, therefore we need the target's attributes as well
+ Button openPageButton = table.getRowActionButtonGroup().getButtons().stream()
+ .filter(b -> b.getActionDefinition().getIsOpenPageAction())
+ .findFirst().orElse(null);
+ if (openPageButton != null) {
+ OpenPageActionDefinition def = (OpenPageActionDefinition) openPageButton.getActionDefinition();
+ Action openAction = pageDefinition.getActions().stream().filter(a -> a.getActionDefinition().equals(def)).findFirst().orElse(null);
+ if (openAction != null && openAction.getTargetPageDefinition() != null) {
+ MaskEntry viewMask = getMaskForView(openAction.getTargetPageDefinition(), counter + 1);
+ mask.addPrimitives(viewMask.getPrimitives());
+ mask.addRelations(viewMask.getRelations());
+ }
+ }
+ }
+
+ return mask;
+ }
- return "{" + tableColumns + "}";
+ public static String serializeMaskForTable(Table table, PageDefinition pageDefinition) {
+ return "{" + getMaskForTable(table, pageDefinition, 0).serialize() + "}";
}
- public static String getMaskForLink(Link link) {
+ public static MaskEntry getMaskForLink(Link link, PageDefinition pageDefinition, Integer counter) {
+ MaskEntry mask = new MaskEntry(Set.of(), null);
Set columnAttributeNames = ((List) link.getColumns()).stream().map(c -> c.getAttributeType().getName()).collect(Collectors.toSet());
columnAttributeNames.addAll(link.getAdditionalMaskAttributes().stream().map(NamedElement::getName).collect(Collectors.toSet()));
- String linkColumns = String.join(",", columnAttributeNames.stream().sorted().toList());
+ mask.addPrimitives(columnAttributeNames);
- return "{" + linkColumns + "}";
+ if (link.isIsEager() && counter < 5) {
+ // link items can be potentially opened, therefore we need the target's attributes as well
+ Button openPageButton = link.getActionButtonGroup().getButtons().stream()
+ .filter(b -> b.getActionDefinition().getIsOpenPageAction())
+ .findFirst().orElse(null);
+ if (openPageButton != null) {
+ OpenPageActionDefinition def = (OpenPageActionDefinition) openPageButton.getActionDefinition();
+ Action openAction = pageDefinition.getActions().stream().filter(a -> a.getActionDefinition().equals(def)).findFirst().orElse(null);
+ if (openAction != null && openAction.getTargetPageDefinition() != null) {
+ MaskEntry viewMask = getMaskForView(openAction.getTargetPageDefinition(), counter + 1);
+ mask.addPrimitives(viewMask.getPrimitives());
+ mask.addRelations(viewMask.getRelations());
+ }
+ }
+ }
+
+ return mask;
+ }
+
+ public static String serializeMaskForLink(Link link, PageDefinition pageDefinition) {
+ return "{" + getMaskForLink(link, pageDefinition, 0).serialize() + "}";
}
- public static String getMaskForView(PageContainer container) {
- Set mask = new LinkedHashSet<>();
+ public static MaskEntry getMaskForView(PageDefinition pageDefinition, Integer counter) {
+ MaskEntry mask = new MaskEntry(Set.of(), null);
+ PageContainer container = pageDefinition.getContainer();
+ Set tokens = new LinkedHashSet<>();
Set inputs = new HashSet<>();
collectVisualElementsMatchingCondition(container, (VisualElement element) -> element instanceof AttributeBased, inputs);
@@ -193,17 +242,30 @@ public static String getMaskForView(PageContainer container) {
attributeNames.add(container.getTitleAttribute().getName());
}
- mask.addAll(attributeNames.stream().sorted().toList());
+ mask.addPrimitives(attributeNames);
for (Table table: ((List) container.getTables()).stream().filter(t -> t.getRelationType().getIsRelationKindComposition() || t.getRelationType().getIsRelationKindAggregation()).toList()) {
- mask.add(table.getDataElement().getName() + getMaskForTable(table));
+ MaskEntry tableMask = getMaskForTable(table, pageDefinition, counter + 1);
+ tableMask.setRelationName(table.getDataElement().getName());
+ mask.addRelations(tableMask);
}
for (Link link: ((List) container.getLinks()).stream().filter(t -> t.getRelationType().getIsRelationKindComposition() || t.getRelationType().getIsRelationKindAggregation()).toList()) {
- mask.add(link.getDataElement().getName() + getMaskForLink(link));
+ MaskEntry linkMask = getMaskForLink(link, pageDefinition, counter + 1);
+ linkMask.setRelationName(link.getDataElement().getName());
+ mask.addRelations(linkMask);
}
- return "{" + String.join(",", mask) + "}";
+ return mask;
+ }
+
+ public static String serializeMaskForView(PageDefinition pageDefinition) {
+ try {
+ return "{" + getMaskForView(pageDefinition, 0).serialize() + "}";
+ } catch (StackOverflowError e) {
+ // keeping this for debugging purposes
+ throw e;
+ }
}
public static boolean containerHasDateInput(PageContainer container) {
@@ -222,6 +284,10 @@ public static boolean containerHasTable(PageContainer container) {
return !container.getTables().isEmpty();
}
+ public static Table getFirstTableForContainer(PageContainer container) {
+ return (Table) container.getTables().stream().findFirst().orElse(null);
+ }
+
public static boolean containerHasNumericInput(PageContainer container) {
return !collectElementsOfType(container, new ArrayList<>(), NumericInput.class).isEmpty();
}
diff --git a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPageHelper.java b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPageHelper.java
index 48bd33fa..61ff176a 100644
--- a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPageHelper.java
+++ b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiPageHelper.java
@@ -457,4 +457,15 @@ public static String calculateNavigationRoute(PageDefinition pageDefinition) {
public static boolean hasExportAction(PageDefinition pageDefinition) {
return pageDefinition.getActions().stream().anyMatch(page -> page.getIsExportAction());
}
+
+ public static boolean isDialogValidationSupported(PageDefinition pageDefinition) {
+ if (pageDefinition.getRelationType() != null) {
+ if (pageDefinition.getContainer().isView() && pageDefinition.getRelationType().getIsUpdateValidatable()) {
+ return true;
+ } else if (pageDefinition.getContainer().isForm() && pageDefinition.getRelationType().getIsCreateValidatable()) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiWidgetHelper.java b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiWidgetHelper.java
index 2bc3c7c1..aada7c21 100644
--- a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiWidgetHelper.java
+++ b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiWidgetHelper.java
@@ -377,20 +377,21 @@ public static String tableRowButtonDisabledConditions(Button button, Table table
if (container.isView()) {
result += "(isFormUpdateable ? !isFormUpdateable() : false) || ";
}
- } else if (button.getActionDefinition().getIsDeleteAction()) {
+ } else if (button.getActionDefinition().getIsRowDeleteAction()) {
if (!container.isTable()) {
- result += "editMode || ";
+ if (!table.isIsEager()) {
+ result += "editMode || ";
+ }
if (table.getEnabledBy() != null) {
result += "(ownerData ? !ownerData." + table.getEnabledBy().getName() + " : false) || ";
}
}
+ result += "(typeof row.__deleteable === 'boolean' && !row.__deleteable) || ";
if (container.isView()) {
result += "(isFormUpdateable ? !isFormUpdateable() : false) || ";
}
-
- result += "!row.__deleteable || ";
} else if (!container.isTable()) {
result += "editMode || ";
}
@@ -444,5 +445,6 @@ public static boolean isLinkAssociation(Link link) {
public static boolean displayTableHeading(Table table, PageContainer container) {
return elementHasIconOrLabel(table) && !container.isIsSelector() && !container.isTable();
+
}
}
diff --git a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/mask/MaskEntry.java b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/mask/MaskEntry.java
new file mode 100644
index 00000000..15deb521
--- /dev/null
+++ b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/mask/MaskEntry.java
@@ -0,0 +1,73 @@
+package hu.blackbelt.judo.ui.generator.react.mask;
+
+import java.util.Comparator;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class MaskEntry {
+ private final Set primitives = new LinkedHashSet<>();
+ private String relationName;
+ private final Set relations = new LinkedHashSet<>();
+
+ public MaskEntry(Set primitives, String relationName) {
+ if (primitives != null && !primitives.isEmpty()) {
+ this.primitives.addAll(primitives);
+ }
+ if (relationName != null) {
+ this.relationName = relationName;
+ }
+ }
+
+ public String getRelationName() {
+ return relationName;
+ }
+
+ public MaskEntry setRelationName(String rn) {
+ relationName = rn;
+ return this;
+ }
+
+ public Set getPrimitives() {
+ return primitives;
+ }
+
+ public Set getRelations() {
+ return relations;
+ }
+
+ public MaskEntry addPrimitives(String... p) {
+ primitives.addAll(Set.of(p));
+ return this;
+ }
+
+ public MaskEntry addPrimitives(Set ps) {
+ primitives.addAll(ps);
+ return this;
+ }
+
+ public MaskEntry addRelations(MaskEntry... r) {
+ relations.addAll(Set.of(r));
+ return this;
+ }
+
+ public MaskEntry addRelations(Set rs) {
+ relations.addAll(rs);
+ return this;
+ }
+
+ public String serialize() {
+ String p = primitives.stream()
+ .sorted()
+ .collect(Collectors.joining(","));
+ p += (!p.isEmpty() && !relations.isEmpty()) ? "," : "";
+ String r = relations.stream()
+ .sorted(Comparator.comparing(MaskEntry::getRelationName))
+ .map(MaskEntry::serialize)
+ .collect(Collectors.joining(","));
+ if (relationName != null) {
+ return relationName + "{" + p + r + "}";
+ }
+ return p + r;
+ }
+}
diff --git a/judo-ui-react/src/main/resources/actor/src/components/table/EagerTable.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/components/table/EagerTable.tsx.hbs
index 7e3622c5..aa583ec5 100644
--- a/judo-ui-react/src/main/resources/actor/src/components/table/EagerTable.tsx.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/components/table/EagerTable.tsx.hbs
@@ -64,7 +64,6 @@ interface EagerTableProps[];
tableRowActions: TableRowAction[];
tableFilterOptions: FilterOption[];
- mask: string;
isOwnerLoading?: boolean;
validationError?: string;
actions: any;
@@ -80,7 +79,7 @@ interface EagerTableProps[];
additionalToolbarButtons?: (...args: any[]) => Record;
tableHasSelectorColumn?: boolean;
- maskAction?: () => string;
+ maskAction: () => string;
ownerData?: any;
checkboxSelection?: boolean;
isFormUpdateable?: () => boolean;
@@ -101,7 +100,6 @@ export function EagerTable>({
- _mask: mask,
+ _mask: maskAction(),
_seek: {
limit: rowsPerPage + 1,
},
diff --git a/judo-ui-react/src/main/resources/actor/src/components/table/LazyTable.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/components/table/LazyTable.tsx.hbs
index fcb36fe9..fa5e615a 100644
--- a/judo-ui-react/src/main/resources/actor/src/components/table/LazyTable.tsx.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/components/table/LazyTable.tsx.hbs
@@ -68,7 +68,6 @@ interface LazyTableProps[];
tableRowActions: TableRowAction[];
tableFilterOptions: FilterOption[];
- mask: string;
isOwnerLoading?: boolean;
validationError?: string;
actions: any;
@@ -83,7 +82,7 @@ interface LazyTableProps[];
containerHasTable?: boolean;
hasRefreshAction?: boolean;
- maskAction?: () => string;
+ maskAction: () => string;
fetch: any;
refreshCounter: number;
ownerData?: any;
@@ -112,7 +111,6 @@ export function LazyTable>({
- _mask: mask,
+ _mask: maskAction(),
_seek: {
limit: rowsPerPage + 1,
},
diff --git a/judo-ui-react/src/main/resources/actor/src/containers/components/link/index.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/containers/components/link/index.tsx.hbs
index 9e1ff928..21638fee 100644
--- a/judo-ui-react/src/main/resources/actor/src/containers/components/link/index.tsx.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/containers/components/link/index.tsx.hbs
@@ -54,7 +54,7 @@ export function {{ componentName link }}(props: {{ componentName link }}Props) {
visible: (value: any) => exists(value) && !!actions.{{ simpleActionDefinitionName actionDefinition }},
disabled: editMode && !({{ boolValue link.dataElement.isInlineCreatable }} && !isDraft),
onClick: async (value: {{ classDataName link.dataElement.target 'Stored' }} | null) => {
- await actions.{{ simpleActionDefinitionName actionDefinition }}!(value!, {{# if container.form }}true{{ else }}false{{/ if }});
+ await actions.{{ simpleActionDefinitionName actionDefinition }}!(value!, {{# if link.isEager }}true{{ else }}false{{/ if }});
},
title: t('judo.component.SingleRelationInput.navigate', { defaultValue: 'Navigate to element' }) as string,
{{/ if }}
@@ -66,7 +66,7 @@ export function {{ componentName link }}(props: {{ componentName link }}Props) {
},
title: t('judo.component.SingleRelationInput.create', { defaultValue: 'Create' }) as string,
{{/ if }}
- {{# if button.actionDefinition.isDeleteAction }}
+ {{# if button.actionDefinition.isRowDeleteAction }}
dropDownButtonIsVisible: (value: any) => exists(value) && !readOnly && !isRequired && value.__deleteable && !!actions.{{ simpleActionDefinitionName actionDefinition }},
disabled: disabled || editMode,
onClick: async (value: {{ classDataName link.dataElement.target 'Stored' }} | null) => {
@@ -101,7 +101,7 @@ export function {{ componentName link }}(props: {{ componentName link }}Props) {
if (actions.{{ link.dataElement.name }}RefreshAction) {
try {
const queryCustomizer: {{ classDataName link.dataElement.target 'QueryCustomizer' }} = {
- _mask: '{{ getMaskForLink link }}',
+ _mask: actions.get{{ firstToUpper link.relationType.name }}Mask!(),
};
const { data: result } = await actions.{{ link.dataElement.name }}RefreshAction!(queryCustomizer);
return result || null; // result can be empty string for no data...
@@ -138,7 +138,7 @@ export function {{ componentName link }}(props: {{ componentName link }}Props) {
{ operator: StringOperation.like, value: searchText }
],
} : {}),
- _mask: '{{ getMaskForLink link }}',
+ _mask: actions.get{{ firstToUpper link.relationType.name }}Mask!(),
_orderBy: [
{{# with (getSortColumnForLink link) as |sortColumn| }}
{ attribute: '{{ sortColumn.attributeType.name }}', descending: {{ isSortDirectionDescending sortColumn }} },
diff --git a/judo-ui-react/src/main/resources/actor/src/containers/components/link/types.ts.hbs b/judo-ui-react/src/main/resources/actor/src/containers/components/link/types.ts.hbs
index 502afa0a..a4a9870b 100644
--- a/judo-ui-react/src/main/resources/actor/src/containers/components/link/types.ts.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/containers/components/link/types.ts.hbs
@@ -11,6 +11,7 @@ export interface {{ componentName link }}ActionDefinitions {
{{/ if }}
is{{ firstToUpper link.relationType.name }}Required?: ({{{ inputModifierParams container false }}}) => boolean;
is{{ firstToUpper link.relationType.name }}Disabled?: ({{{ inputModifierParams container true }}}) => boolean;
+ get{{ firstToUpper link.relationType.name }}Mask?: () => string;
}
export interface {{ componentName link }}Props {
diff --git a/judo-ui-react/src/main/resources/actor/src/containers/components/table/index.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/containers/components/table/index.tsx.hbs
index e7dc663d..7eee86e2 100644
--- a/judo-ui-react/src/main/resources/actor/src/containers/components/table/index.tsx.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/containers/components/table/index.tsx.hbs
@@ -226,7 +226,6 @@ export function {{ componentName table }}(props: {{ componentName table }}Props)
tableColumns={columns}
tableRowActions={rowActions}
tableFilterOptions={filterOptions}
- mask={ '{{ getMaskForTable table }}' }
isOwnerLoading={isOwnerLoading}
validationError={validationError}
actions={actions}
@@ -247,7 +246,7 @@ export function {{ componentName table }}(props: {{ componentName table }}Props)
{{/ if }}
{{# each table.rowActionDefinitions as |actionDefinition| }}
{{# if actionDefinition.isOpenPageAction }}
- onRowClick={ actions.{{ simpleActionDefinitionName actionDefinition }} ? async (params: GridRowParams<{{ classDataName (getReferenceClassType table) 'Stored' }}>) => await actions.{{ simpleActionDefinitionName actionDefinition }}!(params.row, {{# if container.form }}true{{ else }}false{{/ if }}) : undefined }
+ onRowClick={ actions.{{ simpleActionDefinitionName actionDefinition }} ? async (params: GridRowParams<{{ classDataName (getReferenceClassType table) 'Stored' }}>) => await actions.{{ simpleActionDefinitionName actionDefinition }}!(params.row, {{# if table.isEager }}true{{ else }}false{{/ if }}) : undefined }
{{/ if }}
{{/ each }}
sidekickComponentFilter={sidekickComponentFilter}
@@ -267,9 +266,7 @@ export function {{ componentName table }}(props: {{ componentName table }}Props)
ownerData={ownerData}
editMode={editMode}
{{/ unless }}
- {{# if (getRefreshActionDefinitionForTable table) }}
- maskAction={actions.get{{ firstToUpper table.relationName }}Mask}
- {{/ if }}
+ maskAction={actions.get{{ firstToUpper table.relationName }}Mask!}
enabledByName='{{ table.enabledBy.name }}'
dataElement={ ownerData?.{{ table.dataElement.name }} }
relationName='{{ table.relationName }}'
@@ -292,7 +289,6 @@ export function {{ componentName table }}(props: {{ componentName table }}Props)
tableColumns={columns}
tableRowActions={rowActions}
tableFilterOptions={filterOptions}
- mask={ '{{ getMaskForTable table }}' }
isOwnerLoading={isOwnerLoading}
validationError={validationError}
actions={actions}
@@ -326,9 +322,7 @@ export function {{ componentName table }}(props: {{ componentName table }}Props)
{{# if getRefreshActionDefinitionForTable table }}
hasRefreshAction={ true }
{{/ if }}
- {{# if (getRefreshActionDefinitionForTable table) }}
- maskAction={actions.get{{ firstToUpper table.relationName }}Mask}
- {{/ if }}
+ maskAction={actions.get{{ firstToUpper table.relationName }}Mask!}
{{# if container.isSelector }}
{{# with (getRangeActionDefinitionForTable table) as |actionDefinition| }}
fetch={ actions.{{ simpleActionDefinitionName actionDefinition }} }
diff --git a/judo-ui-react/src/main/resources/actor/src/containers/components/table/types.ts.hbs b/judo-ui-react/src/main/resources/actor/src/containers/components/table/types.ts.hbs
index ffdd096d..0192c9e2 100644
--- a/judo-ui-react/src/main/resources/actor/src/containers/components/table/types.ts.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/containers/components/table/types.ts.hbs
@@ -10,13 +10,13 @@ import type { Filter, FilterOption } from '~/components-api';
import type { DialogResult } from '~/utilities';
export interface {{ componentName table }}ActionDefinitions {
+ get{{ firstToUpper table.relationName }}Mask?: () => string;
{{# each table.tableActionDefinitions as |actionDefinition| }}
{{# if actionDefinition.isFilterAction }}
{{ simpleActionDefinitionName actionDefinition }}?: (id: string, filterOptions: FilterOption[], model?: GridFilterModel, filters?: Filter[]) => Promise<{ model?: GridFilterModel; filters?: Filter[] }>;
{{ else }}
{{# if actionDefinition.isRefreshAction }}
{{ simpleActionDefinitionName actionDefinition }}?: (queryCustomizer: {{ classDataName (getReferenceClassType table) 'QueryCustomizer' }}) => Promise>;
- get{{ firstToUpper table.relationName }}Mask?: () => string;
{{ else }}
{{# if actionDefinition.isExportAction }}
{{ simpleActionDefinitionName actionDefinition }}?: (queryCustomizer: {{ classDataName (getReferenceClassType table) 'QueryCustomizer' }}) => Promise;
diff --git a/judo-ui-react/src/main/resources/actor/src/containers/dialog.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/containers/dialog.tsx.hbs
index 129a499b..cd3788dd 100644
--- a/judo-ui-react/src/main/resources/actor/src/containers/dialog.tsx.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/containers/dialog.tsx.hbs
@@ -79,11 +79,11 @@ export default function {{ containerComponentName container }}Dialog({{# unless
isDraft
{{/ unless }}
} = props;
- {{# unless container.table }}
+ {{# if container.view }}
const queryCustomizer: {{ classDataName container.dataElement 'QueryCustomizer' }} = {
- _mask: {{# unless container.form }}actions.getMask ? actions.getMask!() : {{/ unless }}'{{ getMaskForView container }}',
+ _mask: actions.getMask?.(),
};
- {{/ unless }}
+ {{/ if }}
{{# if (containerHasCreateAction container) }}
const handleCreateDropdownToggle = useCallback(() => {
@@ -196,11 +196,19 @@ export default function {{ containerComponentName container }}Dialog({{# unless
: {{# unless button.actionDefinition.autoOpenAfterCreate }}t('{{ getTranslationKeyForVisualElement button }}', { defaultValue: '{{ button.label }}' }){{ else }}t('judo.pages.create-and-navigate', { defaultValue: 'Create and open' }){{/ unless }}
}
{{ else }}
- {{# and button.actionDefinition.isUpdateAction button.actionDefinition.autoCloseOnSave }}
- {t('judo.pages.save-and-close', { defaultValue: 'Save and close' })}
+ {{# if button.actionDefinition.isUpdateAction }}
+ {
+ isDraft
+ ? t('judo.dialogs.draft.submit', { defaultValue: 'Ok' })
+ : t('{{ getTranslationKeyForVisualElement button }}', { defaultValue: '{{ button.label }}' })
+ }
{{ else }}
- {t('{{ getTranslationKeyForVisualElement button }}', { defaultValue: '{{ button.label }}' })}
- {{/ and }}
+ {{# and button.actionDefinition.isUpdateAction button.actionDefinition.autoCloseOnSave }}
+ {t('judo.pages.save-and-close', { defaultValue: 'Save and close' })}
+ {{ else }}
+ {t('{{ getTranslationKeyForVisualElement button }}', { defaultValue: '{{ button.label }}' })}
+ {{/ and }}
+ {{/ if }}
{{/ if }}
{{# if button.actionDefinition.isCreateAction }}
diff --git a/judo-ui-react/src/main/resources/actor/src/containers/page.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/containers/page.tsx.hbs
index 70d0653d..130c7741 100644
--- a/judo-ui-react/src/main/resources/actor/src/containers/page.tsx.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/containers/page.tsx.hbs
@@ -38,11 +38,12 @@ export default function {{ containerComponentName container }}Page ({{# unless (
const { t } = useTranslation();
const { navigate, back } = useJudoNavigation();
const { actions, isLoading, editMode, refreshCounter{{# unless container.table }}, data, isFormUpdateable, isFormDeleteable, storeDiff, validation, setValidation, submit{{/ unless }} } = props;
- {{# unless container.table }}
+ const isDraft = false; // currently no page can be opened as draft, but we need this variable anyway
+ {{# if container.view }}
const queryCustomizer: {{ classDataName container.dataElement 'QueryCustomizer' }} = {
- _mask: {{# unless container.form }}actions.getMask ? actions.getMask!() : {{/ unless }}'{{ getMaskForView container }}',
+ _mask: actions.getMask?.(),
};
- {{/ unless }}
+ {{/ if }}
{{/ unless }}
return (
diff --git a/judo-ui-react/src/main/resources/actor/src/containers/types.ts.hbs b/judo-ui-react/src/main/resources/actor/src/containers/types.ts.hbs
index 9d16e163..cf2fbf6f 100644
--- a/judo-ui-react/src/main/resources/actor/src/containers/types.ts.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/containers/types.ts.hbs
@@ -43,9 +43,9 @@
{{# each (getTextInputsWithTypeAhead container) as |ve| }}
get{{ firstToUpper ve.attributeType.name }}Options?: (searchText: string, data: {{ classDataName container.dataElement '' }}, editMode: boolean, validation: Map) => Promise;
{{/ each }}
- {{# if container.view }}
+ {{# unless container.form }}
getMask?: () => string;
- {{/ if }}
+ {{/ unless }}
}
export interface {{ containerComponentName container }}PageActions extends {{ pageContainerActionDefinitionTypeName container }} {
diff --git a/judo-ui-react/src/main/resources/actor/src/dialogs/customization.ts.hbs b/judo-ui-react/src/main/resources/actor/src/dialogs/customization.ts.hbs
index 321fff2f..4cb7e93a 100644
--- a/judo-ui-react/src/main/resources/actor/src/dialogs/customization.ts.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/dialogs/customization.ts.hbs
@@ -7,6 +7,7 @@
import { {{ restParamName imp }} } from '~/services/data-api/model/{{ restParamName imp }}';
{{/ each }}
import type { {{ containerComponentName page.container }}DialogActionsExtended, {{ pageName page }}Props } from './types';
+ import type { DialogResultReason } from '~/utilities';
export const {{ camelCaseNameToInterfaceKey (pageName page) }}_ACTIONS_HOOK_INTERFACE_KEY = '{{ camelCaseNameToInterfaceKey (pageName page) }}_ACTIONS_HOOK';
export type {{ containerComponentName page.container }}ActionsHook = (
@@ -24,9 +25,9 @@
{{/ if }}
submit: () => Promise,
{{# if (pageHasOutputTarget page) }}
- onSubmit: (result?: {{ classDataName (getPageOutputTarget page) 'Stored' }}, isDraft?: boolean, openCreated?: boolean) => Promise,
+ onSubmit: (result?: {{ classDataName (getPageOutputTarget page) 'Stored' }}, reason?: DialogResultReason, openCreated?: boolean) => Promise,
{{ else }}
- onSubmit: (result?: {{ dialogDataType page }}{{# if page.container.table }}[]{{/ if }}, isDraft?: boolean, openCreated?: boolean) => Promise,
+ onSubmit: (result?: {{ dialogDataType page }}{{# if page.container.table }}[]{{/ if }}, reason?: DialogResultReason, openCreated?: boolean) => Promise,
{{/ if }}
) => {{ containerComponentName page.container }}DialogActionsExtended;
{{ else }}
diff --git a/judo-ui-react/src/main/resources/actor/src/dialogs/hooks.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/dialogs/hooks.tsx.hbs
index bd428c94..92f18bb7 100644
--- a/judo-ui-react/src/main/resources/actor/src/dialogs/hooks.tsx.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/dialogs/hooks.tsx.hbs
@@ -54,13 +54,13 @@ export const use{{ pageName page }} = (): ({{{ getDialogOpenParameters page }}})
result: 'close',
});
}}
- onSubmit={async (result, isDraft, openCreated) => {
+ onSubmit={async (dataParam, variant = 'submit', openCreated = false) => {
await closeDialog();
resolve({
- result: isDraft ? 'submit-draft' : 'submit',
+ result: variant,
openCreated,
{{# if (dialogHasResult page) }}
- data: result,
+ data: dataParam,
{{/ if }}
});
}}
diff --git a/judo-ui-react/src/main/resources/actor/src/dialogs/index.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/dialogs/index.tsx.hbs
index 72861cee..47c41a30 100644
--- a/judo-ui-react/src/main/resources/actor/src/dialogs/index.tsx.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/dialogs/index.tsx.hbs
@@ -1,6 +1,6 @@
{{> fragment.header.hbs }}
-import { {{# unless page.container.table }}useCallback, useEffect, useRef, {{/ unless }}useState, useMemo, lazy, Suspense } from 'react';
+import { {{# unless page.container.table }}useCallback, useRef, {{/ unless }}useEffect, useState, useMemo, lazy, Suspense } from 'react';
{{# unless (containerIsEmptyDashboard page.container) }}
import type { FC, ReactNode, Dispatch, SetStateAction } from 'react';
import { OBJECTCLASS } from '@pandino/pandino-api';
@@ -30,6 +30,10 @@ import { {{# unless page.container.table }}useCallback, useEffect, useRef, {{/ u
useErrorHandler,
isErrorNestedValidationError,
cleanUpPayload,
+ decoratePayloadWithMetaFromData,
+ {{# if page.container.view }}
+ cloneDeep,
+ {{/ if }}
} from '~/utilities';
import type {
DialogResult,
@@ -113,6 +117,20 @@ export default function {{ pageName page }}(props: {{ pageName page }}Props) {
// Ref section
const payloadDiff = useRef>({} as unknown as Record);
+ {{# if page.container.view}}
+ // Rollback handling
+ const rollbackPoint = useRef<{{ dialogDataType page }} | null>((isDraft && templateDataOverride) ? cloneDeep<{{ dialogDataType page }}>(templateDataOverride) : null);
+ const rollbackData = useCallback(() => {
+ if (rollbackPoint.current) {
+ setData(rollbackPoint.current);
+ // re-set payloadDiff
+ payloadDiff.current = {} as unknown as Record;
+ decoratePayloadWithMetaFromData(payloadDiff.current, rollbackPoint.current);
+ setEditMode(false);
+ }
+ }, [data, editMode, payloadDiff, rollbackPoint]);
+ {{/ if }}
+
// Callback section
const storeDiff: (attributeName: keyof {{ classDataName (getReferenceClassType page) '' }}, value: any) => void = useCallback((attributeName: keyof {{ classDataName (getReferenceClassType page) '' }}, value: any) => {
{{# if isDebugPrint }}// include: actor/src/fragments/page/store-diff-body.hbs{{/ if }}
@@ -122,7 +140,7 @@ export default function {{ pageName page }}(props: {{ pageName page }}Props) {
{{# if page.container.form }}
return true;
{{ else }}
- return {{ boolValue (isPageUpdateable page) }} && typeof data?.__updateable === 'boolean' && data?.__updateable;
+ return isDraft || ({{ boolValue (isPageUpdateable page) }} && typeof data?.__updateable === 'boolean' && data?.__updateable);
{{/ if }}
}, [data]);
const isFormDeleteable = useCallback(() => {
@@ -135,11 +153,31 @@ export default function {{ pageName page }}(props: {{ pageName page }}Props) {
{{# if page.container.view }}
const getPageQueryCustomizer: () => {{ classDataName (getReferenceClassType page) 'QueryCustomizer' }} = () => ({
- _mask: actions.getMask ? actions.getMask!() : '{{ getMaskForView page.container }}',
+ _mask: actions.getMask ? actions.getMask!() : '{{ serializeMaskForView page }}',
});
{{/ if }}
{{/ unless }}
+ // Masks
+ {{# if page.container.table }}
+ {{# with (getFirstTableForContainer page.container) as |table| }}
+ const getMask: () => string = () => {
+ return '{{ serializeMaskForTable table page }}';
+ };
+ {{/ with }}
+ {{/ if }}
+ {{# if page.container.view }}
+ const getMask = () => '{{ serializeMaskForView page }}';
+ {{/ if }}
+ {{# unless page.container.table }}
+ {{# each page.container.links as |link| }}
+ const get{{ firstToUpper link.relationType.name }}Mask = () => '{{ serializeMaskForLink link page }}';
+ {{/ each }}
+ {{# each page.container.tables as |table| }}
+ const get{{ firstToUpper table.relationName }}Mask = () => '{{ serializeMaskForTable table page }}';
+ {{/ each }}
+ {{/ unless }}
+
// Private actions
const submit = async () => {
{{# with (getCreateActionForPage page) as |action| }}
@@ -152,70 +190,72 @@ export default function {{ pageName page }}(props: {{ pageName page }}Props) {
await {{ simpleActionDefinitionName action.actionDefinition }}();
{{/ with }}
};
- const refresh = async () => {
- {{# if page.container.table }}
- setRefreshCounter((prev) => prev + 1);
- {{/ if }}
- {{# if page.container.view }}
- {{# with (getRefreshActionDefinitionForContainer page.container) as |refreshActionDefinition| }}
- if (actions.{{ simpleActionDefinitionName refreshActionDefinition }}) {
- await actions.{{ simpleActionDefinitionName refreshActionDefinition }}!(processQueryCustomizer(getPageQueryCustomizer()));
+ const refresh = async (forced = false) => {
+ if (!isDraft) {
+ {{# if page.container.table }}
+ setRefreshCounter((prev) => prev + 1);
+ {{/ if }}
+ {{# if page.container.view }}
+ if (!editMode || forced) {
+ {{# with (getRefreshActionDefinitionForContainer page.container) as |refreshActionDefinition| }}
+ if (actions.{{ simpleActionDefinitionName refreshActionDefinition }}) {
+ await actions.{{ simpleActionDefinitionName refreshActionDefinition }}!(processQueryCustomizer(getPageQueryCustomizer()));
+ }
+ {{/ with }}
}
- {{/ with }}
- {{/ if }}
+ {{/ if }}
+ }
};
// Validation
const validate: (data: {{ dialogBareDataType page }}) => Promise = async (data) => {
- {{# if page.dataElement.isCreateValidatable }}
- try {
+ {{# if (isDialogValidationSupported page) }}
if (ownerValidation) {
await ownerValidation(data);
} else {
- await {{ getServiceImplForPage page }}.validateCreate({{# unless page.dataElement.isAccess }}ownerData, {{/ unless }}data);
- }
- } catch (error: any) {
- if (isDraft && isErrorNestedValidationError(error, '{{ page.dataElement.name }}')) {
- throw error;
+ await {{ getServiceImplForPage page }}.validate{{# if page.container.view }}Update{{ else }}Create{{/ if }}({{# unless page.dataElement.isAccess }}ownerData, {{/ unless }}data);
}
- }
{{/ if }}
};
{{# each page.container.links as |link| }}
- {{# if (createNestedValidation link.relationType page) }}
- const validate{{ firstToUpper link.relationType.name }} = async (linkData: {{ classDataName link.relationType.target '' }}): Promise => {
- {{# if link.relationType.isCreateValidatable }}
- await {{ getServiceImplForPage page }}.validateCreate({{# unless page.dataElement.isAccess }}ownerData, {{/ unless }}{
- ...cleanUpPayload({
- ...payloadDiff.current,
- {{ link.relationType.name }}: {
- ...linkData,
- } as any
- }),
- } as {{ dialogBareDataType page }});
- {{ else }}
- // not yet supported
- {{/ if }}
- };
- {{/ if }}
+ {{# and (createNestedValidation link.relationType page) link.isEager }}
+ const validate{{ firstToUpper link.relationType.name }} = async (linkData: {{ classDataName link.relationType.target '' }}): Promise => {
+ try {
+ await validate({
+ ...cleanUpPayload({
+ ...payloadDiff.current,
+ {{ link.relationType.name }}: {
+ ...linkData,
+ } as any
+ }),
+ } as {{ dialogBareDataType page }});
+ } catch (error: any) {
+ if (isErrorNestedValidationError(error, '{{ link.relationType.name }}')) {
+ throw error;
+ }
+ }
+ };
+ {{/ and }}
{{/ each }}
{{# each page.container.tables as |table| }}
- {{# if (createNestedValidation table.relationType page) }}
- const validate{{ firstToUpper table.relationType.name }} = async (tableData: {{ classDataName table.relationType.target '' }}): Promise => {
- {{# if table.relationType.isCreateValidatable }}
- await {{ getServiceImplForPage page }}.validateCreate({{# unless page.dataElement.isAccess }}ownerData, {{/ unless }}{
- ...cleanUpPayload({
- ...payloadDiff.current,
- {{ table.relationType.name }}: [
- { ...tableData } as any
- ],
- }),
- } as {{ dialogBareDataType page }});
- {{ else }}
- // not yet supported
- {{/ if }}
- };
- {{/ if }}
+ {{# and (createNestedValidation table.relationType page) table.isEager }}
+ const validate{{ firstToUpper table.relationType.name }} = async (tableData: {{ classDataName table.relationType.target '' }}): Promise => {
+ try {
+ await validate({
+ ...cleanUpPayload({
+ ...payloadDiff.current,
+ {{ table.relationType.name }}: [
+ { ...tableData } as any
+ ],
+ }),
+ } as {{ dialogBareDataType page }});
+ } catch (error: any) {
+ if (isErrorNestedValidationError(error, '{{ table.relationType.name }}')) {
+ throw error;
+ }
+ }
+ };
+ {{/ and }}
{{/ each }}
// Pandino Action overrides
@@ -249,6 +289,17 @@ export default function {{ pageName page }}(props: {{ pageName page }}Props) {
{{# each page.actions as |action| }}
{{ simpleActionDefinitionName action.actionDefinition }},
{{/ each }}
+ {{# unless page.container.form }}
+ getMask,
+ {{/ unless }}
+ {{# unless page.container.table }}
+ {{# each page.container.links as |link| }}
+ get{{ firstToUpper link.relationType.name }}Mask,
+ {{/ each }}
+ {{# each page.container.tables as |table| }}
+ get{{ firstToUpper table.relationName }}Mask,
+ {{/ each }}
+ {{/ unless }}
...(customActions ?? {}),
};
@@ -286,22 +337,22 @@ export default function {{ pageName page }}(props: {{ pageName page }}Props) {
};
// Effect section
- {{# if (pageShouldInitialize page) }}
- useEffect(() => {
- actions.{{ simpleActionDefinitionName page.container.onInit }}!({{# if page.container.view }}getPageQueryCustomizer(){{/ if }});
- }, []);
- {{ else }}
- {{# if page.container.form }}
- useEffect(() => {
- if (templateDataOverride) {
- setData((prevData) => ({ ...prevData, ...templateDataOverride }));
- payloadDiff.current = {
- ...(templateDataOverride as Record),
- };
- }
- }, []);
- {{/ if}}
- {{/ if }}
+ useEffect(() => {
+ {{# if (pageShouldInitialize page) }}
+ if (!isDraft) {
+ actions.{{ simpleActionDefinitionName page.container.onInit }}!({{# if page.container.view }}getPageQueryCustomizer(){{/ if }});
+ }
+ {{/ if }}
+ {{# unless page.container.table }}
+ if (templateDataOverride) {
+ setData((prevData) => ({ ...prevData, ...templateDataOverride }));
+ {{# if page.container.view }}
+ // re-set payloadDiff
+ decoratePayloadWithMetaFromData(payloadDiff.current, templateDataOverride);
+ {{/ if }}
+ }
+ {{/ unless }}
+ }, []);
{{/ unless }}
return (
diff --git a/judo-ui-react/src/main/resources/actor/src/dialogs/types.ts.hbs b/judo-ui-react/src/main/resources/actor/src/dialogs/types.ts.hbs
index de2a65c5..b9797571 100644
--- a/judo-ui-react/src/main/resources/actor/src/dialogs/types.ts.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/dialogs/types.ts.hbs
@@ -7,6 +7,7 @@
import { {{ restParamName imp }} } from '~/services/data-api/model/{{ restParamName imp }}';
{{/ each }}
import type { {{ containerComponentName page.container }}DialogActions } from '~/containers/{{ containerPath page.container }}/types';
+ import type { DialogResultReason } from '~/utilities';
export type {{ containerComponentName page.container }}DialogActionsExtended = {{ containerComponentName page.container }}DialogActions & {
{{# each (getAllCallOperationActions page) as |action| }}
@@ -27,14 +28,14 @@
ownerData: any;
{{# and (isPageDataElementUnmappedSingle page) page.container.view }}data: {{ classDataName (getReferenceClassType page) '' }};{{/ and }}
{{# if page.container.isRelationSelector }}alreadySelected: {{ classDataName (getReferenceClassType page) 'Stored' }}[];{{/ if }}
- {{# unless page.container.isSelector }}templateDataOverride?: Partial<{{ classDataName (getReferenceClassType page) '' }}>;{{/ unless }}
+ {{# unless page.container.isSelector }}templateDataOverride?: {{# if page.container.view }}{{ classDataName (getReferenceClassType page) 'Stored' }}{{ else }}Partial<{{ classDataName (getReferenceClassType page) '' }}>{{/ if }};{{/ unless }}
isDraft?: boolean;
ownerValidation?: (data: {{ dialogBareDataType page }}) => Promise;
onClose: () => Promise;
{{# if (pageHasOutputTarget page) }}
- onSubmit: (result?: {{ classDataName (getPageOutputTarget page) 'Stored' }}, isDraft?: boolean, openCreated?: boolean) => Promise;
+ onSubmit: (result?: {{ classDataName (getPageOutputTarget page) 'Stored' }}, reason?: DialogResultReason, openCreated?: boolean) => Promise;
{{ else }}
- onSubmit: (result?: {{ dialogDataType page }}{{# if page.container.table }}[]{{/ if }}, isDraft?: boolean, openCreated?: boolean) => Promise;
+ onSubmit: (result?: {{ dialogDataType page }}{{# if page.container.table }}[]{{/ if }}, reason?: DialogResultReason, openCreated?: boolean) => Promise;
{{/ if }}
{{# unless page.container.table}}
maskRequest?: string;
diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/AutocompleteSetAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/AutocompleteSetAction.fragment.hbs
index 9de40fe1..9e42cd7c 100644
--- a/judo-ui-react/src/main/resources/actor/src/pages/actions/AutocompleteSetAction.fragment.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/AutocompleteSetAction.fragment.hbs
@@ -5,13 +5,7 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async (selected
storeDiff('{{ link.dataElement.name }}', selected);
{{ else }}
await {{ getServiceImplForPage page }}.set{{ firstToUpper link.dataElement.name }}(data, selected);
- {{# if page.container.view }}
- if (!editMode) {
- {{# with (getRefreshActionDefinitionForContainer page.container) as |actionDefinition| }}
- await actions.{{ simpleActionDefinitionName actionDefinition }}!(processQueryCustomizer(getPageQueryCustomizer()));
- {{/ with }}
- }
- {{/ if }}
+ await refresh();
{{/ if }}
} catch (error) {
handleError(error);
diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/BulkDeleteAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/BulkDeleteAction.fragment.hbs
index 0e13715b..fadbaa9f 100644
--- a/judo-ui-react/src/main/resources/actor/src/pages/actions/BulkDeleteAction.fragment.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/BulkDeleteAction.fragment.hbs
@@ -1,50 +1,59 @@
const {{ simpleActionDefinitionName action.actionDefinition }} = async (selectedRows: {{ classDataName action.actionDefinition.bulkOf.targetType 'Stored' }}[]): Promise>> => {
{{# with (getTableParentForActionDefinition action.actionDefinition) as |table| }}
return new Promise((resolve) => {
- openCRUDDialog<{{ classDataName (getReferenceClassType table) 'Stored' }}>({
- dialogTitle: t('{{ getTranslationKeyForVisualElement (translationElementForBulkAction action) }}', { defaultValue: '{{ defaultLabelForBulkAction action }}' }),
- {{# with (getFirstTitleColumnForTable table) as |column| }}
- itemTitleFn: (item) => item.{{ column.attributeType.name }}!,
- {{ else }}
- itemTitleFn: (item) => t('judo.placeholder', { defaultValue: 'placeholder' }) as string,
- {{/ with }}
- selectedItems: selectedRows,
- action: async (item, successHandler: () => void, errorHandler: (error: any) => void) => {
- try {
- if (actions.{{ simpleActionDefinitionName actionDefinition.bulkOf }}) {
- await actions.{{ simpleActionDefinitionName actionDefinition.bulkOf }}!(item, true);
+ {{# if table.isEager }}
+ const selectedIds = selectedRows.map(r => r.__identifier);
+ const newList = (data?.{{ table.dataElement.name }} ?? []).filter((c: any) => !selectedIds.includes(c.__identifier));
+ storeDiff('{{ table.dataElement.name }}', newList);
+ resolve({
+ result: 'delete',
+ data: [],
+ });
+ {{ else }}
+ openCRUDDialog<{{ classDataName (getReferenceClassType table) 'Stored' }}>({
+ dialogTitle: t('{{ getTranslationKeyForVisualElement (translationElementForBulkAction action) }}', { defaultValue: '{{ defaultLabelForBulkAction action }}' }),
+ {{# with (getFirstTitleColumnForTable table) as |column| }}
+ itemTitleFn: (item) => item.{{ column.attributeType.name }}!,
+ {{ else }}
+ itemTitleFn: (item) => t('judo.placeholder', { defaultValue: 'placeholder' }) as string,
+ {{/ with }}
+ selectedItems: selectedRows,
+ action: async (item, successHandler: () => void, errorHandler: (error: any) => void) => {
+ try {
+ if (actions.{{ simpleActionDefinitionName actionDefinition.bulkOf }}) {
+ await actions.{{ simpleActionDefinitionName actionDefinition.bulkOf }}!(item, true);
+ }
+ successHandler();
+ } catch (error) {
+ errorHandler(error);
}
- successHandler();
- } catch (error) {
- errorHandler(error);
- }
- },
- autoCloseOnSuccess: true
- ,
- onClose: async (needsRefresh) => {
- if (needsRefresh) {
- {{# if page.container.table }}
- setRefreshCounter((prev) => prev + 1);
- {{/ if }}
- {{# if page.container.view }}
- {{# with (getRefreshActionDefinitionForContainer page.container) as |refreshActionDefinition| }}
- if (actions.{{ simpleActionDefinitionName refreshActionDefinition }}) {
- await actions.{{ simpleActionDefinitionName refreshActionDefinition }}!(processQueryCustomizer(getPageQueryCustomizer()));
- }
- {{/ with }}
- {{/ if }}
- resolve({
- result: 'submit',
- data: [],
- });
- } else {
- resolve({
- result: 'close',
- data: [],
- });
- }
- },
- });
+ },
+ autoCloseOnSuccess: true,
+ onClose: async (needsRefresh) => {
+ if (needsRefresh) {
+ {{# if page.container.table }}
+ setRefreshCounter((prev) => prev + 1);
+ {{/ if }}
+ {{# if page.container.view }}
+ {{# with (getRefreshActionDefinitionForContainer page.container) as |refreshActionDefinition| }}
+ if (actions.{{ simpleActionDefinitionName refreshActionDefinition }}) {
+ await actions.{{ simpleActionDefinitionName refreshActionDefinition }}!(processQueryCustomizer(getPageQueryCustomizer()));
+ }
+ {{/ with }}
+ {{/ if }}
+ resolve({
+ result: 'delete',
+ data: [],
+ });
+ } else {
+ resolve({
+ result: 'close',
+ data: [],
+ });
+ }
+ },
+ });
+ {{/ if }}
});
{{/ with }}
};
diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/CallOperationAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/CallOperationAction.fragment.hbs
index 03d0e764..31632880 100644
--- a/judo-ui-react/src/main/resources/actor/src/pages/actions/CallOperationAction.fragment.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/CallOperationAction.fragment.hbs
@@ -45,16 +45,7 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async ({{# if a
{{/ or }}
{{ else }}
{{# if (allowRefreshAfterOperationCall action) }}
- {{# if page.container.table }}
- setRefreshCounter((prev) => prev + 1);
- {{/ if }}
- {{# if page.container.view }}
- if (!editMode) {
- {{# with (getRefreshActionDefinitionForContainer page.container) as |actionDefinition| }}
- await actions.{{ simpleActionDefinitionName actionDefinition }}!(processQueryCustomizer(getPageQueryCustomizer()));
- {{/ with }}
- }
- {{/ if }}
+ await refresh();
{{/ if }}
{{/ if }}
diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/CancelAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/CancelAction.fragment.hbs
index 407721dc..ac7f39e2 100644
--- a/judo-ui-react/src/main/resources/actor/src/pages/actions/CancelAction.fragment.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/CancelAction.fragment.hbs
@@ -1,6 +1,12 @@
const {{ simpleActionDefinitionName action.actionDefinition }} = async () => {
- {{# with (getRefreshActionDefinitionForContainer page.container) as |actionDefinition| }}
+ {{# and page.openInDialog page.container.view }}
+ if (isDraft) {
+ rollbackData();
+ } else {
+ {{/ and }}
// no need to set editMode to false, given refresh should do it implicitly
- await {{ simpleActionDefinitionName actionDefinition }}(processQueryCustomizer(getPageQueryCustomizer()));
- {{/ with }}
+ await refresh(true);
+ {{# and page.openInDialog page.container.view }}
+ }
+ {{/ and }}
};
diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/ClearAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/ClearAction.fragment.hbs
index 90435627..cb4909ea 100644
--- a/judo-ui-react/src/main/resources/actor/src/pages/actions/ClearAction.fragment.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/ClearAction.fragment.hbs
@@ -6,20 +6,11 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async () => {
try {
setIsLoading(true);
await {{ getServiceImplForPage page }}.set{{ firstToUpper action.targetDataElement.name }}({{# if (pageHasSignedId page) }}{ __signedIdentifier: signedIdentifier } as JudoIdentifiable{{ else }}ownerData{{/ if }}, []);
- {{# if page.container.table }}
- setRefreshCounter((prev) => prev + 1);
- {{/ if }}
- {{# if page.container.view }}
- if (!editMode) {
- {{# with (getRefreshActionDefinitionForContainer page.container) as |actionDefinition| }}
- await actions.{{ simpleActionDefinitionName actionDefinition }}!(processQueryCustomizer(getPageQueryCustomizer()));
- {{/ with }}
- }
- {{/ if }}
+ await refresh();
} catch(e) {
console.error(e);
} finally {
- setIsLoading(false);
+ setIsLoading(false);
}
{{/ if }}
{{/ with }}
diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/CreateAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/CreateAction.fragment.hbs
index ade59082..05b07741 100644
--- a/judo-ui-react/src/main/resources/actor/src/pages/actions/CreateAction.fragment.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/CreateAction.fragment.hbs
@@ -5,13 +5,14 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async (openCrea
try {
setIsLoading(true);
await validate(cleanUpPayload(payloadDiff.current));
- onSubmit(payloadDiff.current, true);
+ onSubmit(payloadDiff.current, 'submit-draft');
} catch (error) {
- if (!isErrorNestedValidationError(error, '{{ page.dataElement.name }}')) {
+ if (ownerValidation && !isErrorNestedValidationError(error, '{{ page.dataElement.name }}')) {
// relation form has no remaining error(s), proceed with submission
- onSubmit(payloadDiff.current, true);
+ onSubmit(payloadDiff.current, 'submit-draft');
} else {
- handleError<{{ classDataName (getReferenceClassType page) '' }}>(error, { setValidation }, data, isDraft ? '{{ page.dataElement.name }}' : undefined);
+ let relationName: string | undefined = (isDraft && ownerValidation) ? '{{ page.dataElement.name }}' : undefined;
+ handleError<{{ classDataName (getReferenceClassType page) '' }}>(error, { setValidation }, data, relationName);
}
} finally {
setIsLoading(false);
@@ -30,7 +31,7 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async (openCrea
await customActions.post{{ firstToUpper (simpleActionDefinitionName action.actionDefinition) }}(data, res, onSubmit, onClose, openCreated);
} else {
showSuccessSnack(t('judo.action.create.success', { defaultValue: 'Create successful' }));
- await onSubmit(res, isDraft, openCreated);
+ await onSubmit(res, isDraft ? 'submit-draft' : 'submit', openCreated);
}
} catch (error) {
handleError<{{ classDataName (getReferenceClassType page) '' }}>(error, { setValidation }, data);
diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/DeleteAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/DeleteAction.fragment.hbs
index c9a3dd3b..0965bbf1 100644
--- a/judo-ui-react/src/main/resources/actor/src/pages/actions/DeleteAction.fragment.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/DeleteAction.fragment.hbs
@@ -1,15 +1,18 @@
const {{ simpleActionDefinitionName action.actionDefinition }} = async ({{# if action.actionDefinition.targetType }}target: {{ classDataName action.actionDefinition.targetType 'Stored' }}{{# if action.actionDefinition.isBulkCapable }}, silentMode?: boolean{{/ if }}{{/ if}}) => {
try {
- const confirmed = {{# if action.actionDefinition.isBulkCapable }}!silentMode ? {{/ if }}await openConfirmDialog(
- 'row-delete-action',
+ const confirmed = await openConfirmDialog(
+ 'delete-action',
t('judo.modal.confirm.confirm-delete', { defaultValue: 'Are you sure you would like to delete the selected element?' }),
t('judo.modal.confirm.confirm-title', { defaultValue: 'Confirm action' }),
- ){{# if action.actionDefinition.isBulkCapable }} : true{{/ if }};
+ );
if (confirmed) {
- await {{ getServiceImplForPage page }}.delete{{# if action.targetDataElement }}{{ firstToUpper action.targetDataElement.name }}{{/ if }}({{# if action.actionDefinition.targetType }}target{{ else }}data{{/ if }});
- {{# if action.actionDefinition.isBulkCapable }}
- if (!silentMode) {
+ {{# if page.openInDialog }}
+ if (isDraft) {
+ onSubmit({{# if action.actionDefinition.targetType }}target{{ else }}data{{/ if }}, 'delete');
+ return;
+ }
{{/ if }}
+ await {{ getServiceImplForPage page }}.delete{{# if action.targetDataElement }}{{ firstToUpper action.targetDataElement.name }}{{/ if }}({{# if action.actionDefinition.targetType }}target{{ else }}data{{/ if }});
showSuccessSnack(t('judo.action.delete.success', { defaultValue: 'Delete successful' }));
{{# if page.container.table }}
setRefreshCounter((prev) => prev + 1);
@@ -17,35 +20,22 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async ({{# if a
{{# if page.container.view }}
{{# unless action.actionDefinition.isContainedRelationAction }}
{{# if page.openInDialog }}
- onSubmit();
+ onSubmit({{# if action.actionDefinition.targetType }}target{{ else }}data{{/ if }}, 'delete');
{{ else }}
navigateBack();
{{/ if }}
{{ else }}
{{# if (containerIsRefreshable page.container) }}
- {{# with (getRefreshActionDefinitionForContainer page.container) as |refreshActionDefinition| }}
- {{ simpleActionDefinitionName refreshActionDefinition }}(getPageQueryCustomizer());
- {{/ with }}
+ await refresh();
{{/ if }}
{{/ unless }}
{{/ if }}
- {{# if action.actionDefinition.isBulkCapable }}
- }
- {{/ if }}
}
} catch(error) {
- {{# if action.actionDefinition.isBulkCapable }}
- if (!silentMode) {
- {{/ if }}
- {{# if action.actionDefinition.targetType }}
- handleError<{{ classDataName action.actionDefinition.targetType '' }}>(error, undefined, target);
- {{ else }}
- handleError(error, undefined, data);
- {{/ if }}
- {{# if action.actionDefinition.isBulkCapable }}
- } else {
- throw error;
- }
+ {{# if action.actionDefinition.targetType }}
+ handleError<{{ classDataName action.actionDefinition.targetType '' }}>(error, undefined, target);
+ {{ else }}
+ handleError(error, undefined, data);
{{/ if }}
}
};
diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenAddSelectorAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenAddSelectorAction.fragment.hbs
index 07508f2b..18b0113c 100644
--- a/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenAddSelectorAction.fragment.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenAddSelectorAction.fragment.hbs
@@ -20,16 +20,7 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async () => {
{{/ if }},
returnedData
);
- {{# if page.container.table }}
- setRefreshCounter((prev) => prev + 1);
- {{/ if }}
- {{# if page.container.view }}
- if (!editMode) {
- {{# with (getRefreshActionDefinitionForContainer page.container) as |actionDefinition| }}
- await actions.{{ simpleActionDefinitionName actionDefinition }}!(processQueryCustomizer(getPageQueryCustomizer()));
- {{/ with }}
- }
- {{/ if }}
+ await refresh();
} catch(e) {
console.error(e);
} finally {
diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenCreateFormAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenCreateFormAction.fragment.hbs
index c9e94f29..c8a26dcc 100644
--- a/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenCreateFormAction.fragment.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenCreateFormAction.fragment.hbs
@@ -21,6 +21,14 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async ({{# if a
{{# if page.container.view }}
if (result === 'submit' && !editMode) {
await actions.{{ simpleActionDefinitionName actionDefinition }}!(processQueryCustomizer(getPageQueryCustomizer()));
+ } else if (result === 'submit-draft' && returnedData) {
+ const decoratedData = {
+ ...returnedData,
+ __identifier: `draft:${uuidv4()}`,
+ };
+ const newData = {{# if action.targetDataElement.isCollection }}[...(data.{{ action.targetDataElement.name }} || []), decoratedData]{{ else }}decoratedData{{/ if }}
+ storeDiff('{{ action.targetDataElement.name }}', newData);
+ return;
}
{{/ if }}
{{# if page.container.table }}
diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenOperationInputFormAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenOperationInputFormAction.fragment.hbs
index 6c547fe8..5f1f9be3 100644
--- a/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenOperationInputFormAction.fragment.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenOperationInputFormAction.fragment.hbs
@@ -1,15 +1,6 @@
const {{ simpleActionDefinitionName action.actionDefinition }} = async ({{# if action.actionDefinition.targetType }}target: {{ classDataName action.actionDefinition.targetType 'Stored' }}, templateDataOverride?: Partial<{{ classDataName action.actionDefinition.targetType '' }}>, {{/ if}}isDraft?: boolean, ownerValidation?: (data: any) => Promise) => {
const { result, data: returnedData } = await open{{ actionTargetPageName action }}({{{ getFormOpenParameters page action }}});
- {{# with (getRefreshActionDefinitionForContainer page.container) as |actionDefinition| }}
- {{# if page.container.view }}
- if (result === 'submit' && !editMode) {
- await actions.{{ simpleActionDefinitionName actionDefinition }}!(processQueryCustomizer(getPageQueryCustomizer()));
- }
- {{/ if }}
- {{# if page.container.table }}
- if (result === 'submit') {
- setRefreshCounter((prev) => prev + 1);
- }
- {{/ if }}
- {{/ with }}
+ if (result === 'submit'{{# if page.container.view }} && !editMode{{/ if }}) {
+ await refresh();
+ }
};
diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenOperationInputSelectorAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenOperationInputSelectorAction.fragment.hbs
index 6c99c22f..2327e9ca 100644
--- a/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenOperationInputSelectorAction.fragment.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenOperationInputSelectorAction.fragment.hbs
@@ -1,12 +1,6 @@
const {{ simpleActionDefinitionName action.actionDefinition }} = async ({{# if action.actionDefinition.targetType }}target?: {{ classDataName action.actionDefinition.targetType 'Stored' }}{{/ if }}) => {
const { result, data: returnedData } = await open{{ pageName action.targetPageDefinition }}({{{ getSelectorOpenActionParameters action page.container }}});
if (result === 'submit') {
- {{# if page.container.view }}
- if (!editMode) {
- {{# with (getRefreshActionDefinitionForContainer page.container) as |actionDefinition| }}
- await actions.{{ simpleActionDefinitionName actionDefinition }}!(processQueryCustomizer(getPageQueryCustomizer()));
- {{/ with }}
- }
- {{/ if }}
+ await refresh();
}
};
diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenPageAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenPageAction.fragment.hbs
index 0e2cc6d7..bb19f496 100644
--- a/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenPageAction.fragment.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenPageAction.fragment.hbs
@@ -1,7 +1,7 @@
-const {{ simpleActionDefinitionName action.actionDefinition }} = async (target: {{ classDataName action.actionDefinition.targetType '' }} | {{ classDataName action.actionDefinition.targetType 'Stored' }}, isDraft?: boolean) => {
- if (isDraft && (!target || !(target as {{ classDataName action.actionDefinition.targetType 'Stored' }}).__signedIdentifier)) {
- {{# with (getOpenFormActionPairForOpenPageAction page action) as |openFormAction| }}
- const { result, data: returnedData } = await open{{ pageName openFormAction.targetPageDefinition }}(
+const {{ simpleActionDefinitionName action.actionDefinition }} = async (target: {{ classDataName action.actionDefinition.targetType '' }} | {{ classDataName action.actionDefinition.targetType 'Stored' }}, isDraftParam?: boolean) => {
+ {{# and (isActionParentEagerElement action) action.targetPageDefinition.openInDialog }}
+ if (isDraftParam) {
+ const { result, data: returnedData } = await open{{ pageName action.targetPageDefinition }}(
{{# if (pageHasSignedId page) }}
{ __signedIdentifier: signedIdentifier } as JudoIdentifiable
{{ else }}
@@ -11,22 +11,24 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async (target:
data
{{/ if }}
{{/ if }},
- target,
+ target as {{ classDataName action.actionDefinition.targetType 'Stored' }},
true,
- {{# if page.container.form }}
- validate{{ firstToUpper openFormAction.targetDataElement.name }}
+ {{# if action.targetDataElement.isUpdateValidatable }}
+ validate{{ firstToUpper action.targetDataElement.name }}
{{/ if }}
);
// we might need to differentiate result handling between operation inputs and crud relation creates
if (result === 'submit-draft' && returnedData) {
{{# if action.targetDataElement.isCollection }}
- const existingIndex = (payloadDiff.current.{{ action.targetDataElement.name }} || []).findIndex((r: { __identifier?: string }) => r.__identifier === returnedData.__identifier);
+ const existingIndex = (data.{{ action.targetDataElement.name }} || []).findIndex((r: { __identifier?: string }) => r.__identifier === returnedData.__identifier);
if (existingIndex > -1) {
- payloadDiff.current.{{ action.targetDataElement.name }}[existingIndex] = {
+ // we need to create a copy to keep order, and because mui datagrid freezes elements, and crashes on reference updates
+ const updatedList = [...(data.{{ action.targetDataElement.name }} || [])];
+ updatedList[existingIndex] = {
...returnedData,
};
+ storeDiff('{{ action.targetDataElement.name }}', updatedList);
}
- storeDiff('{{ action.targetDataElement.name }}', [...(payloadDiff.current.{{ action.targetDataElement.name }} || [])]);
{{ else }}
storeDiff('{{ action.targetDataElement.name }}', {
...returnedData
@@ -34,20 +36,28 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async (target:
{{/ if }}
return;
}
- {{/ with }}
- } else if (!isDraft) {
+ if (result === 'delete' && returnedData) {
+ {{# if action.targetDataElement.isCollection }}
+ const existingIndex = (data.{{ action.targetDataElement.name }} || []).findIndex((r: { __identifier?: string }) => r.__identifier === returnedData.__identifier);
+ if (existingIndex > -1) {
+ // we need to create a copy to keep order, and because mui datagrid freezes elements, and crashes on reference updates
+ const updatedList = [...(data.{{ action.targetDataElement.name }} || [])];
+ updatedList.splice(existingIndex, 1);
+ storeDiff('{{ action.targetDataElement.name }}', updatedList);
+ }
+ {{ else }}
+ storeDiff('{{ action.targetDataElement.name }}', null);
+ {{/ if }}
+ return;
+ }
+ if (result === 'close') {
+ return;
+ }
+ } else {
+ {{/ and }}
{{# if action.targetPageDefinition.openInDialog }}
await open{{ pageName action.targetPageDefinition }}(target!);
- {{# with (getRefreshActionDefinitionForContainer page.container) as |actionDefinition| }}
- {{# if page.container.view }}
- if (!editMode) {
- await actions.{{ simpleActionDefinitionName actionDefinition }}!(processQueryCustomizer(getPageQueryCustomizer()));
- }
- {{/ if }}
- {{# if page.container.table }}
- setRefreshCounter((prev) => prev + 1);
- {{/ if }}
- {{/ with }}
+ await refresh();
{{ else }}
{{# if action.targetPageDefinition }}
{{# if (pageHasSignedId action.targetPageDefinition) }}
@@ -61,5 +71,7 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async (target:
// There was no .targetPageDefinition for this action. Target Page is most likely empty in the model!
{{/ if }}
{{/ if }}
- }
+ {{# and (isActionParentEagerElement action) action.targetPageDefinition.openInDialog }}
+ }
+ {{/ and }}
};
diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenSetSelectorAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenSetSelectorAction.fragment.hbs
index 4525f781..37d6d534 100644
--- a/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenSetSelectorAction.fragment.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/OpenSetSelectorAction.fragment.hbs
@@ -8,13 +8,7 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async (): Promi
storeDiff('{{ link.dataElement.name }}', returnedData[0]);
{{ else }}
await {{ getServiceImplForPage page }}.set{{ firstToUpper link.dataElement.name }}(data, returnedData[0]);
- {{# if page.container.view }}
- if (!editMode) {
- {{# with (getRefreshActionDefinitionForContainer page.container) as |actionDefinition| }}
- await actions.{{ simpleActionDefinitionName actionDefinition }}!(processQueryCustomizer(getPageQueryCustomizer()));
- {{/ with }}
- }
- {{/ if }}
+ await refresh();
{{/ if }}
return returnedData[0];
} catch(error) {
diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/RefreshAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/RefreshAction.fragment.hbs
index 37fc2ba4..5b49c418 100644
--- a/judo-ui-react/src/main/resources/actor/src/pages/actions/RefreshAction.fragment.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/RefreshAction.fragment.hbs
@@ -17,12 +17,8 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async (queryCus
setData(result);
setLatestViewData(result);
// re-set payloadDiff
- payloadDiff.current = {
- __identifier: result.__identifier,
- __signedIdentifier: result.__signedIdentifier,
- __version: result.__version,
- __entityType: result.__entityType,
- } as Record;
+ payloadDiff.current = {} as unknown as Record;
+ decoratePayloadWithMetaFromData(payloadDiff.current, result);
if (customActions?.post{{ firstToUpper (simpleActionDefinitionName action.actionDefinition) }}) {
await customActions?.post{{ firstToUpper (simpleActionDefinitionName action.actionDefinition) }}(result{{# unless page.container.table }}, storeDiff, setValidation{{/ unless }});
}
diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/RemoveAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/RemoveAction.fragment.hbs
index 37b09b44..c5976a73 100644
--- a/judo-ui-react/src/main/resources/actor/src/pages/actions/RemoveAction.fragment.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/RemoveAction.fragment.hbs
@@ -11,16 +11,7 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async (target?:
}
await {{ getServiceImplForPage page }}.remove{{ firstToUpper action.targetDataElement.name }}({{# if page.container.table }}{ __signedIdentifier: signedIdentifier } as JudoIdentifiable{{ else }}data{{/ if }}, [target!]);
if (!silentMode) {
- {{# if page.container.table }}
- setRefreshCounter((prev) => prev + 1);
- {{/ if }}
- {{# if page.container.view }}
- if (!editMode) {
- {{# with (getRefreshActionDefinitionForContainer page.container) as |actionDefinition| }}
- await actions.{{ simpleActionDefinitionName actionDefinition }}!(processQueryCustomizer(getPageQueryCustomizer()));
- {{/ with }}
- }
- {{/ if }}
+ await refresh();
}
} catch(error) {
if (!silentMode) {
diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/RowDeleteAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/RowDeleteAction.fragment.hbs
new file mode 100644
index 00000000..cc51131b
--- /dev/null
+++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/RowDeleteAction.fragment.hbs
@@ -0,0 +1,59 @@
+const {{ simpleActionDefinitionName action.actionDefinition }} = async ({{# if action.actionDefinition.targetType }}target: {{ classDataName action.actionDefinition.targetType 'Stored' }}{{# if action.actionDefinition.isBulkCapable }}, silentMode?: boolean{{/ if }}{{/ if}}) => {
+ try {
+ const confirmed = {{# if action.actionDefinition.isBulkCapable }}!silentMode ? {{/ if }}await openConfirmDialog(
+ 'row-delete-action',
+ t('judo.modal.confirm.confirm-delete', { defaultValue: 'Are you sure you would like to delete the selected element?' }),
+ t('judo.modal.confirm.confirm-title', { defaultValue: 'Confirm action' }),
+ ){{# if action.actionDefinition.isBulkCapable }} : true{{/ if }};
+ if (confirmed) {
+ {{# if (isActionParentEagerElement action) }}
+ {{# with (getTableParentForActionDefinition action.actionDefinition) as |table| }}
+ const newList = (data?.{{ table.dataElement.name }} ?? []).filter((c: any) => c.__identifier !== target.__identifier);
+ storeDiff('{{ table.dataElement.name }}', newList);
+ {{/ with }}
+ {{# with (getLinkParentForActionDefinition action.actionDefinition) as |link| }}
+ storeDiff('{{ link.dataElement.name }}', null);
+ {{/ with }}
+ {{ else }}
+ await {{ getServiceImplForPage page }}.delete{{# if action.targetDataElement }}{{ firstToUpper action.targetDataElement.name }}{{/ if }}({{# if action.actionDefinition.targetType }}target{{ else }}data{{/ if }});
+ {{# if action.actionDefinition.isBulkCapable }}
+ if (!silentMode) {
+ {{/ if }}
+ showSuccessSnack(t('judo.action.delete.success', { defaultValue: 'Delete successful' }));
+ {{# if page.container.table }}
+ setRefreshCounter((prev) => prev + 1);
+ {{/ if }}
+ {{# if page.container.view }}
+ {{# unless action.actionDefinition.isContainedRelationAction }}
+ {{# if page.openInDialog }}
+ onSubmit({{# if action.actionDefinition.targetType }}target{{ else }}data{{/ if }}, 'delete');
+ {{ else }}
+ navigateBack();
+ {{/ if }}
+ {{ else }}
+ {{# if (containerIsRefreshable page.container) }}
+ await refresh();
+ {{/ if }}
+ {{/ unless }}
+ {{/ if }}
+ {{# if action.actionDefinition.isBulkCapable }}
+ }
+ {{/ if }}
+ {{/ if }}
+ }
+ } catch(error) {
+ {{# if action.actionDefinition.isBulkCapable }}
+ if (!silentMode) {
+ {{/ if }}
+ {{# if action.actionDefinition.targetType }}
+ handleError<{{ classDataName action.actionDefinition.targetType '' }}>(error, undefined, target);
+ {{ else }}
+ handleError(error, undefined, data);
+ {{/ if }}
+ {{# if action.actionDefinition.isBulkCapable }}
+ } else {
+ throw error;
+ }
+ {{/ if }}
+ }
+};
diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/UnsetAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/UnsetAction.fragment.hbs
index 9c223cb7..31a73500 100644
--- a/judo-ui-react/src/main/resources/actor/src/pages/actions/UnsetAction.fragment.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/UnsetAction.fragment.hbs
@@ -4,13 +4,7 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async ({{# if a
storeDiff('{{ link.dataElement.name }}', null);
{{ else }}
await {{ getServiceImplForPage page }}.unset{{ firstToUpper link.dataElement.name }}(data);
- {{# if page.container.view }}
- if (!editMode) {
- {{# with (getRefreshActionDefinitionForContainer page.container) as |actionDefinition| }}
- await actions.{{ simpleActionDefinitionName actionDefinition }}!(processQueryCustomizer(getPageQueryCustomizer()));
- {{/ with }}
- }
- {{/ if }}
+ await refresh();
{{/ if }}
{{/ with }}
};
diff --git a/judo-ui-react/src/main/resources/actor/src/pages/actions/UpdateAction.fragment.hbs b/judo-ui-react/src/main/resources/actor/src/pages/actions/UpdateAction.fragment.hbs
index e3575adc..577d4b55 100644
--- a/judo-ui-react/src/main/resources/actor/src/pages/actions/UpdateAction.fragment.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/pages/actions/UpdateAction.fragment.hbs
@@ -2,9 +2,31 @@ const {{ simpleActionDefinitionName action.actionDefinition }} = async () => {
{{# if (hasPageRequiredBy page) }}
{{> actor/src/fragments/page/local-validate.fragment.hbs }}
{{/ if }}
+ {{# if page.openInDialog }}
+ if (isDraft) {
+ try {
+ setIsLoading(true);
+ await validate(cleanUpPayload(payloadDiff.current));
+ // we send data back in draft mode, because the owner is responsible to handle it in-memory
+ onSubmit(data, 'submit-draft');
+ } catch (error) {
+ if (ownerValidation && !isErrorNestedValidationError(error, '{{ page.dataElement.name }}')) {
+ // relation form has no remaining error(s), proceed with submission
+ onSubmit(payloadDiff.current, 'submit-draft');
+ } else {
+ let relationName: string | undefined = (isDraft && ownerValidation) ? '{{ page.dataElement.name }}' : undefined;
+ handleError<{{ classDataName (getReferenceClassType page) '' }}>(error, { setValidation }, data, relationName);
+ }
+ } finally {
+ setIsLoading(false);
+ }
+
+ return;
+ }
+ {{/ if }}
setIsLoading(true);
try {
- const { data: res } = await {{ getServiceImplForPage page }}.update(payloadDiff.current);
+ const { data: res } = await {{ getServiceImplForPage page }}.update(cleanUpPayload(payloadDiff.current));
if (res) {
showSuccessSnack(t('judo.action.save.success', { defaultValue: 'Changes saved' }));
{{# and page.openInDialog action.actionDefinition.autoCloseOnSave }}
diff --git a/judo-ui-react/src/main/resources/actor/src/pages/index.tsx.hbs b/judo-ui-react/src/main/resources/actor/src/pages/index.tsx.hbs
index 4599aba9..f2d2527f 100644
--- a/judo-ui-react/src/main/resources/actor/src/pages/index.tsx.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/pages/index.tsx.hbs
@@ -33,6 +33,7 @@ import { OBJECTCLASS } from '@pandino/pandino-api';
{{# if (containerHasTimeInput page.container) }}uiTimeToServiceTime,{{/ if }}
useErrorHandler,
cleanUpPayload,
+ decoratePayloadWithMetaFromData,
{{# if (hasExportAction page) }}fileHandling,{{/ if }}
} from '~/utilities';
import type {
@@ -146,11 +147,31 @@ export default function {{ pageName page }}() {
{{# if page.container.view }}
const getPageQueryCustomizer: () => {{ classDataName (getReferenceClassType page) 'QueryCustomizer' }} = () => ({
- _mask: actions.getMask ? actions.getMask!() : '{{ getMaskForView page.container }}',
+ _mask: actions.getMask ? actions.getMask!() : '{{ serializeMaskForView page }}',
});
{{/ if }}
{{/ unless }}
+ // Masks
+ {{# if page.container.table }}
+ {{# with (getFirstTableForContainer page.container) as |table| }}
+ const getMask: () => string = () => {
+ return '{{ serializeMaskForTable table page }}';
+ };
+ {{/ with }}
+ {{/ if }}
+ {{# if page.container.view }}
+ const getMask = () => '{{ serializeMaskForView page }}';
+ {{/ if }}
+ {{# unless page.container.table }}
+ {{# each page.container.links as |link| }}
+ const get{{ firstToUpper link.relationType.name }}Mask = () => '{{ serializeMaskForLink link page }}';
+ {{/ each }}
+ {{# each page.container.tables as |table| }}
+ const get{{ firstToUpper table.relationName }}Mask = () => '{{ serializeMaskForTable table page }}';
+ {{/ each }}
+ {{/ unless }}
+
// Private actions
const submit = async () => {
{{# with (getCreateActionForPage page) as |action| }}
@@ -163,19 +184,53 @@ export default function {{ pageName page }}() {
await {{ simpleActionDefinitionName action.actionDefinition }}();
{{/ with }}
};
- const refresh = async () => {
+ const refresh = async (forced = false) => {
{{# if page.container.table }}
setRefreshCounter((prev) => prev + 1);
{{/ if }}
{{# if page.container.view }}
- {{# with (getRefreshActionDefinitionForContainer page.container) as |refreshActionDefinition| }}
- if (actions.{{ simpleActionDefinitionName refreshActionDefinition }}) {
- await actions.{{ simpleActionDefinitionName refreshActionDefinition }}!(processQueryCustomizer(getPageQueryCustomizer()));
- }
- {{/ with }}
+ if (!editMode || forced) {
+ {{# with (getRefreshActionDefinitionForContainer page.container) as |refreshActionDefinition| }}
+ if (actions.{{ simpleActionDefinitionName refreshActionDefinition }}) {
+ await actions.{{ simpleActionDefinitionName refreshActionDefinition }}!(processQueryCustomizer(getPageQueryCustomizer()));
+ }
+ {{/ with }}
+ }
{{/ if }}
};
+ // Validation
+ {{# unless page.container.table }}
+ {{# each page.container.links as |link| }}
+ {{# and page.dataElement.isUpdateValidatable link.relationType.isUpdateValidatable link.isEager }}
+ const validate{{ firstToUpper link.relationType.name }} = async (linkData: {{ classDataName link.relationType.target '' }}): Promise => {
+ await {{ getServiceImplForPage page }}.validateUpdate({{# unless page.dataElement.isAccess }}null as any, {{/ unless }}{
+ ...cleanUpPayload({
+ ...payloadDiff.current,
+ {{ link.relationType.name }}: {
+ ...linkData,
+ } as any
+ }),
+ } as {{ classDataName (getReferenceClassType page) 'Stored' }});
+ };
+ {{/ and }}
+ {{/ each }}
+ {{# each page.container.tables as |table| }}
+ {{# and page.dataElement.isUpdateValidatable table.relationType.isUpdateValidatable table.isEager }}
+ const validate{{ firstToUpper table.relationType.name }} = async (tableData: {{ classDataName table.relationType.target '' }}): Promise => {
+ await {{ getServiceImplForPage page }}.validateUpdate({{# unless page.dataElement.isAccess }}null as any, {{/ unless }}{
+ ...cleanUpPayload({
+ ...payloadDiff.current,
+ {{ table.relationType.name }}: [
+ { ...tableData } as any
+ ],
+ }),
+ } as {{ classDataName (getReferenceClassType page) 'Stored' }});
+ };
+ {{/ and }}
+ {{/ each }}
+ {{/ unless }}
+
// Pandino Action overrides
const { service: customActionsHook } = useTrackService<{{ containerComponentName page.container }}ActionsHook>(`(${OBJECTCLASS}=${ {{~ camelCaseNameToInterfaceKey (pageName page) }}_ACTIONS_HOOK_INTERFACE_KEY})`);
const customActions: {{ containerComponentName page.container }}PageActionsExtended | undefined = customActionsHook?.(
@@ -219,6 +274,17 @@ export default function {{ pageName page }}() {
{{# each page.actions as |action| }}
{{ simpleActionDefinitionName action.actionDefinition }},
{{/ each }}
+ {{# unless page.container.form }}
+ getMask,
+ {{/ unless }}
+ {{# unless page.container.table }}
+ {{# each page.container.links as |link| }}
+ get{{ firstToUpper link.relationType.name }}Mask,
+ {{/ each }}
+ {{# each page.container.tables as |table| }}
+ get{{ firstToUpper table.relationName }}Mask,
+ {{/ each }}
+ {{/ unless }}
...(customActions ?? {}),
};
diff --git a/judo-ui-react/src/main/resources/actor/src/utilities/helper.ts.hbs b/judo-ui-react/src/main/resources/actor/src/utilities/helper.ts.hbs
index 0ca7ae28..f1f42e3c 100644
--- a/judo-ui-react/src/main/resources/actor/src/utilities/helper.ts.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/utilities/helper.ts.hbs
@@ -1,6 +1,7 @@
{{> fragment.header.hbs }}
import type { RandomUtils } from './interfaces';
+import type { JudoStored } from '../services/data-api/common/JudoStored';
export const exists = (element: any) => element !== undefined && element !== null;
@@ -53,3 +54,24 @@ export const GENERATOR_UUID_V4 = '{{ getUUIDv4 }}';
export const randomUtils: RandomUtils = {
getGeneratorUUIDv4: () => GENERATOR_UUID_V4,
};
+
+export const decoratePayloadWithMetaFromData: (payload: any, data: JudoStored) => any = (payload, data) => {
+ if (data.__identifier) {
+ payload.__identifier = data.__identifier;
+ }
+ if (data.__signedIdentifier) {
+ payload.__signedIdentifier = data.__signedIdentifier;
+ }
+ if (data.__version) {
+ payload.__version = data.__version;
+ }
+ if (data.__entityType) {
+ payload.__entityType = data.__entityType;
+ }
+ return payload;
+}
+
+export const cloneDeep = (obj: object): T => {
+ // We know that this is slow, if becomes a bottleneck, we can replace this with a dep, or write it manually
+ return JSON.parse(JSON.stringify(obj)) as T;
+};
diff --git a/judo-ui-react/src/main/resources/actor/src/utilities/interfaces.ts.hbs b/judo-ui-react/src/main/resources/actor/src/utilities/interfaces.ts.hbs
index 5945d285..52c26a0e 100644
--- a/judo-ui-react/src/main/resources/actor/src/utilities/interfaces.ts.hbs
+++ b/judo-ui-react/src/main/resources/actor/src/utilities/interfaces.ts.hbs
@@ -13,8 +13,10 @@ export type ColumnActionsProvider = (
options?: ColumnsActionsOptions,
) => GridColDef[];
+export type DialogResultReason = 'close' | 'submit' | 'submit-draft' | 'delete';
+
export interface DialogResult {
- result: 'close' | 'submit' | 'submit-draft';
+ result: DialogResultReason;
data?: T;
openCreated?: boolean;
}
diff --git a/judo-ui-react/src/test/java/hu/blackbelt/judo/ui/generator/react/MaskEntryTest.java b/judo-ui-react/src/test/java/hu/blackbelt/judo/ui/generator/react/MaskEntryTest.java
new file mode 100644
index 00000000..4f1154da
--- /dev/null
+++ b/judo-ui-react/src/test/java/hu/blackbelt/judo/ui/generator/react/MaskEntryTest.java
@@ -0,0 +1,48 @@
+package hu.blackbelt.judo.ui.generator.react;
+
+import hu.blackbelt.judo.ui.generator.react.mask.MaskEntry;
+import org.junit.jupiter.api.Test;
+
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class MaskEntryTest {
+ @Test
+ void testEmpty() {
+ MaskEntry mask = new MaskEntry(Set.of(), null);
+
+ assertEquals("", mask.serialize());
+ }
+
+ @Test
+ void testOnlyPrimitives() {
+ MaskEntry mask = new MaskEntry(Set.of("b", "a", "c"), null);
+
+ assertEquals("a,b,c", mask.serialize());
+ }
+
+ @Test
+ void testWithRelations() {
+ MaskEntry mask = new MaskEntry(Set.of("b", "a", "c"), null);
+ MaskEntry rel1 = new MaskEntry(Set.of("j", "f"), "rel1");
+ MaskEntry rel2 = new MaskEntry(Set.of("r", "k"), "rel2");
+ mask.addRelations(rel1, rel2);
+
+ assertEquals("a,b,c,rel1{f,j},rel2{k,r}", mask.serialize());
+ }
+
+ @Test
+ void testComplex() {
+ MaskEntry mask = new MaskEntry(Set.of("a", "b"), null);
+ MaskEntry rel1 = new MaskEntry(Set.of("j", "f"), "rel1");
+ MaskEntry rel11 = new MaskEntry(Set.of("x", "i"), "rel11");
+ rel1.addRelations(rel11);
+ MaskEntry rel111 = new MaskEntry(Set.of("u", "n"), "rel111");
+ rel11.addRelations(rel111);
+ MaskEntry rel2 = new MaskEntry(Set.of("r", "k"), "rel2");
+ mask.addRelations(rel1, rel2);
+
+ assertEquals("a,b,rel1{f,j,rel11{i,x,rel111{n,u}}},rel2{k,r}", mask.serialize());
+ }
+}
diff --git a/pom.xml b/pom.xml
index b7c12a9e..591001f7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -55,9 +55,9 @@
18.14.2
8.9.2
- 1.1.0.20240521_213816_bd94ae76_develop
+ 1.1.0.20240531_103848_5dc2f3ba_feature_JNG_5742_improve_meta_model
1.0.0.20231009_184136_321053b3_develop
- 1.0.0.20240308_130018_c6d4c7e2_feature_JNG_5598_command_masks
+ 1.0.0.20240528_150529_45bc3759_develop
3.0.0-M7