Skip to content
master
Go to file
Code

Latest commit

…-toolkit-plugin/junit-junit-4.13.1

[Tech] Bump junit from 4.12 to 4.13.1 in /aem-authoring-toolkit-plugin
c267dda

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
 
 
 
 
 
 
 
 

README.md

AEM Authoring Toolkit

AEM Authoring Toolkit Logo

AEM Authoring Toolkit is the set of tools for creating comprehensive TouchUI dialogs for AEM components with use of existing and/or specially designed Java classes.

The Toolkit is aimed at providing the fastest and most intuitive way to supplement an AEM component based on a Sling model class or a POJO with a Touch UI dialog and in-place editing interface.

Resulting dialogs and editors are compliant with the newest facilities of AEM 6.4+ / Coral UI v.3+ and have support for Coral UI v.2.

As the Toolkit was developed, thorough comparative investigation of Coral v.2 and Coral v.3 has been carried out, their features and drawbacks tested, so that backward compatibility is preserved to a highest degree.


Practice to use AEM Authoring Toolkit with our sandbox project under samples


Table of contents

  1. About the Toolkit
  2. Installation
  3. Usage: API
  4. Customization
  5. Plugin settings
  6. Extra features and assets
  7. Samples

About the Toolkit

AEM Authoring Toolkit is intended to free an AEM developer of the necessity to compose and/or edit XML markup by hand. For typical use cases it provides set of reasonable defaults and feature templates. For more specific ones, it is powerful enough to create or edit arbitrary XML nodes and attributes at any level of XML tree, even if not directly supported by any predefined component.

The usage of the Toolkit helps to reduce the risk of compilation extensions and misbehavior in production due to XML design errors and typos. In fact, there's virtually no necessity to edit TouchUI dialogs via XML or crx/de interface anymore.

It works at the package phase of Maven-powered project building process. Dialog markup is autogenerated based on the values of specific Java annotations added to fields of Sling Model / POJO use classes. Then the markup is injected into content package before their deployment to JCR.

The Toolkit

  • Generates rich and versatile AEM components dialogs and in-place editing facilities based on annotations added to backend Java classes and/or their fields.
  • Relieves from writing down the complex XML markup for the authoring interface, preventing difficult-to-trace errors.
  • Allows “inheriting” certain features and markup specificity across components, reducing the amount of Java code to store and maintain.
  • Supports reusing and fine-tuning standard available authoring widgets and redefining their attributes.
  • Ships with the frontend DependsOn package for providing best interactive authoring experience.
  • Highly configurable via both annotation values and Maven configuration.
  • Works with AEM 6.4 and newer on JDK 1.8+.

The Toolkit has been developed in the course of Exadel™ Digital Marketing Practice. It is an evolving project that has yet to reach its maturity. Many new features are planned to be implemented, and more testing for the present features is required.

The authors heartily welcome the creative input from the AEM community worldwide to bring the best of programming techniques and design for creating best authoring and user experience.

Installation

Compiling project by hand

Feel free to clone the project sources and run mvn clean install from the project's root folder. The plugin and the API artifacts will be installed in local .m2 repository. The compiled aem-authoring-toolkit-assets package will be found under /distrib folder of the project. You may then deploy it to your AEM authoring instance as usual.

Using precompiled artifacts

  1. Insert dependency to the API module in the <dependencies> section of the POM file of your bundle:
<dependency>
   <groupId>com.exadel.aem</groupId>
   <artifactId>aem-authoring-toolkit-api</artifactId>
   <version>1.0.1</version> <!-- prefer latest stable version whenever possible -->
</dependency>
  1. Insert plugin's config in the <plugins> section of the POM file of your package:
<plugin>
    <groupId>com.exadel.aem</groupId>
    <artifactId>aem-authoring-toolkit-plugin</artifactId>
    <version>1.1.0</version>
    <executions>
        <execution>
            <goals>
                <goal>aem-authoring</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <!-- MANDATORY: Place here the path to the node under which your component nodes are stored -->
        <componentsPathBase>jcr_root/apps/projectName/components</componentsPathBase>
        <!-- OPTIONAL: specify root package for component classes --> 
        <componentsReferenceBase>com.acme.project.samples</componentsReferenceBase>
        <!-- OPTIONAL: specify list of exceptions, comma-separated, that would cause 
             this plugin to terminate the build process. 
             See section "terminateOn setting" below -->
        <terminateOn>ALL</terminateOn>
    </configuration>
</plugin>

Installing assets

For some of the Toolkit's features to work properly, namely the DependsOn set of instructions, you need to deploy the aem-authoring-toolkit-assets-[version].zip package to your AEM author instance.

If you compile the Toolkit from the source code, you'll find the zip file under ./aem-authoring-toolkit/aem-authoring-toolkit-assets/target directory.

If you are using ready artifacts, the easiest way is to append the DependsOn package to one of your content packages. Since DependsOn is small in size, this will not hamper your deployment process.

If you choose to import the source code and build the project by hand, run Maven with install-assets profile like mvn clean install -Pinstall-assets. You may need to change the following values in the properties part of the project's main POM file:

<aem.host>10.0.0.1</aem.host> <!-- Your AEM instance address or hostname -->
<aem.port>4502</aem.port> <!-- Your AEM instance port -->

Add the following dependency to your content package's POM file.

<dependency>
    <groupId>com.exadel.aem</groupId>
    <artifactId>aem-authoring-toolkit-assets</artifactId>
    <version>1.0.1</version>
    <type>content-package</type>
</dependency>

And then for instance specify the subpackage in your Vault plugin (refer to your content plugin documentation for particulars).

   <plugin>
       <groupId>com.day.jcr.vault</groupId>
       <artifactId>content-package-maven-plugin</artifactId>
       <extensions>true</extensions>
       <configuration>
           <!-- ... -->
           <subPackages>
               <subPackage>
                   <groupId>com.exadel.aem</groupId>
                   <artifactId>aem-authoring-toolkit-assets</artifactId>
                   <filter>true</filter>
               </subPackage>
           </subPackages>
           <targetURL>http://${aem.host}:${aem.port}/crx/packmgr/service.jsp</targetURL>
       </configuration>
   </plugin>

Usage: API

@Dialog annotation

In order to create a dialog you need create a Java class and mark it with @Dialog annotation. All required root attributes and namespace fields for the XML markup of cq:dialog will be added.

Besides, @Dialog possesses properties that are translated into common attributes of AEM component itself, according to the Adobe specification, thus covering most of the use-cases. See the code snippet below:

@Dialog(
    name = "myComponent",
    title = "My AEM Component",
    description = "The most awesome AEM component ever",
    componentGroup = "my-brand-new-components",
    templatePath = "/some/absolute/jcr/path",
    resourceSuperType = "/path/to/resource",
    cellName = "some-cell-name",
    helpPath = "https://www.google.com/search?q=my+aem+component",
    isContainer = true,
    width = 800,
    height = 600,
    extraClientlibs = "cq.common.wcm",
    layout = DialogLayout.TABS
)
public class MyComponentDialog { /* ... */ }

@Tab annotation

There are several ways to create tabbed dialogs. First, you may need to mark a nested class of your @Dialog-annotated class with @Tab annotation. The title property of @Tab will be used as the tab's node name, non-alphanumeric characters skipped (for example, @Tab(title="First tab title!") will produce <firstTabTitle> tag)

@Dialog(layout = Layout.TABS)
public class Dialog {
    @Tab(title = "First tab")
    static class Tab1 {
        @DialogField(label="Field on the first tab")
        @TextField
        String field1;
    }
    @Tab(title = "Second tab")
    static class Tab2 {
        @DialogField(label="Field on the first tab")
        @TextField
        String field2;
    }
}

(Note the layout = DialogLayout.TABS assignment. This is to specify that the dialog must display fields encapsulated in nested classes per corresponding tabs. If layout is skipped, or set to its default FIXED_COLUMNS value, tabs will not show and only "immediate" fields of the basic class will be displayed).

The other way of laying out tabs is to define array of @Tab within @Dialog annotation. Then, to settle a field to a certain tab you will need to add @PlaceOnTab annotation to this particular field. The values of @PlaceOnTab must correspond to the title value of the desired tab. This is a somewhat more flexible technique which avoids creating nested classes and allows freely moving fields. You only need to ensure that tab title is specified everywhere in the very same format, no extra spaces, etc.

@Dialog(
    name = "test-component",
    title = "test-component-dialog",
    tabs = {
        @Tab(title = "First tab"),
        @Tab(title = "Second tab"),
        @Tab(title = "Third tab")
    }
    // layout = Layout.TABS is implied by default here because of "tabs" property set
)
public class TestTabs {
    @DialogField(label = "Field on the first tab")
    @TextField
    @PlaceOnTab("First tab")
    String field1;
 
    @DialogField(label = "Field on the second tab")
    @TextField
    @PlaceOnTab("Second tab")
    String field2;
 
    @DialogField(description = "Field on the third tab")
    @TextField
    @PlaceOnTab("Third tab")
    String field3;
}

Tabs inheritance

In AEM Authoring Toolkit, if a Java class annotated with @Dialog extends another class where potential dialog fields exist, these fields also become the part of the dialog. This may sound inobvious, because Java itself doesn't have the notion of field inheritance while AEM entities have (see overlaying).

Same way, tabs defined in a superclass are "inherited" by the subclass, and the PlaceOnTab instructions are in effect.

If you do not want to have some "inherited" tabs in yor dialog, add the @IgnoreTabs annotation as follows:

@Dialog(
    name = "test-component",
    title = "test-component-dialog",
    tabs = {
        @Tab(title = "Fourth tab"),
        @Tab(title = "Fifth tab")
    }
)
// In AEM Authoring Toolkit, tabs are manipulated by their title strings
@IgnoreTabs({"First tab", "Second tab"})
public class TestTabsExtension { /* ... */}

Note that @IgnoreTabs setting is not inherited, unlike fields themselves, and works only for the class where it was specified.

See also: Fields inheritance and ways to cancel it

Fields annotations

The plugin makes use of @DialogField annotation and the set of specific annotations, such as @TextField, @Checkbox, @DatePicker, etc., discussed further. The latter are referred as field-specific annotations.

@DialogField

Used for defining common properties of a field, such as the name attribute (specifies under which name the value will be persisted, equals to the class' field name if not specified), and also label, description, required, disabled, wrapperClass, renderHidden. In addition, @DialogField provides the possibility to order fields inside the dialog container by specifying ranking value. Generally in reflects the task and capabilities of Adobe's Granite UI Field component.

Typically @DialogField is used in pair with one of field-specific annotations e.g. @TextField.

@Dialog
public class Dialog {
    @DialogField(
        label = "Field 1",
        description = "This is the first field",
        wrapperClass = "my-class",
        renderHidden = true,
        ranking = 5,
        validation = "foundation.jcr.name" // may as well accept array of strings
    )
    @TextField
    String field1;
}

Please note that if @DialogField is specified but a field-specific annotation is not, such field will not be rendered (@DialogField exposes only most common information about a field and does not hint on which HTML component to use).

The other way around, you can indeed specify a field-specific annotation and omit @DialogField. Such field will be rendered (however without label and description, etc.), but its value will not be persisted.

In case when the dialog class extends another class that has some fields marked with field-specific annotations, relevant fields from both ancestral and child class are rendered. All fields from ancestral and child class (even those sharing same name) are considered different and rendered separately. Still namesake fields may interfere if rendered within same container (dialog or tab), so please avoid using same names. Still if you wish to engage some deliberate "field overriding", refer to the chapter on usage of @Extends below.

The fields are sorted in order of their ranking. If several fields have the same (or default) ranking, they are rendered in the order as they appear in the source code. Fields collected from ancestral classes have precedence over fields from child classes.

Widget annotations (A-Z)

@Alert

Used to render components responsible for showing conditional alerts to the users in TouchUI dialogs. Exposes properties as described in Adobe's Granite UI manual on Alert. Usage is similar to the following:

public class DialogWithAlert{
    @Alert(
            size = AlertSize.LARGE,
            text = "Alert content",
            title = "Alert title",
            variant = StatusVariantConstants.SUCCESS
    )
    String alertField;
}

Mind that alert variants available as of Coral 3 are enumerated in StatusVariantConstants class of the Toolkit's API.

@Autocomplete

Used to render the component in TouchUI dialogs. Exposes properties as described in Adobe's Granite UI manual on Autocomplete. Options becoming available as user enters text depend on the value of "namespaces" property of @AutocompleteDataSource. If unset, all tags under the content/cq:Tags JCR directory will be available. Otherwise you specify one or more particular cq:Tag nodes as in the snippet below:

public class AutocompleteDialog {
    @DialogField
    @Autocomplete(multiple = true, datasource = @AutocompleteDatasource(namespaces = {"workflow", "we-retail"}))
    String field;
}
@Button

Used to produce buttons in TouchUI dialogs. Exposes properties as described in Adobe's Granite UI manual on Button.

Note: this widget annotation does not need to be accompanied with @DialogWidget

public class DialogWithButton {
    @Button(
        type = ButtonType.SUBMIT,
        text = "save",
        icon = "edit",
        command = "shift+s",
        variant = ElementVariantConstants.PRIMARY,
        block = true
    )
    String field;
}
@Checkbox

Used to produce checkbox inputs in TouchUI dialogs. Exposes properties as described in Adobe's Granite UI manual on Checkbox.

Checkbox nesting

Sometimes there is a need to supply a list of sub-level checkboxes to a parent checkbox whose displayed state will be affected by the states of child inputs. You can achieve this by specifying sublist property of @Checkbox with a reference to a nested class encapsulating all the sub-level options. This is actually a full-feature rendition of Granite UI NestedCheckboxList.

@Dialog
public class NestedCheckboxListDialog {
    @Checkbox(text = "Level 1 Checkbox", sublist = Sublist.class)
    private boolean option1L1;
 
    private static class Sublist {
        @Checkbox(text = "Level 2 Checkbox 1")
        boolean option2L1;
 
        @Checkbox(text = "Level 2 Checkbox 2", sublist = Sublist2.class)
        boolean option2L2;
    }
 
    private static class Sublist2 {
        @Checkbox(text = "Level 3 Checkbox 1")
        boolean option3L1;
 
        @Checkbox(text = "Level 3 Checkbox 2")
        boolean option3L2;
    }
}
@ColorField

Used to render inputs for storing color values in TouchUI dialogs. Exposes properties as described in Adobe's Granite UI manual on ColorField.

@DatePicker

Used to render date/time pickers in TouchUI dialogs. Exposes properties as described in Adobe's Granite UI manual on DatePicker. You can set the type of DatePicker (whether it stores only date, only time, or both). Also you can display format (see Java documentation on possible formats), minimal and maximal date/time to select (may also specify timezone). To make formatter effective, set typeHint = TypeHint.STRING to store date/time to JCR as merely string and not a numeric value.

public class DatePickerDialog {
    @DialogField
    @DatePicker(
        type = DatePickerType.DATETIME,
        displayedFormat = "DD.MM.YYYY HH:mm",
        valueFormat = "DD.MM.YYYY HH:mm",
        minDate = @DateTimeValue(day = 1, month = 1, year = 2019),
        maxDate = @DateTimeValue(day = 30, month = 4, year = 2020, hour = 12, minute = 10, timezone = "UTC+3"),
        typeHint = TypeHint.STRING
    )
    String currentDate;
}
@FileUpload

Used to render the FileUpload components in TouchUI dialogs. Exposes properties as described in Adobe's Granite UI manual on FileUpload. You can specify MIME types of files acceptable, graphic styles of the created component. It is required to specify uploadUrl to an actual and accessible JCR path (may also specify a sub-node of an existing node that will be created as needed). Sling shortcut ${suffix.path} for component-relative JCR path is also supported.

public class FileUploadDialog {
    @DialogField
    @FileUpload(
        uploadUrl = "/content/dam/my-project",
        autoStart = true,
        async = true,
        mimeTypes = {
            "image/png",
            "image/jpg"
        },
        buttonSize = ButtonSize.LARGE,
        buttonVariant = ButtonVariant.ACTION_BAR,
        icon = "dataUpload",
        iconSize = IconSize.SMALL
    )
    String currentDate;
}
@ImageUpload

Designed as a companion to @FileUpload, mimics the features of FileUpload component that was there before Coral 3 was introduced, and the build-it upload component situated at cq/gui/components/authoring/dialog/fileupload in your AEM installation. Technically, this is but another rendition of FileUpload logic aimed at mainly uploading images via drag-and-drop

public class ImageFieldDialog {
    @DialogField
    @ImageUpload(
        mimeTypes="image",
        title="Upload Image Asset",
        sizeLimit = 100000
    )
    String file;
}
@Heading

Used to render heading element in TouchUI dialogs. Exposes properties as described in Adobe's Granite UI manual on Heading.

Note: this widget annotation does not need to be accompanied with @DialogWidget

public class DialogWithHeading {
    @Heading(text = "Heading text", level = 2)
    String heading;
}
@Hidden

Used to render hidden inputs in TouchUI dialogs. Exposes properties as described in Adobe's Granite UI manual on Hidden.

@NumberField

Used to render inputs for storing numbers in TouchUI dialogs. Exposes properties as described in Adobe's Granite UI manual on NumberField.

@Password

Used to render password inputs in TouchUI dialogs. Exposes properties as described in Adobe's Granite UI manual on Password. If you wish to engage "confirm password" box in your dialog's layout, create two @Password-annotated fields in your Java class, then feed the name of the second field to the retype property for the first one. If the values of the two fields do not match, validation error is produced.

public class PasswordDialog {
    @DialogField
    @Password(retype = "confirmPass")
    String pass;
    @DialogField
    @Password
    String confirmPass;
}
@PathField

Used to produce path selectors in TouchUI dialogs. Exposes properties as described in Adobe's Granite UI manual on PathField.

@RadioGroup

Used to render groups of RadioButtons in TouchUI dialogs. Exposes properties as described in Adobe's Granite UI manual on RadioGroup. The usage is as follows:

public class RadioGroupDialog {
    @DialogField
    @RadioGroup(
        buttons = {
            @RadioButton(text = "Button 1", value = "1", checked=true),
            @RadioButton(text = "Button 2", value = "2"),
            @RadioButton(text = "Button 3", value = "3", disabled=true)
        }
    )
    String field8;
}

Mind you can set up to use a datasource instead of list of options. This way your @Select would look as follows:

public class RadioGroupDialog {
    @DialogField
    @RadioGroup(datasource = @DataSource(path = "my/path", resourceType = "my/res/type"))
    String field8;
}
@Select

Used to render select inputs in TouchUI dialogs. Exposes properties as described in Adobe's Granite UI manual on Select. @Select comprises set of @Option items. Each of them must be initialized with mandatory value and several optional parameters, such as text (represents option label), boolean flags selected and disabled, and also String values responsible for visual presentation of an option: icon, statusIcon, statusText and statusVariant.

Here is a code snippet for a typical @Select usage:

public class MyDialogWithDropdown {
    @DialogField(label = "Rating")
    @Select(
        options = {
            @Option(
                    text = "1 star", 
                    value = "1", 
                    selected = true,
                    statusIcon = "/content/dam/samples/icons/1-star-rating.png",
                    statusText = "This is to set 1-star rating",
                    statusVariant = StatusVariantConstants.SUCCESS
            ),
            @Option(text = "2 stars", value = "2"),
            @Option(text = "3 stars", value = "3"),
            @Option(text = "4 stars", value = "4", disabled = true),
            @Option(text = "5 stars", value = "5", disabled = true)
        },
        emptyText = "Select rating",
        multiple = false,
        translateOptions = false,
        ordered = false,
        emptyOption = false,
        variant = SelectVariant.DEFAULT,
        deleteHint = false,
        forceIgnoreFreshness = false
    )
    String dropdown;
}

Mind you can set up to use a datasource instead of list of options. This way your @Select would look as follows:

public class MyDialogWithDropdown {
    @DialogField(label = "Rating")
    @Select(
        datasource = @DataSource(path = "my/path", resourceType = "my/res/type"),
        emptyText = "Select rating"
    )
    String dropdown;
}
@Switch

Used to render on-off toggle switches in TouchUI dialogs. Exposes properties as described in Adobe's Granite UI manual on Switch.

@TextArea

Used to render textarea HTML inputs in TouchUI dialogs. Exposes properties as described in Adobe's Granite UI manual on TextArea.

@TextField

Used to produce text inputs in TouchUI dialogs. Exposes properties as described in Adobe's Granite UI manual on TextField.

Fields grouping and multiplying

@FieldSet

Used to logically group a number of different fields as described in Adobe's Granite UI manual on FieldSet. This goal is achieved by an external or a nested class that encapsulates grouping fields. Then a <OtherClass>-typed field is declared, and @FieldSet annotation is added.

Hierarchy of classes is honored (so that a FieldSet-producing class may extend another class from same or even foreign scope. Proper field order within a fieldset can be guaranteed by use of ranking values (see chapter on @DialogField above).

Names of fields added to a FieldSet may share a common prefix specified in namePrefix property. This can be a simple word, or a string trailed with slash. In the latter case, values assigned to the FieldSet's fields will directed to a subnode of the resource being edited.

If you do not need a margin around the fieldset added by default, add @Attribute(className="u-coral-noMargin")

public class DialogWithFieldSet {
    @FieldSet(title = "Field set example", namePrefix="fs-")
    private FieldSetExample fieldSet;
 
    static class FieldSetExample extends ParentFieldSetExample {
        // Constructors are omitted
        // Rankings are not necessary, put here to show the way a parent's field can be
        // rendered after the fields of a subclass, and not before
        @DialogField(ranking = 1)
        @TextField
        String field6;
 
        @DialogField(ranking = 2)
        @TextField
        String field7;
 
        @DialogField(ranking = 3)
        @TextField
        String field8;
    }
 
    private static class ParentFieldSetExample {
        //Constructors are omitted for simplicity
        @DialogField(ranking = 4)
        @TextField
        String field6;
    }
}
@MultiField

Used to facilitate multiple (repeating) instances of same fields or same groups if fields as described in Adobe's Granite UI manual on MultiField. The logic of the component relies on the presence of a nested class encapsulating one or more fields to be repeated. Reference to that class is passed to @MultiField's field property. See below how it works for a single field repetition, and for a subset of fields multiplied.

Simple multi field
public class SimpleMultiFieldDialog {
    @DialogField(label="Multi")
    @MultiField(field = MultiFieldContainer.class)
    String multiField;
 
    static class MultiFieldContainer {
        @DialogField
        @TextField
        String dialogItem;
    }
}

######Composite multi field

public class CompositeMultiFieldDialog {
    @DialogField
    @MultiField(field = MultiCompositeField.class)
    String multiComposite;
 
    private static class MultiCompositeField {
        @DialogField
        @TextField(description = "Multi Text")
        String multiText;
 
        @DialogField(description = "Multi Checkbox")
        @Checkbox(text = "Multi CheckBox")
        String checkboxMulti;
    }
}

Note that the inheritance of class(-es) encapsulating multifield items works here the same way as for the @FieldSet.

@Multiple

The easiest way to create a Multifield is with the @Multiple annotation. Just add it to the Java class field where a widget annotation is already present. A simple multifield containing this particular widget will be created on the fly.

If you, on the other hand, add @Multiple to a field marked with @Fieldset, a composite multifield will be created (much like the one you could have adding @Multifield annotation itself). Moreover, you can add @Multiple to a mere @Multifield-marked field and enjoy a sort of "multifield of multifields".

Please note, however, that @Multiple is primarily designed for easy, "quick give me a multifield out of my single widget without creating a nested class" cases. For more complicated cases, it lacks tweaking capablities that @Multifield itself presents.

Common attributes of fields

Components TouchUI 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.

public class DialogWithHtmlAttributes {
    @DialogField
    @TextField
    @Attribute(
        id = "field1-id",
        className = "field1-attribute-class",
        data = {
            @Data(name = "field1-data1", value = "value-data1"),
            @Data(name = "field1-data2", value = "value-data2")
    })
    String field1;
}

Implementing RichTextEditor

RichTextEditor (RTE) is somehow special yet vastly demanded Coral dialog component that provides possibility of editing strings and texts with WYSIWYG experience. the functionality of the component is based upon set of plugins, either built-in or custom. Most plugins expose sets of "features" reflected by UI elements (buttons, or dropdown lists, or button panels, or floating panels - so called "popovers").

Traditionally, to add a feature to RichTextEditor a user needs to include a string representing a button in toolbar attributes of one or more XML nodes, include another node representing a plugin to a corresponding plugin tree and/or populate features attribute of that node and then possibly set plugin's custom features, each in one's own specific format. It the feature is to sit in a floating panel, the <popovers> node and its sub-nodes must be additionally taken care of.

The Toolkit streamlines that process a lot. ######RTE features and popovers Using AEM Authoring Toolkit, to initialize a RichTextEditor component with certain plugins/features, a user needs to apply @RichTextEditor annotation to a class field and then set the annotation's features property. This property accepts an array of strings in plugin#feature format. To specify a popover, one adds to the array a square-bracketed string like [plugin#feature1, plugin#feature2,...plugin#featureN] or [plugin:feature1:feature2:...featureN] depending on plugin's specification.

The built-in plugin#feature pairs are stored as constants of RteFeatures class for convenience. Feature sets of various built-in plugins grouped by plugin (so that to show them in separate popovers) are stored within RteFeatures.Popovers class. Definitions of specific panels are in RteFeatures.Panels class (for now only one specific panel, "table", is supported).

Thus the nearly maximal set of built-in features for a RichTextEditor component can be exposed in the following manner:

@RichTextEditor(
    features = {
        RteFeatures.Popovers.CONTROL_ALL,
        RteFeatures.UNDO_UNDO,
        RteFeatures.UNDO_REDO,
        RteFeatures.SEPARATOR,
        RteFeatures.Popovers.EDIT_ALL,
        RteFeatures.Popovers.FINDREPLACE_ALL,
        RteFeatures.SEPARATOR,
        RteFeatures.Popovers.FORMAT_ALL,
        RteFeatures.Popovers.SUBSUPERSCRIPT_ALL,
        RteFeatures.Popovers.STYLES,
        RteFeatures.Popovers.PARAFORMAT,
        RteFeatures.Popovers.JUSTIFY_ALL,
        RteFeatures.Popovers.LISTS_ALL,
        RteFeatures.Popovers.LINKS_MODIFY_DELETE,
        RteFeatures.SEPARATOR,
        RteFeatures.Panels.TABLE,
        RteFeatures.SPELLCHECK_CHECKTEXT,
        RteFeatures.Popovers.MISCTOOLS_ALL,
        RteFeatures.FULLSCREEN_TOGGLE,
    }
)
private String text;

Apart from built-in features, you can append any features provided by a custom RTE plugin using the same string format. Technically, the plugin searches for plugin#feature strings and converts each into a toolbar button. Then it searches for [plugin#feature1,plugin#feature2] patterns and converts each into a popover. First plugin#feature entry becomes the button to bring on the popover. This one and all the rest entries are shown as the popover content.

Thus, you alter any of the predefined popovers or compose a different popover (from either built-in, or custom features, or both). See the following snippet that indicates appending a custom feature, then two custom popovers to a feature set:

@RichTextEditor ( /* ... */
    features = {
        "some#feature",
        RteFeatures.BEGIN_POPOVER +
            "myPlugin#feature1" + RteFeatures.FEATURE_SEPARATOR +
            "myPlugin#feature2" +
        RteFeatures.END_POPOVER,
        RteFeatures.BEGIN_POPOVER +
            RteFeatures.FORMAT_BOLD + RteFeatures.FEATURE_SEPARATOR +
            "myAnotherPlugin#feature3" + RteFeatures.FEATURE_SEPARATOR +
            RteFeatures.LINKS_ANCHOR +
        RteFeatures.END_POPOVER
    }
)
RTE view modes

RichTextEditor configuration allows specifying features for three different editor modes. These are:

  • inline (for an ordinary dialog window),
  • dialogFullScreen (for a "maximized" dialog window), and
  • fullscreen (for a dialog window which shows after "ToggleFullscreen" button pressed and for a "maximized" in-place editor).

You can separately specify set of features for inline and dialogFullScreen/fullscreen modes by populating "features" and fullscreenFeatures properties, accordingly. These two properties accept values in the same format. Or you can use one set of features for either, by populating only "features".

Generally it is recommended that a narrower set of features be used for the inline, e.g. "windowed" mode, and popover elements avoided in this mode due to unwanted visual effects, and a wider set of features with popovers unrestricted be used for any of the "fullscreen" modes.

If neither features nor fullscreenFeatures are populated, a default set of buttons will be generated for each of the three editor modes.

Toolbar icons

A user can override existing or add new icon definitions for toolbar buttons via icons property. Several icon definitions may be missing from Coral installation. To provide complete user experience with the mentioned full feature set, you may use the following snippet:

@RichTextEditor ( /* ... */
    icons = {
        @IconMapping(command = "#edit", icon = "copy"),
        @IconMapping(command = "#findreplace", icon = "search"),
        @IconMapping(command = "#links", icon = "link"),
        @IconMapping(command = "#table", icon = "table"),
        @IconMapping(command = "#subsuperscript", icon = "textSuperscript"),
        @IconMapping(command = "#control", icon = "check"),
        @IconMapping(command = "#misctools", icon = "fileCode"),
    }
)
Settings for pasting text

One substantial concern for a RichTextEditor component maintainer is the rules for processing the input from clipboard. A user may specify defaultPasteMode and htmlPasteRules for dealing with non-plaintext clipboard content as in the below snippet:

@RichTextEditor ( /* ... */
    defaultPasteMode = PasteMode.WORDHTML,
    htmlPasteRules = @HtmlPasteRules(
        allowBold = false,
        allowItalic = true,
        allowImages = false,
        allowLists = AllowElement.ALLOW,
        allowTables = AllowElement.REPLACE_WITH_PARAGRAPHS,
        allowedBlockTags = {"p", "sub"},
        fallbackBlockTag = "p"
    )
)

Setting the htmlLinkRules property allows to additionally control the way internal and external links in pasted text are processed. See the following snippet:

@RichTextEditor ( /* ... */
    htmlLinkRules = @HtmlLinkRules(
        cssInternal = "my-internal-link-style",
        cssExternal = "my-external-link-style",
        targetInternal = LinkTarget.MANUAL,
        targetExternal = LinkTarget.BLANK,
        protocols = {Protocol.HTTP, Protocol.HTTPS},
        defaultProtocol = Protocol.HTTPS,
    )
)
Inserting special characters

Among the commonly user RTE assets is the misctools#specialchars feature that represents an "Insert symbol"-like dialog. The set of Unicode characters to offer may be defined in specialCharacters property. This is an array that stores either a single HTML entity definition or a Unicode range (decimal values) as in the following snippet:

@RichTextEditor ( /* ... */
    specialCharacters = {
        @Characters(name = "Copyright", entity = "&copy"),
        @Characters(name = "Euro sign", entity = "&#x20AC"),
        @Characters(name = "Registered", entity = "&#x00AE"),
        @Characters(name = "Trademark", entity = "&#x2122"),
        @Characters(rangeStart = 512, rangeEnd = 514),
        @Characters(rangeStart = 998, rangeEnd = 1020)
    }
)
Paragraph tagging and text styles

Set of formatting tags for a "paraformat" button may be defined in formats property as in the snippet:

@RichTextEditor ( /* ... */
    formats = {
        @ParagraphFormat(tag = "h1", description = "My custom header"),
        @ParagraphFormat(tag = "h2", description = "My custom subheader")
    }
)

RichTextEditor allows to define text visual features by CSS rules. Property externalStyleSheets is for specifying array of strings representing paths to JCR-stored CSS files that will be applied to the RTE content. After externalStyleSheets are set, one may populate the styles property with the CSS classes that will be offered to a use in styles dropdown, as in the below snippet:

@RichTextEditor ( /* ... */
    externalStyleSheets = {
        "/etc/clientlibs/myLib/style1.css",
        "/etc/clientlibs/myLib/style2.css"
    },
    styles = {
        @Style(cssName = "my-style", text = "My custom style 1"),
        @Style(cssName = "my-another-style", text = "My custom style 2")
    }
)

(Unlike formats above, these are not HTML tag definitions but rather <span style='...'>...</span> blocks that will be added to selected text.)

Miscellaneous tweaks

Additionally, a user can specify amount of edit operations stored for undo/redo logic (via maxUndoSteps property), the width of tabulation (in spaces, via tabSize property) and the indentation margin of lists (in spaces, via indentMargin property).

Altering field's decoration tag with @HtmlTag

To create a specific decoration tag for your widget, you need to mark your Java class with @HtmlTag. Then the cq:htmlTag node will be added to your component's nodeset.

@HtmlTag(
    className = "my-class",
    tagName = "span"
)
public class MyComponentDialog { /* ... */ }

Fields inheritance and ways to cancel it

Same as dialog tabs, dialog fields are "inherited" across the Java classes. However, there is the possibility to "cancel" a superclass-bound field from rendering in a current dialog or a narrower container. Add @IgnoreFields annotation to the current class:

    @Dialog(
            name = "component-dialog",
            title = "Dialog Title",
            layout = DialogLayout.TABS
    )
    @IgnoreFields({
            // The "field" parameter is mandatory while "source" may be skipped
            // In such case, current class is implied  
            @ClassField(source = ComponentWithTabsAndInnerClass.class, field = "field1"),
            @ClassField(field = "field2")
    })
    public static class ComponentDialog extends MultipleFieldsDialog { /* ... */ }

This setting works similarly for dialog classes, and also FieldSets and Multifields. Yet there is the possibility to control secondary containers (FieldSets and Multifields) in an even more flexible manner.

If we add an @IgnoreFields(@ClassField(...)) instruction with its source pointing to a FieldSet or Multifield class to the current dialog, the field will be ignored in all fieldsets / multifields of the given type that are declared within the dialog.

But if we need to ignore a field in one particular FieldSet of Multifield within the dialog, we may add @IgnoreFields(@ClassField(...)) to that very field, below @FieldSet or @Multifield annotation accordingly. The source parameter is naturally skipped in this case. The setting will take effect for the field and will not affect others.

Same as for tabs ignoring, the @IgnoreFields setting is not inherited, unlike fields themselves, and works only for the class where it was specified.

@Extends-ing fields annotations

Several dialog fields, such as RichTextEditor field, may require vast and sophisticated annotation code. If there are multiple such fields in your Java files, they may become overgrown and difficult to maintain. Moreover, you will probably face the need to copy the lengthy annotation listings between fields, e.g. if you plan to use several RTE boxes with virtually the same set of toolbar buttons, plugins, etc.

One of the powerful features of AEM Authoring Toolkit is its extension/inheritance technique that helps to cope with that issue.

Suppose that you have marked private String sampleText; in your HelloWorld.java class with several AEM Authoring Toolkit annotations and wish to use the same set of annotations for private String anotherField; in this very or other class.

To achieve this, add to the anotherText field the @Extends annotation pointing to sampleText. Whatever field-specific annotation you defined for the sampleText field will now be "inherited" by anotherText. You still can add another @TextField to anotherText with properties that were not specified in sampleText field or have different values there. Thereby "inheritance with overriding" is achieved. See the following snippet:

public class CustomPropetiesDialog {
    @DialogField(label = "My text field")
    @Extends(value = HelloWorld.class, field = "sampleText")
    @TextField(emptyText = "Enter your text here")
    private String anotherText;
    /* ... */
}

The plugin will first look for the sampleText field in HelloWorld class, and if found, will use that field's @DialogField and @DatePicker annotations to prepare XML markup for the current field. For such properties as label or emptyText that have local "overrides", the local values will be used, rest will be taken from the anotherText field.

Note that it is possible that the "parent" field in its own turn @Extends-es some third "grandparent" field, so rendering starts from "grandparent" (same as it is with inheriting class members in object-oriented programming).

Yet make sure that all the fields involved have the same component annotation. A field marked with, say, @DatePicker will not extend some @Checkbox field, and so on.

Also mind that when you extend a field and add another field-specific annotation to override some properties (like in the sample above), property values are either replaced or appended (like adding values from an array-typed property of "child" to the array-typed property of "parent"), but not erased. You cannot replace a non-empty value of a "parent" with a blank, or empty, value of a "child". So take care to design you "inheritance tree" starting from fields with more abstract, less populated component annotations, and then shifting to more specific ones.

EditConfig settings

If you wish to engage such TouchUI dialog features as listeners or in-place editing (those living in <cq:editConfig> node and, accordingly, _cq_editConfig.xml file), add @EditConfig annotation to your Java class.

It facilitates setting of the following properties and features:

  • Actions
  • Empty text
  • Inherit
  • Dialog layout
  • Drop targets
  • Form parameters
  • In-place editing
  • Listeners

In-place editing configurations

To specify in-place editing configurations for your component, populate the inplaceEditing property of @EditConfig annotation like follows.

@Dialog(name = "componentName")
@EditConfig(
    inplaceEditing = @InplaceEditingConfig(
        type = EditorType.TEXT,
        editElementQuery = ".editable-header",
        name = "header",
        propertyName = "header"
    )
)
public class CustomPropertiesDialog {
    @DialogField
    @TextField
    String field1;
}

Note that if you use type = EditorType.PLAINTEXT, there is an additional required textPropertyName value. If you do not specify a value for that, same string as for propertyName will be used.

There is the possibility to create multiple in-place editors like in the following snippet:

@Dialog(name = "componentName")
@EditConfig(
    inplaceEditing = {
        @InplaceEditingConfig(
            type = EditorType.PLAINTEXT,
            editElementQuery = ".editable-headline",
            name = "headline",
            propertyName = "headline"
        ),
        @InplaceEditingConfig(
            type = "CustomType",
            editElementQuery = ".editable-description",
            name = "description",
            propertyName = "description"
        )
    }
)
public class CustomPropertiesDialog {
    @DialogField
    @TextField
    String field1;
}

RichText configuration for the in-place editing

With an in-place configuration of type = EditorType.TEXT, a richTextConfig may be specified with syntax equivalent to that of @RichTextEditor component annotation. Here is a very basic example of "richTextConfig" for an in-place editor

@InplaceEditingConfig (
    type = EditorType.TEXT, ...
    richTextConfig = @RichTextEditor(
        features = {
            RteFeatures.UNDO_UNDO,
            RteFeatures.UNDO_REDO,
            RteFeatures.Popovers.MISCTOOLS,
            RteFeatures.Panels.TABLE,
            RteFeatures.FULLSCREEN_TOGGLE
        },
        icons = @IconMapping(command = "#misctools", icon = "fileCode"),
        htmlPasteRules = @HtmlPasteRules(
            allowBold = false,
            allowImages = false,
            allowLists = AllowElement.REPLACE_WITH_PARAGRAPHS,
            allowTables = AllowElement.REPLACE_WITH_PARAGRAPHS
        )
    )
)
class DialogSample { /* ... */ }

Ever simpler, you can specify the richText field to "extend" RTE configuration specified for a Touch-UI dialog elsewhere in your project:

@InplaceEditingConfig (
    type = EditorType.TEXT,
    richText = @Extends(value = HelloWorld.class, field = "myRteAnnotatedField"),
    richTextConfig = @RichTextEditor(/* ... */)
)

From the above snippet you can see that richText and richTextConfig work together fine. Configuration inherited via richText can be altered by whatever properties 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. But array-typed values (such as features, specialCharacters, formats, etc.) are actually merged. So you can design a fairly basic set of features, styles, formats to store in a field somewhere in your project and then implement several richTextConfig-s with more comprehensive and different feature sets.

ChildEditConfig settings

Apart from cq:editConfig itself, the Adobe Granite gives you possibility to define some im-place editing features for the children of the current component. This is done via the cq:childEditConfig node having generally the same structure as cq:editConfig.

This one can be pipolated with use of @ChildEditConfig annotation.

It facilitates setting of the following properties and features:

  • Actions
  • Drop targets
  • Listeners

Usage is as follows:

@Dialog(name = "parentComponent")
@ChildEditConfig(
    actions = {"edit", "copymove"}
)
public class Dialog {
    @DialogField
    @TextField
    String field1;
}

Value restrictions

Value restrictions can be imposed on some of the annotations' fields. For instance, if you set a negative integer to a field that requires a positive one (say, tabIndent or undoSteps field of @RichTextEditor), or you set some string that is not a complete JCR path to a field requiring such (e.g uploadUrl field), a warning will be issued. You may change this behavior by altering terminateOn in plugin's <configuration> section in POM file. Put com.exadel.aem.toolkit.core.exceptions.ValidationException there to see Maven build terminated in case a restriction is broken (unless the value is already set to ALL). Same approach applies to any other particular kind of exception you want the plugin to terminate on.

Customization

The AEM Authoring Toolkit allows to flexibly customize the structure of TouchUI dialog markup using the following approaches

Custom annotations and handlers

You can create your custom annotations to change existing node structure of a particular field. One requirement for a custom annotation is to be in turn annotated with @DialogAnnotation or @DialogWidgetAnnotation. Its source property is needed to pick up appropriate custom handler (see below).

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@DialogWidgetAnnotation(source = "helloworld")
public @interface HelloWorld {
    String greeting() default "Hello World!";
}

Basically, declaring @interface HelloWorld as above and then using it to annotate a field in a Sling POJO/model Java class is enough to render an XML node in TouchUI dialog markup that will have greeting attribute with "Hello World!" value. Combined with @DialogField annotation, it would produce a nearly complete TouchUI dialog component (you may add resourceType property with a default value to your @HelloWorld interface and anything else necessary to mimic a "regular" dialog component).

Still there might be a necessity to implement special rendering for your custom annotation. To achieve this, you may create a handler class implementing either DialogHandler or DialogWidgetHandler interfaces.

DialogHandler interface is for custom processing of the whole Dialog XML structure. It has 'getName()' method to be overridden in your implementation. This represents the name we need it to bind a custom annotation that has equivalent source value, with the handler. Since DialogHandler extends BiCounsumer<Element, Class>, you will then need to override the .accept() method. The Element instance represents root element of the corresponding XML file, and the Class<?> parameter points to the current AEM component Java class.

Same way, if you want to apply the handler's logic to only particular field of class, you can add @DialogWidgetComponent to an own-written annotation, and then implement DialogWidgetHandler interface that extends BiCounsumer<Element, Field>.

If you want your annotations' fields to be automatically transformed to TouchUI node properties, supply you annotation with @PropertyMapping. This way they will get to the final XML markup with no need of a handler.

To automatically map only some properties, you may populate mappings attribute of @PropertyMapping with names of corresponding fields. The other way around, you may add specific @IgnorePropertyMapping annotation to some of the fields themselves. The first way is more convenient if you have only several of the multitude of fields to be auto-mapped, while the second if rather for the case that you have only several fields to skip, and many others to auto-map.

Here is how a custom DialogAnnotation and a custom DialogHandler may look like:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@DialogAnnotation(source = "testSource")
@PropertyMapping
public @interface CustomDialog {
    String greeting() default "Hello World!";
}
public class CustomDialogHandler implements DialogHandler {
    @Override
    public String getName() {
        return "testSource";
    }
    @Override
    public void accept(Element element, Class<?> dialogClass) {
        element.setAttribute("test", dialogClass.getSimpleName());
    }
}

This way, added to a Sling model / POJO use class like

@Dialog(name = "componentName")
@CustomDialog
public class CustomStructureDialog {
    @DialogField(label = "Field 1")
    @TextField
    String field1;
}

...the @CustomDialog annotation will result in greeting and test attributes being added to the <cq:dialog> node of TouchUI markup (first from the auto-mapping, and second because of handler routine).

There's another option for @PropertyMapping, and this is to specify its prefix value. If prefix is set to simple literal, like "cq:", all of the auto-mapped attribute names will be prepended with this. Yet if the prefix is a relative path, like "granite:data/", all of the auto-mapped attributes will go to the specifically created sub-node (particularly useful for creating granite:data nodes for TouchUI tweaks).

Runtime methods for custom handlers

If you define in your handler class a field of type RuntimeContext marked with @Injected annotation, the link to the global RuntimeContext object will be injected by the Maven plugin. It allows to engage a number of utility methods and techniques, such as those of the XmlUtility interface. Of special interest are the methods .createNodeElement() with overloads for creating nodes with specific jcr:primaryType, sling:resourceType and other attributes, .appendChild() with overloads for appending or merging a newcomer node to a set of existing child nodes of a local root, and .setAttribute() with overloads for populating previously created node with generic-typed annotation values, optionally validated and then optionally fallen back to defaults.

Developer can (and is encouraged to) also call .getExceptionHandler() method whenever his or her logic is ought to throw or manage an exception. This way, all the exceptions from either built-in or custom routines are managed uniformly.

Restricting custom annotations' values

You can modify rendering of your custom-developed annotations by adding built-in "meta"-annotations, such as @PropertyRendering or @ValueRestriction.

If you need to store a value with a name other that the corresponding annotation field's name (this may be the case if a namespaced name - one with a colon - required), prepend @PropertyRendering(name = "some:specific-name") to your method.

To avoid writing down an attribute with a value implied by Coral/Granite engine and thus redundant, use @PropertyRendering(ignoreValues = "this-default"). A single string or an array of strings may be provided.

There are also specific flags in @PropertyRendering "meta"-annotation, namely allowBlank to render attributes with values resulting in an empty or blank string (turned off by default), and ignorePrefix - to not use prefix defined at FieldSet level with this attribute name.

@ValueRestriction accepts fully qualified name of a class implementing Validator interface as an argument. Predefined names are in ValueRestrictions class.

You can develop your own value restrictions. It as easy as implementing Validator interface yourself.

Suppose you are shipping an AEM component that has its webProtocols property accepting an array of strings. You want to restrict user's input to only specific protocols. Define your custom annotation as follows:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@DialogComponentAnnotation(source = "helloworld")
public @interface HelloWorld {
    /* ... */
    @ValueRestriction("SupportedProtocols")
    String[] webProtocols() default {};
}

Then create SupportedProtocols.java class with the following code:

@SuppressWarnings("WeakerAccess")
public class SupportedProtocols implements Validator {
    private static final String[] PROTOCOLS = {"http:", "https:", "ftp:"};
 
    /**
    * Returns whether this particular test is applicable to the value specified (to sort out cases when,
    * for instance, this validation erroneously applied to some "long value();"
    * @param obj user-inputted value of a component annotation property
    * @return true or false
    */
    @Override
    public boolean isApplicableTo(Object obj) {
        return obj.getClass().equals(String.class);
    }
 
    /**
    * Probes the value against an arbitrary condition.
    * @param obj user-inputted value of a component annotation property
    * @return true if the value is considered valid, false otherwise
    */
    @Override
    public boolean test(Object obj) {
        return isApplicableTo(obj) && Arrays.asList(PROTOCOLS).contains(obj.toString().toLowerCase());
    }
 
    /**
    * Called by the toolkit *in case* .test() returns false
    * @return The exception message to log
    */
    @Override
    public String getWarningMessage() {
        return "only HTTP and HTTPS protocols supported";
    }
}

Now whenever your fellow developer tries to specify, for instance, @HelloWorld(webProtocols = {"http:", "telnet:", "https:"}), a warning will be logged and build will, optionally, erroneous value skipped from rendering. Note that, in this particular case, only "https:" will be stored to webProtocols attribute, because "http:" is set to be ignored, and "telnet:" is considered invalid.

Custom Properties

Custom properties for fields

If you need some attributes with plain values added to a dialog field, this can be achieved without creating a custom annotation and handler. Just add @Properties annotation to a field in your Java class and populate it with properties you need.

@Dialog(name = "componentName")
public class CustomPropertiesDialog {
    @DialogField(label = "Field 1")
    @TextField
    @Propeties({
        // this will produce the String-typed JCR attribute "stringAttr" with value "Hello World"
        @Property(name = "stringAttr", value = "Hello World"),
        // this way you create a Long-typed JCR attribute. Attributes of other JCR-supported types
        // are stored similarly
        @Property(name = "numericAttr", value = "{Long}42")
    })
    String field1;

    //another way to define properties
    @DialogField(label = "Field 2")
    @TextField
    @Property(name = "stringAttr", value = "Hello World")
    @Property(name = "numericAttr", value = "{Long}42")
    String field1;
}
Custom properties for in-place editing configurations

Arbitrary attributes can be set to in-place editing configurations. For that, set a value for the config field of an @InplaceEditingConfig annotation.

@InplaceEditingConfig(
    type = "CustomType",
    editElementQuery = ".editable-description",
    name = "description", propertyName = "description",
    config = {
        @Property(name="stringAttr", value = "Hello World"),
        @Property(name="booleanAttr", value = "{Boolean}true")
    }
)
public class CustomPropertiesDialog { /* ... */ }
Dialog-wide properties

Yet another mechanism available is to specify custom properties at Java class level. This may be used:

  • for setting entire component's attributes (those exposed in .content.xml file);
  • for setting attributes of <cq:dialog> root node (_cq_dialog.xml file);
  • for setting attributes of <cq:editConfig> node (_cq_editConfig.xml file).

For these goals, @CommonProperties annotation is designed. It accepts similar arguments to those of @Properties annotation. Yet you can also specify the XML scope for each @CommonProperty (it 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:

@CommonProperties({
    @CommonProperty(name = "stringAttr", value = "Hello World"), // goes to .content.xml by default
    @CommonProperty(scope = XmlScope.CQ_DIALOG, name = "numericAttr", value = "{Long}-1000"),
    @CommonProperty(scope = XmlScope.CQ_EDIT_CONFIG, name = "arrayAttr", value = "[any,many,minny,moe]"),
    @CommonProperty(
        scope = XmlScope.CQ_EDIT_CONFIG,
        path = "/root/inplaceEditing/config/rtePlugins/edit/htmlPasteRules/table",
        name = "allow",
        value = "{Boolean}true"
    ),
    @CommonProperty(
        scope = XmlScope.CQ_DIALOG,
        path = "//*[@size='L']",
        name = "size",
        value = "S"
    )
})
public class CustomPropertiesDialog { /* ... */ }

//another way to define properties
@CommonProperty(name = "stringAttr", value = "Hello World")
@CommonProperty(scope = XmlScope.CQ_DIALOG, name = "numericAttr", value = "{Long}-1000")
@CommonProperty(scope = XmlScope.CQ_EDIT_CONFIG, name = "arrayAttr", value = "[any,many,minny,moe]")
public class CustomPropertiesDialog { /* ... */ }

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 TouchUI logic (may be used for debugging also). For instance, 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.

Debugging custom logic

You can debug your custom logic while building your app. In order to do it run your build in debug mode e.g.:

mvnDebug clean install -PautoInstallPackage

Afterwards you can set breakpoints in your IDE, start a debugging session and connect to the build process. Default port is 8000.

Plugin settings

"terminateOn" setting

Specifies the list of exceptions, comma-separated, that would cause this plugin to terminate the build process.

Each item may present:

  • a particular exception, by its fully qualified name like java.io.IOException. When a singular exception is specified, all subclasses of the provided class also count;
  • or a package where multiple exceptions reside, like com.exadel.aem.plugin.exceptions.*.

Apart from this, you may specify the values all (alias *) and none. If an exception or a group of exceptions must be explicitly neglected, ! should be prepended to the item.

Exception patterns are considered in order. Earlier patterns are applied before later patterns. For example, if java.*, !java.lang.RuntimeException provided, and a NullPointerException is thrown, the second ("negated") pattern will have no effect since any exception originating from the java package has already been accounted by the first pattern. That is why, if you need to define a scope of exceptions that would cause termination but need to explicitly exclude some items from that scope, put "negated" patterns in the first place.

It is also considered a good practice to end the enumeration with a default "fallback" pattern, typically *, if there are exclusions on the list. So, <terminateOn>!java.lang.NullPointerException, !com.exadel.aem.plugin.exceptions.*,java.lang.RuntimeException</terminateOn>, or <terminateOn>!java.lang.RuntimeException, !iava.io.IOException, *</terminateOn> would be some good samples.

Extra features and assets

DependsOn

DependsOn asset is a client library that triggers pre-defined actions over a dependent TouchUI dialog widget or tab upon a change of other (referenced) widget/field in the authoring interface on the AEM installation frontend. Typical use-case for DependsOn is changing widget's visibility or turning it enabled/disabled because upon triggering some switch, and also storing conditional data to a widget's input field.

DependsOn uses data attributes for fetching expected configuration. To define data attribute from JCR use granite:data sub-node under the widget node. AEM Authoring Toolkit provides a set of annotations to use DependsOn from Java code.

(see more in DependsOn Readme)

DependsOn annotations
  • @DependsOn - to define single DependsOn Action with the Query. Multiple annotations per element can be used.
  • @DependsOnRef - to define referenced element name and type. Only a single annotation is allowed.
  • @DependsOnTab - to define DependsOn query with tab-visibility action for tab.

The following snippet discloses the @DependsOn usage in brief:

@Dialog(
  // ...
  tabs = {
    @Tab(name = "tab"), 
    @Tab(name = "conditionalTab")
  }
)
@DependsOnTab(tabTitle = "conditionalTab", query = "@ref")
public class DependsOnSample {
    @DialogField(label = "The switch")
    @Switch
    @DependsOnRef(name = "ref", type = DependsOnRefTypes.BOOLEAN)
    private boolean firstDialogEnabled;

    @DialogField
    @FieldSet(title = "Conditional fieldset")
    @DependsOn(query = "@ref", action = "someAction", params = {@DependsOnParam(name = "param", value = "paramValue")} )
    @PlaceOnTab(TAB_ADDITIONAL_TOPICS)
    private SomeFieldsetDefinitionClass fieldsetDefinitionClass;
}

Samples

Examples of how to use AEM Authoring Toolkit API and DependsOn library are presented in a separate AAT Samples module.

Run mvn clean install -P install-samples from the root folder of AAT Samples to install the sample project.

To directly find necessary annotations or specific use of these annotations, read AAT Samples Readme.

About

Automates generating rich and responsive authoring interfaces for Adobe Experience Manager (TM) via Maven-powered build and deployment workflow

Resources

License

Packages

No packages published
You can’t perform that action at this time.