gridLayout 表格布局
gridLayout 将内部的子组件或数据排布在一个响应式、二维的 CSS 表格中。
XML 元素 |
|
|---|---|
Java 类 |
|
基本用法
声明式添加子组件,并使用 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。
<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>
@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 元素来渲染项目。
-
创建 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> -
创建 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; } } -
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 或其子组件的特殊属性:
| 名称 | 描述 | 默认值 |
|---|---|---|
在子组件设置。子组件在网格单元格内沿块轴(列轴)的对齐方式。对应于 CSS 的 align-self 属性。 |
|
|
设置 |
|
|
网格单元格之间的间距(包括行间距和列间距)。该值为 CSS 长度值(例如 |
|
|
在子组件设置。子组件在网格单元格内沿内轴(行轴)的对齐方式。对应于 CSS 的 justify-self 属性。 |
|
XML 内部元素
一个非数据感知的 gridLayout 可以直接包含 UI 组件。一个数据感知的布局可以包含 fragmentRenderer,为每个条目创建组件。