Render Conditions Render Condition là cơ chế kiểm soát xem một element trong Granite UI (dialog field, tab, toolbar button, hay bất kỳ widget nào) có được render hay không. Evaluation diễn ra server-side trước khi HTML được gửi về browser — element bị ẩn sẽ không bao giờ xuất hiện trong DOM.
Đây là cơ chế chính cho:
Show/hide dialog field dựa trên group membership của user
Ẩn/hiện tab hoặc field dựa trên content path hoặc độ sâu của path
Ẩn author-only controls trên Publish instance
Giới hạn UI element theo run mode, permissions, hoặc business logic tuỳ chỉnh
Quan trọng: Render conditions khác với showhide client-side. Client-side chỉ ẩn DOM element — vẫn có thể reveal bằng DevTools. Render conditions ẩn element trước khi HTML được tạo ra — an toàn hơn cho fields nhạy cảm.
Cơ chế hoạt động
Dialog XML node
└── granite:rendercondition
└── sling:resourceType → Sling Servlet evaluate condition
↓
request.setAttribute(RenderCondition, SimpleRenderCondition(true/false))
↓
Granite UI framework đọc attribute → render hoặc skip element
Các bước cụ thể:
Bất kỳ Granite UI element nào cũng có thể chứa child node granite:rendercondition.
sling:resourceType của node đó trỏ tới một Sling Servlet để evaluate.
Servlet set SimpleRenderCondition(true/false) vào request attribute với key RenderCondition.class.getName().
Granite UI framework đọc attribute đó và quyết định render hay bỏ qua element.
API classes quan trọng
Built-in Render Conditions
AEM cung cấp sẵn một số render conditions không cần viết Java code. Tất cả nằm dưới base path:
granite/ui/components/coral/foundation/renderconditions/
User permissions (privilege)
Hiện element chỉ khi user có quyền cụ thể trên một resource:
< granite:rendercondition
jcr : primaryType = " nt:unstructured "
sling : resourceType = " granite/ui/components/coral/foundation/renderconditions/privilege "
path = " ${requestPathInfo.suffix} "
privileges = " [rep:write] " />
path thường là $\{requestPathInfo.suffix\} — path của content đang được edit (AEM truyền qua URL suffix khi mở dialog).
Group membership (group)
Hiện element chỉ khi user thuộc group được chỉ định:
< granite:rendercondition
jcr : primaryType = " nt:unstructured "
sling : resourceType = " granite/ui/components/coral/foundation/renderconditions/group "
groups = " [administrators,content-authors] "
matchAll = " {Boolean}false " />
Simple expression (simple)
Evaluate một EL expression đơn giản:
< granite:rendercondition
jcr : primaryType = " nt:unstructured "
sling : resourceType = " granite/ui/components/coral/foundation/renderconditions/simple "
expression = " ${requestPathInfo.suffix != null} " />
AND / OR kết hợp nhiều conditions
Khi cần kết hợp nhiều điều kiện:
AND — tất cả điều kiện phải đúng:
< granite:rendercondition
jcr : primaryType = " nt:unstructured "
sling : resourceType = " granite/ui/components/coral/foundation/renderconditions/and " >
< condition1
jcr : primaryType = " nt:unstructured "
sling : resourceType = " granite/ui/components/coral/foundation/renderconditions/privilege "
path = " ${requestPathInfo.suffix} "
privileges = " [rep:write] " />
< condition2
jcr : primaryType = " nt:unstructured "
sling : resourceType = " granite/ui/components/coral/foundation/renderconditions/group "
groups = " [content-authors] " />
</ granite:rendercondition >
OR — ít nhất một điều kiện đúng:
< granite:rendercondition
jcr : primaryType = " nt:unstructured "
sling : resourceType = " granite/ui/components/coral/foundation/renderconditions/or " >
< condition1
jcr : primaryType = " nt:unstructured "
sling : resourceType = " granite/ui/components/coral/foundation/renderconditions/group "
groups = " [administrators] " />
< condition2
jcr : primaryType = " nt:unstructured "
sling : resourceType = " granite/ui/components/coral/foundation/renderconditions/group "
groups = " [super-authors] " />
</ granite:rendercondition >
Bảng tham chiếu built-in conditions
Custom Render Conditions
Khi built-in conditions không đủ, viết custom render condition servlet.
Sling Servlet
Servlet đầy đủ với OSGi injection, testable, là cách hiện đại và recommended:
File: core/src/main/java/com/myproject/core/servlets/GroupRenderConditionServlet.java
package com.myproject.core.servlets ;
import com.adobe.granite.ui.components.Config;
import com.adobe.granite.ui.components.rendercondition.RenderCondition;
import com.adobe.granite.ui.components.rendercondition.SimpleRenderCondition;
import org.apache.jackrabbit.api.JackrabbitSession;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.Group;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.util.Iterator;
@ Component (
service = Servlet.class,
property = {
" sling.servlet.resourceTypes= " + GroupRenderConditionServlet.RESOURCE_TYPE,
" sling.servlet.methods=GET "
}
)
public class GroupRenderConditionServlet extends SlingSafeMethodsServlet {
private static final Logger LOG = LoggerFactory. getLogger (GroupRenderConditionServlet.class);
// Virtual resource type — không cần file vật lý trên disk, chỉ dùng trong dialog XML
static final String RESOURCE_TYPE = " myproject/renderconditions/group-check " ;
@ Override
protected void doGet ( SlingHttpServletRequest request , SlingHttpServletResponse response )
throws ServletException {
boolean shouldRender = false ;
// Đọc config từ node XML của render condition
Resource conditionResource = request. getResource ();
Config config = new Config (conditionResource);
String requiredGroup = config. get ( " group " , String.class);
if (requiredGroup != null ) {
shouldRender = isUserInGroup (request, requiredGroup);
}
request. setAttribute (
RenderCondition.class. getName (),
new SimpleRenderCondition (shouldRender)
);
}
private boolean isUserInGroup ( SlingHttpServletRequest request , String groupName ) {
try {
Session session = request. getResourceResolver (). adaptTo (Session.class);
if (session instanceof JackrabbitSession) {
UserManager userManager = ((JackrabbitSession) session). getUserManager ();
Authorizable currentUser = userManager. getAuthorizable (session. getUserID ());
if (currentUser != null ) {
Iterator < Group > groups = currentUser. memberOf ();
while (groups. hasNext ()) {
if (groups. next (). getID (). equals (groupName)) {
return true ;
}
}
}
}
} catch ( RepositoryException e ) {
LOG. error ( " Error checking group membership " , e);
}
return false ;
}
}
Dùng trong dialog XML:
< myField
jcr : primaryType = " nt:unstructured "
sling : resourceType = " granite/ui/components/coral/foundation/form/textfield "
fieldLabel = " Admin-only field "
name = " ./adminNote " >
< granite:rendercondition
jcr : primaryType = " nt:unstructured "
sling : resourceType = " myproject/renderconditions/group-check "
group = " administrators " />
</ myField >
Truyền config vào Render Condition
Dùng class Config để đọc property từ node granite:rendercondition trong XML. Mọi property đặt trên node đó đều có thể đọc được:
XML:
< granite:rendercondition
jcr : primaryType = " nt:unstructured "
sling : resourceType = " myproject/renderconditions/custom-check "
group = " content-authors "
minDepth = " {Long}3 "
allowedPaths = " [/content/myproject,/content/dam/myproject] "
enabled = " {Boolean}true " />
Java (trong Servlet):
Config config = new Config (request. getResource ());
String group = config. get ( " group " , String.class); // "content-authors"
Long minDepth = config. get ( " minDepth " , Long.class); // 3
String [] paths = config. get ( " allowedPaths " , new String [ 0 ]); // ["/content/myproject", ...]
Boolean enabled = config. get ( " enabled " , Boolean.class); // true
Type hints trong XML:
Practical Examples
Hiện field chỉ trên content pages (không phải template)
@ Component (
service = Servlet.class,
property = {
" sling.servlet.resourceTypes=myproject/renderconditions/content-page-only " ,
" sling.servlet.methods=GET "
}
)
public class ContentPageRenderCondition extends SlingSafeMethodsServlet {
@ Override
protected void doGet ( SlingHttpServletRequest request , SlingHttpServletResponse response )
throws ServletException {
String contentPath = request. getRequestPathInfo (). getSuffix ();
// Hiện trên content pages (/content/...), ẩn trên template structure (/conf/...)
boolean isContentPage = contentPath != null
&& contentPath. startsWith ( " /content/ " )
&& ! contentPath. contains ( " /conf/ " );
request. setAttribute (
RenderCondition.class. getName (),
new SimpleRenderCondition (isContentPage)
);
}
}
Hiện tab chỉ cho component types cụ thể
@ Component (
service = Servlet.class,
property = {
" sling.servlet.resourceTypes=myproject/renderconditions/resource-type-check " ,
" sling.servlet.methods=GET "
}
)
public class ResourceTypeRenderCondition extends SlingSafeMethodsServlet {
@ Override
protected void doGet ( SlingHttpServletRequest request , SlingHttpServletResponse response )
throws ServletException {
Config config = new Config (request. getResource ());
String [] allowedTypes = config. get ( " allowedResourceTypes " , new String [ 0 ]);
String contentPath = request. getRequestPathInfo (). getSuffix ();
boolean shouldRender = false ;
if (contentPath != null && allowedTypes.length > 0 ) {
Resource contentResource = request. getResourceResolver (). getResource (contentPath);
if (contentResource != null ) {
String resourceType = contentResource. getResourceType ();
for ( String allowed : allowedTypes) {
if (allowed. equals (resourceType)) {
shouldRender = true ;
break ;
}
}
}
}
request. setAttribute (
RenderCondition.class. getName (),
new SimpleRenderCondition (shouldRender)
);
}
}
Dùng trong dialog:
< advancedTab
jcr : primaryType = " nt:unstructured "
jcr : title = " Advanced "
sling : resourceType = " granite/ui/components/coral/foundation/container " >
< granite:rendercondition
jcr : primaryType = " nt:unstructured "
sling : resourceType = " myproject/renderconditions/resource-type-check "
allowedResourceTypes = " [myproject/components/hero,myproject/components/teaser] " />
< items jcr : primaryType = " nt:unstructured " >
<!-- fields chỉ hiện cho hero và teaser -->
</ items >
</ advancedTab >
Hiện field theo WCM Mode
@ Component (
service = Servlet.class,
property = {
" sling.servlet.resourceTypes=myproject/renderconditions/wcm-mode " ,
" sling.servlet.methods=GET "
}
)
public class WcmModeRenderCondition extends SlingSafeMethodsServlet {
@ Override
protected void doGet ( SlingHttpServletRequest request , SlingHttpServletResponse response )
throws ServletException {
Config config = new Config (request. getResource ());
String [] allowedModes = config. get ( " modes " , new String []{ " EDIT " });
WCMMode currentMode = WCMMode. fromRequest (request);
boolean shouldRender = false ;
for ( String mode : allowedModes) {
if (currentMode. name (). equalsIgnoreCase (mode)) {
shouldRender = true ;
break ;
}
}
request. setAttribute (
RenderCondition.class. getName (),
new SimpleRenderCondition (shouldRender)
);
}
}
Feature flag từ OSGi config
Combine render condition với OSGi service để làm feature flags:
@ Component (
service = Servlet.class,
property = {
" sling.servlet.resourceTypes=myproject/renderconditions/feature-flag " ,
" sling.servlet.methods=GET "
}
)
public class FeatureFlagRenderCondition extends SlingSafeMethodsServlet {
@ Reference
private FeatureFlagService featureFlagService;
@ Override
protected void doGet ( SlingHttpServletRequest request , SlingHttpServletResponse response )
throws ServletException {
Config config = new Config (request. getResource ());
String featureName = config. get ( " feature " , String.class);
boolean shouldRender = featureName != null && featureFlagService. isEnabled (featureName);
request. setAttribute (
RenderCondition.class. getName (),
new SimpleRenderCondition (shouldRender)
);
}
}
Dùng trong dialog:
< experimentalField
jcr : primaryType = " nt:unstructured "
sling : resourceType = " granite/ui/components/coral/foundation/form/textfield "
fieldLabel = " Experimental Feature "
name = " ./experimentalValue " >
< granite:rendercondition
jcr : primaryType = " nt:unstructured "
sling : resourceType = " myproject/renderconditions/feature-flag "
feature = " enable-experimental-dialog " />
</ experimentalField >
Áp dụng Render Conditions ở đâu
Render conditions hoạt động trên bất kỳ Granite UI element nào , không chỉ form fields:
Ví dụ: Ẩn toàn bộ tab với group condition
< tabs
jcr : primaryType = " nt:unstructured "
sling : resourceType = " granite/ui/components/coral/foundation/tabs " >
< items jcr : primaryType = " nt:unstructured " >
<!-- Tab luôn hiện -->
< general
jcr : primaryType = " nt:unstructured "
jcr : title = " General "
sling : resourceType = " granite/ui/components/coral/foundation/container " >
< items jcr : primaryType = " nt:unstructured " >
<!-- ... general fields ... -->
</ items >
</ general >
<!-- Tab chỉ hiện cho administrators -->
< advanced
jcr : primaryType = " nt:unstructured "
jcr : title = " Advanced "
sling : resourceType = " granite/ui/components/coral/foundation/container " >
< granite:rendercondition
jcr : primaryType = " nt:unstructured "
sling : resourceType = " granite/ui/components/coral/foundation/renderconditions/group "
groups = " [administrators] " />
< items jcr : primaryType = " nt:unstructured " >
<!-- ... admin-only fields ... -->
</ items >
</ advanced >
</ items >
</ tabs >
Render Conditions vs các cách ẩn/hiện khác
Quy tắc: Nếu field ẩn vì lý do bảo mật hoặc permission → dùng Render Conditions. Nếu ẩn vì UX/flow (ví dụ: field A chỉ hiện khi chọn giá trị X ở field B) → dùng showhide client-side.
Best practices
1. Dùng built-in conditions trước
Trước khi viết custom servlet, kiểm tra privilege, group, simple, and/or/not có cover nhu cầu chưa. Built-in conditions không cần code, không cần deploy.
2. Giữ condition logic nhanh
Render conditions chạy trong quá trình render dialog. Logic chậm (gọi external API, query JCR phức tạp) sẽ làm dialog mở chậm. Nếu cần, cache kết quả trong OSGi service hoặc precompute.
3. Dùng virtual resource types
sling:resourceType của render condition servlet không cần file vật lý trên disk. Đây là virtual identifier do Sling servlet registration resolve. Không cần tạo node dưới /apps cho resource type này.
Không cần: /apps/myproject/renderconditions/group-check/.content.xml
Chỉ cần: @Component property "sling.servlet.resourceTypes=myproject/renderconditions/group-check"
4. Test bằng AEM Mocks
@ ExtendWith (AemContextExtension.class)
class GroupRenderConditionServletTest {
private final AemContext context = new AemContext ();
@ Test
void shouldRenderForAdministrators () throws Exception {
context. create (). resource ( " /apps/test/rendercondition " ,
" sling:resourceType " , " myproject/renderconditions/group-check " ,
" group " , " administrators " );
context. currentResource ( " /apps/test/rendercondition " );
GroupRenderConditionServlet servlet = new GroupRenderConditionServlet ();
servlet. doGet (context. request (), context. response ());
RenderCondition condition = (RenderCondition) context. request ()
. getAttribute (RenderCondition.class. getName ());
assertNotNull (condition);
}
}
5. Document render conditions
Render conditions "vô hình" với author — nếu field không hiện, họ không biết tại sao. Nên:
Thêm fieldDescription giải thích tại sao field bị ẩn trong một số trường hợp.
Ghi chú trong project docs: condition nào kiểm soát element nào, group/permission nào được phép.
Pitfalls thường gặp
Checklist khi implement thật
Xác định loại condition: built-in (group/privilege/simple) hay cần custom.
Nếu built-in đủ: thêm node granite:rendercondition trực tiếp vào dialog XML.
Nếu cần custom: tạo Sling Servlet với sling.servlet.resourceTypes = virtual resource type.
Đọc config từ XML node bằng Config class trong servlet.
Set request.setAttribute(RenderCondition.class.getName(), new SimpleRenderCondition(bool)).
Reference sling:resourceType trong dialog XML granite:rendercondition node.
Deploy, mở dialog trong author, test với user thuộc group có quyền và không có quyền.
Verify qua /system/console/servletresolver nếu condition không hoạt động.
Viết unit test bằng AEM Mocks.
Tài liệu tham khảo