lowercase; dashes for spaces/special chars
Page Components are postfixed with the word "page"
camelCase; no dashes, underscores, spaces
/apps/site/components
Content components are the components that are placed ON a page and are not intended to represent the page template. All content components will have an (in)direct slign:resourceSuperType of the /apps/site/components/base component.
/apps/site/components/content
/apps/site/components/title
/apps/site/components/article
/apps/site/components/accordian
Page components implement cq:Pages. All content components will have an (in)direct slign:resourceSuperType of the /apps/site/page/basepage component.
/apps/site/components/page
/apps/site/components/page/base-page
/apps/site/components/page/home-page
/apps/site/components/page/section-page
/apps/site/components/page/cotainer-page
sling:OsgiConfig nodes are added to the config folder to configure OSGi services.
/apps/site/config
global.jsp is included in every component JSP (content and page) and is used to include common objects into the pageContext.
/apps/site/global/global.jsp
cq:Template definitions are defined here. Futhur organization is at the discression of the developer.
/apps/site/templates
Any ExtJS widgets used for authoring inputs are maintained here.
/apps/site/widgets
CQ Workflow related assets are stored here.
/apps/site/workflow/scripts
/apps/site/workflow/widgets
/apps/site/workflow/dialogs
Client libs will contain the front-end assets managed by developers and not modifiable by Authors.
/etc/clientlibs/site/css
/etc/clientlibs/site/images
/etc/clientlibs/site/js
Use QueryBuilder API; Generally avoid raw queries
Adobe AEM Component Properties
/apps/.../sample
/apps/.../sample/analytics
/apps/.../sample/clientlibs
/apps/.../sample/clientlibs/css
/apps/.../sample/clientlibs/js
/apps/.../sample/clientlibs/css.txt
/apps/.../sample/clientlibs/js.txt
/apps/.../sample/cq:editConfig
/apps/.../sample/cq:htmlTag
/apps/.../sample/cq:template
/apps/.../sample/dialog
/apps/.../sample/design_dialog
/apps/.../sample/icon.png
/apps/.../sample/sample.jsp
/apps/.../sample/post.POST.jsp
/apps/.../sample/views/x.jsp
/apps/.../sample/partials/y.jsp
OSGi Services are usually singletons.
Use Resources/ ValueMaps for READING data – Use Nodes for WRITING data
Getting a Resource and/or ValueMap
Resource myResource = resourceResolver.resolve("/path/to/my/pet");
ValueMap myProperties = myResource.adaptTo(ValueMap.class);
// Default value of "Ira" if no property exists
String pet = myProperties.get("name", "Ira");
// No default value, but converts to String[]
String[] petSounds = myProperties.get("sounds", String[].class);
If you know the full path to a resource, use .getResource(..) instead of .resolve(..)
Resource myResource = resourceResolver.getResource("/path/to/my/pet");
You can get related nodes by...
Resource parent = myResource.getParent();
Resource descendant = myResource.getChild("great/grand/child");
Iterator children = myResource.listChildren();
On CQ 5.6+..
ModifiableValueMap props = myResource.adaptTo(ModifiableValueMap.class);
props.put("color", "golden");
props.remove("sound");
props.save();
Otherwise, write to a node, use JcrUtil or JCR apis.
Node node = myResource.adaptTo(Node.class);
JcrUtil.setProperty(node, "myProperty", "some value!");
node.getSession().save();
In some contexts you only have a Node; turn it into a Resource or ValueMap to read from it.
Resource myResource = resourceResolver.getResource(node.getPath()) ;
ValueMap myProperties = myResource.adaptTo(ValueMap.class);
Create a cq:template node under the cq:Component and populate with default properties or default resource tree
/apps/.../demo[cq:Component]
/apps/.../demo/cq:template[nt:unstructured]
/apps/.../demo/cq:template@cat = "meow"
/apps/.../demo/cq:template@sling:resourceType = ".../demo"
Create a jcr:content node under the cq:Template and populate with default properties or default resource tree
Use Resources/ValueMaps for READING data.
Use Nodes for WRITING data.
ValueMap props1 = resource.adaptTo(ValueMap.class);
Map map = props1.getMap();
map.put("customKey", "custom data");
ValueMap props2 = new ValueMapDecorator(map);
Map map = new HashMap();
map.put("customKey", "custom data");
ValueMap props = new ValueMapDecorator(map);
Path accepts
<cq:include
path="[/]some/resource[.s1.s2]"
resourceType="some/component">
<sling:include
path="[/]some/resource"
[resourceType="some/component"]>
[addSelectors="s1.s2"]
[replaceSelectors="s3.s4"]
[replaceSuffix="foo/bar"]
Use with hierarchy nodes such as CQ Pages, whose content is stored under jcr:content.
Resource resource = currentPage.getContentResource();
InheritanceValueMap valueMap = new HierarchyNodeInheritanceValueMap(resource);
String val = valueMap.getInherited("foo", String.class);
@Reference
ResourceResolverFactory rrf;
...
Map<String, Object> authInfo = new HashMap<String, Object>();
authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, jcrSession);
ResourceResolver rr = rrf.getResourceResolver(authInfo);
<%
sling.getService(SampleService.class);
sling.getServices(SampleService.class, "(uid=foo)");
%>
@Reference(target = "(myprop=foo)")
SampleService sampleService;
Whenever possible, use the ResourceResolver exposed via Sling and the SlingHttpServletRequest obj.
In code not associated with a HTTP Request (ex. workflow, events) you may retrieve a ResourceResolver for a specific user.
NOTE: Avoid using the admin ResourceResolver/Session whenever possible; Instead use a service account whose access is tailored to the use-case.
@Reference
SlingRepository slingRepository;
...
Session adminSession = repo.loginAdministrative(null);
Session session = adminSession.impersonate(
new SimpleCredentials("userIdToImpersonate", new char[0]));
adminSession.logout();
...
// When you're done, close the impersonate session
session.logout();
Create a login token for a specific user.
@Reference
private SlingRepository slingRepository;
...
// Typically in an @Activate method
this.repositoryId = slingRepository.getDescriptor(REPO_DESC_CLUSTER_ID);
if(StringUtils.isBlank(this.repositoryId)) { this.repositoryId = slingRepository.getDescriptor(REPO_DESC_ID); }
if(StringUtils.isBlank(this.repositoryId)) { this.repositoryId = slingSettings.getSlingId(); }
if(StringUtils.isBlank(this.repositoryId)) {
this.repositoryId = UUID.randomUUID().toString();
log.error("Unable to get Repository ID; falling back to a random UUID.");
}
// Create the login-token and set to the response
TokenUtil.createCredentials(request, response, repository, userId, true);
@Reference
WorkflowService wfService;
...
WorkflowSession wfSession = wfService.getWorkflowSession(jcrSession);
WorkflowModel model = wfSession.getModel("/etc/workflow/models/x/jcr:content/model");
WorkflowData data = wfSession.newWorkflowData("JCR_PATH", payloadPath);
Workflow workflow = wfSession.startWorkflow(model, data);
List<HistoryItem> history = workflowSession.getHistory(workItem.getWorkflow());
UserProperties props = slingRequest.adaptTo(UserProperties.class);
or
@Reference
private UserPropertiesService ups;
...
UserPropertiesManager upm = ups.createUserPropertiesManager(resolver);
Authorizable authorizable = resolver.adaptTo(Authorizable.class);
UserProperties properties = upm.getUserProperties(authorizable, "profile");
or
UserPropertiesManager upm = resolver.adaptTo(UserPropertiesManager.class);
Authorizable authorizable = resolver.adaptTo(Authorizable.class);
UserProperties props = upm.getUserProperties(authorizable, "profile");
Configurations methods, resourceTypes, selectors, extensions are IGNORED if paths is set.
Methods defaults to GET if not specified.
@SlingServlet(
label = "Samples - Sling Servlet",
description = "...",
paths = {"/services/all-sample"},
methods = {"GET", "POST"},
resourceTypes = {},
selectors = {"print.a4"},
extensions = {"html", "htm"}
)
public class SampleServlet extends SlingSafeMethodsServlet implements OptingServlet {
@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException, IOException { ... }
}
public class SampleServlet extends SlingAllMethodsServlet implements OptingServlet {
@Override
protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException, IOException { ... }
}
@SlingFilter(
label = "Samples - Sling Filter",
description = "...",
metatype = true,
generateComponent = true,
generateService = true,
order = 0,
scope = SlingFilterScope.REQUEST)
public class SampleSlingFilter
implements javax.servlet.Filter {
...
}
Add the following to a clientlib with categories = [cq.widgets]
CQ.Ext.namespace("app", "app.sidekick", "app.sidekick.actions");
app.sidekick.actions.MyButton = {
"context": CQ.wcm.Sidekick.PAGE,
/*
CQ.wcm.Sidekick.PAGE
CQ.wcm.Sidekick.COMPONENTS
CQ.wcm.Sidekick.WORKFLOW
CQ.wcm.Sidekick.VERSIONING
CQ.wcm.Sidekick.INFO
*/
"text": "My Button",
"handler": function() {
alert("Do something!");
}
};
// Add to bottom of list
CQ.wcm.Sidekick.DEFAULT_ACTIONS.push(app.sidekick.actions.MyButton);
// Insert into list
CQ.wcm.Sidekick.DEFAULT_ACTIONS.splice(2, app.sidekick.actions.MyButton);
To be used on CQ Author
var [CQ.WCM.Page] page =
CQ.WCM.getPage('/content/path/to/page');
CQ.WCM.getContentUrl();
CQ.WCM.isEditMode();
CQ.WCM.isDesignMode();
CQ.WCM.isPreviewMode();
CQ.WCM.setMode( CQ.WCM.MODE_EDIT | CQ.WCM.MODE_DESIGN | CQ.WCM.MODE_PREVIEW );
var userName = "David";
CQ.I18n.getMessage("Hello from {0}", userName);
CQ.shared.HTTP.getPath()
CQ.shared.HTTP.getSelectors()
CQ.shared.HTTP.getSuffix()
CQ.shared.HTTP.getExtension()
For synchronous HTTP Requests: Do not pass in the Callback function
var suppressForbiddenCheck = true;
CQ.shared.HTTP.get('/content/geometrixx/en.json',
function(options, success, response) {
console.log(options); // Request options
console.log(success); // true/false
console.log(response); // Response obj
if(success) {
// eval turns JSON response to JS Obj
var myPage = CQ.shared.HTTP.eval(response);
console.log(myPage['jcr:primaryType'])
}
}, this, suppressForbiddenCheck);
var suppressErrorMsg = false;
var suppressForbiddenCheck = true;
var data = {
city: 'Charlestown',
state: 'MA',
}
CQ.shared.HTTP.post('/home/users/a/admin/profile/me',
function(options, success, xhr, response) {
console.log(options); // Request options
console.log(success); // true/false
console.log(xhr); // w3c xhr
console.log(response); // Response
if(success) {
console.log('Profile Location Updated!');
}
}, data, this, supressErrorMsg, supressForbiddenCheck);
var unsafe = '<script>alert("foo")</script>';
CQ.shared.XSS.getXSSValue(unsafe);
Parameter format: storeName/propertyName
ClientContext.get('profile/authorizableId');
Set and retrieve attributes. (Set auto-persists to ClientContext Store)
ClientContext.set('profile/initials', 'DG');
ClientContext.get('profile/initials'); //=> 'DG'
Persist Store data to the defined persistance layer (usually Cookie)
ClientContext.persist('profile');
Reset ClientContext store to its 'init' state
ClientContext.reset();
Remove all properties/values from ClientContext Store
ClientContext.clear();
ClientContext Stores can be interacted with directly. This is helpful for custom Stores that implement custom APIs.
CQ_Analytics.ProfileDataMgr.getData() //=> {authorizableId : "anonymous", etc : '...' }
CQ_Analytics.ProfileDataMgr.getProperty('authorizableId')
// Persists automatically
CQ_Analytics.ProfileDataMgr.setProperty('foo', 'bar')
CQ_Analytics.ProfileDataMgr.getProperty('foo') //=> 'bar'
Wait for a ClientContext Store to load before using it.
CQ_Analytics.ClientContextUtils.onStoreInitialized("profile",function(store) {
console.log(ClientContext.get('profile/authorizableId'));
}, true);
@Component(
label = "Service name",
description = "Service description",
metatype = true,
immediate = false)
@Properties({
@Property(
label = "My Label"
name = "property-name",
value = "my value",
propertyPrivate = true
)
})
@Service
public class SampleServiceImpl implements SampleService {
private final Logger log =
LoggerFactory.getLogger(this.getClass());
/**
* OSGi Properties *
*/
private static final String DEFAULT_SAMPLE = "hello!";
private String mySample = DEFAULT_SAMPLE;
@Property(label = "Prop name",
description = "Prop description",
value = DEFAULT_SAMPLE)
public static final String PROP_SAMPLE = "sample";
...
@Activate
protected void activate(final Map<String, String> config) {
mySample = PropertiesUtil.toString(config.get(PROP_SAMPLE), DEFAULT_SAMPLE);
}
@Deactivate
protected void deactivate(final Map<String, String> config) { }
}
<%@include file="/apps/../global/global.jsp"%><%
%><%@page session="false" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"
import="com.site.components.SampleComponent"%><%
/**
* BEGIN COMPONENT INITIALIZATION;
* MAJORITY OF LOGIC SHOULD BE ABSTRACTED INTO THE OSGi SERVICE LAYER
**/
final SampleComponent c = sling.getService(SampleComponent.class);
final List<String> list = c.doSomething(slingRequest);
/** BEGIN MARKUP; MINIMAL LOGIC AFTER THIS POINT **/
%>
<%-- Use cq:text to display values from the resource as it supports Diffing --%>
<cq:text property="jcr:title" tagName="h2"/>
<%-- Any hardcoded values, including default values should be passed through
i18n.get(..) or i18n.var(..) for future localization compatability --%>
<h3><%= i18n.get("My List") %></h3>
<ul class="my-list">
<% for(final String item : list) { %>
<li><%= item %></li>
<%=
<% } %>
</ul>
Supporting JavaScript should be unobtrusive (not in the Component JSP), but rather maintained and served from a clientlib.
Component instance specific data or parameters can be written to the component as data-* attributes, and then read by the supporting JavaScript.
$(function() {
$('.js-myapp-components-content-sample .my-link').on('click', function() {
var $this = $(this);
var componentName =
$(this).data('component-name');
var alertMsg = $(this).data('alert-message');
alert(componentName + ': ' + alertMsg);
});
});
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured"/>
<?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="Component Title"
xtype="tabpanel">
<items jcr:primaryType="cq:WidgetCollection">
<tab1
jcr:primaryType="cq:Widget"
title="Tab 1"
xtype="panel">
<items jcr:primaryType="cq:WidgetCollection">
<checkbox
jcr:primaryType="cq:Widget"
fieldLabel="Checkbox"
fieldDescription=""
defaultValue="false"
inputValue="true"
name="./checkbox"
type="checkbox"
xtype="selection"/>
<cqinclude
jcr:primaryType="cq:Widget"
path="/path/to/other/dialog/inputs.infinity.json"
xtype="cqinclude"/>
<datetime
jcr:primaryType="cq:Widget"
fieldLabel="Date Time"
fieldDescription=""
name="./dateTime"
allowBlank="{Boolean}true"
xtype="datetime"/>
<dialogfieldset
jcr:primaryType="cq:Widget"
collapsed="{Boolean}true"
collapsible="{Boolean}true"
title="Fieldset Title"
xtype="dialogfieldset>
<items
jcr:primaryType="cq:WidgetCollection">
</items>
</dialogfieldset>
<displayfield
jcr:primaryType="cq:Widget"
ignoreData="{Boolean}true"
hideLabel="{Boolean}true"
hidden="{Boolean}false"
value="Escaped HTML or plain-text to display"
xtype="displayfield"/>
<dropdown
jcr:primaryType="cq:Widget"
fieldLabel="Dropdown"
fieldDescription=""
defaultValue=""
name="./dropdown"
type="select"
xtype="selection">
<options jcr:primaryType="cq:WidgetCollection">
<children
jcr:primaryType="nt:unstructured"
text="Option 1"
value="option1"/>
<descendants
jcr:primaryType="nt:unstructured"
text="Option 2"
value="option2"/>
</options>
</dropdown>
<html5smartfile
jcr:primaryType="cq:Widget"
autoUploadDelay="1"
ddGroups="[media]"
fieldLabel="HTML 5 Smart File"
fieldDescription=""
fileNameParameter="./fileName"
fileReferenceParameter="./fileReference"
name="./file"
xtype="html5smartfile"/>
<inlinetextfield
jcr:primaryType="cq:Widget"
fieldLabel="Inline Text Field"
fieldDescription=""
name="./inlineTextField"
xtype="inlinetextfield"/>
<ownerdraw
jcr:primaryType="cq:Widget"
fieldLabel="Owner Draw"
fieldDescription=""
html="HTML fragment to use as the owner draw's body content"
name="./ownerDraw"
url="The URL to retrieve the HTML code from. Replaces HTML defined in html."
xtype="ownerdraw"/>
<paragraphreference
jcr:primaryType="cq:Widget"
fieldLabel="Paragraph Reference"
fieldDescription=""
name="./paragraphReference"
xtype="paragraphreference"/>
<multifield
jcr:primaryType="cq:Widget"
fieldLabel="Multifield"
fieldDescription="Click the '+' to add a new page"
name="./multifield"
xtype="multifield">
<fieldConfig
jcr:primaryType="cq:Widget"
width="155"
xtype="pathfield"/>
</multifield>
<pathfield
jcr:primaryType="cq:Widget"
fieldLabel="Pathfield"
fieldDescription="Drop files or pages from the Content Finder"
name="./pathfield"
rootPath="/content"
suffix=".html"
showTitlesInTree="{Boolean}true"
typeAhead="{Boolean}true"
xtype="pathfield"/>
<radiobuttons
jcr:primaryType="cq:Widget"
fieldLabel="Radio button"
fieldDescription="Field Description"
name="./radioButton
defaultValue="option1"
type="radio"
xtype="selection">
<options jcr:primaryType="cq:WidgetCollection">
<option1
jcr:primaryType="nt:unstructured"
text="Option 1"
value="option1"/>
<option2
jcr:primaryType="nt:unstructured"
text="Option 2"
value="option2"/>
</options>
</radiobuttons>
<resType
jcr:primaryType="cq:Widget"
ignoreData="{Boolean}true"
name="./sling:resourceType"
value="some/resource/type"
xtype="hidden"/>
<richtext
jcr:primaryType="cq:Widget"
fieldLabel="Rich Text"
fieldDescription=""
defaultValue=""
hideLabel="{Boolean}true"
name="./richText"
xtype="richtext">
<rtePlugins jcr:primaryType="nt:unstructured">
<table
jcr:primaryType="nt:unstructured"
features="*"/>
</rtePlugins>
</richtext>
<searchfield
jcr:primaryType="cq:Widget"
fieldDescription=""
fieldLabel="Search Field"
name="./searchField"
url="The URL where the search request is sent to (defaults to "/content.search.json")"
xtype="searchfield"/>
<sizefield
jcr:primaryType="cq:Widget"
fieldLabel="Sizefield"
fieldDescription=""
heightParameter="./height"
widthParameter="./width"
heightSuffix="px"
widthSuffix="px"
xtype="sizefield"/>
<textarea
jcr:primaryType="cq:Widget"
fieldLabel="Textarea"
name="./textarea"
grow="{Boolean}true"
xtype="textarea"/>
<textfield
jcr:primaryType="cq:Widget"
fieldLabel="Textfield"
name="./textfield"
xtype="textfield"/>
</items>
</tab1>
<tab2
jcr:primaryType="cq:Widget"
title="Image"
allowUpload="{Boolean}false"
cropParameter="./image/imageCrop"
ddGroups="[media]"
fileNameParameter="./image/fileName"
fileReferenceParameter="./image/fileReference"
mapParameter="./image/imageMap"
name="./image/file"
requestSuffix="/image.img.png"
rotateParameter="./image/imageRotate"
sizeLimit="100"
xtype="html5smartimage"/>
</items>
</jcr:root>
<VirtualHost *:80>
ServerAdmin webmaster@localhost
ServerName mysite.com
DocumentRoot /opt/aem/cache
<Directory /opt/aem/cache>
Options FollowSymLinks
AllowOverride None
</Directory>
<IfModule disp_apache2.c>
SetHandler dispatcher-handler
</IfModule>
# BEGIN REWRITE RULES
# Rewrite rules will pre-pend the AEM path prefix to requests to content pages
RewriteEngine On
# Prevent other page content trees from being served from this domain (dam and campaigns are OK)
RewriteCond %{REQUEST_URI} ^/content
RewriteCond %{REQUEST_URI} !^/content/campaigns
RewriteCond %{REQUEST_URI} !^/content/dam
RewriteRule !^/content/mysite/en - [R=404,L,NC]
# Home page re-writing
# mysite.com/ or mysite.com/home > Home page
RewriteCond %{REQUEST_URI} ^/$
RewriteCond %{REQUEST_URI} ^home$
RewriteRule ^/(.*)$ /content/mysite/en.html [PT,L]
# Ignore requests to "known" AEM root paths, and prefix all others with the proper AEM prefix
RewriteCond %{REQUEST_URI} !^/apps
RewriteCond %{REQUEST_URI} !^/content
RewriteCond %{REQUEST_URI} !^/etc
RewriteCond %{REQUEST_URI} !^/home
RewriteCond %{REQUEST_URI} !^/libs
RewriteCond %{REQUEST_URI} !^/tmp
RewriteCond %{REQUEST_URI} !^/var
RewriteRule ^/(.*)$ /content/mysite/en/$1 [PT,L]
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/access-mysite.com.log combined
ErrorLog ${APACHE_LOG_DIR}/error-mysite.com.log
# Shamlessly stolen from: http://www.cognifide.com/blogs/cq/multidomain-cq-mappings-and-apache-configuration/