Showing posts with label Component. Show all posts
Showing posts with label Component. Show all posts

Wednesday, 1 January 2020

A servlet to Query Components inside your apps folder.

If you want to get your list of components inside your project apps folder in the form of json response, then here's a servlet for you.

The servlet is registered at path - /bin/generate/componentsreport  to which you need to pass your project folder path (default is /apps) as a query-param and it will output the list of components (their name and path) as a json response.

What this servlet is doing is - it is forming a Predicate Map of Query having the path (apps path from the input you provide) and node type (cq:Component) and using it in QueryBuilder API and fetching the results in form of component paths.
Further it is processing the hits (components path) from the search result to form the json response which you can consume by dong an ajax call.

This is the servlet for it-
package com.aem.community.wf.core.servlets;

import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.sling.SlingServlet;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.day.cq.search.PredicateGroup;
import com.day.cq.search.Query;
import com.day.cq.search.QueryBuilder;
import com.day.cq.search.result.Hit;
import com.day.cq.search.result.SearchResult;

import javax.jcr.Session;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@SlingServlet(paths = "/bin/generate/componentsreport")
public class ComponentQueryServlet extends SlingSafeMethodsServlet {

 private static final long serialVersionUID = 1L;
 private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
 @Reference
        private QueryBuilder builder;
 
 @Reference
        private ResourceResolverFactory resolverFactory;
 
 @SuppressWarnings("deprecation")
 @Override
        protected void doGet(SlingHttpServletRequest request, 
                SlingHttpServletResponse response) throws IOException {
        
  
     String compPath = request.getParameter("comppath");   
     if(compPath == null){
      compPath = "/apps";
     }
     long totalMatches = 0;
     
     response.setCharacterEncoding("UTF-8");
 response.setContentType("application/json");
     
     JSONObject json = new JSONObject();
     JSONArray jsonarray = new JSONArray();
     JSONObject tempJson;
     
     try { 
      ResourceResolver resolver = request.getResourceResolver();
      Session session = resolver.adaptTo(Session.class);
      
      Map map = new HashMap();             
         map.put("path", compPath);
         map.put("type", "cq:Component");
         map.put("p.offset", "0");
         map.put("p.limit", "-1");
                           
         Query query = builder.createQuery(PredicateGroup.create(map),
                                                                     session);                    
                     
         SearchResult result = query.getResult();
         totalMatches = result.getTotalMatches();                             
         String hitpath;
         Resource compResource;
         String compTitle;
         
         for (Hit hit : result.getHits()) {
                hitpath = hit.getPath();   
                tempJson = new JSONObject();
                
                compResource = resolver.getResource(hitpath); 
         if (compResource != null) { 
                    ValueMap valueMap = compResource.getValueMap(); 
                    compTitle = valueMap.get("jcr:title", String.class);
                    tempJson.put("componentname", compTitle);
                }
         tempJson.put("componentpath", hitpath);
         jsonarray.put(tempJson);
         }
         json.put("totalcomponents", totalMatches);
         json.put("components", jsonarray);
         logger.info("Total" + totalMatches + "paths were found: \n");
         
     } catch (Exception e) {
      logger.error("Exception caught: "+e);
 }
     response.getWriter().print(json.toString());
    
    }
}

Below is the sample url to call this servlet by passing apps folder path-
http://localhost:4502/bin/generate/componentsreport?comppath=/apps/weretail

And this is the screenshot of the response by hitting above url-



  

Thursday, 17 January 2019

Touch UI - Hide/Show fields inside coral-3 multifield in AEM by coral dropdown

This post shows how to hide/show fields inside multifield in the Touch UI dialog of AEM.
The resourceType used here for composite multifield, selection dropdown, textfield and numberfield are coral 3 ui components. For ex: granite/ui/components/coral/foundation/form/multifield and it has been tested in aem 6.3.

This method uses granite:class attribute added as property to add class in the fields and granite:data node with data key-value added as property to add data attributes in the fields.
Implemented 4 steps are mentioned below: 

1. Add granite:class attribute to the selection field as shown below
 granite:class : cq-dialog-dropdown-showhide1


2. Add granite:data node inside selection field with data key-value added as property as shown below:
cq-dialog-dropdown-showhide-target1 : .list-option-listfrom-showhide-target



3. Add granite:class attribute to each of the fields which you want to show/hide based on 
dropdown selection which matches the data-target attribute of selection field as shown below:
granite:class : list-option-listfrom-showhide-target1


4. Add granite:data node inside each of the fields which you want to show/hide with showhidetargetvalue key 
and value equal to dropdown options value as shown below:
  showhidetargetvalue : textval



To avoid any confusion, the cq_dialog xml i created is pasted below-

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" 
xmlns:granite="http://www.adobe.com/jcr/granite/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="Multi ShowHide Component"
    sling:resourceType="cq/gui/components/authoring/dialog"
    helpPath="https://www.adobe.com/go/aem6_3_docs_component_en#Image - HTL">
    <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"
            type="nav"/>
        <items jcr:primaryType="nt:unstructured">
            <multi
                jcr:primaryType="nt:unstructured"
                jcr:title="Multi"
                sling:resourceType="granite/ui/components/foundation/section">
                <layout
                    jcr:primaryType="nt:unstructured"
      sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"
                    margin="{Boolean}false"/>
                <items jcr:primaryType="nt:unstructured">
                    <custom
                        jcr:primaryType="nt:unstructured"
      sling:resourceType="granite/ui/components/foundation/container">
                        <items jcr:primaryType="nt:unstructured">
                            <heading
                                jcr:primaryType="nt:unstructured"
      sling:resourceType="granite/ui/components/foundation/heading"
                                class="coral-Heading coral-Heading--4"
                                level="{Long}4"
                                text="Composite Multifield"/>
                            <well
                                jcr:primaryType="nt:unstructured"
      sling:resourceType="granite/ui/components/foundation/container">
                                <layout
                                    jcr:primaryType="nt:unstructured"
      sling:resourceType="granite/ui/components/foundation/layouts/well"/>
                                <items jcr:primaryType="nt:unstructured">
                                    <fieldenter
                                        jcr:primaryType="nt:unstructured"
      sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
                                        composite="{Boolean}true">
                                        <field
                                            jcr:primaryType="nt:unstructured"
      sling:resourceType="granite/ui/components/coral/foundation/container"
                                            fieldLabel="Products Container"
                                            name="./products">
      <items jcr:primaryType="nt:unstructured">
                                            <listFromMulti
      granite:class="cq-dialog-dropdown-showhide1"
                                            jcr:primaryType="nt:unstructured"
      sling:resourceType="granite/ui/components/coral/foundation/form/select"
                                            fieldLabel="Select Multi showhide"
                                            name="listFromMulti">
      <items jcr:primaryType="nt:unstructured">
                                            <text
                                            jcr:primaryType="nt:unstructured"
                                            text="Text field"
                                            value="textval"/>
                                            <num
                                            jcr:primaryType="nt:unstructured"
                                            text="Num field"
                                            value="numval"/>
                                            </items>
                                            <granite:data
                                            jcr:primaryType="nt:unstructured"
      cq-dialog-dropdown-showhide-target1=".list-option-listfrom-showhide-target1"/>
                                            </listFromMulti>
                                            <multitext
      granite:class="list-option-listfrom-showhide-target1"
                                            jcr:primaryType="nt:unstructured"
      sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                                              fieldLabel="Multi Text"
                                              name="multitext"
                                              showhidetargetvalue="textval">
                                              <granite:data
                                                jcr:primaryType="nt:unstructured"
                                                showhidetargetvalue="textval"/>
                                              </multitext>
                                              <multinum
      granite:class="list-option-listfrom-showhide-target1"
                                              jcr:primaryType="nt:unstructured"
      sling:resourceType="granite/ui/components/coral/foundation/form/numberfield"
                                              fieldLabel="Multi Num"
                                               name="multinum"
                                               showhidetargetvalue="numval">
                                               <granite:data
                                               jcr:primaryType="nt:unstructured"
                                               showhidetargetvalue="numval"/>
                                               </multinum>
                                            </items>
                                        </field>
                                    </fieldenter>
                                </items>
                            </well>
                        </items>
                    </custom>
                </items>
            </multi>
        </items>
    </content>
</jcr:root>

Now to toggle fields using selection dropdown inside composite multifield independently, use below jquery logic 
written in a js file inside clientlibs folder of component with category cq.authoring.dialog .
 
(function(document, $) {
    "use strict";

    // when dialog gets injected
    $(document).on("foundation-contentloaded", function(e) {
        // if there is already an inital value make sure the 
  //according target element becomes visible
        showHideHandler($(".cq-dialog-dropdown-showhide1", e.target));
    });

    $(document).on("change", ".cq-dialog-dropdown-showhide1", function(e) {
        showHideHandler($(this));
    });

    function showHideHandler(el) {
        el.each(function(i, element) {
                // handle Coral3 base drop-down
                Coral.commons.ready(element, function(component) {
                    showHideCustom(component, element);
                    component.on("change", function() {
                        showHideCustom(component, element);
                    });
                });
        })
    }

    function showHideCustom(component, element) {
         // get the selector to find the target elements. 
         //its stored as data-.. attribute
       var target = $(element).data("cq-dialog-dropdown-showhide-target1");
       var $target = $(target);
       var elementIndex = $(element).closest('coral-multifield-item').index();

       if (target) {
         var value;
         if (component.value) {
           value = component.value;
         } else {
           value = component.getValue();
         }
         $(element).closest("coral-multifield-item").find(target)
         .each(function(index) {
            var tarIndex = $(this).closest('coral-multifield-item').index();
            if (elementIndex == tarIndex) {
                $(this).not(".hide").parent().addClass("hide");
                $(this).filter("[data-showhidetargetvalue='" + value + "']")
                .parent().removeClass("hide");
            }
         });
        }
    }

})(document, Granite.$);

The screenshot of how the dialog looks like and how it toggle fields is shown below- 


You can also find this component in my github project here .

Wednesday, 13 June 2018

Servlet Invoker Utility Component

This post will be a beginning of various posts in which I will create a Servlet Invoker utility component and through this component invoke the servlet for various operations in AEM by passing parameters from our component as an argument to the servlet.

Often times, we create a utility in AEM in form of Services, Servlets etc. and call them by various means using Sling Models, WCMUSEPojo etc. which is not consistent though it achieves your functionality, but here through a common component we can invoke our Servlets and pass parameters to it and achieve our functionality.

To begin with, create a component in AEM and it's dialog. The dialog can contains the field for which servlet to invoke and what parameters to pass to the Servlet .
It's html contains a simple Button on press of which our servlet will invoke.

For ex:
<sly data-sly-use.clientlib="/libs/granite/sightly/templates/clientlib.html">

   <h2>Welcome to Servlet Invoker Utility Component</h2>

   <strong>Source path:</strong> <span id="path1">${properties.path1}</span> <br>
   <strong>Destination path:</strong> <span id="path2">${properties.path2}</span>
   <br><br>

   <input id="submitButton" type="submit" title="Submit" value="Begin Operation">

   <div id="result">
       <h3 data-sly-test="${properties.path1 && properties.path1}">
                        Press Begin Operation button to invoke servlet</h3>
       <h3 data-sly-test="${!(properties.path1 && properties.path1)}">
                        Please author source and destination path</h3>
   </div>

<sly data-sly-call="${clientlib.js @ categories='servlet.invoke'}" />
It looks something like this:















Along with the html file of the component, create a clientlibs for the component with your categories say servlet.invoke and write a JS code which contains a logic to do an ajax call to your servlet on press of the Button. Also you can pass the parameters to your servlets through the same ajax call which you can get from your html.
For ex.
(function ($, document) {
    'use strict';
    $("#submitButton").click(function(){
        var r = confirm("This will invoke the servlet and begin operation.
                         Do you want to proceed? ");
        if (r == true) {
            $("#result").html('<h3>
                          Operation in progress...Please wait</h3>');
            $.ajax({
                    type : "GET",
                    url : '/system/utility/servletinvoker',

                    data : {
                        path1: $("#path1").html(), path2:$("#path2").html()
                    },
                    success : function(data) {

                        $("#result").html('<h3>
                                          '+data+'</h3>');
                    },
                    error : function(XMLHttpRequest, textStatus, errorThrown) {
                        $("#result").html('<h3>
                    Error calling utility servlet: '+errorThrown+'</h3>');
                    }
            });}});

})(Granite.$, document);
Notice the url of the servlet for ajax call, by this you can understand that the servlet is registered via path, Since most probably, you will be using this utility component in author instance, you can find out a path for the servlet to be registered in AEM.

Now, you are good to go with your simple component. In my next post, I'll create a servlet and show you how to invoke it through this component.

Sunday, 8 April 2018

Retrieve Composite Multifield child values in AEM through Java (WCMUsePojo)

This post is a part 2 of my previous blog Composite Collapsible Multifield in AEM 6.3 Touch UI in which we retrieved the authored multifield values in our sightly code by using JS Use-API.

Here, we'll use the Java WCMUsePojo API to retrieve the multifield child values in our sightly code.

Since the multifield values stored in the page is stored as child nodes as shown below:





First, we'll create the MultiList bean having same properties as in our dialog with getters and setters as shown below:

package com.foo.community.core;

public class MultiList {
 
 private String multitext;
 private String multinum;
 private String multicheck;

 public String getMultitext() {
  return multitext;
 }

 public void setMultitext(String multitext) {
  this.multitext = multitext;
 }

 public String getMultinum() {
  return multinum;
 }

 public void setMultinum(String multinum) {
  this.multinum = multinum;
 }

 public String getMulticheck() {
  return multicheck;
 }

 public void setMulticheck(String multicheck) {
  this.multicheck = multicheck;
 }
}

And then, we'll create a class extending WCMUsePojo where we'll get the current node and then get the products node (where child values of multifield are stored) and iterate over that and set the retrieved property values in List of MultiList bean created above. Below is the implementation of this process:

package com.foo.community.core;

import java.util.ArrayList;
import java.util.List;   
import com.adobe.cq.sightly.WCMUsePojo;
import javax.jcr.Node;
import javax.jcr.NodeIterator;    

public class MultiListComponent extends WCMUsePojo{

  private List multiItems = new ArrayList();

  @Override
  public void activate() throws Exception {

     Node currentNode = getResource().adaptTo(Node.class);
     if(currentNode.hasNode("products")){
     Node productsNode = currentNode.getNode("products");
     NodeIterator ni =  productsNode.getNodes();
     
     String multitext;
     String multinum;
     String multicheck;

     while (ni.hasNext()) {

      MultiList multiItem = new MultiList();          
      Node child = (Node)ni.nextNode();

         multitext= child.hasProperty("multitext") ? 
           child.getProperty("multitext").getString(): ""; 
         multinum = child.hasProperty("multinum") ? 
           child.getProperty("multinum").getString(): ""; 
         multicheck = child.hasProperty("multicheck") ? 
           child.getProperty("multicheck").getString(): "";    
     
         multiItem.setMultitext(multitext);
         multiItem.setMultinum(multinum);
         multiItem.setMulticheck(multicheck);
         multiItems.add(multiItem);
     }  
    }
 } 

 public List getMultiItems() {
  return multiItems;
 }
}

And use below sightly code (blog.html) to render values in the html:

<div data-sly-use.blog="com.foo.community.core.MultiListComponent">
  <h4>Composite Multifield</h4>
  <ol data-sly-list.product="${blog.multiItems}">
     <li>
        ${product.multitext} has ${product.multinum} with boolean 
        ${product.multicheck}
     </li>
  </ol>
</div>

Saturday, 7 April 2018

Composite Collapsible Multifield in AEM 6.3 Touch UI

This post discusses how to create a composite collapsible multifield touch ui dialog which uses Granite UI Coral3 multifield in AEM 6.3 .

We'll be using OOTB coral multifield provided by AEM 6.3 for storing multifield values as child nodes (NODE STORE)granite/ui/components/coral/foundation/form/multifield, and we'll retrieve the authored multifield values in our sightly code by using JS Use-API (To retrieve values via WCMUsePojo API, refer this post here).

The advantage of using JS Use-API here is you can get the node of multifield values stored in pages and pass it to the sightly code in few lines of code as compared to more lines of code when using JAVA for this.

Also you won't need to use acs-commons package or any other custom multifield here for storing composite multifield values.

To make the multi-fields collapsible we'll be using the foundation layout type inside container as: granite/ui/components/foundation/layouts/collapsible, thus we would not need to write customization code in clientlibs for this.

The touch ui dialog structure is shown below:

<?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="Blog Component"
    sling:resourceType="cq/gui/components/authoring/dialog"
    helpPath="https://www.adobe.com/go/aem6_3_docs_component_en#Image - HTL">
    <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"
         type="nav"/>
        <items jcr:primaryType="nt:unstructured">
            <multi
                jcr:primaryType="nt:unstructured"
                jcr:title="Multi"
                sling:resourceType="granite/ui/components/foundation/section">
                <layout
                    jcr:primaryType="nt:unstructured"
       sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"
                    margin="{Boolean}false"/>
                <items jcr:primaryType="nt:unstructured">
                    <column
                        jcr:primaryType="nt:unstructured"                       
       sling:resourceType="granite/ui/components/foundation/container">
                        <items jcr:primaryType="nt:unstructured">
                            <fieldenter
                                jcr:primaryType="nt:unstructured"
       sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
                                composite="{Boolean}true"
                                fieldLabel="Composite Multifield">
                                <field
                                    jcr:primaryType="nt:unstructured"
                                    jcr:title="Click to Expand"                              
       sling:resourceType="granite/ui/components/foundation/container"
                                    fieldLabel="Products Container"
                                    name="./products">
                                    <layout
                                     jcr:primaryType="nt:unstructured"                                 
       sling:resourceType="granite/ui/components/foundation/layouts/collapsible"/>
                                    <items 
                                     jcr:primaryType="nt:unstructured">
                                        <multitext
                                            jcr:primaryType="nt:unstructured"
       sling:resourceType="granite/ui/components/foundation/form/textfield"
                                            fieldLabel="Multi Text"
                                            name="multitext"/>
                                        <multinum
                                            jcr:primaryType="nt:unstructured"
       sling:resourceType="granite/ui/components/foundation/form/numberfield"
                                            fieldLabel="Multi Num"
                                            name="multinum"/>
                                        <multicheck
                                            jcr:primaryType="nt:unstructured"               
       sling:resourceType="granite/ui/components/foundation/form/checkbox"
                                            name="multicheck"
                                            text="Multi Check"
                                            value="{Boolean}true"/>
                                    </items>
                                </field>
                            </fieldenter>
                        </items>
                    </column>
                </items>
            </multi>
        </items>
    </content>
</jcr:root>

Touch UI Dialog authoring interface is as shown below:


The multifield values stored in the page is stored as child nodes as shown below:





You can use below JS Use code (blog.js) to fetch the node and pass to sightly:

"use strict";
use(function() {
    var resourceResolver = resource.getResourceResolver();
    return {
      node : resourceResolver.getResource(currentNode.getPath() + "/products"),
    };
});

And use below sightly code (blog.html) to render values in the html:

<div data-sly-use.blog="blog.js">
    <h4>Composite Multifield Values: </h4>
    <ol data-sly-list.product="${blog.node.listChildren}">
      <li>
          ${product.multitext} has ${product.multinum} with boolean 
          ${product.multicheck}
      </li>
    </ol>
</div>