Date Range Validation Extension

Exposing hidden features of the CFM editor in AEM - Part 1

Have you ever wondered how you can make sure that two date fields inside a Content Fragment (CF) can be validated together to make sure one is before/after the other?

Unfortunately, Content Fragment Models (CFMs) in AEM don't have any validation rules that span over multiple fields like in AEM Forms.
But there is a hidden functionality in the datepicker that can be exposed to make a date range validation possible e.g. to check if one date field value is before/after another date field value.

This hidden functionality is part of the underlying granite datepicker that is used for the Content Fragment datepicker.

From the Documentation:

  • beforeSelector
    • Specifies a CSS selector targeting another datepickers that are before this datepicker. If those datepickers are not before this datepicker, it will be invalid.
  • afterSelector
    • Specifies a CSS selector targeting another datepickers that are after this datepicker. If those datepickers are not after this datepicker, it will be invalid.

 

First Step: Add the properties fields to the CFM editor

The first step we need to do is to add the input fields for the beforeSelector and afterSelector values in the CFM editor. To do this we will create two JSP files under this path:

 

  • /apps/dam/cfm/models/editor/components/datatypeproperties/datebeforefield/datebeforefield.jsp
  • /apps/dam/cfm/models/editor/components/datatypeproperties/dateafterfield/dateafterfield.jsp

 

dateafterfield.jsp

<%@include file="/libs/granite/ui/global.jsp" %><%
%><%@ page session="false" contentType="text/html" pageEncoding="utf-8"
           import="com.adobe.granite.ui.components.formbuilder.FormResourceManager,
         		 org.apache.sling.api.resource.Resource,
         		 org.apache.sling.api.resource.ValueMap,
         		 java.util.HashMap" %><%

    ValueMap fieldProperties = resource.adaptTo(ValueMap.class);

    HashMap values = new HashMap();
    values.put("granite:class",    "field-dateafter-descriptor");
    values.put("fieldLabel",       i18n.get("Date after field selector for validation"));
    values.put("name",             "./content/items/" + resource.getName() + "/afterSelector");
    values.put("value",            fieldProperties.get("afterSelector", String.class));
    values.put("fieldDescription", "The property name needs to be encapsulated inside this template: [name='PROPERTYNAME']");
    values.put("emptyText",        "[name='PROPERTYNAME']");

    FormResourceManager formResourceManager = sling.getService(FormResourceManager.class);
    Resource labelFieldResource = formResourceManager.getDefaultPropertyFieldResource(resource, values);

%>

datebeforefield.jsp

<%@include file="/libs/granite/ui/global.jsp" %><%
%><%@ page session="false" contentType="text/html" pageEncoding="utf-8"
           import="com.adobe.granite.ui.components.formbuilder.FormResourceManager,
         		 org.apache.sling.api.resource.Resource,
         		 org.apache.sling.api.resource.ValueMap,
         		 java.util.HashMap" %><%

    ValueMap fieldProperties = resource.adaptTo(ValueMap.class);

    HashMap values = new HashMap();
    values.put("granite:class",    "field-datebefore-descriptor");
    values.put("fieldLabel",       i18n.get("Date before field selector for validation"));
    values.put("name",             "./content/items/" + resource.getName() + "/beforeSelector");
    values.put("value",            fieldProperties.get("beforeSelector", String.class));
    values.put("fieldDescription", "The property name needs to be encapsulated inside this template: [name='PROPERTYNAME']");
    values.put("emptyText",        "[name='PROPERTYNAME']");

    FormResourceManager formResourceManager = sling.getService(FormResourceManager.class);
    Resource labelFieldResource = formResourceManager.getDefaultPropertyFieldResource(resource, values);

%>

Keep in mind that afterSelector and beforeSelector need a CSS selector. That's why the property value needs to be in a specific format.

See also the "fieldDescription": [name='PROPERTYNAME']

 

Now we need to assign these property fields to the actual datepicker for CFMs. We can do this by overwrite a file under /libs by adding this file to the /apps folder:

  • /apps/settings/dam/cfm/models/formbuilderconfig/datatypes/.content.xml

<?xml version="1.0" encoding="UTF-8"?>

<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"

    jcr:primaryType="nt:unstructured">

    <items jcr:primaryType="nt:unstructured">

        <date

            jcr:primaryType="nt:unstructured"

            fieldProperties="[datepickerfields,labelfield,maptopropertyfield,datetimepickerfield,datevaluefield,requiredfield,datebeforefield,dateafterfield]"/>

    </items>

</jcr:root>

How to add the validation to the Content Fragment Models

And that's basically it. Now the CFM author only needs to add the selector with the property to the Content Fragment Model like this:

Example of a date field with a before selector
Example of a date field with a after selector

And then the validation is active for the author of the Content Fragments

Author view with failed date validation

I hope this little extension helped you and if you need a more in-depth explanation you can continue reading.

Technical Explanation

If we look inside the Content Fragment Model field for datepicker we will find this component:

  • /libs/dam/cfm/models/editor/components/datatypes/datepicker

And in the properties we find the resourceSuperType

  • /libs/granite/ui/components/coral/foundation/form/datepicker
resourceSuperType for cfm datepicker

This helps us to see what functionalities we can use and which "hidden" features we can expose to the CFM author.

 

Disclaimer:

By extending AEM out-of-the-box functionality we always need to question ourselves if it's worth the risk to overwrite. Because a new AEM release can contain changes at our changed files and could lead to unforeseen problems as there is no way to know what changed if you are not looking into the jsp/html/.. files in folder /libs directly.

But in this case the risk is fairly low because we are adding new files instead of overwriting existing ones. You only need to keep an eye on the fieldProperties as these could be extended/changed in the future. 

Tested with AEM 6.5.17

 

If you want to know how to extend the Tag field to enable "forceSelection" you can click here:

Part 2