本教程介绍自定义的端到端创建 Byline
显示在对话框中创作内容的AEM组件,并探索了开发Sling模型以封装填充组件HTL的业务逻辑。
查看所需的工具和设置说明 本地开发环境.
如果您成功完成了上一章,则可以重用该项目并跳过签出入门项目的步骤。
查看本教程所基于的基线代码:
查看 tutorial/custom-component-start
分支来源 GitHub
$ cd aem-guides-wknd
$ git checkout tutorial/custom-component-start
使用您的Maven技能将代码库部署到本地AEM实例:
$ mvn clean install -PautoInstallSinglePackage
如果使用AEM 6.5或6.4,请附加 classic
配置文件到任何Maven命令。
$ mvn clean install -PautoInstallSinglePackage -Pclassic
您始终可以在以下位置查看完成的代码 GitHub 或通过切换到分行在本地签出代码 tutorial/custom-component-solution
.
在WKND教程的这一可选部分中,创建了一个署名组件,用于显示有关文章投稿人的创作信息。
署名组件
“署名”组件的实施包括一个收集署名内容的对话框和一个自定义Sling模型,该模型可检索以下详细信息:
首先,创建“署名”组件节点结构并定义一个对话框。 这表示AEM中的组件,并隐式定义组件在JCR中的位置资源类型。
该对话框将显示内容作者可以使用的界面。 对于此实施,AEM WCM核心组件的 图像 组件用于处理Byline图像的创作和渲染,因此必须将其设置为此组件的 sling:resourceSuperType
.
在 ui.apps 模块,导航到 /apps/wknd/components
并创建一个名为的文件夹 byline
.
内部 byline
文件夹,添加名为的文件 .content.xml
填充 .content.xml
文件包含以下内容:
<?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"
jcr:primaryType="cq:Component"
jcr:title="Byline"
jcr:description="Displays a contributor's byline."
componentGroup="WKND Sites Project - Content"
sling:resourceSuperType="core/wcm/components/image/v2/image"/>
上述XML文件提供了组件的定义,包括标题、描述和组。 此 sling:resourceSuperType
指向 core/wcm/components/image/v2/image
,也就是 核心图像组件.
内部 byline
文件夹,添加文件 byline.html
,负责组件的HTML演示。 将文件命名为与文件夹相同的名称很重要,因为它会成为Sling用于呈现此资源类型的默认脚本。
将以下代码添加到 byline.html
.
<!--/* byline.html */-->
<div data-sly-use.placeholderTemplate="core/wcm/components/commons/v1/templates.html">
</div>
<sly data-sly-call="${placeholderTemplate.placeholder @ isEmpty=true}"></sly>
此 byline.html
是 稍后重新访问,在创建Sling模型后。 HTL文件的当前状态允许组件在拖放到页面上时以空状态显示在AEM Sites的页面编辑器中。
接下来,使用下列字段为署名组件定义一个对话框:
内部 byline
文件夹,创建一个名为的文件夹 _cq_dialog
.
内部 byline/_cq_dialog
,添加名为的文件 .content.xml
. 这是对话框的XML定义。 添加以下XML:
<?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="Byline"
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}false">
<items jcr:primaryType="nt:unstructured">
<asset
jcr:primaryType="nt:unstructured"
sling:hideResource="{Boolean}false"/>
<metadata
jcr:primaryType="nt:unstructured"
sling:hideResource="{Boolean}true"/>
<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">
<name
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
emptyText="Enter the contributor's name to display."
fieldDescription="The contributor's name to display."
fieldLabel="Name"
name="./name"
required="{Boolean}true"/>
<occupations
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
fieldDescription="A list of the contributor's occupations."
fieldLabel="Occupations"
required="{Boolean}false">
<field
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
emptyText="Enter an occupation"
name="./occupations"/>
</occupations>
</items>
</column>
</items>
</columns>
</items>
</properties>
</items>
</tabs>
</items>
</content>
</jcr:root>
这些对话框节点定义使用 Sling资源合并器 控制从继承哪些对话框选项卡 sling:resourceSuperType
组件,在本例中 核心组件的图像组件.
遵循与创建对话框相同的方法,创建策略对话框(以前称为设计对话框)以隐藏从核心组件的图像组件继承的策略配置中不需要的字段。
内部 byline
文件夹,创建一个名为的文件夹 _cq_design_dialog
.
内部 byline/_cq_design_dialog
,创建一个名为的文件 .content.xml
. 使用以下内容更新文件:使用以下XML。 最简单的做法是打开 .content.xml
并将下面的XML复制/粘贴到其中。
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/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="Byline"
sling:resourceType="cq/gui/components/authoring/dialog">
<content
jcr:primaryType="nt:unstructured">
<items jcr:primaryType="nt:unstructured">
<tabs
jcr:primaryType="nt:unstructured">
<items jcr:primaryType="nt:unstructured">
<properties
jcr:primaryType="nt:unstructured">
<items jcr:primaryType="nt:unstructured">
<content
jcr:primaryType="nt:unstructured">
<items jcr:primaryType="nt:unstructured">
<decorative
jcr:primaryType="nt:unstructured"
sling:hideResource="{Boolean}true"/>
<altValueFromDAM
jcr:primaryType="nt:unstructured"
sling:hideResource="{Boolean}true"/>
<titleValueFromDAM
jcr:primaryType="nt:unstructured"
sling:hideResource="{Boolean}true"/>
<displayCaptionPopup
jcr:primaryType="nt:unstructured"
sling:hideResource="{Boolean}true"/>
<disableUuidTracking
jcr:primaryType="nt:unstructured"
sling:hideResource="{Boolean}true"/>
</items>
</content>
</items>
</properties>
<features
jcr:primaryType="nt:unstructured">
<items jcr:primaryType="nt:unstructured">
<content
jcr:primaryType="nt:unstructured">
<items jcr:primaryType="nt:unstructured">
<accordion
jcr:primaryType="nt:unstructured">
<items jcr:primaryType="nt:unstructured">
<orientation
jcr:primaryType="nt:unstructured"
sling:hideResource="{Boolean}true"/>
<crop
jcr:primaryType="nt:unstructured"
sling:hideResource="{Boolean}true"/>
</items>
</accordion>
</items>
</content>
</items>
</features>
</items>
</tabs>
</items>
</content>
</jcr:root>
前一项的基础 “策略”对话框 XML是从 核心组件图像组件.
类似于对话框配置中的内容, Sling资源合并器 用于隐藏从继承而来的不相关字段 sling:resourceSuperType
,如包含的节点定义所示 sling:hideResource="{Boolean}true"
属性。
同步中的更改 ui.apps
使用IDE或使用Maven技能。
为了简化操作并集中精力开发AEM组件,让我们将署名组件在其当前状态下添加到文章页面以验证 cq:Component
节点定义正确。 此外,还要验证AEM是否可以识别新组件定义,并且组件的对话框是否可用于创作。
首先,将拍摄的头像示例上传到AEM Assets,以用于填充Byline组件中的图像。
导航到AEM Assets中的“洛杉矶滑板场”文件夹: http://localhost:4502/assets.html/content/dam/wknd/en/magazine/la-skateparks.
上传头像 stacey-roswells.jpg 到文件夹。
接下来,将署名组件添加到AEM中的页面。 因为署名组件已添加到 WKND站点项目 — 内容 组件组,通过 ui.apps/src/main/content/jcr_root/apps/wknd/components/byline/.content.xml
定义,它自动可供任何 容器 其 策略 允许 WKND站点项目 — 内容 组件组。 因此,它可以在文章页面的布局容器中使用。
导航至LA Skatepark文章,网址为: http://localhost:4502/editor.html/content/wknd/us/en/magazine/guide-la-skateparks.html
从左侧边栏中,拖放 署名组件 结束日期 bottom 已打开文章页面的布局容器的。
确保左侧边栏处于打开状态和可见,以及已选中资产查找器**。
选择 署名组件占位符,然后显示操作栏并点按 扳手 图标以打开对话框。
打开对话框并且第一个选项卡(资产)处于活动状态时,打开左侧边栏,然后从资产查找器中将图像拖入图像拖放区域。 搜索“stacey”以查找WKND ui.content包中提供的Stacey Roswells生物图片。
添加图像后,单击 属性 选项卡,输入 名称 和 职业.
输入职位时,请输入 按字母顺序反转 从而验证了Sling模型中实现的业务逻辑的字母化。
点按 完成 按钮以保存更改。
AEM作者通过对话框配置和创作组件。 此时,在开发署名组件时,将包含用于收集数据的对话框,但尚未添加呈现创作内容的逻辑。 因此,只显示占位符。
保存对话框后,导航到 CRXDE Lite 和查看组件内容如何存储在AEM页面下的byline组件内容节点上。
在“洛杉矶滑板场”页面下找到“署名”组件内容节点,即 /content/wknd/us/en/magazine/guide-la-skateparks/jcr:content/root/container/container/byline
.
请注意属性名称 name
, occupations
、和 fileReference
存储在 署名节点.
此外,请注意以下事项 sling:resourceType
节点的ID设置为 wknd/components/content/byline
这是将此内容节点绑定到Byline组件实施的原因。
接下来,让我们创建一个Sling模型以用作数据模型并存储Byline组件的业务逻辑。
Sling模型是注释驱动的Java™ POJO(纯旧Java™对象),有助于将数据从JCR映射到Java™变量,并在AEM上下文中开发时提供效率。
Byline Sling模型依赖于AEM提供的多个Java™ API。 这些API通过以下方式提供: dependencies
列于 core
模块的POM文件。 本教程中使用的项目是针对AEMas a Cloud Service构建的。 但它是独一无二的,因为它向后兼容AEM 6.5/6.4。因此,其中同时包含了Cloud Service和AEM 6.x的依赖项。
打开 pom.xml
下的文件 <src>/aem-guides-wknd/core/pom.xml
.
查找的依赖关系 aem-sdk-api
- 仅AEMas a Cloud Service
<dependency>
<groupId>com.adobe.aem</groupId>
<artifactId>aem-sdk-api</artifactId>
</dependency>
此 aem-sdk-api 包含AEM公开的所有公共Java™ API。 此 aem-sdk-api
在构建此项目时,默认使用。 版本在项目的根目录中的父Reactor pom中维护 aem-guides-wknd/pom.xml
.
查找的依赖项 uber-jar
- 仅限AEM 6.5/6.4
...
<dependency>
<groupId>com.adobe.aem</groupId>
<artifactId>uber-jar</artifactId>
<classifier>apis</classifier>
</dependency>
...
此 uber-jar
仅在 classic
调用配置文件,即 mvn clean install -PautoInstallSinglePackage -Pclassic
. 同样,这是此项目所特有的。 在从AEM项目原型生成的真实项目中, uber-jar
如果指定的AEM版本为6.5或6.4,则为默认设置。
此 uber-jar 包含AEM 6.x公开的所有公共Java™ API。版本在项目的根目录中的父反应器pom中维护 aem-guides-wknd/pom.xml
.
查找的依赖关系 core.wcm.components.core
:
<!-- Core Component Dependency -->
<dependency>
<groupId>com.adobe.cq</groupId>
<artifactId>core.wcm.components.core</artifactId>
</dependency>
这是由AEM核心组件公开的完整公共Java™ API。 AEM核心组件是在AEM之外维护的项目,因此具有单独的发布周期。 因此,需要单独包含依赖关系,并且 非 包含在 uber-jar
或 aem-sdk-api
.
与uber-jar一样,此依赖项的版本在来自的父reactor pom文件中维护 aem-guides-wknd/pom.xml
.
在本教程的后面部分,核心组件图像类用于显示署名组件中的图像。 为了构建和编译Sling模型,需要具有核心组件依赖关系。
为署名创建公共Java™接口。 此 Byline.java
定义驱动 byline.html
HTL脚本。
内部, core
中的模块 core/src/main/java/com/adobe/aem/guides/wknd/core/models
文件夹创建一个名为的文件 Byline.java
更新 Byline.java
方法:
package com.adobe.aem.guides.wknd.core.models;
import java.util.List;
/**
* Represents the Byline AEM Component for the WKND Site project.
**/
public interface Byline {
/***
* @return a string to display as the name.
*/
String getName();
/***
* Occupations are to be sorted alphabetically in a descending order.
*
* @return a list of occupations.
*/
List<String> getOccupations();
/***
* @return a boolean if the component has enough content to display.
*/
boolean isEmpty();
}
前两种方法会公开 name 和 职业 (对于署名组件)。
此 isEmpty()
方法用于确定组件是否具有任何要渲染的内容,或者组件是否等待配置。
请注意,图像没有方法; 稍后将对此进行审核.
包含公共Java™类的Java™包(在本例中为Sling模型)必须使用包的 package-info.java
文件。
由于WKND源的Java™包 com.adobe.aem.guides.wknd.core.models
声明的版本 1.0.0
,并且正在添加不间断的公共接口和方法,必须将版本提高为 1.1.0
. 打开文件,位于 core/src/main/java/com/adobe/aem/guides/wknd/core/models/package-info.java
和更新 @Version("1.0.0")
到 @Version("2.1.0")
.
@Version("2.1.0")
package com.adobe.aem.guides.wknd.core.models;
import org.osgi.annotation.versioning.Version;
每当对此包中的文件做出更改时, 必须在语义上调整包版本. 如果不能,Maven项目的 bnd-baseline-maven-plugin 检测无效的包版本,并中断内置。 幸运的是,如果失败,Maven插件将报告无效的Java™包版本及其应有的版本。 更新 @Version("...")
违反的Java™包中的声明 package-info.java
到插件建议修复的版本。
此 BylineImpl.java
是实施的Sling模型的实施 Byline.java
之前定义的接口。 的完整代码 BylineImpl.java
可在本节底部找到。
创建名为的文件夹 impl
下 core/src/main/java/com/adobe/aem/guides/core/models
.
在 impl
文件夹,创建文件 BylineImpl.java
.
打开 BylineImpl.java
. 指定它实施 Byline
界面。 使用IDE的自动完成功能或手动更新文件,以包含实施 Byline
界面:
package com.adobe.aem.guides.wknd.core.models.impl;
import java.util.List;
import com.adobe.aem.guides.wknd.core.models.Byline;
public class BylineImpl implements Byline {
@Override
public String getName() {
// TODO Auto-generated method stub
return null;
}
@Override
public List<String> getOccupations() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isEmpty() {
// TODO Auto-generated method stub
return false;
}
}
通过更新添加Sling模型注释 BylineImpl.java
包含以下类级注释。 此 @Model(..)
注释可将类转换为Sling模型。
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
...
@Model(
adaptables = {SlingHttpServletRequest.class},
adapters = {Byline.class},
resourceType = {BylineImpl.RESOURCE_TYPE},
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class BylineImpl implements Byline {
protected static final String RESOURCE_TYPE = "wknd/components/byline";
...
}
让我们查看此注释及其参数:
@Model
注释在部署到AEM时将BylineImpl注册为Sling模型。adaptables
参数指定此模型可以根据请求进行调整。adapters
参数允许在Byline接口下注册实现类。 这允许HTL脚本通过接口(而不是直接实施)调用Sling模型。 有关适配器的更多详细信息见此处.resourceType
指向署名组件资源类型(之前创建),如果存在多个实施,可帮助解析正确的模型。 有关将模型类与资源类型关联的更多详细信息见此处.实现的第一个方法是 getName()
,则它只会返回存储在属性下的署名JCR内容节点中的值 name
.
为此, @ValueMapValue
Sling模型注释用于使用请求的资源的ValueMap将该值注入Java™字段。
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
public class BylineImpl implements Byline {
...
@ValueMapValue
private String name;
...
@Override
public String getName() {
return name;
}
...
}
由于JCR属性将名称与Java™字段共享(两者均为“name”), @ValueMapValue
自动解析此关联并将属性的值注入Java™字段。
下一个实施方法是 getOccupations()
. 此方法会加载存储在JCR属性中的职业 occupations
并返回它们的已排序(按字母顺序)集合。
使用在中探索的相同技术 getName()
属性值可以插入Sling模型的字段中。
一旦JCR属性值通过插入的Java™字段在Sling模型中可用 occupations
,排序业务逻辑可以应用于 getOccupations()
方法。
import java.util.ArrayList;
import java.util.Collections;
...
public class BylineImpl implements Byline {
...
@ValueMapValue
private List<String> occupations;
...
@Override
public List<String> getOccupations() {
if (occupations != null) {
Collections.sort(occupations);
return new ArrayList<String>(occupations);
} else {
return Collections.emptyList();
}
}
...
}
...
最后一个公共方法是 isEmpty()
它确定组件何时应认为自身“创作得充分”才能呈现。
对于此组件,业务需求是所有三个字段, name, image and occupations
必须填写 早于 可以渲染组件。
import org.apache.commons.lang3.StringUtils;
...
public class BylineImpl implements Byline {
...
@Override
public boolean isEmpty() {
if (StringUtils.isBlank(name)) {
// Name is missing, but required
return true;
} else if (occupations == null || occupations.isEmpty()) {
// At least one occupation is required
return true;
} else if (/* image is not null, logic to be determined */) {
// A valid image is required
return true;
} else {
// Everything is populated, so this component is not considered empty
return false;
}
}
...
}
检查名称和占用条件并不重要,Apache Commons Lang3提供了方便的方法 StringUtils 类。 然而,目前还不清楚 图像存在 可以验证,因为核心组件图像组件用于呈现图像。
有两种方法可以解决这个问题:
检查 fileReference
JCR属性解析为资产。 或 将此资源转换为核心组件图像Sling模型并确保 getSrc()
方法不为空。
让我们使用 秒 方针。 第一种方法可能已足够,但在本教程中,将使用后者来允许我们探索Sling模型的其他功能。
创建一个获取图像的专用方法。 此方法保留为私有,因为不需要在HTL本身中公开Image对象,并且它仅用于驱动 isEmpty().
为添加以下私有方法 getImage()
:
import com.adobe.cq.wcm.core.components.models.Image;
...
private Image getImage() {
Image image = null;
// Figure out how to populate the image variable!
return image;
}
如上所述,还有两种方法可获取 图像Sling模型:
第一个使用 @Self
注释,自动使当前请求适应核心组件的 Image.class
第二个使用 Apache Sling ModelFactory OSGi服务,它是一项方便的服务,可帮助我们在Java™代码中创建其他类型的Sling模型。
让我们使用第二种方法。
在实际的实施中,采用“One”方法,使用 @Self
是首选,因为它是更简单、更优雅的解决方案。 在本教程中,将使用第二个方法,因为它需要探索更多适用于更复杂组件的Sling模型方面!
由于Sling模型是Java™ POJO的,而不是OSGi服务,因此通常的OSGi注入注释 @Reference
无法 ,而Sling模型会提供一个特殊的 @OSGiService 提供类似功能的注释。
更新 BylineImpl.java
以包含 OSGiService
注解以插入 ModelFactory
:
import org.apache.sling.models.factory.ModelFactory;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
...
public class BylineImpl implements Byline {
...
@OSGiService
private ModelFactory modelFactory;
}
使用 ModelFactory
在可用时,可以使用以下方式创建核心组件图像Sling模型:
modelFactory.getModelFromWrappedRequest(SlingHttpServletRequest request, Resource resource, java.lang.Class<T> targetClass)
但是,此方法同时需要请求和资源,而Sling模型中尚未提供这两个资源。 要获取这些注释,请使用更多Sling模型注释!
要获取当前请求,请 @Self 注释可用于插入 adaptable
(定义于 @Model(..)
作为 SlingHttpServletRequest.class
,放入一个Java™类字段中。
添加 @Self 注释以获取 SlingHttpServletRequest请求:
import org.apache.sling.models.annotations.injectorspecific.Self;
...
@Self
private SlingHttpServletRequest request;
记住,使用 @Self Image image
插入核心组件图像Sling模型是上方的选项 — @Self
注释尝试插入自适应对象(在本例中为SlingHttpServletRequest),并适应注释字段类型。 由于核心组件图像Sling模型可从SlingHttpServletRequest对象中进行改写,因此这本应有效,并且代码比探索性更少 modelFactory
方针。
现在,将插入通过ModelFactory API实例化图像模型所需的变量。 让我们使用Sling模型的 @PostConstruct 注释,以在Sling模型实例化后获取此对象。
@PostConstruct
非常有用,并且充当构造函数具有类似的作用,但是,它在实例化类并注入所有带注释的Java™字段后调用。 与其他Sling模型注释注释Java™类字段(变量)不同, @PostConstruct
注释void,zero parameter方法,通常名为 init()
(但任何名称都可以)。
添加 @PostConstruct 方法:
import javax.annotation.PostConstruct;
...
public class BylineImpl implements Byline {
...
private Image image;
@PostConstruct
private void init() {
image = modelFactory.getModelFromWrappedRequest(request,
request.getResource(),
Image.class);
}
...
}
请记住,Sling模型是 NOT OSGi服务,因此维护类状态是安全的。 经常 @PostConstruct
派生和设置Sling模型类状态以供以后使用,类似于普通构造函数。
如果 @PostConstruct
方法引发异常,Sling模型未实例化并且为空。
getImage() 现在可以更新为仅返回图像对象。
/**
* @return the Image Sling Model of this resource, or null if the resource cannot create a valid Image Sling Model.
*/
private Image getImage() {
return image;
}
让我们回到 isEmpty()
并完成实施:
@Override
public boolean isEmpty() {
final Image componentImage = getImage();
if (StringUtils.isBlank(name)) {
// Name is missing, but required
return true;
} else if (occupations == null || occupations.isEmpty()) {
// At least one occupation is required
return true;
} else if (componentImage == null || StringUtils.isBlank(componentImage.getSrc())) {
// A valid image is required
return true;
} else {
// Everything is populated, so this component is not considered empty
return false;
}
}
记下对的多个调用 getImage()
没有问题,因为返回已初始化的 image
类变量且不会调用 modelFactory.getModelFromWrappedRequest(...)
这并非成本过高,但值得避免不必要的呼叫。
决赛 BylineImpl.java
应如下所示:
package com.adobe.aem.guides.wknd.core.models.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.PostConstruct;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.Self;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
import org.apache.sling.models.factory.ModelFactory;
import com.adobe.aem.guides.wknd.core.models.Byline;
import com.adobe.cq.wcm.core.components.models.Image;
@Model(
adaptables = {SlingHttpServletRequest.class},
adapters = {Byline.class},
resourceType = {BylineImpl.RESOURCE_TYPE},
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class BylineImpl implements Byline {
protected static final String RESOURCE_TYPE = "wknd/components/byline";
@Self
private SlingHttpServletRequest request;
@OSGiService
private ModelFactory modelFactory;
@ValueMapValue
private String name;
@ValueMapValue
private List<String> occupations;
private Image image;
/**
* @PostConstruct is immediately called after the class has been initialized
* but BEFORE any of the other public methods.
* It is a good method to initialize variables that is used by methods in the rest of the model
*
*/
@PostConstruct
private void init() {
// set the image object
image = modelFactory.getModelFromWrappedRequest(request, request.getResource(), Image.class);
}
@Override
public String getName() {
return name;
}
@Override
public List<String> getOccupations() {
if (occupations != null) {
Collections.sort(occupations);
return new ArrayList<String>(occupations);
} else {
return Collections.emptyList();
}
}
@Override
public boolean isEmpty() {
final Image componentImage = getImage();
if (StringUtils.isBlank(name)) {
// Name is missing, but required
return true;
} else if (occupations == null || occupations.isEmpty()) {
// At least one occupation is required
return true;
} else if (componentImage == null || StringUtils.isBlank(componentImage.getSrc())) {
// A valid image is required
return true;
} else {
// Everything is populated, so this component is not considered empty
return false;
}
}
/**
* @return the Image Sling Model of this resource, or null if the resource cannot create a valid Image Sling Model.
*/
private Image getImage() {
return image;
}
}
在 ui.apps
模块,打开 /apps/wknd/components/byline/byline.html
之前的AEM组件设置中创建的属性。
<div data-sly-use.placeholderTemplate="core/wcm/components/commons/v1/templates.html">
</div>
<sly data-sly-call="${placeholderTemplate.placeholder @ isEmpty=false}"></sly>
让我们回顾一下此HTL脚本到目前为止的作用:
此 placeholderTemplate
指向核心组件的占位符,该占位符在组件未完全配置时显示。 这在AEM Sites页面编辑器中呈现为一个带有组件标题的框,如上面的中定义 cq:Component
的 jcr:title
属性。
此 data-sly-call="${placeholderTemplate.placeholder @ isEmpty=false}
加载 placeholderTemplate
以上定义的并传递一个布尔值(当前硬编码为 false
)填充到占位符模板中。 时间 isEmpty
为true,占位符模板将渲染灰色框,否则不会渲染任何内容。
更新 byline.html 骨架HTML结构:
<div data-sly-use.placeholderTemplate="core/wcm/components/commons/v1/templates.html"
class="cmp-byline">
<div class="cmp-byline__image">
<!--/* Include the Core Components Image Component */-->
</div>
<h2 class="cmp-byline__name"><!--/* Include the name */--></h2>
<p class="cmp-byline__occupations"><!--/* Include the occupations */--></p>
</div>
<sly data-sly-call="${placeholderTemplate.placeholder @ isEmpty=true}"></sly>
请注意,CSS类遵循 BEM命名约定. 虽然不强制使用BEM约定,但建议使用BEM,因为它用在核心组件CSS类中,并且通常会产生干净的可读CSS规则。
此 Use block语句 用于在HTL脚本中实例化Sling模型对象并将其分配给HTL变量。
此 data-sly-use.byline="com.adobe.aem.guides.wknd.models.Byline"
使用由BylineImpl实现的Byline接口(com.adobe.aem.guides.wknd.models.Byline),并将当前SlingHttpServletRequest调整到该接口,并将结果存储在HTL变量名称byline ( data-sly-use.<variable-name>
)。
更新外部 div
以引用 署名 Sling模型及其公共接口:
<div data-sly-use.byline="com.adobe.aem.guides.wknd.core.models.Byline"
data-sly-use.placeholderTemplate="core/wcm/components/commons/v1/templates.html"
class="cmp-byline">
...
</div>
HTL从JSTL借入,并使用与Java™ getter方法名称相同的缩短。
例如,调用署名Sling模型的 getName()
方法可缩短为 byline.name
,类似于,而不是 byline.isEmpty
,这可以简写为 byline.empty
. 使用完整方法名, byline.getName
或 byline.isEmpty
也适用。 请注意 ()
绝不会用于在HTL中调用方法(与JSTL类似)。
需要参数的Java™方法 无法 在HTL中使用。 这是通过设计来保持HTL中的逻辑简单。
可以通过调用 getName()
方法(在Byline Sling模型或HTL中): ${byline.name}
.
更新 h2
标记:
<h2 class="cmp-byline__name">${byline.name}</h2>
HTL表达式选项 在HTL中充当内容的修饰符,范围从日期格式到i18n翻译。 表达式也可用于连接值列表或数组,以逗号分隔格式显示占用位置时需要使用这些值列表。
表达式是通过 @
运算符。
要通过“, ”加入职业列表,请使用以下代码:
<p class="cmp-byline__occupations">${byline.occupations @ join=', '}</p>
AEM组件的大多数HTL脚本使用 占位符范式 为作者提供视觉提示 指示组件的创作不正确,并且未显示在AEM发布上. 推动此决策的惯例是在组件的支持Sling模型上实施一种方法,在本例中为: Byline.isEmpty()
.
此 isEmpty()
方法在Byline Sling模型和结果(或者说是负值)上调用,方法是通过 !
运算符)保存到HTL变量 hasContent
:
更新外部 div
保存名为的HTL变量 hasContent
:
<div data-sly-use.byline="com.adobe.aem.guides.wknd.core.models.Byline"
data-sly-use.placeholderTemplate="core/wcm/components/commons/v1/templates.html"
data-sly-test.hasContent="${!byline.empty}"
class="cmp-byline">
...
</div>
请注意的使用 data-sly-test
,HTL test
块是关键,它设置HTL变量并渲染/不渲染它所在的HTML元素。 它基于HTL表达式评估的结果。 如果为“true”,则HTML元素渲染,否则不会渲染。
此HTL变量 hasContent
现在可重用来有条件地显示/隐藏占位符。
将条件调用更新为 placeholderTemplate
文件底部包含以下内容:
<sly data-sly-call="${placeholderTemplate.placeholder @ isEmpty=!hasContent}"></sly>
的HTL脚本 byline.html
现已接近完成,只是缺少了图像。
作为 sling:resourceSuperType
指向核心组件的图像组件以创作图像,可使用核心组件的图像组件渲染图像。
为此,让我们包含当前署名资源,但强制使用资源类型使用核心组件的图像组件的资源类型 core/wcm/components/image/v2/image
. 这是一个用于组件重用的强大模式。 为此,HTL的 data-sly-resource
块已使用。
更换 div
具有类 cmp-byline__image
,如下所示:
<div class="cmp-byline__image"
data-sly-resource="${ '.' @ resourceType = 'core/wcm/components/image/v2/image' }"></div>
此 data-sly-resource
,通过相对路径包含当前资源 '.'
,和强制包含当前资源(或署名内容资源),资源类型为 core/wcm/components/image/v2/image
.
核心组件资源类型是直接使用的,而不是通过代理使用的,因为这是一种脚本内使用,永远不会保留到内容中。
已完成 byline.html
下:
<!--/* byline.html */-->
<div data-sly-use.byline="com.adobe.aem.guides.wknd.core.models.Byline"
data-sly-use.placeholderTemplate="core/wcm/components/commons/v1/templates.html"
data-sly-test.hasContent="${!byline.empty}"
class="cmp-byline">
<div class="cmp-byline__image"
data-sly-resource="${ '.' @ resourceType = 'core/wcm/components/image/v2/image' }">
</div>
<h2 class="cmp-byline__name">${byline.name}</h2>
<p class="cmp-byline__occupations">${byline.occupations @ join=', '}</p>
</div>
<sly data-sly-call="${placeholderTemplate.placeholder @ isEmpty=!hasContent}"></sly>
将代码库部署到本地AEM实例。 由于更改了 core
和 ui.apps
这两个模块都需要部署。
$ cd aem-guides-wknd/ui.apps
$ mvn clean install -PautoInstallPackage
$ cd ../core
$ mvn clean install -PautoInstallBundle
要部署到AEM 6.5/6.4,请调用 classic
个人资料:
$ cd ../core
$ mvn clean install -PautoInstallBundle -Pclassic
您还可以使用Maven配置文件从根构建整个项目 autoInstallSinglePackage
但这可能会覆盖页面上的内容更改。 这是因为 ui.content/src/main/content/META-INF/vault/filter.xml
已针对教程入门代码进行了修改,以彻底覆盖现有的AEM内容。 在现实世界中,这并不是问题。
部署更新后,导航到 洛杉矶滑板场终极指南 页面中,或者在章节前面添加署名组件的位置。
此 图像, name、和 职业 此时会出现,并且会显示一个未设置样式但正常工作的署名组件。
此 AEM Web控制台的Sling模型状态视图 显示AEM中所有已注册的Sling模型。 通过查看此列表,可以验证署名Sling模型是否安装并可进行识别。
如果 署名实施 不会显示在此列表中,可能是由于Sling模型的注释有问题,或者模型未添加到正确的包中(com.adobe.aem.guides.wknd.core.models
)。
http://localhost:4502/system/console/status-slingmodels
要将“署名”组件与提供的创意设计对齐,让我们设置其样式。 这是通过使用SCSS文件并更新 ui.frontend 模块。
为Byline组件添加默认样式。
返回到IDE和 ui.frontend 下的项目 /src/main/webpack/components
:
创建名为的文件 _byline.scss
.
将Byline实施CSS(作为SCSS写入)添加到 _byline.scss
:
.cmp-byline {
$imageSize: 60px;
.cmp-byline__image {
float: left;
/* This class targets a Core Component Image CSS class */
.cmp-image__image {
width: $imageSize;
height: $imageSize;
border-radius: $imageSize / 2;
object-fit: cover;
}
}
.cmp-byline__name {
font-size: $font-size-medium;
font-family: $font-family-serif;
padding-top: 0.5rem;
margin-left: $imageSize + 25px;
margin-bottom: .25rem;
margin-top:0rem;
}
.cmp-byline__occupations {
margin-left: $imageSize + 25px;
color: $gray;
font-size: $font-size-xsmall;
text-transform: uppercase;
}
}
打开终端并导航到 ui.frontend
模块。
启动 watch
使用以下npm命令处理:
$ cd ui.frontend/
$ npm run watch
返回到浏览器并导航到 LA SkateParks文章. 您应该会看到组件中更新的样式。
您可能需要清除浏览器缓存以确保未提供过时的CSS,然后使用署名组件刷新页面以获得完整样式。
恭喜,您已使用Adobe Experience Manager从头开始创建自定义组件!
通过探索如何为Byline Java™代码编写JUnit测试,继续了解AEM组件开发,以确保正确开发所有内容,并实现正确完整的业务逻辑。
查看完成的代码 GitHub 或在Git分支上查看并本地部署代码 tutorial/custom-component-solution
.
tutorial/custom-component-solution
分支