Expand my Community achievements bar.

SOLVED

Core Component Breadcrumb Title

Avatar

Level 4

Is it possible to override the Breadcrumb or Page List component to use a different page title than the Navigation Title? 

 

I have a requirement where I need to use a shorter title in the Breadcrumb component than in the Page List Component, however, both are using the Navigation Title. If there is a way that I can use one title type for one component and another for the other component, that would be ideal.

 

I'm a relative beginner with Java and don't entirely understand how the Delegation Pattern for Sling Models (https://github.com/adobe/aem-core-wcm-components/wiki/Delegation-Pattern-for-Sling-Models) could tie into what I'm trying to do here, but if there is a tutorial or some information that might help guide a solution, I would appreciate the insight.

1 Accepted Solution

Avatar

Correct answer by
Level 10

Hi @jetate,

It depends what you mean by "one title type for one component and another for the other component", because there are many title types. However, let's have a look at why the Navigation Title is used.

The breadcrumb model returns a list of NavigationItems (the interface). These are in fact instances of BreadcrumbItemImpl (the class). They in turn fetch the title property (which is used in the rednering HTL script using the following algorithm:

@Override
    public String getTitle() {
        String title = page.getNavigationTitle();
        if (title == null) {
            title = page.getPageTitle();
        }
        if (title == null) {
            title = page.getTitle();
        }
        if (title == null) {
            title = page.getName();
        }
        return title;
    }

 So all you need to do is override this behavior slightly. Let's say for example that you want to use the Page Title and not the Navigation Title in a breadcrumb, you should create the following item:

import com.adobe.cq.wcm.core.components.models.NavigationItem;
import lombok.Getter;
import lombok.experimental.Delegate;

public class CustomNavigationItem implements NavigationItem {

    // Here we delegate everything to the original item, except the getTitle method (see interface below)
    @Delegate(excludes = DelegationExclusion.class)
    private final NavigationItem delegate;

    // Rather than getting a title from a page using an the algorithm I showed above, this item will simply return
    // whatever String was given to it in its constructor
    @Getter
    private final String title;

    public CustomNavigationItem(final NavigationItem item, final String title) {
        this.delegate = item;
        this.title = title;
    }

    private interface DelegationExclusion {
        String getTitle();
    }
}

Then, return these CustomNavigationItems rather than the default items using this model:

import com.adobe.cq.wcm.core.components.models.Breadcrumb;
import com.adobe.cq.wcm.core.components.models.NavigationItem;
import com.day.cq.wcm.api.PageManager;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Via;
import org.apache.sling.models.annotations.injectorspecific.ScriptVariable;
import org.apache.sling.models.annotations.injectorspecific.Self;
import org.apache.sling.models.annotations.via.ResourceSuperType;

import java.util.Collection;
import java.util.stream.Collectors;

@Model(
        adaptables = {Resource.class, SlingHttpServletRequest.class},
        adapters = Breadcrumb.class,
        resourceType = "demo/components/content/breadcrumb",
        defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class CustomBreadcrumb implements Breadcrumb {

    @Self
    @Via(type = ResourceSuperType.class)
    private Breadcrumb delegate;

    @ScriptVariable
    private PageManager pageManager;

    @Override
    public Collection<NavigationItem> getItems() {

        // For each item, create an instance of our custom item
        return delegate.getItems().stream().map(item -> {

            // Set the title to be the page title
            final String pageTitle = pageManager.getPage(item.getPath()).getPageTitle();
            return new CustomNavigationItem(item, pageTitle);
            
        }).collect(Collectors.toList());
    }

    @Override
    public String getExportedType() {
        return delegate.getExportedType();
    }
}

Here is the result! The page properties:

Selection_168.png

Ans as you can see, the page title is used, not the navigation title! Sorry, didn't have time for CSS  

Selection_169.png

I've been generous with comments on the parts that are relative to your use-case. If you want a step-by-step tutorial to explain exactly how it all works, I wrote one recently here: https://levelup.gitconnected.com/aem-extend-core-component-models-using-resource-type-association-an... 

View solution in original post

5 Replies

Avatar

Correct answer by
Level 10

Hi @jetate,

It depends what you mean by "one title type for one component and another for the other component", because there are many title types. However, let's have a look at why the Navigation Title is used.

The breadcrumb model returns a list of NavigationItems (the interface). These are in fact instances of BreadcrumbItemImpl (the class). They in turn fetch the title property (which is used in the rednering HTL script using the following algorithm:

@Override
    public String getTitle() {
        String title = page.getNavigationTitle();
        if (title == null) {
            title = page.getPageTitle();
        }
        if (title == null) {
            title = page.getTitle();
        }
        if (title == null) {
            title = page.getName();
        }
        return title;
    }

 So all you need to do is override this behavior slightly. Let's say for example that you want to use the Page Title and not the Navigation Title in a breadcrumb, you should create the following item:

import com.adobe.cq.wcm.core.components.models.NavigationItem;
import lombok.Getter;
import lombok.experimental.Delegate;

public class CustomNavigationItem implements NavigationItem {

    // Here we delegate everything to the original item, except the getTitle method (see interface below)
    @Delegate(excludes = DelegationExclusion.class)
    private final NavigationItem delegate;

    // Rather than getting a title from a page using an the algorithm I showed above, this item will simply return
    // whatever String was given to it in its constructor
    @Getter
    private final String title;

    public CustomNavigationItem(final NavigationItem item, final String title) {
        this.delegate = item;
        this.title = title;
    }

    private interface DelegationExclusion {
        String getTitle();
    }
}

Then, return these CustomNavigationItems rather than the default items using this model:

import com.adobe.cq.wcm.core.components.models.Breadcrumb;
import com.adobe.cq.wcm.core.components.models.NavigationItem;
import com.day.cq.wcm.api.PageManager;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Via;
import org.apache.sling.models.annotations.injectorspecific.ScriptVariable;
import org.apache.sling.models.annotations.injectorspecific.Self;
import org.apache.sling.models.annotations.via.ResourceSuperType;

import java.util.Collection;
import java.util.stream.Collectors;

@Model(
        adaptables = {Resource.class, SlingHttpServletRequest.class},
        adapters = Breadcrumb.class,
        resourceType = "demo/components/content/breadcrumb",
        defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class CustomBreadcrumb implements Breadcrumb {

    @Self
    @Via(type = ResourceSuperType.class)
    private Breadcrumb delegate;

    @ScriptVariable
    private PageManager pageManager;

    @Override
    public Collection<NavigationItem> getItems() {

        // For each item, create an instance of our custom item
        return delegate.getItems().stream().map(item -> {

            // Set the title to be the page title
            final String pageTitle = pageManager.getPage(item.getPath()).getPageTitle();
            return new CustomNavigationItem(item, pageTitle);
            
        }).collect(Collectors.toList());
    }

    @Override
    public String getExportedType() {
        return delegate.getExportedType();
    }
}

Here is the result! The page properties:

Selection_168.png

Ans as you can see, the page title is used, not the navigation title! Sorry, didn't have time for CSS  

Selection_169.png

I've been generous with comments on the parts that are relative to your use-case. If you want a step-by-step tutorial to explain exactly how it all works, I wrote one recently here: https://levelup.gitconnected.com/aem-extend-core-component-models-using-resource-type-association-an... 

Avatar

Level 10
Oh and I should mention: I am using Lombok annotations to implement the delegation pattern. This all might be a bit advanced if you're a beginner, sorry if everything is not 100% clear but please read the tutorial for more detailed explanations

Avatar

Level 4
This is great, thank you! I had a little trouble with the Lambok setup but found that I needed to add some dependencies for biz.aQute.bnd. Now I just need to update it to still allow for fallback from the Page Title for the title if it exists so that I'm just skipping the Navigation Title.

Avatar

Level 2

The code worked on one server and failing on other.

Avatar

Level 2

Facing NPE on delegate.getITems().

The Breadcrumb object is not getting instance of self and its null at

 

@Deleted Account
@Via(type = ResourceSuperType.class)
private Breadcrumb delegate;