The Otter framework provides a placeholder component mechanism to help integrate dynamic HTML elements (with a basic rendering system) at a predefined position in the application.
The placeholder component is exposed by the @o3r/components package and is working with the NgRX placeholderTemplate
store exposed by the same package.
The component only has 1 input and supports a content value.
id
input is required and will determine the unique identification for a specific placeholder component.dynamic-media-path
)Based on the id
provided to the placeholder component, it will register itself to the event coming from placeholderTemplate
and will display the template corresponding to its ID in the store.
[!IMPORTANT] It is strongly encouraged to use the placeholder mechanism in concert with the Rules Engine.
The placeholder template is defined in a JSON file following the JSON Schema defined in placeholder-template.schema.json.
Example:
Example :{
"template": "<p>My fact : <%= myFact %></p>",
"vars": {
"myFact ": {
"value": "myFact",
"type": "fact"
}
}
}
Then the JSON file can be downloaded and dispatched to the store as follows:
Example :import { setPlaceholderTemplateEntityFromUrl } from '@o3r/components';
const JsonFileUrl = 'url-to-the-json-file';
store.dispatch(setPlaceholderTemplateEntityFromUrl({
call: (await fetch(JsonFileUrl)).json(),
id: 'my-placeholder-template',
resolvedUrl: JsonFileUrl,
url: JsonFileUrl
}));
The following steps should be followed in the application:
PlaceholderTemplateStoreModule
module in the application AppModule
.PlaceholderModule
module in the component using the placeholder in its template.import { PlaceholderModule } from '@o3r/components';
@NgModule({
imports: [
...
PlaceholderModule
],
...
})
export class MyComponentModule {
}
@Component({
selector: 'my-component',
template: `<p>the placeholder:</p><o3r-placeholder messagePanel id="pl2358lv-2c63-42e1-b450-6aafd91fbae8">Placeholder loading ...</o3r-placeholder>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyComponent {
}
The loading message is provided by projection. Feel free to provide a spinner if you need.
Once your placeholder has been created, you will need to manually create the metadata file and add the path to the
extract-components
property in your angular.json
.
Metadata file example:
[
{
"library": "@scope/app",
"name": "ExampleComponent",
"placeholders": [
{
"id": "pl2358lv-2c63-42e1-b450-6aafd91fbae8",
"description": "Example component placeholder from app"
}
]
}
]
Then, in the angular.json
file:
{
//...
"extract-components": {
"builder": "@o3r/components:extractor",
"options": {
"tsConfig": "./tsconfig.cms.json",
"libraries": [
"@scope/components"
],
"placeholdersMetadataFilePath": "placeholders.metadata.manual.json"
}
}
//...
}
The placeholders will be merged inside the component metadata file that will be sent to the CMS.
The first choice you have when you want to localize your template is the static localization.
You need to create a localized template for each locale and provide the template URL with [LANGUAGE]
(
ex: assets/placeholders/[LANGUAGE]/myPlaceholder.json
)
The rules engine service will handle the replacement of [LANGUAGE]
for you, and when you change language a new call
will be performed to the new 'translated' URL.
[!NOTE] The URL caching mechanism is based on the url NOT 'translated', meaning that if you change from en-GB to fr-FR then you decide to switch back and all the calls will be done again. This behavior is based on the fact that a real user rarely goes back and forth with the language update.
Your second option is to manage your placeholder in a single template and use the dynamic localization mechanism.
In that use case, you can refer to localization keys in your master placeholder template. The module will then translate the template based on the localization service and keep it updated after every language change. As your placeholder URL remains the same, it will be updated dynamically without any server call.
You can activate the dynamic localization feature in your placeholder by following this example:
Example :{
"vars": {
"keyToTranslation": {
"type": "localisation",
"value": "my-localisation-key"
}
},
"template": "<div class=\"fancy-box\"><%= keyToTranslation %></div>"
}
Your key should be described in a placeholder variable where the type will be localisation
and its value
the translation key itself. You can refer to the variable in your template thanks to the variable
key (keyToTranslation
in the example above).
Note: you should not create [LANGUAGE]
files for placeholders that use the dynamic localization feature. The static localization is not compatible with the dynamic localization.
Today, the Otter Framework supports the ICU syntax as well as parameters for localization variables.
As an example, let's create a counter that will emit every second. You will first need to design your placeholder referencing the translation key and the facts it is bound to.
Let's consider what this placeholder would look like if it were completely integrated in your Angular component.
Example :<div style=\"border-radius:10%; background:red;\">{{'o3r-increment-key' | o3rTranslate}}</div>
Then, let's create a new localization key for each of your supported languages:
{
"o3r-increment-key": "{increment, plural, =1 {1 second has} other {{{increment}} seconds have}} elapsed since you opened the page"
}
{
"o3r-increment-key": "Cela fait {increment, plural, =1 {1 seconde} other {{{increment}} secondes}} que tu as ouvert cette page"
}
Note that the o3r-increment-key
translations take increment
as a parameter.
This is how template.json
should look like.
{
"vars": {
"incrementKey": {
"type": "localisation",
"value": "o3r-increment-key",
"parameters": {
"increment": "incrementVar"
}
},
"incrementVar": {
"type": "fact",
"value": "incrementFact"
}
},
"template": "<div style=\"border-radius:10%; background:red;\"><%= incrementKey %></div>"
}
Now you will need to create an incrementFact
fact to fill the value.
You can follow the fact creation documentation.
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { FactsService, RulesEngineRunnerService } from '@o3r/rules-engine';
import { interval } from 'rxjs';
import { retrieveUrl } from './fact-factories/index';
import { PageFacts } from './page.facts';
@Injectable()
export class PageFactsService extends FactsService<PageFacts> {
public facts = {
incrementFact: interval(2000)
};
constructor(rulesEngine: RulesEngineRunnerService, private router: Router) {
super(rulesEngine);
}
}
Thanks to the parameters map you can use fact variables with JSONPath in localization strings.
en-GB.json
{
"o3r-greet-key": "Welcome back, { firstName } { lastName }!"
}
template.json
{
"vars": {
"greetKey": {
"type": "localisation",
"value": "o3r-greet-key",
"parameters": {
"firstName": "firstNameVar",
"lastName": "lastNameVar"
}
},
"firstNameVar": {
"type": "fact",
"value": "user",
"path": "$.firstName"
},
"lastNameVar": {
"type": "fact",
"value": "user",
"path": "$.lastName"
}
},
"template": "<div><%= greetKey %></div>"
}
You can use placeholder actions to target the same placeholderId with different template URLs.
It groups the rendered templates in the same placeholder, and you can choose the order by using the priority
attribute
in the action.
If not specified, the priority defaults to 0. Then the higher the number, the higher the priority. The final results are
displayed in descending order of priority.
The placeholder component waits for all the calls to be resolved (not pending) to display the content.
The placeholder component ignores a template if the application failed to retrieve it.
You need to reference one or several CSS files from your application in the cms.json
file:
{
"assetsFolder": "dist/assets",
"libraries": [
{
"npmName": "@o3r/styling"
}
],
"defaultLanguage": "en-GB",
"placeholderCssFiles": [
"dist/assets/placeholders/placeholders.css"
]
}
Those files will be loaded by the CMS to show the placeholder preview. Note that you could provide an empty file and update it with the dynamic content mechanism from AEM, to be able to reference the new classes afterward. There is just no user-friendly editor available yet. You can include this file in your application using the style loader service in your app component:
Example :this.styleLoader.asyncLoadStyleFromDynamicContent({id: 'placeholders-styling', href: 'assets/rules/placeholders.css'});
If the placeholder is not rendered properly, you can perform several checks to find out the root cause, simply looking at the store state.
Example: