Act! Plugins Library

The @act/plugins library was built using TypeScript. This allows us to provide defined types for our objects to help you in your development. This does not need you mean to use TypeScript within your own project (although we would recommend it!), but some parts of the documentation may have TypeScript syntax for type definitions.

To communicate with Act!, initialize an ActPlugin object.

import { ActPlugin } from '@act/plugins';

const actPlugin = new ActPlugin();
actPlugin.init(window);

Calling init will create the link between the Act! application and your application. You must pass the window object as that is the object that event handling for messages will take place from.

Properties

claims - returns a promise with the current user's claims.

context$ - a stream object that emits the current context for Act!. To use this object, .subscribe to the property and pass the function to handle the context.

// The entity is off of the `payload` property of the context object
actPlugin.context$.subscribe(context => this.handleContext(context));

// Destructure the context object and pull the payload property
handleContext({ payload }) {
// If the context is a Contact entity, the fullName will be printed (since this property only exists on a Contact)
const {
fullName,
} = payload;

if (fullName) {
console.log(fullName);
}
}

locale$ - a stream object that emits the current locale for Act!. To use this object, .subscribe to the property and pass the function to handle the context.

actPlugin.locale$.subscribe(locale => this.handleLocale(locale));

handleLocale(locale) {
this.locale = locale;
}

searchContext$ - a stream object that emits the most recent search context for Act!. The object passed to the stream has the following structure:

actPlugin.searchContext$.subscribe((searchContext: { entity: 'contact' | 'company' | 'group' | 'opportunity', query: ODataQuery, searchTerm: string }) => {
this.searchContext = searchContext;
});

The stream will always return the most recent search context. Therefore if you are on the detail view when this subscription occurs, you will receive a searchContext (if there was one). Be sure to filter the context based on the entity passed to receive the desired search context for the entity you plugin works off of.

Functions

init - initialize the ActPlugin object. Pass the window context to listen to messages on (default to the global window).

localStorage - a localStorage wrapper to expose that library functionality for plugin usage. This object has two functions:

// The shared parameter, used to share storage values between your company's plugins
async () => {
var isShared = false;
await actPlugin.localStorage.setItem('your-localStorage-item', 'test', isShared);
const storageItem = await actPlugin.localStorage.getItem('your-localStorage-item', isShared);
}

Note: each option has a shared parameter that can be used to share storage items across your plugins via the company name

navigate - navigates the Act! application to the passed path. Use the Act! application to discover the desired path. Note: do not include the origin within the path, it should only be the route as the path

// Here is an example call that navigates to the contact detail view for a specific contact and
// shows the tab with the name of `your-plugin-tab`
actPlugin.navigate(`contacts/${contactId}#your-plugin-tab`);

openUrl - opens a new window with the provided url. This is necessary because Act! isolates your plugin into an iframe with a certain security level. In order to open a new URL, your application must request Act! to open it for you.

registerAction - dynamically registers an action to the passed entity. The action has the same form as the extension payload for an action extension.

Note: make sure you have the actions and translations already set in the manifest for any dynamic action as you cannot dynamically load a manifest

actPlugin.registerAction('contact', {
functionName: 'onClick',
functionParams: [{ name: 'Rodney McKay', role: 'Scientist' }], // add dynamic parameters to be passed to your actions
label: 'PLUGIN_DOCS',
staticLabel: 'A button', // use this to override label with a static value not existent in the manifest
name: 'test',
position: {
name: 'call',
type: 'after'
},
});

requestData - returns a promise with the response body as the resolve value. The parameter for the function should be an object of the following structure:

{
body?: any,
context?: any;
entity: string,
relatedEntity?: string;
request: 'get' | 'create' | 'update' | 'delete' | 'patch',
query?: ODataQuery,
}

This function passes the parameters to the correct service handler within the Act! application to make your query. It will then return the results of that query to your plugin for use. Because the application handles the data for you, we can provide this service with offline functionality built in.

For basic requests (such as list or single entity fetching), the call would look like:

// Gets the list of contacts
actPlugin.requestData({ entity: 'contacts' });

// Gets a single contact with the id of '000' (note this is not a valid Act! id, but is simply for demonstration purposes)
actPlugin.requestData({ entity: 'contacts', query: '000' });

The query can be either a string to represent the id of the entity you wish to fetch, or an ODataQuery object from the @act/api package. The query object will be the path into more complex style data fetching. Here is an example of fetching:

actPlugin.requestData({
entity: 'contacts',
query: new ODataQuery(
null,
new ODataSort('startTime', ODataSortDirection.DESCENDING)
),
});

Related entities can be accessed with relation to the main entity via the relatedEntity property in the request object. Requesting data like this requires that you pass the context in the request object:

actPlugin.context$.subscribe((context) => {
requestData({ context, entity: 'contacts', relatedEntity: 'activities' });
});

Furthermore, you can use the ODataQuery object to create complex queries off of those related entities:

actPlugin.context$.subscribe((context) => {
requestData({
context,
entity: 'contacts',
relatedEntity: 'activities',
query: new ODataQuery(
null,
new ODataSort('startTime', ODataSortDirection.DESCENDING)
),
});
});

It is highly recommended to check out the Web API documentation for the available data to query via the API, the respective models for each, and the available HTTP request types for each entity.

requestUser - returns a promise with the User object as the resolve value.

sessionStorage - a sessionStorage wrapper to expose that library functionality for plugin usage. This object has two functions:

// The shared parameter, used to share storage values between your company's plugins
async () => {
var isShared = false;
await actPlugin.sessionStorage.setItem('your-sessionStorage-item', 'test', isShared);
const storageItem = await actPlugin.sessionStorage.getItem('your-sessionStorage-item', isShared);
}

Note: each option has a shared parameter that can be used to share storage items across your plugins via the company name

setActions - sets the global actions used by your plugin. This should only be necessary if your plugin extends the Act! application through the addition of contextual action buttons or any functions that exist outside of your plugin application (i.e. within the Act! application).

closeDialog - tells the Act! application to close the dialog currently displayed by the showDialog function call.

showDialog - tells the Act! application to show a dialog based on the passed type and payload parameters. This function returns an object with a close function on it to tell Act! to close the dialog. This allows your plugin control over the lifecycle of the dialog. Note: If you are going to add click handlers to the dialogs, then you will need to add the actions file to your manifest. This will allow Act! to call the correct function handler for you and resolve the action. See the Actions Extension below for steps to create that file and necessary structure. This is not the case for iframe dialogs in which you control all actions and must handle the dialog lifecycle. The payload object takes the following form:

interface PluginDialogPayload {
actions?: PluginDialogAction[];
content?: string;
dialog?: string;
title: string;
subTitle?: string;
size?: {
height: string;
width: string;
};
}

The currently supported dialogs types are:

"dialogs": {
"Sample_List": {
"src": "https://example-deployed-site.com/dialog.html",
"size": {
"height": "500px",
"width": "350px"
}
}
// Note: You can also declare the size of the dialog through the payload here
// ex. actPlugin.showDialog('iframe', { dialog: 'Sample_List', size: { height: '500px', width: '350px' } });
actPlugin.showDialog('iframe', { dialog: 'Sample_List' });

There are two cases to cover. The first is calling showDialog from the actions.js file.

Here is an example of doing so:

dialogRef = actPlugin.showDialog('text', {
actions: [
{
name: 'close',
label: 'Close',
onClick: 'handleCloseClick',
},
],
content: 'Created a task',
title: 'Quick Task',
});

Then in your global actions file, you will need the click handler:

handleCloseClick: () => {
dialogRef.close();
};

The following case is an example of calling showDialog from you application rather than the actions.js file:

class TestApplication {
actPlugin;
dialogRef;
// Some initializer for your application
constructor() {
this.actPlugin = new ActPlugin();
this.actPlugin.init();
// Set the actions available for the Act! application to call here
this.actPlugin.setActions({
handleCloseClick: () => this.handleCloseClick(),
});
}
// The function to called to display the dialog and set the reference
showDialog() {
this.dialogRef = this.actPlugin.showDialog('text', {
actions: [
{
name: 'close',
label: 'Close',
onClick: 'handleCloseClick', // Note: this string should match the value set in the `setActions` call
},
],
content: 'Created a task',
title: 'Quick Task',
});
}
// The handler that `ActPlugin` will call based on the set actions
handleCloseClick() {
if (!this.dialogRef) {
return;
}
this.dialogRef.close();
this.dialogRef = null;
}
}

updateContext - a function that takes the context object and merges the values (as overrides) to the existing context within the Act! application.

// Here is an example action that updates the current Contact context to the values in contactUpdate when the updateContext action is called
let contact = null;

const actions = {
/**
* Define plugin actions here that will be called by function name
*/

onActionClick: () => {
// An example call to update the current Contact model to the following firstName and lastName values
const contactUpdate = {
...contact,
firstName: 'Rodney',
lastName: 'McKay',
namePrefix: 'Dr.',
};
actPlugin
.requestData({
entity: 'contacts',
request: 'update',
body: contactUpdate,
})
.then((updatedContact) => {
actPlugin.updateContext(updatedContact);
});
},
};

actPlugin.context$.subscribe((context) => {
contact = context;
});
actPlugin.setActions(actions);

Actions

If your plugin requires actions used by the Actions extensions, then you must create a new actions.js file that should be served by your plugin.

The structure for the file should be:

const actions = {
// Name this function based on your extension function strings
testAction: () => {
// Do something here...
},
};

actPlugin.setActions(actions);

The actPlugin object is exposed to the actions.js file by Act! (see restrictions on importing third party libraries and DOM manipulation within WebWorkers). It contains all properties and functions as described above for the ActPlugin API. Please note that this means all code associated with the actions file should be standard JavaScript without any third party libraries. If you would like to use third party libraries, you must bundle the whole library into your actions.js file via a tool such as Webpack.

Lifecycle

Next, let's talk about the lifecycle of your plugin.

Lifecycle