May 10, 2020
Estimated Post Reading Time ~

CQ Component Plugin or How I lost my XML

The Components of Yesteryear
The Component's Nature
.content.xml
<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"
cq:isContainer="{Boolean}false"
jcr:primaryType="cq:Component"
jcr:title="Title"
componentGroup="Client"/>

The Component's Authorability
dialog.xml
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="cq:Dialog" title="Title" xtype="dialog">
<items jcr:primaryType="cq:TabPanel">
<items jcr:primaryType="cq:WidgetCollection">
<tab1 jcr:primaryType="cq:Widget" title="Title" xtype="panel">
<items jcr:primaryType="cq:WidgetCollection">
<linkText jcr:primaryType="cq:Widget" fieldLabel="Title"
fieldDescription="Title for this component" name="./title"
xtype="textfield" />
</items>
</tab1>
</items>
</items>
</jcr:root>

The Component's Editing Context
_cq_editConfig.xml
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" 
xmlns:jcr="http://www.jcp.org/jcr/1.0"
cq:actions="[text:Title ,-,EDITDELETE,-]"
cq:layout="editbar"
cq:dialogMode="floating"
jcr:primaryType="cq:EditConfig"/>
</jcr:root>

The Component's Business Logic
Java Backing Bean
public class Title {
private final String title;

public Title(SlingHttpServletRequest request) {
ValueMap properties = request.getResource().adaptTo(ValueMap.class);
this.title = properties.get("title", "DEFAULT TITLE");
}

public String getTitle() {
return title;
}
}

The Component's View
JSP
<%@include file="/libs/foundation/global.jsp"%>
<%@ page import="com.client.components.content.title.Title" %>

<c:set var="title" value="<%=new Title(slingRequest) %>"/>

${title.title}

Issues with the current process
The fracturing of Component Definition
Complicated/Schemaless XML
Development Process Encouraged By The Complexity
Introducing...
The CQ Component Plugin
What is the CQ Component Plugin?
The CQ Component Plugin is a Maven/Gradle plugin that uses annotations inside of the backing beans for components to create the required XML files for a component.

Overview
How To Use It (Maven)
<plugin>
    <groupId>com.citytechinc.cq.cq-component-plugin</groupId>
    <artifactId>cq-component-maven-plugin</artifactId>
    <version>2.6.0</version>
    <executions>
        <execution>
            <goals>
                <goal>component</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <componentPathBase>jcr_root/apps/client/components</componentPathBase>
        <defaultComponentGroup>Client Group</defaultComponentGroup>
    </configuration>
</plugin>

How To Use It (Gradle)
buildscript {
    repositories {
       mavenLocal()
       mavenCentral()
    }
    dependencies {
classpath group: 'com.citytechinc.cq.cq-component-plugin', 
name: 'cq-component-maven-plugin', version: '2.6.0'
    }
}

componentPlugin {
    componentPathBase = "jcr_root/apps/project/components"
    defaultComponentGroup="Client Group"
}

install.dependsOn generateComponents

How it works

  • Runs after the package are created
  • Scans classes/jars for annotations
  • Adds dialog.xml, _cq_editConfig.xml, and .content.xml to package

Examples
Basic Example
@Component(value = "Title")
public class Title {

@DialogField(fieldLabel = "Title", fieldDescription = "Our title")
private final String title;

public Title(SlingHttpServletRequest request) {
ValueMap properties = request.getResource().adaptTo(ValueMap.class);
this.title = properties.get("title", "DEFAULT TITLE");
}

public String getTitle() {
return title;
}
}

Basic Example
@Component(value = "Title")
public class Title {

@DialogField(fieldLabel = "Title", fieldDescription = "Our title")
private final String title;

public Title(SlingHttpServletRequest request) {
ValueMap properties = request.getResource().adaptTo(ValueMap.class);
this.title = properties.get("title", "DEFAULT TITLE");
}

public String getTitle() {
return title;
}
}

Basic Example
@Component(value = "Title")
public class Title {

@DialogField(fieldLabel = "Title", fieldDescription = "Our title")
private final String title;

public Title(SlingHttpServletRequest request) {
ValueMap properties = request.getResource().adaptTo(ValueMap.class);
this.title = properties.get("title", "DEFAULT TITLE");
}

public String getTitle() {
return title;
}
}

Basic Example
@Component(value = "Title")
public class Title {

private final ValueMap properties;

public Title(SlingHttpServletRequest request) {
properties = request.getResource().adaptTo(ValueMap.class);
}

@DialogField(fieldLabel = "Title", fieldDescription = "Our title")
public String getTitle() {
return properties.get("title", "DEFAULT TITLE");;
}
}

Basic Example
Complex Widgets and Tabs Example
@Component(value = "Mixed Tabs", tabs = { @Tab(title = "First tab"),
@Tab(path = "/libs/foundation/components/page/tab_basic.infinity.json"), 
@Tab(title = "Last tab") })
public class MixedTabs {
@DialogField(fieldLabel = "Title", fieldDescription = "Our title", tab = 1)
@MultiField
private String title;

@DialogField(fieldLabel = "Image", tab = 3)
@Html5SmartImage(disableZoom = true, name = "image", allowCrop=true, 
allowUpload = false, tab = false, height = 150)
private String image;
}

Complex Widgets and Tabs Example
Extending a Component
Original Title
@Component(value = "Title")
public class Title {

@DialogField(fieldLabel = "Title", fieldDescription = "Our title", ranking = 1)
private final String title;

public Title(SlingHttpServletRequest request) {
ValueMap properties = request.getResource().adaptTo(ValueMap.class);
this.title = properties.get("title", "DEFAULT TITLE");
}

public String getTitle() {
return title;
}
}

Title with Link
@Component("Title With Link")
public class TitleWithLink extends Title {

@DialogField(fieldLabel = "Link", fieldDescription = "Our link", ranking = 2)
@PathField
private final String link;

public TitleWithLink(SlingHttpServletRequest request) {
super(request);
ValueMap properties = request.getResource().adaptTo(ValueMap.class);
this.link = properties.get("link", String.class);
}

public String getLink() {
return link;
}

}

Title with Link
Reusability
Using objects provides the ability to use dialog elements and authoring experiences across multiple dialogs with no additional effort

A Basic Link
public class Link {
@DialogField(fieldLabel = "Link Path")
@PathField
private String linkPath;

@DialogField(fieldLabel = "Link Text")
private String linkText;

public String getLinkPath() {
return linkPath;
}

public void setLinkPath(String linkPath) {
this.linkPath = linkPath;
}

public String getLinkText() {
return linkText;
}

public void setLinkText(String linkText) {
this.linkText = linkText;
}
}

A Link With a Title
@Component(value = "Single Link")
public class SingleLink {

@DialogField(fieldLabel = "Title")
private final String title;

@DialogField
@DialogFieldSet
private final Link link;

public SingleLink(SlingHttpServletRequest request) {
ValueMap properties = request.getResource().adaptTo(ValueMap.class);
title = properties.get("title", "DEFAULT TITLE");
String path = properties.get("linkPath", "/content/defualtpath");
String linkText = properties.get("linkText", "DEFAULT LINK TITLE");
link = new Link();
link.setLinkPath(path);
link.setLinkText(linkText);
}

public String getTitle() {
return title;
}

public Link getLink() {
return link;
}
}

A Link With a Title
A title with 3 Links
@Component(value = "Three Links")
public class ThreeLinks {

@DialogField(fieldLabel = "Title")
private final String title;

@DialogField(fieldLabel = "Link 1")
@DialogFieldSet(namePrefix = "link1/")
private final Link link1;

@DialogField(fieldLabel = "Link 2")
@DialogFieldSet(namePrefix = "link2/")
private final Link link2;

@DialogField(fieldLabel = "Link 3")
@DialogFieldSet(namePrefix = "link3/")
private final Link link3;

public ThreeLinks(SlingHttpServletRequest request) {
ValueMap properties = request.getResource().adaptTo(ValueMap.class);
title = properties.get("title", "DEFAULT TITLE");
link1 = new Link();
link1.setLinkPath(properties.get("link1/linkPath", "/content/defualtpath"));
link1.setLinkText(properties.get("link1/linkText", "DEFAULT LINK TITLE"));

//More Logic
}
}

A title with 3 Links
A title with unlimited Links
@Component(value = "Multi Links")
public class MultiLinks {

@DialogField(fieldLabel = "Title")
private final String title;

@DialogField
@DialogFieldSet
@MultiCompositeField
private final List links;

public MultiLinks(SlingHttpServletRequest request) {
ValueMap properties = request.getResource().adaptTo(ValueMap.class);
title = properties.get("title", "DEFAULT TITLE");
links = new ArrayList();

Iterable resources = request.getResource().getChild("links").getChildren();
for (Resource r : resources) {
ValueMap linkProps = r.adaptTo(ValueMap.class);
String path = linkProps.get("linkPath", "/content/defualtpath");
String linkText = linkProps.get("linkText", "DEFAULT LINK TITLE");
Link link = new Link();
link.setLinkPath(path);
link.setLinkText(linkText);
links.add(link);

}
}

public String getTitle() {
return title;
}

public List getLinks() {
return links;
}
}

A title with unlimited Links
Extending the plugin

  • Adding Widgets
  • Adding Transformers

Adding a widget
The annotation
@Retention(RetentionPolicy.CLASS)
@Target({ ElementType.METHOD, ElementType.FIELD })
public @interface MultiCompositeField {

boolean matchBaseName() default false;

String prefix() default "./";
}

Adding a widget
The Maker
public final class MultiCompositeFieldWidgetMaker extends AbstractWidgetMaker {

private static final String FIELD_CONFIGS = "fieldConfigs";

public MultiCompositeFieldWidgetMaker(WidgetMakerParameters parameters) {
super(parameters);
}

@Override
public DialogElement make() throws ClassNotFoundException, SecurityException, InvalidComponentFieldException,
NotFoundException, CannotCompileException, NoSuchFieldException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException {
MultiCompositeField multiCompositeFieldAnnotation = getAnnotation(MultiCompositeField.class);

MultiCompositeFieldWidgetParameters widgetParameters = new MultiCompositeFieldWidgetParameters();

widgetParameters.setMatchBaseName(multiCompositeFieldAnnotation.matchBaseName());
widgetParameters.setPrefix(multiCompositeFieldAnnotation.prefix());
widgetParameters.setFieldName(getFieldNameForField());
widgetParameters.setFieldLabel(getFieldLabelForField());
widgetParameters.setFieldDescription(getFieldDescriptionForField());
widgetParameters.setAdditionalProperties(getAdditionalPropertiesForField());
widgetParameters.setHideLabel(getHideLabelForField());
widgetParameters.setName(getNameForField());
widgetParameters.setAllowBlank(!getIsRequiredForField());
widgetParameters.setDefaultValue(getDefaultValueForField());
widgetParameters.setListeners(getListeners());
widgetParameters.setContainedElements(buildWidgetCollection(multiCompositeFieldAnnotation));

return new MultiCompositeFieldWidget(widgetParameters);
}

.
.
.
}

Adding a widget
The widget
@Widget(annotationClass = MultiCompositeField.class, 
makerClass = MultiCompositeFieldWidgetMaker.class, 
xtype = MultiCompositeFieldWidget.XTYPE)
public final class MultiCompositeFieldWidget extends AbstractWidget {

public static final String XTYPE = "multicompositefield";

private final boolean matchBaseName;

private final String prefix;

public MultiCompositeFieldWidget(MultiCompositeFieldWidgetParameters parameters) {
super(parameters);
this.matchBaseName = parameters.isMatchBaseName();
this.prefix = parameters.getPrefix();
}

public String getPrefix() {
return prefix;
}

public boolean isMatchBaseName() {
return matchBaseName;
}
}

Helpful Links
https://github.com/Citytechinc/cq-component-maven-plugin

Source: https://slidedeck.io/michaelhodgdon/circuit-component-plugin


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.