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.
Maintain a clean seperation of concerns even within the JSP. A proven pattern is:
<%@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>
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>
Pull JavaScript out into supporting JS files. Ideally JavaScript is handled ubobtrusively and maintained in JS files served via ClientLibs.
$(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();
});
});
Move “branding” CSS to the brand’s centeralized ClientLib.
.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.
.js-aemcasts-demo-components-content-general-q-and-a .question .close,
.js-aemcasts-demo-components-content-general-q-and-a .answer {
display: none;
}