Jmix Views – List and Detail Screens
🎧 Focus sounds
Soft beat sounds that many learners like while reading. Pick one, press Play, and keep studying.
Keep the volume low. These sounds are for comfort while studying, not a medical treatment. If you have epilepsy or are sensitive to pulsing sounds, use the background noise only.
In this lesson
A Jmix view is one screen of your application: a list of courses, a course form, a dashboard. Each view has an XML descriptor (layout and data) and a Java controller (behaviour).
Watch a request flow through a Jmix app#
Switch between opening the list and saving a new record:
Generate views#
Right-click the Course entity → *Views → Create View* → Entity list and detail views. Studio asks for the fetch plan (which attributes and references to load) and creates four files:
view/course/CourseListView.java view/course/course-list-view.xml
view/course/CourseDetailView.java view/course/course-detail-view.xml
It also adds a menu item. Restart, and Courses appears in the menu with a working table and form.
The list view#
<view xmlns="http://jmix.io/schema/flowui/view"
title="msg://courseListView.title"
focusComponent="coursesDataGrid">
<data>
<collection id="coursesDc" class="com.vitalstack.courseadmin.entity.Course">
<fetchPlan extends="_base">
<property name="category" fetchPlan="_base"/>
<property name="instructor" fetchPlan="_base"/>
</fetchPlan>
<loader id="coursesDl" readOnly="true">
<query>
<![CDATA[select e from Course e order by e.title]]>
</query>
</loader>
</collection>
</data>
<facets>
<dataLoadCoordinator auto="true"/>
<urlQueryParameters>
<genericFilter component="genericFilter"/>
<pagination component="pagination"/>
</urlQueryParameters>
</facets>
<layout>
<genericFilter id="genericFilter" dataLoader="coursesDl"/>
<hbox id="buttonsPanel" classNames="buttons-panel">
<button id="createButton" action="coursesDataGrid.create"/>
<button id="editButton" action="coursesDataGrid.edit"/>
<button id="removeButton" action="coursesDataGrid.remove"/>
<simplePagination id="pagination" dataLoader="coursesDl"/>
</hbox>
<dataGrid id="coursesDataGrid" width="100%" minHeight="20em" dataContainer="coursesDc">
<actions>
<action id="create" type="list_create"/>
<action id="edit" type="list_edit"/>
<action id="remove" type="list_remove"/>
</actions>
<columns>
<column property="title"/>
<column property="level"/>
<column property="category"/>
<column property="instructor"/>
<column property="price"/>
</columns>
</dataGrid>
</layout>
</view>
@Route(value = "courses", layout = MainView.class)
@ViewController("Course.list")
@ViewDescriptor("course-list-view.xml")
@LookupComponent("coursesDataGrid")
@DialogMode(width = "64em")
public class CourseListView extends StandardListView<Course> {
}
The key ideas:
- Data container (
coursesDc): holds the loaded entities; UI components bind to it. - Data loader (
coursesDl): runs the JPQL query and fills the container, with paging and the filter’s conditions added automatically. - Fetch plan: which attributes and references to load in one go. Loading
categoryandinstructorhere avoids N+1 queries when the grid shows them (the problem you studied in the Hibernate course). - Facets:
dataLoadCoordinatorloads data when the view opens;urlQueryParametersputs filter and page into the URL so users can bookmark and share them. - Actions: standard
list_create,list_edit,list_removeopen the detail view, ask for confirmation, check security and refresh the grid.
The detail view#
<view xmlns="http://jmix.io/schema/flowui/view"
title="msg://courseDetailView.title"
focusComponent="form">
<data>
<instance id="courseDc" class="com.vitalstack.courseadmin.entity.Course">
<fetchPlan extends="_base">
<property name="category" fetchPlan="_base"/>
<property name="instructor" fetchPlan="_base"/>
<property name="lessons" fetchPlan="_base"/>
</fetchPlan>
<loader id="courseDl"/>
<collection id="lessonsDc" property="lessons"/>
</instance>
</data>
<facets>
<dataLoadCoordinator auto="true"/>
</facets>
<actions>
<action id="saveAction" type="detail_saveClose"/>
<action id="closeAction" type="detail_close"/>
</actions>
<layout>
<formLayout id="form" dataContainer="courseDc">
<textField id="titleField" property="title"/>
<select id="levelField" property="level"/>
<entityComboBox id="categoryField" property="category" itemsQuery="select c from Category c order by c.name"/>
<entityPicker id="instructorField" property="instructor">
<actions>
<action id="entityLookup" type="entity_lookup"/>
<action id="entityClear" type="entity_clear"/>
</actions>
</entityPicker>
<bigDecimalField id="priceField" property="price"/>
</formLayout>
<h4 text="msg://lessons"/>
<dataGrid id="lessonsDataGrid" dataContainer="lessonsDc" width="100%" height="15em">
<actions>
<action id="create" type="list_create"/>
<action id="edit" type="list_edit"/>
<action id="remove" type="list_remove"/>
</actions>
<columns>
<column property="position"/>
<column property="title"/>
<column property="durationMinutes"/>
</columns>
</dataGrid>
<hbox id="detailActions">
<button id="saveAndCloseButton" action="saveAction"/>
<button id="closeButton" action="closeAction"/>
</hbox>
</layout>
</view>
@Route(value = "courses/:id", layout = MainView.class)
@ViewController("Course.detail")
@ViewDescriptor("course-detail-view.xml")
@EditedEntityContainer("courseDc")
public class CourseDetailView extends StandardDetailView<Course> {
@ViewComponent
private JmixBigDecimalField priceField;
@Subscribe
public void onInitEntity(final InitEntityEvent<Course> event) {
event.getEntity().setLevel(CourseLevel.BEGINNER); // defaults for NEW courses
}
@Subscribe("levelField")
public void onLevelFieldValueChange(final ComponentValueChangeEvent<JmixSelect<CourseLevel>, CourseLevel> event) {
if (event.getValue() == CourseLevel.ADVANCED && getEditedEntity().getPrice() == null) {
priceField.setValue(new BigDecimal("999")); // suggest a price
}
}
}
- Form fields bind to entity attributes with
property="..."; validation annotations on the entity are enforced, and the save button stays safe. @Subscribemethods handle view lifecycle events (InitEntityEvent,BeforeShowEvent,BeforeSaveEvent) and component events.@ViewComponentinjects components declared in the XML by id.- On save, the DataContext collects every changed entity (the course and its lessons) and commits them in one transaction.
Customising the UI#
- Studio’s view designer: drag components from the palette (text fields, combo boxes, tabs, charts) and edit properties with a live preview.
- Navigation:
ViewNavigators/DialogWindowsbeans open views from code, e.g. a detail view in a dialog. - Notifications: inject
Notificationsand callnotifications.create("Course published").show(). - Themes: edit CSS in
frontend/themes/<app>/.
Prefer XML + standard actions for what they do well (CRUD, filters, paging). Write Java only for real behaviour. Generated views take minutes; your time goes into business logic and good UX.
Exercise#
- Generate list and detail views for
Course,CategoryandInstructor. - Add a
levelcolumn filter to the course list using the generic filter’s “Add condition”. - In the course detail view, show a notification after saving (
@Subscribe AfterSaveEvent). - Add a custom button “Duplicate” on the list that copies the selected course with ” (copy)” in the title.
Hint for 4
Add <button id="duplicateButton" text="Duplicate"/>, then in the controller @Subscribe(id = "duplicateButton", subject = "clickListener") to create a copy with dataManager.create(Course.class), set its fields, dataManager.save(copy) and reload the loader coursesDl.load().
Interview corner#
- What makes up a Jmix view? An XML descriptor (data components, layout, actions, facets) and a Java controller.
- Data container vs data loader? Containers hold entities for binding; loaders fetch them with JPQL into containers.
- What is a fetch plan? A description of which attributes and references to load, preventing lazy-loading problems and N+1 queries.
- How are changes saved? The view’s DataContext tracks modified entities and commits them together.
Summary#
- Studio generates list and detail views from entities.
- XML defines data (containers, loaders, fetch plans), facets and layout; Java controllers add behaviour with
@Subscribe. - Standard actions give CRUD with security checks; the DataContext saves everything in one transaction.
Next: data access in Java with DataManager.
Quick quiz
3 questions. Check what stuck and earn 2 XP for each right answer.
Progress is saved in this browser. Create a free account to keep it on every device.
Get new lessons in your inbox
One email when we publish a new tutorial or guide. No spam, unsubscribe in one click.