Chuyển đến nội dung chính

[COMPONENT] Dialog validation in CQ5

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>

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;  
           }
       }
   };    
}();

Nhận xét

Bài đăng phổ biến từ blog này

How to add a new supported language in CQ / WEM (Translator in CQ)

Use case:  You want to add new language to CQ Change display language options in translator grid Change language name and default countries  Solution: You can access translator UI in CQ with following URL http://<HOST>:<PORT>/libs/cq/i18n/translator.html Create new language location for Dictionary Go to CRXDE lite (or your favorite JCR browser) and add this structure (assuming /apps/myapp/i18n as a typical location for custom apps): /apps/myapp/i18n [sling:Folder]     - de [nt:unstructured]         + jcr:mixinTypes = [mix:language]         + jcr:language = de     - fr [nt:unstructured]         + jcr:mixinTypes = [mix:language]         + jcr:language = fr Then reload the translator and the path /apps/ myapp /i18n should show up in the drop-down at the top. Note: the translator will only save translations for languages that are actually present underneath the path (e.g. /apps/myapp/i18n), others will be skipped. Then on jsp pa

Login / Logout on a Publish instance and Closed User Group (CUG)

In CQ5 there is the login logout could be configured using a Closed User Group. Closed User Groups (CUGs) are used to limit access to specific pages that reside within a published internet site. Such pages require the assigned members to login and provide security credentials. http://dev.day.com/docs/en/cq/5-4/howto/create_apply_cug.html The logout using /libs/cq/core/content/login.logout.html always gets redirected to the geometrixx site   http://localhost:4503/content/geometrixx-outdoors/en.html By configuring the Default login page  under the osgi configuration for com.day.cq.auth.impl.LoginSelectorHandler to be – /content/mysite/en/login But still after logout the page goes to the geometrixx site. IIRC, the redirect first goes to to / which then goes through the standard, somewhat complex handling of the root with multiple redirects: 1) / has a resource type of sling:redirect and redirects to /index.html 2) /index.html is handled by the RootMappingServlet [0] which h

[PERFORMANCE] Adobe WEM/CQ performance tuning

Adobe WEM/CQ performance tuning Contents Caching-related configurations CRX Bundle cache CRX Search index handler (Lucene) cache Tar PM index cache Scalability Maintenance Optimizing Tar Files (for Tar Persistence Manager) Data Store Garbage Collection Main documentation you should consult first: http://dev.day.com/docs/en/cq/current/deploying/performance.html http://dev.day.com/content/kb/home/cq5/CQ5Troubleshooting/performancetuningtips.html Caching-related configurations CRX Bundle cache CRX caches bundles, consisting of a node with all its properties. This is used by all bundle-based Persistence Managers. The default size of BundleCache is 8 MB. If this is too small it can cause an excessive number of read-accesses to the underlying persistence layer. Set the bundleCacheSize to something larger than the default. See more here: http://dev.day.com/docs/en/cq/current/deploying/performance.html#CRX%20Bundle%20Cache CRX Search index handler (Lucene