For AI agents: the complete documentation index is available at https://nhanbe.id.vn/llms.txt, the full documentation bundle is available at https://nhanbe.id.vn/llms-full.txt, and this page is available as Markdown at https://nhanbe.id.vn/aem/ui/render-conditions.md.

Render Conditions - developer documentation and reference notes for Render Conditions.

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ể:

  1. Bất kỳ Granite UI element nào cũng có thể chứa child node granite:rendercondition.
  2. sling:resourceType của node đó trỏ tới một Sling Servlet để evaluate.
  3. Servlet set SimpleRenderCondition(true/false) vào request attribute với key RenderCondition.class.getName().
  4. Granite UI framework đọc attribute đó và quyết định render hay bỏ qua element.

API classes quan trọng

ClassVai trò
com.adobe.granite.ui.components.rendercondition.RenderConditionInterface, dùng làm key cho request attribute
com.adobe.granite.ui.components.rendercondition.SimpleRenderConditionTriển khai đơn giản, nhận boolean
com.adobe.granite.ui.components.ConfigĐọc property từ XML node của render condition

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"/>
PropertyTypeÝ nghĩa
groupsString[]Danh sách group IDs
matchAllBooleantrue = user phải thuộc tất cả groups; false (default) = thuộc ít nhất một là đủ

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

Resource type (suffix)Mô tả
renderconditions/privilegeKiểm tra JCR privilege của user
renderconditions/groupKiểm tra group membership
renderconditions/simpleEL expression đơn giản
renderconditions/andTất cả child conditions phải true
renderconditions/orÍt nhất một child condition true
renderconditions/notĐảo ngược kết quả của child condition
renderconditions/emptytextKiểm tra một field có giá trị hay không

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:

SyntaxKiểu Java
\{Long\}3Long
\{Boolean\}trueBoolean
[val1,val2]String[]
Không có hintString

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:

ElementĐặt granite:rendercondition trên
Dialog fieldNode của field (textfield, select, ...)
Dialog tabNode của tab container
Toàn bộ dialogNode content (hiếm dùng)
Toolbar buttonNode của button trong toolbar definition
Admin console itemNode item trong console structure
Navigation itemNode overlay trong /apps/cq/core/content/nav

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

CáchKhi nào dùngƯu điểmNhược điểm
Render ConditionsẨn field nhạy cảm theo user/permissionServer-side, an toàn, không thể bypassCần deploy Java code
data-sly-test (HTL)Ẩn element trong component templateĐơn giản, inline trong HTLChỉ dùng khi render component, không dùng trong dialog
showhide (client JS)Ẩn/hiện field dựa trên giá trị field khácReactive, không cần serverKhông an toàn — có thể bypass bằng DevTools
sling:hideResourceẨn element UI cố định qua OverlayKhông cần code, chỉ cần JCRẨn với mọi user, không conditional

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

Vấn đềNguyên nhân / Cách xử lý
Field không bao giờ hiện, kể cả adminSai sling:resourceType — kiểm tra servlet đã registered chưa bằng /system/console/servletresolver
requestPathInfo.suffix trả về nullDialog không được mở qua URL có suffix (ví dụ: mở trực tiếp không qua page editor)
@Reference trong servlet không inject đượcServlet không được OSGi resolve đúng — kiểm tra @Component annotation và bundle active
showhide client-side không ẩn đủKhông dùng showhide cho field nhạy cảm; phải dùng render condition server-side
Condition không có tác dụng sau deployClear cache /libs/granite/ui/content/dumplibs.rebuild.html và restart bundle nếu cần
Config.get() trả về null khi có valueSai type — dùng đúng Java type tương ứng với hint trong XML (\{Long\}, \{Boolean\})
Condition chậm làm dialog lagLogic trong condition quá nặng (query JCR, external call) — cache kết quả hoặc refactor

Checklist khi implement thật

  1. Xác định loại condition: built-in (group/privilege/simple) hay cần custom.
  2. Nếu built-in đủ: thêm node granite:rendercondition trực tiếp vào dialog XML.
  3. Nếu cần custom: tạo Sling Servlet với sling.servlet.resourceTypes = virtual resource type.
  4. Đọc config từ XML node bằng Config class trong servlet.
  5. Set request.setAttribute(RenderCondition.class.getName(), new SimpleRenderCondition(bool)).
  6. Reference sling:resourceType trong dialog XML granite:rendercondition node.
  7. Deploy, mở dialog trong author, test với user thuộc group có quyền và không có quyền.
  8. Verify qua /system/console/servletresolver nếu condition không hoạt động.
  9. Viết unit test bằng AEM Mocks.

Tài liệu tham khảo