aem-component-development
npx skills add https://github.com/headwirecom/aem-agent-skills --skill aem-component-development
Agent 安装分布
Skill 文档
AEM Component Development
AEM components are the building blocks of page content. Each component is a cq:Component
node in the JCR that ties together:
- HTL markup â the rendering template (
.htmlfile) - Sling Model â server-side business logic (Java)
- Dialog â author UI for content editing (
cq:dialog) - Client libraries â CSS/JS for frontend behavior
- Edit config â authoring toolbar behavior (
cq:editConfig)
Prerequisite skill: This skill assumes HTL knowledge. Load the htl-scripting skill for
expression syntax, block statements, XSS contexts, and global objects.
Component File Structure
Standard Maven archetype layout (Touch UI, modern AEM):
ui.apps/src/main/content/jcr_root/apps/<project>/components/<component-name>/
âââ .content.xml â cq:Component definition (title, group, supertype)
âââ <component-name>.html â HTL rendering script
âââ _cq_dialog/
â âââ .content.xml â Touch UI dialog (Granite UI)
âââ _cq_editConfig/
â âââ .content.xml â Edit behavior config
âââ _cq_template/
â âââ .content.xml â Default content on drag-and-drop
âââ _cq_design_dialog/
â âââ .content.xml â Policy/design dialog
âââ clientlib/ â Optional co-located clientlib
âââ .content.xml
âââ css.txt
âââ js.txt
âââ css/
âââ js/
core/src/main/java/<package>/models/
âââ <ComponentName>Model.java â Sling Model
Note: Underscored directories (_cq_dialog) are the filesystem representation of JCR nodes
with colons (cq:dialog). The FileVault serialization requires this naming.
Component Definition (.content.xml)
<?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:Component"
jcr:title="My Component"
jcr:description="A description for authors"
componentGroup="My Project - Content"
sling:resourceSuperType="core/wcm/components/text/v2/text"/>
Key Properties
| Property | Purpose |
|---|---|
jcr:title |
Display name in component browser |
jcr:description |
Tooltip in component browser |
componentGroup |
Grouping in component browser. Use .hidden to hide |
sling:resourceSuperType |
Inherit from another component (rendering, dialog, edit config) |
cq:isContainer |
true if component can contain child components (like a parsys) |
cq:icon |
Coral UI icon name for the component browser |
abbreviation |
2-char abbreviation if no icon |
Agent Workflow: Creating a New Component
Step 1: Define the component node
Create .content.xml with jcr:primaryType="cq:Component", title, group, and optionally
sling:resourceSuperType to extend an existing component.
Step 2: Create the Sling Model
Write the Java model class in core/ module. See references/sling-models.md.
@Model(adaptables = SlingHttpServletRequest.class,
adapters = MyComponent.class,
resourceType = "myproject/components/mycomponent",
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class MyComponentImpl implements MyComponent {
@ValueMapValue
private String title;
@ValueMapValue
private String description;
@Override
public String getTitle() { return title; }
@Override
public String getDescription() { return description; }
}
Step 3: Create the HTL script
<sly data-sly-use.model="com.myproject.core.models.MyComponent"/>
<div class="cmp-mycomponent"
data-sly-test="${model.title || model.description}">
<h2 class="cmp-mycomponent__title"
data-sly-test="${model.title}">${model.title}</h2>
<div class="cmp-mycomponent__description"
data-sly-test="${model.description}">
${model.description @ context='html'}
</div>
</div>
<sly data-sly-test="${!model.title && !model.description && (wcmmode.edit || wcmmode.preview)}">
<div class="cq-placeholder" data-emptytext="My Component"></div>
</sly>
Step 4: Create the dialog
See references/dialogs.md for full Granite UI field reference.
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
xmlns:cq="http://www.day.com/jcr/cq/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0"
xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured"
jcr:title="My Component"
sling:resourceType="cq/gui/components/authoring/dialog">
<content jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<tabs jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/tabs"
maximized="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<properties jcr:primaryType="nt:unstructured"
jcr:title="Properties"
sling:resourceType="granite/ui/components/coral/foundation/container"
margin="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<columns jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns"
margin="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<column jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<title jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Title"
name="./title"/>
<description jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textarea"
fieldLabel="Description"
name="./description"/>
</items>
</column>
</items>
</columns>
</items>
</properties>
</items>
</tabs>
</items>
</content>
</jcr:root>
Step 5: Add client library (if needed)
Step 6: Configure edit behavior (if needed)
<?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:EditConfig"
cq:actions="[edit,delete,insert,copymove]"
cq:layout="editbar"
cq:dialogMode="floating">
<cq:listeners jcr:primaryType="cq:EditListenersConfig"
afteredit="REFRESH_PAGE"
afterinsert="REFRESH_PAGE"/>
</jcr:root>
Extending Existing Components
Use sling:resourceSuperType to inherit everything from a parent:
<jcr:root ...
jcr:primaryType="cq:Component"
jcr:title="Custom Text"
sling:resourceSuperType="core/wcm/components/text/v2/text"
componentGroup="My Project"/>
What you inherit automatically:
- HTL rendering scripts (resolved by Sling resource type hierarchy)
- Dialogs (can overlay individual fields via Sling Resource Merger)
- Edit config, descriptions, icons
To overlay a dialog field, create only the nodes you need to change under _cq_dialog/
and set sling:resourceSuperType on the dialog root. The Sling Resource Merger merges your
changes with the parent.
Core Components
Always prefer extending AEM Core Components over building from scratch. They provide:
- Production-tested, accessible, SEO-friendly markup
- BEM CSS class naming (
cmp-<name>__<element>--<modifier>) - Sling Model exporters for JSON/SPA
- Style System support
- Adobe Client Data Layer integration
Common Core Components to extend:
core/wcm/components/text/v2/textcore/wcm/components/image/v3/imagecore/wcm/components/title/v3/titlecore/wcm/components/teaser/v2/teasercore/wcm/components/list/v4/listcore/wcm/components/container/v1/containercore/wcm/components/page/v3/page
Edit Placeholder Pattern
Components MUST render something visible in edit mode even when empty:
<!--/* Reusable Core Components placeholder template */-->
<sly data-sly-use.template="core/wcm/components/commons/v1/templates.html"
data-sly-call="${template.placeholder @ isEmpty=!model.hasContent}"/>
Or manual fallback:
<div class="cq-placeholder" data-emptytext="${component.properties.jcr:title}"
data-sly-test="${(wcmmode.edit || wcmmode.preview) && isEmpty}"></div>
Content Template (_cq_template/)
Pre-populates the component’s content node when dragged onto a page:
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="nt:unstructured"
title="Default Title"
description="Edit this component"/>
Resource Type Resolution
Sling resolves components by sling:resourceType on the content node:
- Content node has
sling:resourceType = "myproject/components/hero" - Sling looks for
/apps/myproject/components/hero/hero.html - If not found, checks
sling:resourceSuperTypechain - Falls back to
/libs/search path
Script resolution order for a resource type myproject/components/hero:
hero.html(matches component name)html.html(matches extension)GET.html(matches method + extension)- Then walks up the
sling:resourceSuperTypechain
Critical Rules
- Always use Touch UI (
cq:dialog) â Classic UI dialogs are deprecated. - Prefer Sling Models over
WCMUsePojoâ Sling Models are testable, cacheable, and the standard for AEM as a Cloud Service. - Never modify
/libsâ always overlay in/apps. Use Sling Resource Merger for partial overrides. - Always render an edit placeholder when component has no content.
- Use BEM naming for CSS classes:
cmp-<component>__<element>--<modifier>. - Set
componentGroupor the component won’t appear in the authoring UI. - Store dialog field values with
./prefix innameattribute (e.g.,name="./title") to write relative to the component’s content node. - Extend Core Components before building from scratch.
- Client libraries under
/appsneedallowProxy=trueto be served via/etc.clientlibs/.
Reference Files
- references/dialogs.md â Touch UI dialog structure, Granite UI field types, tabs, validation
- references/clientlibs.md â Client library folder setup, categories, dependencies, HTL inclusion
- references/sling-models.md â Sling Model annotations, injection strategies, adapters, testing