Quantcast
Channel: Experiencing Adobe Experience Manager (AEM, CQ)
Viewing all 525 articles
Browse latest View live

Developing AEM 56 (Day CQ) Applications on IntelliJ IDEA 12

$
0
0

Goal


Develop the following page from nothing ( no copy/paste ). Source codeof the component is available for download. Check this Adobe page for more detail on how to create a website



Install


Download and Install AEM (Day CQ) 561, IntelliJ IDEA 12 and Maven 310

Set up Maven


1) To add the dependency jars to your IDE, install and setup Maven

2) Download Maven and extract it to C:\dev\apache-maven-3.1.0.

3) Open System Environment Variables and add the System Varilable M2_HOME=C:\dev\apache-maven-3.1.0

4) Add the User Variable M2=%M2_HOME%\bin





Create AEM Template


1) Let’s start with creating a template. For better understanding, in java terms template is a class and page is object of the class

2) Access CRXDE Lite in Firefox, http://localhost:4502/crx/de/index.jsp; if you see an empty left tree, enter credentials by clicking on the Login button ( top right corner of screen )

3) Create parent folder /apps/firstapp

4) Create child folders /apps/firstapp/components, /apps/firstapp/templates



5) Create the FirstApp Basic Template Component. Templates are also components, so this serves as the base component for our templates

5.a) Right click on folder /apps/firstapp/components and select Create Component



5.b) Enter the following information

                 Label: firstapptemplatecomponent
                 Title: FirstApp Basic Template Component
                 Description: Parent component for FirstApp Website Templates
                 Super Type: foundation/components/page
                 Group: hidden



5.c) Click “Next” and you are on “Advanced Component Settings”. Nothing to do

5.d) Click “Next” and you are on “Allowed Parents”. Nothing to do

5.e) Click “Next” and you are on “Allowed Children”. Nothing to do

5.f) Click ok. Your FirstApp Basic Template Component is now created. It has a node with name firstapptemplatecomponent.jsp. Double click and the code opens up in right pane. In the next steps we’ll be renaming and modifying this jsp.

5.g) Save Changes


6) The next step is to create a page structure in your jsp and add some sample content

6.a) Right click on /apps/firstapp/components/firstapptemplatecomponent/firstapptemplatecomponent.jsp and “Rename” it to body.jsp. Reason being the super type (sling:resourceSuperType). The super type for our template component is foundation/components/page.  Expand and double click on node /libs/foundation/components/page/page.jsp. In the source code visible on right pane, come to bottom and you should see the following code. It has includes for head.jsp and body.jsp.



Let us work on “body.jsp” for now. The foundation component page includes “body.jsp” and our component “firstapptemplatecomponent” inherits this, so we rename the created jsp “firstapptemplatecomponent.jsp” to “body.jsp”

6.b) Enter the following sample text in right pane. Here we are trying to keep things simple. In the following sections, we’ll configure IDEA, add some useful code and push it to CRX.



6.c) Save changes

7) Lets create the FirstApp Basic Template now.

7.a) Right click on /apps/firstapp/templates, Select “Create” and Create Template


7.b) Enter the following information in “Create Template” window

           Label: firstappbasictemplate
           Title: FirstApp Basic Template
           Description: FirstApp Basic template with logic for adding a text component using drag and drop
           Resource Type: firstapp/components/firstapptemplatecomponent
           Ranking: 100


Here we are saying, this template is of type /apps/firstapp/components/firstapptemplatecomponent we created in step 5 above

7.c) Click “Next” and you are on “Allowed Paths”. Click “+” and enter

/content/firstapp-demo-site(/.*)?

This regular expression says, make this template available to firstapp-demo-site. In the next section we’ll create a demo site FirstApp Demo Site for which the system gives name firstapp-demo-site. Adding this regular expression in “Allowed Paths” makes this template available to our demo site only and not to other sites like Geometrixx in CQ5 system.

                       
7.d) Click “Next” and you are on “Allowed Parents”. Nothing to do here

7.e) Click “Next” and you are on “Allowed Children”. Nothing to do here

7.f) Click “Ok” and Save changes. Your first template FirstApp Basic Template is now created. Congratulations


Create Page


1) Lets start with creating a page FirstApp Demo Site. Open http://localhost:4502/siteadmin#/content in a browser tab

2) Select Websites in the right tree and Click New ( just “New” and nothing from the drop down)

3) Enter FirstApp Demo Site for Title. Leave “Name” empty and select the first “Form Template” ( it has no significance, just select it for now ) and click “Create”


4) Select “FirstApp Demo Site” in the left tree and click “New” in the right pane

5) In the dialog window that opens you should see our template First App Basic Template.  If you do not see our template, something went wrong, go back and verify if everything is ok, specially the “Allowed Paths” in template should be “/content/firstapp-demo-site(/.*)?”

6) Enter Home for Title, select the template “FirstApp Basic Template” and click “Create”


7) Your page should now exist here in the left tree: Websites -> FirstApp Demo Site -> Home. Double click and it should open the url (http://localhost:4502/cf#/content/firstapp-demo-site/home.html) in a new tab


8) Congratulations. In the next steps, we’ll setup our IntelliJ IDEA IDE, sync the code from CRX, modify our “FirstApp Basic Template Component” body.jsp and add some useful code

Setup IDEA Project


1) Create the folder C:\dev\code\projects\firstapp

2) Open IDEA and start with creating an “Empty Project”. Name the project firstapp and select the location C:\dev\code\projects\firstapp



3) Select File -> New Module -> Maven Module. Enter module name ui, Content root as C:\dev\code\projects\firstapp\ui and Module file location C:\dev\code\projects\firstapp\ui


4) Click “Next” and enter GroupId “com.aem.firstapp.ui”, ArtifactId “ui” and Version “1.0”, Click “Finish”



5) Once the new module is setup, create the folder src/main/content, src/main/content/META-INF, src/main/content/META-INF/vault

6) Create the file filter.xml under src/main/content/META-INF/vault, add the following content and save changes

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

<workspaceFilter version="1.0">
     <filter root="/apps/firstapp"></filter>
     <filter root="/libs/foundation"></filter>
</workspaceFilter>

In the next steps, we’ll try to sync the code existing in CRX repository /apps/firstapp and work on it.

7) To sync the code from CRX, we’ll use FileVault available in cq5 installation

7.a) Unzip the file C:\dev\cq\author\crx-quickstart\opt\filevault\filevault.zip into the same directory

7.b) Add the folder C:\dev\cq\author\crx-quickstart\opt\filevault\vault-cli-2.4.34\bin to your environment PATH variable, to make vlt.bat available from the cmd line.

7.c) Filevault is installed. To confirm open command prompt and type vlt --help, it should show the list of available commands

7.d) Let us setup maven dependencies. Open the file C:\dev\code\projects\firstapp\ui\pom.xml and add the following

<repositories>
<repository>
<id>daycentral</id>
<name>Day Central (repo.adobe.com)</name>
<url>http://repo.adobe.com/nexus/content/groups/public</url>
</repository>
</repositories>


<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.day.cq.wcm</groupId>
<artifactId>cq-wcm-api</artifactId>
<version>5.6.4</version>
</dependency>
<dependency>
<groupId>com.day.cq</groupId>
<artifactId>cq-wcm-commons</artifactId>
<version>5.6.2</version>
</dependency>
<dependency>
<groupId>com.day.cq.wcm</groupId>
<artifactId>cq-wcm-taglib</artifactId>
<version>5.6.2</version>
</dependency>
<dependency>
<groupId>com.day.cq.wcm</groupId>
<artifactId>cq-wcm-foundation</artifactId>
<version>5.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.api</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>
</dependencyManagement>

Some xml fragments in picture below are collapsed



IDEA should have imported the dependencies to your maven repo eg. C:\Users\nalabotu\.m2\repository\com\day\cq\wcm ( if it has not, create the folder structure in your local maven repo by combining the groupId and artifactId, get the jars from CRX and place them in the respective folders )



8) Let us connect IDEA to CRX using FileVault. For this we’ll use the External Tools feature of IDEA

8.a) Click Settings -> External Tools -> + ( in the right pane )

8.b) Enter the following information

            Name: vlt full check out
            Group: vault
            Description: Checkout the full app source
            Program: C:\dev\cq\author\crx-quickstart\opt\filevault\vault-cli-2.4.34\bin\vlt.bat
            Parameters: co http://localhost:4502/crx
            Working Directory: $FileDir$



8.c) Add one for file and directory check-out ( right click on a project folder and do checkout )

                   Name: vlt check out
                   Group: vault
                   Description: Checkout from CRX
                   Program: C:\dev\cq\author\crx-quickstart\opt\filevault\vault-cli-2.4.34\bin\vlt.bat
                   Parameters: update --force
                   Working Directory: $FileDir$

8.d) Add another for check-in

                    Name: vlt check in
                    Group: vault
                    Description: Checkin to CRX
                    Program: C:\dev\cq\author\crx-quickstart\opt\filevault\vault-cli-2.4.34\bin\vlt.bat
                    Parameters: ci --force
                    Working Directory: $FileDir$

9) We’ve setup the necessary commands for check-in, check-out to/from CRX in IDEA IDE. You can explore other vlt options available for revert etc. Optionally you can set keyboard shortcuts for the vault commands you just created. Go to Settings -> KeyMap -> External Tools ->  vault. Here are my shortcuts for check-in and check-out


10) Use the "vlt full check out" and download the code from CRX to your IDEA. Right click on "content" folder and do vault -> vlt full check out ( Check if you have the vault auth xml with CRX credentials here - C:\Users\nalabotu\.vault\auth.xml )


11) The "body.jsp" should be available in your IDE now


12) Add the following logic to body.jsp. Here we are trying to add a parsys so that we can drag a Text component into the area. Add and vlt check-in

<%@include file="/libs/foundation/global.jsp" %>


<div class="header">
    <b>This is my header</b>
</div>


<div class="content">
    <b>This is my content area</b>


    <div class="content_area">
        <cq:include path="par" resourceType="foundation/components/parsys"/>
    </div>
</div>


<div class="footer">
    <b>This is my footer</b>
</div>



13) Refresh the page http://localhost:4502/cf#/content/firstapp-demo-site/home.html and you should see



14) To add a Text component, goto Design mode ( at the bottom of sidekick, click on shape “L” ), click on Edit of Design of par



15) Under Allowed Components -> General, select “Text”, click “Ok”



16) Expand the sidekick and you are back in “Edit” mode, with “Text” component visible in sidekick. Drag the “Text” component into section Drag components or assets here



17) Double click parsys in which the Text component was added, enter some text and click “Ok”.



18) Congratulations, you’ve developed a webpage, created a template, created a component, added parsys and dragged a component onto the page



In the blog "Experiencing Adobe Experience Manger - Day CQ"

$
0
0

3) Developing AEM 56 (Day CQ) Applications on IntelliJ IDEA 12

4) Get AEM 56 (Day CQ) Sidekick instance and disable the components panel

5) A Sample Auto Tag Listener created as AEM 56 Bundle on IntelliJ IDEA 12. Demonstration with fine details of a sample bundle creation using maven; A JCR Observation listener to create tags on new page creation

6) Creating a Custom Properties panel for Page. If you'd like to add project specific properties for pages, this post could be useful.

7) Html 5 Smart Image widget extended to support custom aspect ratios. Using this widget an author can select a custom aspect ratio (besides Free Crop) and create/crop images with a specific aspect ratio.

8) Favorites (Bookmarks) Component, with samples for customizing pathfield widget (CQ.form.PathField), extending CQ.form.CompositeField, CQ.form.MultiField, Sling servlet etc.

9) A Sample java RMI (standalone) client to retrieve Groups and Users from CRX

10) A multifield component (CQ.form.MultiField) implementation with panel field config to store data from multiple widgets

11) Extending CQ.Ext.form.ComboBox to group content, with a sample sling servlet returning groups and users as json

12) Adding a custom property in create page dialog. Extend the CQ.wcm.Page.getCreatePageDialog function and add a textarea field (Description). A sample wcmcommand implementation is also available

13) Show Search tab by default when a user logs into siteadmin console. A simple SiteAdmin.Actions.js extension

14) Add a new button to the top tool bar of grid in Search tab of sitadmin console. Here we demonstrate extending the search panel (CQ.wcm.SiteAdminSearchPanel) grid to add a button.

15) Extending Content Finder page tab to add additional page filters

16) Hide unused tabs in Content Finder of CQ 56. This post explains hiding tabs like images, documents, s7 browser and showing only the page tab in content finder.

17) Extending the New Page dialog in Websites console to add a "Path Field" for selecting the destination folder and adding a "Create & View" button

18) Adding a button to SideKick Page tab. Here we add the Activate Later functionality to SideKick

19) Showing additional image metadata in content finder image tab. Code sample to extend the image tab of content finder and play with tooltips

20) Disable Geometrixx sites in the left tree of siteadmin Websites console. Sample javascript to play with the left tree nodes

21) Add Scheduled Activations to Site Admin Activate Later dialog. Sample CQ ExtJS grid code

22) Disable the Activate and Deactivate functionality for folders in SiteAdmin console. Javascript code to disable the activate deactivate buttons of siteadmin grid top tool bar on folder row click or siteadmin tree folder node click; disable grid activate deactivate context menu buttons for folders

23) Adding a new tab to Sidekick. A sample extjs checkbox tree implementation showing tag nodes and checkboxes for adding/removing tags for a page

24) Modifying a column in Siteadmin Search Panel grid. In this post, we explain how to modify the template column of Siteadmin Search Panel Grid to show template name instead of path ( path is show in tooltip )

25) Multi Image Component. Dynamically add/remove new images in component dialog

    Get AEM 56 Sidekick instance and disable the components panel

    $
    0
    0

    Goal


    In your javascript code of a component, get the sidekick (CQ.wcm.Sidekick) instance and disable the components panel

    Often situations arise where you need to play with the sidekick of AEM (CQ5) instance and do some customization. This is not Adobe recommended or suggested approach. It's just a way to get the sidekick and do operations on sidekick components.

    Here is the sidekick components panel



    Tested on 


    Adobe Experience Manager ( AEM 561 )


    Implementation


    In your component jsp add the following script



    Result


    The Sidekick components panel is disabled


    Coding a Sample Auto Tag AEM 56 Bundle on IntelliJ IDEA 12

    $
    0
    0

    Goal


    This post is about creating a sample OSGI bundle (what is an OSGI bundle? Any search engine will bring you loads of information) from scratch on IntelliJ IDEA 12.

    Here we create a bundle with


    2) When a new page is created, listener automatically creates a new tag with page name and assigns it to the page. So basically some sample code to create and assign tags.


    Install


    Download and Install AEM (Day CQ) 561, IntelliJ IDEA 12 and Maven 310


    Setup Maven


    1) To add the dependency jars to your IDE, install and setup Maven

    2) Download Maven and extract it to C:\dev\apache-maven-3.1.0.

    3) Open System Environment Variables and add the System Varilable M2_HOME=C:\dev\apache-maven-3.1.0

    4) Add the User Variable M2=%M2_HOME%\bin


    New IDEA Module


    1) In your IDEA project, Select File -> New Module -> Maven Module and enter the module name autotag


    2) Enter the following 

                         GroupId: com.mysample.autotag
                         ArtifactId: autotag
                         Version: 1.0

    3) Check "Create from archetype" and click "Add archetype"




    4) Enter the following information and click ok

                            GroupdId: com.day.jcr.vault
                            ArtifactId: multimodule-content-package-archetype
                            Version: 1.0.2
                            Repository: http://repo.adobe.com/nexus/content/groups/public



    5) Add the following maven properties

                            appsFolderName - autotag
                            artifactName - Auto Tag Listener 
                            packageGroup - Test Company



    6) Here is a screen shot with all the details entered. Click Finish



    7) Here is my autotag module in IDEA



    8) Maven dependencies were already imported and created on file system. For example, the dependency "org.osgi"


    On file system, the jars are available in 


    9) Lets delete the unnecessary source files created by archetype; later we'll add our listener code. At the time of this writing i deleted files under "C:\dev\code\projects\autotag\bundle\src\main\java\com\mysample\autotag". Here is my module now with "C:\dev\code\projects\autotag\bundle\src\main\java" added as module source



    Sample Listener Logic


    1) Lets create a folder listeners and add some sample listener code. Here we are adding a JCR observation listener. This listener operates at JCR level and so has no knowledge of sling web framework, we operate on the raw jcr properties.




    2) Here is the listener source code. The annotation "@Reference" for "repository" makes the jcr repository available for querying nodes. "activate" and "deactivate" methods are self-explanatory. 

    Line 31: we are registering the listener for "NODE_ADDED" events only. So when a new page is created, a node is added in JCR under "/content"; the NODE_ADDED event fires and this listener catches execution.

    package com.mysample.autotag.listeners;

    import org.apache.felix.scr.annotations.Component;
    import org.apache.felix.scr.annotations.Reference;
    import org.apache.sling.jcr.api.SlingRepository;
    import org.osgi.service.component.ComponentContext;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import javax.jcr.RepositoryException;
    import javax.jcr.Session;
    import javax.jcr.observation.Event;
    import javax.jcr.observation.EventIterator;
    import javax.jcr.observation.EventListener;
    import javax.jcr.observation.ObservationManager;

    @Component
    public class AutoTagListener implements EventListener {
    private final Logger LOGGER = LoggerFactory.getLogger(AutoTagListener.class);

    @Reference
    private SlingRepository repository;

    private Session session;
    private ObservationManager observationManager;

    protected void activate(ComponentContext context) throws Exception {
    session = repository.loginAdministrative(null);
    observationManager = session.getWorkspace().getObservationManager();

    observationManager.addEventListener(this, Event.NODE_ADDED, "/", true, null,
    null, true);
    LOGGER.info("Added JCR event listener - AutoTagListener");
    }

    protected void deactivate(ComponentContext componentContext) {
    try {
    if (observationManager != null) {
    observationManager.removeEventListener(this);
    LOGGER.info("Removed JCR event listener - AutoTagListener");
    }
    } catch (RepositoryException re) {
    LOGGER.error("Error removing the JCR event listener - AutoTagListener", re);
    } finally {
    if (session != null) {
    session.logout();
    session = null;
    }
    }
    }

    public void onEvent(EventIterator it) {
    try {

    }catch (Exception e) {
    LOGGER.error(e.getMessage(), e);
    }

    return;
    }
    }


    Install and Test Bundle


    1) In your IDEA click on "Maven Projects" on the right side of your screen




    2) Under Profiles -> Check "autoInstallBundle", "autoInstallPackage". Under "Reactor Project" right click on "install' and "Run Maven Build". Make sure your AEM (CQ) instance is up and running before you attempt this.



    3) If the install is successful, in CRX Lite you can see the installed component bundle



    4) Log "C:\dev\code\install\author\crx-quickstart\logs\error.log" shows the autotag bundle installed



    5) Bundles console "http://localhost:4502/system/console/bundles", confirms the bundle installation



    The Real Listener


    1) Lets add some useful logic in the listener. The logic we are going to add creates a tag with page name in CRX and assigns it to the page

    2) Add the following dependency to C:\dev\code\projects\autotag\pom.xml

    com.day.cq.tagging
    cq-tagging
    5.6.2

    3) Add the following dependency to C:\dev\code\projects\autotag\bundle\pom.xml

    com.day.cq.tagging
    cq-tagging


    The jars for necessary dependency are imported to "C:\Users\nalabotu\.m2\repository\com\day\cq\tagging\cq-tagging\5.6.2". If the jar is not available, get it from CRX (/libs/cq/tagging/install/cq-tagging-5.6.2.jar) and place it in the location

    2) Add necessary logic to listener

    Line 25: Declare a variable for tag namespace. Namespaces are like containers for tags. Tags that are going to be created by this listener are stored in the namespace "mysample"

    Line 30, 31: Create a reference to tag manager implementation (a tag manager object for playing with tags)

    Line 67, 74: A node added event is fired when a page content node is created. So we get the page content node, page node and continue with tag creation. If the node created is not of our interest (say an audit node) we skip the event processing

    Line 85: Check if the namespace exists. A null means the namespace doesn't exist, so create one

    Line 90: Create the tag with page name

    Line 95: Create the cq:tags property on page content node and set it with the tag created above.

    Line 96: Very important, save the changes

    package com.mysample.autotag.listeners;

    import com.day.cq.tagging.JcrTagManagerFactory;
    import com.day.cq.tagging.Tag;
    import com.day.cq.tagging.TagManager;
    import org.apache.felix.scr.annotations.Component;
    import org.apache.felix.scr.annotations.Reference;
    import org.apache.sling.jcr.api.SlingRepository;
    import org.osgi.service.component.ComponentContext;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import javax.jcr.Node;
    import javax.jcr.RepositoryException;
    import javax.jcr.Session;
    import javax.jcr.observation.Event;
    import javax.jcr.observation.EventIterator;
    import javax.jcr.observation.EventListener;
    import javax.jcr.observation.ObservationManager;

    @Component
    public class AutoTagListener implements EventListener {
    private final Logger LOGGER = LoggerFactory.getLogger(AutoTagListener.class);

    private static final String NAMESPACE = "/etc/tags/mysample";

    @Reference
    private SlingRepository repository;

    @Reference
    JcrTagManagerFactory tmf;

    private Session session;
    private ObservationManager observationManager;

    protected void activate(ComponentContext context) throws Exception {
    session = repository.loginAdministrative(null);
    observationManager = session.getWorkspace().getObservationManager();

    observationManager.addEventListener(this, Event.NODE_ADDED, "/", true, null,
    null, true);
    LOGGER.info("Added JCR event listener - AutoTagListener");
    }

    protected void deactivate(ComponentContext componentContext) {
    try {
    if (observationManager != null) {
    observationManager.removeEventListener(this);
    LOGGER.info("Removed JCR event listener - AutoTagListener");
    }
    } catch (RepositoryException re) {
    LOGGER.error("Error removing the JCR event listener - AutoTagListener", re);
    } finally {
    if (session != null) {
    session.logout();
    session = null;
    }
    }
    }

    public void onEvent(EventIterator it) {
    try {
    while (it.hasNext()) {
    Event event = it.nextEvent();
    LOGGER.info("AutoTagListener - new add event: ", event.getPath());

    Node pageContentNode = session.getNode(event.getPath());

    if( ( pageContentNode == null ) || !pageContentNode.getPrimaryNodeType().isNodeType("cq:PageContent")){
    LOGGER.debug("Skip processing node: " + event.getPath());
    return;
    }

    Node pageNode = pageContentNode.getParent();

    if( ( pageNode == null ) || !pageNode.getPrimaryNodeType().isNodeType("cq:Page")){
    LOGGER.debug("Skip processing node: " + pageNode);
    return;
    }

    TagManager tMgr = tmf.getTagManager(session);
    Tag superTag = tMgr.resolve(NAMESPACE);
    Tag tag = null;

    if(superTag == null){
    tag = tMgr.createTag(NAMESPACE, "My Sample", "My Sample tags", true);
    LOGGER.info("Tag Name Space created : ", tag.getPath());
    }

    tag = tMgr.createTag(NAMESPACE + "/" + pageNode.getName(), pageNode.getName(), "Auto tag : " + pageNode.getName(), true);

    String tagArray[] = new String[1];
    tagArray[0] = tag.getNamespace().getName() + ":" + tag.getPath().substring(tag.getPath().indexOf(NAMESPACE) + NAMESPACE.length() + 1);

    pageContentNode.setProperty("cq:tags", tagArray);
    session.save();
    }
    }catch (Exception e) {
    LOGGER.error(e.getMessage(), e);
    }

    return;
    }
    }

    3) Reinstall the component (Run Maven Goal "install" )

    If you see the following error in error.log

    02.09.2013 17:08:21.097 *WARN* [127.0.0.1 [1378159701028] POST /crx/packmgr/service.jsp HTTP/1.1] com.day.jcr.vault.packaging.impl.JcrPackageImpl Refusing to recreate snapshot Test Company/.snapshot:autotag-content:1.0, already exists.

    Remove the previously installed bundle from OSGI container (http://localhost:4502/system/console/bundles)



    Remove the previously installed component from CRX (http://localhost:4502/crx/de/index.jsp#/apps, Right click on "autotag" and "Delete")




    4) Run the maven goal "install"


    Test and Check


    1) Create a page "Listener Test" of any template type in siteadmin console (http://localhost:4502/siteadmin)



    2) Check the Tagging console (http://localhost:4502/tagging). The tag listener-test should have been created under namespace "My Sample"



    3) In CRX (http://localhost:4502/crx) check the tags node (/etc/tags/mysample/listener-test)



    4) Check the page "Listener Test" properties in CRX



    5) Check if tag "listener-test" is assigned to the page by opening Side Kick -> Pages tab -> Page Properties -> Tags/Keywords



    6) Check if the tag is assigned by navigating to the page (http://localhost:4502/cf#/content/test-site/listener-test.html) and searching with tags:listener-test in Content Finder Pages tab



    Creating Custom Page Properties Dialog in CQ

    $
    0
    0

    Goal


    In this post, we create a custom page properties dialog with fields to store additional page information and display it on the page. A custom page properties dialog is useful when you'd like to add project specific page properties and make use of them in the components added on page. Let us also add a simple listener on a page properties field. Source code available for download

    First Steps


    If you are new to developing applications on CQ, i recommend reading the following posts and develop a sample component before you continue...

    1) A Developer Installation
    2) Developing a sample CQ Page Component

    Create the Panel


    1) Assuming you have the following page component created and available in CRX



    2) Right click on /apps/samples/components/basictemplate, select "Create Node", enter the following details and click Ok

                            Name: customprops
                            Type: cq:TabPanel



    3) Add other necessary details for creating the tab panel

                             xtype: tabpanel
                             title: Custom Properties
                             activeTab: 0



    4) Save changes in CRX

    5) Right click on /apps/samples/components/basictemplate/customprops, select "Create Node", enter the following details and click "Ok"

                        Name: items
                        Type: cq:WidgetCollection




    6) Save Changes



    7) Follow the same process to create node with name details of type cq:Widget ( /apps/samples/components/basictemplate/customprops/items/details ) with the following details

                    xtype: panel
                    title: Details



    8) Create node items of type cq:WidgetCollection (/apps/samples/components/basictemplate/customprops/items/details/items)

    9) Create node message of type cq:Widget (/apps/samples/components/basictemplate/customprops/items/details/items/message), enter the following details and Save changes

                      xtype: textfield
                      fieldLabel: Welcome Message
                      name: ./title
                      defaultValue: Welcome to CQ




    10) At this point we've created a Custom Properties dialog Tab Panel with a Text Field to enter some text

    Add Panel to SideKick


    1) To add the panel to sidekick, Create a file head.jsp ( Right click on /apps/samples/components/basictemplate, Select "Create" -> "Create File" ), Save changes.



    2) Checkout the file head.jsp to your IDE. ( I use Intelij IDEA IDE, See this post on how to integrate your IDEA with CRX )

    3) Add the following code in your basictemplate head.jsp created above ( These are pieces from /libs/foundation/components/page/head.jsp, /libs/wcm/core/components/init/init.jsp to load the side kick, /libs/foundation/components/page being the sling:resourceSuperType of our basictemplate component )

    <%@include file="/libs/foundation/global.jsp"%>
    <%@page import="com.day.cq.wcm.api.WCMMode,
    com.day.cq.widget.HtmlLibraryManager,org.apache.commons.lang.StringEscapeUtils"
    %>
    <%@taglib prefix="cq" uri="http://www.day.com/taglibs/cq/1.0"%>



    <head>

    <%
    if ( WCMMode.fromRequest(request) != WCMMode.DISABLED ) {
    HtmlLibraryManager htmlMgr = sling.getService(HtmlLibraryManager.class);

    if (htmlMgr != null) {
    htmlMgr.writeCssInclude(slingRequest, out, "cq.wcm.edit", "cq.tagging", "cq.security");
    htmlMgr.writeJsInclude(slingRequest, out, "cq.wcm.edit", "cq.tagging", "cq.security" );
    }

    String dlgPath = null;

    if ( ( editContext != null ) && ( editContext.getComponent() != null ) ) {
    dlgPath = editContext.getComponent().getDialogPath();
    }
    %>



    <%
    }
    %>

    <%= StringEscapeUtils.escapeXml(currentPage.getTitle()) %>
    </head>



    4) Check-in the file to CRX ( or instead of using IDE you can use CRXDE Lite in browser, open head.jsp, add the above code and save changes)

    5) Assuming you have created page "My Page" of type "basictemplate" under http://localhost:4502/siteadmin#/content -> FirstApp Demo Site; Access the url http://localhost:4502/cf#/content/firstapp-demo-site/my-page.html, goto SideKick -> Page tab, at the bottom you should see Custom Page Properties. Click and the tab panel we added earlier is displayed in a window




    6) Enter "Thank you" in the text field and click ok; the page gets refreshed

    7) Open CRXDE Lite (http://localhost:4502/crx/de) and goto /content/firstapp-demo-site/my-page/jcr:content; the title textfield message you added above should have been saved as the property title



    8) Let us modify the basictemplate body.jsp (/apps/samples/components/basictemplate/body.jsp) to show this title on the page. Add the following code to body.jsp and check-in to CRX

    <%@include file="/libs/foundation/global.jsp" %>

    <cq:defineobjects/>

    <%
    //properties is an implicit object defined in cq taglib and made available
    //with tag declaration <cq:defineobjects/>
    String myTitle = String.valueOf(properties.get("title"));
    pageContext.setAttribute("myTitle", myTitle);
    %>

    ${myTitle} message was added in SideKick -> Page tab -> Custom Page Properties

    9) The "Thank you" message added earlier in "Custom Page Properties" is shown




    Add ExtJS Listener on Panel


    1) Let's add a sample JS listener on the panel 

    2) Open CRXDE Lite, goto /apps/samples/components/basictemplate/customprops, right click and add node listeners of type nt:unstructured. Add the following details

                        afterrender: function(dialog){ myTitleFn(dialog); }



    Here we are adding a listener for the afterrender event on tab panel and listener function calls the function myTitleFn with dialog as argument. In the next step we are going define the function "myTitleFn"

    3) Define the myTitleFn global function in your basictemplate body.jsp. It's not a best practice to define global functions, always scope the functions in JS objects. We are doing it to keep things simple, the function shows an alert with dialog title.

    <%@include file="/libs/foundation/global.jsp" %>

    <cq:defineObjects/>

    <%
    //properties is an implicit object defined in cq taglib and made available
    //with tag declaration <cq:defineObjects/>
    String myTitle = String.valueOf(properties.get("title"));
    pageContext.setAttribute("myTitle", myTitle);
    %>

    ${myTitle} message was added in SideKick -> Page tab -> Custom Page Properties

    <script type="text/javascript">
    var myTitleFn = function(dialog){
    alert("Please enter your welcome message in '" + dialog.title + "'");
    }
    </script>

    4) Check-in or Save body.jsp in CRX, access page http://localhost:4502/cf#/content/firstapp-demo-site/my-page.html, Click on SideKick -> Page tab -> Custom Page Properties and you should see the alert





     5) Download the source code

    Configuring CQ 56 war on Tomcat and run it from Intellij IDEA

    $
    0
    0

    Goal


    This post is for people who do not like opening too many windows while coding. To run AEM (CQ) instance the general approach followed by developers is, double click the CQ quickstart jar or open a command prompt window and run the jar using "java" command. In both cases a new window opens and you obviously will have your IDE ( Eclipse or Intellij ) open for coding. So,

    1) A CQ run window
    2) Eclipse or Intellij IDEA IDE application window
    3) Browser to test your application

    The intention of this post is to get rid of CQ window and run the application from your IDE; also explains the CQ configuration on Tomcat

    Install and Configure


    1) Download & Install IntelliJ IDEA 12; Download Tomcat 7 zip and expand it to a directory, say C:\dev\code\install\apache-tomcat-7.0.42

    2) Open C:\dev\code\install\apache-tomcat-7.0.42\bin\catalina.bat and add enough memory

                           set JAVA_OPTS=-Xmx1792m -XX:MaxPermSize=512m

    3) Open C:\dev\code\install\apache-tomcat-7.0.42\conf\server.xml and change the http connector port from 8080 to CQ author instance default port 4502



    4) Delete ROOT war from C:\dev\code\install\apache-tomcat-7.0.42\webapps to deploy CQ war as the root application

    5) Copy cq-quickstart-5.6.0-20130125.war to C:\dev\code\install\apache-tomcat-7.0.42\webapps

    6) Rename cq-quickstart-5.6.0-20130125.war to ROOT.war

    7) Create the CRX folder C:\dev\code\install\apache-tomcat-7.0.42\crx-quickstart. This is the location of your JCR repository created by CQ

    8) Using WinZIP or WinRAR, extract the file web.xml in ROOT.war (ROOT.war\WEB-INF\web.xml) onto the filesystem.Open extracted web.xml in a text editor and change init-paramsling.home from crx-quickstart to C:/dev/code/install/apache-tomcat-7.0.42/crx-quickstart

            <init-param>
            <param-name>sling.home</param-name>
            <param-value>C:/dev/code/install/apache-tomcat-7.0.42/crx-quickstart</param-value>
            </init-param>

    9) Open cmd prompt at C:\dev\code\install\apache-tomcat-7.0.42\bin and start server using the command "catalina run". Just to make sure everything is alright, this is the first and last time we'll be doing it


    10) Check the folder C:\dev\code\install\apache-tomcat-7.0.42\crx-quickstart and the following folders should have be created

                 launchpad
                 logs
                 repository



    11) It may take 3-4 minutes first time, for the server to start and get to a running state, wait and check the log C:\dev\code\install\apache-tomcat-7.0.42\crx-quickstart\logs\error.log to see if all services are starting up properly

    12) Open a browser window and type http://localhost:4502 and it should redirect to http://localhost:8080/system/granite/license/index.html for license information. If you have license.properties get the customer name and license key from file, and enter in the form

    13) Check http://localhost:4502/system/console/bundles and http://localhost:4502/crx/de to see if everything is ok ( user/pass : admin/admin )

    Starting Tomcat from IntelliJ IDEA


    1) Shutdown the tomcat started in section above

    2) In your Intellij 12, open Run -> Edit Configurations


    3) Click "+" and add Tomcat Server -> Local



    4) Enter the following information in "Server" tab

                     Name: Tomcat 7
                     Application Server: Click configure and select the Tomcat Home (C:\dev\code\install\apache-tomcat-7.0.42)
                     Startup page: http://localhost:4502/
                     Http Port: 4502



    5) Click on tab "Deployment", click "+" -> "External Source" and select the CQ ROOT.war. So, when tomcat is started the ROOT war (CQ) is deployed



    6) Click on tab "Startup/Connection". Developers generally start the server in debug mode, so select "Debug" and add necessary settings or just select default settings and click ok.




    7) Start the server from your IDE by clicking on Run -> Debug 'Tomcat 7' and you should see the ROOT war being deployed in server startup messages



          Debugger attached




    HTML 5 Smart Image Component Custom Aspect Ratios

    $
    0
    0

    Goal


    Create a html 5 smart image component that supports custom aspect ratios, image cropping. An author can create images of different aspect ratios using the same image component. Before you proceed, the process explained here is not Adobe suggested approach, it's just a thought; Package install, Source codeand Video demonstration are available for download..


    Prerequisites


    If you are new to CQ pls visit this blog post; it explains the page component basics and setting up your IDE

    Create the Component


    1) In your CRXDE Lite http://localhost:4502/crx/de, create the below folder and save changes

                          /apps/imagecustomar

    2) Copy the component /libs/foundation/components/logo and paste it in path /apps/imagecustomar

    3) Rename /apps/imagecustomar/logo to /apps/imagecustomar/image

    4) Rename /apps/imagecustomar/image/logo.jsp to /apps/imagecustomar/image/image.jsp

    5) Change the package statement of /apps/imagecustomar/image/img.GET.java from libs.foundation.components.logo to apps.imagecustomar.image

    6) Change the following properties of /apps/imagecustomar/image

                         componentGroup - My Components
                         jcr:title - Image with Custom Aspect Ratios

    7) Rename /apps/imagecustomar/image/design_dialog to /apps/imagecustomar/image/dialog

    8) Delete /apps/imagecustomar/image/dialog/items/basic

    9) Replace the code in /apps/imagecustomar/image/image.jsp with the following...

    <%@include file="/libs/foundation/global.jsp" %>
    <%@ page import="com.day.cq.commons.Doctype,
    com.day.cq.wcm.foundation.Image,
    java.io.PrintWriter" %>
    <%
    try {
    Resource res = null;

    if (currentNode.hasProperty("imageReference")) {
    res = resource;
    }
    %>

    <%
    if (res == null) {
    %>
    Configure Image
    <%
    } else {
    Image img = new Image(res);
    img.setItemName(Image.NN_FILE, "image");
    img.setItemName(Image.PN_REFERENCE, "imageReference");
    img.setSelector("img");
    img.setDoctype(Doctype.fromRequest(request));
    img.setAlt("Home");
    img.draw(out);
    }
    } catch (Exception e) {
    e.printStackTrace(new PrintWriter(out));
    }
    %>


    10) Add this Image component on a page and select an image. The image dialog with cropping features enabled looks like below. Here, Free crop has default aspect ratio (0,0)


    10) We now have a basic image component with ootb features ( crop, rotate etc ). Let us modify this component to add custom aspect ratios; in the process we also create and register a new ExtJS xtype

    Create and Register xtype


    1) Create the node /apps/imagecustomar/image/clientlib of type cq:ClientLibraryFolder and add the following properties

                 categories - String[] - mycomponent.imagear
                 dependencies - String[] - cq.widgets

    2) Create file (type nt:file) /apps/imagecustomar/image/clientlib/js.txt and add the following

                  imagear.js

    3) Create file /apps/imagecustomar/image/clientlib/imagear.js. For now, add the following code

                  alert("hi");

    4) Modify /apps/imagecustomar/image/image.jsp to include the clientlib created above

                   <cq:includeClientLib categories="mycomponent.imagear"/>

    5) Save changes and access the component dialog; an alert "hi" should popup, confirming the js file load.

    6) Add the following JS code to imagear.js. This logic adds the custom aspect ratios UI changes to crop tool

    var MyClientLib = MyClientLib || {};

    MyClientLib.Html5SmartImage = CQ.Ext.extend(CQ.html5.form.SmartImage, {
    crops: {},

    constructor: function (config) {
    config = config || {};

    var aRatios = {
    "freeCrop": {
    "value": "0,0",
    "text": CQ.I18n.getMessage("Free crop")
    }
    };

    var tObj = this;

    $.each(config, function (key, value) {
    if (key.endsWith("AspectRatio")) {
    var text = config[key + "Text"];

    if (!text) {
    text = key;
    }

    if (!value) {
    value = "0,0";
    }

    aRatios[key] = {
    "value": value,
    "text": text
    };

    tObj.crops[key] = { text: text, cords : ''};
    }
    });

    var defaults = { "cropConfig": { "aspectRatios": aRatios } };
    config = CQ.Util.applyDefaults(config, defaults);

    MyClientLib.Html5SmartImage.superclass.constructor.call(this, config);
    },

    initComponent: function () {
    MyClientLib.Html5SmartImage.superclass.initComponent.call(this);

    var imgTools = this.imageToolDefs;
    var cropTool;

    if(imgTools){
    for(var x = 0; x < imgTools.length; x++){
    if(imgTools[x].toolId == 'smartimageCrop'){
    cropTool = imgTools[x];
    break;
    }
    }
    }

    if(!cropTool){
    return;
    }

    for(var x in this.crops){
    if(this.crops.hasOwnProperty(x)){
    var field = new CQ.Ext.form.Hidden({
    id: x,
    name: "./" + x
    });

    this.add(field);

    field = new CQ.Ext.form.Hidden({
    name: "./" + x + "Text",
    value: this.crops[x].text
    });

    this.add(field);
    }
    }

    var userInterface = cropTool.userInterface;

    this.on("loadimage", function(){
    var aRatios = userInterface.aspectRatioMenu.findByType("menucheckitem");

    if(!aRatios){
    return;
    }

    for(var x = 0; x < aRatios.length; x++){
    if(aRatios[x].text !== "Free crop"){
    aRatios[x].on('click', function(radio){
    var key = this.getCropKey(radio.text);

    if(!key){
    return;
    }

    if(this.crops[key].cords){
    this.setCoords(cropTool, this.crops[key].cords);
    }else{
    var field = CQ.Ext.getCmp(key);
    this.crops[key].cords = this.getRect(radio, userInterface);
    field.setValue(this.crops[key].cords);
    }
    },this);
    }

    var key = this.getCropKey(aRatios[x].text);

    if(key && this.dataRecord && this.dataRecord.data[key]){
    this.crops[key].cords = this.dataRecord.data[key];

    var field = CQ.Ext.getCmp(key);
    field.setValue(this.crops[key].cords);
    }
    }
    });

    cropTool.workingArea.on("contentchange", function(changeDef){
    var aRatios = userInterface.aspectRatioMenu.findByType("menucheckitem");
    var aRatioChecked;

    if(aRatios){
    for(var x = 0; x < aRatios.length; x++){
    if(aRatios[x].checked === true){
    aRatioChecked = aRatios[x];
    break;
    }
    }
    }

    if(!aRatioChecked){
    return;
    }

    var key = this.getCropKey(aRatioChecked.text);
    var field = CQ.Ext.getCmp(key);

    this.crops[key].cords = this.getRect(aRatioChecked, userInterface);
    field.setValue(this.crops[key].cords);
    }, this);
    },

    getCropKey: function(text){
    for(var x in this.crops){
    if(this.crops.hasOwnProperty(x)){
    if(this.crops[x].text == text){
    return x;
    }
    }
    }

    return null;
    },

    getRect: function (radio, ui) {
    var ratioStr = "";
    var aspectRatio = radio.value;

    if ((aspectRatio != null) && (aspectRatio != "0,0")) {
    ratioStr = "/" + aspectRatio;
    }

    if (ui.cropRect == null) {
    return ratioStr;
    }

    return ui.cropRect.x + "," + ui.cropRect.y + "," + (ui.cropRect.x + ui.cropRect.width) + ","
    + (ui.cropRect.y + ui.cropRect.height) + ratioStr;
    },

    setCoords: function (cropTool, cords) {
    cropTool.initialValue = cords;
    cropTool.onActivation();
    }
    });

    CQ.Ext.reg("myhtml5smartimage", MyClientLib.Html5SmartImage);


    7) To create the following image source paths...

    /content/firstapp-demo-site/test/_jcr_content/par/image.img.png/2To1AspectRatio.jpg
    /content/firstapp-demo-site/test/_jcr_content/par/image.img.png/9To1AspectRatio.jpg
    /content/firstapp-demo-site/test/_jcr_content/par/image.img.png/5To1AspectRatio.jpg

    Replace the code in /apps/imagecustomar/image/image.jsp with below code. This jsp renders the cropped custom aspect ratio images...

    <%@include file="/libs/foundation/global.jsp" %>
    <%@ page import="com.day.cq.wcm.foundation.Image,
    java.io.PrintWriter" %>
    <%@ page import="java.util.HashMap" %>
    <%@ page import="java.util.Map" %>

    <cq:includeClientLib js="mycomponent.imagear"/>

    <%
    try {
    Resource res = null;

    if (currentNode.hasProperty("imageReference")) {
    res = resource;
    }

    if (res == null) {
    %>
    Configure Image
    <%
    } else {
    PropertyIterator itr = currentNode.getProperties();
    Property prop = null; String text = "";
    Map<String, String> aMap = new HashMap<String, String>();

    while(itr.hasNext()){
    prop = itr.nextProperty();

    if(prop.getName().endsWith("AspectRatio")){
    text = prop.getName();

    if(currentNode.hasProperty(prop.getName() + "Text")){
    text = currentNode.getProperty(prop.getName() + "Text").getString();
    }

    aMap.put(prop.getName(), text);
    }
    }

    Image img = null; String src = null;

    if(aMap.isEmpty()){
    %>
    Cropped Images with custom aspect ratios not available
    <%
    }else{
    for(Map.Entry entry : aMap.entrySet()){
    img = new Image(res);
    img.setItemName(Image.PN_REFERENCE, "imageReference");
    img.setSuffix(entry.getKey() + ".jpg");
    img.setSelector("img");

    src = img.getSrc();
    %>
    <br><br><b><%=entry.getValue()%></b><br><br>
    <img src='<%=src%>'/>
    <%
    }
    }
    }
    } catch (Exception e) {
    e.printStackTrace(new PrintWriter(out));
    }
    %>

    8) Add the following code to /apps/imagecustomar/image/img.GET.java. This java logic reads crop co-ordinates from CRX and outputs the image bytes to browser

    package apps.imagecustomar.image;

    import java.awt.*;
    import java.io.IOException;
    import java.io.InputStream;

    import javax.jcr.RepositoryException;
    import javax.jcr.Property;
    import javax.servlet.http.HttpServletResponse;

    import com.day.cq.commons.ImageHelper;
    import com.day.cq.wcm.foundation.Image;
    import com.day.cq.wcm.commons.AbstractImageServlet;
    import com.day.image.Layer;
    import org.apache.commons.io.IOUtils;
    import org.apache.sling.api.SlingHttpServletRequest;
    import org.apache.sling.api.SlingHttpServletResponse;

    public class img_GET extends AbstractImageServlet {
    protected Layer createLayer(ImageContext c) throws RepositoryException, IOException {
    return null;
    }

    protected void writeLayer(SlingHttpServletRequest req, SlingHttpServletResponse resp, ImageContext c, Layer layer)
    throws IOException, RepositoryException {
    Image image = new Image(c.resource);
    image.setItemName(Image.NN_FILE, "image");
    image.setItemName(Image.PN_REFERENCE, "imageReference");

    if (!image.hasContent()) {
    resp.sendError(HttpServletResponse.SC_NOT_FOUND);
    return;
    }

    layer = image.getLayer(false, false,false);

    String rUri = req.getRequestURI();
    String ratio = rUri.substring(rUri.lastIndexOf("/") + 1, rUri.lastIndexOf(".jpg"));
    String cords = c.properties.get(ratio, "");

    boolean modified = false;

    if(!"".equals(cords)){
    Rectangle rect = ImageHelper.getCropRect(cords, c.resource.getPath());
    layer.crop(rect);

    modified = true;
    }else{
    modified = image.crop(layer) != null;
    }

    modified |= image.resize(layer) != null;
    modified |= image.rotate(layer) != null;

    if (modified) {
    resp.setContentType(c.requestImageType);
    layer.write(c.requestImageType, 1.0, resp.getOutputStream());
    } else {
    Property data = image.getData();
    InputStream in = data.getStream();
    resp.setContentLength((int) data.getLength());
    String contentType = image.getMimeType();

    if (contentType.equals("application/octet-stream")) {
    contentType=c.requestImageType;
    }

    resp.setContentType(contentType);
    IOUtils.copy(in, resp.getOutputStream());
    in.close();
    }

    resp.flushBuffer();
    }
    }

    9) Finally, add the following properties in your CRX node /apps/imagecustomar/image/dialog/items/img





    Favorites (Bookmarks) Widget Component

    $
    0
    0

    Goal


    This blog post is on creating a Favorites (Bookmarks) Component. For dashboard use-cases where an author has to group a set of page links and put it on a page for quick access, the following approach could be useful. It also explains

                 1) Creating custom widget in CQ

                 2) Using CQ.form.MultiField

                 3) Extending CQ.form.CompositeField

                 4) Sample Ajax Sling Servlet

                 5) Customizing CQ.form.PathField search window with new buttons and tree loader

    Here is a demo,



    In the following video

                 1) User admin logs in and shows the permissions for user John. John has read permissions on all /content nodes but modify on sites first-app-demo-site and geometrixx only

                 2)  John logs in, creates the favorites component, adds some links, rearranges them, deletes etc.


    Download source code and video

    Thanks to this blogger for giving a starting point http://cq.shishank.info/2011/12/19/multifield-with-custom-xtype/

    Prerequisites


    If you are new to CQ

    1) Read this post on how to create a sample page component

    2) Read this post on how to setup your IDE and create an OSGI component

    Create Servlet


    1) The first step is creating and deploying a servlet to read content page nodes from CRX. Here the servlet PageNodesServlet ( deployed as OSGI component ) accepts content path and returns the nodes in a json response. The pathfield widget treeloader requests this servlet for nodes and based on the input parameters ( path and type) returns all child nodes of a path or just the nodes for which user has modify permission

    package apps.mycomponents.favorites;

    import com.day.cq.commons.LabeledResource;
    import com.day.text.Text;
    import org.apache.commons.collections.Predicate;
    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.servlets.SlingAllMethodsServlet;
    import org.apache.sling.commons.json.io.JSONWriter;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import javax.jcr.Session;
    import javax.servlet.ServletException;
    import java.io.IOException;
    import java.util.Iterator;
    import java.util.LinkedList;
    import java.util.List;

    @SlingServlet (
    paths="/bin/mycomponents/favorites/pagenodes",
    methods = "GET",
    metatype = true,
    label = "Page Nodes Servlet"
    )
    public class PageNodesServlet extends SlingAllMethodsServlet {
    private static final long serialVersionUID = 1L;

    private static final Logger LOG = LoggerFactory.getLogger(PageNodesServlet.class);

    class FolderOrPagePredicate implements Predicate {
    @Override
    public boolean evaluate(Object o) {
    Resource resource = (Resource)o;
    return resource.getResourceType().equals("sling:Folder") || resource.getResourceType().equals("cq:Page");
    }
    }

    private final Predicate FOLDER_PAGE_PREDICATE = new FolderOrPagePredicate();

    @Override
    protected void doGet(final SlingHttpServletRequest request, final SlingHttpServletResponse response)
    throws ServletException, IOException {
    response.setContentType("text/html");
    response.setCharacterEncoding("utf-8");

    String path = request.getParameter("path");
    String type = request.getParameter("type");

    if( ( type == null ) || type.trim().equals("")){
    type = "all";
    }

    ResourceResolver resolver = request.getResourceResolver();
    Resource res = resolver.getResource(path);
    Session userSession = resolver.adaptTo(Session.class);

    List<Resource> children = new LinkedList<Resource>();
    JSONWriter jw = new JSONWriter(response.getWriter());

    try{
    for (Iterator iter = resolver.listChildren(res); iter.hasNext(); ) {
    Resource child = (Resource)iter.next();

    if(FOLDER_PAGE_PREDICATE.evaluate(child) && hasPermission(type, userSession, child.getPath())){
    children.add(child);
    }
    }

    write(request, jw, children, type);
    }catch(Exception e){
    LOG.error("Error getting nodes",e);
    throw new ServletException(e);
    }
    }

    private boolean hasPermission(String type, Session userSession, String resourcePath) throws Exception{
    return "all".equals(type) || userSession.hasPermission(resourcePath, "set_property");
    }

    private boolean hasChildren(Resource res, ResourceResolver resolver, String type ) throws Exception{
    Session userSession = resolver.adaptTo(Session.class);
    Iterator<Resource> iter = resolver.listChildren(res);

    while (iter.hasNext()) {
    Resource child = iter.next();
    if (FOLDER_PAGE_PREDICATE.evaluate(child) && hasPermission(type, userSession, child.getPath())) {
    return true;
    }
    }

    return false;
    }

    public void write(SlingHttpServletRequest request, JSONWriter out, List<Resource> list, String type) throws Exception{
    ResourceResolver resolver = request.getResourceResolver();
    out.array();

    for (Resource resource : list) {
    out.object();

    LabeledResource lr = resource.adaptTo(LabeledResource.class);
    String name = Text.getName(resource.getPath());

    out.key("name").value(name);
    out.key("type").value(resource.getResourceType());

    boolean hasChildren = hasChildren(resource, resolver, type);

    out.key("cls").value(hasChildren ? "folder" : "file");

    if (!hasChildren) {
    out.key("leaf").value(true);
    }

    String text;

    if (lr == null) {
    text = name;
    } else {
    text = lr.getTitle() == null ? name : lr.getTitle();
    }

    out.key("text").value(text);
    out.endObject();
    }

    out.endArray();
    }
    }

    2) Install ( this post explains how-to ) the servlet; you should see folder /apps/favorites/install created in CRXDE Lite (http://localhost:4502/crx/de) with bundle jar

    3) Access the servlet in browser with url http://localhost:4502/bin/mycomponents/favorites/pagenodes?path=/content and nodes under /content are returned in a json response

    Create Component


    1) Create the component (of type cq:Component) /apps/favorites/favorites with following properties



    2) Create dialog /apps/favorites/favorites/dialog (of type cq:Dialog) with following properties



    3) Create node /apps/favorites/favorites/clientlib ( of type cq:ClientLibraryFolder ) and add the following properties



    4) Create file node /apps/favorites/favorites/clientlib/favorites.js and add the following code. Here we are creating a new ExtJS xtype for favorites field widget and registering it for use in multifield widget of dialog, which we are going to configure in the next steps..

    var MyClientLib = {
    dataUrl: ''
    };

    MyClientLib.FavoritesField = CQ.Ext.extend(CQ.form.CompositeField, {
    favText: null,
    favPath: null,
    fav: null,

    constructor: function(config){
    config = config || {};
    var defaults = { "labelWidth" : 150, "layout" : "form", border: true,
    padding: "10px", width: 500, boxMaxWidth : 520 };
    config = CQ.Util.applyDefaults(config, defaults);
    MyClientLib.FavoritesField.superclass.constructor.call(this, config);
    },

    initComponent: function () {
    MyClientLib.FavoritesField.superclass.initComponent.call(this);

    this.fav = new CQ.Ext.form.Hidden({
    name: this.name
    });

    this.add(this.fav);

    this.add(new CQ.Ext.form.Label({
    text: "Display Text"
    }));

    this.favText = new CQ.Ext.form.TextField({
    width: 300,
    allowBlank: true
    });

    this.add(this.favText);

    this.add(new CQ.Ext.form.Label({
    text: "Select Path"
    }));

    var handlerFn = function(thisObj, type){
    var treePanel = thisObj.treePanel;
    var path = thisObj.path;
    thisObj.treeLoader.dataUrl = MyClientLib.dataUrl + "?type=" + type;
    thisObj.treeLoader.load(thisObj.treePanel.root, function(){
    treePanel.selectPath(path);
    });
    }

    var buttons = [ new CQ.Ext.Button( { text: "All", width: 68, tooltip: 'Show all tree nodes', handler: function(){ handlerFn(this,'all'); }} ),
    new CQ.Ext.Button( { text: "Modify", width: 95, tooltip: 'Show nodes with modify permission only', handler: function(){ handlerFn(this,'write'); } } ),
    CQ.Dialog.OK, CQ.Dialog.CANCEL
    ];

    this.favPath = new CQ.form.PathField({
    treeLoader: new CQ.Ext.tree.TreeLoader({
    dataUrl:MyClientLib.dataUrl,
    requestMethod: "GET"
    }),
    browseDialogCfg: { "buttons" : buttons},
    allowBlank: false,
    width: 300,
    listeners: {
    dialogclose: {
    fn: function(f){
    var selNode = this.browseDialog.treePanel.getSelectionModel().getSelectedNode();
    this.ownerCt.favText.setValue(selNode.text);
    }
    }
    }
    });

    this.add(this.favPath);

    var dialog = this.findParentByType('dialog');

    dialog.on('beforesubmit', function(){
    var value = this.getValue();

    if(value){
    this.fav.setValue(value);
    }
    },this)
    },

    getValue: function () {
    if(this.favPath.el && this.favPath.el.dom){
    var link = {
    "url" : this.favPath.getValue(),
    "text" : this.favText.getValue()
    };

    return JSON.stringify(link);
    }

    return null;
    },

    setValue: function (value) {
    var link = JSON.parse(value);
    this.favText.setValue(link.text);
    this.favPath.setValue(link.url);
    this.fav.setValue(value);
    }
    });

    CQ.Ext.reg("myfavoritesfield", MyClientLib.FavoritesField);

    5) Create file node /apps/favorites/favorites/clientlib/js.txt and add

                      favorites.js

    6) Set the title property of /apps/favorites/favorites/dialog/items/items/tab1 to Add

    7) Create node /apps/favorites/favorites/dialog/items/items/tab1/items of type cq:WidgetCollection

    8) Create node /apps/favorites/favorites/dialog/items/items/tab1/items/favfield of type cq:Widget and add the following properties



    9) Create node /apps/favorites/favorites/dialog/items/items/tab1/items/favfield/fieldConfig of type cq:Widget and add the following properties



    10) Add the following code in /apps/favorites/favorites/favorites.jsp

    <%@ page import="com.day.cq.wcm.api.WCMMode" %>
    <%@ page import="org.osgi.framework.FrameworkUtil" %>
    <%@ page import="apps.mycomponents.favorites.PageNodesServlet" %>
    <%@ page import="org.osgi.framework.Bundle" %>
    <%@ page import="org.osgi.framework.ServiceReference" %>
    <%@ page import="org.apache.sling.commons.json.JSONObject" %>
    <%@ page import="java.io.PrintWriter" %>
    <%@include file="/libs/foundation/global.jsp" %>
    <%@page session="false" %>

    <cq:includeClientLib categories="mycomponents.favorites"/>

    <div style="display: block; border-style: solid; border-width: 1px; margin: 10px; padding: 10px">
    <b>Favorites Component</b>

    <%
    try {
    Property property = null;

    if(currentNode.hasProperty("favorites")){
    property = currentNode.getProperty("favorites");
    }

    if (property != null) {
    JSONObject obj = null;
    String resourcePath = null;
    Value[] favorites = null;

    if(property.isMultiple()){
    favorites = property.getValues();
    }else{
    favorites = new Value[1];
    favorites[0] = property.getValue();
    }

    for (Value val : favorites) {
    obj = new JSONObject(val.getString());
    resourcePath = xssAPI.getValidHref(String.valueOf(obj.get("url")) + ".html");
    %>
    <a href="<%=resourcePath%>"><%=obj.get("text")%></a>
    <br><br>
    <%
    }
    } else {
    %>
    Configure the favorite pages in dialog
    <br><br>
    <%
    }
    %>

    </div>

    <%
    if (WCMMode.fromRequest(request) != WCMMode.DISABLED) {
    Bundle bundle = FrameworkUtil.getBundle(PageNodesServlet.class);
    ServiceReference[] services = bundle.getRegisteredServices();

    //assuming we have only one servlet as osgi service
    String sPath = String.valueOf(services[0].getProperty("sling.servlet.paths"));

    %>
    <script type="text/javascript">
    CQ.Ext.onReady(function () {
    MyClientLib.dataUrl = '<%=sPath%>';
    })
    </script>
    <%
    }
    } catch (Exception e) {
    e.printStackTrace(new PrintWriter(out));
    }
    %>


    11) Save All and Done; add the component on a page and you should be able to bookmark site pages


    AEM CQ - Retrieve Groups and Users from CRX using RMI

    $
    0
    0

    Goal


    Create a sample java RMI client to connect to CQ (AEM 56) and access the groups and users in CRX repository

    Download


    Download the necessary jars (JCR, SL4J etc.) from Adobe repo - http://repo.adobe.com/nexus/content/groups/public/ and add them to classpath. Try to run the program and based on errors you'll know which jars are required.

    Setup


    1) Access the CQ configuration console http://localhost:4502/system/console/configMgr in a browser and find the bundle Apache Sling JCR Repository RMI Registrar

    2) Click on Apache Sling JCR Repository RMI Registrar and change the port number from 1099 to 1199 to start RMI bundle

    3) Run the following client

    package apps;

    import org.apache.jackrabbit.rmi.client.ClientNode;
    import org.apache.jackrabbit.rmi.client.ClientRepositoryFactory;

    import javax.jcr.NodeIterator;
    import javax.jcr.Repository;
    import javax.jcr.Session;
    import javax.jcr.SimpleCredentials;
    import javax.jcr.query.Query;
    import javax.jcr.query.QueryManager;

    public class AemGroupsUsers {
    public static void main(String[] args) throws Exception {
    String crxApplicationName = "virtual-crx";
    String repoUrl = "//localhost:1199/" + crxApplicationName;
    String workspace = "crx.default";
    String username = "admin";

    char[] password = "admin".toCharArray();

    ClientRepositoryFactory factory = new ClientRepositoryFactory();
    Repository repository = factory.getRepository(repoUrl);

    Session session = repository.login(new SimpleCredentials(username, password), workspace);
    QueryManager qm = session.getWorkspace().getQueryManager();

    String stmt = "//element(*,rep:Group) order by @rep:principalName";
    Query q = qm.createQuery(stmt, Query.XPATH);

    NodeIterator results = q.execute().getNodes();
    ClientNode node = null;

    System.out.println("Groups Query : " + stmt);

    while (results.hasNext()) {
    node = (ClientNode) results.next();
    System.out.print(node.getName() + ",");
    }

    stmt = "//element(*,rep:User) order by @rep:principalName";
    q = qm.createQuery(stmt, Query.XPATH);

    results = q.execute().getNodes();

    System.out.println("\n\nUsers Query : " + stmt);

    while (results.hasNext()) {
    node = (ClientNode) results.next();
    System.out.print(node.getName() + ",");
    }
    }
    }

    AEM CQ 56 - Panel in CQ.form.MultiField

    $
    0
    0

    Goal


    Create a multifield component (CQ.form.MultiField) with panel field config to store data from multiple widgets. Source code and video are available for download







    Prerequisites


    If you are new to CQ

    1) Read this post on how to create a sample page component

    2) Here is another blog post on multifield fieldConfig customization

    Create Component


    1) Create component /apps/multifieldpanel/multifieldpanel with the following properties


    2) Create clientlib /apps/multifieldpanel/multifieldpanel/clientlib of type cq:ClientLibraryFolder with the following properties



    3) Create file /apps/multifieldpanel/multifieldpanel/clientlib/multipanel.js and add the following code. Here we are creating a panel extension and registering it as xtype mymultipanel

    var MyClientLib = MyClientLib || {};

    MyClientLib.MyMultiPanel = CQ.Ext.extend(CQ.Ext.Panel, {
    panelValue: '',

    constructor: function(config){
    config = config || {};
    MyClientLib.MyMultiPanel.superclass.constructor.call(this, config);
    },

    initComponent: function () {
    MyClientLib.MyMultiPanel.superclass.initComponent.call(this);

    this.panelValue = new CQ.Ext.form.Hidden({
    name: this.name
    });

    this.add(this.panelValue);

    var dialog = this.findParentByType('dialog');

    dialog.on('beforesubmit', function(){
    var value = this.getValue();

    if(value){
    this.panelValue.setValue(value);
    }
    },this);
    },

    getValue: function () {
    var pData = {};

    this.items.each(function(i){
    if(i.xtype == "label" || i.xtype == "hidden" || !i.hasOwnProperty("dName")){
    return;
    }

    pData[i.dName] = i.getValue();
    });

    return $.isEmptyObject(pData) ? "" : JSON.stringify(pData);
    },

    setValue: function (value) {
    this.panelValue.setValue(value);

    var pData = JSON.parse(value);

    this.items.each(function(i){
    if(i.xtype == "label" || i.xtype == "hidden" || !i.hasOwnProperty("dName")){
    return;
    }

    if(!pData[i.dName]){
    return;
    }

    i.setValue(pData[i.dName]);
    });
    },

    validate: function(){
    return true;
    },

    getName: function(){
    return this.name;
    }
    });

    CQ.Ext.reg("mymultipanel", MyClientLib.MyMultiPanel);

    4) Create file /apps/multifieldpanel/multifieldpanel/clientlib/js.txt and add the following code

                   multipanel.js

    5) Create dialog /apps/multifieldpanel/multifieldpanel/dialog for component above with the following properties. Here,

           a) The node /apps/multifieldpanel/multifieldpanel/dialog/items/items/tab1/items/map/fieldConfig has xtype mymultipanel

           b) Each widget in the multipanel (eg. /apps/multifieldpanel/multifieldpanel/dialog/items/items/tab1/items/map/fieldConfig/items/product-year-value) should have a dName property, which is read by multi panel to store value entered by author for the field

    <?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"
    title="Multi Field"
    xtype="dialog">
    <items
    jcr:primaryType="cq:Widget"
    xtype="tabpanel">
    <items jcr:primaryType="cq:WidgetCollection">
    <tab1
    jcr:primaryType="cq:Panel"
    title="Add">
    <items jcr:primaryType="cq:WidgetCollection">
    <map
    jcr:primaryType="cq:Widget"
    hideLabel="false"
    name="./map"
    title="Map"
    xtype="multifield">
    <fieldConfig
    jcr:primaryType="cq:Widget"
    border="true"
    hideLabel="true"
    layout="form"
    padding="10px"
    width="1000"
    xtype="mymultipanel">
    <items jcr:primaryType="cq:WidgetCollection">
    <product-year-value
    jcr:primaryType="cq:Widget"
    dName="year"
    fieldLabel="Year"
    width="60"
    xtype="textfield"/>
    <product-price-value
    jcr:primaryType="cq:Widget"
    dName="price"
    fieldLabel="Price"
    width="60"
    xtype="textfield"/>
    <product-version-value
    jcr:primaryType="cq:Widget"
    dName="version"
    fieldLabel="Path to Version"
    xtype="pathfield"/>
    <product-lowStock-value
    jcr:primaryType="cq:Widget"
    dName="lowStock"
    fieldLabel="Low Stock ?"
    width="25"
    xtype="checkbox"/>
    </items>
    </fieldConfig>
    </map>
    </items>
    </tab1>
    </items>
    </items>
    </jcr:root>



    6) Add the following code in /apps/multifieldpanel/multifieldpanel/multifieldpanel.jsp

    <%@ page import="org.apache.sling.commons.json.JSONObject" %>
    <%@ page import="java.io.PrintWriter" %>
    <%@include file="/libs/foundation/global.jsp" %>
    <%@page session="false" %>

    <div style="display: block; border-style: solid; border-width: 1px; margin: 10px; padding: 10px">
    <b>Multi Field Sample</b>

    <%
    try {
    Property property = null;

    if(currentNode.hasProperty("map")){
    property = currentNode.getProperty("map");
    }

    if (property != null) {
    JSONObject obj = null;
    Value[] values = null;

    if(property.isMultiple()){
    values = property.getValues();
    }else{
    values = new Value[1];
    values[0] = property.getValue();
    }

    for (Value val : values) {
    obj = new JSONObject(val.getString());
    %>
    Year : <b><%= obj.get("year") %></b>,
    Price : <b><%= obj.get("price") %></b>,
    Version : <b><%= obj.get("version")%></b>,
    Low Stock : <b><%=obj.get("lowStock")%></b>
    <%
    }
    } else {
    %>
    Add values in dialog
    <%
    }
    } catch (Exception e) {
    e.printStackTrace(new PrintWriter(out));
    }
    %>

    </div>


    7) Here is the component structure

    AEM CQ 56 - Extend Combo Box (CQ.Ext.form.ComboBox) Widget

    $
    0
    0

    Goal


    This post is about extending the CQ Combo Box (CQ.Ext.form.ComboBox) widget to provide simple hierarchical (2-level) content. It also has a sample Sling Servlet to retrieve users and groups from CRX as json. Source code and video are available for download

    Here is a screenshot and demo







    Prerequisites


    If you are new to CQ

    1) Read this post on how to create a sample page component

    2) Read this post on how to setup your IDE and create an OSGI component

    Create Servlet


    1) Create and deploy servlet to read users and groups from CRX. Here the servlet RepoGroupsUsers ( deployed as OSGI component ) returns the groups and users in json response.

    package com.mycomponents.groupsusers;

    import org.apache.felix.scr.annotations.sling.SlingServlet;
    import org.apache.jackrabbit.api.security.user.*;
    import org.apache.sling.api.SlingHttpServletRequest;
    import org.apache.sling.api.SlingHttpServletResponse;
    import org.apache.sling.api.resource.ResourceResolver;
    import org.apache.sling.api.servlets.SlingAllMethodsServlet;
    import org.apache.sling.commons.json.io.JSONWriter;
    import org.apache.sling.jcr.base.util.AccessControlUtil;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import javax.jcr.Session;
    import javax.servlet.ServletException;
    import java.io.IOException;
    import java.util.Iterator;

    @SlingServlet(
    paths="/bin/mycomponents/groupsusers",
    methods = "GET",
    metatype = true,
    label = "Groups and Users Servlet"
    )
    public class RepoGroupsUsers extends SlingAllMethodsServlet {
    private static final long serialVersionUID = 1L;

    private static final Logger LOG = LoggerFactory.getLogger(RepoGroupsUsers.class);

    @Override
    protected void doGet(final SlingHttpServletRequest request, final SlingHttpServletResponse response)
    throws ServletException, IOException {
    response.setContentType("text/html");
    response.setCharacterEncoding("utf-8");

    try{
    ResourceResolver resolver = request.getResourceResolver();
    Session session = resolver.adaptTo(Session.class);

    UserManager um = AccessControlUtil.getUserManager(session);
    JSONWriter jw = new JSONWriter(response.getWriter());

    Group group = null;
    User user = null; Object obj = null;String id = null;

    Iterator<Authorizable> users, groups = um.findAuthorizables(new Query() {
    public void build(QueryBuilder builder) {
    builder.setSelector(Group.class);
    }
    });

    jw.object();
    jw.key("data").array();

    while(groups.hasNext()){
    group = (Group)groups.next();

    jw.object();
    jw.key("id").value(group.getID());
    jw.key("text").value(group.getPrincipal().getName());
    jw.key("group").value("y");
    jw.endObject();

    users = group.getMembers();

    while(users.hasNext()){
    obj = users.next();

    if(!(obj instanceof User)){
    continue;
    }

    user = (User)obj;
    id = user.getID();

    if(id.contains("@")){
    id = id.substring(0, id.indexOf("@"));
    }

    jw.object();
    jw.key("id").value(id);
    jw.key("text").value(user.getPrincipal().getName());
    jw.endObject();
    }
    }

    jw.endArray();
    jw.endObject();
    }catch(Exception e){
    LOG.error("Error getting groups and users",e);
    throw new ServletException(e);
    }
    }
    }

    2) Install ( this post explains how-to ) the servlet; you should see folder /apps/groupsuserscombo/install created in CRXDE Lite (http://localhost:4502/crx/de) with the bundle jar

    3) Access the servlet in browser with url http://localhost:4502/bin/mycomponents/groupsusers; groups and users in CRX repository ( under /home ) are returned in json response

    Create Component


    1) Create the component (of type cq:Component) /apps/groupsuserscombo/groupsuserscombo with following properties



    2) Create clientlib /apps/groupsuserscombo/groupsuserscombo/clientlib of type cq:ClientLibraryFolder with the following properties



    3) Create file /apps/groupsuserscombo/groupsuserscombo/clientlib/combo.js and add the following code. Here we are creating a combo extension and registering it as xtype stepcombo

    var MyClientLib = MyClientLib || {};

    MyClientLib.StepCombo = CQ.Ext.extend(CQ.Ext.form.ComboBox, {
    constructor: function(config){
    config = config || {};

    config.store = new CQ.Ext.data.Store({
    proxy: new CQ.Ext.data.HttpProxy({
    "autoLoad":false,
    url: "/bin/mycomponents/groupsusers",
    method: 'GET'
    }),
    reader: new CQ.Ext.data.JsonReader({
    root: 'data',
    fields: [
    {name: 'id', mapping: 'id'},
    {name: 'text', mapping: 'text'},
    {name: 'group', mapping: 'group'}
    ]
    })
    });

    config.mode = "remote";
    config.triggerAction = "all";
    config.valueField = 'id';
    config.displayField = 'text';

    config.tpl ='<tpl for=".">' +
    '<tpl if="!group">' +
    '<div class="x-combo-list-item" style="margin-left: 20px">{text}</div>' +
    '</tpl>' +
    '<tpl if="group == \'y\'">' +
    '<div class="x-combo-list-item"><b>{text}</b></div>' +
    '</tpl>' +
    '</tpl>';

    MyClientLib.StepCombo.superclass.constructor.call(this, config);
    },

    initComponent: function () {
    MyClientLib.StepCombo.superclass.initComponent.call(this);

    var resizeFn = function(combo){
    var size = combo.getSize();
    size.width = 200;
    combo.setSize(size);
    };

    this.on('loadcontent', resizeFn);
    }
    });

    CQ.Ext.reg("stepcombo", MyClientLib.StepCombo);

    4) Create file /apps/groupsuserscombo/groupsuserscombo/clientlib/js.txt and add the following code

                   combo.js

    5) Create dialog /apps/groupsuserscombo/groupsuserscombo/dialog (of type cq:Dialog) with following properties

    The xtype of /apps/groupsuserscombo/groupsuserscombo/dialog/items/items/tab1/items/user is set to stepcombo, we've created and registered above

    <?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"
    title="Groups Users"
    xtype="dialog">
    <items
    jcr:primaryType="cq:Widget"
    xtype="tabpanel">
    <items jcr:primaryType="cq:WidgetCollection">
    <tab1
    jcr:primaryType="cq:Panel"
    layout="form"
    title="Add"
    xtype="panel">
    <items jcr:primaryType="cq:WidgetCollection">
    <user
    jcr:primaryType="cq:Widget"
    fieldLabel="Select User"
    name="./user"
    xtype="stepcombo"/>
    </items>
    </tab1>
    </items>
    </items>
    </jcr:root>

    6) Add the following code in /apps/groupsuserscombo/groupsuserscombo/groupsuserscombo.jsp

    <%@include file="/libs/foundation/global.jsp" %>
    <%@page session="false" %>

    Selected User : <%= ( properties.get("user") == null ) ? "None selected" : properties.get("user") %>


    7) Here is the component structure

    AEM CQ 56 - Adding a custom property in create page dialog

    $
    0
    0

    Goal


    Add a new field in create page dialog for storing additional page information. Here we add Description field in create page dialog. Source code and video are available for download



    Prerequisites


    If you are new to CQ

    1) Read this post on how to create a sample page component

    2) Read this post on how to setup your IDE and create an OSGI component

    UI Changes


    1) We use the overlay architecture of CQ to modify (extend) create page dialog, CQ.wcm.Page.getCreatePageDialog

    2) The logic for page create dialog is available in js file /libs/cq/ui/widgets/source/widgets/wcm/Page.Actions.js. So, to overlay the actions available in this file, create path /apps/cq/ui/widgets/source/widgets/wcm

    3) Create file Page.Actions.js under /apps/cq/ui/widgets/source/widgets/wcm and add the following code. Here we are loading the ootb page actions file and in a way extending CQ.wcm.Page.getCreatePageDialog to add a textarea field - Description

    //load ootb page actions
    $.getScript("/libs/cq/ui/widgets/source/widgets/wcm/Page.Actions.js", function(){

    //the original create page dialog fn
    var cqCreatePageDialog = CQ.wcm.Page.getCreatePageDialog;

    //override ootb function and add description field
    CQ.wcm.Page.getCreatePageDialog = function(parentPath){
    //create dialog by executing the product function
    var dialog = cqCreatePageDialog(parentPath);

    //make necessary UI changes to the dialog created above
    var panel = dialog.findBy(function(comp){
    return comp["jcr:primaryType"] == "cq:Panel";
    }, dialog);

    if(panel && panel.length > 0){
    var description = new CQ.Ext.form.TextArea({
    "fieldLabel": "Description",
    "name": "pageDescription"
    });

    panel[0].insert(2,description);
    panel[0].doLayout();

    dialog.params.cmd = "createPageWithDescription";

    var cmdField = dialog.formPanel.findBy(function(comp){
    return comp["name"] == "cmd";
    }, dialog.formPanel);

    cmdField[0].setValue("createPageWithDescription");
    }
    return dialog;
    }
    });

    4) In #32 above, we are overriding ootb create page command with a custom page command createPageWithDescription. The necessary logic for creating a custom command is explained in next section.

    5) When a user enters page title, description etc and clicks ok, the request is POSTed to /bin/wcmcommand (check /libs/cq/ui/widgets/source/widgets/wcm/Page.Actions.js, CQ.wcm.Page.getCreatePageDialog function). Ootb, the cmd param to this servlet is createPage,  replaced with createPageWithDescription.

    Create Command Bundle


    1) Create a bundle with java class CustomPropertyPageCreateCommand and add the following code. Implementing WCMCommand and returning the custom action name in getCommandName() method executes this command by /bin/wcmcommand servlet if a request is sent with respective cmd param. Here its createPageWithDescription. The rest of class is pretty straightforward, create page using page manager api and set jcr:description with value entered in the description UI field

    package apps.mysample.pagecustomprop;

    import com.day.cq.commons.servlets.HtmlStatusResponseHelper;
    import com.day.cq.wcm.api.Page;
    import com.day.cq.wcm.api.PageManager;
    import com.day.cq.wcm.api.commands.WCMCommand;
    import com.day.cq.wcm.api.commands.WCMCommandContext;
    import org.apache.felix.scr.annotations.Component;
    import org.apache.felix.scr.annotations.Service;
    import org.apache.sling.api.SlingHttpServletRequest;
    import org.apache.sling.api.SlingHttpServletResponse;
    import org.apache.sling.api.servlets.HtmlResponse;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import javax.jcr.Node;
    import javax.jcr.Session;

    @Component
    @Service
    public class CustomPropertyPageCreateCommand implements WCMCommand {

    private static final Logger log = LoggerFactory.getLogger(CustomPropertyPageCreateCommand.class);

    public String getCommandName() {
    return "createPageWithDescription";
    }

    public HtmlResponse performCommand(WCMCommandContext commandContext,
    SlingHttpServletRequest request, SlingHttpServletResponse response,
    PageManager pageManager) {
    HtmlResponse resp = null;

    try {
    String parentPath = request.getParameter(PARENT_PATH_PARAM);
    String pageLabel = request.getParameter(PAGE_LABEL_PARAM);
    String template = request.getParameter(TEMPLATE_PARAM);
    String pageTitle = request.getParameter(PAGE_TITLE_PARAM);
    String pageDescription = request.getParameter("pageDescription");

    Page page = pageManager.create(parentPath, pageLabel, template,pageTitle);

    Session session = request.getResourceResolver().adaptTo(Session.class);
    Node pageNode = page.adaptTo(Node.class);

    Node contentNode = pageNode.getNode("./" + Node.JCR_CONTENT);
    contentNode.setProperty("jcr:description", pageDescription);

    session.save();

    resp = HtmlStatusResponseHelper.createStatusResponse(true, "Page created", page.getPath());
    } catch (Exception e) {
    log.error("Error during page creation.", e);
    resp = HtmlStatusResponseHelper.createStatusResponse(false, e.getMessage());
    }

    return resp;
    }
    }


    AEM CQ 56 - Extending SiteAdmin to show Search tab on login

    $
    0
    0

    Goal


    When a user logs into siteadmin console http://localhost:4502/siteadmin, the Websites tab is shown. Here we need the Search tab to be shown by default and not Websites



    Solution


    1) Use the overlay architecture of CQ and extend SiteAdmin actions. So login to CRXDE Lite (http://localhost:4502/crx/de) and create file /apps/cq/ui/widgets/source/widgets/wcm/SiteAdmin.Actions.js to overlay /libs/cq/ui/widgets/source/widgets/wcm/SiteAdmin.Actions.js

    2) Add the following code in js /apps/cq/ui/widgets/source/widgets/wcm/SiteAdmin.Actions.js

    Here we execute ootb SiteActions js before executing the script to activate Search tab

    $.getScript("/libs/cq/ui/widgets/source/widgets/wcm/SiteAdmin.Actions.js", function(){
    var interval = setInterval(function(){
    var comp = CQ.Ext.getCmp("cq-siteadmin-tabpanel");

    if(comp){
    comp.setActiveTab(1);
    clearInterval(interval);
    }
    }, 500);
    });

    AEM CQ 56 - Add New Page button to SiteAdmin Search Panel Grid

    $
    0
    0

    Goal


    In this post we are going to add a button (New page) to the search panel grid of siteadmin console (http://localhost:4502/siteadmin). Generally for creating a new page you visit the Websites tab of siteadmin console, select a node in left tree and click New Page in right grid. Here we do the same from search tab but the page is created in a default folder /content/drafts/admin. Source code and video are available for download





    Prerequisites


    If you are new to CQ

    1) Read this post on how to create a sample page component

    Solution


    1) Using the overlay architecture of CQ we extend ootb /libs/cq/ui/widgets/source/widgets/wcm/SiteAdmin.Actions.js

    2) To extend the default SiteAdmin actions, create file /apps/cq/ui/widgets/source/widgets/wcm/SiteAdmin.Actions.js and add the following code (Note: Adding a button to the grid toolbar is also possible by overlaying /libs/wcm/core/content/siteadmin/tabs/searchpanel node, but we'll not be discussing about it here )

    Here we execute the default Site Admin actions js file and add a button to search panel grid top tool bar. In the button handler we execute ootb New Page dialog function.

    $.getScript("/libs/cq/ui/widgets/source/widgets/wcm/SiteAdmin.Actions.js", function(){
    var INTERVAL = setInterval(function(){
    var grid = CQ.Ext.getCmp("cq-siteadminsearchpanel-grid");

    if(grid){
    clearInterval(INTERVAL);

    var toolBar = grid.getTopToolbar();
    var createInPath = "/content/drafts/admin";

    toolBar.insertButton(1, new CQ.Ext.Toolbar.Button({
    text: 'New Page',
    cls: "cq-siteadmin-create",
    iconCls: "cq-siteadmin-create-icon",
    handler : function(){
    var dialog = CQ.wcm.Page.getCreatePageDialog(createInPath);
    dialog.show();
    }
    }));

    grid.doLayout();
    }
    }, 250);
    });

    3) Here is the path in CRX

    AEM CQ 56 - Extend and Add Filters to Object Finder Page Tab

    $
    0
    0

    Goal


    In this post we are going to extend the object finder page tab to add additional filters. Ootb, page tab in object finder allows the user to filter results based on a keyword and the search is performed in jcr:content. Here we extend the UI to let user perform search in a specific path, based on title or pages of specific template type. Source code and video are available for download




    Prerequisites


    If you are new to CQ

    1) Read this post on how to create a sample page component

    2) Read this post on how to setup your IDE and create an OSGI component

    Create the Servlets


    1) Code a servlet GetTemplates to return the templates from CQ. When a user clicks on Select Template combo, this servlet returns the available cq:Template in system

    package apps.mysample.objectfinder.pagetab;

    import org.apache.commons.lang3.StringUtils;
    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.ResourceResolver;
    import org.apache.sling.api.servlets.SlingAllMethodsServlet;
    import org.apache.sling.commons.json.io.JSONWriter;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import javax.jcr.Node;
    import javax.jcr.NodeIterator;
    import javax.jcr.Session;
    import javax.jcr.query.Query;
    import javax.jcr.query.QueryManager;
    import javax.servlet.ServletException;
    import java.io.IOException;

    @SlingServlet(
    paths="/bin/mycomponents/objectfinder/templates",
    methods = "GET",
    metatype = false,
    label = "Get Templates Servlet"
    )
    public class GetTemplates extends SlingAllMethodsServlet {
    private static final long serialVersionUID = 1L;

    private static final Logger log = LoggerFactory.getLogger(GetTemplates.class);

    @Override
    protected void doGet(final SlingHttpServletRequest request, final SlingHttpServletResponse response)
    throws ServletException, IOException {
    response.setContentType("application/json");
    response.setCharacterEncoding("utf-8");

    JSONWriter jw = new JSONWriter(response.getWriter());
    String template = request.getParameter("query");

    try{
    ResourceResolver resolver = request.getResourceResolver();
    Session session = resolver.adaptTo(Session.class);
    QueryManager qm = session.getWorkspace().getQueryManager();

    String stmt = "//element(*,cq:Template) order by @jcr:title";

    if(StringUtils.isNotEmpty(template)){
    stmt = "//element(*,cq:Template)[jcr:like(fn:upper-case(@jcr:title), '" + template.toUpperCase() + "%')]";
    }

    Query q = qm.createQuery(stmt, Query.XPATH);

    NodeIterator results = q.execute().getNodes();
    Node node = null, tNode = null; String path = null;

    jw.object();
    jw.key("data").array();

    while(results.hasNext()){
    node = results.nextNode();
    path = node.getProperty("jcr:content/sling:resourceType").getString();

    if(path.startsWith("/apps/")){
    path = path.substring(6);//remove /apps/
    }

    jw.object();
    jw.key("id").value(path);
    jw.key("name").value(node.getProperty("jcr:title").getString());
    jw.endObject();
    }

    jw.endArray();
    jw.endObject();
    }catch(Exception e){
    log.error("Error getting templates",e);
    throw new ServletException(e);
    }
    }
    }

    2) Code servlet PageResultsViewHandler to peform search when the user selects a filter and clicks on search button of the page tab content finder

    package apps.mysample.objectfinder.pagetab;

    import com.day.cq.commons.JSONWriterUtil;
    import com.day.cq.wcm.api.NameConstants;
    import com.day.cq.wcm.api.Page;
    import com.day.cq.wcm.core.contentfinder.Hit;
    import com.day.cq.wcm.core.contentfinder.ViewHandler;
    import com.day.cq.wcm.core.contentfinder.ViewQuery;
    import com.day.cq.xss.ProtectionContext;
    import com.day.cq.xss.XSSProtectionException;
    import com.day.cq.xss.XSSProtectionService;
    import com.day.text.Text;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.felix.scr.annotations.sling.SlingServlet;
    import org.apache.jackrabbit.commons.query.GQL;
    import org.apache.sling.api.SlingHttpServletRequest;
    import org.apache.sling.api.resource.Resource;
    import org.apache.sling.api.resource.ResourceResolver;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import javax.jcr.RepositoryException;
    import javax.jcr.Session;
    import javax.jcr.query.Row;
    import javax.jcr.query.RowIterator;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;

    import static com.day.cq.commons.jcr.JcrConstants.JCR_CONTENT;
    import static com.day.cq.commons.jcr.JcrConstants.JCR_PATH;

    @SlingServlet(
    paths="/bin/mycomponents/objectfinder/pageresults",
    methods = "GET",
    metatype = false,
    label = "Object Finder Page Results Servlet"
    )
    public class PageResultsViewHandler extends ViewHandler {
    private static final long serialVersionUID = 0L;
    private static Logger log = LoggerFactory.getLogger(PageResultsViewHandler.class);

    private static final String NT_CQ_PAGE = "cq:Page";
    private static final String NT_CQ_PAGE_CONTENT = "cq:PageContent";
    private static final String DEFAULT_START_PATH = "/content";
    private static final String LAST_MOD_REL_PATH = JCR_CONTENT + "/cq:lastModified";

    public static final String PAGE_PATH = "path";
    public static final String TYPE = "type";
    public static final String TEMPLATE = "template";

    /**
    * @scr.reference policy="static"
    */
    private XSSProtectionService xss;

    private String getDefaultIfEmpty(SlingHttpServletRequest request, String paramName, String dValue){
    String value = request.getParameter(paramName);

    if (StringUtils.isEmpty(value)) {
    value = dValue;
    }

    return value.trim();
    }

    @Override
    protected ViewQuery createQuery(SlingHttpServletRequest request, Session session, String queryString)
    throws RepositoryException {
    ParserCallback cb = new ParserCallback();

    boolean isFullTextSearch = getDefaultIfEmpty(request,"fullText", "true").equalsIgnoreCase("true");
    String stmt = null;

    if(isFullTextSearch){
    stmt = queryString;
    GQL.parse(stmt, session, cb);
    }else{
    GQL.parse("", session, cb);
    cb.term("jcr:title", queryString, false);
    }

    StringBuilder gql = cb.getQuery();

    String path = getDefaultIfEmpty(request, PAGE_PATH, DEFAULT_START_PATH);
    path = "path:\"" + path + "\"";

    if (StringUtils.isNotEmpty(gql)) {
    gql.append("");
    }

    gql.append(path).append("");

    String limit = getDefaultIfEmpty(request, LIMIT, "20");
    limit = "limit:" + limit;

    gql.append(limit).append("");

    String template = getDefaultIfEmpty(request,TEMPLATE, "");

    if (isFullTextSearch && StringUtils.isEmpty(queryString) && StringUtils.isEmpty(template)) {
    return new MostRecentPages(request, session, gql, xss);
    }

    String type = getDefaultIfEmpty(request, TYPE, NT_CQ_PAGE);
    type = "type:\"" + type + "\"";
    gql.append(type).append("");

    if(StringUtils.isNotEmpty(template)){
    cb.term("sling:resourceType", template, false);
    }

    String order = "order:-" + LAST_MOD_REL_PATH;
    gql.append(order).append("");

    return new GQLViewQuery(request, gql.toString(), session, xss);
    }

    private static Hit createHit(Page page, String excerpt, XSSProtectionService xss)
    throws RepositoryException {
    Hit hit = new Hit();
    hit.set("name", page.getName());
    hit.set("path", page.getPath());
    hit.set("excerpt", excerpt);

    if(page.getTitle() != null) {
    hit.set("title", page.getTitle());

    if (xss != null) {
    try {
    hit.set("title" + JSONWriterUtil.KEY_SUFFIX_XSS, xss.protectForContext(
    ProtectionContext.PLAIN_HTML_CONTENT,page.getTitle()));
    } catch (XSSProtectionException e) {
    log.warn("Unable to protect title {}", page.getTitle());
    }
    }
    } else {
    hit.set("title", page.getName());
    }

    if(page.getLastModified() != null) {
    hit.set("lastModified", page.getLastModified());
    }

    return hit;
    }

    private class ParserCallback implements GQL.ParserCallback {
    private StringBuilder query = new StringBuilder();

    public void term(String property, String value, boolean optional)
    throws RepositoryException {
    if(StringUtils.isEmpty(value)){
    return;
    }

    if (optional) {
    query.append("OR ");
    }

    if (StringUtils.isEmpty(property)) {
    query.append("\"jcr:content/.\":\"");
    query.append(value).append("\"");
    } else {
    property = "jcr:content/" + property;
    query.append("\"").append(property).append("\":");
    query.append("\"").append(value).append("\"");
    }
    }

    public StringBuilder getQuery() {
    return query;
    }
    }

    private static class MostRecentPages implements ViewQuery {
    private final SlingHttpServletRequest request;
    private final Session session;
    private final String gql;
    private final XSSProtectionService xss;

    public MostRecentPages(SlingHttpServletRequest request, Session session, StringBuilder gql,
    XSSProtectionService xss) {
    this.request = request;
    this.session = session;

    gql.append("type:\"").append(NT_CQ_PAGE_CONTENT).append("\"");
    gql.append("order:-").append(NameConstants.PN_PAGE_LAST_MOD).append("");

    this.gql = gql.toString();
    this.xss = xss;
    }

    public Collection<Hit> execute() {
    List<Hit> hits = new ArrayList<Hit>();
    ResourceResolver resolver = request.getResourceResolver();
    RowIterator rows = GQL.execute(gql, session);

    try {
    while (rows.hasNext()) {
    Row row = rows.nextRow();
    String path = row.getValue(JCR_PATH).getString();
    path = Text.getRelativeParent(path, 1);
    Resource resource = resolver.getResource(path);

    if (resource == null) {
    continue;
    }

    Page page = resource.adaptTo(Page.class);
    if (page == null) {
    continue;
    }

    String excerpt;
    try {
    excerpt = row.getValue("rep:excerpt()").getString();
    } catch (Exception e) {
    excerpt = "";
    }

    hits.add(createHit(page, excerpt, xss));
    }
    } catch (RepositoryException re) {
    log.error("A repository error occurred", re);
    }

    return hits;
    }
    }

    private static class GQLViewQuery implements ViewQuery {
    private final SlingHttpServletRequest request;
    private final String queryStr;
    private final Session session;
    private final XSSProtectionService xss;

    public GQLViewQuery(SlingHttpServletRequest request, String queryStr, Session session, XSSProtectionService xss) {
    this.request = request;
    this.queryStr = queryStr;
    this.session = session;
    this.xss = xss;
    }

    public Collection<Hit> execute() {
    List<Hit> hits = new ArrayList<Hit>();
    ResourceResolver resolver = request.getResourceResolver();

    RowIterator rows = GQL.execute(queryStr, session);
    try {
    while (rows.hasNext()) {
    Row row = rows.nextRow();
    String path = row.getValue(JCR_PATH).getString();
    Page page = resolver.getResource(path).adaptTo(Page.class);

    String excerpt;
    try {
    excerpt = row.getValue("rep:excerpt()").getString();
    } catch (Exception e) {
    excerpt = "";
    }
    hits.add(createHit(page, excerpt, xss));
    }
    } catch (RepositoryException re) {
    log.error("A repository error occurred", re);
    }
    return hits;
    }
    }
    }

    Extend Object Finder


    1) The next step is to extend object finder to add the UI elements on page tab. We use the overlay architecture of CQ to extend page tab. For this access CRXDE Lite (http://localhost:4502/crx/de) and create file /apps/cq/ui/widgets/source/widgets/wcm/ContentFinder.js. We overlay the ootb content finder js available in path /libs/cq/ui/widgets/source/widgets/wcm/ContentFinder.js by creating a similar path in /apps. In the overlay, we execute ootb content finder js and later add the necessary UI components

    CQ.Ext.ns("MyComponents");

    MyComponents.ContentFinder = {
    TAB_PAGES : "cfTab-Pages",
    PAGES_QUERY_BOX : "cfTab-Pages-QueryBox",
    CONTENT_FINDER_TAB: 'contentfindertab',
    FULL_TEXT_HIDDEN: "cfTab-Pages-fullText",

    getTemplatesCombo: function(){
    var store = new CQ.Ext.data.Store({
    proxy: new CQ.Ext.data.HttpProxy({
    "autoLoad":false,
    url: "/bin/mycomponents/objectfinder/templates",
    method: 'GET'
    }),
    reader: new CQ.Ext.data.JsonReader({
    root: 'data',
    fields: [
    {name: 'id', mapping: 'id'},
    {name: 'name', mapping: 'name'}
    ]
    })
    });

    var combo = {
    store: store,
    hiddenName: "template",
    xtype : "combo",
    "width": "185",
    style: "margin-top:0",
    mode: "remote",
    triggerAction: "all",
    valueField: 'id',
    displayField: 'name',
    emptyText: 'Select or Start Typing',
    minChars: 2
    };

    return combo;
    },

    addPageFilters: function(){
    var tab = CQ.Ext.getCmp(this.TAB_PAGES);
    var queryBox = CQ.Ext.getCmp(this.PAGES_QUERY_BOX);

    queryBox.add({
    id: this.FULL_TEXT_HIDDEN,
    "xtype": "hidden",
    name: "fullText",
    hiddenName: "fullText",
    value: "true"
    });

    var ftHidden = CQ.Ext.getCmp(this.FULL_TEXT_HIDDEN);

    queryBox.add({
    xtype: 'radiogroup',
    style: "margin:10px 0 0 0",
    columns: 1,
    vertical: true,
    items: [{
    boxLabel: ' Full Text',
    name: 'fullTextRB',
    inputValue: '0',
    checked: true
    }, {
    name: 'fullTextRB',
    boxLabel: ' Title',
    inputValue: '1',
    listeners: {
    'check' : function(c){
    ftHidden.setValue(!c.checked);
    }
    }
    }]
    });

    queryBox.add({
    "xtype": "label",
    "text": "Select Path",
    "cls": "x-form-field x-form-item-label"
    });

    var pathField = {
    "xtype": "pathfield",
    "width": "100%",
    "style" : "margin-top: 0px;",
    hiddenName: "path"
    };

    queryBox.add(pathField);

    queryBox.add({
    "xtype": "label",
    "text": "Select Template",
    style: "margin:10px 0 0 0",
    "cls": "x-form-field x-form-item-label"
    });

    queryBox.add(this.getTemplatesCombo());

    var cfTab = queryBox.findParentByType(this.CONTENT_FINDER_TAB);

    queryBox.add(new CQ.Ext.Panel({
    border: false,
    height: 40,
    items: [{
    xtype: 'panel',
    border: false,
    style: "margin:10px 0 0 0",
    layout: {
    type: 'hbox'
    },
    items: [{
    xtype: "button",
    text: "Search",
    width: 60,
    tooltip: 'Search',
    handler: function (button) {
    var params = cfTab.getParams(cfTab);
    cfTab.loadStore(params);
    }
    },{
    baseCls: "non-existent",
    html:"<span style='margin-left: 10px;'></span>"
    },{
    xtype: "button",
    text: "Clear",
    width: 60,
    tooltip: 'Clear the filters',
    handler: function (button) {
    $.each(cfTab.fields, function(key, field){
    field[0].setValue("");
    });
    }
    }
    ]
    }]
    }));

    queryBox.setHeight(230);
    queryBox.doLayout();

    var form = cfTab.findByType("form")[0];
    cfTab.fields = CQ.Util.findFormFields(form);
    },

    changeResultsStore: function(){
    var queryBox = CQ.Ext.getCmp(this.PAGES_QUERY_BOX);
    var resultsView = queryBox.ownerCt.findByType("dataview");

    var rvStore = resultsView[0].store;
    rvStore.proxy = new CQ.Ext.data.HttpProxy({
    url: "/bin/mycomponents/objectfinder/pageresults.json",
    method: 'GET'
    });
    }
    };

    $.getScript("/libs/cq/ui/widgets/source/widgets/wcm/ContentFinder.js", function(){
    var INTERVAL = setInterval(function(){
    var c = MyComponents.ContentFinder;
    var tabPanel = CQ.Ext.getCmp(CQ.wcm.ContentFinder.TABPANEL_ID);

    if(tabPanel){
    clearInterval(INTERVAL);
    c.addPageFilters();
    c.changeResultsStore();
    }
    }, 250);
    });

    2) Here is the overlay in CRXDE Lite





    AEM CQ 56 - Hide Tabs in Content Finder

    $
    0
    0

    Goal


    CQ 56 has many content finder tabs to find content, images that can be drag and dropped onto pages while authoring. If there is a necessity to hide unused tabs this post could be useful. Source code is available for download


    Prerequisites


    If you are new to CQ

    1) Read this post on how to create a sample page component

    2) Read this post on how to setup your IDE and create an OSGI component

    Solution


    1) Use the overlay architecture of CQ to extend content finder. For this access CRXDE Lite (http://localhost:4502/crx/de) and create file /apps/cq/ui/widgets/source/widgets/wcm/ContentFinder.js. We overlay the ootb content finder js available in path /libs/cq/ui/widgets/source/widgets/wcm/ContentFinder.js by creating a similar path in /apps. In the overlay, we execute ootb content finder js and later hide all tabs except page tab

    CQ.Ext.ns("MyComponents");

    MyComponents.ContentFinder = {
    TAB_S7_BROWSE : "cfTab-S7Browse",
    TAB_MOVIES : "cfTab-Movies",
    TAB_PARAGRAPHS : "cfTab-Paragraphs",
    TAB_PARTICIPANTS : "cfTab-Participants",
    TAB_PRODUCTS : "cfTab-Products",
    TAB_MANUSCRIPTS : "cfTab-Manuscripts",
    TAB_IMAGES: "cfTab-Images",
    TAB_BROWSE: "cfTab-Browse",
    TAB_DOCUMENTS: "cfTab-Documents",

    hideTabs: function(){
    var tabPanel = CQ.Ext.getCmp(CQ.wcm.ContentFinder.TABPANEL_ID);
    var c = MyComponents.ContentFinder;

    var tabs = [ c.TAB_S7_BROWSE,c.TAB_MOVIES,c.TAB_PARAGRAPHS, c.TAB_BROWSE, c.TAB_DOCUMENTS,
    c.TAB_PARTICIPANTS,c.TAB_PRODUCTS,c.TAB_MANUSCRIPTS, c.TAB_IMAGES];

    CQ.Ext.each(tabs, function(t){
    var tab = CQ.Ext.getCmp(t);

    if(tab){
    tabPanel.hideTabStripItem(tab);
    }
    });
    }
    };

    $.getScript("/libs/cq/ui/widgets/source/widgets/wcm/ContentFinder.js", function(){
    var INTERVAL = setInterval(function(){
    var c = MyComponents.ContentFinder;
    var tabPanel = CQ.Ext.getCmp(CQ.wcm.ContentFinder.TABPANEL_ID);

    if(tabPanel){
    clearInterval(INTERVAL);
    c.hideTabs();
    }
    }, 250);
    });

    AEM CQ 56 - Create and View Button on Create New Page Dialog

    $
    0
    0

    Goal


    CQ Create New Page Dialog allows authors to create pages by selecting a folder in the left tree of Websites console. Creating and Viewing new page is a two step process

    1) Create page by clicking on the Create button in dialog

    2) Double click on the new page created to view the page for furthur editing

    In this customization we give the option of selecting folder in the dialog itself and user can create and view page in one step. Source codeand videoare available for download




    Prerequisites


    If you are new to CQ

    1) Read this post on how to create a sample page component

    2) Read this post on how to setup your IDE and create an OSGI component

    Solution


    Use the overlay architecture of CQ and extend ootb Page Actions. To achieve the overlay of /libs/cq/ui/widgets/source/widgets/wcm/Page.Actions.js, create file /apps/cq/ui/widgets/source/widgets/wcm/Page.Actions.js. In the apps Page.Actions.js, when a user clicks on New Page execute product's getCreatePageDialog function and to the dialog created, add a Select Path pathfield and Create & View button.

    $.getScript("/libs/cq/ui/widgets/source/widgets/wcm/Page.Actions.js", function(){
    var cqCreatePageDialog = CQ.wcm.Page.getCreatePageDialog;

    CQ.wcm.Page.getCreatePageDialog = function(parentPath){
    var dialog = cqCreatePageDialog(parentPath);

    var panel = dialog.findBy(function(comp){
    return comp["jcr:primaryType"] == "cq:Panel";
    }, dialog);

    if(panel && panel.length > 0){
    var pathField = {
    "xtype": "pathfield",
    fieldLabel: "Select Path",
    style: "margin-bottom: 5px;",
    "width": "100%",
    rootPath: "/content",
    listeners: {
    dialogclose: {
    fn: function(){
    var parentPath = dialog.formPanel.findBy(function(comp){
    return comp["name"] == "parentPath";
    }, dialog);

    parentPath[0].setValue(this.getValue());

    var dView = dialog.findBy(function(comp){
    return comp["itemSelector"] == "div.template-item";
    }, dialog);

    dView[0].store.baseParams.path = this.getValue();
    dView[0].store.reload();
    }
    }
    }
    };

    panel[0].insert(2,pathField);
    panel[0].doLayout();
    }

    dialog.buttons.splice(0,0,new CQ.Ext.Button( {
    text: "Create & View",
    width: 120,
    tooltip: 'Create page and preview',
    handler: function(button){
    dialog.ok(button, function(form, resp){
    try{
    var text = resp.response.responseText;
    var loc = text.substring(text.indexOf("\"", text.indexOf("href=")) + 1);

    loc = "/cf#" + loc.substr(0, loc.indexOf("\"")) + ".html";
    window.location = loc;
    }catch(err){
    console.log("page create and view - error parsing html response");
    }
    });
    }}
    ));

    return dialog;
    }
    });


    AEM CQ 56 - Activate Later (Scheduled Activation) in SideKick

    $
    0
    0

    Goal


    Add Activate Later functionality available in Siteadmin Websites console to the Sidekick Page tab. Package for install and Demo video are available for download



    Prerequisites


    If you are new to CQ

    1) Read this post on how to create a sample page component

    Solution


    Use Overlay architecture of CQ to overlay the file /libs/cq/ui/widgets/source/widgets/wcm/ContentFinder.js. Login to CRXDE Lite (http://localhost:4502/crx/de), add the file /apps/cq/ui/widgets/source/widgets/wcm/ContentFinder.js with following code

    CQ.Ext.ns("MyClientLib");

    MyClientLib.ContentFinder = {
    scheduleForActivation: function (sideKick) {
    var scheduleForActivationDialog = {
    "jcr:primaryType": "cq:Dialog",
    "height": 240,
    "title": CQ.I18n.getMessage("Activate Later"),
    "id": CQ.Util.createId("myclientlib-cq-activate-later-dialog"),
    "params": {
    "_charset_": "utf-8"
    },
    "items": {
    "jcr:primaryType": "cq:Panel",
    "items": {
    "jcr:primaryType": "cq:WidgetCollection",
    "absTime": {
    "xtype": "datetime",
    "fieldLabel": CQ.I18n.getMessage("Activation Date"),
    "name": "absTime",
    "defaultValue": "now"
    }
    }
    },
    "buttons": {
    "jcr:primaryType": "cq:WidgetCollection",
    "custom": {
    "text": CQ.I18n.getMessage("OK"),
    "cls": "cq-btn-create",
    "handler": function () {
    var dlg = this;
    var paths = [ sideKick.getPath() ];
    var dateTime = this.getField("absTime").getValue();

    var data = {
    id: CQ.Util.createId("myclientlib-cq-asset-reference-search-dialog"),
    path: paths,
    callback: function(p) {
    var params = {
    "_charset_":"UTF-8",
    "model":"/etc/workflow/models/scheduled_activation/jcr:content/model",
    "absoluteTime": dateTime ? dateTime.getTime() : new Date().getTime(),
    "payload":paths,
    "payloadType":"JCR_PATH"
    };
    CQ.HTTP.post("/etc/workflow/instances",
    function(options, success, response) {
    if (!success) {
    CQ.Ext.Msg.alert(
    CQ.I18n.getMessage("Error"),
    CQ.I18n.getMessage("Could not schedule page for activation."));
    } else {
    CQ.Ext.Msg.alert("Success","Page scheduled for activation : " + dateTime);
    }
    },params
    );

    dlg.hide();
    }
    };
    new CQ.wcm.AssetReferenceSearchDialog(data);
    }
    },
    "cancel": CQ.Dialog.CANCEL
    }
    };
    var dialog = CQ.WCM.getDialog(scheduleForActivationDialog);
    dialog.show();
    },

    addActivateLater: function(sk){
    var pagePanel = sk.panels["PAGE"];

    var button = pagePanel.findBy(function(comp){
    return comp["name"] == "ACTIVATE_LATER";
    }, pagePanel);

    if(button && button.length > 0){
    return;
    }

    button = {
    xtype: "button",
    scope: sk,
    name: "ACTIVATE_LATER",
    text: "Activate Later",
    "context": [
    CQ.wcm.Sidekick.PAGE
    ],
    handler: function(){
    MyClientLib.ContentFinder.scheduleForActivation(sk);
    }
    };

    pagePanel.insert(6,button);
    sk.actns.push(button);
    }
    };

    $.getScript("/libs/cq/ui/widgets/source/widgets/wcm/ContentFinder.js", function(){
    var c = MyClientLib.ContentFinder;

    if(window.location.pathname != "/siteadmin"){
    var SK_INTERVAL = setInterval(function(){
    var sk = CQ.WCM.getSidekick();

    if(sk){
    clearInterval(SK_INTERVAL);
    c.addActivateLater(sk);
    }
    }, 250);
    }
    });

    AEM CQ 56 - Showing Additional Image Metadata in ContentFinder

    $
    0
    0

    Goal


    Show additional image metadata in tooltip. The results view of image tab in content finder by default shows the image name on hover. This post is about modifying the view template to show additional metadata information. Package available for install

    Tile view




    List View



    Solution


    1) Using the overlay architecture of CQ, overlay file /libs/cq/ui/widgets/source/widgets/wcm/ContentFinder.js by creating /apps/cq/ui/widgets/source/widgets/wcm/ContentFinder.js and add the following code

    CQ.Ext.ns("MyClientLib");

    MyClientLib.ContentFinder = {
    TAB_IMAGES: "cfTab-Images",

    //interested in showing the following metadata only
    displayNames: { "dam:FileFormat": "File Format", "dam:MIMEtype": "Mime Type",
    "jcr:lastModifiedBy": "Last Modified By","jcr:lastModified": "Last Modified",
    "tiff:ImageLength": "Length","tiff:ImageWidth": "Width"},

    addImagesMetadata: function(){
    var tab = CQ.Ext.getCmp(this.TAB_IMAGES);
    var resultsView = tab.findByType("dataview");
    var store = tab.dataView.store;

    if(!resultsView || resultsView.length == 0){
    return;
    }

    var tBar = resultsView[0].ownerCt.getTopToolbar();
    var dNames = this.displayNames;

    //mouseenter fired when the mouse enters a node in results view
    resultsView[0].on('mouseenter', function(dView, index, node){
    if(node.metaAdded == true){
    return;
    }

    //get the selected view type from toolbar: tile or list
    var viewType = tBar.find("pressed", true);

    if(!viewType || (viewType.length == 0)){
    return;
    }

    viewType = viewType[0];

    var rec = store.getAt(index);
    var path = rec.id + "/jcr:content/metadata.json"; //get the metadata of asset as json

    $.ajax({
    url: path,
    dataType: "json",
    success: function(data){
    if(!data || data.length == 0){
    return;
    }

    var nodeValue = "<div>";

    for(var x in dNames){
    if(dNames.hasOwnProperty(x) && data[x]){
    nodeValue = nodeValue + "<b>" + dNames[x] + "</b>:" + data[x];
    }
    }

    node.metaAdded = true;
    nodeValue = nodeValue + "</div>";

    if(viewType.iconCls == "cq-cft-dataview-mosaic"){
    var attrs = node.children[0].attributes;

    CQ.Ext.each(attrs, function(attr){
    if(attr.nodeName == "ext:qtip"){
    attr.nodeValue = nodeValue;
    }
    });
    }else{
    var qTip = document.createAttribute("ext:qtip");
    qTip.nodeValue = nodeValue;
    node.attributes.setNamedItem(qTip);
    }
    }
    });
    });
    }
    };

    $.getScript("/libs/cq/ui/widgets/source/widgets/wcm/ContentFinder.js", function(){
    var c = MyClientLib.ContentFinder;

    if( window.location.pathname == "/cf" ){
    var INTERVAL = setInterval(function(){
    var tabPanel = CQ.Ext.getCmp(CQ.wcm.ContentFinder.TABPANEL_ID);

    if(tabPanel){
    clearInterval(INTERVAL);
    c.addImagesMetadata();
    }
    }, 250);
    }
    });



    AEM CQ 56 - Working with siteadmin tree nodes

    $
    0
    0

    Goal


    Disable Geometrixx site nodes in the left tree of siteadmin Websites console. Package available for install



    Solution


    1) Using the overlay architecture of CQ, overlay /libs/cq/ui/widgets/source/widgets/wcm/SiteAdmin.Actions.js by creating file /apps/cq/ui/widgets/source/widgets/wcm/SiteAdmin.Actions.js and add the following code

    CQ.Ext.ns("MyClientLib");

    MyClientLib.SiteAdmin = {
    SA_TREE: "cq-siteadmin-tree",

    disableGeometrixx: function(tree){
    if(!tree){
    return;
    }

    var node = tree.getRootNode();

    node.on('expand',function(){
    CQ.Ext.each(this.childNodes, function(cNode){
    var disable = cNode.attributes["name"].indexOf("geometrixx") == 0;

    if(disable === true){
    cNode.setCls("x-item-disabled");
    var disableFn = function(){
    return false;
    };
    cNode.on('beforeclick', disableFn);
    cNode.on('beforedblclick', disableFn);
    cNode.on('beforeexpand', disableFn);
    cNode.on('beforecollapse', disableFn);
    cNode.on('beforeinsert', disableFn);
    cNode.on('beforemove', disableFn);
    cNode.on('beforeremove', disableFn);
    }
    });
    });

    //collapse and expand to fire the expand event,
    node.collapse();
    node.expand();
    }
    };

    $.getScript("/libs/cq/ui/widgets/source/widgets/wcm/SiteAdmin.Actions.js", function(){
    if(window.location.pathname == "/siteadmin"){
    var INTERVAL = setInterval(function(){
    var s = MyClientLib.SiteAdmin;
    var tree = CQ.Ext.getCmp(s.SA_TREE);

    if(tree){
    clearInterval(INTERVAL);
    s.disableGeometrixx(tree);
    }
    }, 250);
    }
    }).fail(function(jqxhr, settings, exception){
    console.log("Error parsing /libs/cq/ui/widgets/source/widgets/wcm/SiteAdmin.Actions.js");
    console.log(exception);
    });

    Viewing all 525 articles
    Browse latest View live


    <script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>