Studio Developer Manual / Version 2101
Table Of Contents
The task attempted in this section is to replicate the behavior of the standard
StringPropertyField
.
Create the new property field as an MXML component, since it is a visual component and needs
no application logic. 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
MXML file.
<?xml version="1.0" encoding="UTF-8"?> <FieldContainer xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns="exml:ext.config" xmlns:editor= "exml:com.coremedia.cms.editor.sdk.config" xmlns:ui="exml:com.coremedia.ui.config">
Example 7.23. Custom property field
You need the fx
namespace for the basic structure of the MXML file, the default
name space for predefined Ext JS components, the editor
namespace for
CMS-specific components and plugins (the "Editor SDK"), and the ui
namespace for
generic plugins at the model layer (the "UI Toolkit").
The MXML root element specifies which class to extend. In this case, the Ext JS
standard component FieldContainer
is extended.
An optional description of the entire class can be placed right after the ?xml
declaration as an XML comment using three (!) dashes at the beginning.
<!--- This class... -->
You are now ready to configure a property of your base class, for example the label alignment.
<?xml version="1.0" encoding="UTF-8"?> <FieldContainer xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns="exml:ext.config" xmlns:editor= "exml:com.coremedia.cms.editor.sdk.config" xmlns:ui="exml:com.coremedia.ui.config" labelAlign="top">
The additional Config options supported by the class are now declared, using ActionScript field
declarations, annotated with [Bindable]
to mark them as Config options.
As any class-level ActionScript code in MXML, these declarations are wrapped inside a
<fx:Script>
element.
Alternatively, in MXML, fields can be declared inside an <fx:Declarations>
element,
but only if they are of a type that can be used from MXML, that is, interfaces cannot be used here.
Fields declared in this manner are always [Bindable]
.
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).
To access the initial Config object handed in to the class constructor, you have to declare
private var config:<type-of-this-component>;
. This is
because in Flex, MXML class constructors do not have any parameters, but in Ext, they receive
the Config object. The declaration of config
lets IDEs resolve that identifier
with the appropriate type. For the same reason, you have to declare a constructor with the
config parameter. Because the constructor body is generated by code derived from your MXML
code, the constructor declaration is native
.
<fx:Script><![CDATA[ import com.coremedia.ui.data.ValueExpression; /** * A value expression evaluating to the Bean whose property (path) is edited. */ [Bindable] public var bindTo:ValueExpression; // provide IDE with access to the initial config handed in: private var config:StringPropertyField; // declare native constructor with config parameter: public native function StringPropertyField(config:StringPropertyField = null); ]]></fx:Script> <fx:Declarations> <!--- The property to bind. --> <fx:String id="propertyName"/> </fx:Declarations>
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.
<fx:Declarations> ... <!--- Set the <code>readOnly</code> config option of the contained field. --> <fx:Boolean id="readOnly"/> <!--- Don't show any validation issues on this property field. --> <fx:Boolean id="hideIssues"/> </fx:Declarations>
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 the property field container and
of the actual property editor.
Let's start with a plugin that is attached directly to the FieldContainer
.
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.
<plugins> <editor:SetPropertyLabelPlugin bindTo="{config.bindTo}" propertyName="{config.propertyName}"/> </plugins>
Now, as the only item of the container, the actual property editor is added, which
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.
<items> <TextField tabIndex="1" readOnly="{config.readOnly}"> <plugins>
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:
<editor: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 7.17.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.
<editor: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. This, too, can be localized uniformly, and the setPropertyLabelPlugin
sets
your property field up to play along nicely.
<editor:SetPropertyEmptyTextPlugin bindTo="{config.bindTo}" propertyName="{config.propertyName}"/>
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:
<editor:BindReadOnlyPlugin forceReadOnlyValueExpression="{config.forceReadOnlyValueExpression}" bindTo="{config.bindTo}"/>
Lastly, the edited value must be passed to the server, and the component should display the
server-side value. This ("data binding") is typically done using the versatile
bindPropertyPlugin
, like shown below. Note that the immediate changes plugin just
triggers the change event often enough, whereas the bindPropertyPlugin
handles
the wiring to the server side, and in turn triggers when a change event is fired.
<ui:BindPropertyPlugin bindTo="{config.bindTo.extendBy('properties', config.propertyName)}" ifUndefined="{config.ifUndefined}" bidirectional="{!config.readOnly}"/>
Finally, end the component definition.
</plugins></TextField></items></FieldContainer>
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.