Web extensions
Web module
ShareAspace web can be extended with custom web UI functionality in three different ways:
- Custom ShareAspace web module.
- Custom create and edit forms.
- Custom web controls in OOTB ShareAspace forms.
Extensions are loaded as within IFRAMES inside ShareAspace web.
Module configuration
In order to setup a custom ShareAspace web module a a configuration object must be setup in the clients.options
array of the ShareAspace Space template.
There are a couple of different options when configuring this module.
- "Plain module" - The full module canvas can be utilized by the custom web module.
- Module with OOTB action ribbon and/or OOTB read view - The canvas of the custom web module is framed by a ShareAspace action ribbon at the top and/or a ShareAspace read view to the right.
The custom module can operate in two modes.
- Input dependent - The module requires an input object and will not be available as a tile icon or as an option in the user's module menu list.
- Non input dependent - The module can appear as a tile and will appear the the user's module menu list. It is still possible to configure the module to accept input objects in this mode.
Tip
It is possible to configure a name-value parameter list on the module. These parameters will be passed to the extension module once loaded.
Example: Input dependent module
- Accepting
Document
SoftType as input. - Requires a registered extension on the collection level with id
ExtensionPlain
.
{
"$id": "ExtensionPlain",
"$moduleRef": "Extension",
"prioritized": false,
"settings": {
"acceptTypes": "[{\"order\":0,\"acceptType\":\"Document\"}]",
"documentationRef": "https://docs.shareaspace.com",
"endpoint": "ExtensionPlain",
"inputDependent": "True",
"parameters": "[]",
"title": "Extension plain"
}
}
Example: Non input dependent module with OOTB action ribbon and read view
- Accepting
Document
SoftType as input. - Activates ShareAspace action ribbon for selected object. Will have an
Edit
action requiring the selected object to have an edit view with iddress
. - Activates ShareAspace read view for selected object. Requires selected object to have a configured read view with id
read
. - Requires a registered extension on the collection level with id
Extension
.
{
"$id": "Extension",
"$moduleRef": "Extension",
"prioritized": false,
"settings": {
"acceptTypes": "[{\"order\":0,\"acceptType\":\"Document\"}]",
"actions": "[{\"order\":0,\"title\":\"Edit\",\"type\":\"update\",\"view\":\"dress\"}]",
"activatedFeatures": {
"outputView": true,
"ribbon": true
},
"baseTypes": "Document",
"documentationRef": "https://docs.shareaspace.com",
"endpoint": "Extension",
"externalActions": "[]",
"externalMultiActions": "[]",
"externalStaticActions": "[]",
"inputDependent": "True",
"multiActions": "[]",
"outputViews": "[{\"order\":0,\"title\":\"Read\",\"showEmptyValue\":false,\"view\":\"read\"}]",
"parameters": "[]",
"staticActions": "[]",
"title": "Extension"
}
}
Register extension
In order for custom modules to be loaded they must be registered as extensions on the collection level. See documentation.
Module extensions can be registered as part of the metadata exchange for Nova extensions
or registered as either External endpoint
or Trusted external endpoint
.
If user impersonation is required (having a JSON Web Token, JWT for the current user being handed over to the module) the extension must be registered as a Trusted external endpoint
.
When registering an external module, Category should be set to Client
and Method should be set to GET
.
Startup
Once a module has been loaded in ShareAspace web the web application will send a message to the extension.
window.addEventListener("message", (event) => {
var data = event.data;
if (data.type === "setup") {
route = data.data.apiUrl;
extensionId = data.data.extensionId;
}
});
The name of the initial setup message is typed as setup
using the contract described below.
type ExtensionModuleSetupEvent = {
type: 'setup'
data: {
/** The identifier of the extension we are expecting to run. */
extensionId: string
/** The id of the space currently in context. */
spaceId: string
/** The API URL to communicate with the ShareAspace backend. */
apiUrl: string
/**
* The unique identifier of the iframe this is need when communicating
* back to ShareAspace.
**/
frameId: string
/**
* Access token that must be used to communicate with the ShareAspace API.
* This property can be undefined if impersonate is not defined.
*/
accessToken?: string
/**
* The current ShareAspace information filter encoded as a string.
*/
informationFilter?: string
/**
* One to many ObjectId's to start from, can also be undefined if there were not
* input objects.
*/
inputObjects?: Array<string>
/**
* These are the parameters defined in the extension configuration.
* Can be undefined if there is no parameters defined in the configuration.
*/
parameters?: Record<string, string>
}
}
Communication from ShareAspace to extension
There are two more message types that ShareAspace Web will send to loaded extensions.
Object altered
ShareAspace web will send a object-altered
message to the extension if an action taken from the module action ribbon has updated one or more SoftType instances.
type ObjectAlteredEvent {
type: 'object-altered'
data: {
/**
* An array with ObjectId's for altered SoftType instances.
*/
selectedObjects: Array<{
$oid: string
}>
}}
Refresh
ShareAspace web will send a refresh
message to the extension if a user has clicked the refresh button on the module tab in ShareAspace web.
type RefreshEvent {
type: 'refresh'
}
Communication from extension to ShareAspace
An extension can communicate back to ShareAspace by posting message to its parent (i.e. ShareAspace web).
window.parent.postMessage(
{
type: "...",
data: {
...
},
},
apiUrl // The ShareAspace API url provided by the setup message.
);
Selection changed
Selection changed would typically be used when a user has selected a new SoftType instance within the module. By sending the selection-changed
message ShareAspace web can (if the features are active in the configuration) switch the context object(s) for the action ribbon and the read view.
type SelectionChangedEvent = {
type: 'selection-changed'
data: {
/**
* The frame id for the extension, provided by the setup message.
*/
frameId: string
/**
* An array with ObjectId's for the selected object(s)
*/
selectedObjects: Array<{
$oid: string
}>
}
}
Open in
Open in lets the extension module tell ShareAspace web to open another module in ShareAspace web for a provided SoftType instance.
type OpenInEvent = {
/**
* One to many ObjectId(s) of SoftType instances to be passed into the module to be opened.
*/
inputObject: string | string[];
/**
* The SoftType id of the SoftType instance to be passed into the module to be opened.
*/
softTypeId: string;
/**
* The id of the extension requesting a new module to be opened.
*/
extensionId: string;
/**
* The id of the module to be opened.
*/
moduleId: string;
};
Web form
The custom web form allows for a configuration where it is possible to have complete control of the rendering of:
- a ShareAspace edit form.
- a ShareAspace read form (modal).
- a ShareAspace external action form.
ShareAspace will rely on the extension to provide:
- Form validation information in order to control whether the submit button should be active or not.
- A payload to be:
- Passed as a POST message for a ShareAspace SoftType instance update.
- Passed as a payload to a custom external extension.
Configuration
In order to setup a custom ShareAspace web form an externalStaticAction
must be configured on a module configuration under the clients.options
array of the ShareAspace Space template.
{
"$id": "ModuleId",
"$moduleRef": "Generic",
"prioritized": true,
"settings": {
...
"externalStaticActions": "[{\"order\":0,\"title\":\"External edit\",\"viewExtensionId\":\"ExternalExternal\",\"viewType\":\"Submit\",\"submitButtonText\":\"Save\",\"\":\"Cancel\"}]",
...
}
}
Parameter | Type | Description |
---|---|---|
order |
number |
Starts at 0 . Controls the ordering of configured ribbon actions. |
title |
string |
Name of ribbon action button. |
viewExtensionId |
string |
Id of the form extension to be loaded. |
viewType |
string |
Allowed values Submit /Read . Controls if Submit and Cancel buttons should be rendered. |
submitButtonText |
string |
Submit button text. |
cancelButtonText |
string |
Cancel button text. |
actionExtensionId |
string |
Id of registered extension that hosts the form. |
titleIconPath |
string |
Path an image that will be displayed before the title of the modal form that opens when clicking the action. |
iconRelativePath |
string |
Icon to be used for the ribbon action button. |
hideSubmitButton |
boolean |
Hides submit button if set to true . |
Startup
Once a form has been loaded in ShareAspace web the web application will send a message to the form extension. ShareAspace web expects a setup-response
message back (more information to follow).
The name of the initial setup message is typed as setup
using the contract described below.
type ExternalFormSetupEvent = {
type: 'setup'
data: {
/** The id of the space currently in context. */
spaceId: string
/** The API URL to communicate with the ShareAspace backend. */
apiUrl: string
/**
* The unique identifier of the iframe this is need when communicating
* back to ShareAspace.
**/
frameId: string
/**
* Access token that must be used to communicate with the ShareAspace API.
* This property can be undefined if impersonate is not defined.
*/
accessToken?: string
/**
* ObjectId of the object selected when calling the external form.
*/
selectedObjectId: string
/**
* If there is a object in context when calling the external form this is the ObjectId
* of this object. If there is no object in context this is undefined.
*/
contextObjectId?: string
/**
* The if an entry of a context object is selected this is the id of that entry.
* If there is no entry selected this is undefined.
*/
entryId?: string
}
}
Communication from ShareAspace to extension
When ShareAspace web expects the resulting payload from the form ShareAspace web will pass a get-payload
message to the extension form. ShareAspace expects a get-payload-response
message back from the extension (details in the following section).
Get payload
type GetPayloadEvent = {
type: 'get-payload'
}
Communication from extension to ShareAspace
The form can communicate needed height real estate back to ShareAspace web using the ifram-height
message.
IFRAME height
type IFrameHeightEvent {
type: 'iframe-height',
data: {
/**
* The required pixel height of the custom form (before scrolling is enabled).
*/
height: string|number
}
}
Setup response
The response to the initial setup
message.
type SetupResponseEvent {
type: 'setup-response',
data: {
/**
* The frame id for the extension, provided by the setup message.
*/
frameId: string
/**
* Describes if user input in the external form as fulfilled all requirements (will enable submit button if set to true).
*/
isValid: boolean,
/**
* The resulting form payload. Will be passed by ShareAspace web to the ShareAspace REST API or to the registered external extension (acting on the submit action).
*/
payload: any
}
}
Payload response
The response to the get-payload
message.
type GetPayloadResponseEvent {
type: 'get-payload-response',
data: {
frameId: string
isValid: boolean,
payload: any
}
}
Validation
Validation can be used by the external form to keep ShareAspace up-to-date with user input changes in the form, e.g. a user has taken action to/from valid form input requirements.
type ValidChangedEvent {
type: 'valid-changed',
data: {
isValid: boolean
}
}
Web form control
A web custom web form control can be configured within a ShareAspace web view configuration. The control is bound to a port on the input/output schema of the view. The web form control can message ShareAspace in order to:
- State if the user input has validated correctly.
- Provide ShareAspace with the user input.
- Notify ShareAspace if a user has altered a value.
- Control the required height of the custom control within the view.
The ShareAspace form can aske the extension control for its value.
Startup
type ExternalContentSetupEvent = {
type: 'setup'
data: {
/** The id of the space currently in context. */
spaceId: string
/** The API URL to communicate with the ShareAspace backend. */
apiUrl: string
/**
* The unique identifier of the iframe this is need when communicating
* back to ShareAspace.
**/
frameId: string
/**
* Access token that must be used to communicate with the ShareAspace API.
* This property can be undefined if impersonate is not defined.
*/
accessToken?: string
/**
* The ObjectId of the content configured to be displayed in external content.
*/
objectId: string
}
}
Communication from ShareAspace to extension
When ShareAspace web expects the resulting payload from the form ShareAspace web will pass a get-payload
message to the extension form. ShareAspace expects a get-payload-response
message back from the extension (details in the following section).
Get payload
type GetPayloadEvent = {
type: 'get-payload'
}
Communication from extension to ShareAspace
Resize IFRAME
The form can communicate needed height real estate back to ShareAspace web using the resize-iframe
message.
type ResizeIFrameEvent {
type: 'resize-iframe',
data: {
height: string,
frameId: string,
visible: string,
}
}
Setup response
The response to the initial setup
message.
type SetupResponseEvent {
type: 'setup-response',
data: {
/**
* The frame id for the extension, provided by the setup message.
*/
frameId: string
/**
* Describes if user input in the external form as fulfilled all requirements (will enable submit button if set to true).
*/
isValid: boolean,
/**
* The resulting form payload. Will be passed by ShareAspace web to the ShareAspace REST API or to the registered external extension (acting on the submit action).
*/
payload: any
}
}
Payload response
The response to the get-payload
message.
type GetPayloadResponseEvent {
type: 'get-payload-response',
data: {
frameId: string
isValid: boolean,
payload: any
}
}
Validation
Validation can be used by the external form to keep ShareAspace up-to-date with user input changes in the form, e.g. a user has taken action to/from valid form input requirements.
type ValidChangedEvent {
type: 'valid-changed',
data: {
isValid: boolean
}
}
Open module
type OpenModuleEvent {
type: 'open-module',
data: {
inputObject: string,
softTypeId: string,
moduleId: string,
}
}
Property update
If the value that the control is bound to changes its value, the form can notify ShareAspace web about this update by sending the property-update
message. value
can be any type and depends on the type that the control is managing.
type PropertyUpdateEvent {
type: 'property-update',
data: {
frameId: string,
value: any,
}
}
Examples
Caution
Sample code provided is for demonstration and educational purposes only. The code is simplified in order to describe concepts and do not always follow best development practices. All production code should always follow development best practices. All sample code is supplied "AS IS" without any warranties or support.
Open in
<h3>Open in:</h3>
<div>
Module id: <input id="moduleId" type="text" />
Input object: <input id="inputObject" type="text" />
SoftType id: <input id="softTypeId" type="text" />
</div>
<button onClick="openModule()">Open in module</button>
<script>
let route = undefined;
let extensionId = undefined;
function openModule() {
var payload = {
extensionId: extensionId,
moduleId: document.getElementById("moduleId").value,
inputObject: document.getElementById("inputObject").value,
softTypeId: document.getElementById("softTypeId").value,
};
window.parent.postMessage(payload, route);
}
window.addEventListener("message", (event) => {
var data = event.data;
if (data.type === "setup") {
route = data.data.apiUrl;
extensionId = data.data.extensionId;
}
});
</script>
Selection changed / Object altered
<h3>Send update:</h3>
<div>
<input id="uuid" type="text" value="Paste ObjecId here..." />
<input id="sendUuid" type="button" value="Send" />
</div>
<h3>Update received:</h3>
<div>
<label id="updatedValue">Updates from SAs are reported here...</label>
</div>
<script type="text/javascript">
var route = undefined;
var frameId = undefined;
window.addEventListener("message", (event) => {
var data = event.data;
if (data.type === "setup") {
route = data.data.apiUrl;
frameId = data.data.frameId;
} else if (data.type === "object-altered") {
document.getElementById("updatedValue").innerHTML =
data.data.selectedObjects[0].$oid;
}
});
document.getElementById("sendUuid").addEventListener("click", () => {
var uuid = document.getElementById("uuid").value;
var sasBaseUrl = route;
window.parent.postMessage(
{
type: "selection-changed",
data: {
selectedObjects: [{ $oid: uuid }],
frameId: frameId,
},
},
sasBaseUrl
);
});
</script>
Form
<div>
Id: <input id="id" type="text" /> Name: <input id="name" type="text" />
</div>
<script>
var frameId = undefined;
var route = undefined;
function getPayload() {
var output = {
id: document.getElementById("id").value,
name: document.getElementById("name").value,
};
return output;
}
function isValid() {
var id = document.getElementById("id");
var name = document.getElementById("name");
return id.value == "foo" && name.value === "bar";
}
function listenToInputs() {
document.getElementById("id").addEventListener("change", () => {
window.parent.postMessage(
JSON.stringify({
type: "valid-changed",
data: {
isValid: isValid(),
payload: getPayload(),
frameId: frameId,
},
}),
route
);
});
document.getElementById("name").addEventListener("change", () => {
window.parent.postMessage(
{
type: "valid-changed",
data: {
isValid: isValid(),
payload: getPayload(),
frameId: frameId,
},
},
route
);
});
}
window.addEventListener("message", (event) => {
var data = event.data;
if (data.type === "setup") {
frameId = data.data.frameId;
route = data.data.apiUrl;
// NOTE: We have removed the init and init-response
let payload = {
type: "setup-response",
data: {
isValid: isValid(),
payload: getPayload(),
frameId: frameId,
},
};
window.parent.postMessage(payload, route);
listenToInputs();
} else if (data.type === "get-payload") {
let payload = {
type: "get-payload-response",
data: {
isValid: isValid(),
payload: getPayload(),
frameId: frameId,
},
};
window.parent.postMessage(payload, route);
}
});
</script>