Skip to content

Commit 79fcf72

Browse files
authored
Merge pull request #225 from pegasystems/feat/checkboxrow
improve checkboxrow to add support for selectAll
2 parents dcf85a7 + 7e44108 commit 79fcf72

File tree

11 files changed

+2682
-1802
lines changed

11 files changed

+2682
-1802
lines changed

.github/workflows/validatepr.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name: Validate PRs
33
permissions:
44
contents: read
55
pull-requests: write
6-
6+
77
on:
88
# Event for the workflow to run on
99
push:
88 KB
Loading
-1.17 KB
Loading

package-lock.json

Lines changed: 2541 additions & 1748 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@
5858
"validateAll": "custom-dx-components buildAllComponents"
5959
},
6060
"dependencies": {
61-
"@arcgis/core": "^4.33.14",
62-
"@dagrejs/dagre": "^1.1.5",
61+
"@arcgis/core": "^4.34.8",
62+
"@dagrejs/dagre": "^1.1.8",
6363
"@fullcalendar/core": "^6.1.19",
6464
"@fullcalendar/daygrid": "^6.1.19",
6565
"@fullcalendar/react": "^6.1.19",
@@ -81,9 +81,9 @@
8181
"styled-components": "^5.3.11"
8282
},
8383
"devDependencies": {
84-
"@babel/preset-env": "^7.28.3",
85-
"@babel/preset-react": "^7.27.1",
86-
"@babel/preset-typescript": "^7.27.1",
84+
"@babel/preset-env": "^7.28.5",
85+
"@babel/preset-react": "^7.28.5",
86+
"@babel/preset-typescript": "^7.28.5",
8787
"@pega/configs": "^0.17.0",
8888
"@pega/custom-dx-components": "^24.2.18",
8989
"@pega/eslint-config": "^0.17.0",
@@ -96,33 +96,33 @@
9696
"@storybook/react-webpack5": "^7.6.19",
9797
"@storybook/test-runner": "0.16.0",
9898
"@storybook/theming": "^7.6.19",
99-
"@testing-library/jest-dom": "^6.8.0",
99+
"@testing-library/jest-dom": "^6.9.1",
100100
"@testing-library/react": "^12.1.5",
101101
"@types/jest": "^30.0.0",
102102
"@types/react": "^17.0.80",
103103
"@types/react-dom": "^17.0.25",
104104
"@types/react-image-magnifiers": "^1.3.5",
105-
"@types/styled-components": "^5.1.34",
105+
"@types/styled-components": "^5.1.35",
106106
"axe-playwright": "^2.2.2",
107-
"cspell": "^9.2.1",
107+
"cspell": "^9.3.1",
108108
"eslint": "^8.57.0",
109109
"eslint-plugin-import": "^2.32.0",
110-
"eslint-plugin-jest": "^29.0.1",
110+
"eslint-plugin-jest": "^29.1.0",
111111
"eslint-plugin-jsx-a11y": "^6.10.2",
112112
"eslint-plugin-mdx": "^3.6.2",
113113
"eslint-plugin-prettier": "^5.5.4",
114114
"eslint-plugin-react": "^7.37.5",
115115
"eslint-plugin-react-hooks": "^5.2.0",
116-
"jest": "^30.1.3",
116+
"jest": "^30.2.0",
117117
"jest-canvas-mock": "^2.5.2",
118-
"jest-environment-jsdom": "^30.1.2",
118+
"jest-environment-jsdom": "^30.2.0",
119119
"npm-run-all": "^4.1.5",
120120
"prettier": "^3.6.2",
121121
"sort-package-json": "^3.4.0",
122122
"storybook": "^7.6.19",
123-
"stylelint": "16.24.0",
124-
"ts-jest": "^29.4.4",
125-
"typescript": "^5.9.2"
123+
"stylelint": "16.25.0",
124+
"ts-jest": "^29.4.5",
125+
"typescript": "^5.9.3"
126126
},
127127
"organization": "Pega"
128128
}

src/components/Pega_Extensions_CheckboxRow/Docs.mdx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Meta, Primary, Controls, Story } from '@storybook/blocks';
1+
import { Meta, Primary, Controls, Story } from '@storybook/addon-docs/blocks';
22
import * as DemoStories from './demo.stories';
33

44
<Meta of={DemoStories} />
@@ -9,6 +9,8 @@ The Checkbox Row component is a specialized component used in the context of an
99

1010
This component shares the same configuration as the standard checkbox, but it includes an optional property called labelProperty. If you set a field for this property, the checkbox's caption will automatically use that field's value.
1111

12+
The component can also be used outside of the embedded data list to provide the Select All functionality - When configuring the component, set the selectAllProperty to the path of the property that will contain the selection property, for example "Policies.IsSelected".
13+
1214
<Primary />
1315

1416
# Props
@@ -19,6 +21,8 @@ This component shares the same configuration as the standard checkbox, but it in
1921

2022
To use this component, use an embedded data list field configure as an editable inline table. Add a boolean field in the first field of the table and change it to this component. Set labelProperty if needed. Include other boolean fields in the table as editable.
2123

24+
![Configuration](CheckboxRow_Configuration2.png)
25+
2226
![Configuration](CheckboxRow_Configuration.png)
2327

2428
![Configuration](CheckboxRow_Configuration1.png)

src/components/Pega_Extensions_CheckboxRow/config.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939
"name": "labelProperty",
4040
"label": "Custom label property"
4141
},
42+
{
43+
"name": "selectAllProperty",
44+
"label": "Select All Property",
45+
"format": "TEXT"
46+
},
4247
{
4348
"name": "readOnly",
4449
"label": "Edit mode",

src/components/Pega_Extensions_CheckboxRow/demo.stories.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ export default {
1919
disable: true,
2020
},
2121
},
22+
selectAllProperty: {
23+
table: {
24+
disable: true,
25+
},
26+
},
2227
variant: {
2328
table: {
2429
disable: true,
@@ -156,6 +161,7 @@ export const Default: Story = CheckboxRowDemo({
156161
labelProperty: '',
157162
validatemessage: '',
158163
helperText: '',
164+
selectAllProperty: '',
159165
disabled: false,
160166
readOnly: false,
161167
required: false,

src/components/Pega_Extensions_CheckboxRow/index.tsx

Lines changed: 30 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useState, useRef, type MouseEvent } from 'react';
22
import { withConfiguration, Checkbox, Text } from '@pega/cosmos-react-core';
33
import '../create-nonce';
4+
import { updateAllSiblingCheckboxes, updateBooleanFieldsOnPage } from './utils';
45

56
export type CheckboxRowProps = {
67
getPConnect?: any;
@@ -13,25 +14,13 @@ export type CheckboxRowProps = {
1314
readOnly?: boolean;
1415
required?: boolean;
1516
testId?: string;
17+
selectAllProperty?: string;
1618
fieldMetadata?: any;
1719
additionalProps?: any;
1820
/** display mode */
1921
displayMode?: 'DISPLAY_ONLY' | '';
2022
};
2123

22-
// Helper function to safely access nested object properties
23-
const getNestedValue = (obj: any, path: string) => {
24-
return path.split('.').reduce((current, key) => {
25-
// Handle array indices like 'Policies[0]'
26-
if (key.includes('[') && key.includes(']')) {
27-
const arrayKey = key.substring(0, key.indexOf('['));
28-
const index = parseInt(key.substring(key.indexOf('[') + 1, key.indexOf(']')), 10);
29-
return current?.[arrayKey]?.[index];
30-
}
31-
return current?.[key];
32-
}, obj);
33-
};
34-
3524
// props passed in combination of props from property panel (config.json) and run time props from Constellation
3625
// any default values in config.pros should be set in defaultProps at bottom of this file
3726
export const PegaExtensionsCheckboxRow = (props: CheckboxRowProps) => {
@@ -43,6 +32,7 @@ export const PegaExtensionsCheckboxRow = (props: CheckboxRowProps) => {
4332
value,
4433
helperText = '',
4534
testId = '',
35+
selectAllProperty = '',
4636
additionalProps,
4737
displayMode,
4838
} = props;
@@ -74,6 +64,32 @@ export const PegaExtensionsCheckboxRow = (props: CheckboxRowProps) => {
7464
if (displayMode === 'DISPLAY_ONLY') {
7565
return <Text>{displayComp}</Text>;
7666
}
67+
const handleChange = (checked: boolean) => {
68+
setInputValue(checked);
69+
if (value === checked) return;
70+
71+
actions.updateFieldValue(propName, checked);
72+
hasValueChange.current = true;
73+
74+
const contextName = pConn.getContextName();
75+
const storeData = (window as any).PCore.getStore().getState().data?.[contextName];
76+
if (!storeData) return;
77+
78+
if (selectAllProperty) {
79+
updateAllSiblingCheckboxes({ selectAllProperty, checked, pConn, storeData });
80+
return;
81+
}
82+
83+
const pageRef = pConn.options.pageReference; // e.g. caseInfo.content.Policies[0]
84+
updateBooleanFieldsOnPage({
85+
pageRef,
86+
checked,
87+
actions,
88+
storeData,
89+
excludeProp: propName,
90+
});
91+
};
92+
7793
return (
7894
<Checkbox
7995
{...additionalProps}
@@ -85,30 +101,7 @@ export const PegaExtensionsCheckboxRow = (props: CheckboxRowProps) => {
85101
disabled={disabled}
86102
readOnly={readOnly}
87103
required={required}
88-
onChange={(e: MouseEvent<HTMLInputElement>) => {
89-
setInputValue(e.currentTarget.checked);
90-
if (value !== e.currentTarget.checked) {
91-
actions.updateFieldValue(propName, e.currentTarget.checked);
92-
hasValueChange.current = true;
93-
const context = getPConnect().getContextName();
94-
95-
const storeData = (window as any).PCore.getStore().getState().data?.[context];
96-
const pageRef = getPConnect().options.pageReference;
97-
/* page Ref could be 'caseInfo.content.Policies[0]' */
98-
99-
const data: any = getNestedValue(storeData, pageRef);
100-
101-
/* Iterate over object - if a property is of type boolean - call updateFieldValue to set the value to e.currentTarget.checked */
102-
Object.keys(data).forEach((key) => {
103-
if (typeof data[key] === 'boolean') {
104-
const otherPropName = `.${key}`;
105-
if (otherPropName !== propName) {
106-
actions.updateFieldValue(otherPropName, e.currentTarget.checked);
107-
}
108-
}
109-
});
110-
}
111-
}}
104+
onChange={(e: MouseEvent<HTMLInputElement>) => handleChange(e.currentTarget.checked)}
112105
onBlur={(e: MouseEvent<HTMLInputElement>) => {
113106
if ((!value || hasValueChange.current) && !readOnly) {
114107
actions.triggerFieldChange(propName, e.currentTarget.checked);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Helper: safely access nested object properties with optional array indices like Section[0]
2+
export const getNestedValue = (obj: any, path: string) => {
3+
if (!obj || !path) return undefined;
4+
return path.split('.').reduce((current, key) => {
5+
if (!current) return undefined;
6+
if (key.includes('[') && key.includes(']')) {
7+
const arrayKey = key.substring(0, key.indexOf('['));
8+
const index = parseInt(key.substring(key.indexOf('[') + 1, key.indexOf(']')), 10);
9+
const arr = (current as any)?.[arrayKey];
10+
if (!Array.isArray(arr) || index < 0 || index >= arr.length) return undefined;
11+
return arr[index];
12+
}
13+
return (current as any)?.[key];
14+
}, obj);
15+
};
16+
17+
// Helper: last segment after dot, or entire string if no dot
18+
export const getLastPathSegment = (path: string): string => {
19+
const idx = path.lastIndexOf('.');
20+
return idx === -1 ? path : path.slice(idx + 1);
21+
};
22+
23+
// Bulk update for sibling checkbox rows inside an embedded page array
24+
export const updateAllSiblingCheckboxes = (config: {
25+
selectAllProperty: string;
26+
checked: boolean;
27+
pConn: any;
28+
storeData: any;
29+
}) => {
30+
const { selectAllProperty, checked, pConn, storeData } = config;
31+
if (!selectAllProperty) return;
32+
33+
// Trim the selectAllProperty and remove any leading dots - Also the value should contain a single dot - e.g. Policies.IsSelected
34+
const trimmedSelectAllProperty = selectAllProperty.trim().replace(/^[.]+/, '');
35+
if (!/^[^.]+\.[^.]+$/.test(trimmedSelectAllProperty)) return;
36+
37+
const pageRef = `${pConn.options.pageReference}.${trimmedSelectAllProperty}`;
38+
const embeddedPageName = pageRef.substring(0, pageRef.lastIndexOf('.'));
39+
const embeddedArray = getNestedValue(storeData, embeddedPageName);
40+
if (!Array.isArray(embeddedArray) || embeddedArray.length === 0) return;
41+
const contextName = pConn.getContextName();
42+
const target = pConn.getTarget();
43+
const fieldName = `.${getLastPathSegment(trimmedSelectAllProperty)}`;
44+
45+
for (let i = 0; i < embeddedArray.length; i += 1) {
46+
const messageConfig = {
47+
meta: { config: { context: contextName } },
48+
options: {
49+
context: contextName,
50+
pageReference: `${embeddedPageName}[${i}]`,
51+
target,
52+
},
53+
};
54+
const c11nEnv = (window as any).PCore.createPConnect(messageConfig);
55+
c11nEnv?.getPConnect()?.getActionsApi()?.updateFieldValue(fieldName, checked);
56+
}
57+
};
58+
59+
// Update all boolean fields on the current page, excluding the triggering property
60+
export const updateBooleanFieldsOnPage = (config: {
61+
pageRef: string;
62+
checked: boolean;
63+
actions: any;
64+
storeData: any;
65+
excludeProp: string;
66+
}) => {
67+
const { pageRef, checked, actions, storeData, excludeProp } = config;
68+
if (!pageRef) return;
69+
const pageData = getNestedValue(storeData, pageRef);
70+
if (!pageData || typeof pageData !== 'object') return;
71+
Object.entries(pageData).forEach(([key, val]) => {
72+
if (typeof val === 'boolean') {
73+
const otherPropName = `.${key}`;
74+
if (otherPropName !== excludeProp) {
75+
actions.updateFieldValue(otherPropName, checked);
76+
}
77+
}
78+
});
79+
};

0 commit comments

Comments
 (0)