Tag: AEM6

Custom Responsive grid/Layout container

A custom responsive layout container can be created by extending the OOTB Responsive Grid (Layout Container).

Please follow the following steps:

Step 1: Copy OOTB Responsive Grid component

OOTB responsive grid’s path: /libs/wcm/foundation/components/responsivegrid

  • Copy the component to the target project
  • Update name for the component.
    • Since, component name would be added by default in the decoration tags, it might help add specific CSS for the container.
  • Update jcr:title & componentGroup to uniquely identify the component.

 

Step 2: Extend OOTB responsive grid

Update sling:resourceSuperType of the custom container as:

sling:resourceSuperType=”wcm/foundation/components/responsivegrid”

Custom Container.PNG

Step 3: Make it responsive 

At this point, the component can added to the pages, but will not be responsive in the Layouting Mode (i.e. resizing the container / containing elements would have no visual effect).

Responsiveness can be achieved by overriding ns.responsive.isResponsiveGrid() of  /libs/cq/gui/components/authoring/clientlibs/editor/js/responsive/responsive.js. Please find the steps below:

  • Create clientlibs (cq:ClientLibraryFolder) below your component.
  • Add a javascript file. It can be added to source sub-folder (type=nt:folder)  below the clientlibs. Add following snippet to the file:
 //Fix to declare custom container as responsive
(function ($, ns, channel, window, undefined) {
    ns.responsive.isResponsiveGrid = function (editable) { 
        return editable.type === 'wcm/foundation/components/responsivegrid' || '/apps/<app_name>/<path_to_custom_container>'; 
    } 
}
(jQuery, Granite.author, jQuery(document), this)); 
  • Add js.txt below clientlibs. Update js.txt with the path to the javascript file.
  • Add ‘cq.authoring.editor’ category to component’s clientlibs

 

Step 4: Update the component with the desired behavior

The component is both available for use & responsive. You can update the component script as per the business requirement. Also, delete the scripts that are not being overwritten. The deleted scripts would be inherited via Sling resource merger

 

Adding responsive CSS for the Custom Responsive Grid

A custom CSS file for various devices can be created similar to OOTB responsive Grid.

 

Configuring breakpoints

To configure breakpoints for the site, follow the steps detailed at Adobe Site

 

Advertisements

Default component configurations via Conf Nodes

Have you faced issue of design dialog configurations being overwritten by deployment?

Starting AEM 6.1, AEM provides ability to avoid design configuration overwrite, by extracting configurations from /etc to /conf.

Example:

Lets consider a scenario where business had requested a default title ‘Contact Us’ for a component. Few months later, they change the title to ‘Contact with us’ via Design Dialog.

Your development team is working on second version of the Contact Us component, to add a new field for Email ID with default value ‘contactus@adobe.com’

Once completed, the team deploys the second version of component with default value of email ID. Result would be “Design configuration ‘Contact with us’ configured by business, is restored to old value ‘Contact Us'”

Resolution through /conf to fix design configuration overwrite

  1. Use mode=”merge” in filter.xml for deploying design. This would ensure that on deployment existing nodes are not deleted. For more details refer to link
    • <filter root=”/etc/designs/<app_name>/jcr:content” mode=”merge”/>
  2. Configure default values in /conf: Sling Models can then be used to resolved values in required preference order. Example: Edit -> Design -> Default (from /conf path)

Steps to configure default values in /conf

Step 1: Define Configurations

In this step, we create a suitable configuration hierarchy considering rules described below. Also, add required default configurations in the jcr:content nodes.

Capture.PNG

In the above image, we can observe that configuration hierarchy should have:

  1. ‘sling:Folder’ structure below ‘/conf’ to define the site/app that the configurations belong to.
  2. ‘settings’ of type ‘sling:Folder’ which holds all the configurations.
  3. Configuration nodes below ‘setting’ of type ‘cq:Page’
  4. Configuration properties in ‘jcr:content’ of configuration nodes

 

Step 2: Add cq:conf property to the root of the project.

type: String

value: Site’s configuration path

Capture1.PNG

Step 3: Provide read permission to the target users & authors on:

  • Site configuration node under /conf
  • Root node of the site where cq:conf is configured.

 

Step 4: Use a Sling Model to derive the values in the required  order (edit, design, conf).

There are 2 APIs that could be utilized:

  1. com.adobe.granite.confmgr.Conf resolves the configuration from currentPage
  2. com.adobe.granite.confmgr.ConfMgr resolves the configuration from resource.

Fetch default configuration via com.adobe.granite.confmgr.Conf

import javax.annotation.PostConstruct;
import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.models.annotations.Model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.granite.confmgr.Conf;
import com.day.cq.wcm.api.NameConstants;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.designer.Style;

@Model(adaptables = SlingHttpServletRequest.class)
public class ContactUsModel {
private static final Logger LOG = LoggerFactory.getLogger(ContactUsModel.class);

   @Inject
   private Page currentPage;

   private ValueMap contactUsSettings;

   @Inject
   private Style currentStyle;

   @PostConstruct
   protected void init() {
       Conf conf = currentPage.adaptTo(Conf.class);
       if(conf==null){
           LOG.warn("Configurations not found for: " + currentPage.getPath());
       } else {
           String templatePath = currentPage.getProperties().get(NameConstants.NN_TEMPLATE, "");
           contactUsSettings = conf.getItem(templatePath+"/contactUsComponent");
       }
   }

   public String getDesignTitle() {
       /*
       * Return title in following preference order:
       * 1. Title from Design
       * 2. Title from conf
       */
       String designTitle = currentStyle.get("title", "");
       if(StringUtils.isEmpty(designTitle)){
           //Title not configured in Design. Fetching from /conf
           LOG.debug("Fetching default title from /conf");
           return contactUsSettings.get("contactUsTitle", "");
       } else {
           //Title configured in Design.
           LOG.debug("Title configured in design dialog: " + designTitle);
           return designTitle;
       }
    }
}

Fetch default configuration via com.adobe.granite.confmgr.ConfMgr

import javax.annotation.PostConstruct;
import javax.inject.Inject;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.models.annotations.Model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.granite.confmgr.Conf;
import com.adobe.granite.confmgr.ConfMgr;

@Model(adaptables = SlingHttpServletRequest.class)
public class ContactUsModel {
private static final Logger LOG = LoggerFactory.getLogger(ContactUsModel.class);

    @Inject
    private ConfMgr confMgr;

    @Inject
    private Resource currentResource;

    private ValueMap contactUsSettings;

    @PostConstruct
    protected void init() {
       final Conf conf = confMgr.getConf(currentResource);
       if(conf==null){
           LOG.warn("Configurations not found for: " + currentResource.getPath());
       } else {
           contactUsSettings = conf.getItem("/conf/techrevel/settings/apps/confEx/templates/someTemplate/contactUsComponent");
       }
    }

    public String getTitle() {
        LOG.debug("Fetching default title from /conf");
        return contactUsSettings.get("contactUsTitle", "");
    }
}

For more details, please refer to link

ResourceChangeListener v/s Sling Event

With introduction of ResourceChangeListener, this blog is an effort to help developers choose between ResourceChangeListener and Sling Event for an implementation.

ResourceChangeListener:

pros:

  • Can be configured to listen to only specific paths. Multiple watch paths can also be configured to provide it a more granular approach.
  • For a bulk operation concerning N resources, listener will be executed ONLY once. Thus, total number of persistence operations = 1 (via handler) + 1 (Bulk operation) = 2

cons:

Sling Event:

pros:

cons:

  • Cannot restrict events to a certain path. Sling Event listens to all nodes starting from the root node of a repository (/)
  • For a bulk operation concerning N resources, handler will be called N times. Thus, total number of persistence operations = N (via handler) + 1 (Bulk operation) = N + 1

Sling Model annotations

Creating a Sling Model

Annotation used:

  • @Model: declares a bean as Sling Model.
  • @Inject: Injects resource property into a class variable.
  • @PostConstruct: declares the function which would initialize the bean after deriving information based on business logic. The function is called after the all injections have completed.

Example:

  • In the above example, ‘adaptables = Resource.class’ maps the sling model against a Sling Resource.
  • The @Inject would inject ‘contacts’ property from the resource into ‘contacts’ class variable. The property value injection occurs after adapting the Resource into Value Map.

Optional/Required fields

@Model annotation provides ‘defaultInjectionStrategy’ attribute to indicate if the injected fields in a sling model should be required/optional.

Injection strategy on class level:
  • Use ‘defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL’ to mark all injected fields as optional
  • Use ‘defaultInjectionStrategy = DefaultInjectionStrategy.REQUIRED’ to mark all injected fields as required. Its also the default configuration, if ‘defaultInjectionStrategy’ is not specified.
Injection strategy on Field level:
  • @Required / @Optional annotations can be used to selectively mark fields as required/optional.

Example:

Adapt Model from Resource / SlingHttpServletRequest

A Models can be adapted both Resource / SlingHttpServletRequest. This depends upon the fields required to be injected.

  • If only resource properties are required, prefer using:

  •  If Sling Objects (eg. currentStyle, currentPage, xssApi etc) are also required, prefer adapting to SlingHttpServletRequest.

Default Value

@Default allows to specify default value for fields (including arrays).
Example:

    Refer to link, to use attribute names matching with the field’s type.

Alternate field name

Use @Named if the field name is different from the property name.

Via

To inject field based on JavaBean property of the adaptable. Use with ‘SlingHttpServletRequest’ to inject property value from the resource.

Self

Inject adaptable object to a field.

Additional Notes

Design Object

The Design object can be accessed on adapting model to Resource / SlingHttpServletRequest

Injecting currentStyle object

Used when the Sling Model is adapted from SlingHttpServletRequest. Example:

Fetch currentStyle object from Resource

Used when the Sling Model is adapted from Resource. Example:

Inject OSGi Services

OSGi services can be injected via @Inject. The annotation could be used while adapting to both Resource and SlingHttpServletRequest

Sling-based ResourceChangeListener

With AEM 6.2, a new sling observation support has been provided. The Sling alternative, called “ResourceChangeListener” is only recommended for resource change events. Non-resource events (ex. Workflow events), should be handled via Sling Event Handlers.

For more parameters to help resolve the choice between listener and sling events, refer to link

Benefits of ResourceChangeListener:

  • Avoid long-lived sessions.
  • Avoid Oak observation queue size issues.

Sample Implementation:

@Component(immediate = true)
@Service(value = ResourceChangeListener.class)
@Properties(value = {
@Property(name = ResourceChangeListener.PATHS, value = { "/content/unhcr/intranet" }),
@Property(name = ResourceChangeListener.CHANGES, value = { "CHANGED", "ADDED"}, propertyPrivate=true)
})
public class PropertyMergeListener implements ResourceChangeListener {

    @Reference
    private ResourceResolverFactory resourceResolverFactory;

    /**
    * Called when a resource is added/deleted/modified in Target path
    * @param resourceChangeList List of resource Changes identified by Listener
    */
    @Override
    public void onChange(List<ResourceChange> resourceChangeList) {
        Map<String, Object> param = new HashMap<String, Object>();
        param.put(ResourceResolverFactory.SUBSERVICE, "SubServiceName");
        ResourceResolver resourceResolver = null;
        try {
            resourceResolver = resourceResolverFactory.getServiceResourceResolver(param);

            for (ResourceChange resourceChange : resourceChangeList) {
                String resourcePath = resourceChange.getPath();
                Resource resource = resourceResolver.getResource(resourcePath);

                ...

 

Touch UI – Checkbox for multifield

Problem Description: Consider adding a multifield (with checkbox) to a Component Edit dialog in Touch UI.

An author selects one/many checkbox in multifield and saves the Dialog.

Issue: On reopening the dialog, the state of the checkbox is not retained. In other words, no checkbox is selected.

Solution (Verified on AEM 6.2 and ACS-Commons 3.3.0):

  1. Install ACS Commons. It would allow the multifield values to be stored via nodestore/jsonstore. Steps on how to create a multifield can be found at below mentioned links:
    • to persist values via NODE_STORE, visit link
    • to persist values via JSON_STORE, visit link
  2. Configure ‘value’ configuration for the checkbox as indicated below:
<fieldName
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/checkbox"
name="./persistent_property_name"
text="Field_Label"
value="some_value"/>

Verify the checkbox by checking, saving dialog & reopening dialog again.

Thanks !