May 13, 2020
Estimated Post Reading Time ~

ui.apps

The ui.apps package is your core application, it is deployed into AEM as a bundle either as part of an all deployment, or on it's own

pom.xml

The following is an example of a standard ui.apps pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- ====================================================================== -->
<!-- P A R E N T P R O J E C T D E S C R I P T I O N -->
<!-- ====================================================================== -->
<parent>
<groupId>com.sample</groupId>
<artifactId>base</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<!-- ====================================================================== -->
<!-- P R O J E C T D E S C R I P T I O N -->
<!-- ====================================================================== -->
<artifactId>sample.ui.apps</artifactId>
<packaging>content-package</packaging>
<name>Sample - UI apps</name>
<description>UI apps package for Sample App</description>
<!-- ====================================================================== -->
<!-- B U I L D D E F I N I T I O N -->
<!-- ====================================================================== -->
<build>
<resources>
<!-- define the resources that will go into the package -->
<resource>
<!-- we want to keep some of the META-INF files and not configure everything in the plugin. -->
<directory>${basedir}/src/main/content/META-INF</directory>
<targetPath>../vault-work/META-INF</targetPath>
</resource>
<resource>
<directory>${basedir}/src/main/content/jcr_root</directory>
<excludes>
<!-- exclude .vlt control files in the package -->
<exclude>**/.vlt</exclude>
<exclude>**/.vltignore</exclude>
<exclude>**/.gitignore</exclude>
<exclude>**/*.iml</exclude>
<exclude>**/.classpath</exclude>
<exclude>**/.project</exclude>
<exclude>**/.settings</exclude>
<exclude>**/.DS_Store</exclude>
<exclude>**/target/**</exclude>
<exclude>**/pom.xml</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.17</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<!-- ensure that the empty directories are copied -->
<configuration>
<includeEmptyDirs>true</includeEmptyDirs>
</configuration>
<executions>
<execution>
<id>copy-metainf-vault-resources</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/vault-work/META-INF</outputDirectory>
<resources>
<resource>
<directory>${basedir}/META-INF</directory>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>default</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<!-- ====================================================================== -->
<!-- V A U L T P A C K A G E P L U G I N -->
<!-- ====================================================================== -->
<plugin>
<groupId>com.day.jcr.vault</groupId>
<artifactId>content-package-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<filterSource>${basedir}/META-INF/vault/filter.xml</filterSource>
<verbose>true</verbose>
<failOnError>true</failOnError>
<group>${project.groupId}</group>
<embeddeds>
<embedded>
<groupId>${project.groupId}</groupId>
<artifactId>toolbox.core</artifactId>
<target>/apps/toolbox/install</target>
</embedded>
<embedded>
<groupId>${project.groupId}</groupId>
<artifactId>sample.sling.dev</artifactId>
<target>/apps/toolbox/install.publish.dev</target>
</embedded>
<embedded>
<groupId>${project.groupId}</groupId>
<artifactId>sample.sling.test</artifactId>
<target>/apps/toolbox/install.publish.test</target>
</embedded>
<embedded>
<groupId>${project.groupId}</groupId>
<artifactId>sample.sling.prod</artifactId>
<target>/apps/toolbox/install.publish.prod</target>
</embedded>
</embeddeds>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles> <!-- ====================================================================== -->
<!-- D E P E N D E N C I E S -->
<!-- ====================================================================== --> <dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>sample.core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>sample.sling.dev</artifactId>
<version>${project.version}</version>
<type>content-package</type>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>sample.sling.test</artifactId>
<version>${project.version}</version>
<type>content-package</type>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>sample.sling.prod</artifactId>
<version>${project.version}</version>
<type>content-package</type>
</dependency>
</dependencies>
</project>
This pom is a bit more unusual to your standard java pom, for starters it is not a jar pom
<packaging>content-package</packaging>
This makes it an AEM content package so there are no compilation tasks available to the project, which means that virtually everything in the project is actually a resource, either html, css, js or xml files for configuring AEM.
We store the resources in 2 places
<resources>
<!-- define the resources that will go into the package -->
<resource>
<!-- we want to keep some of the META-INF files and not configure everything in the plugin. -->
<directory>${basedir}/src/main/content/META-INF</directory>
<targetPath>../vault-work/META-INF</targetPath>
</resource>
<resource>
<directory>${basedir}/src/main/content/jcr_root</directory>
<excludes>
<!-- exclude .vlt control files in the package -->
<exclude>**/.vlt</exclude>
<exclude>**/.vltignore</exclude>
<exclude>**/.gitignore</exclude>
<exclude>** /*.iml</exclude>
<exclude>**/ .classpath</exclude>
<exclude>**/.project</exclude>
<exclude>**/.settings</exclude>
<exclude>**/.DS_Store</exclude>
<exclude>**/target /**</exclude>
<exclude>**/ pom.xml</exclude>
</excludes>
</resource>
</resources>
The configuration in /src/main/content/META-INF is the configuration for the vault plugin which allows deployment to AEM itself and configures what will be deployed once the package is installed in AEM.
The second area is the content that will be installed into the jcr:repository, note the excludes, this is a tidyup so that we don't put content into the jcr:repository that is just boilerplate for our project, or those hidden files that appear on the file system because of your IDE or code repository
The maven-resources-plugin is configured to move the first resource into the correct place in the zip file that gets generated
While you don't need to put vault plugin onto a profile, I do this so that I can put in different profiles for different environments if I want to.
The vault plugin is the main one for an aem app project
<plugin>
<groupId>com.day.jcr.vault</groupId>
<artifactId>content- package -maven-plugin</artifactId>
<extensions> true </extensions>
<configuration>
<filterSource>${basedir}/META-INF/vault/filter.xml</filterSource> <verbose> true </verbose>
<failOnError> true </failOnError>
<group>${project.groupId}</group>
<embeddeds>
<embedded>
<groupId>${project.groupId}</groupId>
<artifactId>toolbox.core</artifactId>
<target>/apps/toolbox/install</target>
</embedded>
<embedded>
<groupId>${project.groupId}</groupId>
<artifactId>sample.sling.dev</artifactId>
<target>/apps/toolbox/install.publish.dev</target>
</embedded>
<embedded>
<groupId>${project.groupId}</groupId>
<artifactId>sample.sling.test</artifactId>
<target>/apps/toolbox/install.publish.test</target>
</embedded>
<embedded>
<groupId>${project.groupId}</groupId>
<artifactId>sample.sling.prod</artifactId>
<target>/apps/toolbox/install.publish.prod</target>
</embedded>
</embeddeds>
</configuration>
</plugin>
While the content-package-maven-plugin has a lot of options available to it, not many of them are used in most cases. To see all of the options go to the adobe documentation.
The main pieces that i think are important are
  • failOnError
    • defaults to false
    • causes the build to fail if the plugin finds any issues
  • verbose
    • defaults to false
    • outputs more debug on any issues that it sees
  • filterSource
    • The location of the vault filter document (explained later)
  • group
    • The group of the generated package
  • embeddeds
    • A list of other dependencies to embed in the package
    • All embeds must be in the dependancy tree of the project
    • embedded
      • groupId
        • The groupId of the package to embed
      • artifactId
        • The artifact id of the package to embed
      • target
        • Where in the jcr:repository the file should be placed
  • subPackages
    • Similar to embeddeds, however the package will just be installed along with the current deployment and not left to AEM to decide if it needs to be deployed
    • subPackage
      • groupId
        • The groupId of the package to embed
      • artifactId
        • The artifact id of the package to embed
      • filter
        • If the object should be added to the filter source

All Deployment

While not specifically necessary, I don't like deploying multiple parts to AEM so i build an all project which is simply the pom and combines all of the sub packages into it, and it will use the subPackages notation.
<plugin>
<groupId>com.day.jcr.vault</groupId>
<artifactId>content- package -maven-plugin</artifactId>
<extensions> true </extensions>
<configuration>
<filterSource>${basedir}/META-INF/vault/filter.xml</filterSource>
<verbose> true </verbose>
<failOnError> true </failOnError>
<group>${project.groupId}</group>
<embeddeds>
<embedded>
<groupId>${project.groupId}</groupId>
<artifactId>toolbox.core</artifactId>
<target>/apps/toolbox/install</target>
</embedded>
<embedded>
<groupId>${project.groupId}</groupId>
<artifactId>sample.sling.dev</artifactId>
<target>/apps/toolbox/install.publish.dev</target>
</embedded>
<embedded>
<groupId>${project.groupId}</groupId>
<artifactId>sample.sling.test</artifactId>
<target>/apps/toolbox/install.publish.test</target>
</embedded>
<embedded>
<groupId>${project.groupId}</groupId>
<artifactId>sample.sling.prod</artifactId>
<target>/apps/toolbox/install.publish.prod</target>
</embedded>
</embeddeds>
</configuration>
</plugin>
As you can see the all project brings the ui.app and ui.content content packages together as subPackage's, this makes a single deployable package which includes all parts of the application.
The content-package-maven-plugin plugin has multiple purposes. The first as described above is to create the content package, but it also has the ability to deploy the content package into AEM. For this, you have to provide a few extra parameters
  • targetURL
    • The url of the server that you want to deploy
  • userId
    • The user id to login to aem as for the deploy
    • defaults to admin
  • password
    • The password to login to aem with for the deploy
    • defaults to admin

Structure

An aem deployment is just putting content into the jcr:repository, while this is exactly the same for the ui.content and sling projects we try and make each of the apps specific to their purpose.
For applications the structure is
App Folder Structure
While again, this is the structure that I use, some parts are not necessarily required I will explain what each of the bits is for.
Applications in AEM are always put in the apps folder in the jcr:repository, you can have multiple apps in a single deployment, while not recommended, there are times that you will need to do this, especially when adding additional functionality to core AEM functions.
  • components
    • Again a component doesn't have to be within the components folder, however, it provides a clean structure
    • content
      • This is an area for content-based components.
      • Each component will be built so that it is independent and will sit inside a parsys, or fixed to a page
      • The component should only have the html required for its own display
    • structure
      • This is an area for page-based components
      • Each page will be referenced by a template if an editor can add a page of this type
      • The page should encompass all of the html that the page requires
      • While in the AEM world a page is not talked about as a component, it has all of the same functionality
  • config
    • The location for any OSGI configuration files that are applied to the entire platform
    • The configuration is installed on both the author and publisher
  • config.runmodes
    • Is a server-specific configuration the same structure as the config folder
    • You can have many different runmodes, and hence different configuration folders
      • examples:
        • config.publish
          • only on the publisher
        • config.author
          • only on the author
        • config.dev
          • on both author and publisher, but only when the environment also has the runmode dev
        • config.publish.dev
          • on the publisher when the environment also has the runmode dev
        • config.dev.primary.bigserver
          • on both the author and publisher when the environment has the runmodes dev, primary and big server
  • install
    • The location for any OSGI packages that should be installed as part of this package
  • install.runmodes
    • Is a server-specific OSGI package installations
    • Is the OSGI install version of config.runmodes
  • i18n
    • This is the location of the internationalization files
    • Each file describes a single language
  • templates
    • This is the location for AEM page templates.
    • Each template defines where a page can be used and which page component is created for it
  • widgets
    • This is the location I use for client libs that are specifically for the editorial UI
The second part of the deployment is the design. The design is the css, javascript and base images
One thing to note is when I say base images I mean images that shouldn't be modified, all editorial images should be stored in the content DAM
As I mentioned in the Repository Structure page, it is possible to have multiple designs. You could think of these as themes that you might allow the editors to choose for the site they are creating with your components.
The only thing mandated in the design is the top-level, everything else inside the design including where assets and client libs are stored is up to you, I say this so you understand how flexible it is but I always recommend using a standard structure as shown in the diagram above.
To me, the design has 2 specific tasks

Template definition

in the .content.xml file for the node (viewable in crx/de as jcr:content)
The template definition defines what is available under certain node types, this is generally where you define which components/component groups are available to be selected from a parsys
<?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= "cq:Page" >
<jcr:content
cq:lastModified= "{Date}2017-04-13T15:23:04.018Z"
cq:lastModifiedBy= "admin"
jcr:primaryType= "nt:unstructured"
jcr:title= "Design"
sling:resourceType= "wcm/core/components/designer" >
<advanced-page jcr:primaryType= "nt:unstructured" >
<widgets jcr:primaryType= "nt:unstructured" >
<parwidgets
jcr:lastModified= "{Date}2016-05-24T12:57:50.596+01:00"
jcr:lastModifiedBy= "admin"
jcr:primaryType= "nt:unstructured"
sling:resourceType= "wcm/foundation/components/parsys"
components= "[group:Sample Widgets]" >
<newslist jcr:primaryType= "nt:unstructured" >
<items
jcr:lastModified= "{Date}2016-05-24T12:57:50.596+01:00"
jcr:lastModifiedBy= "admin"
jcr:primaryType= "nt:unstructured"
sling:resourceType= "wcm/foundation/components/parsys"
components= "[group:Sample News Items]" >
<acl
jcr:lastModified= "{Date}2016-05-24T12:57:50.596+01:00"
jcr:lastModifiedBy="admin
jcr:primaryType= "nt:unstructured"
sling:resourceType= "wcm/foundation/components/parsys"
components= "[group:Sample Access Control]" >
<section jcr:primaryType= "nt:unstructured" />
</acl>
</items>
<acl
jcr:lastModified= "{Date}2016-05-24T12:57:50.596+01:00"
jcr:lastModifiedBy= "admin"
jcr:primaryType= "nt:unstructured"
sling:resourceType= "wcm/foundation/components/parsys"
components= "[group:Sample Access Control]" >
<section jcr:primaryType= "nt:unstructured" />
</acl>
</newslist>
</parwidgets>
</widgets>
</advanced-page>
<editorial-page jcr:primaryType= "nt:unstructured" >
<par
jcr:lastModified= "{Date}2016-05-24T12:57:50.596+01:00"
jcr:lastModifiedBy= "admin"
jcr:primaryType= "nt:unstructured"
sling:resourceType= "wcm/foundation/components/parsys"
components= "[group:General,group:Adaptive Form,group:Address,group:Castrol components,group:Columns,group:Commerce,group:Communities,group:Document Services]" >
<section jcr:primaryType= "nt:unstructured" />
</par>
</editorial-page>
</jcr:content>
</jcr:root>
The above example is a bit extensive, but I will try and explain what we have here.
First let me explain that in the jcr:repository .content.xml is a node definition, however as you can see it can also install multiple nodes below it. This is the easiest way of installing content into the jcr:repository in your package
So let's break the xml file down a bit, firstly you can see that the root node is defined as jcr:primaryType="cq:Page", each node has a jcr:primaryType which tells aem what the node represents, in this case, it is telling the aem that this represents a page in aem. The page structure will always have a jcr:content node underneath it that contains it's settings, which you can see is the next node defined. You can see that it's jcr:primaryType="nt:unstructured" is which means it isn't a special type of node and will always just resolve to a Resource. I won't go into much about what types of jcr:primaryType there are the most commonly used are cq:page and nt:unstructured.
The next set of nodes define page types, each has it's own definition of how the page is structured. I will explain the most complex
<advanced-page jcr:primaryType= "nt:unstructured" >
<widgets jcr:primaryType= "nt:unstructured" >
<parwidgets
jcr:primaryType= "nt:unstructured"
sling:resourceType= "wcm/foundation/components/parsys"
components= "[group:Sample Widgets]" >
<newslist jcr:primaryType= "nt:unstructured" >
<items
jcr:primaryType= "nt:unstructured"
sling:resourceType= "wcm/foundation/components/parsys"
components= "[group:Sample News Items]" > <acl
jcr:primaryType= "nt:unstructured"
sling:resourceType= "wcm/foundation/components/parsys"
components= "[group:Sample Access Control]" >
<section jcr:primaryType= "nt:unstructured" />
</acl>
</items> <acl
sling:resourceType= "wcm/foundation/components/parsys"
components= "[group:Sample Access Control]" >
<section jcr:primaryType= "nt:unstructured" />
</acl>
</newslist>
</parwidgets>
</widgets>
</advanced-page>
So what do we see, this set of nodes is defining a page type "advanced-page" in the pages section I will explain more about how pages are defined. For now, all you need to know is that the advanced-page will always have a content item called widgets below it, this will be a component that is on the page.
For any item below this, the logic is always the same. Each node is defining what will be allowed within a node of its name. The name of the node will represent a component defined to be in a node of the same name.
Just to give you an idea of what that would look like in the html (we will go through this later)
<div class="sample-widgets"data-sly-resource="${@path='widgets', resourceType='sample/components/content/widgets'}">
As you can see in this piece of sightly html we can add a resource with the path widgets that are of type sample/components/content/widgets so in the template where we see widgets we know it is the sample/components/content/widgets component it is talking about. There isn't much configuration to the widgets node other than a child node so we can assume in the widgets component there is a reference to the resource parwidgets, you can see this is defined as type wcm/foundation/components/parsys. A parsys is an editorial control that comes with AEM that allows an editor to drag and drop other components into it. By default, a parsys allows all components that are not in the .hidden group (I will discuss groups in the components section)
This is really the main part of the template, defining what components the editor can add to a parsys. As I mentioned by default you can add all of them, but if you have a specific page layout you are after and only want the editor to be able to put specific components into the parsys you can add the components attribute. In this example, we are putting in a group Sample Widgets which means that any component that has been defined in the Sample Widgets group will be available to add to the parsys. You can add individual components by their name, or an entire group by putting group: before it. Again if you don't know the proper names, you can go into design mode in the console and select what is available. If your design has been associated with your page then it will be saved into the designs jcr:content node and can be exported in the package manager.
AEM component selector
As can be seen in the above example of the editorial component selector, this has the group Geometrix Outdoors available to it.
Adding sub-nodes to a parsys, you need to know how the component will name itself. i.e. we have a component in the Sample Widgets group called news list, it doesn't matter if you add multiple new list items to the parsys the individual configuration will apply to all of them.
By nesting the components that you can have below it you can define the editorial journey and what is available, however as you could imagine if you have a lot of components that have parsys in them it will become very complex, and as can be seen in the example you may well end up with some duplication. In the advanced-page, we can see that the ACL component is usable in both the news list as well as below the items node.
Configuring the template of what can and can't be seen in parsys is a complex task, and while it can be done purely in the codebase I recommend using the design mode in AEM's editor to do it, this would, of course, mean you need to add every possible combination of components to get a full view. If you can, avoid repetition of components availability.
i.e. you have composed a that has a parsys that can take components b and c, and components b and c both have a parsys that can take components a,b and c. While this sort of repetition is possible to define, it is very complex and well above the level of discussion we are having.

Client Libs

This is the first, but not the last time I will mention client libs. The client lib functionality in AEM is a very powerful way of allowing a page to have access to a single css or js file that contains all of the css or js of the required components.
Defining a client lib is pretty simple from the codebase
Client libs
The folder structure while again is not fixed, this is the recommendation
The .content.xml file, as usual, defines the node of the clientLib, the main folder name can be whatever you want it to be as you will probably have multiple client libs available to your app, I tend to name them based on the page type they are for.
<?xml version="1.0"encoding="UTF-8"?>
<jcr:root xmlns:cq= http://www.day.com/jcr/cq/1.0" xmlns:jcr=http://www.jcp.org/jcr/1.0"
jcr:primaryType="cq:ClientLibraryFolder"
categories="[sample.applications]"
embed="[sample.folderPage]"/>
As you can see a client lib node is of type cq:ClientLibraryFolder, AEM will be able to find your client libs only if it is of this type
There are only 2 values of importance that can be put into a client lib's definition
  • categories
    • This is how you find a client lib, you can add it into multiple categories so that it can be found with different names
  • embed
    • This is a list of the other client libs that you want to become part of this client lib.
      • i.e When you make a call for this clientlib, it will go off and find all of the client libs with the category of sample.folderPage and will pull in the css/js into the single file that will be downloaded for this client lib's reference
To reference a clientlib in the html you would put in the following code
<sly data-sly-use.clientLib="/libs/granite/sightly/templates/clientlib.html"data-sly-call="${clientLib.css @ categories='sample.applications'}" data-sly-unwrap/>
The above will add the css tags into your html for this client lib while putting clientLib.js will add the javascript tags.
As you can see the client lib can embed other client libs into it, but it can also have it's own css and javascript, this is done via the css.txt and js.txt files. These lists the scripts that are included as part of the clientlib.
While I put the css and js in their own folder, it is not required, again it is just for making the code base tidy.
Both the css.txt and js.txt have the same format
#base=js
jquery.waypoints.min.js
responsive.bootstrap.toolkit.js
moment.min.js
stickyKit.js
The comment #base=js defines where the base folder is so that it knows that all of the files listed below it are in the js folder.


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.