Popular Articles
Welcome on Exadel authoring Kit for AEM
-
Attributes of fields
Components Touch UI dialogs honor the concept of global HTML attributes added to rendered HTML tags. To set them via AEM-Dialog-Plugin, you use the @Attribute annotation. You can define an additional property for a Granite UI widget or overwrite an existing one with the @Property annotation. This will be rendered into an appropriate subnode under the component's <cq:dialog> or <cq:design_dialog> node and will affect the behavior of a dialog widget. A @Property has its name and value attributes. The non-blank name can be the name of the property to write into, or else can contain a relative path. The relative path can be defined in such a way that the substring before the ultimate / represents the path, and the substring after the ultimate / represents the property name. Yet another mechanism available is to specify custom properties at Java class level. This can be used: For these goals the @CommonProperties annotation is designed. It accepts similar arguments to those of @Properties annotation. Yet you can also specify the XML scope for each @CommonProperty (this exactly means: in which of the XML trees, or files, the attribute will be stored, default is .content.xml) and a relative path to the root node. See the code snippet: Pay attention to the third and forth @CommonProperty-s. Specifying the path value gives the ability to traverse to any child node of the prepared XML with use of an XPath-formatted string. @CommonProperties are rendered after the XML tree is completed. Thus, setting them provides a kind of "last-chance" alternation of your Touch UI logic (can also be used for debugging). For example, the last @CommonProperty in the sample uses the power of XPath to change size attribute of every single node where size has been set to "L". Only make sure that the path points to at least one truly existing XML node. Note that XPath parser is namespace-agnostic. That is why you need to use /root/inplaceEditing... instead of /jcr:root/cq:inplaceEditing... in the sample above.
Learn More -
Static OptionProvider setup
Several Granite/Touch UI components, such as RadioGroup or Select, facilitate selecting from a set of options. Traditionally, the options are either inlined (the ToolKit offers its @RadioButton and @Option annotations for that) or supplied via a datasource. Both ways have their limitations; the in-line options are not dynamic and potentially lead to a lot of copy-pasting across components, while the datasource pattern requires creating a datasource servlet for every occasion. The ToolKit is bundled with the OptionProvider subsystem that aims at streamlining the usage of dynamic options without programming overhead. The OptionProvider is capable of delivering options in two modes; for a static Granite component it serves as a conventional datasource. Also it has a JSON-supplying servlet that allows for retrieving and updating options dynamically even after a Granite UI has already been rendered. The options managed by OptionProvider can originate from: Moreover, a single OptionProvider can join several data sources, merge and sort their options in a common sequence, enjoy having a "primary" and "fallback" option source, etc. For a static Granite UI component, OptionProvider is set up via a property of such annotations as ButtonGroup, @RadioGroup, or @Select (see the samples below): @OptionProvider annotation has the following properties: value - contains one or more @OptionSource objects, each referring to a single data source (see below); prepend - if specified, defines one or more extra options that will be inserted in the beginning of the option list independently of items acquired via the option source(s). This may be a kind of "none" or "default" option. Each extra option string must consist of a label and a value separated with a colon (:). A value may be an empty string, in which case the option ends with the : sign. If a label or a value itself must contain a colon, it can be escaped with \. If an option with a similar value is already present, the extra option will not be added; append - same as prepend, but the extra item(s) are appended to the option list. A valid list may consist of only prepended and/or appended options without the "external" part; exclude - if specified, defines one or more options (coming from an external source) that should be skipped for the current component. A string passed must match either the option's value or text. The matching is case-insensitive. The wildcard symbol (*) can be used in matching strings; selectedValue - if set to a string that matches the value or the label of one of the datasource options, this option will be rendered as selected by default; sorted - if set to true, options will be sorted in their labels' alphabetical order regardless of the order they arrived from JCR. However, the prepended and appended options will appear in the order they were specified by the developer and will remain at the beginning and the end, respectively. Every @OptionSource object can be specified with the following properties: value - defines the path to a List-like structure, a node tree, a tag folder, or else an URL of an HTTP server that outputs options in JSON format, or a fully qualified name of a Java class (see below). Plain paths and path references are supported. I.e. if a value is presented like /typical/jcr/path, this exact path will be looked for. However, if given in the /some/node@attr format, the attr attribute will be retrieved from /some/node, and its value will be then assumed to be the "true" path. enumeration - stores a reference of a Java class containing constants or an Enum. This property has a lower priority than value, therefore, if both are set, only value will be taken into account. attributeMembers - if specified, defines one or more attributes of a node or public method names of an Enum class to be rendered as HTML attributes of the corresponding Granite UI entities. For example, attributeMembers = "some-jcr-attribute" will be rendered as <coral-select-item data-some-jcr-attribute="literal_value_of_this_attribute"> in HTML; textTransform - if specified, defines the way the label will be transformed before rendering; valueTransform - if specified, defines the way the value will be transformed before rendering; isFallback - determines that the current @OptionSource is only used if other option source entries yielded no results. It is also used if it is the only option source. This option is useful, e.g., when you have a component with an authorable path to an option source. As the component is just created, the path will probably be empty. But still, there will be a possibility to display some options retrieved via the fallback source. Apart from a JCR path, @OptionSource allows specifying a common network URL (note: must be a complete URL string parseable with new URL("..."). The content reached via the URL is expected to be a JSON entity. A JSON array becomes the list of options. A JSON node that has children produces the list of options from the enumeration of child nodes as well much the same way as a JCR resource with children. If the JSON structure is such that the required array is nested deeper than the "root" node, you can add a "path" within the url like http://acme.com/apis/sample.json/internal/path The "path" is defined similar to a Sling suffix: it is the trailing part of the URL after the .json/ extension. There is the possibility to add authentication info to the URL like in the following example: http://admin:admin@localhost:4502/my/service.json. The authentication info is converted into a Basic auth request header and sent with the request. This feature is mainly for the testing and debugging purposes. You should not use it when calling a 3rd-party API. @OptionSource supports usage of Java enums as well as ordinary Java classes that contain a collection of constants. Take a look at the example below: By default, @OptionProvider uses the return value of the .name() method as the text source, and the value of the .toString() as the source for values. No Granite attributes are added by default. You can redefine this via the textMember, valueMember, and attributeMembers properties. E.g. if the enum you want to use has the .getInteger() method, you may specify it like @OptionSource(enumeration = MyEnum.class, valueMember="getInteger"). Besides, you can, for example, ensure that the value of .toString() is rendered as an HTML attribute by specifying attributeMembers="toString". Apart from an enum, you can make an "ordinary" Java class work as the source of options if it contains public static final fields. Names of such fields will become option titles, and the stringified values (String.valueOf(MY_CONSTANT)) will become option values. You are able to select only some of the available fields with @OptionSource(exclude=...). More interestingly, there is a way to "merge" constants into pairs in the way that one constant will manifest an option title and another - the value. Indeed, many AEM constants classes follow this pattern: To handle that pattern, you may reuse the textMember and valueMember in a bit unusual way. Think of them as not just literals but masks. E.g., the following code: This code will make @OptionProvider look for all the constants whose names match the "LABEL_xxxx" pattern and then for those that follow "VALUE_xxxx". The subsets of constants are merged: "red" goes to "red", etc. In this particular case we receive a list of two option: Red:#ff0000 and Green:#00ff00. Note that the "backgrounds" are not included in the list, as they do not correspond to the provided mask. You can introduce a separate Select, this time bor backgrounds, and populate it with @OptionSource(enumeration = ColorConstants.class, textMember="BACKGROUND_NAME_*", valueMember="BACKGROUND_VALUE_*"). Because an @OptionProvider supports path references apart from regular paths, the setting that says "where to look for the path" can be stored in a dialog field other than the one that actually deals with paths. Therefore, it must be possible to dynamically respond to a path reference change. In the real world, it may look like the following. Imagine there is a dialog field (say, a path picker) that allows you to select a data source (say, an EToolbox List). Below is a select dropdown with options coming from the Exadel Toolbox List selected in the above path picker. Here's how it may look in Java code: The facility that makes it possible to dynamically update selectable options is the DependsOn action "update-options" (see more on DependsOn actions here). It accepts any of the conventional OptionProvider params described above in its params collection.
Learn More -
Creating AEM Components with ToolKit annotations
@AemComponent is your entry point to creating component authoring interfaces, such as a Dialog, a Design dialog, or an In-place editing config. When added to a Java class, this annotation must contain generic properties of the component such as title, description, etc. Additionally, @AemComponent can contain references to other Java classes that can be referred to as “views”. If, for instance, you need editConfig, you can add the @EditConfig to the Java class where the @AemComponent annotation is already present. You can also add @EditConfig to another Java class and put a reference to that class in the views collection of @AemComponent. Take note that you need either to put an annotation such as @Dialog or @EditConfig in the same Java class as @AemComponent or add it to another class and then add the class reference to the views collection. You don't need to do both. See the code snippet below, which displays all currently supported @AemComponent properties: Pay attention to the path property. This can be set in two ways. First is the "relative" path that will be resolved from the point specified by the plugin's componentsPathBase setting. This is a common way. Otherwise, you can store an "absolute" path that will be resolved from the root of the package. The absolute path starts with jcr_root folder or any folder that goes immediately under jcr_root. Usually, it would be something like /apps/vendor/components/myComponent. By default, the plugin searches for the directory specified in path and triggers an exception if unable to find one. This is justified because a non-existing folder usually means an invalid component path (a typo in path, or some misconfiguration). However, there is a way to make the plugin create a folder in the package when missing. This can be useful for single-use components (such as Exadel Toolbox Lists' items), or components that do not need a specific HTML or JSP file. In such a case add writeMode = WriteMode.CREATE to your @AemComponent. @Dialog is used for defining component's Touch UI dialog by creating and populating <cq:dialog> node. If you specify title in @Dialog, it will override the title specified in @AemComponent. Skip this if you need to have the same title rendered for the component itself and for the dialog. Pay attention to the forceIgnoreFreshness option. When set to true, it forces the entire dialog to ignore freshness of the Granite UI form beside the dialog. This will allow any component that resides within the dialog to display its default value (if specified) regardless of whether the underlying resource is being created anew (a "fresh" one) or just being edited. Most Granite UI components don't manage default values when editing a "non-fresh" resource. Some offer their own implementation of forceIgnoreFreshness (most notably, the Select component). However, specifying forceIgnoreFreshness at the @Dialog level makes the rest behave in the way the Select does. @DesignDialog is used for defining component's Touch UI dialog by creating and populating <cq:design_dialog> node. If you specify title in @DesignDialog, it will override the title specified in @AemComponent. Skip this if you need to have the same title rendered for the component itself and for the design dialog. Both the Touch UI dialog and design dialog can have either a relative simple or a complex structure. This is why they can either have a plain "all in the same screen" display or be organized with use of nested sections, or containers. In the first case, no specific setup is required. A dialog is automatically assigned the "fixed column" style. Otherwise, a dialog can be rendered in one or more tabs, or be organized as an accordion with one or more panels. To achieve this, you need to put the @Tabs or @Accordion annotation respectively beside your @Dialog/@DesignDialog. See Laying out your dialog for details. If you wish to engage Touch UI dialog features like listeners or in-place editing (those living in <cq:editConfig> node and, accordingly, _cq_editConfig.xml file), add an @EditConfig annotation to your Java class (same as above, you can as well add the annotation to a separate class and then put the reference to this class into @AemComponent's views property). @EditConfig facilitates setting of the following properties and features: Here is a basic sample of an @EditConfig with several of the parameters specified: To specify in-place editing configurations for your component, populate the inplaceEditing property of @EditConfig annotation as follows: Note that if you use type = EditorType.PLAINTEXT, there is an additional required textPropertyName value. If you do not specify a value, the same propertyName string will be used. It’s also possible to create multiple in-place editors as in the following snippet Even more simply, you can specify the richText field to "extend" RTE configuration for a Touch UI dialog elsewhere in your project: From the above snippet, you can see that richText and richTextConfig work together fine. A configuration inherited via richText can be altered by whatever properties that are specified in richTextConfig. If you use both in the same @InplaceEditingConfig, plain values, such as strings and numbers, specified for the @Extends-ed field are overwritten by their correlates from richTextConfig. On the other hand, array-typed values (such as features, specialCharacters, formats, etc.) are actually merged. So you can design a fairly basic set of features, styles, and formats to store in a field somewhere in your project and then implement several richTextConfig-s with more comprehensive feature sets. In addition to <cq:editConfig> itself, Adobe Granite makes it possible to define some in-place editing features for the current component’s children. This is done via the <cq:childEditConfig> with the same general structure as <cq:editConfig>. It facilitates the setting of the following properties and features: Usage: To create a specific decoration tag for your widget, you need to mark your Java class with @HtmlTag and put this class to the Component's views property. Then the <cq:htmlTag> node will be added to your component's nodeset. You can use an approach similar to the one described above for creating a page properties dialog. Specify an absolute path to the JCR node that refers to the page in your @AemComponent like @AemComponent(path = "/apps/my/components/pages/page"}, or else provide a relative path. Even if your componentsPathBase points to an AEM components' root, you can traverse up and to the "pages" sibling node like this: @AemComponent(path = "../pages/page"}. Page properties dialogs are most often composed of tabs. It is a common practice for page properties dialogs to include an existing tab resource instead of enumerating components one by one. This is because tabs are often reused and overlayed across the page hierarchy. You can include an existing tab as follows: Else, you can add components to the tab as usual. Note that usually in a Page properties dialog tab components are wrapped in an additional column so that they get centered on the screen and do not span the whole of the screen width. This can be achieved through a nested column. Else you can use a nested class annotated with FixedColumns for that purpose.
Learn More