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

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

目标

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

您将构建的内容

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

配置开放天气组件

前提条件

查看所需的工具和设置说明 本地开发环境. 本章是 导航和路由 但是,本章是部署到本地SPA实例中的启用AEM的AEM项目,您需要这样做。

打开天气API密钥

来自的API密钥 开放天气 ,以及教程。 免费注册 有限数量的API调用。

定义AEM组件

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

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

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

  3. 创建新文件,名为 .content.xmlopen-weather 文件夹。 填充 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> 节点。 此对话框包含两个 numberfields和a textfield 这允许用户配置要显示的天气。

    随后将创建一个Sling模型以公开 labellatlong 属性。

    note note
    NOTE
    您可以查看更多内容 通过查看核心组件定义显示的对话框示例. 您还可以查看其他表单字段,如 selecttextareapathfield,可在下方使用 /libs/granite/ui/components/coral/foundation/formCRXDE-Lite.

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

创建Sling模型

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

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

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

  2. 创建名为的文件 OpenWeatherModel.javacore/src/main/java/com/adobe/aem/guides/wkndspa/react/core/models.

  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编辑器框架兼容,它必须扩展 ComponentExporter 类。

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

  5. 创建名为的文件 OpenWeatherModelImpl.javaimpl 并使用以下内容填充:

    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() 用于通过将JSON属性映射到SPA组件 MapTo. @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. 创建新文件夹,名为 OpenWeatherui.frontend/src/components/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. 更新 import-components.jsui.frontend/src/components/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. 通过导航到“ ”,验证新Sling模型的注册 http://localhost:4502/system/console/status-slingmodels.

    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'
    

    您应该会看到上面两行表示 OpenWeatherModelImpl 已与 wknd-spa-react/components/open-weather 组件并通过Sling模型导出器注册。

  2. 导航至SPA页面模板,网址为 http://localhost:4502/editor.html/conf/wknd-spa-react/settings/wcm/templates/spa-page-template/structure.html.

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

    更新布局容器策略

    保存对策略所做的更改,并观察 Open Weather 作为允许的组件:

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

创作开放天气组件

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

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

  2. Edit 模式,添加 Open WeatherLayout Container

    插入新组件

  3. 打开组件的对话框并输入 标签纬度、和 经度. 例如 圣迭戈32.7157、和 -117.1611. 西半球和南半球的数字在开放天气API中表示为负数

    配置开放天气组件

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

  4. 保存更改。 观察天气 圣迭戈 将显示:

    天气组件已更新

  5. 导航到,查看JSON模型 http://localhost:4502/content/wknd-spa-react/us/en.model.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