Componente personalizado custom-component
Este tutorial aborda a criação de ponta a ponta de um componente Byline personalizado do AEM que exibe o conteúdo criado em uma caixa de diálogo e apresenta o desenvolvimento de um modelo do Sling para encapsular a lógica de negócios a fim de preencher o HTL do componente.
Pré-requisitos prerequisites
Consulte as ferramentas e instruções necessárias para configurar um ambiente de desenvolvimento local.
Projeto inicial
Confira o código de linha de base no qual o tutorial se baseia:
-
Confira a ramificação
tutorial/custom-component-startdo GitHubcode language-shell $ cd aem-guides-wknd $ git checkout tutorial/custom-component-start -
Implante a base de código em uma instância do AEM local, usando as suas habilidades do Maven:
code language-shell $ mvn clean install -PautoInstallSinglePackagenote note NOTE Se estiver usando o AEM 6.5 ou 6.4, anexe o perfil classica todos os comandos do Maven.code language-shell $ mvn clean install -PautoInstallSinglePackage -Pclassic
É possível ver o código concluído no GitHub ou conferir o código localmente a qualquer momento, alternando para a ramificação tutorial/custom-component-solution.
Objetivo
- Entender como criar um componente personalizado do AEM
- Aprender a encapsular a lógica de negócios com modelos do Sling
- Aprender a usar um modelo do Sling de dentro de um script HTL
O que você criará what-build
Nesta parte do tutorial do WKND, criamos um componente de linha de identificação usado para exibir informações criadas sobre o colaborador de um artigo.
Componente de linha de identificação
A implementação do componente de linha de identificação inclui uma caixa de diálogo que coleta o conteúdo da linha de identificação e um modelo do Sling personalizado que recupera detalhes como:
- Nome
- Imagem
- Profissões
Criar componente de linha de identificação create-byline-component
Primeiro, crie a estrutura do nó do componente de linha de identificação e defina uma caixa de diálogo. Representa o componente no AEM e define implicitamente o tipo de recurso do componente por sua localização no JCR.
A caixa de diálogo expõe a interface que os criadores de conteúdo podem fornecer. Para esta implementação, o componente de Imagem do componente principal de WCM do AEM é usado para tratar a criação e a renderização da imagem da linha de identificação; portanto, ele deve ser definido como sling:resourceSuperType deste componente.
Criar definição do componente create-component-definition
-
No módulo ui.apps, navegue até
/apps/wknd/componentse crie uma pasta chamadabyline. -
Dentro da pasta
byline, adicione um arquivo chamado.content.xml.
-
Preencha o arquivo
.content.xmlcom o seguinte: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="Byline" jcr:description="Displays a contributor's byline." componentGroup="WKND Sites Project - Content" sling:resourceSuperType="core/wcm/components/image/v2/image"/>O arquivo XML acima fornece a definição do componente, incluindo o título, a descrição e o grupo.
sling:resourceSuperTypeaponta paracore/wcm/components/image/v2/image, que é o Componente de imagem principal.
Criar o script HTL create-the-htl-script
-
Na pasta
byline, adicione um arquivobyline.html, responsável pela apresentação do componente em HTML. É importante nomear o arquivo de forma idêntica à pasta, pois ele se torna o script padrão que o Sling usa para renderizar esse tipo de recurso. -
Adicione o código a seguir ao
byline.html.code language-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>
O byline.html é revisitado mais tarde, depois que o modelo do Sling é criado. O estado atual do arquivo HTL permite que o componente seja exibido em um estado vazio no editor de páginas do AEM Sites quando ele é arrastado e solto na página.
Criar a definição da caixa de diálogo create-the-dialog-definition
Em seguida, defina uma caixa de diálogo para o componente de linha de identificação com os seguintes campos:
- Nome: um campo de texto que representa o nome do colaborador.
- Imagem: uma referência à imagem biográfica do colaborador.
- Profissões: uma lista de profissões atribuídas ao colaborador. As profissões devem ser classificadas alfabeticamente em ordem crescente (de A a Z).
-
Dentro da pasta
byline, crie uma pasta chamada_cq_dialog. -
Dentro do
byline/_cq_dialog, adicione um arquivo chamado.content.xml. Essa é a definição XML da caixa de diálogo. Adicione o seguinte 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" 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>Estas definições de nó da caixa de diálogo usam o Sling Resource Merger para controlar quais guias da caixa de diálogo são herdadas do componente de
sling:resourceSuperType, neste caso, o Componente de imagem dos componentes principais.
Criar a caixa de diálogo de política create-the-policy-dialog
Seguindo a mesma abordagem da criação da caixa de diálogo, crie uma caixa de diálogo de política (anteriormente conhecida como caixa de diálogo de design) para ocultar campos indesejados na configuração de política herdada do componente de imagem dos componentes principais.
-
Dentro da pasta
byline, crie uma pasta chamada_cq_design_dialog. -
Dentro de
byline/_cq_design_dialog, crie um arquivo chamado.content.xml. Atualize o arquivo com o XML a seguir. É mais fácil abrir o.content.xmle copiar/colar o XML abaixo nele.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="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>A base do XML da caixa de diálogo de política anterior foi obtida do Componente de imagem dos componentes principais.
Como na configuração da caixa de diálogo, o Sling Resource Merger é usado para ocultar campos irrelevantes que, de outra forma, seriam herdados do
sling:resourceSuperType, conforme visto pelas definições de nó com a propriedadesling:hideResource="{Boolean}true".
Implantar o código deploy-the-code
-
Sincronize as alterações em
ui.appscom o seu IDE ou usando as suas habilidades do Maven.
Adicionar o componente a uma página add-the-component-to-a-page
Para simplificar as coisas e manter o foco no desenvolvimento de componentes do AEM, vamos adicionar o componente de linha de identificação em seu estado atual a uma página de artigo para verificar se a definição do nó cq:Component está correta. E também para verificar se o AEM reconhece a nova definição de componente e se a caixa de diálogo do componente está funcionando para criação.
Adicionar uma imagem ao AEM Assets
Primeiro, carregue uma amostra de foto de perfil no AEM Assets a ser usada para preencher a imagem no componente de linha de identificação.
-
Navegue até a pasta “LA Skateparks” no AEM Assets: http://localhost:4502/assets.html/content/dam/wknd/en/magazine/la-skateparks.
-
Carregue a foto de perfil stacey-roswells.jpg na pasta.
Criar o componente author-the-component
Em seguida, adicione o componente de linha de identificação a uma página no AEM. Como o componente de linha de identificação é adicionado ao Projeto de sites da WKND: grupo de componentes de conteúdo, por meio da definição ui.apps/src/main/content/jcr_root/apps/wknd/components/byline/.content.xml, ele fica automaticamente disponível para qualquer Container cuja Política permita o Projeto de sites da WKND: grupo de componentes de conteúdo. Assim, ele está disponível no container de layout da página de artigo.
-
Navegue até o artigo “LA Skatepark” em: http://localhost:4502/editor.html/content/wknd/us/en/magazine/guide-la-skateparks.html
-
Na barra lateral esquerda, arraste e solte um componente de linha de identificação na parte inferior do container de layout da página de artigo aberta.
-
Verifique se a barra lateral esquerda está aberta e visível, e se o Localizador de ativos** está selecionado.
-
Selecione o espaço reservado do componente de Linha de identificação, que, por sua vez, exibe a barra de ação, e toque no ícone de chave inglesa para abrir a caixa de diálogo.
-
Com a caixa de diálogo aberta e a primeira guia (Ativo) ativa, abra a barra lateral esquerda e, no localizador de ativos, arraste uma imagem para a área de pouso de imagens. Procure por “stacey” para encontrar a imagem biográfica de Stacey Roswells fornecida no pacote ui.content da WKND.
-
Depois de adicionar uma imagem, clique na guia Propriedades para inserir o Nome e as Profissões.
Ao inserir profissões, insira-as em ordem alfabética inversa, para que a lógica de negócios de alfabetização implementada no modelo do Sling seja verificada.
Toque no botão Concluído, na parte inferior direita, para salvar as alterações.
Os criadores do AEM configuram e criam componentes por meio das caixas de diálogo. Neste ponto, no desenvolvimento do componente de linha de identificação, as caixas de diálogo são incluídas para coletar os dados, mas a lógica para renderizar o conteúdo criado ainda não foi adicionada. Portanto, somente o espaço reservado é exibido.
-
Depois de salvar a caixa de diálogo, navegue até CRXDE Lite e confira como o conteúdo do componente é armazenado no nó do conteúdo do componente de linha de identificação na página do AEM.
Localize o nó de conteúdo do componente de linha de identificação abaixo da página “LA Skate Parks”, ou seja,
/content/wknd/us/en/magazine/guide-la-skateparks/jcr:content/root/container/container/byline.Observe que os nomes das propriedades
name,occupationsefileReferenceestão armazenados no nó da linha de identificação.Além disso, observe que o
sling:resourceTypedo nó está definido comowknd/components/content/byline, que é o que vincula esse nó de conteúdo à implementação do componente de linha de identificação.
Criar modelo do Sling de linha de identificação create-sling-model
Em seguida, vamos criar um modelo do Sling que atuará como modelo de dados e hospedará a lógica de negócios do componente de linha de identificação.
Os modelos do Sling são POJOs (Plain Old Java™ Objects) de Java™ orientados por anotações que facilitam o mapeamento de dados do JCR para variáveis de Java™ e aumentam a eficiência ao desenvolver no contexto do AEM.
Revisar dependências do Maven maven-dependency
O modelo do Sling de linha de identificação depende de várias APIs de Java™ fornecidas pelo AEM. Essas APIs são disponibilizadas através do dependencies listado no arquivo POM do módulo core. O projeto usado neste tutorial foi criado para o AEM as a Cloud Service. No entanto, ele é diferente, pois é compatível com versões anteriores do AEM 6.5/6.4. Portanto, tanto a dependência do Cloud Service quanto a do AEM 6.x estão incluídas.
-
Abra o arquivo
pom.xmlabaixo de<src>/aem-guides-wknd/core/pom.xml. -
Localizar a dependência de
aem-sdk-api: Somente AEM as a Cloud Servicecode language-xml <dependency> <groupId>com.adobe.aem</groupId> <artifactId>aem-sdk-api</artifactId> </dependency>A aem-sdk-api contém todas as APIs de Java™ públicas expostas pelo AEM. O
aem-sdk-apié usado por padrão ao compilar este projeto. A versão é mantida no POM do reator primário da raiz do projeto emaem-guides-wknd/pom.xml. -
Localizar a dependência de
uber-jar: Somente AEM 6.5/6.4code language-xml ... <dependency> <groupId>com.adobe.aem</groupId> <artifactId>uber-jar</artifactId> <classifier>apis</classifier> </dependency> ...O
uber-jaré incluído somente quando o perfilclassicé chamado, ou seja,mvn clean install -PautoInstallSinglePackage -Pclassic. Novamente, isso é exclusivo deste projeto. Em um projeto real, gerado a partir do arquétipo de projeto do AEM, ouber-jarserá o padrão, se a versão do AEM especificada for 6.5 ou 6.4.O uber-jar contém todas as APIs de Java™ públicas expostas pelo AEM 6.x. A versão é mantida no POM do reator primário da raiz do projeto
aem-guides-wknd/pom.xml. -
Localizar a dependência de
core.wcm.components.core:code language-xml <!-- Core Component Dependency --> <dependency> <groupId>com.adobe.cq</groupId> <artifactId>core.wcm.components.core</artifactId> </dependency>Estas são as APIs de Java™ públicas completas expostas pelos componentes principais do AEM. Os componentes principais do AEM são um projeto mantido fora do AEM e, portanto, têm um ciclo de lançamento separado. Por esse motivo, é uma dependência que precisa ser incluída separadamente, não incluída com
uber-jarouaem-sdk-api.Assim como o uber-jar, a versão dessa dependência é mantida no arquivo POM do reator primário de
aem-guides-wknd/pom.xml.Posteriormente neste tutorial, a classe “Imagem” do componente principal será usada para exibir a imagem no componente de linha de identificação. É necessário contar com a dependência do componente principal para criar e compilar o modelo do Sling.
Interface da linha de identificação byline-interface
Crie uma interface Java™ pública para a linha de identificação. O Byline.java define os métodos públicos necessários para direcionar o script HTL byline.html.
-
Lá dentro, o módulo
corena pastacore/src/main/java/com/adobe/aem/guides/wknd/core/modelscria um arquivo chamadoByline.java
-
Atualize
Byline.javacom os seguintes métodos:code language-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(); }Os dois primeiros métodos expõem os valores de nome e profissões do componente de linha de identificação.
O método
isEmpty()é usado para determinar se o componente tem algum conteúdo para renderizar ou se está aguardando para ser configurado.Observe que não há um método para a imagem; isso será revisado mais tarde.
-
Os pacotes de Java™ que contêm classes públicas de Java™, neste caso, um modelo do Sling, precisam contar com a versão criada por meio do arquivo
package-info.javado pacote.Como o pacote de Java™
com.adobe.aem.guides.wknd.core.modelsda origem da WKND declara a versão de1.0.0, e uma interface pública e métodos não separáveis estão sendo adicionados, a versão precisa ser aumentada para1.1.0. Abra o arquivo emcore/src/main/java/com/adobe/aem/guides/wknd/core/models/package-info.javae atualize@Version("1.0.0")para@Version("2.1.0").code language-none @Version("2.1.0") package com.adobe.aem.guides.wknd.core.models; import org.osgi.annotation.versioning.Version;
Sempre que forem feitas alterações nos arquivos deste pacote, a versão do pacote precisará ser ajustada semanticamente. Caso contrário, o bnd-baseline-maven-plugin do projeto do Maven detectará uma versão do pacote inválida e interromperá a compilação. Felizmente, em caso de falha, o plug-in do Maven relata a versão inválida do pacote de Java™ e a versão que deveria ter. Atualize a declaração @Version("...") no package-info.java do pacote de Java™ violador para a versão recomendada pelo plug-in para corrigir.
Implementação da linha de identificação byline-implementation
O BylineImpl.java é a implementação do modelo do Sling que implementa a interface Byline.java definida anteriormente. O código completo para BylineImpl.java pode ser encontrado na parte inferior desta seção.
-
Crie uma pasta chamada
implabaixo decore/src/main/java/com/adobe/aem/guides/core/models. -
Na pasta
impl, crie um arquivoBylineImpl.java.
-
Abra
BylineImpl.java. Especifique que ele implementa a interfaceByline. Use os recursos de preenchimento automático do IDE ou atualize manualmente o arquivo para incluir os métodos necessários para implementar a interfaceByline:code language-java 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; } } -
Adicione as anotações do modelo do Sling, atualizando
BylineImpl.javacom as anotações na camada das classes a seguir. Essa@Model(..)anotação é o que transforma a classe em um modelo do Sling.code language-java 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"; ... }Vamos revisar essa anotação e seus parâmetros:
- A anotação
@Modelregistra BylineImpl como um modelo do Sling quando implantada no AEM. - O parâmetro
adaptablesespecifica que esse modelo pode ser adaptado pela solicitação. - O parâmetro
adapterspermite que a classe de implementação seja registrada na interface da linha de identificação. Isso permite que o script HTL chame o modelo do Sling pela interface (em vez da implementação diretamente). Mais detalhes sobre os adaptadores podem ser encontrados aqui. - O
resourceTypeaponta para o tipo de recurso do componente de linha de identificação (criado anteriormente) e ajuda a resolver o modelo correto, caso haja várias implementações. Mais detalhes sobre como associar uma classe de modelo a um tipo de recurso podem ser encontrados aqui.
- A anotação
Implementação dos métodos do modelo do Sling implementing-the-sling-model-methods
getName() implementing-get-name
O primeiro método implementado é getName(), que simplesmente retorna o valor armazenado ao nó de conteúdo JCR da linha de identificação na propriedade name.
Para isso, a anotação do modelo do Sling @ValueMapValue é usada para injetar o valor em um campo de Java™ usando o ValueMap do recurso da solicitação.
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
public class BylineImpl implements Byline {
...
@ValueMapValue
private String name;
...
@Override
public String getName() {
return name;
}
...
}
Como a propriedade JCR compartilha o nome como o campo de Java™ (ambos são “nome”), @ValueMapValue resolve automaticamente essa associação e injeta o valor da propriedade no campo de Java™.
getOccupations() implementing-get-occupations
O próximo método a implementar é getOccupations(). Este método carrega as profissões armazenadas na propriedade JCR occupations e retorna uma coleção classificada (alfabeticamente) dessas profissões.
Usando a mesma técnica abordada em getName(), o valor da propriedade pode ser inserido no campo do modelo do Sling.
Quando os valores da propriedade JCR estiverem disponíveis no modelo do Sling por meio do campo de Java™ occupations inserido, a lógica de negócios da classificação poderá ser aplicada no método 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() implementing-is-empty
O último método público é isEmpty(), que determina quando o componente deve ser considerado “criado o suficiente” para ser renderizado.
Para esse componente, o requisito comercial inclui todos os três campos; name, image and occupations deve ser preenchido antes que o componente possa ser renderizado.
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;
}
}
...
}
Como lidar com o “problema da imagem” tackling-the-image-problem
Verificar as condições de nome e profissão é trivial, e o Apache Commons Lang3 fornece a classe StringUtils útil. No entanto, não está claro como a presença da imagem pode ser validada, pois o componente de imagem dos componentes principais é usado para trazer a imagem à tona.
Há duas maneiras de contornar isso:
Verifique se a propriedade JCR fileReference está sendo resolvida como um ativo. OU Converta estse recurso em um modelo do Sling de imagem dos componentes principais e confirme que o método getSrc() não está vazio.
Vamos usar a segunda abordagem. A primeira abordagem provavelmente é suficiente, mas, neste tutorial, a última é usada para permitir explorar outros recursos dos modelos do Sling.
-
Crie um método privado que obtenha a imagem. Esse método é deixado como privado, porque não há necessidade de expor o objeto de imagem no próprio HTL, e ele é usado apenas para a unidade
isEmpty().Adicione o seguinte método privado para
getImage():code language-java 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; }Como observado acima, há mais duas abordagens para obter o modelo do Sling de imagem:
O primeiro usa a anotação
@Selfpara adaptar automaticamente a solicitação atual aoImage.classdo componente principal.O segundo usa o serviço Apache Sling ModelFactory da OSGi, que é um serviço prático e nos ajuda a criar modelos do Sling de outros tipos no código Java™.
Vamos usar a segunda abordagem.
note note NOTE Em uma implementação real, a abordagem “Um”, que usa @Self, é preferível, pois é a solução mais simples e elegante. Neste tutorial, a segunda abordagem é usada, pois requer a exploração de mais facetas dos modelos do Sling que são úteis com componentes mais complexos.Como os modelos do Sling são POJOs de Java™, não serviços da OSGi, as anotações de injeção da OSGi comuns
@Referencenão podem ser usadas; em vez delas, os modelos do Sling fornecem uma anotação especial @OSGiService, que oferece uma funcionalidade semelhante. -
Atualize
BylineImpl.javapara incluir a anotaçãoOSGiServicepara inserir oModelFactory:code language-java import org.apache.sling.models.factory.ModelFactory; import org.apache.sling.models.annotations.injectorspecific.OSGiService; ... public class BylineImpl implements Byline { ... @OSGiService private ModelFactory modelFactory; }Com o
ModelFactorydisponível, um modelo do Sling de imagem dos componentes principais pode ser criado com:code language-java modelFactory.getModelFromWrappedRequest(SlingHttpServletRequest request, Resource resource, java.lang.Class<T> targetClass)No entanto, esse método requer uma solicitação e um recurso, o que ainda não está disponível no modelo do Sling. Para obtê-los, mais anotações do modelo do Sling são usadas.
Para obter a solicitação atual, a anotação @Self pode ser usada para injetar o
adaptable(que é definido no@Model(..)comoSlingHttpServletRequest.class, em um campo de classe de Java™. -
Adicione a anotação @Self para obter a solicitação SlingHttpServletRequest:
code language-java import org.apache.sling.models.annotations.injectorspecific.Self; ... @Self private SlingHttpServletRequest request;Lembre-se de que usar
@Self Image imagepara inserir o modelo do Sling de imagem dos componentes principais foi uma opção acima; a anotação@Selftenta inserir o objeto adaptável (neste caso, uma SlingHttpServletRequest) e adaptar-se ao tipo de campo de anotação. Como o modelo do Sling de imagem dos componentes principais é adaptável a partir de objetos SlingHttpServletRequest, isso teria funcionado, e envolve menos código que a abordagemmodelFactory, que é mais exploratória.Agora, as variáveis necessárias para instanciar o modelo de imagem por meio da API ModelFactory são inseridas. Vamos usar a anotação @PostConstruct do modelo do Sling para obter esse objeto após a instanciação do modelo do Sling.
@PostConstructé incrivelmente útil e atua com uma capacidade semelhante a um construtor, mas é chamado depois que a classe é instanciada e todos os campos de Java™ anotados são inseridos. Enquanto outras anotações do modelo do Sling anotam campos de classe de Java™ (variáveis),@PostConstructanota um método de parâmetro nulo, zero, normalmente chamado deinit()(mas pode ser chamado de qualquer coisa). -
Adicionar o método @PostConstruct:
code language-java import javax.annotation.PostConstruct; ... public class BylineImpl implements Byline { ... private Image image; @PostConstruct private void init() { image = modelFactory.getModelFromWrappedRequest(request, request.getResource(), Image.class); } ... }Lembre-se de que os modelos do Sling NÃO são serviços da OSGi; portanto, é seguro manter o estado da classe. Muitas vezes,
@PostConstructderiva e configura o estado da classe do modelo do Sling para uso posterior, semelhante ao que um construtor simples faz.Se o método
@PostConstructacionar uma exceção, o modelo do Sling não será instanciado e será nulo. -
getImage() agora pode ser atualizado para simplesmente retornar o objeto de imagem.
code language-java /** * @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; } -
Vamos voltar a
isEmpty()e concluir a implementação:code language-java @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; } }Observe que várias chamadas para
getImage()não são problemáticas, pois retornam a variável de classeimageinicializada e não invocammodelFactory.getModelFromWrappedRequest(...), o que não é muito oneroso, mas vale a pena evitar chamar desnecessariamente. -
O
BylineImpl.javafinal deve ficar semelhante a:code language-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; } }
Linha de identificação HTL byline-htl
No módulo ui.apps, abra /apps/wknd/components/byline/byline.html criado na configuração anterior do componente do AEM.
<div data-sly-use.placeholderTemplate="core/wcm/components/commons/v1/templates.html">
</div>
<sly data-sly-call="${placeholderTemplate.placeholder @ isEmpty=false}"></sly>
Vamos analisar o que esse script HTL faz até agora:
-
O
placeholderTemplateaponta para o espaço reservado dos componentes principais, que é exibido quando o componente não está totalmente configurado. Isso é renderizado no editor de páginas do AEM Sites como uma caixa com o título do componente, conforme definido acima na propriedadejcr:titledecq:Component. -
O
data-sly-call="${placeholderTemplate.placeholder @ isEmpty=false}carrega oplaceholderTemplatedefinido acima e passa um valor booleano (atualmente embutido no código defalse) para o modelo de espaço reservado. QuandoisEmptyé verdadeiro, o modelo de espaço reservado renderiza a caixa cinza; caso contrário, não renderiza nada.
Atualizar HTL da linha de identificação
-
Atualize byline.html com a seguinte estrutura de HTML estrutural:
code language-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>Observe que as classes de CSS seguem a convenção de nomenclatura do BEM. Embora o uso das convenções do BEM não seja obrigatório, o BEM é recomendado, pois é usado nas classes de CSS dos componentes principais e geralmente resulta em regras de CSS limpas e legíveis.
Instanciação de objetos do modelo do Sling em HTL instantiating-sling-model-objects-in-htl
A declaração de bloco de uso é usada para instanciar objetos de modelo do Sling no script HTL e atribuí-los a uma variável HTL.
O data-sly-use.byline="com.adobe.aem.guides.wknd.models.Byline" usa a interface de linha de identificação (com.adobe.aem.guides.wknd.models.Byline) implementada por BylineImpl e adapta a SlingHttpServletRequest atual a ele, e o resultado é armazenado em uma linha de identificação de nome de variável de HTL (data-sly-use.<variable-name>).
-
Atualize o
divexterno para fazer referência ao modelo do Sling de Linha de identificação por sua interface pública:code language-xml <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>
Acessar os métodos do modelo do Sling accessing-sling-model-methods
O HTL pega emprestado do JSTL e usa o mesmo encurtamento de nomes dos métodos de obtenção do Java™.
Por exemplo, a invocação do método getName() do modelo do Sling de linha de identificação pode ser encurtada para byline.name; da mesma forma, em vez de byline.isEmpty, ela pode ser encurtada para byline.empty. O uso de nomes completos de métodos, byline.getName ou byline.isEmpty, também funciona. Observe que () nunca são usados para invocar métodos em HTL (semelhante ao JSTL).
Métodos de Java™ que exigem um parâmetro não podem ser usados em HTL. A intenção disso é manter a lógica do HTL simples.
-
O nome da linha de identificação pode ser adicionado ao componente, chamando-se o método
getName()no modelo de Sling de linha de identificação ou no HTL:${byline.name}.Atualizar a tag
h2:code language-xml <h2 class="cmp-byline__name">${byline.name}</h2>
Usar opções de expressão em HTL using-htl-expression-options
As opções de expressão em HTL atuam como modificadores no conteúdo em HTL e variam de formatação da data à tradução de i18n. As expressões também podem ser usadas para unir listas ou matrizes de valores, que são necessárias para exibir as profissões em um formato delimitado por vírgulas.
As expressões são adicionadas por meio do operador @ na expressão em HTL.
-
Para entrar na lista de profissões com “, ”, utiliza-se o seguinte código:
code language-html <p class="cmp-byline__occupations">${byline.occupations @ join=', '}</p>
Exibição condicional do espaço reservado conditionally-displaying-the-placeholder
A maioria dos scripts em HTL para componentes do AEM usa o paradigma de espaço reservado para fornecer uma dica visual aos criadores , indicando que um componente foi criado incorretamente e não é exibido na publicação do AEM. A convenção para conduzir essa decisão é implementar um método no modelo do Sling de apoio ao componente, neste caso: Byline.isEmpty().
O método isEmpty() é chamado no modelo do Sling de linha de identificação, e o resultado (ou melhor, seu negativo, por meio do operador !) é salvo em uma variável de HTL chamada hasContent:
-
Atualizar o
divexterno para salvar uma variável de HTL chamadahasContent:code language-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>Observe o uso de
data-sly-test; o bloco de HTLtesté fundamental, pois define uma variável de HTL e renderiza/não renderiza o elemento de HTML no qual se encontra. Tem como base o resultado da avaliação da expressão em HTL. Se for “verdadeiro”, o elemento de HTML será renderizado; caso contrário, não será renderizado.Essa variável de HTL
hasContentagora pode ser reutilizada para mostrar/ocultar condicionalmente o espaço reservado. -
Atualize a chamada condicional para
placeholderTemplatena parte inferior do arquivo com o seguinte:code language-html <sly data-sly-call="${placeholderTemplate.placeholder @ isEmpty=!hasContent}"></sly>
Exibir a imagem com os componentes principais using-the-core-components-image
O script HTL para byline.html agora está quase concluído; só falta a imagem.
À medida que o sling:resourceSuperType aponta para o componente de imagem dos componentes principais para criar a imagem, o componente de imagem dos componentes principais pode ser usado para renderizar a imagem.
Para isso, vamos incluir o recurso de linha de identificação atual, mas forçar o tipo de recurso do componente de imagem dos componentes principais, usando o tipo de recurso core/wcm/components/image/v2/image. Trata-se de um padrão potente para reutilização de componentes. Para isso, o bloco data-sly-resource do HTL é usado.
-
Substitua o
divpor uma classe decmp-byline__imagecom o seguinte:code language-html <div class="cmp-byline__image" data-sly-resource="${ '.' @ resourceType = 'core/wcm/components/image/v2/image' }"></div>Este
data-sly-resourceinclui o recurso atual por meio do caminho relativo'.'e força a inclusão do recurso atual (ou do recurso de conteúdo de linha de identificação) com o tipo de recursocore/wcm/components/image/v2/image.O tipo de recurso do componente principal é usado diretamente, não por proxy, porque esse é um uso no script e nunca é persistente no conteúdo.
-
Conclusão de
byline.htmlabaixo:code language-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> -
Implante a base de código em uma instância local do AEM. Como foram feitas alterações em
coreeui.apps, ambos os módulos precisam ser implantados.code language-shell $ cd aem-guides-wknd/ui.apps $ mvn clean install -PautoInstallPackagecode language-shell $ cd ../core $ mvn clean install -PautoInstallBundlePara implantar no AEM 6.5/6.4, chame o perfil
classic:code language-shell $ cd ../core $ mvn clean install -PautoInstallBundle -Pclassicnote caution CAUTION Você também pode criar todo o projeto a partir da raiz, usando o perfil do Maven autoInstallSinglePackage, mas isso pode substituir as alterações de conteúdo na página. Isso ocorre porque oui.content/src/main/content/META-INF/vault/filter.xmlfoi modificado para o código inicial do tutorial para substituir completamente o conteúdo do AEM existente. No mundo real, isso não seria um problema.
Revisar o componente de linha de identificação não estilizado reviewing-the-unstyled-byline-component
-
Depois de implantar a atualização, navegue até a página Ultimate Guide do LA Skateparks ou até onde você tiver adicionado o componente de linha de identificação anteriormente neste capítulo.
-
A imagem, o nome e as profissões aparecem agora, e um componente de linha de identificação não estilizado está presente.
Revisar o registro do modelo do Sling reviewing-the-sling-model-registration
A visualização do status dos modelos do Sling do console da web do AEM exibe todos os modelos do Sling registrados no AEM. O modelo do Sling de linha de identificação pode ser validado como instalado e reconhecido por meio da conferência desta lista.
Se o BylineImpl não for exibido nessa lista, provavelmente há um problema com as anotações do modelo do Sling, ou o modelo não foi adicionado ao pacote correto (com.adobe.aem.guides.wknd.core.models) no projeto principal.
http://localhost:4502/system/console/status-slingmodels
Estilos de linha de identificação byline-styles
Para alinhar o componente de linha de identificação com o design criativo fornecido, vamos estilizá-lo. Isso é feito por meio do arquivo SCSS, atualizando-se o arquivo no módulo ui.frontend.
Adicionar um estilo padrão
Adicione estilos padrão ao componente de linha de identificação.
-
Retorne ao IDE e ao projeto ui.frontend gerado em
/src/main/webpack/components: -
Crie um arquivo chamado
_byline.scss.
-
Adicionar o CSS de implementações de linha de identificação (gravado como SCSS) ao
_byline.scss:code language-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; } } -
Abra um terminal e navegue até o módulo
ui.frontend. -
Inicie o processo
watchcom o seguinte comando npm:code language-shell $ cd ui.frontend/ $ npm run watch -
Retorne ao navegador e navegue até o artigo “LA SkateParks”. Você deve ver os estilos atualizados no componente.
note tip TIP Talvez seja necessário limpar o cache do navegador para garantir que o CSS obsoleto não esteja sendo fornecido e atualizar a página com o componente de linha de identificação para obter o estilo completo.
Parabéns! congratulations
Parabéns, você criou um componente personalizado do zero com o Adobe Experience Manager!
Próximas etapas next-steps
Continue aprendendo sobre o desenvolvimento de componentes do AEM, explorando como escrever testes de JUnit para o código de linha de identificação em Java™ a fim de garantir que tudo seja desenvolvido corretamente e que a lógica de negócios implementada esteja correta e completa.
Veja o código concluído no GitHub ou revise e implante o código localmente na ramificação do Git tutorial/custom-component-solution.
- Clone o repositório github.com/adobe/aem-guides-wknd.
- Confira a ramificação
tutorial/custom-component-solution