There are many more possibilities. For greater detail, see the inline documentation on Target.
Just like Source, Target is an adaptable entity. By default, Target adapts to DomAdapter with the possibility of being serialized to an XML DOM document. You can apply any custom adapters in the way described in the "Source object" section.
Debugging custom plugin logic
You can debug the ToolKit's plugin while building your AEM project. In order to do so, you need to:
Make sure that the two projects are open each in its own window of your IDE:
the Toolkit itself,
and the "target" project (the one that contains the Toolkit plugin in its POM file).
Launch the build of the target project with mvndebug instead of mvn. Do it like this:
mvnDebug clean install -PautoInstallPackage
-
(Mind that the build won't technically start off before the next step. mvnDebug just establishes a listener service and waits for an incoming connection). 3) Switch to the ToolKit's IDE window. Start the remote debug session with localhost as the host and port 8000. Most convenient is to create a new "Run/debug configuration" with these parameters in your IDE. The rest of the parameters will remain default. 4) Now the actual build process starts. You can set breakpoints in the plugin's code and operate as usual.
Starting from version 2.3.0, the ToolKit supports integration tests powered by Selenide. They are mainly for checking the browser-bound functions as well as checking the connection to a live AEM server and/or 3rd-party services on the Internet.
The integration tests are localed in the it.tests module. Important: unlike unit tests, integration tests are not run in frames of a "regular" build. To run them, you need to specify the dedicated Maven profile like the following:
mvn clean install -Pintegration
-
As the integration tests start, a synthetic content package containing test data will be created and deployed to a live AEM instance (the one specified by the aem.host and/or aem.port properties). You can find the data in AEM under /apps/etoolbox-authoring-kit-test and also in the same folders under /conf and /content.
If the live AEM instance is not available, the tests will fail.
The login and password are needed to install the package. You may specify them with aem.login and aem.password properties if they are not the default admin/admin pair.
After the integration tests are run, the synthetic package and its content are removed from the live AEM instance. You may, however, want them to stay, e.g., to manually re-run the test cases that failed. For that purpose, specify the nouninstall=true property either in the Maven file or in the command line.
A complete command line for running integration tests with different properties specified may look like the following:
Insert dependency to the core module in the <dependencies> section of the POM file of your bundle (core) module:
<dependency><groupId>com.exadel.etoolbox</groupId><artifactId>etoolbox-authoring-kit-core</artifactId>
- <version>2.5.1</version><!-- Prefer the latest stable version whenever possible -->
+ <version>2.5.2</version><!-- Prefer the latest stable version whenever possible --><scope>provided</scope><!-- Do not use compile or runtime scope!--></dependency>
Insert plugin's config in the <plugins> section of the POM file of your package (ui.apps) module:
Specifies the path to a folder in the current package where the AEM components are situated. Whenever you specify the path property of @AemComponent as a relative path (without leading /), its value is appended to the componentsPathBase. It is recommended to always specify this setting.
componentsReferenceBase
Specifies the root package to scan for AEM components' back end Java classes. It can be used to limit the classes scanned by the plugin (= to filter out classes that are not matched by a component folder within the current package), and also to recognize a component address via the matching class name. It is recommended to always specify this setting.
terminateOn
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 is 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 for 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, you should put "negated" patterns in the first spot.
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.
If terminateOn is not configured, the default setting is effective. By default, the plugin will terminate on an IOException or one of its derivatives.
Installing assets
For many of the ToolKit's features to work properly, namely DependsOn and Lists, you need to deploy the etoolbox-authoring-kit-all-.zip package to your AEM author instance.
If you are using ready artifacts, the easiest way is to append the cumulative all package to one of your content packages. Since the package is small, this will not hamper your deployment process.
You need to do two steps.
Insert the dependency into the cumulative all module in the <dependencies> section of the POM file of your package:
(Mind that the build won't technically start off before the next step. mvnDebug just establishes a listener service and waits for an incoming connection). 3) Switch to the ToolKit's IDE window. Start the remote debug session with localhost as the host and port 8000. Most convenient is to create a new "Run/debug configuration" with these parameters in your IDE. The rest of the parameters will remain default. 4) Now the actual build process starts. You can set breakpoints in the plugin's code and operate as usual.
The ToolKit supports integration tests powered by Selenide. They are mainly for checking the browser-bound functions and rely on the connection to a live AEM server and/or 3rd-party services on the Internet.
The integration tests are localed in the it.tests module. Important: unlike unit tests, integration tests are not run in frames of a "regular" build. To run them, you need to specify the dedicated Maven profile like the following:
mvn clean test -Pintegration
+
As the integration tests start, a synthetic content package containing test data will be created and deployed to a live AEM instance (the one specified by the aem.host and/or aem.port properties). You can find the data in AEM under /apps/etoolbox-authoring-kit-test and also in the same folders under /conf and /content.
If the live AEM instance is not available, the tests will fail.
The login and password are needed to install the package. You may specify them with aem.login and aem.password properties if they are not the default admin/admin pair.
After the integration tests are run, the synthetic package and its content are removed from the live AEM instance. You may, however, want them to stay, e.g., to manually re-run the test cases that failed. For that purpose, specify the nouninstall=true property either in the Maven file or in the command line.
A complete command line for running integration tests with different properties specified may look like the following:
mvn clean test -Pintegration -D"aem.host"=192.168.0.81 -D"aem.port"=8080 -D"aem.login"=siteadmin -D"aem.password"=MyPa$$w0rd -Dnouninstall=true
+
Doing regression testing
The ToolKit supports regression testing as well. Regression testing is done using an external AEM project. The aim is to make sure that a version of ToolKit (namely, the plugin) produces essentially the same XML markup as an one. The version is considered a "reference" and is used as a baseline for comparison.
To run regression tests, you need to specify the dedicated Maven profile like the following:
mvn test -Pregression "-Dproject=e:\projects\aem\my_project -Deak.version=${version}"
+
The focus here is the -Dproject=... option. It is the command line to build the target project.
The ToolKit will navigate to the folder e:\projects\aem\my_project_ (and expectedly will find project's POM file in it). It will build the project (without doing unit tests and deployment) with the version of ToolKit that is specified in the project. It will collect the content packages created upon the build.
Then it will switch the version of ToolKit to the latest one and run the build for the second time. The content packages will be collected again. Then the "former" and the "newer" packages will be compared content-wise. If there are no significant disparities, the test is passed. Otherwise, the test is failed.
We assume that your project has a Maven property that stores the version of ToolKit, and the dependencies within your project use that property (like <dependency><groupId>com.exadel.etoolbox</groupId><artifactId>...</artifactId><version>${eak.version}</dependency>). Name of the property does not matter. But you are expected to mention it in the -Dproject=... as shown above. The ${version} part is the placeholder that will be used by the regression code.
Within -Dproject=..., you can specify more property keys that are specific for your project if you need to. You can also specify the -pl option to select particular modules for the build, or also some -P keys to specify build profiles, etc. Do not forget to enclose -Dproject=... in double quotes if there are spaces inside.
You can process more than one project at a time. Separate projects in the -Dproject=... option with a semicolon.
We've said that the second part of the regression is building a target project with the current ToolKit version. If you however wish to test another version, specify it with the optional switch like -Deak.version=1.2.3-SNAPSHOT.`
Besides, you can override the Maven executable used to build the target AEM project by specifying -Dmaven.cmd=/path/to/mvn/or/a/cmd/file. Optionally you can specify a local Maven repository address if it differs from $HOME$/.m2, with -Dmaven.dir=/path/to/maven/repository.
Filtering disparities
Certain disparities that may be found in regression testing are expected and, therefore, can be omitted. This is done with the filters option. A filter is a JavaScript file composed per the EToolbox-Coconut documentation.
By default, the filters are looked for at the current project's path in the eak.regression/filters subdirectory. E.g., /home/projects/my-aem-project/eak.regression/filters.
This subdirectory can contain another subdir that refers to the "older" and "newer" ToolKit's versions delimited with -to- like in the following example: /home/projects/my-aem-project/eak.regression/filters/2.4.0-to-2.4.1-SNAPSHOT. If such a subdirectory exists, the filters are borrowed from it. But if the there isn't a subdir matching the "older" and "newer" versions, the filters are borrowed from just eak.regression/filters.
The comparison is done inside a temp folder. By default, the folder is erased after the regression is done. If you want to keep it, specify -Dnocleanup=true.
Regression tips
Be sure to build the ToolKit completely before doing the regression, or else include the install phase into the regression Maven run.
Also, make sure that your target project(-s) are fully built with the newest changes, especially if you do regression with a selection of modules (via -pl). Otherwise there may occur a dependency resolution issue, or else some compilation errors.
\ No newline at end of file
diff --git a/content/dev-tools/component-management/dialog-fields/configuring-multifield/index.html b/content/dev-tools/component-management/dialog-fields/configuring-multifield/index.html
index adb4d638c..cb209e03a 100644
--- a/content/dev-tools/component-management/dialog-fields/configuring-multifield/index.html
+++ b/content/dev-tools/component-management/dialog-fields/configuring-multifield/index.html
@@ -38,4 +38,4 @@
String dialogItem;}}
-
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.
On the other hand, if you 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" cases. For more complicated cases it lacks tweaking capabilities that @MultiField itself has.
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.
On the other hand, if you 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."
If you choose to use @Multiple together with @FieldSet, make sure that the @DialogField annotation is also present. In other @FieldSet use cases it is not necessary to add @DialogField.
Please note that @Multiple is primarily designed for easy, "quick give me a multifield out of my single widget" cases. For more complicated cases it lacks tweaking capabilities that @MultiField itself has.
\ No newline at end of file
diff --git a/content/dev-tools/component-management/reusing-code/index.html b/content/dev-tools/component-management/reusing-code/index.html
index de6995f59..efc353589 100644
--- a/content/dev-tools/component-management/reusing-code/index.html
+++ b/content/dev-tools/component-management/reusing-code/index.html
@@ -27,11 +27,52 @@
@TextField
@Replace(@ClassMember(source = _Super.class, value = "supertext"))
private String text;
-
This way, the text field in the superclass will be removed from the rendering workflow, but the text field from the current class will remain. Moreover, the latter be placed exactly where the overridden field would reside unless another ranking value is set.
If you omit the source part from @Replace, the current class will be assumed. Otherwise, if you omit the value, the same-named field or method from the specified source class will be assumed.
@Extends-ing fields annotations
Several dialog fields, such as the 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. You will also 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 the ToolKit is its extension/inheritance technique that helps to cope with that issue.
Suppose that you have marked a private String sampleText; in your HelloWorld.java with several ToolKit annotations and wish to use the same set of annotations for private String anotherField; in this or another class.
To achieve this, add the @Extends annotation (the one that contains a pointer to sampleText) to anotherText field. Whatever widget annotation you defined for the sampleText field will now be "inherited" by anotherText. You can still add another @TextField to anotherText with properties that were not specified in sampleText or have different values there. Thereby "inheritance with overriding" is achieved. See the following snippet:
publicclassCustomPropetiesDialog{
+
This way, the text field in the superclass will be removed from the rendering workflow, but the text field from the current class will remain. Moreover, the latter be placed exactly where the overridden field would reside unless another ranking value is set.
If you omit the source part from @Replace, the current class will be assumed. Otherwise, if you omit the value, the same-named field or method from the specified source class will be assumed.
@Extends-ing fields annotations
Several dialog fields, such as the 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. You will also 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 the ToolKit is its extension/inheritance technique that helps to cope with that issue.
Suppose that you have marked a private String sampleText; in your HelloWorld.java with several ToolKit annotations and wish to use the same set of annotations for private String anotherField; in this or another class.
To achieve this, add the @Extends annotation (the one that contains a pointer to sampleText) to anotherText field. Whatever widget annotation you defined for the sampleText field will now be "inherited" by anotherText. You can still add another @TextField to anotherText with properties that were not specified in sampleText or have different values there. Thereby "inheritance with overriding" is achieved. See the following snippet:
publicclassCustomPropertiesDialog{@DialogField(label ="My text field")@Extends(value =HelloWorld.class, field ="sampleText")@TextField(emptyText ="Enter your text here")privateString anotherText;/* ... */}
-
The ToolKit will first look for the sampleText field in HelloWorld.java and, if it is found, will use that field's @DialogField and @DatePicker annotations to prepare an XML markup for the current field. For properties such as label or emptyText that have local "overrides", the local values will be used, and the rest will be taken from the anotherText field.
Note: 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).
You should still 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 pay attention so 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 your "inheritance tree" starting from fields with more abstract, less populated component annotations, and then shifting to more specific ones.
\ No newline at end of file
+
The ToolKit will first look for the sampleText field in HelloWorld.java and, if it is found, will use that field's @DialogField and @DatePicker annotations to prepare an XML markup for the current field. For properties such as label or emptyText that have local "overrides", the local values will be used, and the rest will be taken from the anotherText field.
Note: 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).
You should still 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 pay attention so 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 your "inheritance tree" starting from fields with more abstract, less populated component annotations, and then shifting to more specific ones.
Scripting widget properties' values
Imagine that you have a fieldset ButtonFieldSet that comprises settings for a button such as "label", "hyperlink", "does it open in a new window?" and so on. Naturally, this fieldset contains a number of annotated fields, such as @TextField, @Checkbox, etc., and is reused across several components.
Now suppose that the requirement is such that in some of your components your "hyperlink" field must have the default value https://google.com, and in some other components it should be https://bing.com. Also, in some cases the "open in a new window" checkbox should be checked by default, and in some other cases it should be unchecked.
In this situation you would like to use some variable (or, in other words, scripted value) with your reusable fieldset. It is so that in MyComponentA you use the the fieldset with the "default hyperlink" set to Google, and in MyComponentB you use the same fieldset with the "default hyperlink" set to Bing.
Here is a sample of how you can achieve this with the ToolKit:
publicclassMyComponentA{
+ @FieldSet
+ @Setting(name ="defaultHyperlink", value ="https://google.com"),
+ @Setting(name ="openInNewWindow", value ="{Boolean}true")
+ privateButtonFieldSet buttonFieldSet;
+}
+
publicclassMyComponentB{
+ @FieldSet
+ @Setting(name ="defaultHyperlink", value ="https://bing.com"),
+ @Setting(name ="openInNewWindow", value ="{Boolean}false")
+ privateButtonFieldSet buttonFieldSet;
+}
+
The overall idea is that you declare a "named variable" with the @Setting annotation and then you "insert" it elsewhere into a String-typed property of another annotation using the string template like ${ @variableName }. The format @{ @variableName } is also supported.
The string template does not have to be the only content of a property value. You can combine it with other text, like value = "https://www.google.com/${@defaultPath}".
Every kind of property can be scripted except for properties of the @Setting annotation itself.
Surely, you cannot pass a string template to a boolean-typed or a numeric property. There is however a workaround. If you need to turn a non-string value into a variable, pass it via an additional @Property annotation like in the following example:
publicclassMyComponent{
+ @FieldSet
+ @Setting(name ="minValue", value ="{Double}0.0")
+ @Setting(name ="maxValue", value ="{Double}100.0")
+ privateMyFieldset myFieldset;
+}
+
+// ...
+publicclassMyFieldset{
+ @DialogField
+ @NumberField(min =0/* an optional un-scripted default */, max =1/* another optional default */)
+ @Property(name ="min", value ="${@minValue}")
+ @Property(name ="max", value ="${@maxValue}")
+ privatedouble value;
+}
+
+
What are the places you can declare your settings in?
Basically, the ToolKit supports four sorts of settngs:
Settings that are attached to the same class member they are used (usually does not make much sense but is possible).
Settings that are attached to the class where the said member is declared or to any of its superclasses.
Settings that are attached to the member of another class that has the type of the class in which the settings are used. In the code, this is referred to as the "upstream member".
To put it simple, imagine that there is a field named title in a class named MyFieldset. Then, there is a field named titleFieldset of type MyFieldset in a class named MyComponent. In this case MyComponent.titleFieldset is the upstream member for MyFieldset.title. If you annotate MyComponent.titleFieldset with @Setting, this annotation is respected as the upstream setting when rendering any annotation declared at MyFieldset.title .
Settings that are attached to the class where the upstream member is declared or to any of its superclasses.
As you see it, there is some "stack" of variables that can affect rendering of the current field, and these vars can belong to different "scopes". If there are variables with the same name, the values are overridden from the most "remote" scope to the most "close" one. That is, settings declared at level #4 in the list above are overridden by settings declared at level #3, then by level #2, and finally by level #1.
Scripting expressions syntax
Expressions within the ${} or @{} brackets are not limited to just settings' names. They basically follow the (simplified) JavaScript syntax. Therefore, you can, for example, refer to a setting with a fallback value like ${@mySetting || 'default value'} or use a ternary like ${@mySetting ? 'value if true' : 'value if false'}. You can also use arithmetic operations, string concatenation, and so on.
Inside the expression, you can use the special @this object (alias source). @this refers, naturally, to the class member that is being currently rendered.
With @this.class you can get the declaring class of the current member. Then you can retrieve some "properties" of the class, like @this.class.name or @this.class.parent, or else a collection of @this.class.ancestors.
With @this.context (alias @this.upstream) you can reach the "upstream" class member (see definition above) if the expression is being used inside a fieldset.
There is a way to retrieve a property of a declared annotation with, e.g., @this.annotation('DialogField').label. You can also get all the declared annotations with @this.annotations(). Same way you can get a particular annotation or all the annotations of the declaring class with @this.class.annotation('Dialog') or @this.class.annotations()[1]. See more of it in the test classes.
Property scripting or DependsOn?
From the first look on them, the interpolatable string templates discussed above appear similar to the DependsOn queries. You must however understand the difference between them.
Both techniques alter the view and/or behavior of a Touch UI dialog conditionally.
DependsOn does this dynamically (in runtime) in the browser. In a common scenario, DependsOn is used to modify the state of a dialog widget after the user interacted with another widget of the same dialog (like showing or hiding a text field upon checking or unchecking a box).
Property scripting with @Settings this statically at the time of project building. It is used, e.g., to make a text field inside a fieldset display its default value as "Foo" when used within "MyComponentA" and "Bar" when used within "MyComponentB" -- all without the need to create two different fieldsets.
You cannot however make a scripted template react to a user action like you would do with a DependsOn query. Also, you cannot refer to @some_variable declared with @Setting from a DependsOn query, and vice versa. All in all, property scripting and DependsOn modify the Touch UI experience from different angles and "live" in different worlds.
\ No newline at end of file
diff --git a/content/getting-started/installation/index.html b/content/getting-started/installation/index.html
index 2938b449f..a075d2757 100644
--- a/content/getting-started/installation/index.html
+++ b/content/getting-started/installation/index.html
@@ -1,14 +1,14 @@