mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-05-12 17:04:57 +00:00
348 lines
12 KiB
Markdown
348 lines
12 KiB
Markdown
---
|
|
Title: App extensions
|
|
Added: 3.0.0
|
|
---
|
|
|
|
# App Extensions
|
|
|
|
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.
|
|
|
|
## Contents
|
|
|
|
- [Extension points](#extension-points)
|
|
- [Extensibility features](#extensibility-features)
|
|
- [Setting up an app for extensibility](#setting-up-an-app-for-extensibility)
|
|
- [Creating extensions](#creating-extensions)
|
|
- [Routes](#routes)
|
|
- [Actions](#actions)
|
|
- [Rules](#rules)
|
|
- [Features](#features)
|
|
|
|
## Extension points
|
|
|
|
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](internationalization.md) - the key is used to mark
|
|
a place in the app where the actual content will be supplied dynamically.
|
|
|
|
## Extensibility features
|
|
|
|
ADF provides a number of features that offer extension points or help
|
|
with extensibility in general:
|
|
|
|
- **Components**: The [Dynamic component](../extensions/components/dynamic.component.md)
|
|
has no content of its own but it has an `id` property that
|
|
references a registered component extension ID. The referenced component
|
|
will be added as a child of the Dynamic component at runtime.
|
|
- **Routes**: These are registered as key/ID strings that resolve to standard
|
|
Angular routes. This feature can be used, say, that a click on a list item
|
|
should send the user _somewhere_ but leave the actual destination up to the
|
|
developer.
|
|
- **Auth guards**: Routes can be protected by auth guards
|
|
to prevent unauthorized users from accessing pages they shouldn't see.
|
|
- **Rules**: These are tests that produce a boolean result depending on the app state.
|
|
The extensible app can use them with _features_ or `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.
|
|
- **Actions**: The extensible app can define a set of **application actions** that
|
|
perform basic operations in the app. These are each referenced by a unique key
|
|
string and can take a data value as a parameter. Items from this set can then
|
|
be referenced by extension actions. These contain their own key/ID string along
|
|
with the name of an application action to trigger and a "payload" value to pass
|
|
as a parameter. The payload can either be a string (to represent a static message,
|
|
say) or an expression that calculates a result from app state. The expression could,
|
|
for example, return the current user's name, the currently selected list item or a
|
|
string composed from several data items.
|
|
- **Features**: What counts as a "feature" varies according to the application but
|
|
it is intended to mean any salient piece of functionality that can be
|
|
customized by extensions. For example, a toolbar, navigation bar,
|
|
login page or tools menu might all be regarded as features. Any of these
|
|
features could be extended in a variety of ways. A menu, say, might support custom
|
|
commands that are implemented by actions with each command enabled or disabled
|
|
depending on the value returned by a rule.
|
|
|
|
## Setting up an app for extensibility
|
|
|
|
You can register component classes for use with the [Dynamic component](../extensions/components/dynamic.component.md)
|
|
using the `setComponents` method of the
|
|
[Extension service](../extensions/services/extension.service.md) (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](https://gist.github.com/btroncone/a6e4347326749f938510).
|
|
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.
|
|
|
|
## Creating extensions
|
|
|
|
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:
|
|
|
|
```json
|
|
{
|
|
"$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](../extensions/services/extension.service.md) to read the file into a
|
|
convenient object that implements the [`ExtensionConfig`](../../lib/extensions/src/lib/config/extension.config.ts) and [`ExtensionRef`](../../lib/extensions/src/lib/config/extension.config.ts) interfaces.
|
|
Note that the `extension.schema.json` file contains a [JSON schema](http://json-schema.org/)
|
|
that allows for format checking and also text completion in some editors.
|
|
|
|
### Replacing Values
|
|
|
|
By default, the data from the extensions gets merged with the existing one.
|
|
|
|
For example:
|
|
|
|
**Application Data**
|
|
|
|
```json
|
|
{
|
|
"languages": [
|
|
{ "key": "en", "title": "English" },
|
|
{ "key": "it", "title": "Italian" }
|
|
]
|
|
}
|
|
```
|
|
|
|
**Extension Data**
|
|
|
|
```json
|
|
{
|
|
"languages": [
|
|
{ "key": "fr", "title": "French" },
|
|
]
|
|
}
|
|
```
|
|
|
|
**Expected Result**
|
|
|
|
At runtime, the application is going to display three languages
|
|
|
|
```json
|
|
{
|
|
"languages": [
|
|
{ "key": "en", "title": "English" },
|
|
{ "key": "it", "title": "Italian" },
|
|
{ "key": "fr", "title": "French" },
|
|
]
|
|
}
|
|
```
|
|
|
|
You can replace the value by using the special key syntax:
|
|
|
|
```json
|
|
{
|
|
"<name>.$replace": "<value>"
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```json
|
|
{
|
|
"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)
|
|
|
|
```json
|
|
{
|
|
"languages": [
|
|
{ key: "fr", "title": "French" }
|
|
]
|
|
}
|
|
```
|
|
|
|
### Routes
|
|
|
|
The `routes` array in the config contains objects like those shown in the
|
|
following example:
|
|
|
|
```json
|
|
"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,](../extensions/services/extension.service.md) which returns a [`RouteRef`](../../lib/extensions/src/lib/config/routing.extensions.ts) object. Note that the references
|
|
to the component and auth guards are extension IDs,
|
|
[as described above](#extension-points).
|
|
|
|
### Actions
|
|
|
|
The `actions` array has the following structure:
|
|
|
|
```json
|
|
"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](../extensions/services/extension.service.md) defines a `getActionById` method that returns an
|
|
[`ActionRef`](../../lib/extensions/src/lib/config/action.extensions.ts) 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,](../extensions/services/extension.service.md) which actually evaluates the
|
|
expression. Note that the result of the expression doesn't necessarily
|
|
have to be a string.
|
|
|
|
### Rules
|
|
|
|
The simplest type of rule is configured as shown below:
|
|
|
|
```json
|
|
"rules": [
|
|
{
|
|
"id": "app.trashcan",
|
|
"type": "app.navigation.isTrashcan"
|
|
},
|
|
...
|
|
]
|
|
```
|
|
|
|
The `type` is the ID of a [`RuleEvaluator`](../../lib/extensions/src/lib/config/rule.extensions.ts) function that has been registered using
|
|
the `setEvaluators` method of the [Extension service](../extensions/services/extension.service.md).
|
|
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:
|
|
|
|
```json
|
|
"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](../../lib/extensions/src/lib/evaluators/core.evaluators.ts) file:
|
|
|
|
- `every`: Returns true only if all the parameter rules return true
|
|
- `some`: Returns true if one or more of the parameter rules return true
|
|
- `not`: Returns true only if all the parameter rules return false
|
|
|
|
Note that parameter rules can also recursively invoke their own rules, etc.
|
|
|
|
### Features
|
|
|
|
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:
|
|
|
|
- The title shown in the menu
|
|
- An icon shown next to the title
|
|
- The action that is activated when the command is selected
|
|
- A rule that determines whether or not the command is enabled
|
|
|
|
A `features` object to add an extra item to this menu might look like
|
|
the following:
|
|
|
|
```json
|
|
"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"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|