Skip to main content

Adobe

Mastering Page Properties With Granite Render Conditions and Context-Aware Configuration

aws cross account best practices

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! 

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Saurabh Mahajan, Lead Technical Consultant

Saurabh is certified Adobe Experience Manager Sites Architect.

More from this Author

Categories
Follow Us