How can I inherit page properties in AEM 6.2? - aem

In our AEM 6.2 project, I ran to a scenario where I need to config a navigation in one page (let call this Homepage), all of the other pages can use home navigation config or use their own navigation config values.
I decided to use live copy because the clone pages can cancel the linked properties at any time and use their own values. But there is two problem with this approach:
Users must set the template for clone pages by edit their jcr: content/sling:resourceType and jcr: content/cq: template because the navigation is used in all pages and our web uses about 5+ templates.
Live copy does no allowed clone pages are children of source pages. But I was required to make web structure like this :
Home
|_ Page 1
|_ Page 1.1
|_ Page 2
|_ Page 3
Live copy maybe not suitable for this situation, we change to use HTL ${inheritedPageProperties}, this solve the template and structure issue but it creates two new problems:
Inherited properties in property config dialog of child page will be blank (because they are not set and called via ${inheritedPageProperties} )
If users change properties in "Page 1" page, "Page 1.1" (and Page 1.1.1, etc ...) will use these values (Because ${inheritedPageProperties} search the upper nodes to get value).
What our client want are :
All page can use navigation setting only from the homepage or their own page (use home page by default).
If use homepage properties, these values must show in their config dialog.
Try to avoid config template in CRXDE Lite
The website must have the parent-child structure
How can I achieve these requirements?

You can achieve this with a simple Sling Model and Sling's CompositeValueMap
CompositeValueMap docs state:
An implementation of the ValueMap based on two ValueMaps: - One containing the properties - Another one containing the defaults to use in case the properties map does not contain the values. In case you would like to avoid duplicating properties on multiple resources, you can use a CompositeValueMap to get a concatenated map of properties.
We can use this by supplying the descendant's value map (the current page's) then finding the correct ancestor and supplying its properties valuemap as the defaults.
for the purposes of this question, I always assume that 2rd descendant from root is always the ancestor (you can find your ancestor according to your requirnments)
package com.sample.helpers;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.wrappers.CompositeValueMap;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.Self;
import javax.annotation.PostConstruct;
#Model(adaptables = Resource.class)
public class CustomInheritedPageProperties
{
ValueMap inheritedProperties;
#Self
Resource currentResource;
#OSGiService
PageManager pageManager;
#PostConstruct
protected void init() {
// get the current page, or the "descendant"
Page descendant = pageManager.getContainingPage(currentResource);
/* You have to add your custom logic to get the ancestor page.
* for this question's purposes, I'm always assuming it's the 3rd decendant of root
* more here: https://helpx.adobe.com/experience-manager/6-2/sites/developing/using/reference-materials/javadoc/com/day/cq/wcm/api/Page.html#getParent(int)
*/
Page ancestor = descendant.getParent(2);
// create a CompositeValueMap where the properties are descendant's and the defaults are ancestor's
inheritedProperties = new CompositeValueMap(descendant.getProperties(), ancestor.getProperties());
}
public ValueMap getInheritedProperties()
{
return inheritedProperties;
}
}
Now you can use this as follows
<sly data-sly-use.propHelper="com.sample.helpers.CustomInheritedPageProperties">>/sly>
<!--/* someProp here refers to the property you wish to get (inherited, of course)*/-->
<h1>propHelper.inheritedProperties.someProp</h1>

Related

How do I not include scripts and clientlibs in an experience fragment when exporting to Adobe Target?

I'm trying to build an experience fragment (XF) template in AEM 6.5. We have some custom clientlibs that I want to include when designers are authoring the experience fragment but I don't want to include when the experience fragment is injected via Adobe Target as the clientlibs will already be included on the base page template.
I want the clientlibs on the XF template so components render properly while designing. I've tried building a new page component based on /libs/cq/experience-fragments/components/xfpage and then checking the runmode for author or publish and using the result of that in a data-sly-test to conditionally include them. But I think because the Export to Target option happens on Author, it's including the scripts in the html output when it's exported to Target.
How do I conditionally include clientlibs during authoring of an XF, but not include them when the experience fragment is exported to target and added to a page from there?
There are couple of things you need to do in order to achieve this.
AEM offers the Link Rewriter Provider Interface. This is a ConsumerType interface that you can implement in your bundles, as a service.
It bypasses the modifications AEM performs on internal links of an HTML offer as rendered from an Experience Fragment. This interface allows you to customize the process of rewriting internal HTML links to align with your business needs.
To use the interface you first need to create a bundle containing a new service component that implements the Link Rewriter Provider interface.
This service will be used to plug into the Experience Fragment Export to Target rewriting in order to have access to the various links.
import com.adobe.cq.xf.ExperienceFragmentLinkRewriterProvider;
import com.adobe.cq.xf.ExperienceFragmentVariation;
import org.osgi.service.component.annotations.Service;
import org.osgi.service.component.annotations.Component;
#Component
#Service
public class GeneralLinkRewriter implements ExperienceFragmentLinkRewriterProvider {
#Override
public String rewriteLink(String link, String tag, String attribute) {
return null;
}
#Override
public boolean shouldRewrite(ExperienceFragmentVariation experienceFragment) {
return false;
}
#Override
public int getPriority() {
return 0;
}
}
shouldRewrite
You need to indicate to the system whether it needs to rewrite the links when a call is made for Export to Target on a certain Experience Fragment variation. You do this by implementing the method:
shouldRewrite(ExperienceFragmentVariation experienceFragment);
For example:
#Override
public boolean shouldRewrite(ExperienceFragmentVariation experienceFragment) {
return experienceFragment.getPath().equals("/content/experience-fragment/master");
}
This method receives, as a parameter, the Experience Fragment Variation that the Export to Target system is currently rewriting.
In the example above, we would like to rewrite:
links present in src
href attributes only
for a specific Experience Fragment:
/content/experience-fragment/
Any other Experience Fragments that pass through the Export to Target system are ignored and not affected by changes implemented in this Service.
rewriteLink
For the Experience Fragment variation impacted by the rewriting process, it will then proceed to let the service handle the link rewriting. Everytime a link is encountered in the internal HTML, the following method is invoked:
rewriteLink(String link, String tag, String attribute)
As input, the method receives the parameters:
link
The String representation of the link that is currently being processed. This is usually a relative URL pointing to the resource on the author instance.
tag
The name of the HTML element that is currently being processed.
attribute
The exact attribute name.
If, for example, the Export to Target system is currently processing this element, you can define CSSInclude as:
<link rel="stylesheet" href="/etc.clientlibs/foundation/clientlibs/main.css" type="text/css">
The call to the rewriteLink() method is done using these parameters:
rewriteLink(link="/etc.clientlibs/foundation/clientlibs/main.css", tag="link", attribute="href" )
When you create the service you can make decisions based on the given input, and then rewrite the link accordingly.

Passing data to component in AEM

Background
I am trying to make a dynamic navigation component in AEM. Currently I have been trying to find information in the docs and online to help me but have not been able to find much.
My application loads vue components into AEM so the front end is controlled by VUE and the data is passed in through props.
I would like to pass the pages and sub pages of those pages into the vue component, so I can create a dynamic navigation with a drop down nav for any links.
Example
Currently I found a way to use a java class to load the root pages links. I am doing that like this,
package services.agro.something.core.models;
import java.util.*;
import java.util.Iterator;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageFilter;
import com.adobe.cq.sightly.WCMUsePojo;
public class SidebarNavigationModel extends WCMUsePojo{
private List<Page> items = new ArrayList<Page>();
private Page rootPage;
// Initializes the navigation
#Override
public void activate() throws Exception {
rootPage = getCurrentPage().getAbsoluteParent(2);
if (rootPage == null) {
rootPage = getCurrentPage();
}
Iterator<Page> childPages = rootPage.listChildren(new PageFilter(getRequest()));
while (childPages.hasNext()) {
items.add(childPages.next());
}
}
// Returns the navigation items
public List<Page> getItems() {
return items;
}
// Returns the navigation root
public Page getRoot() {
return rootPage;
}
}
Then I could use this data like this,
<div data-sly-use.sidebarNavigationModel="services.agro.gde.core.models.SidebarNavigationModel" class="sidebar-navigation ae-spacing-indent">
<div class="sidebar-nav-container">
// This is a vue component that I am trying to pass data to. The data comes through as commas and when I expect an array an error throws in console stating it expects array but receives a string.
<sidebar-navigation title="${properties.title}" items="${sidebarNavigationModel.items}"></sidebar-navigation>
</div>
// This outputs 16 commas to the screen ${sidebarNavigationModel.items}
<ul class="nav navbar-nav navbar-center">
// This outputs a link to home
<li>HOME</li>
// This outputs 19 links like I expect
<li class="nav navbar-nav navbar-left" data-sly-repeat="${sidebarNavigationModel.items}">
${item.title}
</li>
</ul>
</div>
Question
If I could better understand the data set that is returned in the loop I believe I could pass it to the vue component as I have tried.
Why does this output commas when outside of the loop and why when passing it to vue does it appears to be a string?
How can I get the sub pages for a site when it loads into a data set I can pass to my vue component from the template?
HTL/Sightly is trying to render the array but for each item it will render nothing (since it cannot evaluate and render the Page object). What you could do is either massage the data in your Use-Object to return exactly the needed string or process it in the script like:
[<sly data-sly-repeat="${logic.items}">{title="${item.title}",path="${sidebarNavigationModel.path}"}${itemList.last?'':','}</sly>]
The vue component is printing commas because the list returned by java class is a list of com.day.cq.wcm.api.Page objects and not a list of String. Sightly understands that it is a Page object and hence able to invoke page methods on it to retrieve path and title.
Getting a list of subpages - There are different ways you can do this. Depends a lot on your content structure and your requirement - what you consider are child and parent pages.
You can set an attribute in page properties for all the parents pages and use this attribute to identify a parent page and then construct a map of parent - child pages.
Or if your content structure runs more than a level deep and you need child pages from deep down the heirarchy, you can recursively fetch all the child pages and format into a data structure your vue component understands.
Or you could use Page API's like hasChild(), listChildren(), etc to drive your logic of constructing parent-child nav structure.
All this can be achieved from your java class.

Difference between currentstyle and currentdesign in AEM

In AEM speak - what is the exact conceptual difference between between currentstyle and currentdesign objects available after including the tag in template / component?
Just to add some clarify to what's already here:
Global content (that is, component values that are common across multiple instances of the same template) are stored as "styles" and edited via a "design dialog".
They are stored as follows:
/etc/designs/<design>/jcr:content/<template>/<component>
So, for the component logo in the template homepage, using the default design:
currentStyle is /etc/designs/default/jcr:content/homepage/logo
currentDesign is /etc/designs/default
currentStyle is of type com.day.cq.wcm.api.designer.Style(1) whereas currentDesign is an instance of com.day.cq.wcm.api.designer.Design (2).
The Design object contains information about the design that is used with the current object, it is most of the time set in the cq:designPath property of the page's template.
If you have the Design object, you can get Style objects for each included Cell(3) from it. On the other hand you can get the surrounding Design, if you have the Style.
So the Design is something like a container object for the Styles used in the included Cells.
In this path
/etc/designs/geometrixx/jcr:content/page/image
image is a Cell, the design path is /etc/designs/geometrixx and the Style object attached to the Cell object has two properties: maxHeight and maxWidth.
(1): https://docs.adobe.com/docs/en/cq/5-6-1/javadoc/index.html?com/day/cq/wcm/api/designer/Design.html
(2): https://docs.adobe.com/docs/en/cq/5-6-1/javadoc/index.html?com/day/cq/wcm/api/designer/Style.html
(3): https://docs.adobe.com/docs/en/cq/5-6-1/javadoc/index.html?com/day/cq/wcm/api/designer/Cell.html
I would say it is more like:
/etc/designs/<design>/jcr:content/<resource-type-of-page>/<component>
so that different templates having the same page resource type will share the same style

AEM DefaultValue written to JCR

I noticed that when I set my defaultValue for a dropdown, altho it is correctly selected in the drop down when I first add my component to the page it does not write the defaultValue to the corresponding JCR until I edit the component and save it. Even if I just open the corresponding dialog and click OK now my component works as expected because the values have been added to the JCR.
I am sure there is an important piece that I am missing here, does anyone knows how defaultValues that are required in order for the component to render properly can be added to the JCR when they are first added to the page?
Like Shwan say's that's the way it works. The default values or empty texts are only for the dialog. They aren't persisted until the dialog is authored. The properties have to be set by a different method. CQ already ships with this feature and you can do it without any custom code.
Under your component , create a node called cq:template[nt:unstructured] . If all the data is stored on the component node itself , add the default values as properties to cq:template node with name same as the ones in your dialog. In case the data is stored in a child node add a similar node under cq:template node.
Source : http://blogs.adobe.com/experiencedelivers/experience-management/defaults-in-your-component/
I believe that is simply the way it works. The default value specified in a dialog does not get used until the dialog is loaded/saved, so until that happens the node on the JCR repository that is being authored won't have the default value.
We got around this on a project by adding back-end code that was tied to the component (a tag) so that when the component was loaded, if the property did not exist, it would be written with the default the first time. Ex:
if (wcmMode == WCMMode.EDIT )
{
if(!currentNode.hasProperty("SomePropertyThatWillAlwaysExistIfTheDialogHasBeenSaved")) {
currentNode.setProperty("PropertyThatShouldHaveDefault", GlobalConstants.TRUE);
currentNode.getSession().save();
}
}
Like Sharath Madappa say's that's the way it works fine if component name and jsp name same. If you dont have componentname.jsp under component or page, cq:template won't work.(Reference:http://labs.6dglobal.com/blog/2014-07-08/using-the-cq-template/)
If you hava componentname.html under your component, changed the node [cq:template] type to [cq:Template] instead of [nt:unstructured]. In this case, defaultValues can be added to the JCR when they are first added to the page.

In CQ How to disable any field in child page properties dialog?

I have added a new selection type field "Theme" in page properties>Basic.
Now if I add a new page using the same template in WCM, there also I
am getting the option "Theme", which is quite obvious.
Is there any way by which I can hide the field in child page?
P.S this is happening because I am using the same template for the child page.
You can't use the same template and have the page property dialogs be different.
What you can do is overload the dialog
create a new template and corresponding resourceType component that
inherit from your current.
copy the dialog, or tab that you want to be different from the lowest parent of the component. Make sure the dialog is the only node under the component.
Make the changes you want to the dialog.
You would then have to include code in the page jsp to get the parent page property something like:
// if the parent page is always a certain level below the root you can use
// currentPage.getAbsoluteParent(3); to get the third page down from the top
// of the current path tree.
Page parentPage = currentPage.getParent();
ValueMap parentPageProperties;
if (parentPage != null) {
parentPageProperties = parentPage.getProperties();
}
// This tries to get the property 'theme' from the current page. If that fails
// then it tries to get the property from the parent page. If that fails it
// defaults to blank.
theme = properties.get("theme", parentPageProperties.get("theme", ""));
A quick solution would also be to create a second set of template / page component. Let's assume you've got template A, that uses page component B as resource type:
Create template X and play with allowedParents allowedChildren and allowedPaths properties so that the two are exclusive (actual solution depends on your content architecture)
Give X same title as A
Create page component Y that extends B, and defines it's own dialog
Make Y's dialog re-use any tabs from B using xtype=cqinclude (see foundation page's dialog for reference)