Skip to content

Commit 158036b

Browse files
arunattri26Arun Kumar Attri
andauthored
FORMS-17107: Client-side custom function parsing changes (#1562)
* FORMS-17107: Client-side custom function parsing changes * FORMS-17107: Client-side custom function parsing changes * FORMS-17107: Client-side custom function parsing changes * FORMS-17107: Client-side custom function parsing changes * FORMS-17107: Client-side custom function parsing changes * FORMS-17107: incorporated review comments * FORMS-17107: Client-side custom function parsing changes * FORMS-17107: Client-side custom function parsing changes * FORMS-17107: Client-side custom function parsing changes * FORMS-17107: incorporated review comments * FORMS-17107: incorporated review comments * FORMS-19095: Enable API Call tool UI for both AEM and SPA * FORMS-17107: Client-side custom function parsing changes * FORMS-17107: Client-side custom function parsing changes * FORMS-17107: Client-side custom function parsing changes * FORMS-17107: Client-side custom function parsing changes --------- Co-authored-by: Arun Kumar Attri <aattri@adobe.com>
1 parent 945972f commit 158036b

File tree

3 files changed

+189
-69
lines changed

3 files changed

+189
-69
lines changed

it/config/src/main/content/jcr_root/apps/system/config/com.adobe.granite.toggle.impl.dev.DynamicToggleProviderImpl.cfg.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
"FT_FORMS-14255",
2323
"FT_FORMS-14068",
2424
"FT_FORMS-16351",
25-
"FT_FORMS-14518"
25+
"FT_FORMS-14518",
26+
"FT_FORMS-13519",
27+
"FT_FORMS-17107"
2628
]
2729
}

ui.frontend/src/RuleUtils.js

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*******************************************************************************
2+
* Copyright 2025 Adobe
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
******************************************************************************/
16+
17+
import HTTPAPILayer from "./HTTPAPILayer.js";
18+
import {FunctionRuntime} from '@aemforms/af-core';
19+
20+
/**
21+
* @module FormView
22+
*/
23+
24+
/**
25+
* Utility class with various helper functions related to rules.
26+
*/
27+
class RuleUtils {
28+
/**
29+
* @deprecated Use `registerCustomFunctionsV2` instead.
30+
* Registers custom functions from clientlibs.
31+
* @param {string} formId - The form ID.
32+
*/
33+
static async registerCustomFunctions(formId) {
34+
const funcConfig = await HTTPAPILayer.getCustomFunctionConfig(formId);
35+
console.debug("Fetched custom functions: " + JSON.stringify(funcConfig));
36+
if (funcConfig && funcConfig.customFunction) {
37+
const funcObj = funcConfig.customFunction.reduce((accumulator, func) => {
38+
if (window[func.id]) {
39+
accumulator[func.id] = window[func.id];
40+
}
41+
return accumulator;
42+
}, {});
43+
FunctionRuntime.registerFunctions(funcObj);
44+
}
45+
}
46+
47+
/**
48+
* Registers custom functions from clientlibs.
49+
* @param {object} formJson - The Sling model exporter representation of the form
50+
*/
51+
static async registerCustomFunctionsV2(formJson) {
52+
let funcConfig;
53+
let customFunctions = [];
54+
const customFunctionsUrl = formJson.properties['fd:customFunctionsUrl'];
55+
if (customFunctionsUrl) {
56+
funcConfig = await HTTPAPILayer.getJson(customFunctionsUrl);
57+
} else {
58+
funcConfig = await HTTPAPILayer.getCustomFunctionConfig(formJson.id);
59+
}
60+
console.debug("Fetched custom functions: " + JSON.stringify(funcConfig));
61+
62+
if (funcConfig && funcConfig.clientSideParsingEnabled) {
63+
customFunctions = this.extractFunctionNames(formJson);
64+
} else if (funcConfig && funcConfig.customFunction) {
65+
customFunctions = funcConfig.customFunction;
66+
}
67+
68+
const funcObj = customFunctions.reduce((accumulator, func) => {
69+
if (Object.prototype.hasOwnProperty.call(window, func.id) && typeof window[func.id] === 'function') {
70+
accumulator[func.id] = window[func.id];
71+
}
72+
return accumulator;
73+
}, {});
74+
console.debug("Registering custom functions:", funcObj);
75+
FunctionRuntime.registerFunctions(funcObj);
76+
}
77+
78+
/**
79+
* Extract custom functions from form json and return them in the required format.
80+
* @param {object} formJson - The Sling model exporter representation of the form
81+
* @returns {Array<{id: string}>} - Array of objects with function names as ids
82+
*/
83+
static extractFunctionNames = (formJson) => {
84+
const functionNames = new Set();
85+
86+
// Helper function to extract function names from rule expressions
87+
const extractFunctionNamesFromRuleExpression = (ruleExpression) => {
88+
// Match patterns like functionName() with optional parameters
89+
const functionPattern = /(\w+)\s*\(/g;
90+
let match;
91+
92+
while ((match = functionPattern.exec(ruleExpression)) !== null) {
93+
// Add the function name to our set
94+
functionNames.add(match[1]);
95+
}
96+
};
97+
98+
// Process events at the form level
99+
if (formJson.events) {
100+
Object.values(formJson.events).forEach(eventArray => {
101+
eventArray.forEach(eventString => {
102+
extractFunctionNamesFromRuleExpression(eventString);
103+
});
104+
});
105+
}
106+
107+
// Process rules at the form level
108+
if (formJson.rules) {
109+
Object.values(formJson.rules).forEach(ruleExpression => {
110+
extractFunctionNamesFromRuleExpression(ruleExpression);
111+
});
112+
}
113+
114+
// Recursively process events and rules in form items
115+
const processItems = (items) => {
116+
if (!items) return;
117+
118+
Object.values(items).forEach(item => {
119+
if (item.events) {
120+
Object.values(item.events).forEach(eventArray => {
121+
eventArray.forEach(eventString => {
122+
extractFunctionNamesFromRuleExpression(eventString);
123+
});
124+
});
125+
}
126+
127+
// Process rules within items
128+
if (item.rules) {
129+
Object.values(item.rules).forEach(ruleExpression => {
130+
extractFunctionNamesFromRuleExpression(ruleExpression);
131+
});
132+
}
133+
134+
// Process nested items if they exist
135+
if (item[':items']) {
136+
processItems(item[':items']);
137+
}
138+
});
139+
};
140+
141+
if (formJson[':items']) {
142+
processItems(formJson[':items']);
143+
}
144+
145+
console.debug("Extracted function names from form JSON:", functionNames);
146+
147+
return Array.from(functionNames).map(functionName => ({
148+
id: functionName
149+
}));
150+
};
151+
152+
/**
153+
* Registers exported custom functions from a given script URL.
154+
* @param {string} url - The script URL to load custom functions from.
155+
* @throws {Error} - If there's an error loading the custom functions from the URL.
156+
* @returns {Promise<void>} - A promise that resolves when the functions are registered.
157+
*/
158+
static async registerCustomFunctionsByUrl(url) {
159+
try {
160+
if (url != null && url.trim().length > 0) {
161+
// webpack ignore is added because webpack was converting this to a static import upon bundling resulting in error.
162+
//This Url should whitelist the AEM author/publish domain in the Cross Origin Resource Sharing (CORS) configuration.
163+
const customFunctionModule = await import(/*webpackIgnore: true*/ url);
164+
const keys = Object.keys(customFunctionModule);
165+
const functions = [];
166+
for (const name of keys) {
167+
const funcDef = customFunctionModule[name];
168+
if (typeof funcDef === 'function') {
169+
functions[name] = funcDef;
170+
}
171+
}
172+
FunctionRuntime.registerFunctions(functions);
173+
}
174+
} catch (e) {
175+
if(window.console){
176+
console.error("error in loading custom functions from url "+url+" with message "+e.message);
177+
}
178+
}
179+
}
180+
}
181+
182+
export default RuleUtils;

ui.frontend/src/utils.js

Lines changed: 4 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import HTTPAPILayer from "./HTTPAPILayer.js";
1919
import {customFunctions} from "./customFunctions.js";
2020
import {FunctionRuntime} from '@aemforms/af-core';
2121
import {loadXfa} from "./handleXfa";
22+
import RuleUtils from "./RuleUtils.js";
2223

2324
/**
2425
* @module FormView
@@ -269,49 +270,6 @@ class Utils {
269270
(button) => button.addEventListener('click', handler));
270271
}
271272

272-
/**
273-
* @deprecated Use `registerCustomFunctionsV2` instead.
274-
* Registers custom functions from clientlibs.
275-
* @param {string} formId - The form ID.
276-
*/
277-
static async registerCustomFunctions(formId) {
278-
const funcConfig = await HTTPAPILayer.getCustomFunctionConfig(formId);
279-
console.debug("Fetched custom functions: " + JSON.stringify(funcConfig));
280-
if (funcConfig && funcConfig.customFunction) {
281-
const funcObj = funcConfig.customFunction.reduce((accumulator, func) => {
282-
if (window[func.id]) {
283-
accumulator[func.id] = window[func.id];
284-
}
285-
return accumulator;
286-
}, {});
287-
FunctionRuntime.registerFunctions(funcObj);
288-
}
289-
}
290-
291-
/**
292-
* Registers custom functions from clientlibs.
293-
* @param {object} formJson - The Sling model exporter representation of the form
294-
*/
295-
static async registerCustomFunctionsV2(formJson) {
296-
let funcConfig;
297-
const customFunctionsUrl = formJson.properties['fd:customFunctionsUrl'];
298-
if (customFunctionsUrl) {
299-
funcConfig = await HTTPAPILayer.getJson(customFunctionsUrl);
300-
} else {
301-
funcConfig = await HTTPAPILayer.getCustomFunctionConfig(formJson.id);
302-
}
303-
console.debug("Fetched custom functions: " + JSON.stringify(funcConfig));
304-
if (funcConfig && funcConfig.customFunction) {
305-
const funcObj = funcConfig.customFunction.reduce((accumulator, func) => {
306-
if (window[func.id]) {
307-
accumulator[func.id] = window[func.id];
308-
}
309-
return accumulator;
310-
}, {});
311-
FunctionRuntime.registerFunctions(funcObj);
312-
}
313-
}
314-
315273
/**
316274
* Sets up the Form Container.
317275
* @param {Function} createFormContainer - The function to create a form container.
@@ -350,8 +308,8 @@ class Utils {
350308
_formJson = await HTTPAPILayer.getFormDefinition(_path, _pageLang);
351309
}
352310
console.debug("fetched model json", _formJson);
353-
await this.registerCustomFunctionsV2( _formJson);
354-
await this.registerCustomFunctionsByUrl(customFunctionUrl);
311+
await RuleUtils.registerCustomFunctionsV2( _formJson);
312+
await RuleUtils.registerCustomFunctionsByUrl(customFunctionUrl);
355313
const urlSearchParams = new URLSearchParams(window.location.search);
356314
const params = Object.fromEntries(urlSearchParams.entries());
357315
let _prefillData = {};
@@ -385,29 +343,7 @@ class Utils {
385343
}
386344
}
387345

388-
static async registerCustomFunctionsByUrl(url) {
389-
try {
390-
if (url != null && url.trim().length > 0) {
391-
// webpack ignore is added because webpack was converting this to a static import upon bundling resulting in error.
392-
//This Url should whitelist the AEM author/publish domain in the Cross Origin Resource Sharing (CORS) configuration.
393-
const customFunctionModule = await import(/*webpackIgnore: true*/ url);
394-
const keys = Object.keys(customFunctionModule);
395-
const functions = [];
396-
for (const name of keys) {
397-
const funcDef = customFunctionModule[name];
398-
if (typeof funcDef === 'function') {
399-
functions[name] = funcDef;
400-
}
401-
}
402-
FunctionRuntime.registerFunctions(functions);
403-
}
404-
} catch (e) {
405-
if(window.console){
406-
console.error("error in loading custom functions from url "+url+" with message "+e.message);
407-
}
408-
}
409-
}
410-
346+
411347
/**
412348
* For backward compatibility with older data formats of prefill services like FDM.
413349
* @param {object} prefillJson - The prefill JSON object.

0 commit comments

Comments
 (0)