Issue:
How can I validate the data from the field of dialogue that cannot be handled with static constraints or regular expressions?
Solution 1:
Complex validation of CQ5 dialogue fields using a custom Sling servlet
Dialog fields in CQ5 occasionally require validation that cannot be handled with static constraints or regular expressions. However, this type of validation can be easily accomplished by delegating the validation function to a custom servlet. The implementation details and code samples below describe a viable solution for most validation scenarios.
Abstract Validation Servlet
A project may require several dialogue validators, so it makes sense to abstract common functionality into a base class. This class is excerpted from the CITYTECH CQ5 library, which we offer to clients as a foundation package for new CQ5 projects.
import java.io.IOException;
import java.util.Collections;
import javax.servlet.ServletException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonGenerator.Feature;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AbstractValidatorServlet extends SlingSafeMethodsServlet {
private static final Logger LOG = LoggerFactory.getLogger(AbstractValidatorServlet.class);
private static final JsonFactory FACTORY = new JsonFactory().disable(Feature.AUTO_CLOSE_TARGET);
private static final ObjectMapper MAPPER = new ObjectMapper();
@Override
protected final void doGet(final SlingHttpServletRequest request, final SlingHttpServletResponse response)
throws ServletException, IOException {
final String value = request.getRequestParameter("value").getString();
final String path = request.getResource().getPath();
final boolean valid = isValid(request, path, value);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
try {
final JsonGenerator generator = FACTORY.createJsonGenerator(response.getWriter());
MAPPER.writeValue(generator, Collections.singletonMap("valid", valid));
} catch (final JsonGenerationException jge) {
LOG.error("error generating JSON response", jge);
} catch (final JsonMappingException jme) {
LOG.error("error mapping JSON response", jme);
} catch (final IOException ioe) {
LOG.error("error writing JSON response", ioe);
}
}
/**
* Validate the given value for this request and path.
*
* @param request servlet request
* @param path path to current component being validated
* @param value input value to validate
* @return true if value is valid, false otherwise
*/
protected abstract boolean isValid(final SlingHttpServletRequest request, final String path, final String value);
}
Dialog- or field-specific Validation Servlet
This sample validator is responsible for ensuring that form names on a given page are unique. Notice that the current component path is passed to the validation method, which provides the necessary context to ensure that the validation function can ignore it's own value when considering the uniqueness of the form names.
Additionally, the servlet implementation uses Apache Felix SCR annotations to create the required OSGi bundle metadata at via the Maven SCR plugin:
-
sling.servlet.resourceTypes : FormsConstants.RT_FORM_BEGIN - register this servlet for the resource type of the component being validated
-
sling.servlet.methods : GET - handle only HTTP GET requests
-
sling.servlet.selectors : validator - trigger this servlet only when the "validator" selector is present
-
sling.servlet.extensions : json - return a JSON response
import java.util.HashMap;
import java.util.Map;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.jcr.resource.JcrResourceConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.day.cq.wcm.foundation.forms.FormsConstants;
@Component
@Service
@Properties({
@Property(name = "sling.servlet.resourceTypes", value = FormsConstants.RT_FORM_BEGIN),
@Property(name = "sling.servlet.extensions", value = "json"),
@Property(name = "sling.servlet.methods", value = "GET"),
@Property(name = "sling.servlet.selectors", value = "validator"),
@Property(name = "service.description", value = "Form Validator Servlet")
})
public final class FormValidatorServlet extends AbstractValidatorServlet {
private static final Logger LOG = LoggerFactory.getLogger(FormValidatorServlet.class);
@Override
protected boolean isValid(final SlingHttpServletRequest request, final String path, final String value) {
final Map<String, String> names = getFormNames(request);
// ensure that form name is unique among all forms defined on this page
// (except for itself)
return !names.containsKey(value) || names.get(value).equals(path);
}
private Map<String, String> getFormNames(final SlingHttpServletRequest request) {
final Map<String, String> names = new HashMap<String, String>();
final Node currentNode = request.getResource().adaptTo(Node.class);
try {
final Node par = currentNode.getParent();
final NodeIterator nodes = par.getNodes();
// get all form names for the current paragraph system
while (nodes.hasNext()) {
final Node node = nodes.nextNode();
if (isFormStart(node)) {
final String name = node.getProperty(FormsConstants.ELEMENT_PROPERTY_NAME).getString();
names.put(name, node.getPath());
}
}
} catch (final RepositoryException re) {
LOG.error("error getting form names", re);
}
return names;
}
private boolean isFormStart(final Node node) throws RepositoryException {
return node.hasProperty(JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY)
&& FormsConstants.RT_FORM_BEGIN.equals(node.getProperty(JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY).getString())
&& node.hasProperty(FormsConstants.ELEMENT_PROPERTY_NAME);
}
}
CQ5 Dialog XML
The name field element includes a "validator" attribute, which defines the function used to call the validation servlet and handle the JSON response. The return value is either "true" (Boolean) or an error message to be displayed to the content author.
<?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="cq:Dialog" xtype="dialog">
<items jcr:primaryType="cq:WidgetCollection">
<tabs jcr:primaryType="cq:TabPanel">
<items jcr:primaryType="cq:WidgetCollection">
<first jcr:primaryType="nt:unstructured" title="Form" xtype="panel">
<items jcr:primaryType="cq:WidgetCollection">
<name jcr:primaryType="cq:Widget" fieldLabel="Name" name="./name" xtype="textfield" allowBlank="{Boolean}false"
validator="function(value) {
var dialog = this.findParentByType('dialog');
var url = CQ.HTTP.addParameter(dialog.path + '.validator.json', 'value', value);
var result = CQ.HTTP.eval(url);
return result.valid ? true : 'Form name already exists on this page.';
}" />
</items>
</first>
</items>
</tabs>
</items>
</jcr:root>
import java.io.IOException;
import java.util.Collections;
import javax.servlet.ServletException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonGenerator.Feature;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AbstractValidatorServlet extends SlingSafeMethodsServlet {
private static final Logger LOG = LoggerFactory.getLogger(AbstractValidatorServlet.class);
private static final JsonFactory FACTORY = new JsonFactory().disable(Feature.AUTO_CLOSE_TARGET);
private static final ObjectMapper MAPPER = new ObjectMapper();
@Override
protected final void doGet(final SlingHttpServletRequest request, final SlingHttpServletResponse response)
throws ServletException, IOException {
final String value = request.getRequestParameter("value").getString();
final String path = request.getResource().getPath();
final boolean valid = isValid(request, path, value);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
try {
final JsonGenerator generator = FACTORY.createJsonGenerator(response.getWriter());
MAPPER.writeValue(generator, Collections.singletonMap("valid", valid));
} catch (final JsonGenerationException jge) {
LOG.error("error generating JSON response", jge);
} catch (final JsonMappingException jme) {
LOG.error("error mapping JSON response", jme);
} catch (final IOException ioe) {
LOG.error("error writing JSON response", ioe);
}
}
/**
* Validate the given value for this request and path.
*
* @param request servlet request
* @param path path to current component being validated
* @param value input value to validate
* @return true if value is valid, false otherwise
*/
protected abstract boolean isValid(final SlingHttpServletRequest request, final String path, final String value);
}
|
|
import java.util.HashMap;
import java.util.Map;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.jcr.resource.JcrResourceConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.day.cq.wcm.foundation.forms.FormsConstants;
@Component
@Service
@Properties({
@Property(name = "sling.servlet.resourceTypes", value = FormsConstants.RT_FORM_BEGIN),
@Property(name = "sling.servlet.extensions", value = "json"),
@Property(name = "sling.servlet.methods", value = "GET"),
@Property(name = "sling.servlet.selectors", value = "validator"),
@Property(name = "service.description", value = "Form Validator Servlet")
})
public final class FormValidatorServlet extends AbstractValidatorServlet {
private static final Logger LOG = LoggerFactory.getLogger(FormValidatorServlet.class);
@Override
protected boolean isValid(final SlingHttpServletRequest request, final String path, final String value) {
final Map<String, String> names = getFormNames(request);
// ensure that form name is unique among all forms defined on this page
// (except for itself)
return !names.containsKey(value) || names.get(value).equals(path);
}
private Map<String, String> getFormNames(final SlingHttpServletRequest request) {
final Map<String, String> names = new HashMap<String, String>();
final Node currentNode = request.getResource().adaptTo(Node.class);
try {
final Node par = currentNode.getParent();
final NodeIterator nodes = par.getNodes();
// get all form names for the current paragraph system
while (nodes.hasNext()) {
final Node node = nodes.nextNode();
if (isFormStart(node)) {
final String name = node.getProperty(FormsConstants.ELEMENT_PROPERTY_NAME).getString();
names.put(name, node.getPath());
}
}
} catch (final RepositoryException re) {
LOG.error("error getting form names", re);
}
return names;
}
private boolean isFormStart(final Node node) throws RepositoryException {
return node.hasProperty(JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY)
&& FormsConstants.RT_FORM_BEGIN.equals(node.getProperty(JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY).getString())
&& node.hasProperty(FormsConstants.ELEMENT_PROPERTY_NAME);
}
}
|
<?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="cq:Dialog" xtype="dialog">
<items jcr:primaryType="cq:WidgetCollection">
<tabs jcr:primaryType="cq:TabPanel">
<items jcr:primaryType="cq:WidgetCollection">
<first jcr:primaryType="nt:unstructured" title="Form" xtype="panel">
<items jcr:primaryType="cq:WidgetCollection">
<name jcr:primaryType="cq:Widget" fieldLabel="Name" name="./name" xtype="textfield" allowBlank="{Boolean}false"
validator="function(value) {
var dialog = this.findParentByType('dialog');
var url = CQ.HTTP.addParameter(dialog.path + '.validator.json', 'value', value);
var result = CQ.HTTP.eval(url);
return result.valid ? true : 'Form name already exists on this page.';
}" />
</items>
</first>
</items>
</tabs>
</items>
</jcr:root>
|
Solution 2:
Here we will create a component which will have some basic fields. Lets say we will use two textfields and do validation on both of them.
Here the process follows:
1)Create dialog for the component as following,
<?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"
jcr:primaryType="cq:Dialog"
xtype="dialog">
<items jcr:primaryType="cq:TabPanel">
<items jcr:primaryType="cq:WidgetCollection">
<validationtab
jcr:primaryType="cq:Panel"
title="Validation Example Tab">
<items jcr:primaryType="cq:WidgetCollection">
<exampletext0
jcr:primaryType="cq:Widget"
fieldLabel="First TextField"
fieldDescription="First field Description"
name="./text0"
xtype="textfield"/>
<exampletext1
jcr:primaryType="cq:Widget"
fieldLabel="Second TextField"
fieldDescription="Second field Description"
name="./text1"
xtype="textfield"/>
</items>
</validationtab>
</items>
</items>
<listeners
jcr:primaryType="nt:unstructured"
beforesubmit="function(dialog) {return Example.checkFields(dialog);};"/>
</jcr:root>
2) After creating the dialogue we will create a js file to get the values from the dialogue and make it valid.
This js file we make include in our clientLibrary folder, by including such, we make it call on load.
Here we are getting the two textfield values which are in the dialogue.
Example = function() {
return {
// Dialog field cross validation.
checkFields : function(dialog,maxWidth,maxHeight) {
var textfieldArray = dialog.findByType("textfield");
var textfieldLength0 = textfieldArray[0].getValue().length;
var textfieldLength1 = textfieldArray[1].getValue().length;
if((textfieldLength0 > 0 && textfieldLength1 > 0) ||
(textfieldLength0 == 0 && textfieldLength1 == 0)) {
// Both fields have or do not have a value
return true;
} else {
// Cross validation fails
CQ.Notification.notify(CQ.I18n.getMessage("Validation Error"),
CQ.I18n.getMessage("Both fields must be filled or left empty."));
return false;
}
}
};
}();
<?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" jcr:primaryType="cq:Dialog" xtype="dialog"> <items jcr:primaryType="cq:TabPanel"> <items jcr:primaryType="cq:WidgetCollection"> <validationtab jcr:primaryType="cq:Panel" title="Validation Example Tab"> <items jcr:primaryType="cq:WidgetCollection"> <exampletext0 jcr:primaryType="cq:Widget" fieldLabel="First TextField" fieldDescription="First field Description" name="./text0" xtype="textfield"/>
<exampletext1
jcr:primaryType="cq:Widget"
fieldLabel="Second TextField" fieldDescription="Second field Description" name="./text1" xtype="textfield"/> </items> </validationtab> </items> </items> <listeners jcr:primaryType="nt:unstructured" beforesubmit="function(dialog) {return Example.checkFields(dialog);};"/> </jcr:root> |
Example = function() {
return { // Dialog field cross validation. checkFields : function(dialog,maxWidth,maxHeight) { var textfieldArray = dialog.findByType("textfield"); var textfieldLength0 = textfieldArray[0].getValue().length; var textfieldLength1 = textfieldArray[1].getValue().length; if((textfieldLength0 > 0 && textfieldLength1 > 0) || (textfieldLength0 == 0 && textfieldLength1 == 0)) { // Both fields have or do not have a value return true; } else { // Cross validation fails CQ.Notification.notify(CQ.I18n.getMessage("Validation Error"), CQ.I18n.getMessage("Both fields must be filled or left empty.")); return false; } } }; }(); |
Nhận xét
Đăng nhận xét