#5 A Component of Normal Complexity

Presented by David G.

Using a static "Question and Answer" component as a guide, this episode runs through the creation of a AEM Component of normal complexity from start to finish.

Component JSP

/apps/aemcasts/demo/components/content/general/q-n-a/q-n-a.jsp

Maintain a clean seperation of concerns even within the JSP. A proven pattern is:

  1. Get services and helper classes
  2. Get component properties and other data
  3. Apply business logic using the services (1) against the data (2)
  4. Check data for correctness
  5. Render presentation markup
<%@include file="/apps/aemcasts/demo/global/global.jsp"%><%
%><%@page session="false" import="com.day.cq.i18n.I18n,
    com.adobe.granite.xss.XSSAPI,
    com.day.cq.wcm.api.WCMMode"%><%

    /* Services and Utilities */
    final XSSAPI xss = sling.getService(XSSAPI.class);
    final I18n i18n = new I18n(slingRequest);
    final WCMMode mode = WCMMode.fromRequest(slingRequest);

    /* Component Properties and Data */
    final String question = properties.get("question", "");
    final String answer = properties.get("answer", "");
    final String seeMorePath = properties.get("seeMorePath", "");
    final Page seeMorePage = pageManager.getContainingPage(seeMorePath);

%>

<%-- Component Init Checks --%>
<% if(seeMorePage == null || "".equals(question) || "".equals("answer")) {
    if(WCMMode.EDIT.equals(mode)) {
        %><div><img src="/libs/cq/ui/resources/0.gif" class="cq-text-placeholder" alt=""></div><%
    }
    return;
} %>

<%-- Component Presentation --%>
<div class="q-and-a js-aemcasts-demo-components-content-general-q-and-a">
    <a class="question" href="#">
        <span class="open">( <%= i18n.get("Open") %> )</span>
        <span class="close">( <%= i18n.get("Close") %> )</span>

        <cq:text property="question" tagName="span" escapeXml="true"/>
    </a>
    <div class="answer">
        <cq:text property="answer" tagName="div" escapeXml="true"/>
        <div class="see-more">
            <hr/>
            <%= i18n.get("See More") %>
            <a href="<%= resourceResolver.map(seeMorePage.getPath()) %>.html">
                <%= xss.encodeForHTML(seeMorePage.getTitle()) %></a>
        </div>
    </div>
</div>

Component Dialog

/apps/aemcasts/demo/components/content/general/q-n-a/dialog.xml

Provide the interface for AEM Authors to input data into the Component.

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" 
    xmlns:cq="http://www.day.com/jcr/cq/1.0" 
    xmlns:jcr="http://www.jcp.org/jcr/1.0" 
    xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    jcr:primaryType="cq:Dialog"
    activeTab="0"
    title="Question and Answer"
    xtype="tabpanel">
    <items jcr:primaryType="cq:WidgetCollection">
        <tab1
            jcr:primaryType="cq:Widget"
            title="QnA"
            xtype="panel">
            <items jcr:primaryType="cq:WidgetCollection">
                <question jcr:primaryType="cq:Widget"
                    fieldLabel = "Question"
                    name="./question"
                    xtype="textfield"/>
                <answer jcr:primaryType="cq:Widget"
                    fieldLabel = "Answer"
                    name="./answer"
                    xtype="textarea"/>
                <see-more jcr:primaryType="cq:Widget"
                    fieldLabel = "See More"
                    name="./seeMorePath"
                    xtype="pathfield"/>
            </items>
        </tab1>
    </items>
</jcr:root>

JavaScript

Pull JavaScript out into supporting JS files. Ideally JavaScript is handled ubobtrusively and maintained in JS files served via ClientLibs.

/apps/aemcasts/demo/components/content/general/q-n-a/clientlibs/js/q-n-a.js

$(function() {
    $('body').on('click', '.js-aemcasts-demo-components-content-general-q-and-a .question', function(e) {
        var $this = $(this);
        $this.find('.open').toggle();
        $this.find('.close').toggle();
        $this.siblings('.answer').toggle();
        e.preventDefault();
    });
});

CSS

Move “branding” CSS to the brand’s centeralized ClientLib.

/etc/clientlibs/aemcasts/demo/main/css/styles.css (append to..)

.q-and-a {
    margin-bottom: 2em;
}

.q-and-a .question,
.q-and-a .answer {
    padding: .5em 1em;
}

.q-and-a .question {
    display: block;
    background-color: #eee;
}

.q-and-a .answer {
    background-color: #f7f7f7;
    border: solid 1px #eee;
    padding: 1.5em 1em;
}

Only keep the functional CSS coupled tightly with the Component.

/apps/aemcasts/demo/components/content/general/q-n-a/clientlibs/css/q-n-a.css

.js-aemcasts-demo-components-content-general-q-and-a .question .close,
.js-aemcasts-demo-components-content-general-q-and-a .answer {
    display: none;
}