TL;DR
There are many different definitions for “default” when discussing AEM authoring requirements. All of these different scenarios must be considered when determining the requirements for a component. This process can be greatly accelerated by agreeing on a definition of “default” for the project and marking some scenarios as out of scope or undefined behavior.
In Adobe Experience Manager development, there are a great many opportunities to provide a default value for an authored field if that field has no value. Usually we generically refer to that replacement value as the “default” and move on with our lives. However for tight requirements, adherence to the principle of least surprise, and a fully QA’d system, that simply isn’t good enough. Multiple times during the development process we need to know exactly which defaults we’re setting, how they will behave, and what the side effects of that behavior are.
Scenarios
The following is a list of different behaviors that might be referred to as the “default”. While I’ve tried to make this list as comprehensive as possible, I’ve surely missed quite a few. The bulk of these are based on text field dialog fields. As with any requirements gathering process, as you encounter new scenarios, you should add them to what you’re tracking, and retroactively apply constraints to your existing feature set for that scenario. Note that I have added code examples for these scenarios at the end of this article.
retrievalDefault
: Value used by the backing bean when the property is not found in the jcrnoResourceDefault
: Value used by the backing bean when the backing node is not found in the jcr (a virtual component)namedDefault
: Value used by the backing bean when the property is explicitly set to a named default (such as for a radio button group, or select drop down)emptyDefault
: Value used by the backing bean when the property is explicitly stored as an empty stringinvalidDefault
: Value used by the backing bean when the property is invalid (such as a negative number for a count of items)exceptionDefault
: Value used by the backing bean when an error is thrown during code executiontemplateDefault
: Value used by the backing bean when a new copy of the component is added to a parsys for the first time (via the component’s cq:template child node)dialogDefault
: Value added to the dialog field when the retrieved value is empty (actual value in the form field)emptyTextDefault
: Value displayed as placeholder text in the dialog field when the retrieved value is empty (will submit as empty if not changed)htlDefault
: Value used by the jsp or HTL template when the property is falsey (it evaluates to 0, null, “”, false, etc), or meets some other criteria
That’s 10 different possible scenarios and only a few of them are particularly situational!
Coping Mechanisms
Having 10 additional scenarios to check for each component’s requirement gathering is too much for most cases. To deal with this, without burning through tons of time, you can come up with top level project rules for the scenarios and then deal with them all identically. If you do this right, what you will create is a project specific “definition of default” that you can use. Then, for each field, simply ask the question “What is the default value for this field?” For any components that break the rules you create, simply add extra requirements to note those differences and the justifications for them.
A Recommended Starting Restriction Set
For most AEM implementations, I would recommend the following set of rules regarding defaults to start:
- The retrievalDefault, noResourceValue, namedDefault, emptyDefault, invalidDefault, and exceptionDefault should be identical and should be the default value for the field’s Java primitive type unless otherwise specified.
- If the value used to cover rule 1 is something other than the primitive type default value, use fieldDescription to provide user instructions including a mention of the defaults
- The templateDefault, dialogDefault, emptyTextDefault and htlDefault should not be used
- templateDefault can create confusion for authors by making the initial behavior for a component differ depending on whether it is statically included or dragged on the page. It can also be difficult to make changes to the templateDefault after initial release. If the value changes, existing uses of the components may all need to be modified manually or via script. I try to avoid using the cq:template even if it increases complexity in other parts of the program.
- dialogDefault can create confusion for authors by causing changes to occur to the field’s stored value when saving a component dialog in which they had made no changes.
- emptyTextDefault can create confusion for authors by making it look like a value has been configured when it has not been. Other implementations may put instructions or the retrievalDefault values in this location.
- htlDefault should not be necessary assuming that the above recommendations are followed
This gives us three rules to cover all 10 scenarios. This recommendation will cover most AEM implementations for most users in the best and most predictable way. It also gives us a concrete definition for “default” that we can use throughout our documentation. Just remember that there are definitely cases where you will want to do something different. For instance you may want to take advantage of dialogDefault or templateDefaults in order to ensure a keyword is stored in the jcr for a search integration, or you may want to use emptyTextDefault in place of, or in addition to, fieldDescription.
Conclusion
There are many scenarios to handle when defining the behavior for even the simplest component. Without properly defined terminology for behaviors like default values, what you get from a component may not be predictable, It may not even match the behaviors of other components in the same project. An early, in-depth, examination of terms like “default” can prevent these problems and create a more consistent and intuitive experience for users.
Code Samples
The following are files for a sample component with all the defaults I listed accounted for. This should make it obvious that eliminating and duplicating some of these behaviors is necessary, while also illustrating the freedom of options that AEM offers for defaulting to a value in different scenarios.
A quick caveat: I have not attempted to compile and run any of the code below. So it might be slightly off syntacticly from what is required. Also note that the .content.xml for the component’s top level node properties would need to be added.
package com.example.aem.components; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceUtil; import org.apache.sling.models.annotations.DefaultInjectionStrategy; import org.apache.sling.models.annotations.Model; import javax.inject.Inject; import javax.inject.Named; import java.util.ArrayList; import java.util.List; import com.example.aem.services.SampleService; @Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL) public ExampleComponent { public SampleService service; public static List<String> allowedValues = new ArrayList<>(); static { allowedValues.add("allowedValue1"); } @Inject private Resource resource; @Inject @Named("blah") private String storedBlah = "retrievalDefault"; public String getBlah() { String blahValue; if (ResourceUtil.isNonExistingResource(resource)) { blahValue = "noResource"; } else if ("namedDefaultKey".equals(storedBlah)) { blahValue = "namedDefault"; } else if ("".equals(storedBlah)) { blahValue = "emptyDefault"; } else if ("callServiceValue".equals(storedBlah) { try { blahValue = service.callServiceMayThrowError(); } catch (Exception e) { blahValue = "exceptionDefault"; } } else if (allowedValues.contains(storedBlah)) { blahValue = storedBlah } else { blahValue = "invalidDefault"; } return blahValue; } }
<?xml version="1.0" encoding="UTF-8"?> <jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" jcr:primaryType="nt:unstructured" blah="templateDefault"/>
<?xml version="1.0" encoding="UTF-8"?> <jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" jcr:primaryType="nt:unstructured" jcr:title="Example Component" sling:resourceType="cq/gui/components/authoring/dialog" fileName="_cq_dialog.xml" mode="edit"> <content jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/container"> <layout jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/layouts/tabs"/> <items jcr:primaryType="nt:unstructured"> <example cq:hideOnEdit="{Boolean}false" cq:showOnCreate="{Boolean}true" jcr:primaryType="nt:unstructured" jcr:title="Example Component" sling:resourceType="granite/ui/components/foundation/section"> <layout jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"/> <items jcr:primaryType="nt:unstructured"> <column jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/container"> <items jcr:primaryType="nt:unstructured"> <blah cq:hideOnEdit="{Boolean}false" cq:showOnCreate="{Boolean}true" jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/textfield" disabled="{Boolean}false" emptyText="emptyTextDefault" fieldLabel="Regular Title" fieldDescription="Defaults to different values depending on the scenario." name="./blah" renderReadOnly="{Boolean}true" required="{Boolean}false" value="dialogDefault"/> </title> </items> </column> </items> </Accordion> </items> </content> </jcr:root>
<sly data-sly-use.example="com.example.aem.components.ExampleComponent"> ${example.blah ? example.blah : "htlDefault"} </sly>
Leave a Reply