close

Filter

loading table of contents...

Studio Developer Manual / Version 2110

Table Of Contents

9.6.2 Standard Component StringPropertyField

The task attempted in this section is to replicate the behavior of the standard StringPropertyField.

Create the new property field as a TypeScript component. You inherit directly from the Ext JS component TextField that is used for displaying the property. Before you can start, you must set the stage for the TypeScript file.

import Config from "@jangaroo/runtime/Config";
import ConfigUtils from "@jangaroo/runtime/ConfigUtils";

import FieldContainer from "@jangaroo/ext-ts/form/FieldContainer";

class ExampleFieldContainer extends FieldContainer {

  constructor(config: Config<ExampleFieldContainer>) {
    super(ConfigUtils.apply(Config(ExampleFieldContainer, {
      // add default Config property values here
    }), config));
  }
}

export default ExampleFieldContainer;

Example 9.30. Custom property field


You are now ready to configure a property of your base class, for example the label alignment.

import Config from "@jangaroo/runtime/Config";
import FieldContainer from "@jangaroo/ext-ts/form/FieldContainer";
import ConfigUtils from "@jangaroo/runtime/ConfigUtils";

class ExampleFieldContainer extends FieldContainer {

  constructor(config: Config<ExampleFieldContainer>) {
    super(ConfigUtils.apply(Config(ExampleFieldContainer, {
      labelAlign: "top",
    }), config));
  }
}

export default ExampleFieldContainer;

The additional Config options supported by your CustomPropertyField are now declared. You can think of the set of these fields as the configuration API description of your component. Any component inherits the Config options from its superclass(es).

The following things are required to declare config options for your custom field:

  1. You declare public variables (without # modifier) in your field class.

  2. You define an interface <YOUR_COMPONENT_CLASS>Config extends Config<<SUPERCLASS>> that expose these variables as config options.

  3. You declare the config interface in your class: declare Config: <YOUR_COMPONENT_CLASS>Config.

import Config from "@jangaroo/runtime/Config";
import Content from "@coremedia/studio-client.cap-rest-client/content/Content";
import FieldContainer from "@jangaroo/ext-ts/form/FieldContainer";
import ValueExpression from "@coremedia/studio-client.ext.client-core/data/ValueExpression";

interface CustomPropertyFieldConfig extends Config<FieldContainer>, Partial<Pick<CustomPropertyField,
  "bindTo" |
  "propertyName"
>> {
}

class CustomPropertyField extends FieldContainer {
  declare Config: CustomPropertyFieldConfig;

  /**
   * A value expression evaluating to the Bean whose property (path) is edited.
   */
  bindTo:ValueExpression<Content>;

  /**
   * The property to bind.
   */
  propertyName: string;

  constructor(config: Config<CustomPropertyField>) {
    super(config);
  }
}

export default CustomPropertyField;

The two properties propertyName and bindTo are mandatory for all property fields. The former declares the name of the property to be edited, which is used both for accessing the model and for localizing the property field. The latter declares a value expression evaluating to the Content object.

import Config from "@jangaroo/runtime/Config";
import Content from "@coremedia/studio-client.cap-rest-client/content/Content";
import FieldContainer from "@jangaroo/ext-ts/form/FieldContainer";
import ValueExpression from "@coremedia/studio-client.ext.client-core/data/ValueExpression";

interface CustomPropertyFieldConfig extends Config<FieldContainer>, Partial<Pick<CustomPropertyField,
  "bindTo" |
  "propertyName" |
  "readOnly" |
  "hideIssues"
  >> {
}

class CustomPropertyField extends FieldContainer {
  declare Config: CustomPropertyFieldConfig;

  /**
   * A value expression evaluating to the Bean whose property (path) is edited.
   */
  bindTo:ValueExpression<Content>;

  /**
   * The property to bind.
   */
  propertyName: string;

  /**
   * Set the <code>readOnly</code> config option of the contained field.
   */
  readOnly: boolean;

  /**
   * Don't show any validation issues on this property field.
   */
  hideIssues: boolean;

  constructor(config: Config<CustomPropertyField>) {
    super(config);
  }
}

export default CustomPropertyField;

Another Config option is to hard-wire the property field to be read-only. As a fourth configuration option, you can disable the visual indication of content errors or warnings via configuration. These options will later on be passed to the appropriate plugins.

Several plugins are available to customize the behavior of your custom property field. For example, the property label is used when displaying the component in a form. Using the following plugin, you can make sure that the label is localized according to the standard localization pattern.

import Config from "@jangaroo/runtime/Config";
import ConfigUtils from "@jangaroo/runtime/ConfigUtils";
import FieldContainer from "@jangaroo/ext-ts/form/FieldContainer";
import SetPropertyLabelPlugin from "@coremedia/studio-client.main.editor-components/sdk/premular/fields/plugins/SetPropertyLabelPlugin";

...

class CustomPropertyField extends FieldContainer {
  declare Config: CustomPropertyFieldConfig;

  ...

  constructor(config: Config<CustomPropertyField>) {
    super(ConfigUtils.apply(Config(FieldContainer, {
      ...
      ...ConfigUtils.append({
        plugins: [
          Config(SetPropertyLabelPlugin, {
            bindTo: config.bindTo,
            propertyName: config.propertyName,
          }),
        ],
      })
    }), config));
  }
}

export default CustomPropertyField;

Now, the actual input property editor is added to the custom field. It needs some configuration and a bunch of plugins of its own. tabIndex is set to 1 to force the text field into the standard focus tab order. The readOnly flag is simply handed through.

import Config from "@jangaroo/runtime/Config";
import ConfigUtils from "@jangaroo/runtime/ConfigUtils";
import FieldContainer from "@jangaroo/ext-ts/form/FieldContainer";
import SetPropertyLabelPlugin from "@coremedia/studio-client.main.editor-components/sdk/premular/fields/plugins/SetPropertyLabelPlugin";

...

class CustomPropertyField extends FieldContainer {
  declare Config: CustomPropertyFieldConfig;

...

  constructor(config: Config<CustomPropertyField>) {
    super(ConfigUtils.apply(Config(FieldContainer, {
      ...
      items: [
        Config(TextField, {
          tabIndex: 1,
          readOnly: config.readOnly,
        }),
      ],
      ...ConfigUtils.append({
        plugins: [
          Config(SetPropertyLabelPlugin, {
            bindTo: config.bindTo,
            propertyName: config.propertyName,
          }),
        ],
      })
    }), config));
  }
}

export default CustomPropertyField;

To register the property field properly with Studio for the purposes of preview-base editing and navigating directly to property field, you need to declare the following plugin:

import Config from "@jangaroo/runtime/Config";
import PropertyFieldPlugin from "@coremedia/studio-client.main.editor-components/sdk/premular/PropertyFieldPlugin";

...

      items: [
        Config(TextField, {
          tabIndex: 1,
          readOnly: config.readOnly,
          ...ConfigUtils.append({
            Config(PropertyFieldPlugin, {
              propertyName: config.propertyName
            }),
          }),
        }),
      ],
...

Using this plugin lets Studio know that your component is authoring a content property. Among other things, this will set up your component to cooperate properly with the content errors and warnings navigation window, and with content shortcuts from the embedded preview.

In order to support content validation, a field should also be highlighted in red (when content errors are present), or orange (when content warnings are present). See Section 9.21.1, “Validators” for information on how to set up server-side content validators. On the client side, the ShowIssuesPlugin as shown below handles all the work. It reads the issues generated on the server and attaches one of the style classes issue-error and issue-warn if an issue is present. Pass all relevant configuration options from the property field to the plugin, especially the options bindTo and propertyName.

Additionally, this plugin highlights the property field in differencing mode when the property value has changed. To this end, it attaches a style class issue-change to its component if the property is reported as changed by the server.

For struct properties, a dot-separated property path can be used as the property name to visualize issues and differences of a property nested in a struct value.

Because the string property field shown here is based on a plain TextField, all formatting rules are already provided in the standard style sheets. For custom components, it might be necessary to add CSS rules for the style classes issue-error, issue-warn, and issue-change in order to visualize issues and changes correctly.

The PropertyFieldPlugin and the showIssuesPlugin are often, but not always attached to the same component. In some cases it may appropriate to designate an outer component as the component to scroll into view when navigating to a property, but to select an inner component to be tagged with issue style classes.

import Config from "@jangaroo/runtime/Config";
import PropertyFieldPlugin from "@coremedia/studio-client.main.editor-components/sdk/premular/PropertyFieldPlugin";
import ShowIssuesPlugin from "@coremedia/studio-client.main.editor-components/sdk/validation/ShowIssuesPlugin";

...

      items: [
        Config(TextField, {
          tabIndex: 1,
          readOnly: config.readOnly,
          ...ConfigUtils.append({
            Config(PropertyFieldPlugin, {
              propertyName: config.propertyName
            }),
            Config(ShowIssuesPlugin, {
              bindTo: config.bindTo,
              ifUndefined: "",
              propertyName: config.propertyName,
              hideIssues: config.hideIssues
            })
          }),
        }),
      ],
...

When the string field is empty, you want to display a message instructing the user to enter a text. Also, the component should be made read only (meaning that the user cannot enter any text but still can mark and copy the content) when the edited content is checked out by another user or is forced to be read only by the document panel. Consequently, two further plugins are added.

import Config from "@jangaroo/runtime/Config";
import BindReadOnlyPlugin from "@coremedia/studio-client.main.editor-components/sdk/premular/fields/plugins/BindReadOnlyPlugin";
import PropertyFieldPlugin from "@coremedia/studio-client.main.editor-components/sdk/premular/PropertyFieldPlugin";
import ShowIssuesPlugin from "@coremedia/studio-client.main.editor-components/sdk/validation/ShowIssuesPlugin";
import SetPropertyEmptyTextPlugin from "@coremedia/studio-client.main.editor-components/sdk/premular/fields/plugins/SetPropertyEmptyTextPlugin";

...

      items: [
        Config(TextField, {
          tabIndex: 1,
          readOnly: config.readOnly,
          ...ConfigUtils.append({
            Config(PropertyFieldPlugin, {
              propertyName: config.propertyName
            }),
            Config(ShowIssuesPlugin, {
              bindTo: config.bindTo,
              ifUndefined: "",
              propertyName: config.propertyName,
              hideIssues: config.hideIssues
            }),
            Config(SetPropertyEmptyTextPlugin, {
              bindTo: config.bindTo,
              propertyName: config.propertyName,
            }),
            Config(BindReadOnlyPlugin, {
              forceReadOnlyValueExpression: config.forceReadOnlyValueExpression,
              bindTo: config.bindTo
            }),
          }),
        }),
      ],
...

Lastly, the most important plugin is added. Editor changes to the field's value need to be passed to the server. The other way around, the field's value should be synchronized to changes of the server-side value. This bi-directional data binding is typically done using the versatile BindPropertyPlugin as shown below.

import Config from "@jangaroo/runtime/Config";
import BindPropertyPlugin from "@coremedia/studio-client.ext.ui-components/plugins/BindPropertyPlugin";
import BindReadOnlyPlugin from "@coremedia/studio-client.main.editor-components/sdk/premular/fields/plugins/BindReadOnlyPlugin";
import PropertyFieldPlugin from "@coremedia/studio-client.main.editor-components/sdk/premular/PropertyFieldPlugin";
import ShowIssuesPlugin from "@coremedia/studio-client.main.editor-components/sdk/validation/ShowIssuesPlugin";
import SetPropertyEmptyTextPlugin from "@coremedia/studio-client.main.editor-components/sdk/premular/fields/plugins/SetPropertyEmptyTextPlugin";

...

      items: [
        Config(TextField, {
          tabIndex: 1,
          readOnly: config.readOnly,
          ...ConfigUtils.append({
            Config(PropertyFieldPlugin, {
              propertyName: config.propertyName
            }),
            Config(ShowIssuesPlugin, {
              bindTo: config.bindTo,
              ifUndefined: "",
              propertyName: config.propertyName,
              hideIssues: config.hideIssues
            }),
            Config(SetPropertyEmptyTextPlugin, {
              bindTo: config.bindTo,
              propertyName: config.propertyName,
            }),
            Config(BindReadOnlyPlugin, {
              forceReadOnlyValueExpression: config.forceReadOnlyValueExpression,
              bindTo: config.bindTo,
            }),
            Config(BindPropertyPlugin, {
              bindTo: config.bindTo.extendBy('properties', config.propertyName),
              ifUndefined: config.ifUndefined,
              bidirectional: config.readOnly,
            })
          }),
        }),
      ],
...

While the list of plugins may appear quite long at first, it is very helpful to be able to separate the different aspects of a property field in different plugins. If you want to provide a custom algorithm of reacting to an empty value, for example, you can easily do so by just omitting the respective plugin declaration, and providing custom handling code - either in the base class or possibly extracted into your own reusable plugin.

Search Results

Table Of Contents
warning

Your Internet Explorer is no longer supported.

Please use Mozilla Firefox, Google Chrome, or Microsoft Edge.