Lessons
  Menu

Creating a Composite Multifield in AEM

Step 1: Extending the Dialog Box for the Profile Component

  1. Location: Start by navigating to the path ui.apps/src/main/content/jcr_root/apps/wknd/components/profile in your AEM project using IntelliJ.
  2. Dialog Box Extension:
    • Add a new node under the existing dialog definition for the “Profile” component.
    • Name this node previousExperience and set its sling:resourceType to "granite/ui/components/coral/foundation/form/multifield".
    • Inside this node, create a field node with sling:resourceType as "granite/ui/components/coral/foundation/form/fieldset".
    • Under the field node, add nodes for each subfield: companyName, startDate, and endDate. Set their respective sling:resourceType to the appropriate field types (textfield for companyName and datepicker for startDate and endDate).

<!-- Previous Experience Multifield -->
<previousExperience
		jcr:primaryType="nt:unstructured"
		sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
		composite="{Boolean}true"
		fieldLabel="Previous Experience"
	   >
	<field
			jcr:primaryType="nt:unstructured"
			sling:resourceType="granite/ui/components/coral/foundation/container"
			name="./prevExperiences">
		<items jcr:primaryType="nt:unstructured">
				<companyName
						jcr:primaryType="nt:unstructured"
						sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
						fieldLabel="Company Name"
						name="./companyName"/>
				<startDate
						jcr:primaryType="nt:unstructured"
						sling:resourceType="granite/ui/components/coral/foundation/form/datepicker"
						fieldLabel="Start Date"
						name="./startDate"/>
				<endDate
						jcr:primaryType="nt:unstructured"
						sling:resourceType="granite/ui/components/coral/foundation/form/datepicker"
						fieldLabel="End Date"
						name="./endDate"/>
		</items>
	</field>
</previousExperience>

Step 2: Sling Model Creation

Profile Component Sling Model:

package com.adobe.aem.guides.wknd.core.models;

import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ChildResource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;


import java.util.List;


@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ProfileModel {

    @ValueMapValue
    private List<String> hobbies;
    @ChildResource
    private List<Experience> previousExperience;

    public List<Experience> getPreviousExperience() {
        return previousExperience;
    }


    public List<String> getHobbies() {
        return hobbies;
    }
}

Package Declaration:

  • package com.adobe.aem.guides.wknd.core.models;
  • This statement declares that the class belongs to the package com.adobe.aem.guides.wknd.core.models.

Imports:

  • The import statements load various classes and interfaces that are used in the ProfileModel class.

Class Annotation (@Model):

  • @Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
  • This is an annotation from the Sling Models framework. It indicates that this class is a Sling Model that can be adapted from a Resource. The defaultInjectionStrategy being OPTIONAL means that if a field cannot be injected, it will be ignored rather than causing an error.

Class Definition:

  • public class ProfileModel { ... }
  • This defines the ProfileModel class.

Fields:

  • @ValueMapValue private List<String> hobbies;
    • This field represents a list of hobbies. The @ValueMapValue annotation is used to inject the value from the resource’s ValueMap (a key-value map of properties).
  • @ChildResource private List<Experience> previousExperience;
    • This field represents a list of Experience objects. The @ChildResource annotation is used to inject child resources. Here, it’s expected that the Experience objects are child nodes of the current resource in the JCR (Java Content Repository).

Getters:

  • These are methods to access the values of the fields:
    • public List<Experience> getPreviousExperience() { return previousExperience; }
    • public List<String> getHobbies() { return hobbies; }
  • These methods allow other parts of the AEM application to access the hobbies and previousExperience properties.

Experience Component Sling Model:

package com.adobe.aem.guides.wknd.core.models;

import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;

//Inner Class for Experience
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class Experience {
    @ValueMapValue
    private String companyName;
    @ValueMapValue
    private String startDate;
    @ValueMapValue
    private String endDate;

    // Getters
    public String getCompanyName() { return companyName; }
    public String getStartDate() { return startDate; }
    public String getEndDate() { return endDate; }
}

This code defines a Java class named Experience, which is part of the com.adobe.aem.guides.wknd.core.models package. It is structured as a Sling Model for use in Adobe Experience Manager (AEM). Sling Models facilitate the mapping of Java objects to content stored in AEM’s Java Content Repository (JCR). Let’s break down the components of the Experience class:

  1. Package Declaration:
    • package com.adobe.aem.guides.wknd.core.models;
    • Specifies that the class is part of the com.adobe.aem.guides.wknd.core.models package.
  2. Imports:
    • The code imports necessary classes and interfaces from the Sling API and Sling Models framework.
  3. Class Annotation (@Model):
    • @Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
    • This annotation marks the class as a Sling Model, meaning it can be adapted from a Sling Resource. The defaultInjectionStrategy set to OPTIONAL indicates that if some fields can’t be injected with values, the model will still be created without throwing an error.
  4. Class Definition:
    • public class Experience { ... }
    • Defines the Experience class.
  5. Fields (Properties):
    • @ValueMapValue private String companyName;
      • This field stores the company name. The @ValueMapValue annotation is used to inject the value from the resource’s ValueMap (a map of the property values).
    • @ValueMapValue private String startDate;
      • Stores the start date of the experience. Again, @ValueMapValue is used for injection.
    • @ValueMapValue private String endDate;
      • Stores the end date of the experience.
  6. Getters:
    • public String getCompanyName() { return companyName; }
    • public String getStartDate() { return startDate; }
    • public String getEndDate() { return endDate; }
    • These methods are getters that provide access to the values of the respective fields. They are used to retrieve the company name, start date, and end date of an experience.

In summary, the Experience class in this code is designed as a Sling Model in AEM. It represents an ‘experience’ entity with properties like company name, start date, and end date, which are injected from a Sling Resource. This class is likely used in conjunction with other models to represent a user’s professional experiences in an AEM application.

Step 3: Write Sightly Code

  1. HTL Code for Profile Component:
    • Use the data-sly-use attribute to instantiate the Profile Sling Model.
    • Render the basic profile information as you have already been doing.
    • For the “Previous Experience” section, use a data-sly-list to iterate over previousExperiences.
    • Inside the loop, render companyName, startDate, and endDate for each Experience object.


<img src="${properties.profilePicture}" alt="Profile Picture">
<h2>${properties.candidateName}</h2>
<p>Date of Birth: ${'dd-MM-yyyy' @ format=properties.dateOfBirth}</p>
<p>Engineering Major: ${properties.engineeringMajor}</p>
<p>Gender: ${properties.gender}</p>

<p>Hobbies Listed Below:</p>
<div data-sly-use.profileModel="com.adobe.aem.guides.wknd.core.models.ProfileModel">
    <!-- Existing fields here -->
    <ul data-sly-list.hobby="${profileModel.hobbies}">
        <li>${hobby}</li>
    </ul>

    <ul data-sly-list.exp="${profileModel.previousExperience}">
        <li>${exp.companyName}</li>
    </ul>
</div>

Step 4: Testing the Component

  1. Deploy the Code: Deploy your changes to AEM.
  2. Open the AEM Authoring Environment: Navigate to a page where the Profile component is used.
  3. Edit the Component: Open the dialog box for the Profile component.
  4. Add Previous Experience Data: Use the multifield to add previous experience details.
  5. Save and Preview: Save the dialog and preview the page to see the rendered output.

Flow of Data from Backend to Frontend

  • Dialog Box: The author enters data in the dialog box fields.
  • Sling Model: When the page is requested, AEM instantiates the Profile Sling Model, injecting the dialog data into the model.
  • HTL Rendering: The HTL script uses the Profile Sling Model to access and render the data on the webpage.
  •