Custom Embedded JavaScript Functions: Difference between revisions

From 3B Knowledge
Jump to navigation Jump to search
(Created page with "== 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 integra...")
 
No edit summary
 
(8 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 (TODO: Add reference to contexts & handling)
* 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 13: Line 13:
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).
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.  
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 [[Custom Value Formatting|Value Formatter]] is compliant with 3B Docs.  


To use the function expression in your template, navigate to the “Functions” icon within the Template builder.
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 Tempalte.
You will see a drop-down of all the available [[Custom Embedded JavaScript Functions|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 Tempalte document via CTRL + V / CMD + V (on Mac) command.
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}
When you paste a function expression, it will look something like this: #{Functions.CurrentDate}


Function expressions are treated just like merge fields (TODO: Add reference to merge fields), so you will have access to the value formatters (TODO: Add reference to value formatters) that may be available to modify the value returned by the Function Expression. So, the following Function Expression is completely valid: <code>#{Functions.CurrentDate> EU_Date_Format}</code>  
[[Custom Embedded JavaScript Functions|Function Expressions]] are treated just like [[Merge Fields and Tags|merge fields]], so you will have access to the [[Custom Value Formatting|custom value formatters]] that may be available to modify the value returned by the Function Expression. So, the following Function Expression is completely valid: <code>#{Functions.CurrentDate> EU_Date_Format}</code>  


== Syntax ==
== Syntax ==
Just like Value Formatters (TODO: Reference to 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 Value Formatters and Function Expressions lies in the fact that Value Formatters can only be applied to a specific merge tag (TODO: add reference to merge tags), whereas Function Expressions form their own independent 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 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 ==
Here are some examples to get you started with the basic syntax used by 3B Docs for function expressions:  
Here are some examples to get you started with the basic syntax used by 3B Docs for Function Expressions:  


=== 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 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 50: Line 173:


=== API Callouts ===
=== API Callouts ===
You can use Function Expressions to do callouts, fetch data externally, load additional resources, use JavaScript Libraries such as FullCalendar (TODO: Reference), ChartsJs (TODO: Reference, JS Tables (TODO: Add reference)
You can use Function Expressions to do callouts, fetch data externally, load additional resources, use JavaScript Libraries such as [https://fullcalendar.io/ FullCalendar], [https://www.chartjs.org/ Chart.js], [https://datatables.net/ 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):<syntaxhighlight lang="javascript" line="1">
document.addEventListener('DOMContentLoaded', function() {
    var calendarEl = document.getElementById('calendar');
    var calendar = new FullCalendar.Calendar(calendarEl, {
      initialView: 'dayGridMonth'
    });
    calendar.render();
});
</syntaxhighlight>


=== User Logging, Tracking and Fingerprinting ===
=== User Logging, Tracking and Fingerprinting ===
You can use such technologies as FingerprintJs (TODO:Add Reference) to user tracking capabilities to your documents
You can use such technologies as [https://fingerprint.com/ Fingerprint] to user tracking capabilities to your documents


[[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