{"id":71734,"date":"2025-06-24T16:57:46","date_gmt":"2025-06-24T11:27:46","guid":{"rendered":"https:\/\/www.tothenew.com\/blog\/?p=71734"},"modified":"2025-08-27T13:41:08","modified_gmt":"2025-08-27T08:11:08","slug":"page-specific-clientlibs-in-aem","status":"publish","type":"post","link":"https:\/\/www.tothenew.com\/blog\/page-specific-clientlibs-in-aem\/","title":{"rendered":"How to Add Clientlibs at Page Level in AEM"},"content":{"rendered":"<p>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.<\/p>\n<p>However, in some scenarios we need such authorable flexibility ( to have control of adding Clientlibs ) on <em><strong>page level<\/strong> <\/em>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&#8217;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.<\/p>\n<p>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.<\/p>\n<p>So, let&#8217;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.<\/p>\n<ol style=\"list-style-type: lower-roman;\">\n<li>Read clientlibs list available in project as data source<\/li>\n<li>Addition of clientlibs to the page properties<\/li>\n<\/ol>\n<h3>Section 1:- Read clientlibs as data source<\/h3>\n<p>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.<\/p>\n<pre>package com.ttn-retail.core.servlets;\r\nimport com.adobe.cq.commerce.common.ValueMapDecorator;\r\nimport com.adobe.granite.ui.components.ds.DataSource;\r\nimport com.adobe.granite.ui.components.ds.SimpleDataSource;\r\nimport com.adobe.granite.ui.components.ds.ValueMapResource;\r\nimport org.apache.commons.collections4.Transformer;\r\nimport org.apache.commons.collections4.iterators.TransformIterator;\r\nimport org.apache.sling.api.SlingHttpServletRequest;\r\nimport org.apache.sling.api.SlingHttpServletResponse;\r\nimport org.apache.sling.api.resource.Resource;\r\nimport org.apache.sling.api.resource.ResourceMetadata;\r\nimport org.apache.sling.api.resource.ResourceResolver;\r\nimport org.apache.sling.api.resource.ValueMap;\r\nimport org.apache.sling.api.servlets.SlingSafeMethodsServlet;\r\nimport org.osgi.service.component.annotations.Component;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport javax.servlet.Servlet;\r\nimport javax.servlet.ServletException;\r\nimport java.io.IOException;\r\nimport java.util.HashMap;\r\nimport java.util.Iterator;\r\nimport java.util.LinkedHashMap;\r\nimport java.util.Map;\r\n\r\n@Component(service = Servlet.class,\r\nproperty = {\r\n   \"sling.servlet.paths=<strong><em>\/apps\/ttn-retail\/components\/datasource<\/em><\/strong>\",\r\n   \"sling.servlet.methods=GET\"\r\n})\r\npublic class ClientLibsDataSourceServlet extends SlingSafeMethodsServlet {\r\n   private static final Logger logger = LoggerFactory.getLogger(ClientLibsDataSourceServlet.class);\r\n   @Override\r\n   protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {\r\n   try {\r\n      ResourceResolver resourceResolver = request.getResourceResolver();\r\n      Resource resource = resourceResolver.getResource(\"<em><strong>\/apps\/ttn-retail\/clientlibs<\/strong><\/em>\");\r\n      Map&lt;String, String&gt; map = new LinkedHashMap&lt;&gt;();\r\n      if (resource != null) {\r\n        Iterator&lt;Resource&gt; children = resource.listChildren();\r\n        while (children.hasNext()) {\r\n          Resource child = children.next();\r\n          String[] categories = child.getValueMap().get(\"categories\", String[].class);\r\n          if (categories != null) {\r\n           for (String category : categories) {\r\n             map.put(category, category);\r\n            }\r\n          }\r\n        }\r\n      }\r\n\r\n    DataSource ds = new SimpleDataSource(\r\n      new TransformIterator&lt;&gt;(map.keySet().iterator(), (Transformer&lt;String, Resource&gt;) key -&gt; {\r\n        ValueMap vm = new ValueMapDecorator(new HashMap&lt;&gt;());\r\n        vm.put(\"value\", key);\r\n        vm.put(\"text\", map.get(key));\r\n        return new ValueMapResource(resourceResolver, new ResourceMetadata(), \"nt:unstructured\", vm);\r\n       })\r\n     );\r\n   request.setAttribute(DataSource.class.getName(), ds);\r\n     } catch (Exception exception) {\r\n       logger.error(\"Error while retrieving clientlibs datasource: {} \", exception.getMessage());\r\n     }\r\n   }\r\n}\r\n<\/pre>\n<p><span style=\"text-decoration: underline;\"><em><strong>Note:<\/strong><\/em><\/span><em><strong>&#8211;<\/strong><\/em> Ensure to change the lines &#8220;sling.servlet.paths=<strong>\/apps\/ttn-retail\/components\/datasource<\/strong>&#8221; and Resource resource = resourceResolver.getResource(&#8220;<strong>\/apps\/ttn-retail\/clientlibs<\/strong>&#8220;); in the servlet as per your project structure as well as<strong><em> package name<\/em><\/strong> where ever applicable throughout the blog.<\/p>\n<h3>Section 2:-\u00a0 Addition of clientlibs to the page properties<\/h3>\n<p><strong>2.1<\/strong> 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 <strong>nt:unstructured<\/strong> with sling:resourceType &#8220;granite\/ui\/components\/coral\/foundation\/form\/select&#8221; and other properties as displayed below in the image or xml.<\/p>\n<pre>&lt;clientlibs\r\n  cq:showOnCreate=\"{Boolean}true\"\r\n  jcr:primaryType=\"nt:unstructured\"\r\n  sling:resourceType=\"granite\/ui\/components\/coral\/foundation\/form\/select\"\r\n  fieldLabel=\"Page Specific Client Libraries\"\r\n  multiple=\"{Boolean}true\"\r\n  name=\".\/clientlibs\"&gt;\r\n   &lt;datasource\r\n      jcr:primaryType=\"nt:unstructured\"\r\n      sling:resourceType=\"\/apps\/ttn-retail\/components\/datasource\"\/&gt;\r\n&lt;\/clientlibs&gt;\r\n<\/pre>\n<div id=\"attachment_72725\" style=\"width: 1115px\" class=\"wp-caption aligncenter\"><img aria-describedby=\"caption-attachment-72725\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-72725 size-full\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPageProperties.png\" alt=\"Clientlibs as page properties\" width=\"1105\" height=\"365\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPageProperties.png 1105w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPageProperties-300x99.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPageProperties-1024x338.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPageProperties-768x254.png 768w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPageProperties-624x206.png 624w\" sizes=\"(max-width: 1105px) 100vw, 1105px\" \/><p id=\"caption-attachment-72725\" class=\"wp-caption-text\">Clientlibs as page properties<\/p><\/div>\n<div id=\"attachment_72724\" style=\"width: 1115px\" class=\"wp-caption aligncenter\"><img aria-describedby=\"caption-attachment-72724\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-72724\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/06\/datasourceAsResourceType-1024x334.png\" alt=\"Resource Type datasource\" width=\"1105\" height=\"361\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/06\/datasourceAsResourceType-1024x334.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/datasourceAsResourceType-300x98.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/datasourceAsResourceType-768x251.png 768w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/datasourceAsResourceType-624x204.png 624w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/datasourceAsResourceType.png 1118w\" sizes=\"(max-width: 1105px) 100vw, 1105px\" \/><p id=\"caption-attachment-72724\" class=\"wp-caption-text\">Data Source<\/p><\/div>\n<p><strong>2.2<\/strong> 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.<\/p>\n<pre>package com.ttn-retail.core.models;\r\nimport com.day.cq.wcm.api.Page;\r\nimport org.apache.sling.api.SlingHttpServletRequest;\r\nimport org.apache.sling.models.annotations.Model;\r\nimport org.apache.sling.models.annotations.injectorspecific.ScriptVariable;\r\n@Model(adaptables = SlingHttpServletRequest.class)\r\npublic class ClientLibsFromPagePropertiesModel {\r\n@ScriptVariable\r\nprivate Page currentPage;\r\npublic String[] getClientLibs() {\r\n      return currentPage != null ? currentPage.getProperties().get(\"clientlibs\", String[].class) : null;\r\n}\r\n}\r\n<\/pre>\n<p><strong>2.3<\/strong> Use sling model created in step 2.2 to add the authored clientlibs if any to customfooter.html file.<\/p>\n<pre>&lt;sly data-sly-use.clientlib=\"\/libs\/granite\/sightly\/templates\/clientlib.html\" data-sly-use.clientlibsModel=\"com.ttn-retail.core.models.ClientLibsFromPagePropertiesModel\"&gt;\r\n     &lt;sly data-sly-call=\"${clientlib.js @ categories=clientlibsModel.clientLibs}\"&gt;&lt;\/sly&gt;\r\n&lt;\/sly&gt;<\/pre>\n<p><strong>If required,<\/strong> we can put css for the respective clientlibs by updating customheaderlibs.html of the page component in our project.<\/p>\n<div id=\"attachment_72766\" style=\"width: 1115px\" class=\"wp-caption aligncenter\"><img aria-describedby=\"caption-attachment-72766\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-72766\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/06\/Screenshot-from-2025-06-16-17-52-58-1024x72.png\" alt=\"Custom footerlibs html\" width=\"1105\" height=\"77\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/06\/Screenshot-from-2025-06-16-17-52-58-1024x72.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/Screenshot-from-2025-06-16-17-52-58-300x21.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/Screenshot-from-2025-06-16-17-52-58-768x54.png 768w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/Screenshot-from-2025-06-16-17-52-58-1536x107.png 1536w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/Screenshot-from-2025-06-16-17-52-58-624x44.png 624w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/Screenshot-from-2025-06-16-17-52-58.png 1687w\" sizes=\"(max-width: 1105px) 100vw, 1105px\" \/><p id=\"caption-attachment-72766\" class=\"wp-caption-text\">Custom footerlibs html<\/p><\/div>\n<p>&nbsp;<\/p>\n<pre>&lt;sly data-sly-use.clientlib=\"\/libs\/granite\/sightly\/templates\/clientlib.html\" data-sly-use.clientlibsModel=\"com.ttn-retail.core.models.ClientLibsFromPagePropertiesModel\"&gt;\r\n     &lt;sly data-sly-call=\"${clientlib.css @ categories=clientlibsModel.clientLibs}\"&gt;&lt;\/sly&gt;\r\n&lt;\/sly&gt;\r\n<\/pre>\n<h3>Result:<\/h3>\n<p>Open any page and navigate to the &#8216;clientlibs property&#8217; tab of the page to author and validate the functionality as displayed in below images.<\/p>\n<div id=\"attachment_72722\" style=\"width: 1115px\" class=\"wp-caption aligncenter\"><img aria-describedby=\"caption-attachment-72722\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-72722\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPagePropertiesEditMode-1024x202.png\" alt=\"clientlibs page properties\" width=\"1105\" height=\"218\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPagePropertiesEditMode-1024x202.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPagePropertiesEditMode-300x59.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPagePropertiesEditMode-768x151.png 768w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPagePropertiesEditMode-1536x303.png 1536w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPagePropertiesEditMode-624x123.png 624w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPagePropertiesEditMode.png 1795w\" sizes=\"(max-width: 1105px) 100vw, 1105px\" \/><p id=\"caption-attachment-72722\" class=\"wp-caption-text\">Clientlibs as Page Properties<\/p><\/div>\n<div id=\"attachment_72721\" style=\"width: 1115px\" class=\"wp-caption aligncenter\"><img aria-describedby=\"caption-attachment-72721\" decoding=\"async\" loading=\"lazy\" class=\"wp-image-72721\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPagePropertiesDropdown-1024x226.png\" alt=\"clientlibs as dropdown\" width=\"1105\" height=\"244\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPagePropertiesDropdown-1024x226.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPagePropertiesDropdown-300x66.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPagePropertiesDropdown-768x170.png 768w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPagePropertiesDropdown-1536x339.png 1536w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPagePropertiesDropdown-624x138.png 624w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsAsPagePropertiesDropdown.png 1788w\" sizes=\"(max-width: 1105px) 100vw, 1105px\" \/><p id=\"caption-attachment-72721\" class=\"wp-caption-text\">Clientlibs<\/p><\/div>\n<p>After saving the changes, open the page in \u201c<strong>view as published<\/strong>\u201d mode to inspect and verify the authored clientlibs in source code or, even the same can be validated from the network tab.<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter wp-image-72720\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsInSourceCode.png\" alt=\"\" width=\"1105\" height=\"35\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsInSourceCode.png 861w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsInSourceCode-300x9.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsInSourceCode-768x24.png 768w, \/blog\/wp-ttn-blog\/uploads\/2025\/06\/clientlibsInSourceCode-624x20.png 624w\" sizes=\"(max-width: 1105px) 100vw, 1105px\" \/><\/p>\n<p>Hope, you find the blog helpful. If similar functionality comes in your project do not hesitate to customise the solution.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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 [&hellip;]<\/p>\n","protected":false},"author":1823,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":172},"categories":[5868],"tags":[1522,5314,4847,7759,6832],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/71734"}],"collection":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/users\/1823"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/comments?post=71734"}],"version-history":[{"count":31,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/71734\/revisions"}],"predecessor-version":[{"id":74326,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/71734\/revisions\/74326"}],"wp:attachment":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/media?parent=71734"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/categories?post=71734"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/tags?post=71734"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}