Skip to content

Commit 796ef90

Browse files
ISSUE-137519: fixing loading custom components with longer Pega server url (not ending on /prweb)
1 parent 21419f3 commit 796ef90

File tree

4 files changed

+205
-3
lines changed

4 files changed

+205
-3
lines changed

core/src/commonMain/kotlin/com/pega/constellation/sdk/kmp/core/api/ComponentScript.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import kotlin.jvm.JvmInline
88
* @property file path to the script file. Relative to /assets folder or provided by [Res.getUri].
99
*
1010
* Examples of creating [ComponentScript]:
11+
* For script placed in multiplatform app in 'commonMain/composeResources':
1112
* ```kotlin
1213
* ComponentScript(
1314
* file = Res.getUri("files/components_overrides/email.component.override.js")
1415
* )
1516
* ```
1617
*
18+
* For script placed in native android app in 'src/main/assets'
1719
* ```kotlin
1820
* ComponentScript(
1921
* file = "components_overrides/email.component.override.js"
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
export class EmailComponent {
2+
// these variables are present in all components
3+
pConn; // object which keeps internal information about component coming from Constellation Core JS library
4+
jsComponentPConnect; // bridge between JS components and Constellation Core JS Library
5+
jsComponentPConnectData = {}; // object which contains additional data like validateMessage
6+
propName; // name of the property which this component represent
7+
compId; // unique id of the component, it will be also used to identify component in Native code
8+
type; // type of the component e.g.: 'Email' in this case
9+
10+
// these are props sent from JS to Native code. Might be different for different component.
11+
props = {
12+
value: '',
13+
label: '',
14+
visible: true,
15+
required: false,
16+
disabled: false,
17+
readOnly: false,
18+
helperText: '',
19+
placeholder: '',
20+
validateMessage: '',
21+
displayMode: ''
22+
}
23+
24+
constructor(componentsManager, pConn) {
25+
this.pConn = pConn;
26+
this.componentsManager = componentsManager;
27+
this.compId = this.componentsManager.getNextComponentId();
28+
this.jsComponentPConnect = componentsManager.jsComponentPConnect
29+
this.type = pConn.meta.type
30+
}
31+
32+
init() {
33+
console.log("Initiating custom email component!")
34+
// registerAndSubscribeComponent registers component to Constellation Core JS redux to receive updates
35+
// onStateChange is a callback called when some change occurs on any component so it is called very frequently
36+
this.jsComponentPConnectData = this.jsComponentPConnect.registerAndSubscribeComponent(this, this.onStateChange);
37+
// causes adding component on Native side
38+
this.componentsManager.onComponentAdded(this);
39+
this.checkAndUpdate();
40+
}
41+
42+
destroy() {
43+
// unsubscribing from Constellation Core JS redux
44+
this.jsComponentPConnectData.unsubscribeFn?.();
45+
// causes removing component on Native side
46+
this.componentsManager.onComponentRemoved(this);
47+
}
48+
49+
// runs whenever components is updated by its parent
50+
update(pConn) {
51+
if (this.pConn !== pConn) {
52+
this.pConn = pConn;
53+
this.jsComponentPConnectData.unsubscribeFn?.();
54+
this.jsComponentPConnectData = this.jsComponentPConnect.registerAndSubscribeComponent(this, this.onStateChange);
55+
this.checkAndUpdate();
56+
}
57+
}
58+
59+
onStateChange() {
60+
this.checkAndUpdate();
61+
}
62+
63+
checkAndUpdate() {
64+
// onStateChange runs very frequently so we run shouldComponentUpdate to check if this component changed and should be updated
65+
const bUpdateSelf = this.jsComponentPConnect.shouldComponentUpdate(this);
66+
67+
if (bUpdateSelf) {
68+
this.updateSelf();
69+
}
70+
}
71+
72+
updateSelf() {
73+
const configProps = this.pConn.resolveConfigProps(this.pConn.getConfigProps());
74+
this.props.displayMode = configProps.displayMode;
75+
this.props.label = configProps.label;
76+
77+
if (configProps.value != undefined) {
78+
this.props.value = configProps.value;
79+
}
80+
this.props.helperText = `This is overridden email component ${configProps.helperText || ''}`;
81+
this.props.placeholder = configProps.placeholder || '';
82+
83+
if (configProps.required != null) {
84+
this.props.required = getBooleanValue(configProps.required);
85+
}
86+
87+
if (configProps.visibility != null) {
88+
this.props.visible = getBooleanValue(configProps.visibility);
89+
}
90+
91+
if (configProps.disabled != undefined) {
92+
this.props.disabled = getBooleanValue(configProps.disabled);
93+
}
94+
95+
if (configProps.readOnly != null) {
96+
this.props.readOnly = getBooleanValue(configProps.readOnly);
97+
}
98+
this.props.validateMessage = this.jsComponentPConnectData.validateMessage || ''
99+
this.propName = this.pConn.getStateProps().value;
100+
// sends updated props to Native side
101+
this.componentsManager.onComponentPropsUpdate(this);
102+
}
103+
104+
// runs whenever Native side sends event to this component
105+
onEvent(event) {
106+
const value = event.componentData !== undefined ? event.componentData.value : undefined;
107+
const focused = event.eventData !== undefined ? event.eventData.focused : undefined
108+
switch (event.type) {
109+
case 'FieldChange':
110+
console.log(`FieldChange for ${this.compId}, value: ${value}`);
111+
this.fieldOnChange(value);
112+
break;
113+
case 'FieldChangeWithFocus':
114+
console.log(`FieldChangeWithFocus for ${this.compId}, value: ${value}, focused: ${focused}`);
115+
if (focused === "false" || focused === false) {
116+
this.fieldOnBlur(value)
117+
}
118+
break;
119+
default:
120+
console.log(`unknown event type: ${event.type}`)
121+
}
122+
}
123+
124+
// helper function called by component manager for event of changing field value (FieldChange event)
125+
fieldOnChange(value) {
126+
this.props.value = value;
127+
}
128+
129+
// helper function called by component manager for event of changing field value with focus (FieldChangeWithFocus event)
130+
fieldOnBlur(value) {
131+
this.props.value = value || this.props.value
132+
const submittedValue = this.pConn.resolveConfigProps(this.pConn.getConfigProps()).value;
133+
if (this.props.value !== submittedValue) {
134+
handleEvent(this.pConn.getActionsApi(), 'changeNblur', this.propName, this.props.value);
135+
}
136+
clearErrorMessagesIfNoErrors(this.pConn, this.propName, this.jsComponentPConnectData.validateMessage);
137+
}
138+
}
139+
140+
function getBooleanValue(value) {
141+
switch(typeof value) {
142+
case 'string':
143+
return value.toLowerCase() === 'true';
144+
case 'boolean':
145+
return value;
146+
default:
147+
throw new Error(`Cannot parse value: ${value} to boolean`)
148+
}
149+
}
150+
151+
152+
function handleEvent(actions, eventType, propName, value) {
153+
switch (eventType) {
154+
case 'change':
155+
// updates value in Constellation Core JS Redux and clears error messages
156+
// see: https://docs.pega.com/bundle/pcore-pconnect/page/pcore-pconnect-public-apis/api/updatefieldvalue-propname-value.html
157+
actions.updateFieldValue(propName, value);
158+
break;
159+
case 'blur':
160+
// triggers additional actions in Constellation Core JS like validation
161+
// see https://docs.pega.com/bundle/pcore-pconnect/page/pcore-pconnect-public-apis/api/triggerfieldchange-propname-value.html
162+
actions.triggerFieldChange(propName, value);
163+
break;
164+
case 'changeNblur':
165+
actions.updateFieldValue(propName, value);
166+
actions.triggerFieldChange(propName, value);
167+
break;
168+
default:
169+
break;
170+
}
171+
}
172+
173+
function clearErrorMessagesIfNoErrors(pConn, propName, validateMessage) {
174+
if (!validateMessage || validateMessage === '') {
175+
pConn.clearErrorMessages({
176+
property: propName
177+
});
178+
}
179+
}

samples/android-compose-app/src/main/java/com/pega/constellation/sdk/kmp/samples/androidcomposeapp/AndroidComposeActivity.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ import com.pega.constellation.sdk.kmp.core.ConstellationSdk.State.Initial
2828
import com.pega.constellation.sdk.kmp.core.ConstellationSdk.State.Loading
2929
import com.pega.constellation.sdk.kmp.core.ConstellationSdk.State.Ready
3030
import com.pega.constellation.sdk.kmp.core.ConstellationSdkConfig
31+
import com.pega.constellation.sdk.kmp.core.api.ComponentDefinition
32+
import com.pega.constellation.sdk.kmp.core.api.ComponentManager
33+
import com.pega.constellation.sdk.kmp.core.api.ComponentScript
34+
import com.pega.constellation.sdk.kmp.core.api.ComponentType
35+
import com.pega.constellation.sdk.kmp.core.components.fields.EmailComponent
3136
import com.pega.constellation.sdk.kmp.engine.webview.android.AndroidWebViewEngine
3237
import com.pega.constellation.sdk.kmp.ui.components.cmp.controls.form.Alert
3338
import com.pega.constellation.sdk.kmp.ui.components.cmp.controls.form.internal.AppContext
@@ -57,7 +62,17 @@ class AndroidComposeActivity : ComponentActivity() {
5762
private fun initConstellation() {
5863
val config = ConstellationSdkConfig(
5964
pegaUrl = AndroidSDKConfig.PEGA_URL,
60-
pegaVersion = AndroidSDKConfig.PEGA_VERSION
65+
pegaVersion = AndroidSDKConfig.PEGA_VERSION,
66+
componentManager = ComponentManager.create(
67+
listOf(
68+
ComponentDefinition(
69+
ComponentType("Email"),
70+
ComponentScript("components_overrides/email.component.override.js"),
71+
::EmailComponent
72+
)
73+
)
74+
),
75+
debuggable = true
6176
)
6277
val caseClassName = AndroidSDKConfig.PEGA_CASE_CLASS_NAME
6378
val engine = AndroidWebViewEngine(this, buildHttpClient())

scripts/dxcomponents/mappings/components-overrides.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,15 @@ function importComponent(type, path) {
2222
if (isIOS()) {
2323
return import(path);
2424
} else if (isAndroid()) {
25-
return fetch(path)
25+
return fetch(`/${path}`)
2626
.then(response => response.text())
27-
.then(textContent => import('data:text/javascript;charset=utf-8,' + textContent));
27+
.then(textContent => {
28+
if (textContent && textContent.trim().length > 0) {
29+
return import('data:text/javascript;charset=utf-8,' + textContent)
30+
} else {
31+
throw Error(`Empty component content for ${type} at path: ${path}`);
32+
}
33+
});
2834
} else {
2935
return Promise.reject("Unexpected platform.");
3036
}

0 commit comments

Comments
 (0)