Reusable Salesforce LWC that renders a review/summary view for intake forms in OmniStudio OmniScripts and Record Pages. Label-driven display, configurable section order, Budget and Document Review sections, and WCAG 2.1 AA–oriented markup.
- Components
- How to Use
- OmniScript Set Values
- Deployment
- Label JSON
- Conditional visibility (_visibleWhen)
- More Documentation
| Component | Purpose |
|---|---|
| intakeFormReviewSummary | Main review/summary; uses labelData + form data. |
| budgetDisplayReadOnly | Read-only budget; uses IntakeFormReviewSummaryController.getBudgetDetail. |
| unifiedDocumentDisplay | Read-only documents (LPI + GRANTS); uses IntakeFormReviewSummaryController.getDocuments (or legacy getDocumentDetail). |
| IntakeFormReviewSummaryController | Apex; getBudgetDetail, getDocumentDetail, getDocuments (OmniStudio interface). |
- Deploy the project:
sf project deploy start -x manifest/package.xml -o <org-alias>. - Deploy Custom Metadata Type
Form_Review_Config__mdt(or create in Setup with Label JSON, Form Type, Is Active, Description). - Create a Custom Metadata record: Form Review Config → New → set Label, Name, Form Type, Is Active, and paste label JSON into Label_JSON__c.
- Create DataRaptor Extract
DRExtractLabelJSON: extractForm_Review_Config__mdt.Label_JSON__c→ outputlabelData, filter byDeveloperName=:configDeveloperNameandIs_Active__c= true. - Create Long Text Area fields on your object (one per step) to store each step’s form JSON.
- Create DataRaptor Extract for form data: extract those Long Text fields, filter by record Id, map output paths to the same keys as in labelData (e.g.
StepName_Step). - Create OmniScript: SetValues (e.g.
configDeveloperName,recordId) → DRExtractLabelJSON (inputconfigDeveloperName) → DRExtract form data (inputrecordId) → Step with Custom LWCintakeFormReviewSummary. - Activate OmniScript and Preview; set Record Id on Budget/Document components when used standalone or in review summary.
In the OmniScript Set Values step, use elementValueMap (or equivalent) so the LWC and DataRaptors get the right config, record id, and org flavor. The review summary reads Org_Type to show the correct synthetic sections (Budget vs Document) and to drive c-unified-document-display (LPI vs GRANTS).
| Key | Role |
|---|---|
isPortalUser |
Expression such as =%userProfile% != "System Administrator" so you can branch portal vs internal behavior. |
configDeveloperName |
Form_Review_Config__mdt DeveloperName for label JSON (e.g. Permit_BuildingSingleDetached). |
Org_Type |
LPI or GRANTS. Must match the program; affects document/budget wiring and which SECTION_CONFIG steps apply. |
recordID |
Record context for extracts and child LWCs. Typical pattern: portal users use parid when Org_Type is LPI and proposal when GRANTS; internal users use ContextId. Adjust merge fields to match your OmniScript. |
Example elementValueMap (expressions use your OmniScript merge-field / formula syntax):
{
"isPortalUser": "=%userProfile% != \"System Administrator\"",
"configDeveloperName": "Permit_BuildingSingleDetached",
"recordID": "=IF(%isPortalUser%, IF(%Org_Type% == \"LPI\", %parid%, %proposal%), %ContextId%)",
"Org_Type": "LPI"
}For a GRANTS flow, set Org_Type to GRANTS (and ensure recordID / proposal merge fields align with your script).
- Deploy all:
sf project deploy start -x manifest/package.xml -o <org-alias>. - Deploy LWCs:
sf project deploy start -p force-app/main/default/lwc/<componentName> -o <org-alias>(e.g. intakeFormReviewSummary, budgetDisplayReadOnly, unifiedDocumentDisplay). - Deploy Apex:
sf project deploy start -p force-app/main/default/classes/IntakeFormReviewSummaryController.cls -o <org-alias>. - Deploy Custom Metadata:
sf project deploy start -p force-app/main/default/objects/Form_Review_Config__mdt -o <org-alias>andsf project deploy start -p force-app/main/default/customMetadata -o <org-alias>. - Retrieve:
sf project retrieve start -x manifest/package.xml -o <org-alias>.
Manifest: ApexClass (IntakeFormReviewSummaryController), CustomObject (Form_Review_Config__mdt), CustomMetadata (MAEOED_Proposal_Config, NB_Teacher_Certification_Config), LightningComponentBundle (budgetDisplayReadOnly, unifiedDocumentDisplay, intakeFormReviewSummary), OmniScript (POC_reviewsummary_English_2).
- Label JSON must mirror your form data structure: same section/block/field keys (case-sensitive). Use
_dataKeyon a section when the form data key differs (e.g. label keyProjectedOutcomesStep→ form keyProgramOutcomesStep). - Use
_sectionTitleand_orderon sections;_blockTitleand_orderon blocks; optional_fieldOrder(array of field keys) to control block field order. - Field value = label string or
{ "label": "...", "type": "phone"|"email"|"currency"|"date"|"boolean"|"number"|"multiselect" }. Semicolon-separated values ortype: "multiselect"render as pills. - Address blocks (key/title containing "address") show only the full-address value; use
_addressColspan(1–12) on the block to set width. - Budget/Document sections are driven by SECTION_CONFIG in the LWC (
isVisible,order); setisVisible: trueto show them. - To generate label JSON from data JSON, use label-json-generation-prompt.md.
Any section, block, array wrapper, array item template, or field label object can carry _visibleWhen (or visibleWhen). If it evaluates to false, the item is not rendered. In edit mode, a hidden field’s value is cleared from the draft (so Save will not persist it).
{
"label": "Business Number",
"type": "text",
"_visibleWhen": { "path": "Applicant.Type", "op": "eq", "value": "Corporation" }
}{
"label": "Spouse Name",
"_visibleWhen": { "field": "MaritalStatus", "equals": "Married" }
}{
"_visibleWhen": {
"all": [
{ "path": "Applicant.Country", "op": "eq", "value": "Canada" },
{ "any": [
{ "path": "Applicant.Province", "op": "in", "value": ["AB", "BC"] },
{ "path": "Applicant.Type", "op": "eq", "value": "Partnership" }
] }
]
}
}{ "_visibleWhen": { "path": "Applicant.MailingAddress", "op": "isNotEmpty" } }| Operator | Meaning |
|---|---|
eq / neq |
equals / not equals (loose, boolean-aware) |
equals / notEquals |
shorthand keys (same idea as eq / neq) |
contains / startsWith |
case-insensitive substring match |
isEmpty / isNotEmpty |
null, empty string, or empty array |
gt / lt / gte / lte |
numeric comparison |
in / notIn |
value membership in an array |
all / any |
nestable group combinators (AND / OR) |
| Level | Key goes on |
|---|---|
| Section | The section’s top-level label object (same object that can carry _sectionTitle) |
| Block | The block’s label object (same one that holds _blockTitle) |
| Array wrapper | The array’s label object (hides the entire block) |
| Array item template | Inside the item template labels (also hides the entire block) |
| Field | The field’s label object |
path (or field) is a dotted path resolved against the merged root:
- Draft takes precedence for any section currently in edit mode.
- Then committed
_formData, thenomniJsonData.
You can reference values such as Applicant.Address.Country, top-level Org_Type, userProfile, etc.
- In edit mode, lightning-input change events trigger a re-process so conditions re-evaluate without a Save round-trip.
- For text inputs this often fires on blur; for checkboxes / dates / pickers, on commit. Focus is preserved because the DOM tree stays stable.
- When a controller changes and a controlled field becomes hidden, its value is cleared from the draft on that same render pass. Re-showing it later starts it blank.
{
"Applicant": {
"_sectionTitle": "Applicant Information",
"_order": 1,
"Type": { "label": "Applicant Type", "type": "text" },
"IncorporationNumber": {
"label": "Incorporation Number",
"type": "text",
"_visibleWhen": { "field": "Applicant.Type", "equals": "Corporation" }
},
"DirectorDetails": {
"_blockTitle": "Director Details",
"_visibleWhen": { "path": "Applicant.Type", "op": "in", "value": ["Corporation", "Partnership"] },
"DirectorName": { "label": "Director Name", "type": "text" },
"DirectorEmail": { "label": "Director Email", "type": "email" }
}
},
"SoleProprietorDetails": {
"_sectionTitle": "Sole Proprietor",
"_visibleWhen": { "path": "Applicant.Type", "op": "eq", "value": "Sole Proprietor" },
"OwnerName": { "label": "Owner Name", "type": "text" }
}
}Longer walkthroughs: README-FULL.md (same content, plus context with other label topics).
- Full documentation (step-by-step setup, label format, conditional visibility, API, troubleshooting): README-FULL.md.
- Conditional visibility (
_visibleWhen): README · Full reference - Section order: Form sections use
_orderin labelData; Budget/Document order is inSECTION_CONFIGinintakeFormReviewSummary.js(e.g. 4.5, 4.6); all sections are sorted by numericorder. - Generate label JSON from data: Use label-json-generation-prompt.md with Gemini/ChatGPT.
- API version: 65.0.
MIT License — see LICENSE.