Skip to content

Commit e06f9a9

Browse files
authored
Merge pull request #2 from surajp/move-to-lwc
Move to lwc
2 parents 61930bd + ba1a9b0 commit e06f9a9

24 files changed

+447
-31
lines changed

.prettierrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
{
99
"files": "*.{cmp,page,component}",
1010
"options": { "parser": "html","printWidth":120 }
11+
},
12+
{
13+
"files": "*.{cls,trigger,apex}",
14+
"options": { "parser": "apex","printWidth":120 }
1115
}
1216
]
1317
}

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Pure JS Buttons in Lightning
22

3-
JS buttons are back in Lightning! (For now, at least) And they are even more powerful than JS buttons in classic, in some respects. SOQL and DML statements supported!
3+
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.
44

55
### The Setup
66

@@ -24,7 +24,9 @@ alert(Array(5).fill(0).map((e,i)=>'Hello, '+i));
2424

2525
```javascript
2626
let accts=|| Select Name,(Select Id from Contacts) from Account order by createddate desc limit 100 ||;
27-
let contacts = accts.filter((a)=>!a.Contacts || a.Contacts.length===0).slice(0,10).map((a)=>({LastName: a.Name+'-Contact', AccountId: a.Id}));
27+
let contacts = accts.filter((a)=>!a.Contacts || a.Contacts.length===0)
28+
.slice(0,10)
29+
.map((a)=>({LastName: a.Name+'-Contact', AccountId: a.Id}));
2830
let contactIds = || insert Contact(contacts) ||; /*Note how the SObjectType has been specified. This is required for insert and upsert*/
2931
$A.get('e.force:refreshView').fire(); /* $A is supported!*/
3032
```
@@ -47,16 +49,16 @@ $A.get('e.force:refreshView').fire();
4749
* Upsert and Update statements must be qualified with the SObjectType thus `|| insert Account(accts) ||;`
4850
* SOQL statements are parsed using template literals. Any arguments should follow the appropriate syntax `${argument}`
4951
* SOQL and DML statements may not be wrapped in a function.
52+
* All statements must be strictly terminated by a semicolon.
5053

5154
### Known Limitations
5255

5356
* Support for delete has been intentionally withheld.
5457
* Single-line comments are not supported.
5558
* Haven't tested DML with date, datetime, boolean, geolocation and other compound fields. I will update this section as I do so.
56-
* Explicit use of async/await, Promises and Generators is not supported, atm.
59+
* 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
5760
* DML on Files, Attachments, Documents, etc. is not supported
5861

59-
### For Developers: Extending to more than one Button per SObjectType
60-
61-
If you need more than one button on an SObjectType, you may create a lightning component quickAction with the name of the custom metadata record containing your JS passed in to the `jsButton` child component. You will also need to implement an `init` method to invoke the controller method in `jsButton`. Refer to the `jsButtonQuickAction` component for implementation details
62+
### Using Salesforce (and other) APIs in your script
6263

64+
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.
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
<aura:component implements="force:lightningQuickActionWithoutHeader,force:hasRecordId,force:hasSObjectName">
2-
<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
3-
<c:jsButton aura:id="jsbutton" recordId="{!v.recordId}" cmdtName="{!v.sObjectName}"></c:jsButton>
2+
<c:jsButtonLwc
3+
aura:id="jsbutton"
4+
recordId="{!v.recordId}"
5+
cmdtName="{!v.sObjectName}"
6+
oninitcomplete="{!c.doInit}"
7+
></c:jsButtonLwc>
48
</aura:component>

force-app/main/default/aura/jsButtonQuickAction/jsButtonQuickActionController.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
({
2-
doInit: function (component, event, helper) {
2+
doInit: function (component) {
33
component
44
.find("jsbutton")
55
.invoke()
66
.then(
7-
$A.getCallback(() => {
7+
$A.getCallback((resp) => {
8+
console.log('>> resp '+JSON.stringify(resp));
89
$A.get("e.force:closeQuickAction").fire();
910
})
1011
)
1112
.catch(
1213
$A.getCallback((err) => {
13-
alert("An error occurred " + err);
1414
$A.get("e.force:closeQuickAction").fire();
1515
})
1616
);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
** description: Controller for making api calls and sending the response back
3+
**/
4+
5+
public with sharing class APICallController {
6+
@AuraEnabled
7+
public static HttpResponseWrapper makeApiCall(
8+
String endPoint,
9+
String method,
10+
String bodyStr,
11+
Map<String, String> headers
12+
) {
13+
HttpRequest req = new HttpRequest();
14+
req.setEndpoint(endPoint);
15+
req.setMethod(method);
16+
if (method != 'GET') {
17+
req.setBody(bodyStr);
18+
}
19+
if (headers != null) {
20+
for (String key : headers.keySet()) {
21+
req.setHeader(key, headers.get(key));
22+
}
23+
}
24+
HttpResponse resp = new Http().send(req);
25+
Map<String, String> respHeaders = new Map<String, String>();
26+
for (String key : resp.getHeaderKeys()) {
27+
respHeaders.put(key, String.valueOf(resp.getHeader(key)));
28+
}
29+
return new HttpResponseWrapper(resp.getBody(), resp.getStatusCode(), respHeaders);
30+
}
31+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<apiVersion>48.0</apiVersion>
4+
<status>Active</status>
5+
</ApexClass>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
@isTest
2+
public with sharing class APICallControllerTest {
3+
@isTest
4+
public static void testAPICall() {
5+
Test.setMock(HttpCalloutMock.class, new APICallMock());
6+
HttpResponseWrapper resp = APICallController.makeApiCall(
7+
'https://api.example.com',
8+
'POST',
9+
'{"message":"sample_request"}',
10+
new Map<String, String>{ 'Accept' => 'application/json', 'Content-Type' => 'application/json' }
11+
);
12+
system.assertEquals('{"message": "sample response"}', resp.body, 'Unexpected Response');
13+
system.assertEquals(200, resp.statusCode, 'Incorrect value for status code');
14+
system.assertEquals(2, resp.headers.size(), 'Mismatch in the number of response headers expected');
15+
system.assertEquals('sample_value1', resp.headers.get('custom_header1'), 'Incorrect value for first header');
16+
system.assertEquals('sample_value2', resp.headers.get('custom_header2'), 'Incorrect value for second header');
17+
}
18+
19+
class APICallMock implements HttpCalloutMock {
20+
public HttpResponse respond(HttpRequest req) {
21+
HttpResponse resp = new HttpResponse();
22+
if (
23+
req.getBody() == '{"message":"sample_request"}' &&
24+
req.getHeader('Accept') == 'application/json' &&
25+
req.getHeader('Content-Type') == 'application/json'
26+
) {
27+
resp.setBody('{"message": "sample response"}');
28+
resp.setHeader('custom_header1', 'sample_value1');
29+
resp.setHeader('custom_header2', 'sample_value2');
30+
resp.setStatusCode(200);
31+
} else {
32+
resp.setStatusCode(400);
33+
resp.setBody('{"message":"Bad Request"}');
34+
}
35+
return resp;
36+
}
37+
}
38+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<apiVersion>48.0</apiVersion>
4+
<status>Active</status>
5+
</ApexClass>

force-app/main/default/classes/DynamicSOQLDMLControllerTest.cls

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ public with sharing class DynamicSOQLDMLControllerTest {
66
insert a;
77
a.Phone = '432424';
88
Account[] recordsToUpdate = new List<Account>{ a };
9-
DynamicSOQLDMLController.executeDml('update', recordsToUpdate, null, null);
9+
DynamicSOQLDMLController.executeDml('update', JSON.serialize(recordsToUpdate), 'Account');
1010
a = [SELECT Phone FROM Account WHERE Id = :a.Id];
1111
System.assertEquals('432424', a.Phone);
1212
}
@@ -16,11 +16,8 @@ public with sharing class DynamicSOQLDMLControllerTest {
1616
// we won't test fetching cmdt
1717
DynamicSOQLDMLController.getJSFromCmdt('Account');
1818
String acctString = '[{"attributes":{"type":"Account"},"Name":"Test Account"}]';
19-
DynamicSOQLDMLController.executeDml('insert', null, acctString, 'Account');
20-
System.assertEquals(
21-
1,
22-
[SELECT ID FROM Account WHERE Name = 'Test Account'].size()
23-
);
19+
DynamicSOQLDMLController.executeDml('insert', acctString, 'Account');
20+
System.assertEquals(1, [SELECT ID FROM Account WHERE Name = 'Test Account'].size());
2421
}
2522

2623
@isTest
@@ -34,7 +31,7 @@ public with sharing class DynamicSOQLDMLControllerTest {
3431
accountsToUpdate.add(a1);
3532

3633
String acctString = JSON.serialize(accountsToUpdate);
37-
DynamicSOQLDMLController.executeDml('upsert', null, acctString, 'Account');
34+
DynamicSOQLDMLController.executeDml('upsert', acctString, 'Account');
3835
System.assertEquals(2, [SELECT ID FROM Account].size());
3936
a = [SELECT Phone FROM Account WHERE Id = :a.Id];
4037
System.assertEquals('432343', a.Phone);
@@ -44,9 +41,7 @@ public with sharing class DynamicSOQLDMLControllerTest {
4441
public static void testSoql() {
4542
Account a = new Account(Name = 'Test Account');
4643
insert a;
47-
Account[] acctsResult = DynamicSOQLDMLController.executeSoqlQuery(
48-
'Select Name from Account'
49-
);
44+
Account[] acctsResult = DynamicSOQLDMLController.executeSoqlQuery('Select Name from Account');
5045
System.assertEquals(1, acctsResult.size());
5146
System.assertEquals('Test Account', acctsResult[0].Name);
5247
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
public with sharing class HttpResponseWrapper {
2+
@AuraEnabled
3+
public String body;
4+
@AuraEnabled
5+
public Integer statusCode;
6+
@AuraEnabled
7+
public Map<String, String> headers;
8+
9+
public HttpResponseWrapper(String body, Integer statusCode, Map<String, String> headers) {
10+
this.body = body;
11+
this.statusCode = statusCode;
12+
this.headers = headers;
13+
}
14+
}

0 commit comments

Comments
 (0)