Change the configuration programmatically

map.apps allows a running app to inspect and change the configuration of its components programmatically through the so-called Configuration Admin Service.

This service provides read and write access to the component configurations of the app.json in a way that allows to even persist modified configurations if desired. Persisting modified configurations requires an app.json to be stored in a database or other kind of system that allows to update the app.json. For example, you cannot persistently modify the Demo app of map.apps for Developers in this way.

But persisting changes to a component configuration is not the only use case where the Configuration Admin Service comes in handy. You can also just pass on a modified version of a component’s configuration to the App Runtime without actually changing the app.json.

For each of the use cases, you find a how-to guide on this page.

But first, let’s explore a basic example to get an impression of what it takes to make use of the Configuration Admin Service.

Changing a component configuration

Here, we will demonstrate how to edit and save the configuration of a simple component. Changing the configuration of a factory component, is demonstrated here.

Step 1: Inject the Configuration Admin Service

First, you need to get instance of the Configuration Admin Service. In this example it is injected into the MyComponentUpdater component of some bundle as configAdminService.

manifest.json
{
    "components" [
        {
            "name": "MyComponentUpdater",
            "references": [
                {
                    "name": "configAdminService",
                    "providing": "ct.framework.api.ConfigurationAdmin"
                }
            ]
        }
    ]
}

Step 2: Get the component configuration

Inside the MyComponentUpdater.js, in some method, we access the injected configAdminService instance to look up the configuration of the Console component provided by the console bundle.

MyComponentUpdater.js
//...
const configAdminService = this.configAdminService;
const config = configAdminService.getConfiguration("console-Console", "console"); (1)
const configProperties = config.get("properties"); (2)
//...
1 "console-Console" identifies the "Console" component of the "console" bundle. Identifiers have to obey the component identifier format. Additionally we need to pass the bundle name, "console" in this case.
2 This stores the component properties as they are defined in the app.json in a local variable.

Step 3: Update the component configuration

The configuration properties for the component can now be inspected, modified, added, or deleted as a plain JavaScript object like this:

MyComponentUpdater.js
//...
configProperties["_shortCutKey"] = "44"; // Set "PrtScr" key as shortcut key
config.update(configProperties);
//...

update(configProperties) eventually applies the new component configuration. This will not save the configuration to the app.json file, yet. But the component properties get updated directly.

Step 4: Save the component configuration

To permanently store the values with the app configuration, you need to save them like this:

MyComponentUpdater.js
...
configAdminService.save();
...

Changing a factory component configuration

The next examples demonstrate a special method to use in conjunction with factory components.

Create Factory Component Configuration
let configAdminService = this.configAdminService;

// sample configuration properties
let configProperties = { "property" : "test" };

// read configuration entry
let config = configAdminService.createFactoryConfiguration("agssearch-AGSStore-Factory", "agssearch");

config.update(configProperties);
configAdminService.save();

If you know an exact identifier of a factory component instance, like agssearch-AGSStore-0, you can use the same code as shown in Changing a component configuration. Otherwise you can use the method demonstrated in the following example, iterating over the returned component configurations:

Iterate factory component instance configurations
let configAdminService = this.configAdminService;

// read all factory configuration instance entries
let configs = configAdminService.getFactoryConfigurations("agssearch-AGSStore-Factory", "agssearch");

for (var i=0;i<configs.length;++i){
    var config = configs[i];
    ...
    // for example delete all factory component instances
    config.remove();
}

configAdminService.save();

Rewrite an app configuration on startup

map.apps allows bundle developers to provide a service that can rewrite the app configuration before it is interpreted by the runtime using configuration interceptors. As an example, a configuration interceptor can be used to migrate a deprecated bundle configuration to the current configuration approach.

To provide a configuration interceptor, a component has to implement the interface system.config.ConfigurationInterceptor like this:

ConfigurationInterceptor in manifest.json file
{
  "startLevel" : 1,  (1)
  "components": [
      {
        "name" : "MyConfigInterceptor",
        "provides" : ["system.config.ConfigurationInterceptor"]
      },
      ... // all other components, some of which require migration
  ]
}

You need to make sure, this bundle is started early enough to intercept the configuration of the targeted component. If the target component is defined within the same bundle, just define the configuration interceptor before all other component, so it is loaded earlier than these. If the target component is defined in another bundle, make sure the startLevel of the bundle containing the interceptor is lower than the targeted one’s.

A configuration interceptor must implement the method "intercept":

ConfigurationInterceptor
intercept(configStore){

    // read old config
    let oldConfig = configStore.getConfig("oldbundle-ComponentName");

    // delete old config
    configStore.removeConfig("oldbundle-ComponentName");

    // check if a configuration exists
    if (configStore.getConfig("newbundle-ComponentName")){
            // do nothing if configuration is present
        return;
    }

    // write new configuration
    configStore.setConfig("newbundle-ComponentName",{
            bundleIdentifier: "newbundle",
            properties: { ... }
    });
}

Making non-persistent changes

In addition to directly changing configuration using the ways explained earlier, there is a way to change configuration properties before they are passed to a managed service and without persisting the changes. This way is called configuration plugin. A configuration plugin is a service registered by the interface ct.framework.api.ConfigurationPlugin. It has to implement the method modifyConfiguration().

Configuration plugin
modifyConfiguration(targetInfo, configProperties){
    // targetInfo can be used to get information about the target component, which properties are given in "configProperties".
    let managedServiceProperties = targetInfo.managedServiceProperties;
    // the persistent identifier
    let targetPid = targetInfo.pid;
    // the factory persistent identifier
    let targetPid = targetInfo.factoryPid;

    // to change the or extent the configuration, simple change the configProperties
    configProperties.changedProperty = "test";

    // optional, allowed to return fully different properties
    return configProperties;
}

A component configuration of a configuration plugin might look like this:

Configuration plugin in manifest.json file
{
    "name" : "MyConfigPlugin",
    "provides" : ["ct.framework.api.ConfigurationPlugin"],
    "properties" : {

        "CM-Target-Pids" : ["themes-ThemeModel"], (1)
        "CM-Ranking" : 0                          (2)
    }]
}
1 Array of component identifiers, for which this plugin should be called. If the property is not specified or an empty array, the plugin is called for each target service.
2 If more than one plugin is configured, you can control the order of their execution by this ranking.
Lower rankings are called before higher rankings.
A plugin with higher ranking can overwrite the changes of plugin with a lower ranking.
Default value is 0.

The configuration plugin mechanism is the recommended way to implement Parametrizables , because you can change the configuration of a component based on query parameters before the component is started.

Components Identifiers Format

When using the Configuration Admin Service, components are identified by different kind of identifies, depending on the type of the component. Throughout this guide, we will use the term factoryPid for components that are factories, and pid for all other components, including those created by factories.

Component identifiers
Component type Format Example

Component

<bundleName>-<componentName>
(pid)

map-MapWidgetFactory

Factory component

<bundleName>-<componentName>-Factory
(factoryPid)

agssearch-AGSStore-Factory

Factory component instances

<bundleName>-<componentName>-<index>
(pid)

agssearch-AGSStore-0