创建自定义天气组件 custom-component

了解如何创建要与AEM SPA编辑器一起使用的自定义天气组件。 了解如何开发创作对话框和Sling模型,以扩展JSON模型来填充自定义组件。 使用了Open Weather APIReact Open Weather组件

目标

  1. 了解Sling模型在操作AEM提供的JSON模型API方面的作用。
  2. 了解如何创建新的AEM组件对话框。
  3. 了解如何创建与SPA编辑器框架兼容的​ 自定义 AEM组件。

您将构建的内容

构建简单的天气组件。 此组件可以由内容作者添加到SPA。 利用AEM对话框,作者可以设置天气的显示位置。 此组件的实施说明了创建与AEM SPA Editor Framework兼容的全新AEM组件所需的步骤。

配置开放天气组件

先决条件

查看设置本地开发环境所需的工具和说明。 本章是导航和路由章节的延续,但您只需将启用了SPA的AEM项目部署到本地AEM实例即可。

打开天气API密钥

需要来自Open Weather的API密钥以及教程。 免费注册有限数量的API调用。

定义AEM组件

AEM组件被定义为节点和属性。 在项目中,这些节点和属性在ui.apps模块中表示为XML文件。 接下来,在ui.apps模块中创建AEM组件。

  1. 在您选择的IDE中,打开ui.apps文件夹。

  2. 导航到ui.apps/src/main/content/jcr_root/apps/wknd-spa-react/components并创建一个名为open-weather的新文件夹。

  3. open-weather文件夹下创建名为.content.xml的新文件。 使用以下内容填充open-weather/.content.xml

    code language-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="Open Weather"
        componentGroup="WKND SPA React - Content"/>
    

    创建自定义组件定义

    jcr:primaryType="cq:Component" — 标识此节点是AEM组件。

    jcr:title是向内容作者显示的值,componentGroup确定创作UI中的组件分组。

  4. custom-component文件夹下,创建另一个名为_cq_dialog的文件夹。

  5. _cq_dialog文件夹下创建名为.content.xml的新文件并使用以下内容填充该文件:

    code language-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="Open Weather"
        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">
                                                <label
                                                    jcr:primaryType="nt:unstructured"
                                                    sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                                                    fieldDescription="The label to display for the component"
                                                    fieldLabel="Label"
                                                    name="./label"/>
                                                <lat
                                                    jcr:primaryType="nt:unstructured"
                                                    sling:resourceType="granite/ui/components/coral/foundation/form/numberfield"
                                                    fieldDescription="The latitude of the location."
                                                    fieldLabel="Latitude"
                                                    step="any"
                                                    name="./lat" />
                                                <lon
                                                    jcr:primaryType="nt:unstructured"
                                                    sling:resourceType="granite/ui/components/coral/foundation/form/numberfield"
                                                    fieldDescription="The longitude of the location."
                                                    fieldLabel="Longitude"
                                                    step="any"
                                                    name="./lon"/>
                                            </items>
                                        </column>
                                    </items>
                                </columns>
                            </items>
                        </properties>
                    </items>
                </tabs>
            </items>
        </content>
    </jcr:root>
    

    自定义组件定义

    上述XML文件为Weather Component生成了一个非常简单的对话框。 文件的关键部分是内部<label><lat><lon>节点。 此对话框包含两个numberfield和一个textfield,允许用户配置要显示的天气。

    随后创建了一个Sling模型以通过JSON模型公开labellatlong属性的值。

    note note
    NOTE
    通过查看核心组件定义🔗,您可以查看更多对话框示例。 您还可以在CRXDE-Lite/libs/granite/ui/components/coral/foundation/form下查看其他表单字段,如selecttextareapathfield

    对于传统AEM组件,通常需要HTL脚本。 由于SPA将渲染组件,因此不需要HTL脚本。

创建Sling模型

Sling模型是注释驱动的Java“POJO”(纯旧的Java对象),有助于将数据从JCR映射到Java变量。 Sling模型通常用于为AEM组件封装复杂的服务器端业务逻辑。

在SPA编辑器的上下文中,Sling模型使用Sling模型导出器通过功能通过JSON模型公开组件的内容。

  1. 在您选择的IDE中,打开aem-guides-wknd-spa.react/core上的core模块。

  2. core/src/main/java/com/adobe/aem/guides/wkndspa/react/core/modelsOpenWeatherModel.java处创建名为的文件。

  3. 使用以下内容填充OpenWeatherModel.java

    code language-java
    package com.adobe.aem.guides.wkndspa.react.core.models;
    
    import com.adobe.cq.export.json.ComponentExporter;
    
    // Sling Models intended to be used with SPA Editor must extend ComponentExporter interface
    public interface OpenWeatherModel extends ComponentExporter {
        public String getLabel();
        public double getLat();
        public double getLon();
    }
    

    这是组件的Java接口。 为了使我们的Sling模型与SPA Editor框架兼容,它必须扩展ComponentExporter类。

  4. core/src/main/java/com/adobe/aem/guides/wkndspa/react/core/models下创建名为impl的文件夹。

  5. impl下创建名为OpenWeatherModelImpl.java的文件,并填充以下内容:

    code language-java
    package com.adobe.aem.guides.wkndspa.react.core.models.impl;
    
    import org.apache.sling.models.annotations.*;
    import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
    import com.adobe.cq.export.json.ComponentExporter;
    import com.adobe.cq.export.json.ExporterConstants;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.sling.api.SlingHttpServletRequest;
    import com.adobe.aem.guides.wkndspa.react.core.models.OpenWeatherModel;
    
    // Sling Model annotation
    @Model(
        adaptables = SlingHttpServletRequest.class,
        adapters = { OpenWeatherModel.class, ComponentExporter.class },
        resourceType = OpenWeatherModelImpl.RESOURCE_TYPE,
        defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
    )
    @Exporter( //Exporter annotation that serializes the modoel as JSON
        name = ExporterConstants.SLING_MODEL_EXPORTER_NAME,
        extensions = ExporterConstants.SLING_MODEL_EXTENSION
    )
    public class OpenWeatherModelImpl implements OpenWeatherModel {
    
        @ValueMapValue
        private String label; //maps variable to jcr property named "label" persisted by Dialog
    
        @ValueMapValue
        private double lat; //maps variable to jcr property named "lat"
    
        @ValueMapValue
        private double lon; //maps variable to jcr property named "lon"
    
        // points to AEM component definition in ui.apps
        static final String RESOURCE_TYPE = "wknd-spa-react/components/open-weather";
    
        // public getter method to expose value of private variable `label`
        // adds additional logic to default the label to "(Default)" if not set.
        @Override
        public String getLabel() {
            return StringUtils.isNotBlank(label) ? label : "(Default)";
        }
    
        // public getter method to expose value of private variable `lat`
        @Override
        public double getLat() {
            return lat;
        }
    
        // public getter method to expose value of private variable `lon`
        @Override
        public double getLon() {
            return lon;
        }
    
        // method required by `ComponentExporter` interface
        // exposes a JSON property named `:type` with a value of `wknd-spa-react/components/open-weather`
        // required to map the JSON export to the SPA component props via the `MapTo`
        @Override
        public String getExportedType() {
            return OpenWeatherModelImpl.RESOURCE_TYPE;
        }
    }
    

    静态变量RESOURCE_TYPE必须指向组件ui.apps中的路径。 getExportedType()用于通过MapTo将JSON属性映射到SPA组件。 @ValueMapValue是一个注释,读取对话框保存的jcr属性。

更新SPA

接下来,更新React代码以包含React Open Weather组件,并使其映射到在之前步骤中创建的AEM组件。

  1. 将React Open Weather组件安装为​ npm ​依赖项:

    code language-shell
    $ cd aem-guides-wknd-spa.react/ui.frontend
    $ npm i react-open-weather
    
  2. ui.frontend/src/components/OpenWeather处创建一个名为OpenWeather的新文件夹。

  3. 添加名为OpenWeather.js的文件并使用以下内容填充该文件:

    code language-js
    import React from 'react';
    import {MapTo} from '@adobe/aem-react-editable-components';
    import ReactWeather, { useOpenWeather } from 'react-open-weather';
    
    // Open weather API Key
    // For simplicity it is hard coded in the file, ideally this is extracted in to an environment variable
    const API_KEY = 'YOUR_API_KEY';
    
    // Logic to render placeholder or component
    const OpenWeatherEditConfig = {
    
        emptyLabel: 'Weather',
        isEmpty: function(props) {
            return !props || !props.lat || !props.lon || !props.label;
        }
    };
    
    // Wrapper function that includes react-open-weather component
    function ReactWeatherWrapper(props) {
        const { data, isLoading, errorMessage } = useOpenWeather({
            key: API_KEY,
            lat: props.lat, // passed in from AEM JSON
            lon: props.lon, // passed in from AEM JSON
            lang: 'en',
            unit: 'imperial', // values are (metric, standard, imperial)
        });
    
        return (
            <div className="cmp-open-weather">
                <ReactWeather
                    isLoading={isLoading}
                    errorMessage={errorMessage}
                    data={data}
                    lang="en"
                    locationLabel={props.label} // passed in from AEM JSON
                    unitsLabels={{ temperature: 'F', windSpeed: 'mph' }}
                    showForecast={false}
                  />
            </div>
        );
    }
    
    export default function OpenWeather(props) {
    
            // render nothing if component not configured
            if (OpenWeatherEditConfig.isEmpty(props)) {
                return null;
            }
    
            // render ReactWeather component if component configured
            // pass props to ReactWeatherWrapper. These props include the mapped properties from AEM JSON
            return ReactWeatherWrapper(props);
    
    }
    
    // Map OpenWeather to AEM component
    MapTo('wknd-spa-react/components/open-weather')(OpenWeather, OpenWeatherEditConfig);
    
  4. ui.frontend/src/components/import-components.js更新import-components.js以包含OpenWeather组件:

    code language-diff
      // import-component.js
      import './Container/Container';
      import './ExperienceFragment/ExperienceFragment';
    + import './OpenWeather/OpenWeather';
    
  5. 使用您的Maven技能,从项目目录的根目录中将所有更新部署到本地AEM环境:

    code language-shell
    $ cd aem-guides-wknd-spa.react
    $ mvn clean install -PautoInstallSinglePackage
    

更新模板策略

接下来,导航到AEM以验证更新,并允许将OpenWeather组件添加到SPA。

  1. 通过导航到http://localhost:4502/system/console/status-slingmodels验证新Sling模型的注册。

    code language-plain
    com.adobe.aem.guides.wkndspa.react.core.models.impl.OpenWeatherModelImpl - wknd-spa-react/components/open-weather
    
    com.adobe.aem.guides.wkndspa.react.core.models.impl.OpenWeatherModelImpl exports 'wknd-spa-react/components/open-weather' with selector 'model' and extension '[Ljava.lang.String;@2fd80fc5' with exporter 'jackson'
    

    您应该看到以上两行,其中表示OpenWeatherModelImplwknd-spa-react/components/open-weather组件相关联,并已通过Sling模型导出程序注册。

  2. 导航到http://localhost:4502/editor.html/conf/wknd-spa-react/settings/wcm/templates/spa-page-template/structure.html处的SPA页面模板。

  3. 更新布局容器的策略以将新Open Weather添加为允许的组件:

    更新布局容器策略

    将更改保存到策略,并将Open Weather作为允许的组件观看:

    自定义组件作为允许的组件

创作开放天气组件

接下来,使用AEM SPA编辑器创作Open Weather组件。

  1. 导航到http://localhost:4502/editor.html/content/wknd-spa-react/us/en/home.html

  2. Edit模式下,将Open Weather添加到Layout Container

    插入新组件

  3. 打开组件的对话框,并输入​ 标签纬度 ​和​ 经度。 例如​ San Diego32.7157 ​和​ -117.1611。 西半球和南半球的数字在开放天气API中表示为负数

    配置开放天气组件

    这是本章前面基于XML文件创建的对话框。

  4. 保存更改。 请注意,此时将显示​ 圣地亚哥 ​的天气:

    天气组件已更新

  5. 导航到http://localhost:4502/content/wknd-spa-react/us/en.model.json以查看JSON模型。 搜索wknd-spa-react/components/open-weather

    code language-json
    "open_weather": {
        "label": "San Diego",
        "lat": 32.7157,
        "lon": -117.1611,
        ":type": "wknd-spa-react/components/open-weather"
    }
    

    JSON值由Sling模型输出。 这些JSON值将作为prop传递到React组件中。

恭喜! congratulations

恭喜,您已了解如何创建要与AEM编辑器一起使用的自定义SPA组件。 您还了解了对话框、JCR属性和Sling模型如何交互以输出JSON模型。

后续步骤 next-steps

扩展核心组件 — 了解如何扩展要与AEM SPA编辑器一起使用的现有AEM核心组件。 了解如何向现有组件添加属性和内容是扩展AEM SPA Editor实施功能的强大技术。

recommendation-more-help
e25b6834-e87f-4ff3-ba56-4cd16cdfdec4