How to Add Clientlibs at Page Level in AEM

24 / Jun / 2025 by Mukesh Singh Yadav 0 comments

Adobe experience manager(AEM) provides Client-Side Libraries which is a mechanism to logically organise and manage CSS and JavaScript files necessary for the sites. AEM by default provides flexibility to the authors to have control of adding Clientlibs at template level and developers have flexibility to add (hard code) Clientlibs up to component level.

However, in some scenarios we need such authorable flexibility ( to have control of adding Clientlibs ) on page level as well. In this scenario, we have to compromise either by creating a new template for a few(single) pages or by keeping clientlibs (especially JS code) at component level which doesn’t suffice the exact requirements as it does not load the clientlibs in footer which is ideal for perfect DOM rendering and performance of the page.

Assume another scenario, the Vanilla JS or Jquery is getting used in the entire project but there are a few complex components which are react or Vue JS based. With the help of this solution we can add Vue JS clientlib only to the page where Vue components are authored in future, instead of creating template and setting header, footer and other common sections. Even if it is required to dynamically decide the order of clientlibs we can achieve the same with this solution.

So, let’s discuss steps of a custom solution which uses page properties to make the clientlibs authorable at page level. The solution consist of following two sections.

  1. Read clientlibs list available in project as data source
  2. Addition of clientlibs to the page properties

Section 1:- Read clientlibs as data source

We can put the below resourceType Servlet which will act as a data source to list the clientlibs as drop downs on the page properties.

package com.ttn-retail.core.servlets;
import com.adobe.cq.commerce.common.ValueMapDecorator;
import com.adobe.granite.ui.components.ds.DataSource;
import com.adobe.granite.ui.components.ds.SimpleDataSource;
import com.adobe.granite.ui.components.ds.ValueMapResource;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.iterators.TransformIterator;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceMetadata;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

@Component(service = Servlet.class,
property = {
   "sling.servlet.paths=/apps/ttn-retail/components/datasource",
   "sling.servlet.methods=GET"
})
public class ClientLibsDataSourceServlet extends SlingSafeMethodsServlet {
   private static final Logger logger = LoggerFactory.getLogger(ClientLibsDataSourceServlet.class);
   @Override
   protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
   try {
      ResourceResolver resourceResolver = request.getResourceResolver();
      Resource resource = resourceResolver.getResource("/apps/ttn-retail/clientlibs");
      Map<String, String> map = new LinkedHashMap<>();
      if (resource != null) {
        Iterator<Resource> children = resource.listChildren();
        while (children.hasNext()) {
          Resource child = children.next();
          String[] categories = child.getValueMap().get("categories", String[].class);
          if (categories != null) {
           for (String category : categories) {
             map.put(category, category);
            }
          }
        }
      }

    DataSource ds = new SimpleDataSource(
      new TransformIterator<>(map.keySet().iterator(), (Transformer<String, Resource>) key -> {
        ValueMap vm = new ValueMapDecorator(new HashMap<>());
        vm.put("value", key);
        vm.put("text", map.get(key));
        return new ValueMapResource(resourceResolver, new ResourceMetadata(), "nt:unstructured", vm);
       })
     );
   request.setAttribute(DataSource.class.getName(), ds);
     } catch (Exception exception) {
       logger.error("Error while retrieving clientlibs datasource: {} ", exception.getMessage());
     }
   }
}

Note: Ensure to change the lines “sling.servlet.paths=/apps/ttn-retail/components/datasource” and Resource resource = resourceResolver.getResource(“/apps/ttn-retail/clientlibs“); in the servlet as per your project structure as well as package name where ever applicable throughout the blog.

Section 2:-  Addition of clientlibs to the page properties

2.1 Navigate to the required (desired) tab hierarchy ( lets add the field on the basic tab itself for keeping it simple as of now ) inside cq:dialog of the page component. Create a node of type nt:unstructured with sling:resourceType “granite/ui/components/coral/foundation/form/select” and other properties as displayed below in the image or xml.

<clientlibs
  cq:showOnCreate="{Boolean}true"
  jcr:primaryType="nt:unstructured"
  sling:resourceType="granite/ui/components/coral/foundation/form/select"
  fieldLabel="Page Specific Client Libraries"
  multiple="{Boolean}true"
  name="./clientlibs">
   <datasource
      jcr:primaryType="nt:unstructured"
      sling:resourceType="/apps/ttn-retail/components/datasource"/>
</clientlibs>
Clientlibs as page properties

Clientlibs as page properties

Resource Type datasource

Data Source

2.2 Create Sling model (Java class) to read the clientlibs from page properties so that the same can be used to fetch in source code of html page.

package com.ttn-retail.core.models;
import com.day.cq.wcm.api.Page;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ScriptVariable;
@Model(adaptables = SlingHttpServletRequest.class)
public class ClientLibsFromPagePropertiesModel {
@ScriptVariable
private Page currentPage;
public String[] getClientLibs() {
      return currentPage != null ? currentPage.getProperties().get("clientlibs", String[].class) : null;
}
}

2.3 Use sling model created in step 2.2 to add the authored clientlibs if any to customfooter.html file.

<sly data-sly-use.clientlib="/libs/granite/sightly/templates/clientlib.html" data-sly-use.clientlibsModel="com.ttn-retail.core.models.ClientLibsFromPagePropertiesModel">
     <sly data-sly-call="${clientlib.js @ categories=clientlibsModel.clientLibs}"></sly>
</sly>

If required, we can put css for the respective clientlibs by updating customheaderlibs.html of the page component in our project.

Custom footerlibs html

Custom footerlibs html

 

<sly data-sly-use.clientlib="/libs/granite/sightly/templates/clientlib.html" data-sly-use.clientlibsModel="com.ttn-retail.core.models.ClientLibsFromPagePropertiesModel">
     <sly data-sly-call="${clientlib.css @ categories=clientlibsModel.clientLibs}"></sly>
</sly>

Result:

Open any page and navigate to the ‘clientlibs property’ tab of the page to author and validate the functionality as displayed in below images.

clientlibs page properties

Clientlibs as Page Properties

clientlibs as dropdown

Clientlibs

After saving the changes, open the page in “view as published” mode to inspect and verify the authored clientlibs in source code or, even the same can be validated from the network tab.

Hope, you find the blog helpful. If similar functionality comes in your project do not hesitate to customise the solution.

FOUND THIS USEFUL? SHARE IT

Leave a Reply

Your email address will not be published. Required fields are marked *