Is it possible to conditionally include different clientlibs based on the user agent of the browser?
IE
<sly data-sly-use.clientLib="/libs/granite/sightly/templates/clientlib.html"
data-sly-call="${clientlib.js # categories='a'}"/>
Modern Browsers
<sly data-sly-use.clientLib="/libs/granite/sightly/templates/clientlib.html"
data-sly-call="${clientlib.js # categories='b'}"/>
AEM version: 6.3
If not, what are the other alternatives to achieve the same?
Note: I tried to get this check done in a sling rewriter server side but the problem is, with the dispatcher on, it will only hit AEM for the furst time and cache the html, any subsequent hit will not invoke any server side logic to render it. Hence, it has to be done client side IMO
For this you have to write custom clientlibs templates as described here : https://github.com/nateyolles/aem-clientlib-async.
And then in the WCMUse class you can check user agent and include clientlibs accordingly.
First of all, as you already pointed out correctly, you need asolution that works with the dispatcher cache. So Sightly is not an option.
Then, regarding the fact, that most AEM templates have paragraph systems with multiple possible components to be added to a page on the one hand and AEM clientlibs are build on a template level (and not on page level) you end up with a clientlib holding lots of unused JS and CSS most of the time, since you have to cover all the possible options of components used in your page and paragraph system.
With that in mind, clientlibs might not be a good option to be used after all.
Having static CSS and JS files in your AEM repo and referencing them client side based on a JS snippet will do the trick and - in most cases - you will not buy any side effects with that approach.
You can either:
Wrap your clientlibs with conditional comments: https://stackoverflow.com/a/11703767
Create a Use-Object that check the User-Agent header and exposes a method isIE that you can use to conditionally include the clientlibs with data-sly-test. Alternatively your Use-Object could just return the proper categiers based on user agent so you can have only one clientlib call.
You could use the <script module> and <script nomodule> to achieve that. The first one is ignored by older browsers and the second one by the modern ones. Similar to Vue's Modern Mode. Check: https://cli.vuejs.org/guide/browser-compatibility.html#modern-mode
More likely you would need some back-end to create your custom clientlib template.
Related
If we give cq:includeClientLib inside my component jsp and if we drag and drop the component twice on that page, will the clientlib gets loaded/included twice?
what will be the case if we do in Sightly way (data-sly-call="${clientlib.all # categories='somecategory'}") ?
And also what is the suggested method of including client libs, either create a clientlib specific to the component and load only for that component or include all the CSS and JS at a common clientlib and use it across?
No, the clientlib is only included once for a category.
This is by design as the HTL (and respective JSP tag) are evaluated during runtime and the processor keeps a map of categories that have already been included and does not include them again.
As #i.net mentioned, each category will only be included once. To answer your follow up question about the suggested method..
The best practice seems to be to define a client library for each component, which is then embedded into a "global" client library. That global client library will then be included within your page template.
/etc/designs/acme/clientlibs-all
categories=["acme-all"]
embed=[compA,compB]
/apps/acme/components/compA/clientlibs
categories=["compA"]
/apps/acme/components/compB/clientlibs
categories=["compB"]
The reason the global client library is located under /etc/designs is to prevent exposing /apps to the public. However, in AEM 6.3, you could make use of the allowProxy property to serve the code at /etc.designs/. This would then look like this:
/apps/acme/clientlibs/clientlibs-all
categories=["acme-all"]
embed=[compA,compB]
allowProxy=true
/apps/acme/components/compA/clientlibs
categories=["compA"]
/apps/acme/components/compB/clientlibs
categories=["compB"]
Adobe recently released a good tutorial of more recent best practices around client library structure: https://helpx.adobe.com/experience-manager/kt/sites/using/getting-started-wknd-tutorial-develop/part3.html
I was referring to adobe blog
and as per standard guidelines, we need to embed all component specific clientlibs in /etc/design/app/clientlibs. Once done, all clientlibs will be consolidated into one call.
I have a question if we do this way, will AEM not call all clientlibs on page load and will that not affect performance on first page load? Of course, subsequent calls will be lighter?
Also is there any way where i can load authoring specific clientlibs only when author is creating/editing content or only on author mode?
I have a question if we do this way, will AEM not call all clientlibs
on page load and will that not affect performance on first page load?
Of course, subsequent calls will be lighter?
The purpose is to avoid multiple network calls to each individual libraries/JS that you have included in your code. With effective compression/minification the size is optimized.
To answer your question, there is a trade off but in long term its beneficial. With dispatcher and CDNs in place in most of the cases the first loads is the only bottleneck.
Also is there any way where i can load authoring specific clientlibs
only when author is creating/editing content or only on author mode?
You can and should include authoring clientlibs on Authoring instances only, you can do this by placing an edit mode check prior to including authoring client libs.
Look at /libs/wcm/foundation/components/page/author.html it makes a check for edit mode for including edit/authoring libraries -
<sly data-sly-use.wcmInit="initwcm.js"
data-sly-use.clientLib="${'/libs/granite/sightly/templates/clientlib.html'}"
data-sly-test="${!wcmInit.wcmmode.disabled && wcmInit.isTouchAuthoring}" data-sly-call="${clientLib.all # categories='cq.authoring.page'}" />
<sly data-sly-test="${!wcmInit.wcmmode.disabled && !wcmInit.isTouchAuthoring}" data-sly-call="${clientLib.all # categories='cq.wcm.edit'}" />
The publisher instance has the following javascript files included:
/etc/clientlibs/granite/jquery.min.js
/etc/clientlibs/granite/utils.min.js
/etc/clientlibs/granite/jquery/granite.min.js
/etc/clientlibs/foundation/main.min.js
/etc/clientlibs/granite/jquery/granite/csrf.min.js
It would be best to exclude them for performance and also the fact that I am using jQuery 2.0 as my part of AEM site.
These scripts(not including the csrf.min.js) are part of the category cq.foundation-main. You can use this utility for checking this.
http://localhost:4502/libs/granite/ui/content/dumplibs.test.html?categories=cq.foundation-main
If you create your page template components by extending the OOTB wcm/foundation/components/page (assuming you use sightly), AEM will add these scripts in the head section. More specifically these are included in headlibs.html file present under the OOTB page component.
To overcome this, you can override this file in your component, and either comment this below line or include it conditionally only when wcmmode is edit.
<sly data-sly-test="${wcmmode.edit}" data-sly-call="${clientLib.all # categories='cq.foundation-main'}" />
For JSP based components (foundation/components/page), the same thing is done in headlibs.jsp.
By the way, you wouldn't want to remove the CSRF JS. It's AEM's solution to counter CSRF issues.
What you see is the clientcontext related js'es
The script that loads the given js'es is as below (or sth similiar)
<cq:include path="clientcontext" resourceType="cq/personalization/components/clientcontext"/>
either you exclude it completely for publish (wcmMode=disabled) which would probably break sth related to analytics on your publish, or change the include to some overwrite of the clientcontext component that you'd develop.
If you however don't use any native Adobe integration you should be safe just with excluding the thing.
I would like to implement an extensible templating mechanism in AEM, so as to permit component users to control markup for individual projects (designs) without modifying the components' pre-defined JSPs.
I have extended the <cq:include> tag to permit this, by passing a template name, which is then retrieved from the current design, falling back to the default markup when an override does not exist in the design:
<ct:template name="listNav/prev" />
This should load the jsp script from [1], unless the location does not exist, defaulting to [2]:
/etc/designs/projectName/component_templates/listNav/prev.jsp
/etc/designs/component_templates/listNav/prev.jsp
When using the extended tag, I'm receiving the exception (yes, the file exists):
Caused by: org.apache.sling.api.SlingException: javax.servlet.ServletException: javax.servlet.jsp.JspException: Could not find script /etc/designs/component_templates/listNav/prev.jsp
This all works when the component_templates is under /apps. Is there any way to make this work? Is there a better approach? I'd prefer to keep the component_templates with the designs, if possible.
I don't think it is a good idea to put application script to etc. They should be under /apps.
But I think it could work, if you add /etc path to the "Resource Search Path" of this service:
system/console/configMgr/org.apache.sling.jcr.resource.internal.JcrResourceResolverFactoryImpl
you can choose between different "designs" within the advanced page properties tab! afaik you should use this mechanism to declare different designts i.e. stylesheets etc. to your pages and of cause you don't need to modify anything at the jsp's to switch between different styles if implemented properly.
Have a look at this:
Adobe AEM Designs
It seems to be quite basic problem, but I still cannot find a nice solution.
I made a component that uses a dialog property.
How could I avoid setting this property for every single page if this component is used also in template?
What I already have tried:
I set name attribute in dialog.xml to absolute path - Component stops working as standalone (dropped into parsys).
Move it to design_dialog.xml - First of all it's conceptually content, so I do not like such move, and again it doeas not make much sense for standalone versions.
Change resource path to absolute, while including in template:
<cq:include path="/content/site/somepage" resourceType="/apps/portal/components/myComponent" />
For the first look it was almost it. Instances included via parsys has it's own path, and Content for template is fetched from single resource... But where to store it, to make template code independent from pages tree structure?
Is there any other nice way to do so? or at least way to improve 3.?
To the original poster, the functionality you are looking for is now supported by Shared Component Properties in ACS AEM Commons (http://adobe-consulting-services.github.io/acs-aem-commons/features/shared-component-properties.html)
Compared to your suggested solutions:
No need for absolute property path required for SCP
Agreed these are "content" properties, so they should be stored as "content" instead of "design". SCP stores these values under the homepage node of a site, making them as genuine of content as any other piece of content.
Agreed that it is bad to have a template hard-coded to a content path of a single site, especially since this makes a multi-site implementation impossible without creating a bunch of templates. SCP does not have this problem, because each site has its own homepage under which the properties are stored.
If I understand correctly, you have a component which may work in two modes:
it may be included statically in the main page renderer via <cq:include>
it may be also dropped into some parsys.
In the first mode component should have some common configuration for all pages and in the second mode it should be configured separately per-instance. The problem is how to create such common configuration.
I think your 3rd solution is perfectly fine assuming that the component configuration is shared by all sites in your CQ instance. At some point it may be too strong assumption, eg. you may have a 3 language branches under /content/site-en, /content/site-fr and /content/site-de and you'd like to make a separate configuration for each branch.
I'd suggest following improvement to the 3rd solution: you may create the shared component under some relative path which will be the same for all pages, like /content/.../configuration/shared-component (where ... may be site1, site2 or site3). Then take first two parts of the current page path, add the /configuration/shared-component suffix and use <cq:include> to include path created in such way.
You may also take a different approach and create a common configuration page referenced by all statically included components. These components may try to find their configuration automatically (via the relative path as above) or they may have a single pathfield that references configuration page.
If you don't like these options (as they assume some site structure or they need some minimal configuration for each component), consider using HierarchyNodeInheritanceValueMap. It allows you to get property from the current resource and if there is no such property, it'll look into the same resource on ancestor pages. Using this you could configure your component just once, in the site root page and inherit configuration across the whole site.