ADF lets you simplify the app developer's task by providing an extensible app as a starting point.
An extensible app is designed with extension points, which are placeholders where components and other content can be "plugged in" to provide functionality. The app may be supplied with default content for the extension points but the idea is that a developer can easily replace this with custom content as necessary. An organization might find this useful, for example, if they want to create a family of apps with consistent appearance and behavior. One developer can produce an extensible app that can then be adapted by other developers to create the various apps in the family.
A pluggable extension is implemented by a class or data object that provides its functionality. The class or object is then registered in the app with a key/ID string that is used to reference it. The general idea is that only the ID string is used directly in the main app code to designate the extension point, while the actual implementation is loaded and registered separately. In this respect, extension points work somewhat like translation keys - the key is used to mark a place in the app where the actual content will be supplied dynamically.
ADF provides a number of features that offer extension points or help with extensibility in general:
id property that
references a registered component extension ID. The referenced component
will be added as a child of the Dynamic component at runtime.ngIf directives, for example, to show or
hide content in certain conditions. The exact conditions, however, are chosen
by the developer who extends the app.You can register component classes for use with the Dynamic component
using the setComponents method of the
Extension service (see the Dynamic component page for further details
and code samples). The service also has setAuthGuards and
setEvaluators methods that behave analogously.
The recommended way to provide the set of application actions (ie, the built-in actions that can be referenced by extension actions) is to use the scheme defined by @ngrx/store. Briefly, the idea is that all app state is stored centrally and can only be updated by functions triggered by named command strings (eg, "ADD_USER", "CLEAR_SELECTION", "NEW_DOCUMENT", etc). ADF's extensibility features are designed to fit in neatly with @ngrx/store but it has many other advantages, as described on the website.
The set of basic classes, evaluators and actions provided by the app can be used to set up extensions. The easiest way to configure the extension functionality is with an extension config file. The structure of this file (in JSON format) follows the basic pattern shown below:
{
"$id": "unique.id",
"$name": "extension.name",
"$version": "1.0.0",
"$vendor": "author.name",
"$license": "license",
"$runtime": "1.5.0",
"$description": "some description",
"routes": [ ... ],
"actions": [ ... ],
"rules": [ ... ],
"features": { ... }
}
You can use the load method of the Extension service to read the file into a
convenient object that implements the ExtensionConfig and ExtensionRef interfaces.
Note that the extension.schema.json file contains a JSON schema
that allows for format checking and also text completion in some editors.
By default, the data from the extensions gets merged with the existing one.
For example:
Application Data
{
"languages": [
{ "key": "en", "title": "English" },
{ "key": "it", "title": "Italian" }
]
}
Extension Data
{
"languages": [
{ "key": "fr", "title": "French" },
]
}
Expected Result
At runtime, the application is going to display three languages
{
"languages": [
{ "key": "en", "title": "English" },
{ "key": "it", "title": "Italian" },
{ "key": "fr", "title": "French" },
]
}
You can replace the value by using the special key syntax:
{
"<name>.$replace": "<value>"
}
Example:
{
"languages.$replace": [
{ "key": "fr", "title": "French" }
]
}
Expected Result
At runtime, the application is going to display languages provided by the extension (given that no other extension file replaces the values, otherwise it is going to be a "last wins" scenario)
{
"languages": [
{ key: "fr", "title": "French" }
]
}
The routes array in the config contains objects like those shown in the
following example:
"routes": [
{
"id": "plugin1.routes.customTrash",
"path": "ext/customtrash",
"component": "yourCustomTrash.component.id",
"layout": "app.layout.main",
"auth": ["app.auth"],
"data": {
"title": "Custom Trashcan"
}
},
...
]
You can access routes from the config using the getRouteById method of the
Extension service, which returns a RouteRef object. Note that the references
to the component and auth guards are extension IDs,
as described above.
The actions array has the following structure:
"actions": [
{
"id": "plugin1.actions.settings",
"type": "NAVIGATE_URL",
"payload": "/settings"
},
{
"id": "plugin1.actions.info",
"type": "SNACKBAR_INFO",
"payload": "I'm a nice little popup raised by extension."
},
{
"id": "plugin1.actions.node-name",
"type": "SNACKBAR_INFO",
"payload": "$('Action for ' + context.selection.first.entry.name)"
},
...
]
The Extension service defines a getActionById method that returns an
ActionRef object corresponding to an item from this array.
The type property refers to an action type that must be provided by the
app (eg, the "SNACKBAR_INFO" in the example presumably just shows a standard snackbar
message).
By default, the payload is just an ordinary string that can be used for
a message, URL or other static text data. However, you can also define a
JavaScript expression here by surrounding it with $( ... ). The expression
has access to an object named context which typically contains information
about the app state. You can supply the object that contains this data via the
runExpression method of the Extension service, which actually evaluates the
expression. Note that the result of the expression doesn't necessarily
have to be a string.
The simplest type of rule is configured as shown below:
"rules": [
{
"id": "app.trashcan",
"type": "app.navigation.isTrashcan"
},
...
]
The type is the ID of a RuleEvaluator function that has been registered using
the setEvaluators method of the Extension service.
The evaluator is a boolean function that represents whether a certain
condition is true or false (eg, whether an item is selected, whether the user
has certain options enabled, etc). The evaluator has access to a context object
that is supplied from the app during the call to evaluateRule (defined in
the Extension service).
A more complex rule can take other rules as parameters:
"rules": [
{
"id": "app.toolbar.favorite.canAdd",
"type": "core.every",
"parameters": [
{ "type": "rule", "value": "app.selection.canAddFavorite" },
{ "type": "rule", "value": "app.navigation.isNotRecentFiles" },
{ "type": "rule", "value": "app.navigation.isNotSharedFiles" },
{ "type": "rule", "value": "app.navigation.isNotSearchResults" }
]
}
]
This is mainly useful for creating "metarules" that require certain relationships to hold among the parameter rules. A few useful metarules are defined in the core.evaluators.ts file:
every: Returns true only if all the parameter rules return truesome: Returns true if one or more of the parameter rules return truenot: Returns true only if all the parameter rules return falseNote that parameter rules can also recursively invoke their own rules, etc.
The features object does not have any defined structure but the intention
is that each key in the object corresponds to the name of a salient feature
of the app that can be extended. The object or array that matches the key
name contains parameters that modify the behavior of the feature, possibly
using actions, rules, etc, defined elsewhere in the config. Suppose, for
example, the app has a tools menu that can be extended with extra commands.
The properties for a new command might include:
A features object to add an extra item to this menu might look like
the following:
"features": {
"toolmenu": [
{
"id": "app.toolmenu.givebiscuit",
"title": "Give a biscuit to the selected user",
"icon": "icons/GiveBiscuit.svg",
"actions": {
"click": "GIVE_BISCUIT"
},
"rules": {
"visible": "app.biscuits.notempty"
}
}
]
}
© 2023 Alfresco Software, Inc. All Rights Reserved.
By using this site, you are agreeing to allow us to collect and use cookies as outlined in Alfresco’s Cookie Statement and Terms of Use (and you have a legitimate interest in Alfresco and our products, authorizing us to contact you in such methods). If you are not ok with these terms, please do not use this website.