├── .forceignore ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── continuousIntegration.yml ├── .gitignore ├── .prettierrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── config └── project-scratch-def.json ├── docs ├── calculated_fields.md ├── custom_components.md ├── editing_and_creating_records.md ├── images │ ├── CSV1.png │ ├── CSV2.png │ ├── CSV3.png │ ├── CalculatedFieldArchitecture.png │ ├── ChangeEditButtonOnPageLayout.gif │ ├── ConditionalWarning.gif │ ├── ConditionalWarningMetadata.png │ ├── ContactCalculatedFieldsApexDemo.png │ ├── ContactCalculatedFieldsFlowDemo.png │ ├── DigitalExperienceBuilder.png │ ├── EditAction.png │ ├── EditOverride.png │ ├── EditOverrideSelection.png │ ├── EventAnimation.gif │ ├── FieldSection.gif │ ├── FlowRendererAssigmnent.png │ ├── FlowRendererOutline.png │ ├── HelpTextOverrides.png │ ├── LabelOverrides.png │ ├── ModifyLightningRecordPage.gif │ ├── NewAction.png │ ├── PageLayoutAndOverride.gif │ ├── RecordCreate.gif │ ├── RelatedParentRecordConfiguration.png │ ├── RelatedRecordParentEdits.gif │ ├── RelatedRecordVariations.png │ ├── SaveAndCancelButtons.png │ └── SliderDemo.gif ├── parent_and_child_records.md └── setup.md ├── evolve-forms └── main │ └── default │ ├── aura │ └── dynamicFormsOverrideEdit │ │ ├── dynamicFormsOverrideEdit.cmp │ │ ├── dynamicFormsOverrideEdit.cmp-meta.xml │ │ ├── dynamicFormsOverrideEditController.js │ │ └── dynamicFormsOverrideEditHelper.js │ ├── classes │ ├── CalculatedField.cls │ ├── CalculatedField.cls-meta.xml │ ├── CalculatedFieldController.cls │ ├── CalculatedFieldController.cls-meta.xml │ ├── CalculatedFieldControllerTest.cls │ ├── CalculatedFieldControllerTest.cls-meta.xml │ ├── DynamicFormsController.cls │ ├── DynamicFormsController.cls-meta.xml │ ├── DynamicFormsControllerTest.cls │ ├── DynamicFormsControllerTest.cls-meta.xml │ ├── LightningFormattedAddress.cls │ ├── LightningFormattedAddress.cls-meta.xml │ ├── LightningFormattedDateTime.cls │ ├── LightningFormattedDateTime.cls-meta.xml │ ├── LightningFormattedEmail.cls │ ├── LightningFormattedEmail.cls-meta.xml │ ├── LightningFormattedLocation.cls │ ├── LightningFormattedLocation.cls-meta.xml │ ├── LightningFormattedName.cls │ ├── LightningFormattedName.cls-meta.xml │ ├── LightningFormattedNumber.cls │ ├── LightningFormattedNumber.cls-meta.xml │ ├── LightningFormattedPhoneNumber.cls │ ├── LightningFormattedPhoneNumber.cls-meta.xml │ ├── LightningFormattedText.cls │ ├── LightningFormattedText.cls-meta.xml │ ├── LightningFormattedTime.cls │ ├── LightningFormattedTime.cls-meta.xml │ ├── LightningFormattedUrl.cls │ └── LightningFormattedUrl.cls-meta.xml │ ├── layouts │ └── Conditional_Warning__mdt-Conditional Warning Layout.layout-meta.xml │ ├── lwc │ ├── calculatedFieldSection │ │ ├── calculatedFieldSection.html │ │ ├── calculatedFieldSection.js │ │ └── calculatedFieldSection.js-meta.xml │ ├── dynamicFormsCollapsibleSection │ │ ├── dynamicFormsCollapsibleSection.html │ │ ├── dynamicFormsCollapsibleSection.js │ │ └── dynamicFormsCollapsibleSection.js-meta.xml │ ├── dynamicFormsCompactPageLayout │ │ ├── dynamicFormsCompactPageLayout.css │ │ ├── dynamicFormsCompactPageLayout.html │ │ ├── dynamicFormsCompactPageLayout.js │ │ └── dynamicFormsCompactPageLayout.js-meta.xml │ ├── dynamicFormsCreate │ │ ├── dynamicFormsCreate.html │ │ ├── dynamicFormsCreate.js │ │ └── dynamicFormsCreate.js-meta.xml │ ├── dynamicFormsElement │ │ ├── dynamicFormsElement.html │ │ ├── dynamicFormsElement.js │ │ └── dynamicFormsElement.js-meta.xml │ ├── dynamicFormsFieldSection │ │ ├── dynamicFormsFieldSection.css │ │ ├── dynamicFormsFieldSection.html │ │ ├── dynamicFormsFieldSection.js │ │ └── dynamicFormsFieldSection.js-meta.xml │ ├── dynamicFormsFieldSet │ │ ├── dynamicFormsFieldSet.html │ │ ├── dynamicFormsFieldSet.js │ │ └── dynamicFormsFieldSet.js-meta.xml │ ├── dynamicFormsHeadlessEdit │ │ ├── dynamicFormsHeadlessEdit.html │ │ ├── dynamicFormsHeadlessEdit.js │ │ └── dynamicFormsHeadlessEdit.js-meta.xml │ ├── dynamicFormsPageLayout │ │ ├── dynamicFormsPageLayout.html │ │ ├── dynamicFormsPageLayout.js │ │ └── dynamicFormsPageLayout.js-meta.xml │ ├── dynamicFormsPredefinedValues │ │ ├── dynamicFormsPredefinedValues.html │ │ ├── dynamicFormsPredefinedValues.js │ │ └── dynamicFormsPredefinedValues.js-meta.xml │ ├── dynamicFormsRelatedRecord │ │ ├── dynamicFormsRelatedRecord.html │ │ ├── dynamicFormsRelatedRecord.js │ │ └── dynamicFormsRelatedRecord.js-meta.xml │ ├── dynamicFormsSaveCancel │ │ ├── dynamicFormsSaveCancel.css │ │ ├── dynamicFormsSaveCancel.html │ │ ├── dynamicFormsSaveCancel.js │ │ └── dynamicFormsSaveCancel.js-meta.xml │ └── dynamicFormsUtils │ │ ├── displayToast.js │ │ ├── dynamicFormsUtils.js │ │ ├── dynamicFormsUtils.js-meta.xml │ │ ├── lightningConsoleApi.js │ │ └── reduceErrors.js │ ├── messageChannels │ └── DynamicForms.messageChannel-meta.xml │ ├── objects │ └── Conditional_Warning__mdt │ │ ├── Conditional_Warning__mdt.object-meta.xml │ │ ├── fields │ │ ├── Flow_API_Name__c.field-meta.xml │ │ └── SObject_API_Name__c.field-meta.xml │ │ └── listViews │ │ └── All_Conditional_Warnings.listView-meta.xml │ ├── permissionsets │ └── Evolve_Forms.permissionset-meta.xml │ └── staticresources │ ├── DynamicFormsCSS.css │ └── DynamicFormsCSS.resource-meta.xml ├── package-lock.json ├── package.json └── sfdx-project.json /.forceignore: -------------------------------------------------------------------------------- 1 | # List files or directories below to ignore them when running force:source:push, force:source:pull, and force:source:status 2 | # More information: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_exclude_source.htm 3 | # 4 | 5 | package.xml 6 | 7 | # LWC configuration files 8 | **/jsconfig.json 9 | **/.eslintrc.json 10 | 11 | # LWC Jest 12 | **/__tests__/** -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Expected Behavior 2 | 3 | ## Actual Behavior 4 | 5 | ## Steps to Reproduce the Problem 6 | 7 | 1. 8 | 1. 9 | 1. 10 | 11 | ## Specifications 12 | 13 | - Version: 14 | - Platform: 15 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | > It's a good idea to open an issue first for discussion. 4 | 5 | - [ ] Tests pass 6 | - [ ] Appropriate changes to README are included in PR 7 | -------------------------------------------------------------------------------- /.github/workflows/continuousIntegration.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | pull_request: 5 | types: [opened, reopened, synchronize, edited] 6 | 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-20.04-4core 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | with: 16 | ref: ${{ github.ref }} 17 | fetch-depth: 0 18 | 19 | - uses: actions/setup-node@v3 20 | with: 21 | node-version: ">=18" 22 | check-latest: true 23 | 24 | - name: Install Salesforce CLI + Scanner 25 | run: | 26 | npm install @salesforce/cli -g 27 | sf --version 28 | sf plugins --core 29 | sf plugins install @salesforce/sfdx-scanner 30 | 31 | - name: Run SFDX Scanner - Report findings as comments 32 | uses: mitchspano/sfdx-scan-pull-request@v0.1.15 33 | with: 34 | severity-threshold: 4 35 | report-mode: comments 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | - name: Authenticate into DevHub 40 | run: | 41 | echo "${SALESFORCE_JWT_SECRET_KEY}" > server.key 42 | sf org login jwt --client-id ${{ secrets.SALESFORCE_CONSUMER_KEY }} --jwt-key-file server.key --username ${{ secrets.SALESFORCE_DEVHUB_USERNAME}} --set-default-dev-hub --alias devhub 43 | env: 44 | SALESFORCE_JWT_SECRET_KEY: ${{ secrets.SALESFORCE_JWT_SECRET_KEY }} 45 | 46 | - name: Validate Contents in a Scratch Org 47 | run: | 48 | sf org create scratch --target-dev-hub devhub --set-default --definition-file config/project-scratch-def.json --alias scratchOrg --duration-days 1 49 | sf project deploy start --target-org scratchOrg 50 | sf apex run test --code-coverage --result-format human --synchronous 51 | sf org delete scratch --no-prompt --target-org scratchOrg 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # GitIgnore for Salesforce Projects 2 | # Project Settings and MetaData 3 | .project 4 | .settings/ 5 | .metadata 6 | build.properties 7 | *jsconfig.json 8 | 9 | # VS Code 10 | .eslintignore 11 | 12 | # .prettierrc 13 | .vscode 14 | .sfdx 15 | .sf 16 | #manifest/ 17 | 18 | # Apex Log as optional 19 | apex-scripts/log 20 | 21 | # Eclipse specific 22 | salesforce.schema 23 | Referenced Packages 24 | bin/ 25 | tmp/ 26 | *.tmp 27 | *.bak 28 | local.properties 29 | .settings 30 | .loadpath 31 | .classpath 32 | *.cache 33 | 34 | # Illuminated Cloud (IntelliJ IDEA) 35 | IlluminatedCloud 36 | out 37 | .idea 38 | *.iml 39 | 40 | # Mavensmate 41 | *.sublime-project 42 | *.sublime-settings 43 | *.sublime-workspace 44 | mm.log 45 | 46 | # Haoide SublimeText 47 | .config 48 | .deploy 49 | .history 50 | 51 | # OSX-specific exclusions 52 | .[dD][sS]_[sS]tore 53 | 54 | # The Welkin Suite specific 55 | **/.localHistory 56 | 57 | *.sfuo 58 | 59 | TestCache.xml 60 | 61 | TestsResultsCache.xml 62 | 63 | # npm 64 | node_modules -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "useTabs": false, 4 | "overrides": [ 5 | { 6 | "files": "**/lwc/**/*.html", 7 | "options": { "parser": "lwc" } 8 | }, 9 | { 10 | "files": "*.{cmp,page,component,email,auradoc,design}", 11 | "options": { "parser": "html", "htmlWhitespaceSensitivity": "ignore" } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | We'd love to accept your patches and contributions to this project. 4 | 5 | ## Before you begin 6 | 7 | ### Sign our Contributor License Agreement 8 | 9 | Contributions to this project must be accompanied by a 10 | [Contributor License Agreement](https://cla.developers.google.com/about) (CLA). 11 | You (or your employer) retain the copyright to your contribution; this simply 12 | gives us permission to use and redistribute your contributions as part of the 13 | project. 14 | 15 | If you or your current employer have already signed the Google CLA (even if it 16 | was for a different project), you probably don't need to do it again. 17 | 18 | Visit to see your current agreements or to 19 | sign a new one. 20 | 21 | ### Review our community guidelines 22 | 23 | This project follows 24 | [Google's Open Source Community Guidelines](https://opensource.google/conduct/). 25 | 26 | ## Contribution process 27 | 28 | ### Code reviews 29 | 30 | All submissions, including submissions by project members, require review. We 31 | use GitHub pull requests for this purpose. Consult 32 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 33 | information on using pull requests. 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Evolve Forms 2 | 3 | Evolve Forms is a powerful solution designed to address the needs of complex Salesforce applications in organizations with multiple business units sharing a single org. With Evolve Forms, you can tailor and enhance the Salesforce user experience to meet the specific requirements of your business units. 4 | 5 | ## Installation 6 | 7 | 8 | Deploy to Salesforce 9 | 10 | 11 | #### [Unlocked Package Installation (Production)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04tDn000000nGCNIA2) 12 | 13 | #### [Unlocked Package Installation (Sandbox)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04tDn000000nGCNIA2) 14 | 15 | ## Key Features 16 | 17 | Evolve Forms offers a range of features that extend the capabilities of your Salesforce instance, allowing you to better support your organization's diverse needs. Some of the key features include: 18 | 19 | ### Dynamic Page Layouts and Field Sections 20 | 21 | Evolve Forms enables you to dynamically render multiple page layouts or field sections based on virtually any set of criteria. This flexibility eliminates the need to create multiple record types, making it easier to tailor your application to the unique needs of your different business units. 22 | 23 | ### Field Customization 24 | 25 | With Evolve Forms, you can optionally override the label and/or help text of fields to provide business unit-specific context. This allows you to offer your users a better understanding of how a field should be used in the context of their team's processes. 26 | 27 | ### Calculated Values 28 | 29 | You can render computed values on a record with complex criteria, going beyond the capabilities of standard formula fields. Evolve Forms provides a solution that doesn't consume a field in the sObject's schema, giving you greater flexibility and control. 30 | 31 | ### User Confirmation 32 | 33 | Evolve Forms allows you to intercept the save operation and prompt users to confirm the accuracy of the information they entered. These conditionally rendered dynamic warning messages can improve data integrity and accuracy. 34 | 35 | ### Custom Component Integration 36 | 37 | With Evolve Forms, you can choreograph custom components seamlessly with the same set of edit, save, and cancel buttons as the rest of the page. This creates a cohesive and user-friendly experience, enhancing interactions for your users. 38 | 39 | ### Getting Started 40 | 41 | For detailed information on how to get started with Evolve Forms and take full advantage of its features, please refer to the project documentation. 42 | 43 | ## Documentation 44 | 45 | ### [Editing and Creating Records](docs/editing_and_creating_records.md) 46 | 47 | ### [Calculated Fields](docs/calculated_fields.md) 48 | 49 | ### [Parent and Child Records](docs/parent_and_child_records.md) 50 | 51 | ### [Custom Components](docs/custom_components.md) 52 | 53 | ### [Setup](docs/setup.md) 54 | 55 | ## License 56 | 57 | See [license](LICENSE) (Apache 2.0). 58 | 59 | ## Authors 60 | 61 | - [Mitch Spano](https://github.com/mitchspano) 62 | - [Gabe Terrell](https://github.com/gabe-terrell) 63 | - [Shivam Goyal](https://github.com/goelshivam555) 64 | - [Bhavesh Malviya](https://github.com/bhavesh25) 65 | -------------------------------------------------------------------------------- /config/project-scratch-def.json: -------------------------------------------------------------------------------- 1 | { 2 | "orgName": "Google", 3 | "edition": "Developer", 4 | "features": [] 5 | } 6 | -------------------------------------------------------------------------------- /docs/calculated_fields.md: -------------------------------------------------------------------------------- 1 | # Calculated Fields 2 | 3 | Evolve Forms allows administrators and developers to dynamically calculate any value to be rendered on the screen as if it were an actual field using Apex or Flow. 4 | 5 | This is _far more_ robust than normal formula fields. Calculated Fields enables rendering data calculated from child records or the result of a callout, and it does not consume any columns in the schema. 6 | 7 | If you can compute it in Apex or Flow, then you can render it on the screen as if it were a field. 8 | 9 | ![Calculated Field Architecture](images/CalculatedFieldArchitecture.png) 10 | 11 | ## Renderers 12 | 13 | The fields will be calculated using a **Renderer**. A renderer will accept the current record's Id as input and return a collection of `CalculatedField` objects. 14 | 15 | The `CalculatedField` class has public `@AuraEnabled` variables whose data types correspond to the public properties of the `lightning-formatted-XYZ` Lightning Web Component. 16 | 17 | 18 | | Data Type | Lightning Web Component | 19 | | --- | --- | 20 | | [LightningFormattedAddress](../evolve-forms/main/default/classes/LightningFormattedAddress.cls) | [lightning-formatted-address](https://developer.salesforce.com/docs/component-library/bundle/lightning-formatted-address/example) | 21 | | [LightningFormattedDateTime](../evolve-forms/main/default/classes/LightningFormattedDateTime.cls) | [lightning-formatted-date-time](https://developer.salesforce.com/docs/component-library/bundle/lightning-formatted-date-time/example) | 22 | | [LightningFormattedEmail](../evolve-forms/main/default/classes/LightningFormattedEmail.cls) | [lightning-formatted-email](https://developer.salesforce.com/docs/component-library/bundle/lightning-formatted-email/example) | 23 | | [LightningFormattedLocation](../evolve-forms/main/default/classes/LightningFormattedLocation.cls) | [lightning-formatted-location](https://developer.salesforce.com/docs/component-library/bundle/lightning-formatted-location/example) | 24 | | [LightningFormattedName](../evolve-forms/main/default/classes/LightningFormattedName.cls) | [lightning-formatted-name](https://developer.salesforce.com/docs/component-library/bundle/lightning-formatted-name/example) | 25 | | [LightningFormattedNumber](../evolve-forms/main/default/classes/LightningFormattedNumber.cls) | [lightning-formatted-number](https://developer.salesforce.com/docs/component-library/bundle/lightning-formatted-number/example) | 26 | | [LightningFormattedPhoneNumber](../evolve-forms/main/default/classes/LightningFormattedPhoneNumber.cls) | [lightning-formatted-phone](https://developer.salesforce.com/docs/component-library/bundle/lightning-formatted-phone/example) | 27 | | [LightningFormattedText](../evolve-forms/main/default/classes/LightningFormattedText.cls) | [lightning-formatted-text](https://developer.salesforce.com/docs/component-library/bundle/lightning-formatted-text/example)
[lightning-formatted-rich-text](https://developer.salesforce.com/docs/component-library/bundle/lightning-formatted-rich-text/example) | 28 | | [LightningFormattedTime](../evolve-forms/main/default/classes/LightningFormattedTime.cls) | [lightning-formatted-time](https://developer.salesforce.com/docs/component-library/bundle/lightning-formatted-time/example) | 29 | | [LightningFormattedUrl](../evolve-forms/main/default/classes/LightningFormattedUrl.cls) | [lightning-formatted-url](https://developer.salesforce.com/docs/component-library/bundle/lightning-formatted-url/example) | 30 | | Boolean| [lightning-input](https://developer.salesforce.com/docs/component-library/bundle/lightning-input/example) (rendered as `type="checkbox"`) | 31 | 32 | The values in the populated data type will be rendered on the screen as if it were a field on the record, using all of the formatting capabilities of the corresponding Lightning Web Component. 33 | 34 | ## Flow Renderers 35 | 36 | To define a flow-based renderer, create an autolaunched flow with the following variable specifications: 37 | 38 | 39 | | Variable Name | Variable Type | Available for Input | Available for Output | Description | 40 | | --- | --- | --- | --- | --- | 41 | | `recordId` | text | yes | no | The Id of the record | 42 | | `calculatedFields` | Collection of [`CalculatedField`](/evolve-forms/main/default/classes/CalculatedField.cls) | no | yes | The calculated fields to be rendered on the screen | 43 | 44 | ![Flow Renderer Outline](images/FlowRendererOutline.png) 45 | 46 | ![Flow Renderer Assignment](images/FlowRendererAssigmnent.png) 47 | 48 | Then, add the `Calculated Fields Section` component to your record page and provide the flow's API name. 49 | 50 | ![Contact Calculated Fields Demo](images/ContactCalculatedFieldsFlowDemo.png) 51 | 52 | ## Apex Renderers 53 | 54 | Apex renderers work very similarly to flow renderers. Create a class which implements the `CalculatedFieldController.Renderer` interface: 55 | 56 | ```java 57 | public class ContactCalculatedFields implements CalculatedFieldController.Renderer { 58 | public List getFields(Id recordId) { 59 | List result = new List(); 60 | 61 | List contacts = [ 62 | SELECT Account.Parent.Name 63 | FROM Contact 64 | WHERE Id = :recordId 65 | ]; 66 | 67 | String parentAccountName = contacts?.get(0)?.Account?.Parent?.Name; 68 | if (parentAccountName == null) { 69 | return result; 70 | } 71 | 72 | CalculatedField field = new CalculatedField(); 73 | field.label = 'Account\'s Parent Account Name'; 74 | field.text = new LightningFormattedText(); 75 | field.text.value = parentAccountName; 76 | 77 | result.add(field); 78 | return result; 79 | } 80 | } 81 | ``` 82 | 83 | Then, add the `Calculated Fields Section` component to your record page and provide the Apex class name. 84 | 85 | ![Contact Calculated Fields Demo](images/ContactCalculatedFieldsApexDemo.png) 86 | -------------------------------------------------------------------------------- /docs/custom_components.md: -------------------------------------------------------------------------------- 1 | # Custom Components 2 | 3 | Evolve Forms enables developers to build custom components which can be used to 4 | enter data in a non-standard way while choreographing the saving of the record 5 | with the same set of edit/save/cancel buttons as the rest of the page. 6 | 7 | In the below example, we created a custom slider component and have the value of 8 | the slider map to the `Account.NumberOfEmployees` field for the record. 9 | 10 | ![Slider Demo](images/SliderDemo.gif) 11 | 12 | #### sliderDemo.html 13 | 14 | 15 | ```html 16 | 37 | ``` 38 | 39 | #### sliderDemo.js 40 | 41 | ```js 42 | import { api, wire } from "lwc"; 43 | import { getRecord } from "lightning/uiRecordApi"; 44 | import DynamicFormsElement from "c/dynamicFormsElement"; 45 | 46 | export default class SliderDemo extends DynamicFormsElement { 47 | @api recordId; 48 | @api objectApiName; 49 | @api label; 50 | @api fieldApiName; 51 | editMode = false; 52 | 53 | @wire(getRecord, { 54 | recordId: "$recordId", 55 | fields: [""], 56 | optionalFields: "$fieldName" 57 | }) 58 | thisRecord; 59 | 60 | get fieldName() { 61 | return this.objectApiName + "." + this.fieldApiName; 62 | } 63 | 64 | get currentFieldValue() { 65 | return this?.thisRecord?.data?.fields[this.fieldApiName]?.value; 66 | } 67 | 68 | handleSliderChange(event) { 69 | this.broadcast(this.EVENT_TYPE.UPDATE, { 70 | newValue: event.detail.value, 71 | fieldApiName: this.fieldApiName 72 | }); 73 | } 74 | 75 | handleMessage(message) { 76 | if (message.eventType === this.EVENT_TYPE.EDIT) { 77 | this.editMode = true; 78 | } else if (message.eventType === this.EVENT_TYPE.CANCEL) { 79 | this.editMode = false; 80 | } else if (message.eventType === this.EVENT_TYPE.SAVE_START) { 81 | this.showSpinner = true; 82 | } else if (message.eventType === this.EVENT_TYPE.SAVE_END) { 83 | this.showSpinner = false; 84 | } 85 | } 86 | 87 | connectedCallback() { 88 | this.subscribeToMessageChannel(); 89 | this.broadcast(this.EVENT_TYPE.CHECK_EDIT); 90 | } 91 | } 92 | ``` 93 | 94 | #### sliderDemo.js-meta.xml 95 | 96 | ```xml 97 | 98 | 99 | 59.0 100 | true 101 | Slider Demonstration 102 | This enables the value of a slider to be saved to a number field. 103 | 104 | lightning__RecordPage 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | ``` 114 | 115 | ## Event Details 116 | 117 | Here are the supported 118 | [event types](/evolve-forms/main/default/lwc/dynamicFormsElement/dynamicFormsElement.js) 119 | which Evolve Forms uses to facilitate all interactions. Use these when building 120 | custom components that interact with the rest of the Evolve Forms Page Layout and 121 | Field Section components on the page. 122 | 123 | 124 | | Event | Description | 125 | | --- | --- | 126 | | CANCEL | Broadcast that the form should cancel the current form modifications | 127 | | CHECK_EDIT | Check if the form is currently in edit mode | 128 | | CHECK_OVERRIDES | Check if any form values should be overridden by a pre-populated value | 129 | | EDIT | Broadcast that the form should switch to edit mode | 130 | | FOCUS_IN | Broadcast that an input element got focus | 131 | | FOCUS_OUT | Broadcast that an input element focused out | 132 | | NO_LONGER_REQUIRED_FIELD | Broadcast to remove requirement from a field which is no longer required | 133 | | REQUIRED_FIELD | Broadcast that this element contains a required field | 134 | | RESET | Broadcast to reset current form fields | 135 | | SAVE_END | Broadcast that the form has finished saving | (successful or not) | 136 | | SAVE_START | Broadcast that the form has begun to save | 137 | | UPDATE | Broadcast that a field value is updated | 138 | -------------------------------------------------------------------------------- /docs/editing_and_creating_records.md: -------------------------------------------------------------------------------- 1 | # Editing and Creating Records 2 | 3 | Editing and creating records with Evolve Forms uses an implementation of the 4 | [Mediator Pattern](https://en.wikipedia.org/wiki/Mediator_pattern) which works 5 | by utilizing the 6 | [Lightning Message Service](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.use_message_channel) 7 | to propagate events across the 8 | [Lightning Record Page](https://trailhead.salesforce.com/content/learn/modules/lightning_app_builder/lightning_app_builder_recordpage) 9 | to manipulate form state, capture data, and orchestrate user interactions. 10 | 11 | ![Event Animation](images/EventAnimation.gif) 12 | 13 | The cancel/save buttons component act as the event hub and mediate all 14 | communication across the components on the Lightning Record Page. They hold all 15 | pending record updates in-memory and choreograph the save of the record with 16 | input gathered from all of the components on the page. 17 | 18 | ### Page Layouts 19 | 20 | Evolve Forms is _fully backwards compatible_ with Page Layouts, so all of the 21 | existing Page Layouts you have in your org can be easily used with the 22 | framework. Just drag the `Dynamic Forms - Page Layout` component onto the 23 | Lightning Record Page. 24 | 25 | By default, the page layout that is assigned will be rendered. You can also 26 | enter the API name of a different page layout into the `Page Layout Name` field 27 | in page builder, and _override the default page layout assignment_: 28 | 29 | ![Page Layout and Override](images/PageLayoutAndOverride.gif) 30 | 31 | You can even _render multiple page layouts_ on one Lightning Record Page if you 32 | like! 33 | 34 | #### Field Sections 35 | 36 | Evolve Forms provides the ability for "Field Sections" to be added to the 37 | Lightning Record Page which are independent of the page layout. Just drag and 38 | drop the `Dynamic Forms - Field Section` component onto the page layout: 39 | 40 | ![Field Section](images/FieldSection.gif) 41 | 42 | Edits from all field sections and page layouts will be orchestrated together, so 43 | the same set of edit/save/cancel buttons control the entire page. 44 | 45 | ##### Comma Separated Field API Names 46 | 47 | To use the `Dynamic Forms - Field Section` component, enter the "Section Label" 48 | you would like to display and the "Comma Separated Field API Names" which you 49 | would like to render. Use the below mapping to label fields as required, read 50 | only, or to insert additional blank spaces: 51 | 52 | | String Format | Description | 53 | | -------------------- | ----------------------- | 54 | | Prefix with Asterisk | Mark field as required | 55 | | Suffix with Asterisk | Mark field as read-only | 56 | | Sequential Commas | Introduce blank space | 57 | 58 | Here are some examples: 59 | 60 | | Comma Separated Field API Names | Image | 61 | | ------------------------------- | ------------------------------------------------- | 62 | | HomePhone, Email, Birthdate | ![HomePhone, Email,Birthdate](images/CSV1.png) | 63 | | \*HomePhone, Email\*, Birthdate | ![*HomePhone, Email*,Birthdate](images/CSV2.png) | 64 | | HomePhone, Email, , Birthdate | ![HomePhone, Email, , Birthdate](images/CSV3.png) | 65 | | | | 66 | 67 | #### Label and Help Text Overrides 68 | 69 | Evolve Forms provides the ability to easily override the label or help text on a 70 | given field. To accomplish this, enter a JSON object with simple key-value pairs 71 | to define the override for a particular field. 72 | 73 | Here are some examples: 74 | 75 | | Override | Image | 76 | | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | 77 | | **No Overrides** | ![No Overrides](images/CSV1.png) | 78 | | **Field Label Overrides**
{
"HomePhone" \: "Home Telephone Number",
"Email": "Personal Email Address",
"Birthdate": "Date of Birth"
} | ![Field Label Overrides](images/LabelOverrides.png) | 79 | | **Help Text Label Overrides**
{
"HomePhone" \: "Home Telephone Number",
"Email": "Personal Email Address",
"Birthdate": "Date of Birth"
} | ![Field Label Overrides](images/HelpTextOverrides.png) | 80 | 81 | **Note:** If a field doesn't have help text defined, then a label override is 82 | also required to define a help text override. 83 | 84 | #### Conditionally Rendered Warnings 85 | 86 | Most folks working on Salesforce are familiar with 87 | [Validation Rules](https://help.salesforce.com/s/articleView?id=sf.fields_defining_field_validation_rules.htm&type=5) 88 | which can programmatically prevent the saving of a record based on some 89 | evaluated criteria. Evolve Forms enables something unique which is not available 90 | as part of the Salesforce platform - conditionally rendered _warnings_ (not 91 | errors) based on the state of the current record before the save button is 92 | pressed. 93 | 94 | ![Conditional Warning](images/ConditionalWarning.gif) 95 | 96 | To enable these warnings, create a row of Custom Metadata in the 97 | `Conditional_Warning__mdt` custom metadata type: 98 | 99 | ![Conditional Warning Metadata](images/ConditionalWarningMetadata.png) 100 | 101 | | Field | Type | Description | 102 | | --------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 103 | | SObject_API_Name\_\_c | Text(255) | API name of the sObject that this conditional warning will be rendered on | 104 | | Flow_API_Name\_\_c | Text(255) | The API name of a Flow which will be used to evaluate this warning. The flow must have an input variable called `record` of type sObject record and an output variable called `warningMessage` of type string. If `warningMessage` has a value, then that value will be rendered as a warning to the user. | 105 | 106 | #### Digital Experience Support 107 | 108 | Evolve Forms is compatible with Digital 109 | Experience pages. Teams can use Evolve to easily create custom experiences 110 | tailored to their exact needs. 111 | 112 | ![Digital Experience Builder](images/DigitalExperienceBuilder.png) 113 | 114 | > **NOTE:** When using Evolve forms within Digital Experiences, the sObject API name must be passed to the component from page builder or from a containing component. If using the Page Layout component, the layout's API name must also be passed to the component from page builder or from a containing component. 115 | -------------------------------------------------------------------------------- /docs/images/CSV1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/CSV1.png -------------------------------------------------------------------------------- /docs/images/CSV2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/CSV2.png -------------------------------------------------------------------------------- /docs/images/CSV3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/CSV3.png -------------------------------------------------------------------------------- /docs/images/CalculatedFieldArchitecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/CalculatedFieldArchitecture.png -------------------------------------------------------------------------------- /docs/images/ChangeEditButtonOnPageLayout.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/ChangeEditButtonOnPageLayout.gif -------------------------------------------------------------------------------- /docs/images/ConditionalWarning.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/ConditionalWarning.gif -------------------------------------------------------------------------------- /docs/images/ConditionalWarningMetadata.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/ConditionalWarningMetadata.png -------------------------------------------------------------------------------- /docs/images/ContactCalculatedFieldsApexDemo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/ContactCalculatedFieldsApexDemo.png -------------------------------------------------------------------------------- /docs/images/ContactCalculatedFieldsFlowDemo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/ContactCalculatedFieldsFlowDemo.png -------------------------------------------------------------------------------- /docs/images/DigitalExperienceBuilder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/DigitalExperienceBuilder.png -------------------------------------------------------------------------------- /docs/images/EditAction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/EditAction.png -------------------------------------------------------------------------------- /docs/images/EditOverride.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/EditOverride.png -------------------------------------------------------------------------------- /docs/images/EditOverrideSelection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/EditOverrideSelection.png -------------------------------------------------------------------------------- /docs/images/EventAnimation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/EventAnimation.gif -------------------------------------------------------------------------------- /docs/images/FieldSection.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/FieldSection.gif -------------------------------------------------------------------------------- /docs/images/FlowRendererAssigmnent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/FlowRendererAssigmnent.png -------------------------------------------------------------------------------- /docs/images/FlowRendererOutline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/FlowRendererOutline.png -------------------------------------------------------------------------------- /docs/images/HelpTextOverrides.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/HelpTextOverrides.png -------------------------------------------------------------------------------- /docs/images/LabelOverrides.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/LabelOverrides.png -------------------------------------------------------------------------------- /docs/images/ModifyLightningRecordPage.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/ModifyLightningRecordPage.gif -------------------------------------------------------------------------------- /docs/images/NewAction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/NewAction.png -------------------------------------------------------------------------------- /docs/images/PageLayoutAndOverride.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/PageLayoutAndOverride.gif -------------------------------------------------------------------------------- /docs/images/RecordCreate.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/RecordCreate.gif -------------------------------------------------------------------------------- /docs/images/RelatedParentRecordConfiguration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/RelatedParentRecordConfiguration.png -------------------------------------------------------------------------------- /docs/images/RelatedRecordParentEdits.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/RelatedRecordParentEdits.gif -------------------------------------------------------------------------------- /docs/images/RelatedRecordVariations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/RelatedRecordVariations.png -------------------------------------------------------------------------------- /docs/images/SaveAndCancelButtons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/SaveAndCancelButtons.png -------------------------------------------------------------------------------- /docs/images/SliderDemo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/evolve-forms/daec4d9b3d7cbf6470b414220003ab7f6a28d5fc/docs/images/SliderDemo.gif -------------------------------------------------------------------------------- /docs/parent_and_child_records.md: -------------------------------------------------------------------------------- 1 | # Parent and Child Records 2 | 3 | Evolve Forms allows developers and administrators to easily render and edit fields from related records on the same page. 4 | 5 | This record can be defined by either defining a relational field to the current object or passing in a record Id (this use case would most likely be used in conjunction with other LWC extensions utilizing this component). 6 | 7 | Relational fields include both parents and children. Parent can span multiple records (e.g. `Parent.Parent.Id` for a case's grandparent, spanning up to five relations). Children fields can also be included (e.g. `Cases` for child cases). 8 | 9 | For child objects, there are multiple strategies to follow if multiple children are found (use the oldest, use the newest, throw an error, hide the component). This can be helpful for child relations where you expect a one-to-one relation. 10 | 11 | All of the variations of parent and child records 12 | 13 | ![Related Record Parent Configuration](images/RelatedParentRecordConfiguration.png) 14 | 15 | ![Related Record Parent Edits](images/RelatedRecordParentEdits.gif) 16 | -------------------------------------------------------------------------------- /docs/setup.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | There are a few steps necessary to get started using Evolve Forms on a given 4 | sObject within your org. 5 | 6 | ## Editing Records 7 | 8 | ### 1. Replace the Default Edit Button 9 | 10 | Evolve Forms uses a custom `Edit` button on the lightning record page, so we need 11 | to modify the page layout to remove the standard `Edit` button and replace it. 12 | 13 | ##### Create Edit Button for Page Layout 14 | 15 | First, navigate to the setup menu, select the sObject you want to use Evolve on, 16 | and create a new Action for that sObject: 17 | 18 | ![New Action](images/NewAction.png) 19 | 20 | Select the `c:dynamicFormsHeadlessEdit` Lightning Web Component and set the 21 | Label as "Edit": ![Edit Action](images/EditAction.png) 22 | 23 | Replace the edit button on the assigned page layout: 24 | 25 | ![ReplaceEditButton](images/ChangeEditButtonOnPageLayout.gif) 26 | 27 | ##### Override Edit Action 28 | 29 | The page layout is not the only place where an edit can be initiated. In order 30 | to handle the record edits initiated outside of the record detail page (such as 31 | a list view or related list) we must override the default "Edit" action for the 32 | sObject. 33 | 34 | ![Edit Action Setup Menu](images/EditOverride.png) 35 | 36 | Select the `c:dynamicFormsOverrideEdit` Lightning Component. 37 | 38 | ![Edit Override Selection](images/EditOverrideSelection.png) 39 | 40 | ### 2. Modify Lightning Record Page 41 | 42 | We are almost complete with the setup. Now, we need to add the `Dynamic Forms - 43 | Cancel and Save Buttons` component to the Lightning Record Page, and replace the 44 | standard `Record Detail` component with the `Dynamic Forms - Page Layout` 45 | component. 46 | 47 | ![Page Layout and Override](images/PageLayoutAndOverride.gif) 48 | 49 | ![Save and Cancel Buttons](images/SaveAndCancelButtons.png) 50 | 51 | Voilà! you are now ready to use Evolve Forms on the selected sObject. 52 | 53 | ## Creating Records 54 | 55 | Creating records using Evolve is very similar to editing records. Use the Page Layout and Field Section components however you need to, but instead of using the [`c-dynamic-forms-save-cancel`](/evolve-forms/main/default/lwc/dynamicFormsSaveCancel/dynamicFormsSaveCancel.js) component to render the buttons, use the [`c-dynamic-forms-create`](/evolve-forms/main/default/lwc/dynamicFormsCreate/dynamicFormsCreate.js) component instead. 56 | 57 | ![Create Record](images/RecordCreate.gif) 58 | 59 | > NOTE: When using Evolve forms to create records, the sObject API name must be passed to the component from page builder or from a containing component. If using the Page Layout component, the layout's API name must also be passed to the component from page builder or from a containing component. 60 | -------------------------------------------------------------------------------- /evolve-forms/main/default/aura/dynamicFormsOverrideEdit/dynamicFormsOverrideEdit.cmp: -------------------------------------------------------------------------------- 1 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 34 | 35 | -------------------------------------------------------------------------------- /evolve-forms/main/default/aura/dynamicFormsOverrideEdit/dynamicFormsOverrideEdit.cmp-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | A Lightning Component Bundle 5 | -------------------------------------------------------------------------------- /evolve-forms/main/default/aura/dynamicFormsOverrideEdit/dynamicFormsOverrideEditController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | /* eslint-disable no-unused-expressions */ 18 | ({ 19 | onRender: function (component, event, helper) { 20 | var workspaceAPI = component.find("workspace"); 21 | var recordId = component.get("v.recordId"); 22 | workspaceAPI.isConsoleNavigation().then(function (response) { 23 | if (response === true) { 24 | helper.closeEditTabAndNavigateToRecordDetail( 25 | component, 26 | recordId, 27 | workspaceAPI 28 | ); 29 | } else { 30 | helper.navigateToRecordDetailPage(component, recordId); 31 | } 32 | }); 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /evolve-forms/main/default/aura/dynamicFormsOverrideEdit/dynamicFormsOverrideEditHelper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | /* eslint-disable no-unused-expressions */ 18 | ({ 19 | closeEditTabAndNavigateToRecordDetail: function ( 20 | component, 21 | recordId, 22 | workspaceAPI 23 | ) { 24 | workspaceAPI.getFocusedTabInfo().then(function (response) { 25 | let focusedTab = response.tabId; 26 | workspaceAPI 27 | .openTab({ 28 | recordId: recordId, 29 | focus: true 30 | }) 31 | .then(function () { 32 | component.find("publishEditEventCompId").invoke(); 33 | }) 34 | .then(function () { 35 | workspaceAPI.closeTab({ tabId: focusedTab }); 36 | }); 37 | }); 38 | }, 39 | 40 | navigateToRecordDetailPage: function (component, recordId) { 41 | var sObjectName = component.get("v.sObjectName"); 42 | var navLink = component.find("navLink"); 43 | var pageRef = { 44 | type: "standard__recordPage", 45 | attributes: { 46 | actionName: "view", 47 | objectApiName: sObjectName, 48 | recordId: recordId 49 | }, 50 | state: { 51 | c__startInEditMode: true 52 | } 53 | }; 54 | navLink.navigate(pageRef, true); 55 | } 56 | }); 57 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/CalculatedField.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | /** 18 | * @description - Metadata about a calculated field in a format that can be utilized by CalculatedFieldController 19 | */ 20 | @SuppressWarnings('PMD.ExcessivePublicCount, PMD.ApexDoc') 21 | public class CalculatedField { 22 | @AuraEnabled 23 | public LightningFormattedAddress address { get; set; } 24 | 25 | @AuraEnabled 26 | public LightningFormattedDateTime dateTimeValue { get; set; } 27 | 28 | @AuraEnabled 29 | public LightningFormattedEmail email { get; set; } 30 | 31 | @AuraEnabled 32 | public LightningFormattedLocation location { get; set; } 33 | 34 | @AuraEnabled 35 | public LightningFormattedName name { get; set; } 36 | 37 | @AuraEnabled 38 | public LightningFormattedNumber numberValue { get; set; } 39 | 40 | @AuraEnabled 41 | public LightningFormattedPhoneNumber phoneNumber { get; set; } 42 | 43 | @AuraEnabled 44 | public LightningFormattedText text { get; set; } 45 | 46 | @AuraEnabled 47 | public LightningFormattedTime timeValue { get; set; } 48 | 49 | @AuraEnabled 50 | public LightningFormattedUrl url { get; set; } 51 | 52 | @AuraEnabled 53 | public Boolean booleanValue { get; set; } 54 | 55 | @AuraEnabled 56 | public Boolean isBoolean { 57 | public get { 58 | return this.booleanValue != null; 59 | } 60 | private set; 61 | } 62 | 63 | @AuraEnabled 64 | public String helpText { get; set; } 65 | 66 | @AuraEnabled 67 | public String label { get; set; } 68 | } 69 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/CalculatedField.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Active 5 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/CalculatedFieldController.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | /** 18 | * @group - Controller 19 | * @description - Controller class for CalculatedFieldSection LWC 20 | */ 21 | public with sharing class CalculatedFieldController { 22 | public static final String APEX_AND_FLOW_ARE_MUTUALLY_EXCLUSIVE = 'Apex Class Name and Flow API Name are mutually exclusive, please input only one value'; 23 | public static final String APEX_OR_FLOW_REQUIRED = 'Please input Apex Class Name or Flow API Name to render calculated fields section'; 24 | public static final String CALCULATED_FIELDS = 'calculatedFields'; 25 | public static final String CLASS_DOES_NOT_EXIST = 'Class does not exists'; 26 | public static final String CLASS_DOES_NOT_IMPLEMENT_INTERFACE = 'Class does not implement Renderer intereface'; 27 | public static final String FLOW_DOES_NOT_EXIST = 'Flow does not exists'; 28 | public static final String FLOW_DOES_NOT_HAVE_OUTPUT_VARIABLE = 'Flow does not have output variable calculatedFields'; 29 | public static final String RECORD_ID = 'recordId'; 30 | 31 | @TestVisible 32 | private static DynamicFormsController.Interview flowInterview = new DynamicFormsController.Interview(); 33 | 34 | /** 35 | * @description Dynamically calculates field and return a list of CalculatedField wrapper 36 | * @param recordId - case record Id. 37 | * @param apexClassName - Apex class used to calculate fields. 38 | * @param flowApiName - Flow name used to calculate fields. 39 | * @return - a list of CalculatedFields to be displayed 40 | */ 41 | @AuraEnabled 42 | public static List getFieldsToRender( 43 | Id recordId, 44 | String apexClassName, 45 | String flowApiName 46 | ) { 47 | if (recordId == null) { 48 | return new List(); 49 | } 50 | validateInput(apexClassName, flowApiName); 51 | 52 | CalculatedFieldController.Renderer fieldRenderer = String.isNotBlank( 53 | apexClassName 54 | ) 55 | ? getRendererFromClassName(apexClassName) 56 | : new FlowRenderer(flowApiName); 57 | 58 | return fieldRenderer.getFields(recordId); 59 | } 60 | 61 | private static void validateInput(String apexClassName, String flowApiName) { 62 | if (apexClassName == null && flowApiName == null) { 63 | throw new IllegalArgumentException(APEX_OR_FLOW_REQUIRED); 64 | } 65 | if (apexClassName != null && flowApiName != null) { 66 | throw new IllegalArgumentException(APEX_AND_FLOW_ARE_MUTUALLY_EXCLUSIVE); 67 | } 68 | } 69 | 70 | private static CalculatedFieldController.Renderer getRendererFromClassName( 71 | String apexClassName 72 | ) { 73 | try { 74 | return (CalculatedFieldController.Renderer) Type.forName(apexClassName) 75 | .newInstance(); 76 | } catch (System.NullPointerException e) { 77 | throw new IllegalArgumentException(CLASS_DOES_NOT_EXIST); 78 | } catch (System.TypeException e) { 79 | throw new IllegalArgumentException(CLASS_DOES_NOT_IMPLEMENT_INTERFACE); 80 | } 81 | } 82 | 83 | /** 84 | * @description - The interface which must be satisfied to render CalculatedFields 85 | */ 86 | public interface Renderer { 87 | /** 88 | * @description - Specification 89 | * @param recordId - The current record's Id 90 | * @return - CalculatedFields to be rendered 91 | */ 92 | List getFields(Id recordId); 93 | } 94 | 95 | /** 96 | * @description - Uses a dynamically instantiated Flow to calculate 97 | * which fields to render 98 | */ 99 | public class FlowRenderer implements CalculatedFieldController.Renderer { 100 | @TestVisible 101 | private String flowApiName = ''; 102 | /** 103 | * @description - Constructor 104 | * @param flowApiName - API name of the autolaunched flow to be dynamically 105 | * instantiated 106 | */ 107 | public FlowRenderer(String flowApiName) { 108 | this.flowApiName = flowApiName; 109 | } 110 | 111 | /** 112 | * @description - Uses the specified flow to dynamically instantiate an interview 113 | * and calculate the field values 114 | * @param recordId - Id of the current record 115 | * @return - CalculatedFields to be rendered 116 | */ 117 | public List getFields(Id recordId) { 118 | List calcFieldList = new List(); 119 | Map inputs = new Map(); 120 | inputs.put(RECORD_ID, recordId); 121 | 122 | try { 123 | flowInterview.createInterview(flowApiName, inputs); 124 | flowInterview.start(); 125 | } catch (System.TypeException e) { 126 | throw new IllegalArgumentException(FLOW_DOES_NOT_EXIST); 127 | } 128 | 129 | try { 130 | calcFieldList = (List) flowInterview.getVariableValue( 131 | CALCULATED_FIELDS 132 | ); 133 | } catch (System.NullPointerException e) { 134 | throw new IllegalArgumentException(FLOW_DOES_NOT_HAVE_OUTPUT_VARIABLE); 135 | } 136 | return calcFieldList; 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/CalculatedFieldController.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Active 5 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/CalculatedFieldControllerTest.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | /** 18 | * @group - Test 19 | * @description - Tests for CalculatedFieldController 20 | */ 21 | @IsTest(isParallel=true) 22 | private class CalculatedFieldControllerTest { 23 | private static final Id CASE_REC_ID = 24 | Case.sObjectType.getDescribe(SObjectDescribeOptions.DEFERRED) 25 | .getKeyPrefix() + '000000000001'; 26 | private static final String BOGUS_APEX_CLASS_NAME = 'BogusClass'; 27 | private static final String BOGUS_FLOW_API_NAME = 'Bogus'; 28 | private static final String EXCEPTION_SHOULD_BE_THROWN = 'An exception should be thrown'; 29 | private static final String EXCEPTION_SHOULD_HAVE_THE_CORRECT_MESSAGE = 'The exception should have the correct message'; 30 | private static final String FLOW_API_NAME = 'Flow_Api_Name'; 31 | private static final String LIST_SHOULD_HAVE_CORRECT_OUTPUT = 'The list should have the expected result'; 32 | private static final String TEST_CALCULATED_FIELD = 'CalculatedFieldControllerTest.TestCalculatedFields'; 33 | 34 | private static Exception myException; 35 | private static List fields = new List(); 36 | 37 | static { 38 | CalculatedField c1 = new CalculatedField(); 39 | LightningFormattedText text = new LightningFormattedText(); 40 | text.isRich = false; 41 | text.linkify = false; 42 | text.value = 'Value 1'; 43 | c1.text = text; 44 | c1.label = 'Field 1'; 45 | 46 | CalculatedField c2 = new CalculatedField(); 47 | LightningFormattedName text2 = new LightningFormattedName(); 48 | text2.firstName = 'John'; 49 | text2.middleName = 'Middleton'; 50 | text2.lastName = 'Doe'; 51 | text2.suffix = 'The 3rd'; 52 | text2.salutation = 'Mr.'; 53 | text2.informalName = 'Jo'; 54 | text2.format = 'short'; 55 | c2.name = text2; 56 | c2.label = 'Field 2'; 57 | 58 | CalculatedField c3 = new CalculatedField(); 59 | LightningFormattedText text3 = new LightningFormattedText(); 60 | text3.isRich = false; 61 | text3.linkify = true; 62 | text3.value = 'Value 3'; 63 | c3.text = text3; 64 | c3.label = 'Field 3'; 65 | 66 | CalculatedField c4 = new CalculatedField(); 67 | LightningFormattedText text4 = new LightningFormattedText(); 68 | text4.isRich = true; 69 | text4.linkify = false; 70 | text4.value = '

Default Value

'; 71 | c4.text = text4; 72 | c4.label = 'Field 4'; 73 | 74 | CalculatedField c5 = new CalculatedField(); 75 | LightningFormattedUrl url1 = new LightningFormattedUrl(); 76 | url1.value = 'https://salesforce.com'; 77 | url1.tooltip = 'Use https://domain-name'; 78 | url1.label = 'Visit salesforce.com'; 79 | url1.target = '_blank'; 80 | url1.tabIndex = 0; 81 | c5.url = url1; 82 | c5.label = 'Field 5'; 83 | 84 | CalculatedField c6 = new CalculatedField(); 85 | LightningFormattedPhoneNumber phone1 = new LightningFormattedPhoneNumber(); 86 | phone1.value = '8005551212'; 87 | phone1.disabled = false; 88 | phone1.tabIndex = 0; 89 | c6.phoneNumber = phone1; 90 | c6.label = 'Field 6'; 91 | 92 | CalculatedField c7 = new CalculatedField(); 93 | LightningFormattedNumber number1 = new LightningFormattedNumber(); 94 | number1.value = 1234.5678; 95 | number1.formatStyle = 'currency'; 96 | number1.currencyCode = 'INR'; 97 | number1.currencyDisplayAs = 'symbol'; 98 | number1.maximumFractionDigits = 2; 99 | number1.maximumSignificantDigits = 2; 100 | number1.minimumFractionDigits = 2; 101 | number1.minimumIntegerDigits = 2; 102 | number1.minimumSignificantDigits = 2; 103 | c7.numberValue = number1; 104 | c7.label = 'Field 7'; 105 | 106 | CalculatedField c8 = new CalculatedField(); 107 | LightningFormattedLocation loc1 = new LightningFormattedLocation(); 108 | loc1.latitude = 22; 109 | loc1.longitude = 122.2222; 110 | c8.location = loc1; 111 | c8.label = 'Field 8'; 112 | 113 | CalculatedField c9 = new CalculatedField(); 114 | LightningFormattedEmail email1 = new LightningFormattedEmail(); 115 | email1.hideIcon = false; 116 | email1.label = 'Email Us!'; 117 | email1.value = 'email@example.com'; 118 | email1.tabIndex = 0; 119 | c9.email = email1; 120 | c9.label = 'Field 9'; 121 | 122 | CalculatedField c10 = new CalculatedField(); 123 | LightningFormattedDateTime dateTime1 = new LightningFormattedDateTime(); 124 | dateTime1.weekday = 'long'; 125 | dateTime1.day = '2-digit'; 126 | dateTime1.month = 'short'; 127 | dateTime1.year = '2-digit'; 128 | dateTime1.value = '1547250828000'; 129 | dateTime1.era = 'long'; 130 | dateTime1.hour = 'numeric'; 131 | dateTime1.hour12 = false; 132 | dateTime1.minute = 'numeric'; 133 | dateTime1.second = 'numeric'; 134 | dateTime1.timeZone = 'UTC'; 135 | dateTime1.timeZoneName = 'short'; 136 | c10.dateTimeValue = dateTime1; 137 | c10.label = 'Field 10'; 138 | 139 | CalculatedField c11 = new CalculatedField(); 140 | LightningFormattedAddress address1 = new LightningFormattedAddress(); 141 | address1.street = '121 Spear St.'; 142 | address1.city = 'San Francisco'; 143 | address1.country = 'US'; 144 | address1.province = 'CA'; 145 | address1.postalCode = '94105'; 146 | address1.latitude = 37.792179; 147 | address1.locale = 'en-US'; 148 | address1.longitude = -122.392735; 149 | address1.showStaticMap = false; 150 | address1.disabled = false; 151 | c11.address = address1; 152 | c11.label = 'Field 11'; 153 | 154 | CalculatedField c12 = new CalculatedField(); 155 | LightningFormattedTime time1 = new LightningFormattedTime(); 156 | time1.value = '22:12:30.999'; 157 | c12.timeValue = time1; 158 | c12.label = 'Field 12'; 159 | 160 | fields.add(c1); 161 | fields.add(c2); 162 | fields.add(c3); 163 | fields.add(c4); 164 | fields.add(c5); 165 | fields.add(c6); 166 | fields.add(c7); 167 | fields.add(c8); 168 | fields.add(c9); 169 | fields.add(c10); 170 | fields.add(c11); 171 | fields.add(c12); 172 | } 173 | 174 | @IsTest 175 | private static void apexClassAndFlowNameInputParamsMissingShouldThrowException() { 176 | List calcFieldList = new List(); 177 | 178 | try { 179 | calcFieldList = CalculatedFieldController.getFieldsToRender( 180 | CASE_REC_ID, 181 | null, 182 | null 183 | ); 184 | } catch (IllegalArgumentException e) { 185 | myException = e; 186 | } 187 | 188 | System.Assert.areNotEqual(null, myException, EXCEPTION_SHOULD_BE_THROWN); 189 | 190 | System.Assert.areEqual( 191 | CalculatedFieldController.APEX_OR_FLOW_REQUIRED, 192 | myException.getMessage(), 193 | EXCEPTION_SHOULD_HAVE_THE_CORRECT_MESSAGE 194 | ); 195 | } 196 | 197 | @IsTest 198 | private static void apexClassAndFlowNameBothPresentShouldThrowException() { 199 | List calcFieldList = new List(); 200 | 201 | try { 202 | calcFieldList = CalculatedFieldController.getFieldsToRender( 203 | CASE_REC_ID, 204 | TEST_CALCULATED_FIELD, 205 | BOGUS_FLOW_API_NAME 206 | ); 207 | } catch (IllegalArgumentException e) { 208 | myException = e; 209 | } 210 | 211 | System.Assert.areNotEqual(null, myException, EXCEPTION_SHOULD_BE_THROWN); 212 | 213 | System.Assert.areEqual( 214 | CalculatedFieldController.APEX_AND_FLOW_ARE_MUTUALLY_EXCLUSIVE, 215 | myException.getMessage(), 216 | EXCEPTION_SHOULD_HAVE_THE_CORRECT_MESSAGE 217 | ); 218 | } 219 | 220 | @IsTest 221 | private static void apexClassDoesNotExistsShouldThrowException() { 222 | List calcFieldList = new List(); 223 | 224 | try { 225 | calcFieldList = CalculatedFieldController.getFieldsToRender( 226 | CASE_REC_ID, 227 | BOGUS_APEX_CLASS_NAME, 228 | null 229 | ); 230 | } catch (IllegalArgumentException e) { 231 | myException = e; 232 | } 233 | 234 | System.Assert.areNotEqual(null, myException, EXCEPTION_SHOULD_BE_THROWN); 235 | 236 | System.Assert.areEqual( 237 | CalculatedFieldController.CLASS_DOES_NOT_EXIST, 238 | myException.getMessage(), 239 | EXCEPTION_SHOULD_HAVE_THE_CORRECT_MESSAGE 240 | ); 241 | } 242 | 243 | @IsTest 244 | private static void validApexClassNameShouldReturnCalculatedFields() { 245 | System.Assert.areEqual( 246 | fields, 247 | CalculatedFieldController.getFieldsToRender( 248 | CASE_REC_ID, 249 | TEST_CALCULATED_FIELD, 250 | null 251 | ), 252 | LIST_SHOULD_HAVE_CORRECT_OUTPUT 253 | ); 254 | } 255 | 256 | @IsTest 257 | private static void flowDoesNotExistsShouldThrowException() { 258 | List calcFieldList = new List(); 259 | 260 | try { 261 | calcFieldList = CalculatedFieldController.getFieldsToRender( 262 | CASE_REC_ID, 263 | null, 264 | BOGUS_FLOW_API_NAME 265 | ); 266 | } catch (IllegalArgumentException e) { 267 | myException = e; 268 | } 269 | 270 | System.Assert.areNotEqual(null, myException, EXCEPTION_SHOULD_BE_THROWN); 271 | 272 | System.Assert.areEqual( 273 | CalculatedFieldController.FLOW_DOES_NOT_EXIST, 274 | myException.getMessage(), 275 | EXCEPTION_SHOULD_HAVE_THE_CORRECT_MESSAGE 276 | ); 277 | } 278 | 279 | @IsTest 280 | private static void validFlowShouldReturnCalculatedFields() { 281 | Map inputs = new Map(); 282 | inputs.put(CalculatedFieldController.CALCULATED_FIELDS, fields); 283 | DynamicFormsControllerTest.FakeInterview fake = new DynamicFormsControllerTest.FakeInterview( 284 | inputs 285 | ); 286 | CalculatedFieldController.flowInterview = fake; 287 | 288 | System.Assert.areEqual( 289 | fields, 290 | CalculatedFieldController.getFieldsToRender( 291 | CASE_REC_ID, 292 | null, 293 | FLOW_API_NAME 294 | ), 295 | LIST_SHOULD_HAVE_CORRECT_OUTPUT 296 | ); 297 | } 298 | 299 | @SuppressWarnings('PMD.ApexDoc') 300 | public class TestCalculatedFields implements CalculatedFieldController.Renderer { 301 | public List getFields(Id recordId) { 302 | return fields; 303 | } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/CalculatedFieldControllerTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Active 5 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/DynamicFormsController.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/DynamicFormsControllerTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedAddress.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | /** 18 | * @description - Wrapper for displaying an address field in a formatted manner. 19 | */ 20 | @SuppressWarnings( 21 | 'PMD.ApexDoc, PMD.FieldAndMethodOrder, PMD.ExcessivePublicCount' 22 | ) 23 | public class LightningFormattedAddress { 24 | @AuraEnabled 25 | public String city { get; set; } 26 | 27 | @AuraEnabled 28 | public String country { get; set; } 29 | 30 | @AuraEnabled 31 | public Boolean disabled { get; set; } 32 | 33 | @AuraEnabled 34 | public Decimal latitude { get; set; } 35 | 36 | @AuraEnabled 37 | public String locale { get; set; } 38 | 39 | @AuraEnabled 40 | public Decimal longitude { get; set; } 41 | 42 | @AuraEnabled 43 | public String postalCode { get; set; } 44 | 45 | @AuraEnabled 46 | public String province { get; set; } 47 | 48 | @AuraEnabled 49 | public Boolean showStaticMap { get; set; } 50 | 51 | @AuraEnabled 52 | public String street { get; set; } 53 | } 54 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedAddress.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Active 5 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedDateTime.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | /** 18 | * @description - Wrapper for displaying a date time field in a formatted manner. 19 | */ 20 | @SuppressWarnings( 21 | 'PMD.ApexDoc, PMD.FieldAndMethodOrder, PMD.ExcessivePublicCount' 22 | ) 23 | public class LightningFormattedDateTime { 24 | @AuraEnabled 25 | public String day { get; set; } 26 | 27 | @AuraEnabled 28 | public String era { get; set; } 29 | 30 | @AuraEnabled 31 | public String hour { get; set; } 32 | 33 | @AuraEnabled 34 | public Boolean hour12 { get; set; } 35 | 36 | @AuraEnabled 37 | public String minute { get; set; } 38 | 39 | @AuraEnabled 40 | public String month { get; set; } 41 | 42 | @AuraEnabled 43 | public String second { get; set; } 44 | 45 | @AuraEnabled 46 | public String timeZone { get; set; } 47 | 48 | @AuraEnabled 49 | public String timeZoneName { get; set; } 50 | 51 | @AuraEnabled 52 | public String value { get; set; } 53 | 54 | @AuraEnabled 55 | public String weekday { get; set; } 56 | 57 | @AuraEnabled 58 | public String year { get; set; } 59 | } 60 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedDateTime.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Active 5 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedEmail.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | /** 18 | * @description - Wrapper for displaying an email in a formatted manner. 19 | */ 20 | @SuppressWarnings('PMD.ApexDoc, PMD.FieldAndMethodOrder') 21 | public class LightningFormattedEmail { 22 | @AuraEnabled 23 | public Boolean hideIcon { get; set; } 24 | 25 | @AuraEnabled 26 | public String label { get; set; } 27 | 28 | @AuraEnabled 29 | public Integer tabIndex { get; set; } 30 | 31 | @AuraEnabled 32 | public String value { get; set; } 33 | } 34 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedEmail.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Active 5 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedLocation.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | /** 18 | * @description - Wrapper for displaying a location in a formatted manner. 19 | */ 20 | @SuppressWarnings('PMD.ApexDoc') 21 | public class LightningFormattedLocation { 22 | @AuraEnabled 23 | public Decimal latitude { get; set; } 24 | 25 | @AuraEnabled 26 | public Decimal longitude { get; set; } 27 | } 28 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedLocation.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Active 5 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedName.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | /** 18 | * @description - Wrapper for displaying a name field in a formatted manner. 19 | */ 20 | @SuppressWarnings('PMD.ApexDoc') 21 | public class LightningFormattedName { 22 | @AuraEnabled 23 | public String firstName { get; set; } 24 | 25 | @AuraEnabled 26 | public String format { get; set; } 27 | 28 | @AuraEnabled 29 | public String informalName { get; set; } 30 | 31 | @AuraEnabled 32 | public String lastName { get; set; } 33 | 34 | @AuraEnabled 35 | public String middleName { get; set; } 36 | 37 | @AuraEnabled 38 | public String salutation { get; set; } 39 | 40 | @AuraEnabled 41 | public String suffix { get; set; } 42 | } 43 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedName.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Active 5 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedNumber.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | /** 18 | * @description - Wrapper for displaying a number field in a formatted manner. 19 | */ 20 | @SuppressWarnings('PMD.ApexDoc') 21 | public class LightningFormattedNumber { 22 | @AuraEnabled 23 | public String currencyCode { get; set; } 24 | 25 | @AuraEnabled 26 | public String currencyDisplayAs { get; set; } 27 | 28 | @AuraEnabled 29 | public String formatStyle { get; set; } 30 | 31 | @AuraEnabled 32 | public Integer maximumFractionDigits { get; set; } 33 | 34 | @AuraEnabled 35 | public Integer maximumSignificantDigits { get; set; } 36 | 37 | @AuraEnabled 38 | public Integer minimumFractionDigits { get; set; } 39 | 40 | @AuraEnabled 41 | public Integer minimumIntegerDigits { get; set; } 42 | 43 | @AuraEnabled 44 | public Integer minimumSignificantDigits { get; set; } 45 | 46 | @AuraEnabled 47 | public Decimal value { get; set; } 48 | } 49 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedNumber.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Active 5 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedPhoneNumber.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | /** 18 | * @description - Wrapper for displaying a phone number in a formatted manner. 19 | */ 20 | @SuppressWarnings('PMD.ApexDoc') 21 | public class LightningFormattedPhoneNumber { 22 | @AuraEnabled 23 | public Boolean disabled { get; set; } 24 | 25 | @AuraEnabled 26 | public Integer tabIndex { get; set; } 27 | 28 | @AuraEnabled 29 | public String value { get; set; } 30 | } 31 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedPhoneNumber.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedText.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | /** 18 | * @description - Wrapper for displaying a text in a formatted manner. 19 | */ 20 | @SuppressWarnings('PMD.ApexDoc') 21 | public class LightningFormattedText { 22 | @AuraEnabled 23 | public Boolean isRich { get; set; } 24 | 25 | @AuraEnabled 26 | public Boolean linkify { get; set; } 27 | 28 | @AuraEnabled 29 | public String value { get; set; } 30 | } 31 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedText.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Active 5 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedTime.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | /** 18 | * @description - Wrapper for displaying a time field in a formatted manner. 19 | */ 20 | @SuppressWarnings('PMD.ApexDoc') 21 | public class LightningFormattedTime { 22 | @AuraEnabled 23 | public String value { get; set; } 24 | } 25 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedTime.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Active 5 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedUrl.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | /** 18 | * @description - Wrapper for displaying a url field in a formatted manner 19 | */ 20 | @SuppressWarnings('PMD.ApexDoc') 21 | public class LightningFormattedUrl { 22 | @AuraEnabled 23 | public String label { get; set; } 24 | 25 | @AuraEnabled 26 | public Integer tabIndex { get; set; } 27 | 28 | @AuraEnabled 29 | public String target { get; set; } 30 | 31 | @AuraEnabled 32 | public String toolTip { get; set; } 33 | 34 | @AuraEnabled 35 | public String value { get; set; } 36 | } 37 | -------------------------------------------------------------------------------- /evolve-forms/main/default/classes/LightningFormattedUrl.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Active 5 | -------------------------------------------------------------------------------- /evolve-forms/main/default/layouts/Conditional_Warning__mdt-Conditional Warning Layout.layout-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | false 6 | true 7 | 8 | 9 | 10 | Required 11 | MasterLabel 12 | 13 | 14 | Required 15 | DeveloperName 16 | 17 | 18 | Required 19 | NamespacePrefix 20 | 21 | 22 | 23 | 24 | Edit 25 | IsProtected 26 | 27 | 28 | Required 29 | SObject_API_Name__c 30 | 31 | 32 | Required 33 | Flow_API_Name__c 34 | 35 | 36 | 37 | 38 | 39 | false 40 | false 41 | true 42 | 43 | 44 | 45 | Readonly 46 | CreatedById 47 | 48 | 49 | 50 | 51 | Readonly 52 | LastModifiedById 53 | 54 | 55 | 56 | 57 | 58 | true 59 | true 60 | false 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | Flow_API_Name__c 69 | 70 | false 71 | false 72 | false 73 | false 74 | false 75 | 76 | 00h6s000004Gri8 77 | 4 78 | 0 79 | Default 80 | 81 | 82 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/calculatedFieldSection/calculatedFieldSection.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 174 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/calculatedFieldSection/calculatedFieldSection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 { api, track } from "lwc"; 18 | import { ShowToastEvent } from "lightning/platformShowToastEvent"; 19 | import DynamicFormsElement from "c/dynamicFormsElement"; 20 | import getFieldsToRender from "@salesforce/apex/CalculatedFieldController.getFieldsToRender"; 21 | 22 | export default class CalculatedFieldSection extends DynamicFormsElement { 23 | @api 24 | apexClassName; 25 | 26 | @api 27 | boundary = false; 28 | 29 | @api 30 | columns = "1"; 31 | 32 | @track 33 | fields; 34 | 35 | @api 36 | flowApiName; 37 | 38 | @api 39 | hideSectionTitle = false; 40 | 41 | @api 42 | recordId; 43 | 44 | @api 45 | sectionLabel; 46 | 47 | @track showSpinner = false; 48 | 49 | connectedCallback() { 50 | this.fetchFieldsToRender(); 51 | this.subscribeToMessageChannel(); 52 | } 53 | 54 | disconnectedCallback() { 55 | this.unsubscribeToMessageChannel(); 56 | } 57 | 58 | fetchFieldsToRender() { 59 | this.showSpinner = true; 60 | getFieldsToRender({ 61 | recordId: this.recordId, 62 | apexClassName: this.apexClassName, 63 | flowApiName: this.flowApiName 64 | }) 65 | .then((result) => { 66 | if (result) { 67 | this.fields = result; 68 | } 69 | this.showSpinner = false; 70 | }) 71 | .catch((error) => { 72 | this.handleError(error); 73 | }); 74 | } 75 | 76 | handleMessage(message) { 77 | if (message.eventType === this.EVENT_TYPE.SAVE_END) { 78 | this.fetchFieldsToRender(); 79 | } 80 | } 81 | 82 | handleError(event) { 83 | this.showSpinner = false; 84 | this.dispatchEvent( 85 | new ShowToastEvent({ 86 | title: "Error", 87 | message: event?.body?.message, 88 | variant: "error" 89 | }) 90 | ); 91 | } 92 | 93 | get hasFields() { 94 | return this.fields && this.fields.length > 0; 95 | } 96 | 97 | get size() { 98 | return this.columns === "2" ? 6 : 12; 99 | } 100 | 101 | get boundaryTheme() { 102 | return this.boundary ? "slds-box slds-theme_default" : "slds-theme_default"; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/calculatedFieldSection/calculatedFieldSection.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | true 5 | Calculated Field Section 6 | This allows for dynamically calculated fields rendered as a field section. 7 | 8 | lightning__RecordPage 9 | 10 | 11 | 12 | 18 | 24 | 30 | 36 | 42 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsCollapsibleSection/dynamicFormsCollapsibleSection.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 50 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsCollapsibleSection/dynamicFormsCollapsibleSection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | /* eslint-disable @lwc/lwc/no-api-reassignments */ 18 | import { LightningElement, api } from "lwc"; 19 | 20 | export default class DynamicFormsCollapsibleSection extends LightningElement { 21 | @api isOpen; 22 | @api label; 23 | @api hideLabel = false; 24 | 25 | get sectionClass() { 26 | return this.isOpen ? "slds-section slds-is-open" : "slds-section"; 27 | } 28 | 29 | connectedCallback() { 30 | if (this.isOpen === undefined || this.isOpen === null) { 31 | this.isOpen = true; 32 | } 33 | } 34 | 35 | toggleOpen(event) { 36 | event.preventDefault(); 37 | this.isOpen = !this.isOpen; 38 | } 39 | 40 | handleKeyPress(event) { 41 | if (["Enter", "Space", " "].includes(event?.key)) { 42 | this.toggleOpen(event); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsCollapsibleSection/dynamicFormsCollapsibleSection.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | false 5 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsCompactPageLayout/dynamicFormsCompactPageLayout.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | .lookupHoverBody { 18 | padding-left: 20px; 19 | padding-top: 10px; 20 | padding-bottom: 20px; 21 | } 22 | .iconSizing { 23 | width: 24px; 24 | height: 24px; 25 | margin-right: 8px; 26 | } 27 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsCompactPageLayout/dynamicFormsCompactPageLayout.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 65 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsCompactPageLayout/dynamicFormsCompactPageLayout.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 { LightningElement, wire, api } from "lwc"; 18 | import { ShowToastEvent } from "lightning/platformShowToastEvent"; 19 | import { getObjectInfo } from "lightning/uiObjectInfoApi"; 20 | 21 | const USER = "User"; 22 | const GROUP = "Group"; 23 | const RECORDTYPE = "RecordType"; 24 | 25 | export default class DynamicFormsCompactPageLayout extends LightningElement { 26 | @api lookupObjectApiName; 27 | @api lookupRecordId; 28 | @api lookupFieldValue; 29 | 30 | isLookupUserOrGroupOrRecordType = false; 31 | objectInfo; 32 | iconUrl = ""; 33 | iconColor = ""; 34 | 35 | @wire(getObjectInfo, { objectApiName: "$lookupObjectApiName" }) 36 | wireObjectInfo(result) { 37 | if (result && result.data) { 38 | this.objectInfo = result.data; 39 | this.iconUrl = 40 | result.data.themeInfo && result.data.themeInfo.iconUrl 41 | ? result.data.themeInfo.iconUrl 42 | : ""; 43 | this.iconColor = 44 | result.data.themeInfo && result.data.themeInfo.color 45 | ? result.data.themeInfo.color 46 | : ""; 47 | } else if (result.error) { 48 | this.handleError(result.error); 49 | } 50 | } 51 | 52 | connectedCallback() { 53 | this.isLookupUserOrGroupOrRecordType = 54 | this.lookupObjectApiName === USER || 55 | this.lookupObjectApiName === GROUP || 56 | this.lookupObjectApiName === RECORDTYPE 57 | ? true 58 | : false; 59 | } 60 | 61 | handleError(event) { 62 | this.dispatchEvent( 63 | new ShowToastEvent({ 64 | title: "Error", 65 | message: event.body.message, 66 | variant: "error" 67 | }) 68 | ); 69 | } 70 | 71 | get iconBackgroundColor() { 72 | return "background-color: #" + this.iconColor; 73 | } 74 | 75 | get isLookupRecordType() { 76 | return this.lookupObjectApiName === RECORDTYPE; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsCompactPageLayout/dynamicFormsCompactPageLayout.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | false 5 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsCreate/dynamicFormsCreate.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 119 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsCreate/dynamicFormsCreate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 { api } from "lwc"; 18 | import { createRecord } from "lightning/uiRecordApi"; 19 | import { ShowToastEvent } from "lightning/platformShowToastEvent"; 20 | import DynamicFormsSaveCancel from "c/dynamicFormsSaveCancel"; 21 | 22 | /* 23 | * this component broadcasts completion events so other LWC can react to this component's actions. 24 | * example: 25 | * 28 | * */ 29 | const COMPLETION_EVENT = "completion"; 30 | 31 | export default class DynamicFormsCreate extends DynamicFormsSaveCancel { 32 | @api objectApiName; 33 | @api buttonLabel = "Submit"; 34 | @api successMessage = "Record Created"; 35 | @api includeCancelButton = false; 36 | @api pinToBottom = false; 37 | @api redirectToRecordOnCreate = false; 38 | /* Used for Parent component override for custom validations */ 39 | @api 40 | get disableCreateButton() { 41 | return this.saveIsDisabled; 42 | } 43 | set disableCreateButton(value) { 44 | this.saveIsDisabled = value; 45 | } 46 | 47 | save() { 48 | this.broadcast(this.EVENT_TYPE.SAVE_START); 49 | 50 | let event = null; 51 | let fields = this.pendingUpdates; 52 | const recordInput = { fields }; 53 | recordInput.apiName = this.objectApiName; 54 | 55 | createRecord(recordInput) 56 | .then((record) => { 57 | event = { 58 | status: "success", 59 | recordId: record.id 60 | }; 61 | this.dispatchEvent( 62 | new ShowToastEvent({ 63 | title: "Success", 64 | message: this.successMessage, 65 | variant: "success" 66 | }) 67 | ); 68 | if (this.redirectToRecordOnCreate) { 69 | this.navigateToRecord(record.id); 70 | } 71 | }) 72 | .catch((error) => { 73 | this.dispatchEvent( 74 | new ShowToastEvent({ 75 | title: "Error Creating Record", 76 | message: this.generateErrorMessage(error), 77 | variant: "error" 78 | }) 79 | ); 80 | }) 81 | .then(() => { 82 | this.broadcast(this.EVENT_TYPE.SAVE_END); 83 | if (event) { 84 | this.dispatchEvent( 85 | new CustomEvent(COMPLETION_EVENT, { 86 | detail: event 87 | }) 88 | ); 89 | } 90 | }); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsCreate/dynamicFormsCreate.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | true 5 | Dynamic Forms - Create Button 6 | This allows for dynamic fields to create a record. 7 | 8 | lightning__AppPage 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsElement/dynamicFormsElement.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 20 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsElement/dynamicFormsElement.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 { LightningElement, api, wire } from "lwc"; 18 | import { NavigationMixin } from "lightning/navigation"; 19 | import { CurrentPageReference } from "lightning/navigation"; 20 | import { 21 | publish, 22 | subscribe, 23 | unsubscribe, 24 | MessageContext, 25 | APPLICATION_SCOPE 26 | } from "lightning/messageService"; 27 | import DynamicFormsMessageChannel from "@salesforce/messageChannel/DynamicForms__c"; 28 | 29 | // Base class for shared functionality between all DynamicForm LWC components. 30 | export default class DynamicFormsElement extends NavigationMixin( 31 | LightningElement 32 | ) { 33 | @api objectApiName; 34 | @api recordId = null; 35 | @api group = null; 36 | 37 | currentPageReference = null; 38 | subscription = null; 39 | 40 | @wire(MessageContext) messageContext; 41 | 42 | // Event types that should be used with broadcast() and handleMessage(). 43 | EVENT_TYPE = { 44 | // Broadcast that the form should cancel the current form modifications 45 | CANCEL: "Cancel", 46 | // Check if the form is currently in edit mode 47 | CHECK_EDIT: "CheckEdit", 48 | // Check if any form values should be overridden by a pre-populated value 49 | CHECK_OVERRIDES: "CheckOverrides", 50 | // Broadcast that the form should switch to edit mode 51 | EDIT: "Edit", 52 | // Broadcast that an input element got focus 53 | FOCUS_IN: "FocusIn", 54 | // Broadcast that an input element focused out 55 | FOCUS_OUT: "FocusOut", 56 | // Broadcast to remove required field which is no longer required 57 | NO_LONGER_REQUIRED_FIELD: "NoLongerRequiredField", 58 | // Broadcast that this element contains a required field 59 | REQUIRED_FIELD: "RequiredField", 60 | // Broadcast to reset current form fields 61 | RESET: "Reset", 62 | // Broadcast that the form has finished saving (successful or not) 63 | SAVE_END: "SaveEnd", 64 | // Broadcast that the form has begun to save 65 | SAVE_START: "SaveStart", 66 | // Broadcast that a field value is updated 67 | UPDATE: "Update" 68 | }; 69 | 70 | // Default group to either be the recordId of the record page or apiName of the app page. 71 | @wire(CurrentPageReference) 72 | handleParameters(pageRef) { 73 | this.currentPageReference = pageRef; 74 | if (this.group === null) { 75 | this.group = 76 | pageRef?.attributes?.recordId ?? 77 | pageRef?.attributes?.apiName ?? 78 | `${pageRef?.attributes?.objectApiName}.${pageRef?.attributes?.actionName}`; 79 | } 80 | } 81 | 82 | // Subscribe to the DynamicFormsMessageChannel channel that all DynamicFormsElements communicate 83 | // through. Call during connectedCallback(). Implement handleMessage() in child class. 84 | subscribeToMessageChannel() { 85 | if (!this.subscription) { 86 | this.subscription = subscribe( 87 | this.messageContext, 88 | DynamicFormsMessageChannel, 89 | (message) => this.verifyAndHandleMessage(message), 90 | { scope: APPLICATION_SCOPE } 91 | ); 92 | } 93 | } 94 | 95 | // Verify that a message is sent to this form group, then designate handleMessage() logic to the 96 | // child component. 97 | verifyAndHandleMessage(message) { 98 | if (message && message.eventType && message.group === this.group) { 99 | this.handleMessage(message); 100 | } 101 | } 102 | 103 | // Stub method. Called when a message is broadcast via DynamicFormsMessageChannel. 104 | handleMessage(message) { 105 | // Implement custom behavior in the child component. 106 | } 107 | 108 | // Broadcast a message on the DynamicFormsMessageChannel to all DynamicFormsElements. 109 | // eventType is a String that must be provided. 110 | // additionalData is an optional object containing additional values to broadcast. 111 | broadcast(eventType, additionalData) { 112 | if (this.messageContext) { 113 | /* Sometimes the messageContext can be invalid and publishing with invalid messageContext 114 | throws an error. Falsy check on messageContext doesn't help because messageContext 115 | still has a truthy value. Added try catch block to catch these errors and discard. */ 116 | try { 117 | publish(this.messageContext, DynamicFormsMessageChannel, { 118 | eventType: eventType, 119 | group: this.group, 120 | ...additionalData 121 | }); 122 | } catch (error) { 123 | console.error(error); 124 | } 125 | } 126 | } 127 | 128 | // Unsubscribe to the channel when the element is disconnected. 129 | // Call during disconnectedCallback(). 130 | unsubscribeToMessageChannel() { 131 | unsubscribe(this.subscription); 132 | this.subscription = null; 133 | } 134 | 135 | // Helper method to try to convert a JSON string to a JSON object without breaking the LWC. 136 | // Used for converting properties passed into the LWC from app builder. 137 | tryToParseJSON(jsonVariable) { 138 | if (typeof jsonVariable === "string") { 139 | try { 140 | return JSON.parse(jsonVariable?.replace(/\r?\n|\r/g, "")); 141 | } catch (e) { 142 | /* Fall through to bottom return statement */ 143 | } 144 | } else if (typeof jsonVariable === "object") { 145 | return jsonVariable; 146 | } 147 | return {}; 148 | } 149 | 150 | // Helper method to navigate to another record. 151 | navigateToRecord(recordId) { 152 | this[NavigationMixin.Navigate]({ 153 | type: "standard__recordPage", 154 | attributes: { 155 | recordId: recordId?.substring(0, 15), 156 | actionName: "view" 157 | } 158 | }); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsElement/dynamicFormsElement.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | false 5 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsFieldSection/dynamicFormsFieldSection.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | .inactive { 18 | opacity: 0.2; 19 | } 20 | 21 | .pendingEditClass { 22 | background-color: rgb(250, 255, 189); 23 | } 24 | 25 | .pencilIconClass { 26 | background: transparent; 27 | border: none; 28 | color: transparent; 29 | } 30 | 31 | .blankSpace { 32 | min-height: 48px; 33 | } 34 | 35 | .lookupField { 36 | color: rgb(1, 118, 211); 37 | border-bottom: 1px dotted; 38 | display: inline; 39 | } 40 | 41 | .lookupFieldValueContainer:empty { 42 | min-height: 1.25rem; 43 | } 44 | 45 | .systemDateTime { 46 | font-size: 0.875rem; 47 | font-weight: 400; 48 | color: rgb(24, 24, 24); 49 | width: 100%; 50 | } 51 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsFieldSection/dynamicFormsFieldSection.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 217 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsFieldSection/dynamicFormsFieldSection.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | true 5 | Dynamic Forms - Field Section 6 | This allows for dynamic fields rendered as a field section. 7 | 8 | lightning__AppPage 9 | lightning__RecordPage 10 | lightningCommunity__Page 11 | lightningCommunity__Default 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsFieldSet/dynamicFormsFieldSet.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 34 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsFieldSet/dynamicFormsFieldSet.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 getFieldsFromFieldSetAPIName from "@salesforce/apex/DynamicFormsController.getFieldsFromFieldSetAPIName"; 18 | import { ShowToastEvent } from "lightning/platformShowToastEvent"; 19 | import { reduceErrors } from "c/dynamicFormsUtils"; 20 | import { LightningElement, api } from "lwc"; 21 | 22 | export default class DynamicFormsFieldSet extends LightningElement { 23 | @api boundary = false; 24 | @api fieldSetApiName; 25 | @api numberOfColumns = "2"; 26 | @api objectApiName; 27 | @api recordId = null; 28 | @api sectionLabel; 29 | @api readOnlyMode = false; 30 | @api hideEditHighlighting = false; 31 | @api startInEditMode = false; 32 | @api labelOverrides = {}; 33 | @api helpTextOverrides = {}; 34 | 35 | apiNamesCsv; 36 | 37 | /** 38 | * description fetch field api names only if fieldset api name is defined 39 | */ 40 | connectedCallback() { 41 | if (this.fieldSetApiName) { 42 | this.getFieldAPINamesFromFieldSet(); 43 | } 44 | } 45 | /** 46 | * description fetch field api names in the fieldset for the object by fieldset api name. 47 | */ 48 | getFieldAPINamesFromFieldSet() { 49 | getFieldsFromFieldSetAPIName({ 50 | objectApiName: this.objectApiName, 51 | fieldSetApiName: this.fieldSetApiName 52 | }) 53 | .then((result) => { 54 | this.sectionLabel = this.sectionLabel 55 | ? this.sectionLabel 56 | : result["fieldSetLabel"]; 57 | this.apiNamesCsv = result["apiNamesCsv"]; 58 | }) 59 | .catch((error) => { 60 | this.dispatchEvent( 61 | new ShowToastEvent({ 62 | title: "Error", 63 | message: this.generateErrorMessage(error), 64 | variant: "error" 65 | }) 66 | ); 67 | }); 68 | } 69 | /** 70 | * Method to generate error message 71 | * @param error - error object 72 | */ 73 | generateErrorMessage(error) { 74 | let message = "The following errors occured:"; 75 | for (const errorMessage of reduceErrors(error)) { 76 | message += `\n\t* ${errorMessage}`; 77 | } 78 | return message; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsFieldSet/dynamicFormsFieldSet.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | true 5 | Dynamic Forms - Field Section via FieldSet 6 | This allows to create field section based on fieldset 7 | 8 | lightning__RecordPage 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsHeadlessEdit/dynamicFormsHeadlessEdit.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsHeadlessEdit/dynamicFormsHeadlessEdit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 { api } from "lwc"; 18 | import userId from "@salesforce/user/Id"; 19 | import { displayToast, reduceErrors } from "c/dynamicFormsUtils"; 20 | import DynamicFormsElement from "c/dynamicFormsElement"; 21 | import fetchUserRecordAccess from "@salesforce/apex/DynamicFormsController.fetchUserRecordAccess"; 22 | 23 | export default class DynamicFormsHeadlessEdit extends DynamicFormsElement { 24 | @api executeInvoke = false; 25 | @api recordId = null; 26 | userId = userId; 27 | 28 | renderedCallback() { 29 | if (this.executeInvoke === true) { 30 | this.invoke(); 31 | } 32 | } 33 | 34 | checkAccess() { 35 | fetchUserRecordAccess({ 36 | userId: this.userId, 37 | recordId: this.recordId 38 | }) 39 | .then((result) => { 40 | if (result && result.HasEditAccess) { 41 | this.broadcast(this.EVENT_TYPE.EDIT); 42 | } else { 43 | displayToast(this, "Info", "Record cannot be edited", "info"); 44 | } 45 | }) 46 | .catch((error) => { 47 | displayToast(this, "Error", this.generateErrorMessage(error), "error"); 48 | }); 49 | } 50 | 51 | generateUserAccessErrorMessage(error) { 52 | const errorMessages = reduceErrors(error); 53 | let message = 54 | "There is an issue with retrieving user access for this record:"; 55 | for (const errorMessage of errorMessages) { 56 | message += `\n\t* ${errorMessage}`; 57 | } 58 | return message; 59 | } 60 | 61 | @api invoke() { 62 | this.checkAccess(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsHeadlessEdit/dynamicFormsHeadlessEdit.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | true 5 | Dynamic Form - Headless Edit Button 6 | This allows creating a headless Edit action on a record page. 7 | 8 | lightning__RecordAction 9 | 10 | 11 | 12 | Action 13 | 14 | 15 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsPageLayout/dynamicFormsPageLayout.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 38 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsPageLayout/dynamicFormsPageLayout.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 { LightningElement, api, track, wire } from "lwc"; 18 | import getLayoutSectionsByPageLayoutName from "@salesforce/apex/DynamicFormsController.getLayoutSectionsByPageLayoutName"; 19 | import { getRecordUi } from "lightning/uiRecordApi"; 20 | import { displayToast } from "c/dynamicFormsUtils"; 21 | 22 | const ASTERISK = "*"; 23 | const COMMA = ","; 24 | const EMPTY_STRING = ""; 25 | const ERROR_TITLE = "Error"; 26 | const ERROR_VARIANT = "error"; 27 | const FIELD = "Field"; 28 | const FULL = "Full"; 29 | const RECORD_TYPE_ID_PREFIX = "012"; 30 | const VIEW = "View"; 31 | 32 | export default class DynamicFormsPageLayout extends LightningElement { 33 | @api objectApiName; 34 | @api recordId = null; 35 | @api pageLayoutName; 36 | @api labelOverrides = {}; 37 | @api helpTextOverrides = {}; 38 | @api startInEditMode = false; 39 | @api boundary = false; 40 | @api disableCompactLayoutHover = false; 41 | 42 | @track layoutSections; 43 | @track pageLayoutId; 44 | @track error; 45 | 46 | @wire(getRecordUi, { 47 | recordIds: "$recordId", 48 | layoutTypes: FULL, 49 | modes: [VIEW] 50 | }) 51 | wiredUI(result) { 52 | if (this.pageLayoutName || !this.recordId) { 53 | return; 54 | } 55 | if (result.data) { 56 | this.layoutSections = this.extractLayoutSectionsFromResponse(result.data); 57 | } 58 | if (result.error) { 59 | console.error({ error: result.error }); 60 | displayToast(this, ERROR_TITLE, result.error?.body?.error, ERROR_VARIANT); 61 | } 62 | } 63 | 64 | get boundaryTheme() { 65 | return this.boundary ? "slds-box slds-theme_default" : "slds-theme_default"; 66 | } 67 | 68 | extractLayoutSectionsFromResponse(responseData) { 69 | let result = []; 70 | for (let key in responseData.layouts[this.objectApiName]) { 71 | if (key.startsWith(RECORD_TYPE_ID_PREFIX)) { 72 | for (let section of responseData.layouts[this.objectApiName][key].Full[ 73 | VIEW 74 | ].sections) { 75 | let fields = []; 76 | for (let layoutRow of section.layoutRows) { 77 | for (let layoutItem of layoutRow.layoutItems) { 78 | for (let component of layoutItem.layoutComponents) { 79 | fields.push( 80 | this.getFieldStringRepresentation(component, layoutItem) 81 | ); 82 | } 83 | } 84 | } 85 | let hideHeader = section.useHeading === false; 86 | let formattedSection = { 87 | id: section.id, 88 | sectionLabel: section.heading, 89 | numberOfColumns: section.columns.toString(), 90 | apiNamesCsv: fields.join(COMMA), 91 | hideSectionLabelOnView: hideHeader, 92 | hideSectionLabelOnEdit: hideHeader 93 | }; 94 | result.push(formattedSection); 95 | } 96 | } 97 | } 98 | return result; 99 | } 100 | 101 | getFieldStringRepresentation(component, layoutItem) { 102 | if (component.componentType === FIELD) { 103 | let isReadOnly = 104 | (layoutItem.editableForUpdate === false && this.recordId) || 105 | (layoutItem.editableForNew === false && !this.recordId); 106 | let formattedApiName = `${layoutItem.required ? ASTERISK : EMPTY_STRING}${ 107 | component.apiName 108 | }${isReadOnly ? ASTERISK : EMPTY_STRING}`; 109 | return formattedApiName; 110 | } 111 | return EMPTY_STRING; 112 | } 113 | 114 | getPageLayout() { 115 | if (this.pageLayoutName) { 116 | getLayoutSectionsByPageLayoutName({ 117 | layoutName: this.pageLayoutName 118 | }).then((response) => { 119 | this.layoutSections = response; 120 | }); 121 | } 122 | } 123 | 124 | connectedCallback() { 125 | this.getPageLayout(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsPageLayout/dynamicFormsPageLayout.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | true 5 | Dynamic Forms - Page Layout 6 | This allows for a fully rendered Page Layout from LWC 7 | 8 | lightning__RecordPage 9 | lightning__AppPage 10 | lightningCommunity__Page 11 | lightningCommunity__Default 12 | 13 | 14 | 15 | 20 | 26 | 32 | 37 | 42 | 43 | 44 | 51 | 56 | 61 | 67 | 73 | 78 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsPredefinedValues/dynamicFormsPredefinedValues.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 21 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsPredefinedValues/dynamicFormsPredefinedValues.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 { api, wire } from "lwc"; 18 | import { CurrentPageReference } from "lightning/navigation"; 19 | import DynamicFormsElement from "c/dynamicFormsElement"; 20 | 21 | export default class DynamicFormsPredefinedValues extends DynamicFormsElement { 22 | @api readValuesFromUrl = false; 23 | 24 | predefinedFields = {}; 25 | urlFieldOverrides = {}; 26 | 27 | fieldsThatHaveChanged = new Set(); 28 | 29 | @api get predefinedFieldValues() { 30 | return this.predefinedFields; 31 | } 32 | 33 | set predefinedFieldValues(value) { 34 | let oldValues = this.predefinedFields; 35 | this.predefinedFields = this.tryToParseJSON(value); 36 | if (this.predefinedFields !== oldValues) { 37 | this.broadcast(this.EVENT_TYPE.CHECK_OVERRIDES); 38 | } 39 | } 40 | 41 | // Read any values passed via URL 42 | // Param can be Salesforce's standard defaultFieldValues or custom c__fieldOverrides 43 | // Format: c__fieldOverrides=Status=Assigned,Subject=Multiword+Subject 44 | @wire(CurrentPageReference) 45 | getStateParameters(currentPageReference) { 46 | if (this.readValuesFromUrl) { 47 | this.urlFieldOverrides = { 48 | ...this.extractFields(currentPageReference?.state?.defaultFieldValues), 49 | ...this.extractFields(currentPageReference?.state?.c__fieldOverrides) 50 | }; 51 | } 52 | } 53 | 54 | // Extract CSV mapping of field overrides per Salesforce's standard format: 55 | // Format: c__fieldOverrides=Status=Assigned,Subject=Multiword+Subject 56 | extractFields(data) { 57 | let fields = {}; 58 | if (data) { 59 | data 60 | .split(",") 61 | .map((fieldOverride) => fieldOverride.split("=")) 62 | .filter((fieldComponents) => fieldComponents.length === 2) 63 | .forEach((fieldComponents) => { 64 | fields[fieldComponents[0]] = this.parseFieldComponent(decodeURIComponent(fieldComponents[1])); 65 | }); 66 | } 67 | return fields; 68 | } 69 | 70 | // Parse fieldComponent to identify boolean fields 71 | parseFieldComponent(val) { 72 | if (typeof val === "string" && val.toLowerCase() === "true") 73 | return true; 74 | else if (typeof val === "string" && val.toLowerCase() === "false") 75 | return false; 76 | return val; 77 | } 78 | 79 | // Handle message from Dynamic Forms Message Channel 80 | handleMessage(message) { 81 | if (message.eventType === this.EVENT_TYPE.CHECK_OVERRIDES) { 82 | this.broadcastFieldOverrides(); 83 | } else if (message.eventType === this.EVENT_TYPE.UPDATE) { 84 | if (this.getPredefinedValue(message.fieldApiName) !== message.newValue) { 85 | this.fieldsThatHaveChanged.add(message.fieldApiName); 86 | } 87 | } else if (message.eventType === this.EVENT_TYPE.CANCEL) { 88 | this.fieldsThatHaveChanged.clear(); 89 | } else if (message.eventType === this.EVENT_TYPE.SAVE_END) { 90 | this.fieldsThatHaveChanged.clear(); 91 | } 92 | } 93 | 94 | // Get predefined value for this field, if it exists 95 | getPredefinedValue(fieldName) { 96 | return ( 97 | this.urlFieldOverrides[fieldName] ?? this.predefinedFieldValues[fieldName] 98 | ); 99 | } 100 | 101 | broadcastFieldOverrides() { 102 | // URL Field Overrides trump LWC-level defined values 103 | for (let [field, value] of Object.entries({ 104 | ...this.predefinedFieldValues, 105 | ...this.urlFieldOverrides 106 | })) { 107 | if (!this.fieldsThatHaveChanged.has(field)) { 108 | this.broadcast(this.EVENT_TYPE.UPDATE, { 109 | newValue: value, 110 | fieldApiName: field 111 | }); 112 | } 113 | } 114 | } 115 | 116 | // Connect to Dynamic Forms Message Channel 117 | connectedCallback() { 118 | this.subscribeToMessageChannel(); 119 | } 120 | 121 | // Disconnect from Dynamic Forms Message Channel 122 | disconnectedCallback() { 123 | this.unsubscribeToMessageChannel(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsPredefinedValues/dynamicFormsPredefinedValues.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | true 5 | Dynamic Forms - Predefined Field Values 6 | Add this invisible component to predefine field values directly through configuration or by reading from the URL 7 | 8 | lightning__AppPage 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsRelatedRecord/dynamicFormsRelatedRecord.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 90 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsRelatedRecord/dynamicFormsRelatedRecord.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 { api } from "lwc"; 18 | import DynamicFormsElement from "c/dynamicFormsElement"; 19 | import getChildRecordId from "@salesforce/apex/DynamicFormsController.getChildRecordId"; 20 | import getParentRecordId from "@salesforce/apex/DynamicFormsController.getParentRecordId"; 21 | 22 | const GENERAL_ERROR_MESSAGE = 23 | "An error occured provisioning this component. Please contact a System Administrator."; 24 | const STRATEGY_ERROR = "Error"; 25 | const STRATEGY_HIDDEN = "Hidden"; 26 | const STRATEGY_NONE = "None"; 27 | 28 | export default class DynamicFormsRelatedRecord extends DynamicFormsElement { 29 | @api recordId; 30 | @api objectApiName; 31 | @api relatedRecordId = null; 32 | @api relatedRecordObjectApiName; 33 | @api childRelation; 34 | @api noRecordStrategy = STRATEGY_ERROR; 35 | @api multipleChildrenStrategy = STRATEGY_NONE; 36 | @api parentRelation; 37 | @api fieldApiNames; 38 | @api sectionLabel; 39 | @api sectionIcon; 40 | @api hideSectionLabelOnView = false; 41 | @api hideSectionLabelOnEdit = false; 42 | @api hideEditHighlighting = false; 43 | @api readOnlyMode = false; 44 | @api startInEditMode = false; 45 | @api columns = "1"; 46 | @api boundary = false; 47 | @api labelOverrides = {}; 48 | @api helpTextOverrides = {}; 49 | 50 | errorMessage = null; 51 | hideComponent = false; 52 | 53 | connectedCallback() { 54 | if (!this.group) { 55 | this.errorMessage = GENERAL_ERROR_MESSAGE; 56 | return; 57 | } 58 | if (this.parentRelation) { 59 | this.getParentId(); 60 | } else if (this.childRelation) { 61 | this.getChildId(); 62 | } else { 63 | this.handleEmptyRecord(); 64 | } 65 | } 66 | 67 | getParentId() { 68 | getParentRecordId({ 69 | sObjectType: this.objectApiName, 70 | pathToParentId: this.parentRelation, 71 | recordId: this.recordId 72 | }) 73 | .then((result) => { 74 | this.relatedRecordId = result; 75 | }) 76 | .catch((error) => { 77 | this.errorMessage = error?.body?.message ?? GENERAL_ERROR_MESSAGE; 78 | }) 79 | .finally(() => this.handleEmptyRecord()); 80 | } 81 | 82 | getChildId() { 83 | getChildRecordId({ 84 | sObjectType: this.objectApiName, 85 | childRelation: this.childRelation, 86 | recordId: this.recordId, 87 | multipleChildrenStrategy: this.multipleChildrenStrategy 88 | }) 89 | .then((result) => { 90 | this.relatedRecordId = result; 91 | }) 92 | .catch((error) => { 93 | this.errorMessage = error?.body?.message ?? GENERAL_ERROR_MESSAGE; 94 | }) 95 | .finally(() => this.handleEmptyRecord()); 96 | } 97 | 98 | handleEmptyRecord() { 99 | if (this.relatedRecordId !== null) { 100 | return; 101 | } 102 | if (this.noRecordStrategy === STRATEGY_ERROR) { 103 | this.errorMessage = this.errorMessage || "No record was found."; 104 | } else if (this.noRecordStrategy === STRATEGY_HIDDEN) { 105 | this.hideComponent = true; 106 | } 107 | } 108 | 109 | get relatedRecordGroup() { 110 | // this.group is populated via parent DynamicFormsElement class. 111 | // this new grouping scopes the related record uniquely to this page. 112 | return `${this.group}-${this.relatedRecordId}`; 113 | } 114 | 115 | get shouldRenderSection() { 116 | return !this.errorMessage && this.relatedRecordId; 117 | } 118 | 119 | openRecord() { 120 | this.navigateToRecord(this.relatedRecordId); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsRelatedRecord/dynamicFormsRelatedRecord.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | true 5 | Dynamic Forms - Related Record 6 | This allows for exposing fields from a related record. 7 | 8 | lightning__AppPage 9 | lightning__RecordPage 10 | lightningCommunity__Page 11 | lightningCommunity__Default 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsSaveCancel/dynamicFormsSaveCancel.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | .bump { 18 | position: static; 19 | height: 6rem; 20 | } 21 | 22 | .pinnedFooter { 23 | position: fixed; 24 | z-index: 10; 25 | width: 100%; 26 | background: rgba(236, 235, 234, 0.9); 27 | } 28 | 29 | .utilityBarPadding { 30 | bottom: 40px; 31 | } 32 | 33 | table { 34 | border-spacing: 0px; 35 | table-layout: fixed; 36 | margin-left: auto; 37 | margin-right: auto; 38 | } 39 | 40 | th { 41 | border-bottom: 1px solid rgba(243, 242, 242, 255); 42 | background-color: rgba(243, 242, 242, 255); 43 | word-wrap: break-word; 44 | padding: 5px; 45 | } 46 | 47 | td { 48 | border-bottom: 1px solid rgba(243, 242, 242, 255); 49 | word-wrap: break-word; 50 | padding: 5px; 51 | } 52 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsSaveCancel/dynamicFormsSaveCancel.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 139 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsSaveCancel/dynamicFormsSaveCancel.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | true 5 | Dynamic Forms - Cancel and Save Buttons 6 | This allows for dynamic fields. 7 | 8 | lightning__RecordPage 9 | lightningCommunity__Page 10 | lightningCommunity__Default 11 | 12 | 13 | 14 | 21 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsUtils/displayToast.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 { ShowToastEvent } from "lightning/platformShowToastEvent"; 18 | 19 | /** 20 | * Utility function to display a toast event 21 | * @param component - LWC component - should be passed as "this" 22 | * @param title - Header of the toast popup 23 | * @param message - Body of the toast popup 24 | * @param variant - Variant of the toast popup: [info (default), success, warning, error] 25 | */ 26 | export const displayToast = (component, title, message, variant) => { 27 | component.dispatchEvent( 28 | new ShowToastEvent({ 29 | title: title, 30 | message: message, 31 | variant: variant 32 | }) 33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsUtils/dynamicFormsUtils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | export * from "./displayToast"; 18 | export * from "./lightningConsoleApi"; 19 | export * from "./reduceErrors"; 20 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsUtils/dynamicFormsUtils.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 59.0 4 | Utility methods for other LWC 5 | false 6 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsUtils/lightningConsoleApi.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | /** 18 | * Allows Lightning Console API to be invoked from LWC 19 | * @param String apiName 20 | * @param String methodName 21 | * @param {} methodArgs 22 | * @return Promise lightningConsoleApi Promise 23 | * Source - https://developer.salesforce.com/docs/atlas.en-us.232.0.api_console.meta/api_console/sforce_api_console_lex_getting_started.htm 24 | */ 25 | const lightningConsoleApi = (component, apiName, methodName, methodArgs) => { 26 | return new Promise((resolve, reject) => { 27 | const apiEvent = new CustomEvent("internalapievent", { 28 | bubbles: true, 29 | composed: true, 30 | cancelable: false, 31 | detail: { 32 | category: apiName, 33 | methodName: methodName, 34 | methodArgs: methodArgs, 35 | callback: (err, response) => { 36 | if (err) { 37 | return reject(err); 38 | } 39 | return resolve(response); 40 | } 41 | } 42 | }); 43 | 44 | component.dispatchEvent(apiEvent); 45 | }); 46 | }; 47 | 48 | export const invokeUtilityBarApi = (component, methodName, methodArgs) => { 49 | return lightningConsoleApi( 50 | component, 51 | "utilityBarAPI", 52 | methodName, 53 | methodArgs 54 | ); 55 | }; 56 | 57 | export const invokeWorkspaceApi = (component, methodName, methodArgs) => { 58 | return lightningConsoleApi(component, "workspaceAPI", methodName, methodArgs); 59 | }; 60 | -------------------------------------------------------------------------------- /evolve-forms/main/default/lwc/dynamicFormsUtils/reduceErrors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | /** 18 | * Reduces one or more LDS errors into a string[] of error messages. 19 | * @param {FetchResponse|FetchResponse[]} errors 20 | * @return {String[]} Error messages 21 | * Source - http://shortn/_c5tgNH8l7z 22 | */ 23 | const STRING = "string"; 24 | const reduceErrors = (errors) => { 25 | if (!Array.isArray(errors)) { 26 | errors = [errors]; 27 | } 28 | 29 | return ( 30 | errors 31 | // Remove null/undefined items 32 | .filter((error) => !!error) 33 | // Extract an error message 34 | .map((error) => { 35 | // UI API read errors 36 | if (Array.isArray(error.body)) { 37 | return error.body.map((e) => e.message); 38 | } 39 | // FIELD VALIDATION, FIELD, and trigger.addError 40 | else if ( 41 | error.body && 42 | error.body.enhancedErrorType && 43 | error.body.enhancedErrorType.toLowerCase() === "recorderror" && 44 | error.body.output 45 | ) { 46 | let firstError = ""; 47 | if ( 48 | error.body.output.errors.length && 49 | error.body.output.errors[0].errorCode.includes("_") // one of the many salesforce errors with underscores 50 | ) { 51 | firstError = error.body.output.errors[0].message; 52 | } 53 | if ( 54 | !error.body.output.errors.length && 55 | error.body.output.fieldErrors 56 | ) { 57 | // It's in a really weird format... 58 | firstError = 59 | error.body.output.fieldErrors[ 60 | Object.keys(error.body.output.fieldErrors)[0] 61 | ][0].message; 62 | } 63 | return firstError; 64 | } 65 | // UI API DML, Apex and network errors 66 | else if (error.body && typeof error.body.message === STRING) { 67 | let errorMessage = error.body.message; 68 | if (typeof error.body.stackTrace === STRING) { 69 | errorMessage += `\n${error.body.stackTrace}`; 70 | } 71 | return errorMessage; 72 | } 73 | // PAGE ERRORS 74 | else if ( 75 | error.body && 76 | error.body.pageErrors && 77 | error.body.pageErrors.length 78 | ) { 79 | return error.body.pageErrors[0].message; 80 | } 81 | // JS errors 82 | else if (typeof error.message === STRING) { 83 | return error.message; 84 | } 85 | // Unknown error shape so try HTTP status text 86 | return error.statusText; 87 | }) 88 | // Flatten 89 | .reduce((prev, curr) => prev.concat(curr), []) 90 | // Remove empty strings 91 | .filter((message) => !!message) 92 | ); 93 | }; 94 | 95 | export { reduceErrors }; 96 | -------------------------------------------------------------------------------- /evolve-forms/main/default/messageChannels/DynamicForms.messageChannel-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dynamic Forms Message Channel 4 | true 5 | 6 | fieldApiName 7 | API name of the field whose values have been updated 8 | 9 | 10 | newValue 11 | New value of the updated field 12 | 13 | 14 | eventType 15 | Type of the event to be communicated across the page 16 | 17 | 18 | group 19 | Used to group the field sections and save/cancel buttons together. RecordId is used for record updates; UUId can be used for inserts. 20 | 21 | -------------------------------------------------------------------------------- /evolve-forms/main/default/objects/Conditional_Warning__mdt/Conditional_Warning__mdt.object-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Used to provide a conditional warning before a record is saved when using Evolve dynamic forms. 4 | 5 | Conditional Warnings 6 | Public 7 | 8 | -------------------------------------------------------------------------------- /evolve-forms/main/default/objects/Conditional_Warning__mdt/fields/Flow_API_Name__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Flow_API_Name__c 4 | Enter the API name of a Flow which will be used to evaluate this warning. The flow must have an input variable called "record" of type sObject record and an output variable called "errorMessage" of type string. 5 | false 6 | DeveloperControlled 7 | Enter the API name of a Flow which will be used to evaluate this warning. The flow must have an input variable called "record" of type sObject record and an output variable called "errorMessage" of type string. 8 | 9 | 255 10 | true 11 | Text 12 | false 13 | 14 | -------------------------------------------------------------------------------- /evolve-forms/main/default/objects/Conditional_Warning__mdt/fields/SObject_API_Name__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | SObject_API_Name__c 4 | SObject API Name 5 | false 6 | DeveloperControlled 7 | SObject API Name 8 | 9 | 255 10 | true 11 | Text 12 | false 13 | 14 | -------------------------------------------------------------------------------- /evolve-forms/main/default/objects/Conditional_Warning__mdt/listViews/All_Conditional_Warnings.listView-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | All_Conditional_Warnings 4 | MasterLabel 5 | DeveloperName 6 | SObject_API_Name__c 7 | Flow_API_Name__c 8 | Everything 9 | 10 | 11 | -------------------------------------------------------------------------------- /evolve-forms/main/default/permissionsets/Evolve_Forms.permissionset-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CalculatedField 5 | true 6 | 7 | 8 | CalculatedFieldController 9 | true 10 | 11 | 12 | CalculatedFieldControllerTest 13 | true 14 | 15 | 16 | DynamicFormsController 17 | true 18 | 19 | 20 | DynamicFormsControllerTest 21 | true 22 | 23 | 24 | LightningFormattedAddress 25 | true 26 | 27 | 28 | LightningFormattedDateTime 29 | true 30 | 31 | 32 | LightningFormattedEmail 33 | true 34 | 35 | 36 | LightningFormattedLocation 37 | true 38 | 39 | 40 | LightningFormattedName 41 | true 42 | 43 | 44 | LightningFormattedNumber 45 | true 46 | 47 | 48 | LightningFormattedPhoneNumber 49 | true 50 | 51 | 52 | LightningFormattedText 53 | true 54 | 55 | 56 | LightningFormattedTime 57 | true 58 | 59 | 60 | LightningFormattedUrl 61 | true 62 | 63 | 64 | true 65 | Conditional_Warning__mdt 66 | 67 | Enables access to all of the components in the Evolve forms package 68 | false 69 | 70 | 71 | -------------------------------------------------------------------------------- /evolve-forms/main/default/staticresources/DynamicFormsCSS.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 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 | /** 18 | * Unfortunately, despite marking a label as hidden when using a Salesforce input-field, 19 | * the help-text is still rendered. 20 | * This CSS injection force hides this help text from rendering. 21 | */ 22 | c-dynamic-forms-field-section 23 | div.label-hidden 24 | lightning-input-field 25 | lightning-helptext { 26 | visibility: hidden !important; 27 | display: none; 28 | } 29 | 30 | .toastMessage.forceActionsText { 31 | white-space: pre-line !important; 32 | } 33 | -------------------------------------------------------------------------------- /evolve-forms/main/default/staticresources/DynamicFormsCSS.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public 4 | text/css 5 | External CSS file to override default styling on child LWC elements under Evolve. 6 | 7 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sf-evolve-forms", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "devDependencies": { 8 | "prettier": "3.0.3", 9 | "prettier-plugin-apex": "2.0.1" 10 | } 11 | }, 12 | "node_modules/@hapi/hoek": { 13 | "version": "9.3.0", 14 | "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", 15 | "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", 16 | "dev": true 17 | }, 18 | "node_modules/@hapi/topo": { 19 | "version": "5.1.0", 20 | "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", 21 | "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", 22 | "dev": true, 23 | "dependencies": { 24 | "@hapi/hoek": "^9.0.0" 25 | } 26 | }, 27 | "node_modules/@sideway/address": { 28 | "version": "4.1.5", 29 | "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", 30 | "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", 31 | "dev": true, 32 | "dependencies": { 33 | "@hapi/hoek": "^9.0.0" 34 | } 35 | }, 36 | "node_modules/@sideway/formula": { 37 | "version": "3.0.1", 38 | "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", 39 | "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", 40 | "dev": true 41 | }, 42 | "node_modules/@sideway/pinpoint": { 43 | "version": "2.0.0", 44 | "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", 45 | "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", 46 | "dev": true 47 | }, 48 | "node_modules/asynckit": { 49 | "version": "0.4.0", 50 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 51 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 52 | "dev": true 53 | }, 54 | "node_modules/axios": { 55 | "version": "1.6.7", 56 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", 57 | "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", 58 | "dev": true, 59 | "dependencies": { 60 | "follow-redirects": "^1.15.4", 61 | "form-data": "^4.0.0", 62 | "proxy-from-env": "^1.1.0" 63 | } 64 | }, 65 | "node_modules/combined-stream": { 66 | "version": "1.0.8", 67 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 68 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 69 | "dev": true, 70 | "dependencies": { 71 | "delayed-stream": "~1.0.0" 72 | }, 73 | "engines": { 74 | "node": ">= 0.8" 75 | } 76 | }, 77 | "node_modules/delayed-stream": { 78 | "version": "1.0.0", 79 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 80 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 81 | "dev": true, 82 | "engines": { 83 | "node": ">=0.4.0" 84 | } 85 | }, 86 | "node_modules/detect-newline": { 87 | "version": "3.1.0", 88 | "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", 89 | "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", 90 | "dev": true, 91 | "engines": { 92 | "node": ">=8" 93 | } 94 | }, 95 | "node_modules/follow-redirects": { 96 | "version": "1.15.5", 97 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", 98 | "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", 99 | "dev": true, 100 | "funding": [ 101 | { 102 | "type": "individual", 103 | "url": "https://github.com/sponsors/RubenVerborgh" 104 | } 105 | ], 106 | "engines": { 107 | "node": ">=4.0" 108 | }, 109 | "peerDependenciesMeta": { 110 | "debug": { 111 | "optional": true 112 | } 113 | } 114 | }, 115 | "node_modules/form-data": { 116 | "version": "4.0.0", 117 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 118 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 119 | "dev": true, 120 | "dependencies": { 121 | "asynckit": "^0.4.0", 122 | "combined-stream": "^1.0.8", 123 | "mime-types": "^2.1.12" 124 | }, 125 | "engines": { 126 | "node": ">= 6" 127 | } 128 | }, 129 | "node_modules/jest-docblock": { 130 | "version": "29.7.0", 131 | "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", 132 | "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", 133 | "dev": true, 134 | "dependencies": { 135 | "detect-newline": "^3.0.0" 136 | }, 137 | "engines": { 138 | "node": "^14.15.0 || ^16.10.0 || >=18.0.0" 139 | } 140 | }, 141 | "node_modules/joi": { 142 | "version": "17.12.2", 143 | "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.2.tgz", 144 | "integrity": "sha512-RonXAIzCiHLc8ss3Ibuz45u28GOsWE1UpfDXLbN/9NKbL4tCJf8TWYVKsoYuuh+sAUt7fsSNpA+r2+TBA6Wjmw==", 145 | "dev": true, 146 | "dependencies": { 147 | "@hapi/hoek": "^9.3.0", 148 | "@hapi/topo": "^5.1.0", 149 | "@sideway/address": "^4.1.5", 150 | "@sideway/formula": "^3.0.1", 151 | "@sideway/pinpoint": "^2.0.0" 152 | } 153 | }, 154 | "node_modules/lodash": { 155 | "version": "4.17.21", 156 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 157 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 158 | "dev": true 159 | }, 160 | "node_modules/mime-db": { 161 | "version": "1.52.0", 162 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 163 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 164 | "dev": true, 165 | "engines": { 166 | "node": ">= 0.6" 167 | } 168 | }, 169 | "node_modules/mime-types": { 170 | "version": "2.1.35", 171 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 172 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 173 | "dev": true, 174 | "dependencies": { 175 | "mime-db": "1.52.0" 176 | }, 177 | "engines": { 178 | "node": ">= 0.6" 179 | } 180 | }, 181 | "node_modules/minimist": { 182 | "version": "1.2.8", 183 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 184 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 185 | "dev": true, 186 | "funding": { 187 | "url": "https://github.com/sponsors/ljharb" 188 | } 189 | }, 190 | "node_modules/prettier": { 191 | "version": "3.0.3", 192 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", 193 | "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", 194 | "dev": true, 195 | "bin": { 196 | "prettier": "bin/prettier.cjs" 197 | }, 198 | "engines": { 199 | "node": ">=14" 200 | }, 201 | "funding": { 202 | "url": "https://github.com/prettier/prettier?sponsor=1" 203 | } 204 | }, 205 | "node_modules/prettier-plugin-apex": { 206 | "version": "2.0.1", 207 | "resolved": "https://registry.npmjs.org/prettier-plugin-apex/-/prettier-plugin-apex-2.0.1.tgz", 208 | "integrity": "sha512-S64zate3iXPKiBKHf27YRapIAPPd1wQ/u5IOcWwSHNgoJv15I14HhoUB/WOKXtxFb0ZH5MO1nF8gRGWXLajwlA==", 209 | "dev": true, 210 | "dependencies": { 211 | "jest-docblock": "^29.0.0", 212 | "wait-on": "^7.0.0" 213 | }, 214 | "bin": { 215 | "apex-ast-serializer": "vendor/apex-ast-serializer/bin/apex-ast-serializer", 216 | "apex-ast-serializer-http": "vendor/apex-ast-serializer/bin/apex-ast-serializer-http", 217 | "start-apex-server": "dist/bin/start-apex-server.js", 218 | "stop-apex-server": "dist/bin/stop-apex-server.js" 219 | }, 220 | "engines": { 221 | "node": ">= 18.11.0" 222 | }, 223 | "peerDependencies": { 224 | "prettier": "^3.0.0" 225 | } 226 | }, 227 | "node_modules/proxy-from-env": { 228 | "version": "1.1.0", 229 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 230 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", 231 | "dev": true 232 | }, 233 | "node_modules/rxjs": { 234 | "version": "7.8.1", 235 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", 236 | "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", 237 | "dev": true, 238 | "dependencies": { 239 | "tslib": "^2.1.0" 240 | } 241 | }, 242 | "node_modules/tslib": { 243 | "version": "2.6.2", 244 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", 245 | "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", 246 | "dev": true 247 | }, 248 | "node_modules/wait-on": { 249 | "version": "7.2.0", 250 | "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", 251 | "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", 252 | "dev": true, 253 | "dependencies": { 254 | "axios": "^1.6.1", 255 | "joi": "^17.11.0", 256 | "lodash": "^4.17.21", 257 | "minimist": "^1.2.8", 258 | "rxjs": "^7.8.1" 259 | }, 260 | "bin": { 261 | "wait-on": "bin/wait-on" 262 | }, 263 | "engines": { 264 | "node": ">=12.0.0" 265 | } 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "prettier": "3.0.3", 4 | "prettier-plugin-apex": "2.0.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /sfdx-project.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageDirectories": [ 3 | { 4 | "path": "evolve-forms", 5 | "default": true, 6 | "package": "Evolve Forms", 7 | "versionName": "ver 0.1", 8 | "versionNumber": "0.1.0.NEXT", 9 | "versionDescription": "" 10 | } 11 | ], 12 | "name": "Evolve Forms", 13 | "namespace": "", 14 | "sfdcLoginUrl": "https://login.salesforce.com", 15 | "sourceApiVersion": "59.0", 16 | "packageAliases": { 17 | "Evolve Forms": "0HoDn000000XZtZKAW", 18 | "Evolve Forms@0.1.0-1": "04tDn000000nG2MIAU", 19 | "Evolve Forms@0.1.1-1": "04tDn000000nG7rIAE", 20 | "Evolve Forms@0.1.2": "04tDn000000nGCNIA2" 21 | } 22 | } --------------------------------------------------------------------------------