UI Constraints

The UI Constraints add-on allows you to control the visibility and accessibility of UI components using declarative policies defined in resource roles.

For example, a button can be made inactive, a data grid can be hidden, and so on.

Any UI component or action can be managed by the UI Constraints, even if the component is not bound to data model and is not affected by entity operation and entity attribute policies. The only condition is that the component must have an ID.

This feature saves you from writing code that analyzes user’s roles or specific permissions and manages the component state in your application views. It also allows you to fine-tune the access control at runtime by editing roles in the Security → Resource roles views.

Installation

This add-on requires the Enterprise subscription. If you don’t have the subscription, see the Enterprise Trial section for how to get a trial version.

For automatic installation through Jmix Marketplace, follow instructions in the Add-ons section.

For manual installation, follow the steps below.

  1. Configure access to the premium repository.

    • Add the premium repository to your build.gradle:

      repositories {
          // ...
          maven {
              url = 'https://global.repo.jmix.io/repository/premium'
              credentials {
                  username = rootProject['premiumRepoUser']
                  password = rootProject['premiumRepoPass']
              }
          }
      }
    • Add premium repository credentials to ~/.gradle/gradle.properties:

      premiumRepoUser=123456123456
      premiumRepoPass=abcdefabcdef

      Get the repository credentials from your license key: the first part of the key before dash is the repository user name, the part after dash is the password. For example, if your key is 123456123456-abcdefabcdef, then the user name is 123456123456 and the password is abcdefabcdef.

  2. Add dependencies to your build.gradle:

    implementation 'io.jmix.uiconstraints:jmix-uiconstraints-starter'

Usage

The UI Constraints add-on provides UI component policies that can be specified in resource roles either at development time using annotated Java interfaces or at runtime using the role management views.

The @UiComponentPolicy annotation that is used in Java interfaces is described below.

The runtime role editor can be accessed through the Security → Resource roles main menu item. When the UI Constraints add-on is included in the project, the dropdown list of policies will include the UI component policy item:

resource role policies

The UI component policy editor allows you to specify a target view, select existing components from a dropdown list and specify the policy action and effect:

ui component policy

Unlike entity and entity attribute policies, the UI component policy is "denying", that is it can only make a UI component or action less visible or accessible than it was before applying the policy. For example, if some entity attribute is visible due to an entity attribute policy, a UI component policy can make the corresponding UI component invisible if it specifies VISIBLE action with DENY effect. See more information about effects below.

UI component policies are applied at the end of the view opening process, after sending the ReadyEvent. By this moment, all view initialization is complete and other policies are applied.

Policies also apply to the content of a lazy tab when it is first selected. For other components created after the view opens, apply the policies programmatically.

Checking UI Component Policies

Use UiComponentPolicyContext with the AccessManager bean to check whether the current user’s UI component policies permit showing or editing a component or action. Pass the view ID and the component path in the same format as the componentIds attribute, for example emailField, addressFragment.streetField, or userGrid.edit.

The following example checks whether the policies permit showing and editing emailField in the Customer.detail view:

@Autowired
private AccessManager accessManager;
private boolean isEmailFieldEditable() {
    UiComponentPolicyContext context =
            new UiComponentPolicyContext("Customer.detail", "emailField");
    accessManager.applyRegisteredConstraints(context);
    return context.isVisiblePermitted() && context.isEnabledPermitted();
}

After calling applyRegisteredConstraints(), use isVisiblePermitted() and isEnabledPermitted() to read the permissions. Both are true initially; a component not mentioned by any policy is permitted unless another registered constraint denies access.

The context checks policy permissions, not the component’s current state or its entity attribute permissions. A permitted result does not make a component visible or editable if it was hidden, disabled, or made read-only for another reason.

The built-in UiComponentPolicyConstraint evaluates the current user’s effective policies. UiComponentPolicyApplier uses the same context when applying them. An application can register an AccessConstraint<UiComponentPolicyContext> to restrict access further, using setVisibleDenied() or setEnabledDenied(). Denied checks are recorded by AccessLogger.

Applying Policies to Dynamic Content

For components created after a view opens, inject UiComponentPolicyApplier and call applyUiComponentPolicies(view, content) after adding the content to the view. This applies the view’s policies to the component and its children. Components and actions must have IDs matching their policy paths.

@Autowired
private UiComponentPolicyApplier uiComponentPolicyApplier;
private void addRuntimeContent(Component content) {
    getContent().add(content);
    uiComponentPolicyApplier.applyUiComponentPolicies(this, content);
}

The single-argument applyUiComponentPolicies(content) method finds the owning view through the component tree. If it cannot find the view, no policies are applied.

For content installed into a tabSheet tab, use the two-argument applyUiComponentPolicies(view, content) method. JmixTabSheet keeps only the selected tab’s content in the component tree and attaches it before the client response, so the single-argument method cannot reliably find the owning view there.

Tab Selection

By default, the add-on applies policies to a tab’s content when the tab is selected. This includes components and actions in <tab lazy="true">, whose content is created only at that moment. Lazy tabs require no application code to apply policies.

Each content component is processed once. Selecting the same tab again does not reapply policies over changes the application has made to its components. Content passed to applyUiComponentPolicies() is also marked as processed for tab selection.

If a policy hides the selected tab, another tab may become selected while the user is working with the view.

To disable automatic policy application on tab selection, set jmix.uiconstraints.apply-policies-on-tab-selection to false. This leaves content created after view opening, including lazy tab content, without automatic policy application.

UiComponentPolicy Annotation

The @UiComponentPolicy annotation allows you to define UI constraints in resource role interfaces at development time.

For example:

package com.company.demo.security;

import com.company.demo.view.customer.CustomerDetailView;
import com.company.demo.view.customer.CustomerListView;
import io.jmix.security.role.annotation.ResourceRole;
import io.jmix.uiconstraints.annotation.UiComponentPolicy;
import io.jmix.uiconstraints.annotation.UiComponentPolicyAction;
import io.jmix.uiconstraints.annotation.UiComponentPolicyEffect;

@ResourceRole(name = "EmployeeRole", code = EmployeeRole.CODE, scope = "UI")
public interface EmployeeRole {
    String CODE = "employee-role";

    @UiComponentPolicy(
            viewClass = CustomerListView.class,
            componentIds = {"customersDataGrid.importCustomersAction"},
            action = UiComponentPolicyAction.ENABLED,
            effect = UiComponentPolicyEffect.DENY
    )
    @UiComponentPolicy(
            viewClass = CustomerDetailView.class,
            componentIds = {"commentsPane"},
            action = UiComponentPolicyAction.VISIBLE,
            effect = UiComponentPolicyEffect.DENY
    )
    void customers();
}

Attributes of the @UiComponentPolicy annotation are described below.

viewClass and viewId

UI component policy is defined for components inside a view. The view can be specified either by the viewClass or by the viewId attribute.

componentIds

Each @UiComponentPolicy annotation can define policies for one or more UI components of a view. Their ID must be listed in the componentIds attribute. A policy can be applied to UI components of the following types:

  • A visual component (textField, button, vbox, div, etc.) of a given view. For example: firstNameField, importButton.

  • An action that belongs to a given view. For example: save.

  • An action that belongs to a UI component. For example: userGrid.edit.

  • A component or an action located inside a fragment. For example: addressFragment.cityField.

action

UI component policy supports two types of actions:

  • VISIBLE - manages the visibility of the component.

  • ENABLED - manages the accessibility of the component. If the component supports the read-only state, it will be set to read-only. Otherwise, it will be disabled.

effect

The effect attribute can have two values: ALLOW and DENY.

By default, if no UI component policies are applied, all UI components in the view have their initial visibility and accessibility state. Data-bound components are visible and accessible if a corresponding entity attribute policy exists.

If the DENY effect is specified in a UI component policy, it hides or disables a UI component or action. This is the usual use case for a UI component policy.

The ALLOW effect can be used if you need to override the effect of another UI component policy that has been applied to the same component by a different role. For example, if a user has already been assigned the A role and this role prevents access to a certain UI component, using the B role with the ALLOW effect will restore access to that component.

The ALLOW effect cannot show or enable a component that was hidden or disabled by its initial state or because by the absence of an enabling entity or entity attribute policy.

Application Properties

jmix.uiconstraints.apply-policies-on-tab-selection

Since Jmix 3.1

Controls whether UI component policies are applied to tab content when a tab is selected, including content created for lazy tabs. See Tab Selection.

Default value: true.

Set to false to disable this handling. Policies are still applied when the view opens, and can be applied programmatically using UiComponentPolicyApplier.