Technical details

Along with the existing personalization settings, we introduced a generic interface for registering new settings and using them in specific places in the application. 

Previous Behavior

For any new personalization setting, a developer would have to add:

  • A new personalization key to the Personalization service
  • Methods to set and get the value of the setting from the database to the Personalization service
  • Specific code that uses this setting where it's needed

New Behaviour

The user can directly register the setting in the place it will be used without touching the Personalization Service.

The new interface can be found in PersonalizationService (Public API).

How to use it

  1. Inject mx.SolutionBuilderAgent.PersonalizationService in the place where you need to set/get a setting and call register(). The setting appears in the personalization panel immediately.
  2. If you need to read the setting value in another place, use the get() method.
    If you need to update the value, use the set() method. 

The interface does not have to know about how the setting itself affects the app. In order to give freedom to the developer and simplify logic, the interface allows to read/write to user storage only.

There are 2 types of supported settings: leaf settings and drilldown settings.

How does the register() method work?

Parameter Description Required/Optional
key Unique id; dot notation nests under a drilldown parent Required
title Setting name Required
valueType Required for leaf settings: boolean | 'string | number Required
category Groups settings under a heading; omitted = uncategorized Optional
categoryDescription Displays as a subtitle under the category heading Optional
description Displays as a subtitle under the label Optional
inputType Overrides the control inferred from valueType (e.g. 'radio') Optional
defaultValue Shows the default value until changed, and on reset Optional
value This parameter is rarely set directly; The stored value normally wins Optional
position Lower sorts earlier in the panel. The default value is 999 Optional
hideInPanel Preserves the value without showing a panel row Optional
options Required only for select/radio controls; see below Optional
disabled Greys out the control but keeps it visible Optional
icon Sets the material icon shown on drilldown rows Optional
drilldownDescription Sets the description shown once inside a drilldown; only meaningful on a parent Optional

In order to register a drilldown setting

  • register a parent key
  • then register child keys using dot notation.

The parent is automatically rendered as a drilldown container; no extra config is needed.

 Call `nregister('my-feature') to remove the setting from the panel.

Example

Parent

 this.personalizationService.register({
    key: 'my-feature',
    title: 'myFeatureKey',
    category: 'myCategoryKey',
 }); 

Children - dot notation links them to the parent
this.personalizationService.register({
    key: 'my-feature.sub-setting',
    title: 'myFeatureSubKey',
    valueType: 'string',
    defaultValue: 'default',
});

Control type inference

`valueType` is required and determines the default UI control:

  • `'boolean'` → toggle
  • `'string'` + `options` → select, `'string'` (no options) → text input
  • `'number'` + `options` → select, `'number'` (no options) → number input

Override with inputType parameter

You can force a control to use a specific inputType instead of the inferred one:

 inputType: 'radio' // Forces radio buttons instead of inferred toggle

Open panel to specific category

From any component or service, inject PersonalizationPanelService. Panel opens and smoothly scrolls to the specified category group.

personalizationPanelService.openToCategory(`category-i18n-key`); 

I18n integration

All string fields accept:

  • Plain string: used literally
  • i18n key: looked up via i18n.get() at registration time
  • Fallback: if key doesn't exist, original string is used
  • For dynamic/computed text, resolve first to avoid re-lookup:

personalizationService.register({
    key: 'version-display',
    title: i18n.get('shell.version'),  // Pre-resolved, won't be re-looked-up
});

Setting values at runtime

ALWAYS use get() / set() for reading/writing values:
const current = await personalizationService.get('my-key');
await personalizationService.set('my-key', newValue);

NOT direct mutation:
setting.value = newValue;  // Wrong — won't persist

Storage and persistence

  • Values persist to user storage via UserStorageService
  • Reset button resets to defaultValue
  • Reset All resets all settings at once
  • onChange callbacks trigger on value change

Accessibility

  • Escape: Close panel
  • Tab: Focus wrapping (last → first element)
  • Arrow keys (radio groups): Navigate between options
  • Space (radio groups): Select focused option