The component dialog has a pathfield widget where the authors can set a page path. In the Sightly component, I would like to look up that page resource and get (and display) properties from it.
The dialog...
<linkedPathLocation jcr:primaryType="cq:Widget"
fieldLabel="Linked Path"
name="./linkedPathLocation"
xtype="pathfield"
fieldDescription="Select a page. URL, Title, Description and Image are properties of the selected page"/>
The component code I would like to work (it's not).
<div class="row" data-sly-resource.page = "${properties.linkedPathLocation}">
<h1 >${page.title}</h1>
<p>${page.description}</p>
</div>
My question: Is there a way in Sightly to resolve and use some resource from a given path? If not, I could create a USE-API class and to do the following...
Page page = resourceResolver.resolve("/path/to/resource").adaptTo(Page.class);
I feel there should be a better answer which allows resources to be resolved directly from the Sightly, but the following USE-API solution works if not...
Java Use Class
public class PageHelper extends WCMUsePojo {
String pagePath = "";
#Override
public void activate() {
pagePath = get("path", String.class);
}
public Page getPage() {
return this.getPageManager().getPage(pagePath);
}
}
The component Sightly...
<div class="row" data-sly-use.linkedPage = "${'com.package.PageHelper' # path = properties.linkedPathLocation}">
<h1 >${linkedPage.page.title}</h1>
<p>${linkedPage.page.description}</p>
</div>
What you are trying to do, in essence, is render a Resource within the context of the rendering of another Resource. data-sly-resource seems the appropriate attribute to use, but instead of attempting to nest additional elements into the element containing the data-sly-resource you should define another Sightly .html file which dictates how the nested resource is to be rendered.
Let us say that your Resource is of type application/components/content/type. Within type.html you might have the following statement
<sly data-sly-resource="${properties.linkedPathLocation} # resourceType='application/components/content/type/subtype' />
You would then be able to define /apps/application/components/content/type/subtype/subtype.html containing the rendering to produce which would be invoked in the context of the Resource identified by your path.
The main purpose of Sightly templates is separation of responsibility (frontend & backend) so that we can have simple, clean and beautiful html markup which is designer friendly and easily readable.
For your case, writing a Class (Java/Javascript) to process dialog information, and supply it back to Sightly template is correct way. Read here for more details.
Related
I have a component that uses two different resources in its HTL/Sightly template.
Is there a way to pass a parameter, to the say in my example the eventResource template, so that I can depending on this passed paramter change a css class on it?
<ul data-sly-list.teasers="${model.resourceList}" class="teaser-list-alternating c-list--reset">
<sly data-sly-test="${teasers.eventTeaser}"
data-sly-resource="${teasers.resource # resourceType='xxx/components/content/eventTeaser'}"></sly>
<li data-sly-test="${teasers.contentTeaser}" class="l-stack l-stack--horse"
data-component-hook-content-hub="teaser"
data-sly-resource="${teasers.resource # resourceType='xxx/components/content/contentHubTeaser'}"></li>
</ul>
I tried using data-sly-resource="${teasers.resource # resourceType='xxx/components/content/eventTeaser', requestAttributes=model.config, selectors='blabla'} to no availability.
#RequestAttribute(name = "contentHub")
private String contentHub;
The requestAttribute contentHub in the eventTeaser model is alway null and I am not sure how to get the selectors value in the eventTeaser template.
I can do it using TypeScript on the front end part but it is not very clean.
I was able to solve it using the selectors indeed and reading the selector value directly from the other sightly template. Here is the documentation I refered to:
data-sly-resource="${teasers.resource # resourceType='xxx/components/content/eventTeaser', requestAttributes=model.config, selectors='inContentHub'}
In the eventTeaser template I then used the following:
data-sly-set.inContentHub="${'inContentHub' == request.requestPathInfo.selectorString}
and depending on the value of the inContentHub variable I was able to change the css class which was my ultimate goal.
I started by creating xml from the ribbon designer. I already have a resource file containing button images.
I got lines like
<button id="btnInsEq2" onAction="BtnInsEq2_Click" showLabel="false" />
which I changed to
<button id="btnInsEq2" onAction="BtnInsEq2_Click" image="InsEquation2" showImage="true" showLabel="false" />
There is some image called InsEquation2 in my resource file, which is accessible at runtime. I tried many variations on "InsEquation2" including the full path of the file. Nothing works.
I see answers where images are loaded in code. Is that really necessary or can I do it simpler in the xml?
By the way a line like
<button id="btnInsEq3" onAction="BtnInsEq3_Click" imageMso="Bold" label=" " showLabel="false" />
works fine. But I dont want B!
I would use the getImage tag like:
<button id="btnInsEq2" onAction="BtnInsEq2_Click" showLabel="false" getImage="GetImage"/>
which requires a callback. Assuming you write in C# it could look something like:
public Bitmap GetImage(Office.IRibbonControl control)
{
return new Bitmap(Properties.Resources.RelevantImage);
}
There are two ways for loading images in the Ribbon XML.
The getImage callback for each element where it is supported.
The loadImages callback for the customUI element where all images are loaded.
So, if you want to specify image names in the ribbon XML markup you need to implement the loadImages callback for the customUI element.
The <customUI> element's loadImage attribute enables you to specify a callback that can load all images. After you set up this callback procedure, Office calls the callback procedure and passes the string from the image attribute for each control that loads images. You do not need to implement the getImage callback multiple times, such as one time for each control that requires images. For example, a customization might include markup like the following.
<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui"
loadImage="GetImage">
<!-- Later in the markup -->
<button id="myButton" image="mypic.jpg" />
To supply the button's image, Office calls the GetImage function. It passes the parameter "mypic.jpg" and expects an IPictureDisp object in return. By using this technique, you can write a single callback procedure that returns all the images your customization needs, without having to write each individual control's getImage callback. Note that the loadImage callback is not called again when you call the Ribbon's Invalidate method or InvalidateControl method. For the controls that need to change images dynamically at run time, use the getImage callback.
So now I have found out what to put in the GetImage function suggested by #EugeneAstafiev . Basically you need to iterate through the resources and find the one indicated by the imageId input parameter. Here it is in XML / VB
<customUI xmlns="http://schemas ... loadImage="GetButtonImages" >
..
<group id="InsertEquations" label="Add" image="InsEquation">
<button id="btnInsEq2" onAction="BtnInsEq2_Click" image="InsEquation2"/>
Public Function GetButtonImages(imageId As String) As Bitmap 'was IPictureDisp as specified in documenation
'enumerate resources ...
'see https://stackoverflow.com/questions/4656883/how-enumerate-resources-inside-a-resx-file-programmatically
Dim ErrorBitmap As New Bitmap(My.Resources._Error)
Dim ResMan As New ResourceManager("EquationAcc.Resources1", Assembly.GetExecutingAssembly)
Dim ResSet As ResourceSet
ResSet = ResMan.GetResourceSet(CultureInfo.CurrentCulture, True, True)
For Each Item As DictionaryEntry In ResSet
'Debug.WriteLine(item.Key) 'appears in window from Debug / windows / output
If Item.Key = imageId Then
Return Item.Value
End If
Next
Return ErrorBitmap
End Function
Also needed Imports System.Drawing, .Collections, .Globalization, .Reflection, .Resources
for declarations and
.Diagnostics, stdole for some now unused declarations
EquationAcc is the project, Resources1.resx the resource file
If there is a better way I'd love to know.
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.
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.
I am trying to implement something which I hope is relatively straight forward... I have one component (lets call it the wrapper component) which contains another component (lets call it the inner component) inside it via the data-sly-resource tag:
<div data-sly-resource="${ 'inner' # resourceType='/projectname/components/inner' }"></div>
I would like to pass in some additional parameters with this tag, specifically a parameter that can be picked up by sightly in the inner component template? I am trying to specify whether the inner templates outer html tag is unwrapped based on a parameter being passed in when the component is called via data-sly-resource.
After experimenting and perusing the sightly documentation, I can't find a way of achieving this.
Does anyone know if this is possible?
Many thanks,
Dave
You can use the Use-API to write and read request attributes if the alternatives proposed here don't work for you.
A quick example of two components where the outer component sets attributes that are then displayed by the inner component:
/apps/siteName/components/
outer/ [cq:Component]
outer.html
inner/ [cq:Component]
inner.html
utils/ [nt:folder]
setAttributes.js
getAttributes.js
/content/outer/ [sling:resourceType=siteName/components/outer]
inner [sling:resourceType=siteName/components/inner]
/apps/siteName/components/outer/outer.html:
<h1>Outer</h1>
<div data-sly-use="${'../utils/setAttributes.js' # foo = 1, bar = 2}"
data-sly-resource="inner"></div>
/apps/siteName/components/inner/inner.html:
<h1>Inner</h1>
<dl data-sly-use.attrs="${'../utils/getAttributes.js' # names = ['foo', 'bar']}"
data-sly-list="${attrs}">
<dt>${item}</dt> <dd>${attrs[item]}</dd>
</dl>
/apps/siteName/components/utils/setAttributes.js:
use(function () {
var i;
for (i in this) {
request.setAttribute(i, this[i]);
}
});
/apps/siteName/components/utils/getAttributes.js:
use(function () {
var o = {}, i, l, name;
for (i = 0, l = this.names.length; i < l; i += 1) {
name = this.names[i];
o[name] = request.getAttribute(name);
}
return o;
});
Resulting output when accessing /content/outer.html:
<h1>Outer</h1>
<div>
<h1>Inner</h1>
<dl>
<dt>bar</dt> <dd>2</dd>
<dt>foo</dt> <dd>1</dd>
</dl>
</div>
As commented by #AlasdairMcLeay, this proposed solution has an issue in case the inner component is included multiple times on the request: the subsequent instances of the component would still see the attributes set initially.
This could be solved by removing the attributes at the moment when they are accessed (in getAttributes.js). But this would then again be a problem in case the inner component is split into multiple Sightly (or JSP) files that all need access to these attributes, because the first file that accesses the request attributes would also remove them.
This could be further worked-around with a flag telling wether the attributes should be removed or not when accessing them... But it also shows why using request attributes is not a good pattern, as it basically consists in using global variables as a way to communicate among components. So consider this as a work-around if the other two solutions proposed here are not an option.
There is a newer feature that request-attributes can be set on data-sly-include and data-sly-resource :
<sly data-sly-include="${ 'something.html' # requestAttributes=amapofattributes}" />
Unfortunately it doesn't seem to be possible to construct a Map with HTL (=Sightly) expressions, and I don't see a way to read a request attribute from HTL, so you still need some Java/Js code for that.
unfortunately, no. there is no way to extend sightly functionality. you cannot add new data-sly attributes or modify existing ones. The best you can do is write your own helper using the USE API
If you just need to wrap or unwrap the html from your inner component in different situations, then you can just keep the html in the component unwrapped, and wrap it only when needed by using the syntax:
<div data-sly-resource="${ 'inner' # resourceType='/projectname/components/inner', decorationTagName='div', cssClassName='someClassName'}"></div>
If you need more complex logic, and you need to pass a value to your inner component template, you can use the selectors. The syntax for including the resource with selectors is:
<div data-sly-resource="${ 'inner' # resourceType='/projectname/components/inner', selectors='mySelectorName'}"></div>
The syntax to check the selectors in the inner component is:
${'mySelectorName' in request.requestPathInfo.selectorString}"
or
${'mySelectorName' == request.requestPathInfo.selectorString}"