Custom Embedded JavaScript Functions: Difference between revisions
No edit summary |
No edit summary |
||
(6 intermediate revisions by the same user not shown) | |||
Line 6: | Line 6: | ||
* To obtain a photo of the currently viewing user using their integrated camera and then embed it in the document | * To obtain a photo of the currently viewing user using their integrated camera and then embed it in the document | ||
* To connect via REST API to an external service to fetch additional data for the contextual record | * To connect via REST API to an external service to fetch additional data for the [[Document Contexts|contextual record]] | ||
* To import an external library and display custom elements/components that are rendered by the external library (say a table, chart, image slider etc) | * To import an external library and display custom elements/components that are rendered by the external library (say a table, chart, image slider etc) | ||
* To patch-fix a bug in 3B Docs | * To patch-fix a bug in 3B Docs | ||
Line 26: | Line 26: | ||
== Syntax == | == Syntax == | ||
Just like [[Custom Value Formatting|custom value formatters]], when you create a Function Expression, you are basically creating a JavaScript function. This JavaScript function has access to the global context and thus to the user’s navigator object, the application variables & collections and much more. | Just like [[Custom Value Formatting|custom value formatters]], when you create a Function Expression, you are basically creating a JavaScript function. This JavaScript function has access to the global context and thus to the [[Current User Navigator Object|user’s navigator object]], the application variables & collections and much more. | ||
The difference between [[Custom Value Formatting|custom value formatters]] and Function Expressions lies in the fact that [[Custom Value Formatting|custom value formatters]] can only be applied to a specific [[Merge Fields and Tags|merge tag]], whereas [[Custom Embedded JavaScript Functions|Function Expressions]] form their own independent [[Merge Fields and Tags|merge tags]]. | The difference between [[Custom Value Formatting|custom value formatters]] and Function Expressions lies in the fact that [[Custom Value Formatting|custom value formatters]] can only be applied to a specific [[Merge Fields and Tags|merge tag]], whereas [[Custom Embedded JavaScript Functions|Function Expressions]] form their own independent [[Merge Fields and Tags|merge tags]]. | ||
You must always return a string value – this return value will be your “transformation” of the [[Merge Fields and Tags|merge tag]]. | Furthermore, here are a few considerations: | ||
* You must always return a string value – this return value will be your “transformation” of the [[Merge Fields and Tags|merge tag]]. | |||
* You have access to "element", "documentData", "contextObject" and the function "callout" | |||
<syntaxhighlight lang="javascript" line="1"> | |||
async (args) => { | |||
//Wrapper element which will be inserted in the document at requested index | |||
let element = args[0]; | |||
//An object that is populated with all the data loaded from the server | |||
let documentData = args[1]; | |||
//The contextual record where the Function Expression is placed in | |||
let contextObject = args[2]; | |||
//Server communication class | |||
let callout = args[3]; | |||
//Always return a string that will be inserted in the document | |||
return ''; | |||
} | |||
</syntaxhighlight>''Note: The "contextObject" is the document's "Core Object" record. If the function expression is embedded in a child repeatable context, then the contextObject becomes the child record.'' | |||
== Callouts == | |||
Function Expressions can perform callouts to APEX via the callout() function that is passed to a function expression. This function is a wrapper around an authenticated AJAX call. Follow the instructions below to enable your function expressions to communicate with APEX. | |||
=== Apex === | |||
Firstly, you need to set up a new class that implements both b3d.GlobalRemotingInterface and Callable (native Salesforce class). | |||
Secondly, you need to implement a global method "getRemotingData()" which will get a JSON string of a JS Object with params. The important parameter is "endp" which will contain the name of the APEX method requested by the function expression.<syntaxhighlight lang="java" line="1"> | |||
global with sharing class ApexClassName implements b3d.GlobalRemotingInterface, Callable { | |||
//Required constructor | |||
global ApexClassName() { | |||
} | |||
//Required signature by b3d.GlobalRemotingInterface | |||
global String getRemotingData(String params) { | |||
try { | |||
Map<String, Object> requestObj = (Map<String, Object>)JSON.deserializeUntyped(params); | |||
return JSON.serialize(ApexClassName.call((String)requestObj.get('endp'), (Map<String, Object>)JSON.deserializeUntyped(params))); | |||
} catch(Exception e){ | |||
String generalError = String.format( | |||
Label.CRM_GeneralApexFailureRouter, | |||
new Object[]{ | |||
e.getMessage(), | |||
e.getLineNumber(), | |||
e.getStackTraceString() | |||
} | |||
); | |||
...error handling | |||
} | |||
} | |||
private static Object call(String action, Map<String, Object> args) { | |||
String recordId = (String)args.get('recordId'); //Template ID - always present | |||
switch on action { | |||
when 'classMethodName' { | |||
return classMethodName((String)args.get('param1')); | |||
} | |||
when else { | |||
return new b3d.ErrorResponse(String.format( | |||
Label.CRM_BadRemoteRequest, | |||
new Object[]{ | |||
ApexClassName.class.getName() | |||
} | |||
)); | |||
} | |||
} | |||
} | |||
protected static Object classMethodName(String param1){ | |||
//...Do something | |||
return new b3d.SuccessResponse([param1]); | |||
} | |||
} | |||
</syntaxhighlight>Please try to always return b3d.SuccessResponse or b3d.ErrorResponse within your APEX implementation. This will make it easier for our team to support your custom implementations in the future. | |||
=== Javascript === | |||
Now that you have the APEX class created successfully, you are ready to call it from within a function expression. Here is an example of how to call our sample class<syntaxhighlight lang="javascript"> | |||
const response = await callout({ | |||
endp: "classMethodName", | |||
...// additional parameters | |||
}, | |||
false,//Use REST or AJAX | |||
"ApexClassName" | |||
); | |||
</syntaxhighlight> | |||
== Examples == | == Examples == | ||
Line 37: | Line 123: | ||
=== Return Current Date === | === Return Current Date === | ||
This example is useful for getting a dynamically generated timestamp of the current date relative to when the document is opened in dd/mm/yyyy format (don’t worry, you can still apply [[Custom Value Formatting|custom value formatters]] and transform the date to a US date!)<syntaxhighlight lang="javascript" line="1"> | This example is useful for getting a dynamically generated timestamp of the current date relative to when the document is opened in dd/mm/yyyy format (don’t worry, you can still apply [[Custom Value Formatting|custom value formatters]] and transform the date to a US date!)<syntaxhighlight lang="javascript" line="1"> | ||
//Get current date in UK format | |||
() => new Date().toLocaleDateString(‘en-GB’); | () => new Date().toLocaleDateString(‘en-GB’); | ||
</syntaxhighlight><syntaxhighlight lang="javascript"> | |||
//Get current date, formatted based on user's browser locale | |||
() =>{ | |||
const currentDate = new Date(); | |||
return currentDate.toLocaleDateString(navigator.language); | |||
} | |||
</syntaxhighlight> | |||
=== Embed Charts === | |||
This example demonstrates how we can embed highcharts into a template. This example uses static data, but you can route with data from the template.<syntaxhighlight lang="javascript" line="1" start="1"> | |||
async (args) => { | |||
let element = args[0]; | |||
let documentData = args[1]; | |||
let contextObject = args[2]; | |||
let callout = args[3]; | |||
const reportData = await callout({ | |||
endp: 'getReportData' | |||
},'FunctionExpressions'); | |||
var script = document.createElement('script'); | |||
script.src = 'https://code.highcharts.com/highcharts.js'; | |||
document.head.appendChild(script); | |||
let customId = Math.random().toString(36).slice(2, 7); | |||
let chartWrapper = document.createElement('div'); | |||
chartWrapper.id = customId; | |||
element.after(chartWrapper); | |||
script.onload = function () { | |||
//Implement highcharts loading | |||
element.appendChild(...); | |||
}; | |||
//Return an empty string | |||
return ''; | |||
} | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 66: | Line 189: | ||
[[Category:3B Docs Features]] | [[Category:3B Docs Features]] | ||
[[Category:3B Docs]] |
Latest revision as of 06:40, 7 June 2023
Intro
3B Docs allows you to write JavaScript code snippets that can be embedded on a document to extend existing functionality and/or create new functionality.
Usage
Most often, a Function Expression will be used to return strings like timestamps, user identifiable information (such as IP address, location, etc) and for locale, timezone and language reasons. Here are some other examples:
- To obtain a photo of the currently viewing user using their integrated camera and then embed it in the document
- To connect via REST API to an external service to fetch additional data for the contextual record
- To import an external library and display custom elements/components that are rendered by the external library (say a table, chart, image slider etc)
- To patch-fix a bug in 3B Docs
How To
In order to add custom JavaScript function, you need to create a Function Expression record, which you can access from the 3B Docs Application (or simply search for “Function Expressions” in the app launcher drop down).
When you have navigated to the object list view, you can click on the “New” button which will display the new record form. What is important here is that you give the Function Expression a unique name that has no spaces or special characters. Don’t worry about enforcing this rule yourself, we have built a salesforce validation rule to make sure that the name of the Value Formatter is compliant with 3B Docs.
To use the function expression in your template, navigate to the “Functions” icon within the Template builder.
You will see a drop-down of all the available Function Expressions that you can use within the Template.
When you select a specific Function Expression, it will be copied to your clipboard and you can simply paste it anywhere inside the Template document via CTRL + V / CMD + V (on Mac) command.
When you paste a function expression, it will look something like this: #{Functions.CurrentDate}
Function Expressions are treated just like merge fields, so you will have access to the custom value formatters that may be available to modify the value returned by the Function Expression. So, the following Function Expression is completely valid: #{Functions.CurrentDate> EU_Date_Format}
Syntax
Just like custom value formatters, when you create a Function Expression, you are basically creating a JavaScript function. This JavaScript function has access to the global context and thus to the user’s navigator object, the application variables & collections and much more.
The difference between custom value formatters and Function Expressions lies in the fact that custom value formatters can only be applied to a specific merge tag, whereas Function Expressions form their own independent merge tags.
Furthermore, here are a few considerations:
- You must always return a string value – this return value will be your “transformation” of the merge tag.
- You have access to "element", "documentData", "contextObject" and the function "callout"
async (args) => {
//Wrapper element which will be inserted in the document at requested index
let element = args[0];
//An object that is populated with all the data loaded from the server
let documentData = args[1];
//The contextual record where the Function Expression is placed in
let contextObject = args[2];
//Server communication class
let callout = args[3];
//Always return a string that will be inserted in the document
return '';
}
Note: The "contextObject" is the document's "Core Object" record. If the function expression is embedded in a child repeatable context, then the contextObject becomes the child record.
Callouts
Function Expressions can perform callouts to APEX via the callout() function that is passed to a function expression. This function is a wrapper around an authenticated AJAX call. Follow the instructions below to enable your function expressions to communicate with APEX.
Apex
Firstly, you need to set up a new class that implements both b3d.GlobalRemotingInterface and Callable (native Salesforce class).
Secondly, you need to implement a global method "getRemotingData()" which will get a JSON string of a JS Object with params. The important parameter is "endp" which will contain the name of the APEX method requested by the function expression.
global with sharing class ApexClassName implements b3d.GlobalRemotingInterface, Callable {
//Required constructor
global ApexClassName() {
}
//Required signature by b3d.GlobalRemotingInterface
global String getRemotingData(String params) {
try {
Map<String, Object> requestObj = (Map<String, Object>)JSON.deserializeUntyped(params);
return JSON.serialize(ApexClassName.call((String)requestObj.get('endp'), (Map<String, Object>)JSON.deserializeUntyped(params)));
} catch(Exception e){
String generalError = String.format(
Label.CRM_GeneralApexFailureRouter,
new Object[]{
e.getMessage(),
e.getLineNumber(),
e.getStackTraceString()
}
);
...error handling
}
}
private static Object call(String action, Map<String, Object> args) {
String recordId = (String)args.get('recordId'); //Template ID - always present
switch on action {
when 'classMethodName' {
return classMethodName((String)args.get('param1'));
}
when else {
return new b3d.ErrorResponse(String.format(
Label.CRM_BadRemoteRequest,
new Object[]{
ApexClassName.class.getName()
}
));
}
}
}
protected static Object classMethodName(String param1){
//...Do something
return new b3d.SuccessResponse([param1]);
}
}
Please try to always return b3d.SuccessResponse or b3d.ErrorResponse within your APEX implementation. This will make it easier for our team to support your custom implementations in the future.
Javascript
Now that you have the APEX class created successfully, you are ready to call it from within a function expression. Here is an example of how to call our sample class
const response = await callout({
endp: "classMethodName",
...// additional parameters
},
false,//Use REST or AJAX
"ApexClassName"
);
Examples
Here are some examples to get you started with the basic syntax used by 3B Docs for Function Expressions:
Return Current Date
This example is useful for getting a dynamically generated timestamp of the current date relative to when the document is opened in dd/mm/yyyy format (don’t worry, you can still apply custom value formatters and transform the date to a US date!)
//Get current date in UK format
() => new Date().toLocaleDateString(‘en-GB’);
//Get current date, formatted based on user's browser locale
() =>{
const currentDate = new Date();
return currentDate.toLocaleDateString(navigator.language);
}
Embed Charts
This example demonstrates how we can embed highcharts into a template. This example uses static data, but you can route with data from the template.
async (args) => {
let element = args[0];
let documentData = args[1];
let contextObject = args[2];
let callout = args[3];
const reportData = await callout({
endp: 'getReportData'
},'FunctionExpressions');
var script = document.createElement('script');
script.src = 'https://code.highcharts.com/highcharts.js';
document.head.appendChild(script);
let customId = Math.random().toString(36).slice(2, 7);
let chartWrapper = document.createElement('div');
chartWrapper.id = customId;
element.after(chartWrapper);
script.onload = function () {
//Implement highcharts loading
element.appendChild(...);
};
//Return an empty string
return '';
}
Tips & Tricks
Here are some tips and tricks for using Function Expressions:
Block Document Renders in Certain Locales
You can throw a JavaScript error if you deem the currently viewing user’s locale as one that you do not support/allow.
Get Access to the Viewing User’s Hardware
You can request access to hardware such as GPS, Camera, Accelerometer, Barometer etc. and log the return inside the document.
API Callouts
You can use Function Expressions to do callouts, fetch data externally, load additional resources, use JavaScript Libraries such as FullCalendar, Chart.js, Datatables and many more..
Here is a sample of how to initialize FullCalendar (you may need extra code in order to import JS and CSS):
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth'
});
calendar.render();
});
User Logging, Tracking and Fingerprinting
You can use such technologies as Fingerprint to user tracking capabilities to your documents