Adobe Articles / Blogs / Perficient https://blogs.perficient.com/category/partners/adobe/ Expert Digital Insights Wed, 22 Jan 2025 17:14:30 +0000 en-US hourly 1 https://blogs.perficient.com/files/favicon-194x194-1-150x150.png Adobe Articles / Blogs / Perficient https://blogs.perficient.com/category/partners/adobe/ 32 32 30508587 Unlocking Sales and Delivery Excellence: Insights from Matt Shields, Managing Director at Perficient https://blogs.perficient.com/2025/01/22/unlocking-sales-and-delivery-excellence-with-perficient-and-adobe/ https://blogs.perficient.com/2025/01/22/unlocking-sales-and-delivery-excellence-with-perficient-and-adobe/#respond Wed, 22 Jan 2025 17:14:30 +0000 https://blogs.perficient.com/?p=376077

In a rapidly evolving digital landscape, staying ahead demands more than just cutting-edge tools; it requires a vision, strategy, and a commitment to excellence. In an exclusive interview for We Are Perficient, Matt Shields, Managing Director of Perficient’s Adobe practice, shared his expert insights on sales and delivery excellence, the transformative potential of Adobe technologies, and the collaborative power of Perficient’s global reach. 

With over 15 years of experience in the Adobe ecosystem, Matt has helped some of the world’s leading brands leverage digital transformations to gain a competitive edge. Here’s what he had to say about the future of sales and technology and how Perficient drives impact across industries. 

The Growing Influence of Adobe in a Content-Driven World 

As the demand for personalized content grows exponentially, Adobe is leading the charge in helping organizations connect with their customers in meaningful ways. According to Matt, the future lies in how companies deliver experiences tailored to each individual’s unique needs, preferences, and cultural contexts. 

“Understanding customers directly—whatever culture, geography, or language—is the key to creating meaningful engagement that drives business objectives,” he explained. 

Adobe’s innovative tools, such as generative AI capabilities within Adobe Experience Cloud, are empowering companies to produce dynamic, high-quality content at scale. This technology is not just about efficiency; it’s about fostering genuine, memorable connections between brands and their audiences. 

Perficient’s Role in Driving Digital Transformation 

Perficient’s partnership with Adobe amplifies the ability to meet the demands of a global market. With a focus on customer experience, Perficient delivers personalized solutions that go beyond traditional content management. 

“It’s no longer just about managing content. It’s about creating it at speed and scale while ensuring it reaches the right audience through the right channels,” Matt emphasized. 

What sets Perficient apart is its global presence and deep industry expertise. By leveraging teams from diverse regions, Perficient offers clients a wealth of knowledge, perspective, and technical capabilities that ensure every project is executed with precision and cultural relevance. 

The Role of Artificial Intelligence in Transforming Experiences 

Artificial Intelligence is reshaping the way businesses engage with customers, and Adobe is at the forefront of this transformation. Matt highlighted how Adobe’s AI tools, such as GenStudio for Performance Marketing, enable companies to generate relevant, impactful content faster and more effectively. 

“AI is a conversation we’re having with every customer,” he noted. “Whether it’s streamlining internal processes or enhancing customer interactions, AI is central to every industry’s digital transformation journey.”

Perficient’s ability to integrate AI within Adobe’s ecosystems ensures clients can harness these innovations to achieve their strategic goals. From content generation to customer engagement, AI is paving the way for smarter, more efficient processes. 

Industry Trends and Perficient’s Global Advantage 

When asked about emerging trends, Matt underscored the importance of creating memorable customer experiences. It’s not just about conversions; it’s about how well a brand resonates with its audience. 

“The question is: How do we connect effectively? Is it the right time, the right content, the right message?”

Perficient’s global footprint plays a crucial role in delivering these impactful experiences. By combining local insights with global expertise, Perficient ensures its clients stay ahead in an increasingly connected world. 

Transforming the Healthcare Sector with Adobe 

In healthcare, customer experience is synonymous with patient experience. Matt shared how Adobe solutions are enabling healthcare organizations to create patient-centric journeys that improve outcomes. 

“From follow-ups to treatment accessibility, driving a better patient experience can literally be a matter of life and death,” Matt explained. 

Perficient’s industry-specific expertise, combined with Adobe’s platform capabilities, is helping healthcare providers deliver care that is not only efficient but also empathetic and personalized. 

The Power of Partnership: Adobe and Perficient 

As a highly specialized Adobe Platinum Partner, Perficient works hand-in-hand with Adobe to deliver the latest technologies and innovations across industries. This partnership is rooted in shared expertise and a commitment to the client. 

“We collaborate daily with Adobe teams across healthcare, manufacturing, financial services, and more to ensure our clients get the best of both worlds—industry expertise and cutting-edge technology,” Matt said. 

Discover More 

Curious about how our partnership with Adobe is transforming industries? Learn how we leverage Adobe’s cutting-edge technologies to deliver personalized, impactful solutions for our clients here.

]]>
https://blogs.perficient.com/2025/01/22/unlocking-sales-and-delivery-excellence-with-perficient-and-adobe/feed/ 0 376077
AEM Front-End Developer: 10 Essential Tips for Beginners https://blogs.perficient.com/2024/12/20/aem-front-end-developer-10-essential-tips-for-beginners/ https://blogs.perficient.com/2024/12/20/aem-front-end-developer-10-essential-tips-for-beginners/#comments Fri, 20 Dec 2024 16:45:31 +0000 https://blogs.perficient.com/?p=373468

Three years ago, I started my journey with Adobe Experience Manager (AEM) and I still remember how overwhelmed I was when I started using it. As a front-end developer, my first task in AEM – implementing responsive design – was no cakewalk and required extensive problem solving. 

In this blog, I share the 10 tips and tricks I’ve learned to help solve problems faced by front-end developers. Whether you’re exploring AEM for the first time or seeking to enhance your skills, these tips will empower you to excel in your role as a front-end developer in the AEM ecosystem. 

1. Get Familiar With AEM Architecture

My first tip is to understand AEM’s architecture early on.   

  • Learn Core Concepts – Before diving into code, familiarize yourself with AEM’s components, templates, client libraries, and the content repository. Learn how each of the components interact and fit in your application. 
  • Sling and JCR (Java Content Repository) – Gain a basic understanding of Apache Sling (the web framework AEM is built on) and how JCR stores content. This foundational knowledge will help you understand how AEM handles requests and manages content. 
  • Get Familiar with CRXDE Lite – CRXDE Lite is a lightweight browser-based development tool that comes out of the box with Adobe Experience Manager.   Using CRXDE Lite developers can access and modify the repository in your local development environments within the browser. You can edit files, folders, nodes, and properties. The entire repository is accessible to you in this easy-to-use interface. Keep in mind that CRXDE offers you the possibility to make instant changes to the website. You can even synchronize these changes with your code base using plugins for the most used most used code editors like Visual Studio Code, Brackets, and Eclipse. 
  • Content Package – An AEM front-end developer needs to work on web pages, but we don’t have to create them from the beginning.  We can use the CRXDE Lite build and download content to share with other developers or bring content from production to local development environments.  

The above points are the basic building blocks that FE developers should be aware of to start with. For more detail read check out the AEM architecture intro on Adobe Experience League.

2. Focus on HTML Template Language (HTL)

AEM uses HTL, which is simpler and more secure than JSP. Start by learning how HTL works, as it’s the main way you’ll handle markup in AEM. It’s similar to other templating languages, so you’ll likely find it easy to grasp.

3. Master Client Libraries (Clientlibs)

Efficient Management of CSS/JS  

AEM uses client libraries (in short clientlibs) to manage and optimize CSS and JavaScript files. So, it’s important to learn how to organize CSS/JS files efficiently as categories and dependencies. This helps load only the required CSS/JS for a webpage to help with page performance.   

Minimize and Bundle

Use Out of the Box Adobe Granite HTML Library Manager (com.adobe.granite.ui.clientlibs.impl.HtmlLibraryManagerImpl) OSGI configuration to minify the CSS/JS that will build small file size for CSS/JS and boost in page load time.  

For more information check out Adobe Experience League.  

4. Leverage AEM’s Component-Based Architecture

Build components with reusability in mind. AEM is heavily component-driven, and your components will be used across different pages. Keeping them modular will allow authors to mix and match them to create new pages. 

5. Use AEM’s Editable Templates

Editable templates are better than static. AEM’s editable templates give content authors control over layout without developer intervention. As a front-end developer, CSS/JS that we build must be independent of templates.  Clientlib related to a UI component should work without any issues on any template-based pages. 

6. Get Familiar with AEM Development Tools

There are multiple development tools that you can find within the most used text editors like Brackets, Visual Studio Code and Eclipse. You should use these extensions to speed up your development process. These tools help you synchronize your local environment with AEM, making it easier to test changes quickly.   

Check out Experience League for more information.  

7. Start With Core Components

AEM comes with a set of core components that cover many basic functionalities such as text, image, carousel. Using the core components as building blocks (extending) to build custom components, saves development time and follows best practices.  For more details check out the following links:  

8. Understand the AEM Content Authoring Experience

Work With Content Authors  

As a front-end developer, it’s important to collaborate closely with content authors. Build components and templates that are intuitive to use and provide helpful options for authors. By doing this, you will gain understanding about how authors use your components and will help you make them more “user friendly” each time. 

Test Authoring

Test the authoring experience frequently to ensure that non-technical users can easily create content. The easier you make the interface the less manual intervention will be required later. 

9. Keep Accessibility in Mind

Accessibility First  

Make sure your components are accessible. AEM is often used by large organizations, where accessibility is key. Implement best practices like proper ARIA roles, semantic HTML, and keyboard navigation support. I have spent some time on different projects enhancing accessibility attributes. So, keep it in mind. 

AEM Accessibility Features  

Leverage AEM’s built-in tools for accessibility testing and ensure all your components meet the required standards (e.g., WCAG 2.1). For more information, you can read the Experience League article on accessibility.  

10. Leverage AEM’s Headless Capabilities

Headless CMS With AEM

Explore how to use AEM as a headless CMS and integrate it with your front end using APIs. This approach is particularly useful if you’re working with modern front-end frameworks like React, Angular, or Vue.js. 

GraphQL in AEM

AEM offers GraphQL support, allowing you to fetch only the data your front end needs. Start experimenting with AEM’s headless features to build SPAs or integrate with other systems. 

SPA Editor

The AEM SPA Editor is a specialized tool in Adobe Experience Manager designed to integrate Single Page Applications (SPAs) into the AEM authoring environment. It enables developers to create SPAs using modern frameworks like React and Angular out of the box while allowing content authors to edit and manage content within AEM, just as they would with traditional templates. Do you remember when I mentioned the developer tools for IDEs? Well, there is one to map your spa application to work with AEM ecosystem.   

More Insights for AEM Front-End Developers

In this blog, we’ve discussed AEM architecture, HTL, Clientlibs, templates, tools, components, authoring, accessibility, and headless CMS as focus areas to help you grow and excel as an AEM developer.  

If you have questions, feel free to drop the comments below. And if you have any tips not mentioned in this blog, feel free to share those as well!  

And make sure to follow our Adobe blog for more Adobe platform insights! 

]]>
https://blogs.perficient.com/2024/12/20/aem-front-end-developer-10-essential-tips-for-beginners/feed/ 1 373468
How Nested Context-Aware Configuration Makes Complex Configuration Easy in AEM https://blogs.perficient.com/2024/12/20/how-nested-context-aware-configuration-makes-complex-configuration-easy-in-aem/ https://blogs.perficient.com/2024/12/20/how-nested-context-aware-configuration-makes-complex-configuration-easy-in-aem/#respond Fri, 20 Dec 2024 16:30:52 +0000 https://blogs.perficient.com/?p=373487

Managing configurations in Adobe Experience Manager (AEM) can be challenging, especially when sharing configs across different websites, regions, or components.  The Context-Aware Configuration (CAC) framework in AEM simplifies configuration management by allowing developers to define and resolve configurations based on the context, such as the content hierarchy. However, as projects scale, configuration needs can become more intricate, involving nested configurations and varying scenarios. 

In this blog, we will explore Nested Context-Aware Configurations and how they provide a scalable solution to handle multi-layered and complex configurations in AEM. We’ll cover use cases, the technical implementation, and best practices for making the most of CAC. 

Understanding Nested Context-Aware Configuration

AEM’s Context-Aware Configuration allows you to create and resolve configurations dynamically, based on the content structure, so that the same configuration can apply differently depending on where in the content tree it is resolved. However, some projects require deeper levels of configurations — not just based on content structure but also different categories within a configuration itself. This is where nested configurations come into play. 

Nested Context-Aware Configuration involves having one or more configurations embedded within another configuration. This setup is especially useful when dealing with hierarchical or multi-dimensional configurations, such as settings that depend on both global and local contexts or component-specific configurations within a broader page configuration. 

You can learn more about basic configuration concepts on Adobe Experience League.

Categorizing Configurations with Nested Contexts

Nested configurations are particularly useful for categorizing configurations based on broad categories like branding, analytics, or permissions, and then nesting more specific configurations within those categories. 

For instance, at the parent level, you could define global categories for analytics tracking, branding, or user permissions. Under each category, you can then have nested configurations for region-specific overrides, such as: 

  • Global Analytics Config: Shared tracking ID for the entire site. 
  • Regional Analytics Config: Override global analytics tracking for specific regions. 
  • Component Analytics Config: Different tracking configurations for components that report analytics separately. 

This structure: 

  • Simplifies management: Reduces redundancy by categorizing configurations and using fallback mechanisms. 
  • Improves organization: Each configuration is neatly categorized and can be inherited from parent configurations when needed. 
  • Enhances scalability: Allows for easy extension and addition of new nested configurations without affecting the entire configuration structure. 

Benefits of Nested Context-Aware Configuration

  1. Scalability: Nested configurations allow you to scale your configuration structure as your project grows, without creating redundant or overlapping settings. 
  2. Granularity: Provides fine-grained control over configurations, enabling you to apply specific settings at various levels (global, regional, component). 
  3. Fallback Mechanism: If a configuration isn’t found at a specific level, AEM automatically falls back to a parent configuration, ensuring that the system has a reliable set of defaults to work with. 
  4. Maintainability: By organizing configurations hierarchically, you simplify maintenance. Changes at the global level automatically apply to lower levels unless explicitly overridden.

Advanced Use Cases

  1. Feature Flag Management: Nested CAC allows you to manage feature flags across different contexts. For example, global feature flags can be overridden by region or component-specific feature flags. 
  2. Personalization: Use nested configurations to manage personalized experiences based on user segments, with global rules falling back to more specific personalization at the regional or page level. 
  3. Localization: Nested CAC can handle localization configurations, enabling you to define language-specific content settings under broader regional or global configurations. 

Implementation

To implement the nested configurations, we need to define configurations for individual modules first. In the example below, we are going to create SiteConfig which will have some configs along with two Nested configs and then Nested config will have its own attributes. 

Let’s define Individual config first. Th they will Look like this: 

@Configuration(label = "Global Site Config", description = "Global Site Context Config.") 
public @interface SiteConfigurations { 
 
    @Property(label = "Parent Config - Property 1", 
            description = "Description for Parent Config Property 1", order = 1) 
    String parentConfigOne(); 
 
    @Property(label = "Parent Config - Property 2", 
            description = "Description for Parent Config Property 2", order = 2) 
    String parentConfigTwo(); 
 
    @Property(label = "Nested Config - One", 
            description = "Description for Nested Config", order = 3) 
    NestedConfigOne NestedConfigOne(); 
 
    @Property(label = "Nested Config - Two", 
            description = "Description for Nested Config", order = 4) 
    NestedConfigTwo[] NestedConfigTwo(); 
 
}

Following with this Nested ConfigOne and NestedConfigTwo will look like this: 

public @interface NestedConfigOne { 
 
    @Property(label = "Nested Config - Property 1", 
            description = "Description for Nested Config Property 1", order = 1) 
    String nestedConfigOne(); 
 
 
    @Property(label = "Nested Config - Property 2", 
            description = "Description for Nested Config Property 2", order = 2) 
    String nestedConfigTwo(); 
 
 
}

And…

public @interface NestedConfigTwo { 
 
    @Property(label = "Nested Config - Boolean Property 1", 
            description = "Description for Nested Config Boolean Property 1", order = 1) 
    String nestedBooleanProperty(); 
 
    @Property(label = "Nested Config - Multi Property 1", 
            description = "Description for Nested Config Multi Property 1", order = 1) 
    String[] nestedMultiProperty(); 
 
}

Note that we didn’t annotate nested configs with Property as this is not the main config.  

Let’s create service to read this and it will look like this:

public interface NestedConfigService { 
    SiteConfigurationModel getAutoRentalConfig(Resource resource); 
}

Implementation of service will be like this: 

@Component(service = NestedConfigService.class, 
        immediate = true) 
@ServiceDescription("Implementation For NestedConfigService") 
public class NestedConfigServiceImpl implements NestedConfigService { 
 
    @Override 
    public SiteConfigurationModel getAutoRentalConfig(Resource resource) { 
        final SiteConfigurations configs = getConfigs(resource); 
        return new SiteConfigurationModel(configs); 
    } 
 
    private SiteConfigurations getConfigs(Resource resource) { 
        return resource.adaptTo(ConfigurationBuilder.class) 
                .name(SiteConfigurations.class.getName()) 
                .as(SiteConfigurations.class); 
    } 
 
}

SiteConfigurationModel will hold the final config including all the configs. We can modify getters based on need. So currently, I am just adding its dummy implementation. 

public class SiteConfigurationModel { 
    public SiteConfigurationModel(SiteConfigurations configs) { 
 
        String parentConfigOne = configs.parentConfigOne(); 
        NestedConfigOne nestedConfigOne = configs.NestedConfigOne(); 
        NestedConfigTwo[] nestedConfigTwos = configs.NestedConfigTwo(); 
        //Construct SiteConfigurationModel As per Need 
 
    } 
}

Once you deploy the code On site config menu in context editor, it should look like :  

AEM Global Site Config

We can see it has given us the ability to configure property 1 and property 2 directly but for Nested one it gave an additional Edit button which will take us to configure the Nested Configs and it will look like this : 

AEM Global Site Config Nested Config One

AEM Global Site Config Nested Config Two

Since Nested config two is multifield it gives the ability to add an additional entry. 

A Powerful Solution to Simplify and Streamline

Nested Context-Aware Configuration in AEM offers a powerful solution for managing complex configurations across global, regional, and component levels. By leveraging nested contexts, you can easily categorize configurations, enforce fallback mechanisms, and scale your configuration management as your project evolves. 

Whether working on a multi-region site, handling diverse user segments, or managing complex components, nested configurations can help you simplify and streamline your configuration structure while maintaining flexibility and scalability. 

Learn More

Make sure to follow our Adobe blog for more Adobe platform insights! 

]]>
https://blogs.perficient.com/2024/12/20/how-nested-context-aware-configuration-makes-complex-configuration-easy-in-aem/feed/ 0 373487
Perficient Named as a Major Player for Worldwide Adobe Experience Cloud Professional Services https://blogs.perficient.com/2024/12/10/perficient-named-as-a-major-player-for-worldwide-adobe-experience-cloud-professional-services/ https://blogs.perficient.com/2024/12/10/perficient-named-as-a-major-player-for-worldwide-adobe-experience-cloud-professional-services/#respond Tue, 10 Dec 2024 16:13:44 +0000 https://blogs.perficient.com/?p=373304

We’re pleased to announce that Perficient has been named a Major Player in the IDC MarketScape: Worldwide Adobe Experience Cloud Professional Services 2024-2025 Vendor Assessment (Doc #US51741024, December 2024). We believe this recognition is a testament to our commitment to excellence and our dedication to delivering top-notch Adobe services to our clients.

Continue reading to learn more about what the IDC MarketScape is, why Perficient is named a Major Player, and what this designation means to our clients.

Understanding This IDC MarketScape

This IDC MarketScape evaluated Adobe Experience Cloud professional service providers, creating a framework to compare vendors’ capabilities and strategies. Many organizations need help planning and deploying technology, and finding the right vendor is critical.

According to Douglas Hayward, senior research director for CX services and strategies at IDC, “Organizations choosing an Adobe Experience Cloud professional service should look for proof that their vendor has high-quality professionals who have a track record in empowering their clients and delivering the best value for the fairest price.”

This IDC MarketScape study provides a comprehensive vendor assessment of the Adobe Experience Cloud professional services ecosystem. It evaluates both quantitative and qualitative characteristics that contribute to success in this market. The study covers various vendors, assessing them against a rigorous framework that highlights the most influential factors for success in both the short and long term.

Perficient is a Major Player

We believe being named a Major Player in the IDC MarketScape is a significant achievement for Perficient and underscores our Adobe Experience Cloud capabilities, industry and technical acumen, global delivery center network, and commitment to quality customer service. We further believe the study is evidence of our expertise and continued focus on solving our clients’ business challenges.

Hayward said, “In our evaluation of Perficient for the IDC MarketScape: Worldwide Adobe Experience Cloud Professional Services 2024-2025 Vendor Assessment, it was evident that Perficient has global delivery expertise that combines an experience design heritage with strong capabilities in digital experience transformation.”

The IDC MarketScape also says, “Based on conversations with Perficient’s clients, the vendor’s three main strengths are value creation, people quality, and client empowerment.”

Our Commitment to Excellence

At Perficient, we are committed to maintaining and improving our services and solutions. We continuously strive to innovate and enhance our capabilities and offerings to meet the evolving needs of our clients, further empower them, and drive value.

Learn More

You can also read our News Release for more details on this recognition and make sure to follow our Adobe blog for more Adobe platform insights!

]]>
https://blogs.perficient.com/2024/12/10/perficient-named-as-a-major-player-for-worldwide-adobe-experience-cloud-professional-services/feed/ 0 373304
Create Content Fragment Variations in AEM With GenAI https://blogs.perficient.com/2024/09/24/create-content-fragment-variations-in-aem-with-genai/ https://blogs.perficient.com/2024/09/24/create-content-fragment-variations-in-aem-with-genai/#respond Tue, 24 Sep 2024 10:50:17 +0000 https://blogs.perficient.com/?p=369459

Earlier this year, Adobe introduced new generative AI capabilities in Adobe Experience Manager (AEM). As a Platinum partner of Adobe, Perficient has early adopter features provisioned for our environments. One of the more exciting and relevant features is the ability to use GenAI to generate variations within Content Fragments in AEM.

In this blog, we’ll talk about a sample use-case scenario, the steps involved with this new feature, and show how it can empower marketers and content authors who spend a lot of time in AEM and make their lives easier. 

The Scenario

In this sample use case, we have contributors who write content for a site called WKND Adventures. We’d like to create a contributor biography to enable an engaging experience for the end user. A biography will further enhance the user experience and increase the chance of content leading to a conversion, such as booking a vacation.  

How to Quickly Create a Content Fragment Variation 

1. Open a Content Fragment for Editing

After logging into AEM as a Cloud Service authoring environment, head over to a Content Fragment and open it up for editing.

Note: If you don’t see the new editor, try selecting the “Try New Editor” button to bring up the latest interface.

AEM as a Cloud Service Content Fragment Editing

As you can see, we still have the standard editing features such as associating images, making rich text edits, and publishing capabilities.

2. Generate Variations

Select the “Generate Variations” button on the top toolbar, and then a new window opens with the Generative Variations interface as seen in the image below.

AEM as a Cloud Service Generative Variations

What’s important to note here is that we are tied to the authoring environment in this interface. So, any variations that are generated will be brought back into our content fragment interface. Although a new prompt can be generated, we’ll start with the Cards option.

Note: There will be more prompt templates created after the writing of this blog.

3. Prompt Templates

The Cards option is pre-filled with some default helper text to provide guidance on a potential prompt and help fine-tune what’s being generated. Providing relevant and concise explanations to the user interaction will also improve the generated results. The interaction can also be explained. The generations can be further enhanced by providing Adobe Target, or a CSV file to further improve the generations. Providing a tone of voice also further defines the variations.

AEMaaCS Generative Variations Prompts

One of our favorite features is the ability to provide a URL for domain knowledge. In this case, we’re going to select a site from Rick Steves on winter escapes as seen in the image below.

AEMaaCS Generative Variations Prompt Url Domain Knowledge

After selecting the appropriate user interaction, tone, temperature intent, and number of variations, we select the “Generate” button.

4. Choose a Content Fragment Variation

Once the variations are created, we can review the results and then choose one to bring back into our Content Fragment editor.

AEMaaCS Generative Variations Selection

After selecting a variation and giving it a name, we can then export that variation. This will create a new variation of that content fragment in AEM.

AEM as a Cloud Service New Content Fragment

Although this is a simple example, many other prompt templates can be used to generate variations that can be used in AEM. Such as creating FAQs, a headline, hero banners, tiles, and more. Additional technical details can be found on Adobe’s GenAI page.

The Exciting Future of Content Creation

Having a direct integration to generate variations from an authoring environment will certainly speed up content creation and allow authors to create relevant and engaging content with the help of GenAI. We look forward to more features and improvements from Adobe in this exciting space, and helping customers adopt the technologies to effectively and safely create content to build exciting experiences.

]]>
https://blogs.perficient.com/2024/09/24/create-content-fragment-variations-in-aem-with-genai/feed/ 0 369459
Running AEM Author, Publisher, and Dispatcher Within Docker https://blogs.perficient.com/2024/09/18/running-aem-author-publisher-and-dispatcher-within-docker/ https://blogs.perficient.com/2024/09/18/running-aem-author-publisher-and-dispatcher-within-docker/#respond Wed, 18 Sep 2024 10:03:16 +0000 https://blogs.perficient.com/?p=369172

About eight years ago, I was introduced to Docker during a meetup at a restaurant with a coworker. He was so engrossed in discussing the Docker engine and containers that he barely touched the hors d’oeuvres. I was skeptical. 

I was familiar with Virtual Machines (VMs) and appreciated the convenience of setting up application servers without worrying about hardware. I wanted to know what advantages Docker could offer that VMs couldn’t. He explained that instead of virtualizing the entire computer, Docker only virtualizes the OS, making containers much slimmer than their VM counterparts. Each container shares the host OS kernel and often binaries and libraries. 

Curious, I wondered how AEM would perform inside Docker—a Java application running within the Java Virtual Machine, inside a Docker container, all on top of a desktop PC. I expected the performance to be terrible. Surprisingly, the performance was comparable to running AEM directly on my desktop PC. In hindsight, this should not have been surprising. The Docker container shared my desktop PC’s kernel, RAM, CPUs, storage, and network allowing the container to behave like a native application. 

I’ve been using Docker for my local AEM development ever since. I love how I can quickly spin up a new author, publish, or dispatch environment whenever I need it and just as easily tear it down. Switching to a new laptop or PC is a breeze — I don’t have to worry about installing the correct version of Java or other dependencies to get AEM up and running. 

In this blog, we’ll discuss running AEM author, publisher, and dispatcher within Docker and the setup process.

Setup Requirements

The AEM SDK, which includes the Quickstart JAR and Dispatcher tools, is necessary for this setup.  Additionally, Apache Maven must be installed. For the Graphical User Interface, we will use Rancher Desktop by SUSE, which operates on top of Docker’s command-line tools.  While the Docker engine itself is open source, Docker Desktop, the GUI distributed by Docker, is not. 

Step One: Installing Rancher Desktop

Download and Install Rancher Desktop by SUSE. Installing Racker Desktop will provide the Docker CLI (command line interface). If you wish to install the Docker CLI without Rancher Desktop, run the following command:

Windows

Install WinGet via the Microsoft store.

winget install --id=Docker.DockerCLI -e

Mac

Install Homebrew: 

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh) 

brew cask install docker

Step Two: Creating the “AEM in Docker” folder

Create a folder named “aem-in-docker”.  Unzip the contents of the AEM SDK into this folder.  Copy your AEM “license.properties” file to this directory. 

Step Three: Creating the subfolders to contain the Docker Image instructions

Make three subfolders within your “aem-in-docker” folder named “base”, “author”, and “publish”. 

Your “aem-in-docker” folder should look something like this: 

AEM-In-Docker Folder

Step Four: Creating the Base Docker Image instruction (Dockerfile)

Create a file named “Dockerfile” within the “base” subdirectory.

Ensure the file does not have an extension.  Set the contents of the file to the following:

FROM ubuntu
# Setting the working directory
WORKDIR /opt/aem
# Copy the license file
COPY license.properties .
# Copy Quickstart jar file
COPY aem-sdk-quickstart-2024.8.17465.20240813T175259Z-240800.jar cq-quickstart.jar
# Install Java, Vim, and Wget.  Install Dynamic Media dependencies.
RUN apt-get update && \
    apt-get install -y curl && \
    apt-get install -y software-properties-common && \
    add-apt-repository ppa:openjdk-r/ppa && \
    apt-get update && \
    apt-get install -y openjdk-11-jdk vim ca-certificates gnupg wget imagemagick ffmpeg fontconfig expat freetype2-demos
# Unack the Jar file
RUN java -jar cq-quickstart.jar -unpack
# Set the LD_LIBRARY_PATH environmental variable
ENV LD_LIBRARY_PATH=/usr/local/lib

This file directs Docker to build a new image using the official Ubuntu image as a base. It specifies the working directory, copies the license file and the quickstart file into the image (note that your quickstart file might have a different name), installs additional packages (like Java, Vim, Wget, and some Dynamic Media dependencies), unpacks the quickstart file, and sets some environment variables.

Step Five: Create the Base Docker Image

Run the following command from within the “aem-in-docker” folder.

docker build -f base/Dockerfile -t aem-base .

It should take a few minutes to run. After the command has been completed run:

docker image ls

You should see your newly created “aem-base” image.

AEM Base Image

Step Six: Creating the Author Docker Image instruction (Dockerfile)

Create a file named “Dockerfile” within the “author” subdirectory.

Set the contents of the file to the following:

# Use the previously created aem-base
FROM aem-base

# Expose AEM author in port 4502 and debug on port 5005
EXPOSE 4502
EXPOSE 5005
VOLUME ["/opt/aem/crx-quickstart/logs"]
# Make the container always start in Author mode with Port 4502.  Add additional switches to support JAVA 11: https://experienceleague.adobe.com/en/docs/experience-manager-65/content/implementing/deploying/deploying/custom-standalone-install.  Add the Dynamic Media runmode.
ENTRYPOINT ["java", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "-XX:+UseParallelGC", "--add-opens=java.desktop/com.sun.imageio.plugins.jpeg=ALL-UNNAMED", "--add-opens=java.base/sun.net.www.protocol.jrt=ALL-UNNAMED", "--add-opens=java.naming/javax.naming.spi=ALL-UNNAMED", "--add-opens=java.xml/com.sun.org.apache.xerces.internal.dom=ALL-UNNAMED", "--add-opens=java.base/java.lang=ALL-UNNAMED", "--add-opens=java.base/jdk.internal.loader=ALL-UNNAMED", "--add-opens=java.base/java.net=ALL-UNNAMED", "-Dnashorn.args=--no-deprecation-warning", "-jar", "cq-quickstart.jar", "-Dsling.run.modes=author,dynamicmedia_scene7", "-p", "4502", "-nointeractive"]

This file instructs Docker to create a new image based on the “aem-base” image. It makes ports 4502 and 5005 available (5005 for debugging purposes), sets up a mount point at “/opt/aem/crx-quickstart/logs”, and specifies the command to run when the image is executed.

Step Seven: Create the Author Docker Image

Run the following command from within the “aem-in-docker” folder.

docker build -f author/Dockerfile -t aem-author .

After the command has been completed run:

docker image ls

You should see your newly created “aem-author” image.

AEM Author Image

Step Eight: Creating the Publisher Docker Image instruction (Dockerfile)

Create a file named “Dockerfile” within the “publish” subdirectory.

Set the contents of the file to the following:

# Use the previously created aem-base
FROM aem-base
# Expose AEM publish in port 4503 and debug on port 5006
EXPOSE 4503
EXPOSE 5006
VOLUME ["/opt/aem/crx-quickstart/logs"]
# Make the container always start in Author mode with Port 4503.  Add additional switches to support JAVA 11: https://experienceleague.adobe.com/en/docs/experience-manager-65/content/implementing/deploying/deploying/custom-standalone-install.  Add the Dynamic Media runmode.
ENTRYPOINT ["java", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5006", "-XX:+UseParallelGC", "--add-opens=java.desktop/com.sun.imageio.plugins.jpeg=ALL-UNNAMED", "--add-opens=java.base/sun.net.www.protocol.jrt=ALL-UNNAMED", "--add-opens=java.naming/javax.naming.spi=ALL-UNNAMED", "--add-opens=java.xml/com.sun.org.apache.xerces.internal.dom=ALL-UNNAMED", "--add-opens=java.base/java.lang=ALL-UNNAMED", "--add-opens=java.base/jdk.internal.loader=ALL-UNNAMED", "--add-opens=java.base/java.net=ALL-UNNAMED", "-Dnashorn.args=--no-deprecation-warning", "-jar", "cq-quickstart.jar", "-Dsling.run.modes=publish,dynamicmedia_scene7", "-p", "4503", "-nointeractive"]

Step Nine: Create the Publisher Docker Image

Run the following command from within the “aem-in-docker” folder.

docker build -f publish/Dockerfile -t aem-publish .

After the command has been completed run:

docker image ls

You should see your newly created “aem-publish” image.

AEM Publish Image

Step Ten: Create the Adobe Network

Let’s set up a network to connect Docker containers and facilitate data sharing between them.

docker network create adobe

Step Eleven: Run the Author Docker Image

It’s time to run our Author Docker Image. First, create a local directory for the logs volume specified in the Dockerfile. Within the author subdirectory, create a directory named “logs.” Run the following command within the new logs folder:

Windows

docker run -d --name author -p 4502:4502 -p 5005:5005 --network adobe -v ${PWD}:/opt/aem/crx-quickstart/logs aem-author

macOS/Linux

docker run -d --name author -p 4502:4502 -p 5005:5005 --network adobe -v `pwd`:/opt/aem/crx-quickstart/logs aem-author

The command will return the ID of the new Docker container. It may take some time for the new AEM instance to start. To check its status, you can monitor the “error.log” file in the logs directory to check its status.

Windows

Get-Content -Path .\error.log -Wait

macOS/Linux

tail -f error.log

After AEM has finished starting up, check that everything is loading correctly by visiting: http://localhost:4502/aem/start.html.

Let’s stop the AEM container for the time being:

docker stop author

Step Twelve: Run the Publisher Docker Image

It’s time to run our Publisher Docker Image.  First, create a local directory for the logs volume specified in the Dockerfile. Within the publish subdirectory, create a directory named “logs.”  Run the following command within the new logs folder:

Windows

docker run -d --name publish -p 4503:4503 -p 5006:5006 --network adobe -v ${PWD}:/opt/aem/crx-quickstart/logs aem-publish

macOS/Linux

docker run -d --name publish -p 4503:4503 -p 5006:5006 --network adobe -v `pwd`:/opt/aem/crx-quickstart/logs aem-publish

The command will return the ID of the new Docker container. It may take some time for the new AEM instance to start. To check its status, you can monitor the “error.log” file in the logs directory to check its status.

Windows

Get-Content -Path .\error.log -Wait

macOS/Linux

tail -f error.log

After AEM has finished starting up, check that everything is loading correctly by visiting: http://localhost:4503/content.html.  You will see a “Not Found” page.  That is fine for now.

Let’s stop the AEM container for the time being:

docker stop publish

Step Thirteen: Start the Containers via Rancher Desktop

Open Rancher Desktop and go to the Containers tab in the left navigation pane. To start individual containers, check the box in the State column for each container you want to start, then click the Start button. To start all containers at once, check the box in the header row of the State column, and then click the Start button. Let’s go ahead and start all containers.

If you prefer using the command line, you can run:

docker start author
docker start publish

Containers Via Rancher Desktop

Step Fourteen: Create an AEM Project and install it on the Author and Publish instance

Since Docker’s mascot is a whale, I thought it would be fun to name our new AEM project after a famous fictional whale: Monstro from Pinocchio.

Run the following command from a command line (Note: you may have to run this command with elevated privileges):

mvn -B archetype:generate -D archetypeGroupId=com.adobe.aem -D archetypeArtifactId=aem-project-archetype -D archetypeVersion=50 -D aemVersion=cloud -D appTitle="Monstro" -D appId="monstro" -D groupId="com.monstro" -D frontendModule=general -D includeExamples=n

Once this project has been created, let us build and deploy it to our Author instance.

Run the following command from within the “Monstro” project:

mvn clean install -PautoInstallSinglePackage

Check that the project is installed by visiting the following URL to view the results: http://localhost:4502/editor.html/content/monstro/us/en.html.  You should see the following:

Project Monstro

Now, let us build and deploy the project to our Publish instance.

Run the following command from within the “Monstro” project:

mvn clean install -PautoInstallSinglePackagePublish

Verify that the project is installed by visiting this URL: http://localhost:4503/content/monstro/us/en.html.  Installation may take up to five minutes. After this period, you should see the following:

Post Installation Project Monstro

Step Fifteen: Set up the Publish Agent on Author

It’s time to configure the publish agent on our author instance. Go to this URL: http://localhost:4502/etc/replication/agents.author/publish.html.

Click the “Edit” button (next to settings).

Publish Agent On Author Setup

  • Click the checkbox next to “Enabled”
  • Enter “admin” in the “Agent User Id” field
  • Navigate to the Transport tab and enter the following in the URI field:  http://publish:4503/bin/receive?sling:authRequestLogin=1
  • Instead of using “localhost,” the hostname for our publish instance is our container’s name, “publish”
  • In the “username” field, enter “admin,” and in the “password” field, enter the admin’s password
  • Click the “OK” button to save the Agent settings
  • Click the “Test Connection” link, and the replication test should be successful

Step Sixteen: Publish content from the Author

Go back to http://localhost:4502/editor.html/content/monstro/us/en.html. Edit the “Hello, World” component by changing the text from “lalala :)” to “Monstro is the enormous, fearsome whale from Disney’s 1940 animated film Pinocchio.” Verify the update and publish the page. Then, check http://localhost:4503/content/monstro/us/en.html to see your changes on the Publisher as well.

Step Seventeen: Create the Dispatcher Container

Make sure the publisher instance is running before proceeding. Extract the AEM SDK Dispatcher tools.

Windows

Expand-Archive .\aem-sdk-dispatcher-tools-2.0.222-windows.zip
Rename-Item -Path .\aem-sdk-dispatcher-tools-2.0.222-windows -NewName dispatcher-sdk-2.0.222

macOS/Linux

chmod +x ./aem-sdk-dispatcher-tools-2.0.222-unix.sh
./aem-sdk-dispatcher-tools-2.0.222-unix.sh

Since we’ve set up a custom network for our AEM containers, the docker run script won’t function correctly because it doesn’t recognize this network. Let’s modify the docker run script.

Windows

Open “dispatcher-sdk-2.0.222\bin\docker_run.cmd” in your favorite editor.

Add the “–network adobe” argument to the docker command inside the “else” statement.

Modify Docker Run Script For Windows

macOS/Linux

Open “dispatcher-sdk-2.0.222/bin/docker_run.sh” in your favorite editor.

Add the “–network adobe” argument to the docker command inside the “else” statement.

Modify Docker Run Script For macOS/Linux

Execute the docker run script with the following parameters. Be sure to replace the dispatcher source path with the path to your “monstro” source.

Windows

.\ dispatcher-sdk-2.0.222\bin\docker_run.cmd C:\Users\shann\Sites\monstro\dispatcher\src publish:4503 8080

macOS/Linux

./dispatcher-sdk-2.0.222/bin/docker_run.sh ~/Sites/monstro/dispatcher/src publish:4503 8080

Once the text stream in your terminal has stopped, go to http://localhost:8080/.  You should see the following:

Dispatch Container For Project Monstro

Open Rancher Desktop and navigate to the Containers tab. Locate the container with an unusual name. If you stop this container, it won’t be possible to start it again. Please go ahead and stop this container. The dispatcher code running in your terminal will also terminate. We want this container to be more permanent, so let’s make some additional changes to the docker run script.

Creating A Permanent Container For Project Monstro

Windows

Open “dispatcher-sdk-2.0.222\bin\docker_run.cmd” in your favorite editor.

macOS/Linux

Open “dispatcher-sdk-2.0.222/bin/docker_run.sh” in your favorite editor.

Add the “–name dispatcher” argument to the “docker” command within the “else” statement. Also, remove the “–rm” switch. According to Docker documentation, the “–rm” switch automatically removes the container and its associated anonymous volumes when it exits, which is not what we want.

Windows

Modify Docker Run Script For Windows 2

macOS/Linux

Modify Docker Run Script For Macos Linux 2

Run the docker run command in your terminal again:

Windows

.\ dispatcher-sdk-2.0.222\bin\docker_run.cmd C:\Users\shann\Sites\monstro\dispatcher\src publish:4503 8080

macOS/Linux

./dispatcher-sdk-2.0.222/bin/docker_run.sh ~/Sites/monstro/dispatcher/src publish:4503 8080

Open Rancher Desktop and go to the Containers tab. You should see a container named “dispatcher.” Stop this container. The dispatcher code running in your terminal will terminate, but the container will remain in Rancher Desktop. You can now stop and restart this container as many times as you’d like. You can also start and stop the dispatcher via the command line:

docker start dispatcher
docker stop dispatcher

Docker Provides Value and Flexibility

We have an author and publisher AEM instance running inside a Docker container. Additionally, we have a dispatcher container created using the source from the Monstro project. Although this dispatcher container isn’t very useful, the advantage of Docker is that you can easily delete and create new containers as needed.

I hope you found this blog helpful. I’ve been using Docker on my local machine for the past eight years and value the flexibility it provides. I can’t imagine going back to managing a local AEM instance or dealing with Apache configurations to get the dispatcher working. Those days are behind me.

]]>
https://blogs.perficient.com/2024/09/18/running-aem-author-publisher-and-dispatcher-within-docker/feed/ 0 369172
Generative AI: The 3 Things No One is Telling You About https://blogs.perficient.com/2024/09/12/generative-ai-the-3-things-no-one-is-telling-you-about/ https://blogs.perficient.com/2024/09/12/generative-ai-the-3-things-no-one-is-telling-you-about/#respond Thu, 12 Sep 2024 19:32:18 +0000 https://blogs.perficient.com/?p=369055

Generative AI or, as I prefer to call it, generative machine learning (ML) has taken the business world by storm. By now you’ve likely encountered an email or some form of marketing generated by one of these models, perhaps without even realizing it. They’re powerful tools and I look forward to the improvements we can bring to people’s lives. But I also think it’s essential to understand their limitations, lest you end up with some embarrassing results.

So let’s review how some popular un-customized models handle text and image prompts and the 3 things no one is talking about when it comes to generative AI. 

1. Artificial Intelligence is a Misnomer

Though artificial intelligence is useful as a marketing term, intelligence is more of the end goal for many researchers rather than an accurate assessment of where the technology is right now. Intelligence implies many attributes including reflection, judgment, initiative, and wonder. That field of study is referred to as Artificial General Intelligence (AGI) and remains outside of the scope of the most popular models.  This distinction becomes only more evident as you feed complex scenarios and requirements into the currently available models.  Remember that most of the time these tools are backed by a predictive model based on training data rather than logically assessing an ask against a series of rules like traditional algorithms.

If you introduce it to prompts or situations where it does not have training, it may have trouble producing a realistic or accurate result.  For example, I have given a popular generative model the following prompt ‘Owl breathing in space.’   

Owl Breathing In Space

It’s a nice picture, but I’m afraid the owl won’t be breathing any time soon…

We as humans easily recognize a series of conceptual rules about a subject (e.g. owls need air to breathe) but this model has no such teaching yet.  That’s not to say that these models won’t reach a basic level of these attributes of intelligence someday but to be careful to understand this limitation in the meantime.

Fortunately, we can get to our desired result by specifying some additional instructions: “Owl breathing in space, wearing an astronaut suit.”

Owl Breathing In Space With Space Suit

2. Deep Learning Models Aren’t Always Trustworthy

Deep Learning Models typically present information with the same projected degree of confidence, even if it is completely incorrect or contradictory to something it said previously. Remember it’s a predictive language model, not a conscious actor or trustworthy source.​

Deep Learning Models Are Not Always A Trustworthy Source

It’s a very important distinction!  The model outputs the information exactly in reverse of what it should have stated.

To help mitigate this issue, we can instruct the model to pull answers only from an approved set of answers like in the image below.

Ask Your Model To Pull Approved Answers Example

However, this approach often isn’t sufficient. We can easily end up with the model filling in some gaps using language that is outside of the strictly approved set as seen in the example below.

Example Of A Gen Ai Model Breaking Rules

While the model tried to answer all the questions, it broke the rule we provided at the start of the session.

That’s where Perficient’s policies and governance frameworks come in. We can implement a procedural algorithm that sits between the raw output of the AI model and any user requests.  This management layer ensures strict output compliance and rejects any attempt by the model to provide an answer outside the exact language in the approved set. It’s much more in line with what we’re looking for!

3. Be Wary of Partner-Owned Models

While partner-owned models may have a high ROI if you do not have an existing ML practice, you are limiting your business’ potential by relying on partner closed-source models. For instance, closed-source models are often built for broad use cases rather than fine-tuned to your products or services.

Generative AI Answers How To Program Aem To Automatically Refresh The Page

While options #1 and #2 in the image above are technically feasible, the much simpler afterinsert EditConfig solution exists and is in better alignment with Adobe’s best practices. Notably, option #2 is a very complex solution I would never recommend anyone implement and #3 doesn’t answer the question at all. I would not trust this model to answer questions about AEM consistently and correctly without a lot more training. There’s no easy way around this yet, so make sure you always verify what the model is telling you against a trustworthy source.

So if you’re going to have to eventually spend a lot of time training a third-party model, why not invest in your own model? You don’t have to do it alone, as Perficient has a comprehensive artificial intelligence practice that can help guide you from the ground up.

Thinking Critically About Generative AI Safeguards

I hope these considerations were helpful and have led you to think about how to put safeguards in place when using generative AI. Until next time!

For more information on how Perficient can implement your dream digital experiences, contact us! We’d love to hear from you.

Contact Perficient to start your journey.

]]>
https://blogs.perficient.com/2024/09/12/generative-ai-the-3-things-no-one-is-telling-you-about/feed/ 0 369055
AEM OSGi Services: Take Advantage of Strategy Design Pattern https://blogs.perficient.com/2024/09/09/aem-osgi-services-take-advantage-of-strategy-design-pattern/ https://blogs.perficient.com/2024/09/09/aem-osgi-services-take-advantage-of-strategy-design-pattern/#respond Mon, 09 Sep 2024 10:00:41 +0000 https://blogs.perficient.com/?p=368773

Design patterns are pivotal in crafting application solutions that are both maintainable and scalable. The Strategy Design Pattern is ideal for scenarios that require runtime selection among various available algorithms. In this blog, we’ll cover how to implement the Strategy Design Pattern in an AEM OSGi service, boosting your code’s flexibility and manageability. 

What is the Strategy Design Pattern?

The Strategy Design Pattern comes under the category of behavioral design pattern which defines a family of algorithms, encapsulates each one, and makes them interchangeable. This pattern allows the algorithm to vary independently from the clients that use it. It’s particularly useful for scenarios where multiple methods can be applied to achieve a specific goal, and the method to be used can be selected at runtime. 

You can read more about Strategy Pattern in general here. 

UML Diagram of the Strategy Design Pattern

Here’s a UML diagram that illustrates the Strategy Design Pattern: 

UML Diagram of The Strategy Design Pattern

  • Strategy: The interface that defines the common behavior for all strategies.
  •  ConreteImplementation1, ConreteImplementation2, ConreteImplementation3: Concrete implementations of the Strategy interface.
  •  Context: Maintains a reference to a Strategy object and uses it to execute the strategy.

Are There Use Cases in AEM

So, is there any scenario where we can use it in AEM? Yes! There are many AEM solution use cases where we have multiple strategies and the proper strategy is chosen at run time. We generally use conditional statements and switch statements in such scenarios. Here are a few examples where Strategy Pattern can be applied. 

  • Template Specific Behaviors: Different strategies for a component when it has different behavior according to different templates. 
  • Content Transformation: Different strategies for transforming HTML, JSON, or XML content. 
  • Brand-specific behaviors:  Different strategies for individual brand sites in multitenant environments when they share certain common code base and scenarios. 
  • Channel Specific implementations: Different channels for sending notifications such as email, and web. 

Benefits of Using the Strategy Design Pattern

Implementing the Strategy Design Pattern in AEM OSGi services offers numerous benefits, aligning well with SOLID principles and other best practices in software development. 

Six Benefits 

  1. Adaptability and Scalability: The Strategy Design Pattern facilitates the addition of new algorithms without altering the existing code, enhancing the system’s extensibility. When a new algorithm is required, it can be seamlessly integrated by creating a new strategy class, thereby leaving the context and other strategies unchanged. 
  2. Adherence to SOLID Principles: The Strategy Design Pattern aligns with SOLID principles by ensuring each strategy class adheres to the Single Responsibility Principle (SRP), encapsulating a specific algorithm for easier comprehension, testing, and maintenance. It supports the Open/Closed Principle (OCP) by allowing new strategies to be added without modifying existing code. The pattern ensures compliance with the Liskov Substitution Principle (LSP), enabling interchangeable use of strategy implementations without altering functionality. It adheres to the Interface Segregation Principle (ISP) by having clients interact with the strategy interface, thus reducing unnecessary dependencies. Lastly, it upholds the Dependency Inversion Principle (DIP) by making the context depend on abstractions rather than concrete implementations, promoting flexible and decoupled code. 
  3. Ease of Testing: Since each strategy is a separate implementation and does not directly depend on other implementations, it can be unit-tested independently of other strategies and the context. This leads to more manageable and comprehensible tests. 
  4. Code Reusability: If needed Strategies can be reused across different parts of the application or even in different projects. This reduces redundancy and encourages the reuse of well-tested code. 
  5. Maintainability: The clear separation of different algorithms into their own classes means that any changes or additions to the algorithms do not impact the rest of the codebase. This makes the system more maintainable. 
  6. Better Code Readability: By encapsulating the logic of each algorithm in its own implementation class, the Strategy Design Pattern makes the code more readable. Developers can quickly understand the purpose of each strategy by looking at the strategy interface and its implementations. 

Typical Strategy Implementation for AEM 

In AEM, we can utilize regular classes for strategy implementation, but with OSGi, we get a better experience of handling strategies. A typical implementation of the Strategy Design Pattern in AEM will consist of: 

  • Strategy Context: Holds all the strategies for the client. 
  • Strategy Interface: Provides a generic interface for different strategies. 
  • OSGi Implementations for Strategies: Contains the logic for individual algorithms for each strategy. 

Implementation Steps

Step 1: Define the Strategy Interface 

First, create a Java interface that defines the common behavior for all strategies. 

public interface StrategyService { 
 
 
    /** 
     * Name of the strategy. 
     * @return Strategy name. 
     */ 
    String getName(); 
 
 
    /** 
     * Executes the strategy based on criteria. 
     * @param strategyBean Dummy Strategy bean object consists of the fields required for strategy execution. 
     * @return Strategy Result bean. You can replace it with result object according to your need. 
     */ 
    StrategyResult execute(StrategyBean strategyBean); 
 
 
    /** 
     * Method for executing isFit operation if given strategy is fit for scenario. 
     * @param strategyBean Strategy Object consisting of params needed for strategy is fit operation. 
     * @return true if given strategy fits the criteria. 
     */ 
    boolean isFit(StrategyBean strategyBean); 
}

Comments are self-explanatory here but let’s still deep dive into what each method intends to do. 

String getName() 

Name of the strategy. The name should be self-explanatory. So, when someone sneaks into the code, they should come to know for what scenario a strategy is aiming for. 

StrategyResult execute(StrategyBean strategyBean);

This method executes the logic for the operation required at runtime. StrategyResult Bean represented here is just for reference. You can update the same according to your operation. 

StrategyBean consists of params needed for executing the operation and again this is also just for reference you can execute the same with your own object. 

boolean isFit(StrategyBean strategyBean); 

This method is where decisions will be made if the given strategy is fit for the scenario or not. StrategyBean will provide the necessary parameters to execute the operation. 

Step 2: Implement Concrete Strategies

Create multiple implementations of the StrategyService interface. Each class will implement the methods differently. 

Here for reference, let’s create two concrete implementations for StrategyService namely FirstStrategyServiceImpl and SecondStrategyServiceImpl 

@Component(service = StrategyService.class, immediate = true, property = { 
        "strategy.type=FirstStrategy" 
}) 
@Slf4j 
public class FirstStrategyServiceImpl implements StrategyService { 
 
    @Override 
    public String getName() { 
        return "FirstStrategy"; 
    } 
 
    @Override 
    public StrategyResult execute(StrategyBean strategyBean) { 
        log.info("Executing First Strategy Service Implementation...."); 
        //Implement Logic 
        return new StrategyResult(); 
 
    } 
 
    @Override 
    public boolean isFit(StrategyBean strategyBean) { 
        //Implement Logic 
        return new Random().nextBoolean(); 
    } 
 
}

And our Second Service implementation here will look like this:

@Component(service = StrategyService.class, immediate = true, property = { 
        "strategy.type=SecondStrategy" 
}) 
@Slf4j 
public class SecondStrategyServiceImpl implements StrategyService { 
 
    @Override 
    public String getName() { 
        return "SecondStrategy"; 
    } 
 
    @Override 
    public StrategyResult execute(StrategyBean strategyBean) { 
        log.info("Executing Second Strategy Service Implementation...."); 
        //Implement Logic 
        return new StrategyResult(); 
    } 
 
    @Override 
    public boolean isFit(StrategyBean strategyBean) { 
        //Implement Logic 
        return new Random().nextBoolean(); 
    } 
}

Here, we do not have any logic. We’re just giving dummy implementations. You can add your operational logic in execute and isFit method. 

Step 3: Implement Context Service Class 

Our context service implementation will hold all the strategies with dynamic binding and will look like this. 

 
@Component(service = StrategyContextService.class, immediate = true) 
@Slf4j 
public class StrategyContextServiceImpl implements StrategyContextService { 
 
    private final Map<String, StrategyService> strategies = new HashMap<>(); 
 
    @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) 
    protected void bindStrategy(final StrategyService strategy) { 
        strategies.put(strategy.getName(), strategy); 
    } 
 
    protected void unbindStrategy(final StrategyService strategy) { 
        strategies.remove(strategy.getName()); 
    } 
 
 
    public Map<String, StrategyService> getStrategies() { 
        return strategies; 
    } 
 
    @Override 
    public StrategyService getApplicableStrategy(final StrategyBean strategyBean) { 
        return strategies 
                .values() 
                .stream() 
                .filter(strategyService -> strategyService.isFit(strategyBean)) 
                .findFirst() 
                .orElse(null); 
 
    } 
 
    @Override 
    public StrategyResult executeApplicableStrategy(final StrategyBean strategyBean) { 
        final var strategyService = getApplicableStrategy(strategyBean); 
        if (strategyService != null) { 
            return strategyService.execute(strategyBean); 
        } else { 
            // Run Default Strategy Or Update Logic Accordingly 
            return strategies.get("default").execute(strategyBean); 
        } 
    } 
 
    @Override 
    public Collection<StrategyService> getAvailableStrategies() { 
        return strategies.values(); 
    } 
 
    @Override 
    public Map<String, StrategyService> getAvailableStrategiesMap() { 
        return strategies; 
    } 
}

Here you can see we are dynamically binding instances of StrategyService implementations. So, we don’t need to worry about change in this context class whenever new Strategy is implemented. The context here will dynamically bind the same and update available strategies. 

We are providing methods here to get the strategy, execute the strategies, and get all the available strategies. 

The advantage we have is we don’t need to worry about changes needed for additional strategies. When a new strategy is needed, we just need its implementation class and don’t need to worry about its impact on others. If isFit logic is not causing conflict with others we don’t need to worry about other services. This isolation gives us confidence for complex solutions as we have the flexibility here to not worry about the impact of newly added functionalities. 

Step 4: Using this implementation 

Since this is OSGi implementation you can use this in any OSGi Services, Servlet, Workflows, Schedulers, Sling Jobs, Sling Models, etc. 

You can either use this directly or you can create a wrapper service around this if any further post-processing of strategy results is needed. 

Our example consumer service and sling model will look like this: 

@Component(service = StrategyConsumer.class, immediate = true) 
public class StrategyConsumer { 
 
    @Reference 
    private StrategyContextService strategyContext; 
 
    public StrategyResult performTask() { 
        final StrategyBean strategyBean = new StrategyBean(); 
        //Set strategy object based on data 
 
        StrategyResult strategyResult = strategyContext.executeApplicableStrategy(strategyBean); 
        //Do Post Processing 
        return  strategyResult; 
    } 
 
 
    public StrategyResult performTask(final StrategyBean strategyBean) { 
        StrategyResult strategyResult = strategyContext.executeApplicableStrategy(strategyBean); 
        //Do Post Processing 
        return  strategyResult; 
    } 
}

The method can be updated to accommodate logical operation for the addition of data into strategy bean if needed. 

Sample sling model which will use consumer service or directly connect with context service will look like this: 

@Model(adaptables = SlingHttpServletRequest.class) 
public class StrategyConsumerModel { 
    @OSGiService 
    private StrategyConsumer strategyConsumer; 
 
    @PostConstruct 
    public void init(){ 
        StrategyResult strategyResult = strategyConsumer.performTask(); 
        //do processing if needed 
 
        // OR 
        StrategyBean strategyBean = new StrategyBean(); 
        strategyResult = strategyConsumer.performTask(strategyBean); 
 
    } 
}

And you are all set now.  

This was just a dummy implementation, but you can twist it according to your business needs. 

In the introduction, use cases have already been mentioned. You can use this in implementing such scenarios. Additional use cases can be any task where you need to select implementation dynamically at run time based on scenarios.  

Final Thoughts

With the above implementation, we can see that The Strategy Design pattern is well suited for managing and extending complex algorithms in a clean and modular way which also adheres to best practices including SOLID principles. It promotes flexibility, scalability, maintainability, and ease of  testing. As you develop AEM solutions consider incorporating this, it will significantly improve code quality and reusability ensuring that evolving business requirements can be accommodated easily. 

 

Learn more about AEM developer tips and tricks by reading our Adobe blog!

]]>
https://blogs.perficient.com/2024/09/09/aem-osgi-services-take-advantage-of-strategy-design-pattern/feed/ 0 368773
Edge Delivery Services Tips and Tricks for Developers https://blogs.perficient.com/2024/09/03/edge-delivery-services-tips-and-tricks-for-developers/ https://blogs.perficient.com/2024/09/03/edge-delivery-services-tips-and-tricks-for-developers/#respond Tue, 03 Sep 2024 11:27:58 +0000 https://blogs.perficient.com/?p=368351

Edge Delivery Services empowers developers and authors to create lightning-fast, high-performing digital experiences that score a perfect 100% on Google’s Lighthouse metrics. With Edge Delivery, you can choose from two flexible implementation options – document-based authoring and WYSIWYG (What You See Is What You Get) – to streamline your development workflow and accelerate content authoring. 

Edge Delivery Services Setup

This blog will focus on document-based authoring, also known as Project Franklin, Helix, or AEM (Adobe Experience Manager) Live. To unlock the full potential of Edge Delivery Services and get started with local development, please refer to Adobe’s comprehensive guide for setting up Edge Delivery Services locally.

With this foundation, we will discuss the developer’s insights on document-based authoring in the subsequent section. 

Tips and Tricks

Be a Lighthouse Keeper

The overarching goal is to maintain a perfect Lighthouse score throughout the development process. To achieve this, regularly monitor the score at key milestones, such as the completion of each feature or block. Referring to the diagnostics and addressing suggested areas of improvement will help you maximize your score.

For instance, look at the items listed in the image below. By keeping a close eye on Lighthouse performance, you will significantly reduce the number of issues to address, resulting in a much shorter list. Regular monitoring helps identify and resolve problems early on, streamlining your development process.

Adobe Edge Delivery Services Diagnostics

Example of Google’s Lighthouse Diagnostics for a website.

Additionally, by adopting a mobile-first development approach, you can minimize rework and ensure a seamless user experience across all devices. This proactive strategy will eventually save time and effort.

Best Practices for Accurate Lighthouse Score Testing

When testing Lighthouse scores during local development, it’s essential to minimize external factors that can impact your results. Only enable necessary browser extensions, as they can negatively affect your score. If you’re unable to disable extensions, consider using the command line interface instead. To get started, refer to the steps below.

  1. Install Lighthouse: Run the following command to install Lighthouse globally on your machine:
    npm install -g lighthouse
  2. Run Lighthouse: Check your score using the following command:
    lighthouse <url>
  3. Customize breakpoints (optional): To test specific breakpoints, pass options using the following command:
    lighthouse <url> --view --chrome-flags="--window-size=<windowWidth, windowHeight>"

    Replace <url> with the URL you want to test, and <windowWidth> and <windowHeight> with your desired dimensions.

Leveraging the Boilerplate Code for Enhanced Performance

Edge Delivery Services’ boilerplate code offers a range of functions designed to optimize code performance. To explore these functions and gain a deeper understanding of the code, review the scripts in aem.js and script.js. These scripts provide out-of-the-box (OOTB) functions that can significantly enhance your development workflow.

One of the noteworthy functions is the createOptimizedPicture(), which optimizes image delivery. Additionally, the buildAutoBlocks() function is a valuable tool for autoblocking, particularly on blog post pages or when creating common components for pages using the same template. This function can serve as an inspiration for your own custom solutions.

Naming Conventions for Blocks

To ensure seamless rendering, verify that the block name on the document matches the corresponding block folder and file names (CSS/JS) under the /blocks directory. For instance, if the block is named “social-media-links“, the folder structure should mirror this naming convention.

Edge Delivery Naming Conventions For Blocks

Example block folder structure.

On the document, the block can be referenced using the following variations, all of which will render the above-sown block successfully:

  • social media links
  • Social Media Links
  • social-media-links
  • Social-Media-Links
Edge Delivery Block Authored In A Table Example

Example block authored in a table.

Note that while the naming convention is case-insensitive, it is sensitive to camelCase. Therefore, using “socialMediaLinks” will not render the block correctly. Maintaining consistent naming conventions ensures efficient block rendering and avoids potential issues.

Checkout the OOTB Block collection

Before building a new block from scratch, take a moment to explore the out-of-the-box (OOTB) options available with Edge Delivery Services. The Edge Delivery Services offers a comprehensive array of block examples that can serve as a foundation for your project, saving you time and effort. Although the OOTB blocks may seem comparable to core components, they serve a distinct purpose. Unlike core components, the block collection from the boilerplate code cannot be extended, and no updates from Adobe should be expected. Developers are free to utilize or repurpose any block from the collection or those found in the boilerplate code. By leveraging the OOTB collection, you can accelerate your development process and avoid reinventing the wheel.

Overcome New Edge Delivery Services Obstacles

By incorporating these tips and tricks into your Edge Delivery Services-based development routine, you’ll be able to overcome common obstacles faced by a new Edge Delivery developer. Remember, the key to success lies in continuous learning, adaptability, and a willingness to explore new approaches.

Stay curious!

More Edge Delivery Services Content!

New From Adobe: Next-Gen Composability

Adobe Edge Delivery Services POC

]]>
https://blogs.perficient.com/2024/09/03/edge-delivery-services-tips-and-tricks-for-developers/feed/ 0 368351
4 Ways Perficient’s Adobe Practice is Forging the Future So Far in 2024 https://blogs.perficient.com/2024/08/21/4-ways-perficients-adobe-practice-is-forging-the-future-so-far-in-2024/ https://blogs.perficient.com/2024/08/21/4-ways-perficients-adobe-practice-is-forging-the-future-so-far-in-2024/#respond Wed, 21 Aug 2024 14:36:15 +0000 https://blogs.perficient.com/?p=364615

Perficient’s people are the key to our success and make our organization the unique, dynamic, and innovative workplace it is today. This is the first blog in our Business Unit Spotlight series where we will share colleagues’ perspectives on leading digital change, driving real results for our clients, and collaborating with the best and brightest across the globe.

Perficient’s Adobe practice is comprised of more than 700 dedicated Adobe-focused consultants, accompanied by more than 50 skilled Adobe architects. We have a powerhouse of certified talent across Perficient who are shattering boundaries and forging the future. Continue reading as our colleagues share their experience at Perficient and the amazing achievements accomplished so far this year.

1. Perficient is an Award-Winning, Highly Specialized Adobe Partner 

Perficient has been honored with a number of awards for our work on the Adobe platform, including the 2023 Adobe Digital Experience Emerging Partner of the Year, Americas. This year, we earned our seventh Adobe specialization! Our latest specialization in Customer Journey Analytics strengthens our position as a leading Adobe partner and demonstrates our expertise in empowering clients to make informed decisions that drive business growth. Perficient is recognized by Adobe and our clients for our ability to deliver incredible digital experiences.

Santhosh Nair

“We are proud to be recognized time and time again for our distinguished Adobe work,” said Santhosh Nair, senior vice president. “These recognitions are a testament to the deep expertise, growth, and commitment of our entire Adobe practice. We are focused on building and delivering Adobe-centric offerings that connect brands with their customers, and our team takes great pride in the value we provide for our clients.”

From strategy to implementation, Perficient’s team of experts are building exceptional experiences on the Adobe platform that are delighting our clients and their customers.

Grace Siwicki

“I think it’s great that Perficient has received these honors from Adobe as they reflect how well aligned we are with Adobe, ensuring we have the best possible solutions for our clients,” said Grace Siwicki, Adobe platform director. “It’s also indicative of our level of experience and competency within the Adobe ecosystem that enables us to be subject matter experts. I believe that Adobe will continue to be a leader in the industry, which positions Perficient well in our strong partnership and allows us to continue to grow our business and talent.”

2. Showcasing Digital Experience at Adobe Summit 2024 

Perficient had a significant presence at this year’s Adobe Summit conference, including hosting three industry focused events, two client success story presentations, and co-hosting the Women in Digital Breakfast. Our colleagues had the opportunity to connect with clients and other experts in the space to stay on the leading edge of technology. Robert Sumner Photo

“We had a great turnout for the automotive and manufacturing breakfast,” said Robert Sumner, principal. “I had the opportunity to speak alongside Adobe’s head of industry strategy about how to modernize, innovate, and win in an increasingly competitive automotive and mobility market.” 

Watch Now: Robert Sumner Discusses the Impact of GenAI on the Automotive and Manufacturing Industries 

The Women in Digital event at Adobe Summit has become an honored tradition for Perficient as it features a panel of influential women leaders facilitating conversations on professional growth, emerging industry trends, and other key topics. 

Meghan Frederick“The Women in Digital event is an opportunity for female leaders to connect, share advice, and learn from their peers all centered around women empowerment,” said Meghan Frederick, senior marketing manager. “Perficient has partnered with Adobe to host Women in Digital panel discussions since 2017, making it the longest running event at Adobe Summit.”

3. Perficient Included in Forrester’s Adobe Services Landscape, Q1 2024 Report 

Perficient was included in Forrester’s Adobe Services Landscape, Q1 2024 report as a medium-sized consultancy among many notable Adobe service vendors. In the report, Forrester identified top business scenarios that buyers most frequently seek and expect along with extended business scenarios to illustrate vendor differentiation. 

“It is with immense pride and joy that Perficient is included in Forrester’s Adobe Services Landscape report, a significant milestone that we have pursued for years,” said Robert. “This recognition stands as a testament to our dedication and expertise in the field. Personally, this achievement holds significance as it reflects the culmination of my own efforts and contributions to Perficient’s success. It is a moment of celebration not only for the company but also for each individual who has played a role in shaping our Adobe journey towards excellence.” 

Robert Presenting

Robert has been with Perficient for more than 20 years and is regarded as one of the founders of the Adobe practice at Perficient. 

“The practice began as a team of generalized web content management specialists,” said Robert. “Embracing global delivery has allowed us to be more competitive in the marketplace. We became renowned for the quality of work delivered by our onshore and offshore teams.” 

Learn More: Perficient’s Collaborative Culture 

4. Fun and Rewarding Team Building Opportunities 

There is no shortage of fun to be had within the Adobe business unit! From cooking competitions to golf tournaments and ping-pong tournaments, our Adobe team knows how to work hard and play hard. Here is what some of our colleagues had to say:

Raf Winterpacht“In our Adobe BU, we care about each other,” said Raf Winterpacht, director, based in our Chicago office. “We take the time not only to ask how we can support each other, but we actually take on tasks when we see that someone’s workload needs to be balanced. Even though we’re dispersed around the world, we greet each other regularly. That is so important because it provides a sense of human connection.”

Women In Digital Golf Clinic

Women in Digital golf clinic

“Outside of our day-to-day work, we also pride ourselves in participating in knowledge-sharing activities,” said Raf. “We believe in sharing our findings and viewpoints when it comes to new and upcoming technology. This makes for a more harmonious work dynamic amongst our team.”

“Our team of thought leaders contribute to the amazing culture we have here at Perficient,” said Meghan Frederick. “We have a great group of people across the world working together to achieve incredible things. There is much excitement around our Adobe practice at Perficient, and our colleagues are true difference makers.”

Matt Shields“I want to know everyone in this practice and understand what they’re good at, what they like to do, where they want to grow, and what makes them uncomfortable,” said Matt Shields, managing director. “All of this is helpful to know when ensuring that people are well aligned in their role. As a result, we’re helping our clients in the best ways we can.”

Read More: Making a Splash and Growing with Perficient: From Solution Director to Adobe Managing Director

Perficient’s Adobe Practice is #ProudlyPerficient 

Perficient prioritizes a people-centric work culture. Our colleagues work collaboratively to deliver solutions that excite and solve real world problems. We are helping the world’s biggest brands digitally transform while also fostering an inclusive, growth-oriented environment at Perficient that will challenge, champion, and celebrate our colleagues at every turn. 

Click to view slideshow.

 


READY TO GROW YOUR CAREER? 

It’s no secret our success is because of our people. No matter the technology or time zone, our colleagues are committed to delivering innovative, end-to-end digital solutions for the world’s biggest brands, and we bring a collaborative spirit to every interaction. We’re always seeking the best and brightest to work with us. Join our team and experience a culture that challenges, champions, and celebrates our people. 

Visit our Careers page to see career opportunities and more! 

Go inside Life at Perficient and connect with us on LinkedIn, YouTube, Twitter, Facebook, TikTok, and Instagram. 

]]>
https://blogs.perficient.com/2024/08/21/4-ways-perficients-adobe-practice-is-forging-the-future-so-far-in-2024/feed/ 0 364615
Composite Components in AEM SPA (React) https://blogs.perficient.com/2024/08/20/composite-components-in-aem-spa-react/ https://blogs.perficient.com/2024/08/20/composite-components-in-aem-spa-react/#respond Tue, 20 Aug 2024 18:24:42 +0000 https://blogs.perficient.com/?p=358632

About Composite Components

Composite components are combinations of multiple components that reside or can be accessed within a single parent/container component. The main goal of developing components in such a way is to make life easier for authors by being able to drag and drop only one component rather than multiple. It also helps in building reusable modular components that can function independently or as a part of other composite components. In some cases, the parent/container component has its own dialog while in others it does not. The authors can individually edit each of the components as they would if they were added to the page as a standalone component. 

Composite Components in SPA Versus Non-SPA

Developing composite components in a non-SPA/HTL world is pretty straightforward. All we need to do is add the resource path of the child component using data-sly-resource inside the HTL of the parent component. We’ll touch more on this below. 

To achieve the same on AEM SPA architecture it involves a few more steps. This is because only the JSON data of the authored components is exposed via the Sling model exporter while the React component does the heavy lifting of mapping to them and reading the data. If the JSON does not have a nested structure (child components listed under the parent) the React component does not know that we are trying to build a composite component. Once we have the nested structure available, we can iterate and initialize each as a standalone React component.  

More details are mentioned below in the implementation section. 

Implementing in Non-SPA 

The HTL/non-SPA way of building this component can be achieved by the following steps.  

If the parent/container component does not have a dialog or design dialog, we need to create a cq:template node of type nt:unstructuredand under cq:template node., mMake a node of type nt:unstructuredwith sling:resourceType having the value of the path to the component included.  

Repeat the same for all the child components. In the HTL of the parent/container component add the tag below to simply include the component at render time: 

<sly data-sly-resource=”${‘<component name>’ @ resourceType=’<resource path of the component>’ }”/> 

Below is an example of a title and image (composite) component built using a core title and image. The parent/container component does not have a dialog, so we need to create a cq:template as mentioned above. The HTL will need to include the image and title components by using data-sly-resource. 

 <sly data-sly-resource=”${‘title’ @ resourceType=’weretail/components/content/title’ }”/> 

 <sly data-sly-resource=”${‘image’ @ resourceType=’weretail/components/content/image}”/> 

Title And Image Component In Aem Spa

Title And Image Component In Aem Spa Htl

Implementing in AEM SPA Editor

When implementing this in AEM SPA Editor (React), the process is very different because the component is completely rendered as a part of the SPA application. As mentioned above, in AEM SPA the authored data is still stored in the JCR but exposed as JSON which gets mapped to its React counterpart rather than reading it in traditional HTL.  

We’ll showcase how we can build a header component in AEM SPA (React) by leveraging a mix of core and custom components. The parent component which is the header in this case will be updated to export the data in a nested (JSON) structure for all the components authored as its children. This will help the React component read the data as one element having child nodes and then iterate over them. After this, we need to include the object returned by each node which will then initialize the child component. The child components are: 

  • Logo (Core Image)
  • Primary Navigation (Core Navigation)
  • Secondary Navigation
    • Search
    • My Account

Second Navigation Child Components

This implementation requires us to build:

  1. Proxies of Core Image & Core Navigation
  2. Search & My Account Components
  3. An ExportChildComponents sling model
  4. A parent/container header component

1. Proxies of Core Image & Core Navigation

  • Create a proxy of the core image and update the title as a logo.
  • Create a proxy of the core navigation component.
  • For the sake of this demo, the proxies do not have any customizations.

Proxies Of Core Image And Core Navigation

2. Search & My Account Components

  • Create a standalone search component.
  • It can be reused in the header as well as dragged and dropped anywhere.
  • Create a My Account component with fields to configure account management.

3. An ExportChildComponents Sling model

  • The export Child Components Sling model implements the Core Container Sling model.
import com.adobe.cq.export.json.ComponentExporter;
import com.drew.lang.annotations.NotNull;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Exporter;
import com.adobe.cq.export.json.ExporterConstants;
import com.adobe.cq.wcm.core.components.models.Container;
import java.util.Map;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.HashMap;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.injectorspecific.*;
import org.apache.sling.models.factory.ModelFactory;
import com.adobe.cq.export.json.SlingModelFilter;

//Sling Model annotation 
@Model(adaptables = SlingHttpServletRequest.class, 
       adapters = { ExportChildComponents.class,ComponentExporter.class },
       defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
       
@Exporter( // Exporter annotation that serializes the model as JSON
        name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, 
        extensions = ExporterConstants.SLING_MODEL_EXTENSION, 
        selector = ExporterConstants.SLING_MODEL_SELECTOR)

public class ExportChildComponents implements Container{
       
    private Map<String, ? extends ComponentExporter> childrenModels;
    private String[] exportedItemsOrder;

    @ScriptVariable
    private Resource resource;
    
    @Self
    private SlingHttpServletRequest request;

    @OSGiService
    private SlingModelFilter slingModelFilter;

    @OSGiService
    private ModelFactory modelFactory;
    
    @NotNull
    @Override
    public Map<String, ? extends ComponentExporter> getExportedItems() {
        if (childrenModels == null) {
            childrenModels = getChildrenModels(request, ComponentExporter.class);
        }
        Map<String, ComponentExporter> exportChildrenModels = new HashMap<String, ComponentExporter>();
        exportChildrenModels.putAll(childrenModels);
        return exportChildrenModels;
    }

    @NotNull
    @Override
    public String[] getExportedItemsOrder() {
        if (exportedItemsOrder == null) {
            Map<String, ? extends ComponentExporter> models = getExportedItems();
            if (!models.isEmpty()) {
                exportedItemsOrder = models.keySet().toArray(ArrayUtils.EMPTY_STRING_ARRAY);
            } else {
                exportedItemsOrder = ArrayUtils.EMPTY_STRING_ARRAY;
            }
        }
        return Arrays.copyOf(exportedItemsOrder,exportedItemsOrder.length);
    }

    private  Map<String, T> getChildrenModels(@NotNull SlingHttpServletRequest request, @NotNull Class
            modelClass) {
        Map<String, T> models = new LinkedHashMap<>();
        for (Resource child : slingModelFilter.filterChildResources(resource.getChildren())) {
            T model = modelFactory.getModelFromWrappedRequest(request, child, modelClass);
            if (model != null) {
                models.put(child.getName(), model);
            }
        }
        return models;
    }
}
...
  • It helps to iterate over child components saved under the parent node.
  • Then export all of them as a single JSON output via their Sling model exporter.
import com.adobe.cq.export.json.ComponentExporter;
{
    ":items": {
      "root": {
        "columnClassNames": {
          "header": "aem-GridColumn aem-GridColumn--default--12"
        },
        "gridClassNames": "aem-Grid aem-Grid--12 aem-Grid--default--12",
        "columnCount": 12,
        ":items": {
          "header": {
            "id": "header-346949959",
            ":itemsOrder": [
              "logo",
              "navigation",
              "search",
              "account"
            ],
            ":type": "perficient/components/header",
            ":items": {
              "logo": {
                "id": "image-ee58d4cd48",
                "linkType": "",
                "tagLinkType": "",
                "displayPopupTitle": true,
                "decorative": false,
                "srcUriTemplate": "/content/experience-fragments/perficient/us/en/site/header/master/_jcr_content/root/header/logo.coreimg{.width}.png/1705295980301/perficient-logo-horizontal.png",
                "lazyEnabled": true,
                "title": "Logo",
                "uuid": "799c7831-264c-42c0-be02-1d0cbb747bd2",
                "areas": [],
                "alt": "NA",
                "src": "/content/experience-fragments/perficient/us/en/site/header/master/_jcr_content/root/header/logo.coreimg.png/1705295980301/perficient-logo-horizontal.png",
                "widths": [],
                ":type": "perficient/components/logo"
              },
              "navigation": {
                "id": "navigation-1727181688",
                "items": [
                  {
                    "id": "navigation-cd54619f8f-item-12976048d7",
                    "path": "/content/react-spa/us/en/cases",
                    "level": 0,
                    "active": false,
                    "current": false,
                    "title": "Link 1",
                    "url": "/content/react-spa/us/en/cases.html",
                    "lastModified": 1705203487624,
                    ":type": "perficient/components/structure/page"
                  },
                  {
                    "id": "navigation-cd54619f8f-item-438eb66728",
                    "path": "/content/react-spa/us/en/my-onboarding-cases",
                    "level": 0,
                    "active": false,
                    "current": false,
                    "title": "Link 2",
                    "url": "/content/react-spa/us/en/my-onboarding-cases.html",
                    "lastModified": 1705203496764,
                    ":type": "perficient/components/structure/page"
                  },
                  {
                    "id": "navigation-cd54619f8f-item-e8d85a3188",
                    "path": "/content/react-spa/us/en/learning-resources",
                    "level": 0,
                    "active": false,
                    "current": false,
                    "title": "Link 3",
                    "url": "/content/react-spa/us/en/learning-resources.html",
                    "lastModified": 1705203510651,
                    ":type": "perficient/components/structure/page"
                  },
                  {
                    "id": "navigation-cd54619f8f-item-d1553683aa",
                    "path": "/content/react-spa/us/en/Reports",
                    "children": [],
                    "level": 0,
                    "active": false,
                    "current": false,
                    "title": "Link 5",
                    "url": "/content/react-spa/us/en/Reports.html",
                    "lastModified": 1705203601382,
                    ":type": "perficient/components/structure/page"
                  }
                ],
                ":type": "perficient/components/navigation"
              },
              "search": {
                "id": "search-1116154524",
                "searchFieldType": "navbar",
                ":type": "perficient/components/search"
              },
              "account": {
                "id": "account-1390371799",
                "accountConfigurationFields": [],
                "logoutLinkType": "",
                "helpText": "Account",
                "logoutText": "Sign Out",
                ":type": "perficient/components/account"
              }
            }
          }
        },
        ":itemsOrder": [
          "header"
        ],
        ":type": "perficient/components/core/container"
      }
    }
  }
...

4. Parent/Container Header Component

  • Create a Header Component which is an extension of the core container component.

Header Component Aem Spa

  • Include the Search Component dialog as an additional tab in the header component dialog.

Search Component Dialog

  • Include Accounts tab which is a custom tab with fields for user account management.

Accounts Tab

  • Create cq:template having child nodes pointing to their corresponding resource paths.
  • This will help export the authored data using the Sling model exporter.

Sling Model Exporter

  • Create HeaderModelImpl that extends ExportChildComponents (created in step 3). This exports the JSON data of not only the parent but also its children (logo, navigation & search).
import com.adobe.cq.export.json.ComponentExporter;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import com.adobe.cq.export.json.ComponentExporter;
import com.adobe.cq.export.json.ExporterConstants;
import com.chc.ecchub.core.models.ExportChildComponents;
import com.chc.ecchub.core.models.nextgen.header.HeaderModel;

@Model(adaptables = SlingHttpServletRequest.class,
        adapters = { HeaderModel.class, ComponentExporter.class},
        resourceType = HeaderModelImpl.RESOURCE_TYPE, 
        defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)

@Exporter( // Exporter annotation that serializes the model as JSON
        name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, 
        extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class HeaderModelImpl extends ExportChildComponents implements HeaderModel{
    
    static final String RESOURCE_TYPE = "perficient/components/header";

    @Override
    public String getExportedType() {
        return RESOURCE_TYPE;
    }

}
...
  • Create React equivalent for a header that extends the core container and maps to the header component created above.
import { Container, MapTo, withComponentMappingContext } from '@adobe/aem-react-editable-components';
import { useState } from "react";
import { NavigationComp, NavigationConfig } from "./Navigation";
import { LogoComp, LogoConfig } from "../../components/Logo";
import { SearchComp } from "../../components/Search";
import { SecondaryNavigation } from "./SecondaryNavigation";

export const HeaderEditConfig = {
    emptyLabel: 'Header'
};

export default class Header extends Container {
    render() {
        return (
            <>
                <HeaderComp headerData={super.childComponents} headerProps={this.props} hasAuthorization={hasAuthorization}/>
            </>
        )
    }
}

const selectedComponent = (props, selectedCqType) => {
    let selectedComponentProps;
    props?.headerData?.forEach((data) => {
        if (data.props?.cqType === selectedCqType) {
            selectedComponentProps = data;
        }
    });
    return selectedComponentProps;
}


const HeaderComp = (props) => {
    const [searchModalOverlay, setSearchModalOverlay] = useState(false);
    const searchProps = selectedComponent(props, 'perficient/components/search');
    const logoProps = selectedComponent(props, 'perficient/components/logo');
    const navProps = selectedComponent(props, 'perficient/components/navigation');

    return (
        <>
            <header data-analytics-component="Header">
                <SearchComp
                    searchProps={searchProps?.props}
                    onToggleSearch={setSearchModalOverlay}
                    searchModal={searchModalOverlay}
                />
                <div className="header__container">
                    <div className="header__container-wrapper">
                        <div className="company-info">
                            {logoProps}
                        </div>
                        <div className="desktop-only">
                            <nav className="navigation">
                                {navProps}
                            </nav>
                            <nav className="header__secondary-nav">
                                <SecondaryNavigation
                                    notificationRead={notificationRead}
                                    isNotificationRead={isNotificationRead}
                                    snProps={props}
                                    onToggleSearch={setSearchModalOverlay}
                                />
                            </nav>
                        </div>
                    </div>
                </div>
            </header>
        </>
    )
}

MapTo('ECCHub/components/nextgen/header/navigation')(NavigationComp, NavigationConfig);
MapTo('ECCHub/components/nextgen/header/logo')(LogoComp, LogoConfig);
MapTo('ECCHub/components/nextgen/header/header')(withComponentMappingContext(Header), HeaderEditConfig);

 

  • Additionally, add a map for the logo and navigation since they are exported as child components. Since the header extends the core container the child component properties are available via super.childcomponents
import { useEffect } from 'react';
import { MapTo } from '@adobe/aem-react-editable-components';
import * as constants from '../../../../utils/constants';
import { toggleHeaderForUsers } from '../../../../utils/genericUtil';
import { useDispatch } from "react-redux";
import { BreadcrumbActions } from '../../../../store/Breadcrumb/BreadcrumbSlice';

export const NavigationConfig = {
    emptyLabel: 'Navigation',

    isEmpty: function() {
        return true;
    }
};

export const NavigationComp = (navProps) => {
    return (
            <ul className="top-menu" data-analytics-component="Top Navigation">
                {/* Maps through authored navigation links and renders the link title and URL. */}
                {navProps?.items.map((item, index) =>
                <li key={index}><a href={item?.url} title={item?.title}>{item?.title}</a></li>
                )}
            </ul>
    );
}

MapTo("perficient/components/navigation")(NavigationComp, NavigationConfig);

 

 

Super.childcomponents

Worth the Investment

Composite components are a useful feature to leverage as they immensely help the authors while retaining the modularity and reusability of the individual components. Although creating the experience on the SPA framework compared to the HTL approach requires additional effort it is still worth the investment as it is a one-time setup and can be used for any components any number of times as it is quite generic.

 

]]>
https://blogs.perficient.com/2024/08/20/composite-components-in-aem-spa-react/feed/ 0 358632
Mastering Page Properties With Granite Render Conditions and Context-Aware Configuration https://blogs.perficient.com/2024/07/29/mastering-page-properties-with-granite-render-conditions-and-context-aware-configuration/ https://blogs.perficient.com/2024/07/29/mastering-page-properties-with-granite-render-conditions-and-context-aware-configuration/#respond Mon, 29 Jul 2024 11:10:02 +0000 https://blogs.perficient.com/?p=366394

From Static to Dynamic: The Evolution of Template Management

Do you remember the days of static templates? We had a plethora of templates, each with its own page components and CQ dialogs. It was a maintenance nightmare! 

But then came editable templates, and everything changed. With this new approach, we can define a single-page component and create multiple templates from it. Sounds like a dream come true, right? 

But there’s a catch. What if we need different dialogs for different templates? Do we really need to create separate template types for each one? That would mean maintaining multiple template types and trying to keep track of which template uses which type. Not exactly the most efficient use of our time. 

Managing Page Properties in AEM

In this post, we’ll explore the challenges of template management and how we can overcome them using Granite render conditions and context-aware configurations. 

When managing page properties, we’re often faced with a dilemma. While context-aware configurations are ideal for setting up configurations at the domain or language level, they fall short when it comes to managing individual pages. 

The usual go-to solution is to update the Page Properties dialog, but this approach has its own set of limitations. So, what’s a developer to do? 

Fortunately, there’s a solution that combines the power of Granite render conditions with the flexibility of context-aware configurations. 

What is Granite Render Condition? 

Render condition is just conditional logic to render a specific section of the component UI. If you want a more detailed description, you can read Adobe’s official documentation

A Real-World Use Case Using Both Granite Render Condition and Context-Aware Configuration

Say we want to display and hide the page properties tab based on the template name, which can be configured using context-aware configuration without any hardcoded code.   

First, we’d need to build the CAC which will contain fields for adding the template names and tab path to show.   

We will create a service for context-aware configuration which will read config and provide the mapping. 

public interface PageTabsMappingService { 
    List<PageTabsMappingConfig> getPageTabsMappingConfigList(); 
     
}

Here PageTabsMappingConfig is just a POJO bean class that consists of a page tab path and template path. 

@Data 
public class PageTabsMappingConfig { 
    private String templatePath; 
    private String tabPath; 
}

Now let’s create a context-aware configuration implementation class, which will consist of a template path and tabs path configuration ability. 

We want this to be more author-friendly, so we will be using a custom data source. This data source can be found in this blog post

For this example, we need two data sources, one for template path and one for tab paths.  

 So finally, our configuration will look like this:

@Configuration(label = "Page Tabs Mapping Configuration", description = "Page Tabs Mapping Config", property = 
        {EditorProperties.PROPERTY_CATEGORY + "=TemplateAndTabs"}, collection = true) 
public @interface PageTabsMappingConfiguration { 
 
    @Property(label = "Select Template To Be Mapped", description = "Select Template Name To Be Mapped", property = { 
            "widgetType=dropdown", 
            "dropdownOptionsProvider=templateDataSource" 
    },order = 1) 
    String getTemplatePath(); 
 
 
    @Property(label = "Select Tab to be mapped", description = "Select Tab to be mapped", property = { 
            "widgetType=dropdown", 
            "dropdownOptionsProvider=tabDataSource" 
    },order = 2) 
    String getTabPath(); 
     
 
}

Now let’s implement Service to read this config. 

public interface PageTabsMappingService { 
    List<PageTabsMappingConfig> getPageTabsMappingConfigList(Resource resource); 
 
}
@Component(service = PageTabsMappingService.class, 
        immediate = true) 
@ServiceDescription("Implementation For PageTabsMappingService ") 
@Slf4j 
public class PageTabsMappingServiceImpl implements PageTabsMappingService { 
 
    @Override 
    public List<PageTabsMappingConfig> getPageTabsMappingConfigList(final Resource resource) { 
 
        final ConfigurationBuilder configurationBuilder = Optional.ofNullable(resource) 
                .map(resource1 -> resource1.adaptTo(ConfigurationBuilder.class)) 
                .orElse(null); 
        return new ArrayList<>(Optional 
                .ofNullable(configurationBuilder) 
                .map(builder -> builder 
                        .name(PageTabsMappingConfiguration.class.getName()) 
                        .asCollection(PageTabsMappingConfiguration.class)) 
                .orElse(new ArrayList<>())) 
                .stream().map(pageTabsMappingConfiguration ->new PageTabsMappingConfig(pageTabsMappingConfiguration.getTabPath(),pageTabsMappingConfiguration.getTemplatePath())) 
                .collect(Collectors.toList()); 
    } 
}

In the above code, we are reading context-aware configuration and providing the list for further use. 

Now let us create render condition to show and hide tabs in page properties which will utilize the CAC mapping configuration. 

We will be using the Sling Model for the same. This will be invoked whenever Page properties tabs are opened, in page editor mode, creation wizard, or on sites wizard. 

@Model(adaptables = SlingHttpServletRequest.class) 
public class TabsRenderConditionModel { 
 
    @Self 
    private SlingHttpServletRequest request; 
 
    @OSGiService 
    private PageTabsMappingService pageTabsMappingService; 
 
    /** 
     * This is to set render condition for tabs. 
     */ 
    @PostConstruct 
    public void init() { 
 
        final var resource = request.getResource() 
                .getResourceResolver().getResource("/content"); 
        //We are considering root level site config since this will be global. 
        //For multitenant environment you can add additional OSGI Config and use the path accordingly 
        final List<PageTabsMappingConfig> tabRenderConfig = 
                pageTabsMappingService.getPageTabsMappingConfigList(resource); 
        final var name = Optional.ofNullable(request.getResource().getParent()) 
                .map(Resource::getName).orElse(StringUtils.EMPTY); 
        final var props = (ValueMap) request.getAttribute("granite.ui.form.values"); 
        final var template = Optional.ofNullable(props) 
                .map(props1 -> props1.get("cq:template", String.class)) 
                .orElse(StringUtils.EMPTY); 
 
        final var renderFlag = tabRenderConfig.stream() 
                .anyMatch(tabConfig -> 
                        BooleanUtils.and(new Boolean[]{StringUtils.equals(name, tabConfig.getTabName()), 
                                StringUtils.equals(template, tabConfig.getTemplatePath())})); 
 
        request.setAttribute(RenderCondition.class.getName(), 
                new SimpleRenderCondition(renderFlag)); 
    } 
 
 
}

After reading template we simply check if this given tab name mapping exists or not. Based on that, using the simple render condition we are setting a flag for showing and hiding the tab. 

Now it is time to use this Sling model in the actual render condition script file. In our project directory let’s assume /apps/my-project/render-conditions/tabs-renderconditions 

Create tabs-renderconditions.html  

And add content as: 

<sly data-sly-use.tab="com.mybrand.demo.models.TabsRenderConditionModel" />

Build a customs tabs under the base page template folder as follows: 

/apps/my-project/components/structure/page/base-page/tabs 

-landing-page-tab 

-home-page-tab 

-country-page-tab 

-state-page-tab  

-hero-page-tab

And our cq:dialog will be referring the same as this:

<?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"> 
    <content jcr:primaryType="nt:unstructured"> 
        <items jcr:primaryType="nt:unstructured"> 
            <tabs jcr:primaryType="nt:unstructured"> 
                <items jcr:primaryType="nt:unstructured"> 
 
                    <additionalHeroPage 
                            jcr:primaryType="nt:unstructured" 
                            sling:resourceType="granite/ui/components/foundation/include" 
                            path="/mnt/override/apps/my-project/components/structure/page/tabs/additional-hero-page"/> 
                    <additionalStatePage 
                            jcr:primaryType="nt:unstructured" 
                            sling:resourceType="granite/ui/components/foundation/include" 
                            path="/mnt/override/apps/my-project/components/structure/page/tabs/additionalstatepage"/> 
                </items> 
            </tabs> 
        </items> 
    </content> 
</jcr:root>

And our sample tab with render condition config will looks like this: 

<additionalHeroPage 
        cq:showOnCreate="{Boolean}true" 
        jcr:primaryType="nt:unstructured" 
        jcr:title="Additional Hero Page Setting" 
        sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns"> 
    <items jcr:primaryType="nt:unstructured"> 
        <column 
                jcr:primaryType="nt:unstructured" 
                sling:resourceType="granite/ui/components/coral/foundation/container"> 
            <items jcr:primaryType="nt:unstructured"> 
                <section1 
                        jcr:primaryType="nt:unstructured" 
                        jcr:title="Settings" 
                        sling:resourceType="granite/ui/components/coral/foundation/form/fieldset"> 
                    <items jcr:primaryType="nt:unstructured"> 
                        <testProperty 
                                cq:showOnCreate="{Boolean}true" 
                                jcr:primaryType="nt:unstructured" 
                                sling:resourceType="granite/ui/components/coral/foundation/form/textfield" 
                                fieldDescription="Test Property" 
                                fieldLabel="Test Property" 
                                name="./testProp" 
                                required="{Boolean}true"> 
                                <granite:data 
                                    jcr:primaryType="nt:unstructured" 
                                    cq-msm-lockable="./testProp"/> 
                         </testProperty> 
                    </items> 
                </section1> 
            </items> 
        </column> 
    </items> 
    <granite:rendercondition 
            jcr:primaryType="nt:unstructured" 
            sling:resourceType="my-project/render-conditions/tabs-renderconditions"/> 
</additionalHomePage>

In the below Template and Tabs CAC configuration, the “Additional Home Page Setting” tab will be displayed in page properties when an author is opening a page created using the hero-page template. 

AEM Handling Page Properties Tabs Render Config Window

Finally, when you open any page made with a configured template, like the Hero page in the image below, you can see the tabs configured for it. 

AEM Handling Page Properties Additional Hero Page Settings Window

More Helpful AEM Tips and Tricks

I hope you have a better understanding of how to overcome some of the challenges of managing templates in AEM.

For more AEM tips and tricks, keep up with us on our Adobe blog! 

]]>
https://blogs.perficient.com/2024/07/29/mastering-page-properties-with-granite-render-conditions-and-context-aware-configuration/feed/ 0 366394