gridLayout 表格布局

gridLayout 将内部的子组件或数据排布在一个响应式、二维的 CSS 表格中。

XML 元素

gridLayout

Java 类

GridLayout

基本用法

声明式添加子组件,并使用 gap 和 columnMinWidth 控制间距和响应式列宽。这两个属性都需使用 CSS 长度值或变量定义。

<gridLayout id="gridLayout" gap="var(--lumo-space-m)" columnMinWidth="18em" width="100%" height="100%">
    <image width="100%" height="100%" resource="icons/jmix-icon.png"/>
    <image width="100%" height="100%" resource="icons/jmix-icon.png"/>
    <image width="100%" height="100%" resource="icons/jmix-icon.png"/>
    <image width="100%" height="100%" resource="icons/jmix-icon.png"/>
    <image width="100%" height="100%" resource="icons/jmix-icon.png"/>
    <image width="100%" height="100%" resource="icons/jmix-icon.png"/>
    <image width="100%" height="100%" resource="icons/jmix-icon.png"/>
    <image width="100%" height="100%" resource="icons/jmix-icon.png"/>
</gridLayout>

数据感知的 gridLayout

在 gridLayout 组件中,将 itemsContainer 属性设置为 集合数据容器。布局会为容器中的每个实体创建一个组件。

<data readOnly="true">
    <collection id="customerDc" class="io.jmix.uisamples.entity.Customer"
                fetchPlan="_base">
        <loader id="customerDl" readOnly="true">
            <query>
                <![CDATA[select e from Customer e]]>
            </query>
        </loader>
    </collection>
</data>
<facets>
    <dataLoadCoordinator auto="true"/>
</facets>
<layout>
    <split width="100%" height="100%">
        <gridLayout id="gridLayout" itemsContainer="customerDc"
                    width="100%" height="100%"/>
        <div/>
    </split>
</layout>

默认情况下,组件显示实体的 实例名称。

可以通过配置 自定义渲染器 来修改显示内容。

枚举条目

该组件还支持使用 itemsEnum 属性显示枚举值。

<gridLayout itemsEnum="com.company.onboarding.entity.DayOfWeek"
            width="100%"/>

自定义条目渲染

使用渲染器可以将默认的条目文本标签替换为组件。示例将 gridLayout 与 XML 中的集合容器关联,并在控制器使用 @Supply 提供 ComponentRenderer。

XML
<data readOnly="true">
    <collection id="customerDc" class="io.jmix.uisamples.entity.Customer"
                fetchPlan="_base">
        <loader id="customerDl" readOnly="true">
            <query>
                <![CDATA[select e from Customer e]]>
            </query>
        </loader>
    </collection>
</data>
<facets>
    <dataLoadCoordinator auto="true"/>
</facets>
<layout>
    <split width="100%" height="100%">
        <vbox>
            <gridLayout id="gridLayout" itemsContainer="customerDc" gap="var(--lumo-space-m)"
                        width="100%" height="100%"/>
        </vbox>
        <div/>
    </split>
</layout>
Java
@Autowired
private UiComponents uiComponents;
@Autowired
private MetadataTools metadataTools;
@Autowired
private MessageTools messageTools;
@Autowired
private Metadata metadata;

private int colorIndex;

@Supply(to = "gridLayout", subject = "renderer")
public ComponentRenderer<JmixCard, Customer> customerRenderer() {
    return new ComponentRenderer<>(this::createCard, this::initCard);
}

private JmixCard createCard() {
    JmixCard card = uiComponents.create(JmixCard.class);
    card.setWidthFull();
    card.addThemeVariants(CardVariant.OUTLINED, CardVariant.ELEVATED);
    return card;
}

private void initCard(JmixCard card, Customer customer) {
    card.setHeaderPrefix(createAvatar(customer));
    card.setTitle(customer.getInstanceName());
    card.setSubtitle(createSubtitle(customer));
    card.add(createAdditionalInfo(customer));
}

private Avatar createAvatar(Customer customer) {
    Avatar avatar = new Avatar(customer.getInstanceName());
    avatar.setColorIndex(colorIndex++ % 7);
    return avatar;
}

private Component createSubtitle(Customer customer) {
    Span span = uiComponents.create(Span.class);
    String ageCaption = getPropertyCaption(customer, "age");
    span.setText("%s: %s".formatted(ageCaption, customer.getAge()));
    return span;
}

private Component createAdditionalInfo(Customer customer) {
    HorizontalLayout emailLayout = uiComponents.create(HorizontalLayout.class);
    emailLayout.setPadding(false);
    emailLayout.add(createPropertyCaption(customer, "email"), new Span(customer.getEmail()));

    HorizontalLayout gradeLayout = uiComponents.create(HorizontalLayout.class);
    gradeLayout.setPadding(false);
    gradeLayout.add(createPropertyCaption(customer, "grade"), createGradeBadge(customer));

    VerticalLayout infoLayout = uiComponents.create(VerticalLayout.class);
    infoLayout.add(emailLayout, gradeLayout);
    return infoLayout;
}

private Component createPropertyCaption(Customer customer, String property) {
    Span span = uiComponents.create(Span.class);
    String propertyCaption = getPropertyCaption(customer, property);
    span.setText("%s: ".formatted(propertyCaption));
    return span;
}

private String getPropertyCaption(Customer customer, String property) {
    MetaClass metaClass = metadata.getClass(customer);
    return messageTools.getPropertyCaption(metaClass, property);
}

private Component createGradeBadge(Customer customer) {
    CustomerGrade gradeValue = Objects.requireNonNull(customer.getGrade());

    Badge badge = uiComponents.create(Badge.class);
    badge.setText(metadataTools.format(gradeValue));

    if (gradeValue == CustomerGrade.HIGH) {
        badge.addThemeVariants(BadgeVariant.SUCCESS);
    } else if (gradeValue == CustomerGrade.PREMIUM) {
        badge.addThemeVariants(BadgeVariant.FILLED);
    }

    return badge;
}

Fragment 渲染器

或者,可以使用嵌套的 fragmentRenderer 元素来渲染项目。

  1. 创建 fragment:

    <fragment xmlns="http://jmix.io/schema/flowui/fragment">
        <data>
            <instance id="userDc" class="com.company.onboarding.entity.User">
                <loader id="userDl"/>
                <fetchPlan extends="_base"/>
            </instance>
        </data>
        <content>
            <vbox id="root" padding="false"/>
        </content>
    </fragment>
  2. 创建 fragment 的 Java 控制器

    控制器扩展 FragmentRenderer,并使用 fragment 中的根元素和需要渲染的实体类作为泛型参数:

    Show code
    @FragmentDescriptor("card-fragment.xml")
    @RendererItemContainer("userDc")
    public class CardFragment extends FragmentRenderer<VerticalLayout, User> {
    
        @Autowired
        private FileStorageLocator fileStorageLocator;
        @Autowired
        private Metadata metadata;
        @Autowired
        private MessageTools messageTools;
    
        @Override
        protected void onAttach(AttachEvent attachEvent) {
            super.onAttach(attachEvent);
            initLayout();
        }
    
        private void initLayout() {
            Card card = uiComponents.create(Card.class);
            card.setWidthFull();
            card.addThemeVariants(CardVariant.LUMO_OUTLINED, CardVariant.LUMO_ELEVATED);
    
            card.setHeaderPrefix(createAvatar(getItem()));
            card.setTitle(getItem().getFirstName() + " " + getItem().getLastName());
            card.setSubtitle(createSubtitle(getItem()));
            card.setHeaderSuffix(createHeaderSuffix(getItem()));
            getContent().add(card);
        }
    
        private Image createAvatar(User user) {
            Image image = uiComponents.create(Image.class);
            FileRef fileRef = user.getPicture();
            if (fileRef != null) {
                image.setWidth("50px");
                image.setHeight("50px");
                InputStreamDownloadHandler handler = DownloadHandler.fromInputStream(event -> {
                    InputStream inputStream = fileStorageLocator.getByName(fileRef.getStorageName()).openStream(fileRef);
                    return new DownloadResponse(inputStream, fileRef.getFileName(), fileRef.getContentType(), -1);
                });
                image.setSrc(handler);
            }
            return image;
        }
    
        private Span createSubtitle(User user) {
            Span span = uiComponents.create(Span.class);
            span.setText("%s: %s".formatted(
                    getPropertyCaption(user, "department"),
                    (user.getDepartment() != null ?
                            user.getDepartment().getName() :
                            "Not assigned")));
            return span;
        }
    
        private String getPropertyCaption(User user, String property) {
            MetaClass metaClass = metadata.getClass(user);
            return messageTools.getPropertyCaption(metaClass, property);
        }
    
        private Span createHeaderSuffix(User user) {
            Span span = uiComponents.create(Span.class);
            if (user.getActive()) {
                span.setText("Active");
                span.getElement().getThemeList().add("badge success");
            }
            else {
                span.setText("Inactive");
                span.getElement().getThemeList().add("badge error");
            }
            return span;
        }
    }
  3. gridLayout 组件使用 fragmentRenderer:

    <gridLayout id="gridUsers"
                width="100%"
                itemsContainer="usersDc"
                gap="var(--lumo-space-m)">
        <fragmentRenderer class="com.company.onboarding.view.layout.gridlayout.CardFragment"/>
    </gridLayout>

XML 属性

通用属性 对所有组件都是一样的配置。下面是 gridLayout 或其子组件的特殊属性:

名称 描述 默认值

alignSelf

在子组件设置。子组件在网格单元格内沿块轴(列轴)的对齐方式。对应于 CSS 的 align-self 属性。

AUTO

columnMinWidth

设置 gridLayout 中网格列的最小宽度。可以防止在容器调整大小时列变得过窄。 该值为 CSS 长度值(例如 "50px"、"10rem")。

19rem

gap

网格单元格之间的间距(包括行间距和列间距)。该值为 CSS 长度值(例如 "10px"、"1rem")。

0.5rem

justifySelf

在子组件设置。子组件在网格单元格内沿内轴(行轴)的对齐方式。对应于 CSS 的 justify-self 属性。

AUTO

事件和处理器

通用事件和处理器 对所有组件都是一样的配置。

下面是 gridLayout 的特殊事件和处理器:

名称 描述

itemLabelGenerator

为条目生成自定义的显示文本。

renderer

设置条目的渲染器,对每个条目创建一个组件。参考 自定义条目渲染。

XML 内部元素

一个非数据感知的 gridLayout 可以直接包含 UI 组件。一个数据感知的布局可以包含 fragmentRenderer,为每个条目创建组件。