-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor to use functions instead of weird syntax (#7)
* Refactor to use apis instead of weird syntax * Update dependencies * Update README and add scripts Updated Readme. Added composite api script. Fixed bugs with GetSessionId. Updated old scripts to use the new api * Change DML Api * Update readme * Update readme * Minor syntax update * Update readme * Update packages * Update contact add file script * Update JS button scripts in Account and Contact cmdt * Update readme
- Loading branch information
Showing
38 changed files
with
23,028 additions
and
7,145 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,18 @@ | ||
{ | ||
"trailingComma": "none", | ||
"printWidth": 120, | ||
"overrides": [ | ||
{ | ||
"files": "**/lwc/**/*.html", | ||
"options": { "parser": "lwc" } | ||
}, | ||
{ | ||
"files": "*.{cmp,page,component}", | ||
"options": { "parser": "html","printWidth":120 } | ||
"options": { "parser": "html"} | ||
}, | ||
{ | ||
"files": "*.{cls,trigger,apex}", | ||
"options": { "parser": "apex","printWidth":120 } | ||
"options": { "parser": "apex"} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,64 +1,125 @@ | ||
# Pure JS Buttons in Lightning | ||
|
||
JS buttons are back in Lightning! (For now, at least) And they are even more powerful than JS buttons in classic. Run SOQL and DML statements seamlessly. Make callouts to APIs, including Salesforce APIs using named credentials directly from JavaScript! This would allow you to build buttons that do amazing things, just using JavaScript. Check out the `scripts` folder for examples. Feel free to raise a PR to contribute your own scripts. | ||
JS buttons are back in Lightning! (For now, at least) And they are even more | ||
powerful than JS buttons in classic. Run SOQL and DML statements seamlessly. | ||
Make callouts to APIs, including Salesforce APIs directly from JavaScript! | ||
This would allow you to build buttons that do amazing things, just using | ||
JavaScript. Check out the [scripts](./scripts/jsButton) folder for examples. | ||
Feel free to raise a PR to contribute your own scripts. | ||
|
||
### The Setup | ||
|
||
The button can be made available to users via a quick action powered by the `jsButtonQuickAction` component. The actual JavaScript should be entered into a `JS_Button__mdt` custom metadata record, into the `Script__c` field with the same name as the name of the SObject. The repo contains a couple of samples for Account and Contact. The corollary is that, out of the box, only one button per SObjectType may be supported. Note that the Contact js button intentionally throws an error (by attempting to create a File) to showcase error handling capabilities. | ||
The button can be made available to users via a quick action powered by the | ||
`jsButtonQuickAction` component. The actual JavaScript should be entered into a | ||
`JS_Button__mdt` custom metadata record, into the `Script__c` field with the | ||
same name as the name of the SObject. The repo contains a couple of samples | ||
for `Account` and `Contact`. The corollary is that, out of the box, only one | ||
button per SObjectType may be supported, for quick actions. You can add any | ||
number of buttons on the flexipage, with the underlying JS added using the | ||
flexipage builder. | ||
|
||
### APIs | ||
|
||
The library supports the following apis | ||
|
||
- soql | ||
- dml (dml.insert, dml.update, dml.upsert and dml.del ) // `delete` is a resrved keyword :( | ||
- callout ( used for calling external services through Apex. Named credentials are supported! ) | ||
- sfapi ( used for calling Salesforce APIs from the same org. Requires CORS and | ||
CSP Trusted Sites setup. Details below) | ||
- toast ( show a platform toast message ) | ||
|
||
### The Syntax | ||
|
||
This is the fun part. The syntax is quite permissive, with some restrictions, which I will cover below. I haven't, obviously, explored all possible scenarios and the information may still be incomplete. Please raise an issue if you come across something I haven't covered. | ||
This is the fun part. I haven't, obviously, explored all possible scenarios and | ||
the information may still be incomplete. Please raise an issue if you come | ||
across something I haven't covered. | ||
|
||
* Simple examples (no soql/dml) | ||
- Simple examples (no soql/dml) | ||
|
||
```javascript | ||
alert('hello,world'); | ||
```js | ||
alert("hello,world"); | ||
``` | ||
|
||
```javascript | ||
alert(Array(5).fill(0).map((e,i)=>'Hello, '+i)); | ||
```js | ||
toast( | ||
Array(5) | ||
.fill(0) | ||
.map((e, i) => "Hello, " + i) | ||
); /* `toast` service to show message toasts */ | ||
``` | ||
|
||
* Fetch 10 of the 100 latest Accounts without a Contact and add a Contact to each of them | ||
|
||
```javascript | ||
let accts=|| Select Name,(Select Id from Contacts) from Account order by createddate desc limit 100 ||; | ||
let contacts = accts.filter((a)=>!a.Contacts || a.Contacts.length===0) | ||
.slice(0,10) | ||
.map((a)=>({LastName: a.Name+'-Contact', AccountId: a.Id})); | ||
let contactIds = || insert Contact(contacts) ||; /*Note how the SObjectType has been specified. This is required for insert and upsert*/ | ||
$A.get('e.force:refreshView').fire(); /* $A is supported!*/ | ||
- Fetch 100 of the latest Accounts and for upto 10 of the ones without a Contact, add a Contact | ||
|
||
```js | ||
let accts = await soql( | ||
`Select Name,(Select Id from Contacts) from Account order by createddate desc | ||
limit 100` | ||
); /* Querying child records is supported */ | ||
let contacts = accts | ||
.filter((a) => !a.Contacts || a.Contacts.length === 0) | ||
.slice(0, 10) | ||
.map((a) => ({ LastName: a.Name + "-Contact", AccountId: a.Id })); | ||
let contactIds = await dml.insert( | ||
contacts, | ||
"Contact" | ||
); /*Note how the SObjectType has been specified. This is required for insert and upsert*/ | ||
$A.get("e.force:refreshView").fire(); /* $A is supported!*/ | ||
``` | ||
|
||
* Act in the context of the current record | ||
- Act in the context of the current record | ||
|
||
```javascript | ||
let acct = || Select NumberOfEmployees from Account where Id='${recordId}' ||; | ||
```js | ||
let acct = await soql( | ||
`Select NumberOfEmployees from Account where Id='${recordId}'` | ||
); /* Note the use of template literal syntax to resolve | ||
variable values in the query */ | ||
acct[0].NumberOfEmployees = (acct[0].NumberOfEmployees || 0) + 10; | ||
let acctId = || update acct ||; | ||
acct = || Select NumberOfEmployees from Account where Id='${acctId}' ||; | ||
alert(acct[0].NumberOfEmployees); | ||
$A.get('e.force:refreshView').fire(); | ||
let acctId = await dml.update(acct); | ||
acct = await soql(`Select NumberOfEmployees from Account where Id='${acctId}'`); | ||
toast(acct[0].NumberOfEmployees, "success"); | ||
$A.get("e.force:refreshView").fire(); | ||
``` | ||
|
||
- Upload files to ContentVersion(ContentDocument) records | ||
|
||
```js | ||
let fileContent = btoa("Hello World"); | ||
/* convert your file content to base64 data before uploading */ | ||
let cv = { | ||
VersionData: fileContent, | ||
Title: "My Awesome File", | ||
PathOnClient: "MyFile.txt" | ||
}; | ||
let cvId = await dml.insert(cv, "ContentVersion"); | ||
``` | ||
|
||
### About the Syntax | ||
|
||
* Note how the syntax is linear for SOQL and DML. Coupled with JavaScript's support for manipulating arrays, this makes it easier to manipulate data, even compared to Apex in several instances. | ||
* SOQL and DML statements must be enclosed in `||`. Semi-colon can be inside or outside the `||` | ||
* Upsert and Update statements must be qualified with the SObjectType thus `|| insert Account(accts) ||;` | ||
* SOQL statements are parsed using template literals. Any arguments should follow the appropriate syntax `${argument}` | ||
* SOQL and DML statements may not be wrapped in a function. | ||
* All statements must be strictly terminated by a semicolon. | ||
- Note how the syntax is linear for SOQL and DML. Coupled with JavaScript's | ||
support for manipulating arrays, this makes it easier to manipulate data, | ||
even compared to Apex in several instances. | ||
- `dml.insert` and `dml.upsert` expect the SObjectType as the second argument. | ||
Thus `dml.insert(acct,"Account")` | ||
- Statements with contextual arguments such as `recordId` | ||
are best expressed using [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals). | ||
- All statements must be strictly terminated by a semicolon. | ||
|
||
### Known Limitations | ||
|
||
* Support for delete has been intentionally withheld. | ||
* Single-line comments are not supported. | ||
* Haven't tested DML with date, datetime, boolean, geolocation and other compound fields. I will update this section as I do so. | ||
* SOQL and DML statements should be enclosed in async functions, if they are required to be contained in functions. The program automatically adds `await` to SOQL and DML statements | ||
* DML on Files, Attachments, Documents, etc. is not supported | ||
- Single-line comments are not supported. | ||
- Haven't tested DML with date, datetime, boolean, geolocation and other | ||
compound fields. I will update this section as I do so. | ||
- To insert `ContentVersion` make sure to set `VersionData` to base64 data. | ||
Refer to the example [here](./scripts/jsButton/createContactFiles.js) for details. | ||
|
||
### Using Salesforce (and other) APIs in your script | ||
|
||
You can use any of Salesforce's APIs (REST, Tooling, Metadata) by setting up a named credential for your own Salesforce instance. This allows you to write scripts for admins to perform tasks like [deleting inactive versions of flows](scripts/jsButton/deleteInactiveFlowVersions.js), or [creating new JS Buttons](scripts/jsButton/createNewJSButton.js)! You can also use named credentials to interact with other APIs as well, of course. Although, for Public APIs, you can just use `fetch` directly. The Salesforce named credential set up would need to have the following scopes (api refresh_token offline_access web). You would need to set up your own Connected App and a Salesforce Auth. Provider that uses this connected app. | ||
To use Salesforce APIs from your org, using the `sfapi` method, take the following steps: | ||
|
||
- Add your lightning domain (ends with `lightning.force.com`) to the `CORS` list under `Setup`. | ||
- Add your classic domain to `CSP Trusted Sites` list under `Setup`. | ||
|
||
This allows you to write scripts for admins to perform tasks like [deleting inactive versions of flows](./scripts/jsButton/deleteInactiveFlowVersions.js) or [use composite api](./scripts/jsButton/compositeApiExample.js) | ||
for creating parent and child records. | ||
To access protected APIs such as those from other Salesforce orgs, use a named credential and the `callout` api. For Public APIs, you can use `fetch` directly. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/************************************************************ | ||
*** Copyright (c) Vertex Computer Systems Inc. All rights reserved. | ||
*** @author Suraj Pillai | ||
*** @group Controller | ||
*** @date 01/2021 | ||
*** @description Get API-ready session id of the current users | ||
*** | ||
**/ | ||
public with sharing class GetSessionIdController { | ||
/**** | ||
** @description Returns the current user's session id that may be used for calling Salesforce APIs | ||
** @return the current user's api-ready session id | ||
**/ | ||
@AuraEnabled(cacheable=true) | ||
public static String getSessionId() { | ||
String content = Page.GetSessionId.getContent().toString(); | ||
return getSessionIdFromPage(content); | ||
} | ||
|
||
private static String getSessionIdFromPage(String content) { | ||
Integer s = content.indexOf('Start_Of_Session_Id') + 'Start_Of_Session_Id'.length(), | ||
e = content.indexOf('End_Of_Session_Id'); | ||
return content.substring(s, e); | ||
} | ||
|
||
@AuraEnabled(cacheable=true) | ||
public static String getRestAPIBaseUrl() { | ||
return URL.getSalesforceBaseUrl().toExternalForm() + '/services/data/v51.0'; | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
force-app/main/default/classes/GetSessionIdController.cls-meta.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata"> | ||
<apiVersion>49.0</apiVersion> | ||
<status>Active</status> | ||
</ApexClass> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.