5. Работа с данными в пользовательском интерфейсе
На данном этапе разработки в приложении есть управление шагами и отделами, а также управление пользователями с добавленным атрибутом Onboarding status
. Теперь вам нужно связать пользователей с шагами онбординга и отделами.
В этой главе вы сделаете следующее:
-
Добавите атрибуты
department
иjoiningDate
к сущностиUser
и отобразите их в пользовательском интерфейсе. -
Создадите сущность
UserStep
, которая связывает пользователя с шагом онбординга. -
Добавите коллекцию сущностей
UserStep
к сущностиUser
и отобразите ее на экранеUser.detail
. -
Реализуете генерацию и сохранение экземпляров сущности
UserStep
на экранеUser.detail
.
На приведенной ниже диаграмме показаны сущности и атрибуты, рассматриваемые в этой главе:
Добавление ссылочного атрибута
Вы уже выполняли аналогичную задачу, когда создавали атрибут HR-менеджера сущности Department
в качестве ссылки на User
. Теперь вам нужно создать ссылку в обратном направлении: у пользователя должна быть ссылка на отдел.
Если ваше приложение запущено, остановите его с помощью кнопки Stop () на главной панели инструментов.
Дважды щелкните на сущности User
в окне инструментов Jmix и выберите ее последний атрибут (чтобы добавить новый атрибут в конец).
Нажмите Add () на панели Attributes.
В диалоговом окне New Attribute введите department
в поле Name. Затем выберите:
-
Attribute type:
ASSOCIATION
-
Type:
Department
-
Cardinality:
Many to One
Нажмите на кнопку OK.
Выберите новый атрибут department
и нажмите на кнопку Add to Views () на панели Attributes :
В появившемся диалоговом окне будут показаны экраны User.detail
и User.list
. Выберите оба экрана и нажмите на кнопку OK.
Studio добавит атрибут department
в компонент dataGrid
экрана User.list
и в компонент formLayout
экрана User.detail
.
Вы также можете заметить следующий код, автоматически добавленный студией в user-list-view.xml
:
<data readOnly="true">
<collection id="usersDc"
class="com.company.onboarding.entity.User">
<fetchPlan extends="_base">
<property name="department" fetchPlan="_base"/> <!-- added -->
</fetchPlan>
И в user-detail-view.xml
:
<data>
<instance id="userDc"
class="com.company.onboarding.entity.User">
<fetchPlan extends="_base">
<property name="department" fetchPlan="_base"/> <!-- added -->
</fetchPlan>
С помощью этого кода указанный отдел будет загружен вместе с пользователем в одном запросе к базе данных.
Экраны будут работать и без включения department в фетч-план благодаря загрузке связанных сущностей по требованию (lazy loading). Но в этом случае ссылки будут загружаться отдельными запросами к базе данных. Отложенная загрузка может повлиять на производительность экрана просмотра, потому что сначала экран загружает список пользователей первым запросом, а после этого выполняет отдельные запросы для загрузки отдела каждого пользователя в списке (проблема N+1 запросов).
|
Давайте запустим приложение и посмотрим на новый атрибут в действии.
Нажмите кнопку Debug () на главной панели инструментов.
Studio сгенерирует Liquibase changelog для добавления столбца DEPARTMENT_ID
в таблицу USER_
, создания ограничения внешнего ключа и индекса. Подтвердите changelog.
Студия исполнит файл changelog и запустит приложение.
Откройте http://localhost:8080
в вашем веб-браузере и войдите в приложение с учетными данными администратора (admin
/ admin
).
Раскройте меню Application и нажмите на подпункт Users. Вы увидите колонку Department
на экране User.list
и поле выбора Department
на экране User.detail
:
Использование выпадающего списка для выбора ссылки
По умолчанию Studio генерирует компонент entityPicker
для выбора ссылок. Вы можете увидеть это на экране User.detail
. Откройте user-detail-view.xml
и найдите компонент entityPicker
внутри компонента formLayout
:
<layout ...>
<formLayout id="form" dataContainer="userDc">
...
<entityPicker id="departmentField" property="department">
<actions>
<action id="entityLookup" type="entity_lookup"/>
<action id="entityClear" type="entity_clear"/>
</actions>
</entityPicker>
</form>
Этот компонент позволяет вам выбрать связанную сущность на экране списка с фильтрацией, сортировкой и пейджингом. Но когда ожидаемое количество записей относительно невелико (скажем, менее 1000), удобнее выбирать ссылки из простого выпадающего списка.
Давайте изменим экран User.detail
и используем компонент entityComboBox
для выбора отдела.
Измените XML-элемент компонента на entityComboBox
и удалите вложенный элемент actions
:
<entityComboBox id="departmentField" property="department"/>
Переключитесь на запущенное приложение и снова откройте экран деталей пользователя.
Вы увидите, что поле Department
теперь является выпадающим списком, но он не открывается, даже если вы создали несколько отделов.
Создание контейнера данных опций
Давайте предоставим список доступных опций компоненту entityComboBox
, отображающему ссылку на отдел. Список должен содержать все отделы, упорядоченные по названию.
Нажмите на кнопку Add Component на панели действий, выберите раздел Data components и дважды щелкните на элементе Collection
. В окне Data Container Properties Editor выберите Department
в поле Entity и нажмите на кнопку OK:
Новый элемент collection
с именем departmentsDc
будет создан под элементом data
на панели иерархии Jmix UI и в XML:
<data>
...
<collection id="departmentsDc" class="com.company.onboarding.entity.Department">
<fetchPlan extends="_base"/>
<loader id="departmentsDl">
<query>
<![CDATA[select e from Department e]]>
</query>
</loader>
</collection>
</data>
Этот элемент определяет контейнер коллекции данных и загрузчик для него. Контейнер данных будет содержать список сущностей Department
, загруженных загрузчиком с указанным запросом.
Вы можете отредактировать запрос прямо в XML или использовать конструктор JPQL. Чтобы открыть конструктор, щелкните по ссылке напротив атрибута query
, находящейся на панели инспектора Jmix UI:
В окне JPQL Query Designer перейдите на вкладку ORDER и добавьте атрибут name
в список:
Нажмите на кнопку OK.
Результирующий запрос в формате XML будет выглядеть следующим образом:
<data>
...
<collection id="departmentsDc" class="com.company.onboarding.entity.Department">
<fetchPlan extends="_base"/>
<loader id="departmentsDl">
<query>
<![CDATA[select e from Department e
order by e.name asc]]>
</query>
</loader>
</collection>
</data>
Теперь вам нужно связать компонент entityComboBox
с контейнером коллекции departmentsDc
.
Выберите departmentField
на панели иерархии Jmix UI, а затем выберите departmentsDc
для атрибута itemsContainer
в панели инспектора:
Переключитесь на запущенное приложение и снова откройте экран редактирования пользователя.
Вы увидите, что в раскрывающемся списке Department
теперь есть список опций:
Компонент entityComboBox позволяет пользователю фильтровать опции, вводя текст в поле. Но имейте в виду, что фильтрация выполняется в памяти сервера, и все опции загружаются из базы данных сразу.
|
Создание сущности UserStep
В этом разделе вы создадите сущность UserStep
, которая представляет собой шаг онбординга для конкретного пользователя:
Если ваше приложение запущено, остановите его с помощью кнопки Stop () на главной панели инструментов.
В окне инструментов Jmix нажмите New () → JPA Entity и создайте сущность UserStep
с чертой Versioned, как вы делали раньше.
Добавьте следующие атрибуты к новой сущности:
Name | Attribute type | Type | Cardinality | Mandatory |
---|---|---|---|---|
user |
ASSOCIATION |
User |
Many to One |
true |
step |
ASSOCIATION |
Step |
Many to One |
true |
dueDate |
DATATYPE |
LocalDate |
- |
true |
completedDate |
DATATYPE |
LocalDate |
- |
false |
sortValue |
DATATYPE |
Integer |
- |
true |
Конечное состояние дизайнера сущностей должно выглядеть следующим образом:
Добавление атрибута-композиции
Рассмотрим взаимосвязь между сущностями User
и UserStep
. Экземпляры UserStep
существуют только в контексте конкретного экземпляра сущности User
(принадлежат ему). Экземпляр UserStep
не может сменить своего владельца - это не имеет никакого смысла. Кроме того, ссылок на UserStep
из других объектов модели данных нет, они полностью инкапсулированы в контексте User
.
В Jmix такая взаимосвязь называется композицией: пользователь (User
), среди прочих атрибутов, включает в себя набор пользовательских шагов (UserStep
).
Композиция в Jmix реализует шаблон проектирования Aggregate подхода Domain-Driven Design. |
Часто бывает удобно создать атрибут, содержащий коллекцию элементов композиции в сущности-владельце.
Давайте создадим атрибут steps
в сущности User
:
Если ваше приложение запущено, остановите его с помощью кнопки Stop () на главной панели инструментов.
Окройте дизайнер сущности User
и нажмите кнопку Add () на панели Attributes. В диалоговом окне New Attribute введите steps
в поле Name. Затем выберите:
-
Attribute type:
COMPOSITION
-
Type:
UserStep
-
Cardinality: One to Many
Обратите внимание, что user
выбирается автоматически в поле Mapped by. Это атрибут сущности UserStep
, сопоставленный столбцу базы данных, который поддерживает связь между UserSteps и Users (внешний ключ).
Нажмите на кнопку OK.
Исходный код атрибута будет иметь аннотацию @Composition
:
@Composition
@OneToMany(mappedBy = "user")
private List<UserStep> steps;
Шаги пользователя должны отображаться на экране деталей пользователя, поэтому выберите новый атрибут steps
и нажмите кнопку Add to Views () на панели Attributes. Выберите User.detail
и нажмите на кнопку OK.
Студия изменит user-detail-view.xml
как показано ниже:
<data>
<instance id="userDc"
class="com.company.onboarding.entity.User">
<fetchPlan extends="_base">
<property name="department" fetchPlan="_base"/>
<property name="steps" fetchPlan="_base"/> (1)
</fetchPlan>
<loader/>
<collection id="stepsDc" property="steps"/> (2)
</instance>
...
<layout ...>
<formLayout id="form" dataContainer="userDc">
...
</form>
<hbox id="buttonsPanel" classNames="buttons-panel">
<button action="stepsDataGrid.create"/>
<button action="stepsDataGrid.edit"/>
<button action="stepsDataGrid.remove"/>
</hbox>
<dataGrid id="stepsDataGrid" dataContainer="stepsDc" ...> (3)
<actions>
<action id="create" type="list_create"/>
<action id="edit" type="list_edit"/>
<action id="remove" type="list_remove"/>
</actions>
<columns>
<column property="version"/>
<column property="dueDate"/>
<column property="completedDate"/>
<column property="sortValue"/>
</columns>
</dataGrid>
1 | Атрибут steps фетч-плана гарантирует, что коллекция пользовательских шагов загружается жадно (eager fetching) вместе с пользователем (User ). |
2 | Вложенный контейнер коллекции данных stepsDc позволяет привязывать визуальные компоненты к атрибуту-коллекции steps . |
3 | Компонент dataGrid отображает данные из связанного контейнера коллекции stepsDc . |
Давайте запустим приложение и посмотрим на эти изменения в действии.
Нажмите на кнопку Debug () на главной панели инструментов.
Studio сгенерирует Liquibase changelog для создания таблицы USER_STEP
, ограничения внешнего ключа и индексов для ссылок на USER_
и STEP
. Подтвердите список изменений.
Студия исполнит файл changelog и запустит приложение.
Откройте http://localhost:8080
в вашем веб-браузере и войдите в приложение с учетными данными администратора (admin
/ admin
).
Откройте экран деталей пользователя. Вы увидите таблицу Steps, отображающую сущности UserStep
:
Если вы нажмете Create в таблице Steps, вы получите исключение, сообщающее: View 'UserStep.detail' is not defined
. Это правда - вы не создавали экран деталей для сущности UserStep
. Но на самом деле это и не нужно, потому что экземпляры UserStep
должны быть сгенерированы из предопределенных экземпляров сущности Step
для конкретного пользователя.
Генерация пользовательских шагов
В этом разделе вы реализуете генерацию и отображение экземпляров сущности UserStep
для редактируемой сущности User
.
Добавление атрибута joiningDate
Во-первых, давайте добавим атрибут joiningDate
к сущности User
:
Он будет использоваться для вычисления атрибута dueDate
сгенерированной сущности UserStep
по следующей формуле: UserStep.dueDate = User.joiningDate + Step.duration
.
Если ваше приложение запущено, остановите его с помощью кнопки Stop () на главной панели инструментов.
Нажмите на кнопку Add () на панели Attributes дизайнера сущности User
. В диалоговом окне New Attribute введите joiningDate
в поле Name и выберите LocalDate
в поле Type:
Нажмите на кнопку OK.
Выберите ранее созданный атрибут joiningDate
и нажмите кнопку Add to Views () на панели Attributes. Выберите оба экрана User.detail
и User.list
в появившемся диалоговом окне и нажмите OK.
Нажмите на кнопку Debug () на главной панели инструментов.
Studio сгенерирует Liquibase changelog для добавления столбца JOINING_DATE
в таблицу USER_
. Подтвердите changelog.
Студия исполнит changelog и запустит приложение. Откройте http://localhost:8080
в вашем веб-браузере, войдите в приложение и убедитесь, что новый атрибут отображается на экранах списка и деталей пользователя.
Добавление пользовательской кнопки
Теперь вам нужно удалить стандартные действия и кнопки для управления шагами пользователя и добавить кнопку для запуска пользовательской логики создания сущностей.
Откройте user-detail-view.xml
и удалите элемент actions
из таблицы и все элементы button
из hbox
:
<hbox id="buttonsPanel" classNames="buttons-panel">
</hbox>
<dataGrid id="stepsDataGrid" dataContainer="stepsDc" width="100%" height="100%">
<columns>
<column property="version"/>
<column property="dueDate"/>
<column property="completedDate"/>
<column property="sortValue"/>
</columns>
</dataGrid>
Затем выберите hbox
в панели иерархии Jmix UI и нажмите на кнопку Add Component в контекстном меню узла. Выберите компонент Button
в палитре и добавьте его в экран двойным кликом. Затем выберите созданный элемент button
и в панели инспектора укажите свойству id
значение generateButton
, а свойству text
- значение Generate
. После этого перейдите на вкладку Handlers и создайте метод обработчика ClickEvent
:
Нажмите Ctrl/Cmd+S и переключитесь на запущенное приложение. Обновите экран деталей пользователя и убедитесь, что вместо стандартных CRUD-кнопок отображается кнопка Generate:
Создание и сохранение экземпляров UserStep
Давайте реализуем логику генерации экземпляров UserStep
.
Добавьте следующие поля в контроллер UserDetailView
:
public class UserDetailView extends StandardDetailView<User> {
@Autowired
private DataManager dataManager;
@Autowired
private Notifications notifications;
@ViewComponent
private DataContext dataContext;
@ViewComponent
private CollectionPropertyContainer<UserStep> stepsDc;
Вы можете инжектировать компоненты экрана и бины Spring с помощью кнопки Inject на панели действий: |
Добавьте логику создания и сохранения объектов UserStep
в метод обработки нажатия кнопки generateButton
:
@Subscribe("generateButton")
public void onGenerateButtonClick(final ClickEvent<Button> event) {
User user = getEditedEntity(); (1)
if (user.getJoiningDate() == null) { (2)
notifications.create("Cannot generate steps for user without 'Joining date'")
.show();
return;
}
List<Step> steps = dataManager.load(Step.class)
.query("select s from Step s order by s.sortValue asc")
.list(); (3)
for (Step step : steps) {
if (stepsDc.getItems().stream().noneMatch(userStep ->
userStep.getStep().equals(step))) { (4)
UserStep userStep = dataContext.create(UserStep.class); (5)
userStep.setUser(user);
userStep.setStep(step);
userStep.setDueDate(user.getJoiningDate().plusDays(step.getDuration()));
userStep.setSortValue(step.getSortValue());
stepsDc.getMutableItems().add(userStep); (6)
}
}
}
1 | Используйте метод getEditedEntity() базового класса StandardDetailView , чтобы получить редактируемого пользователя. |
2 | Если атрибут joiningDate не установлен, показать сообщение и завершить работу. |
3 | Загрузить список зарегистрированных шагов. |
4 | Пропустить Step , если он уже находится в контейнере коллекции stepsDc . |
5 | Создать новый экземпляр UserStep , используя метод DataContext.create() . |
6 | Добавить новый экземпляр UserStep в контейнер коллекции stepsDc , чтобы отобразить его в пользовательском интерфейсе. |
Когда вы создаете экземпляр сущности с помощью объекта DataContext , данный экземпляр далее отслеживается в DataContext и автоматически сохраняется при сохранении экрана, то есть при нажатии кнопки OK на экране.
|
Нажмите Ctrl/Cmd+S и переключитесь на запущенное приложение. Обновите экран деталей пользователя и убедитесь, что при нажатии кнопки Generate создается несколько записей, соответствующих шагам онбординга.
Если вы сохраните экран, нажав кнопку OK, все созданные экземпляры UserStep
будут сохранены. Если вы нажмете кнопку Cancel, в базе данных ничего сохранено не будет. Это происходит потому, что в приведенном выше коде вы не сохраняете созданные экземпляры UserStep
непосредственно в базу данных. Вместо этого вы добавляете их в DataContext
экрана, создавая их с помощью DataContext.create()
. Таким образом, новые экземпляры сохраняются только тогда, когда сохраняется весь DataContext
.
Улучшение таблицы UserSteps
В нижеприведенных разделах вы доработаете пользовательский интерфейс для работы со сгенерированными UserSteps.
Упорядочивание вложенной коллекции
Вы можете заметить, что когда вы открываете пользователя с ранее сгенерированными UserSteps, они не упорядочены в соответствии с атрибутом sortValue
:
В таблице отображается атрибут коллекции steps
сущности User
, поэтому вы можете ввести порядок на уровне модели данных.
Откройте сущность User
, выберите атрибут steps
и введите sortValue
в поле Order by:
Если вы переключитесь на вкладку Text, вы сможете увидеть аннотацию @OrderBy
у атрибута steps
:
@OrderBy("sortValue")
@Composition
@OneToMany(mappedBy = "user")
private List<UserStep> steps;
Теперь, когда вы загружаете сущность User
, его коллекция steps
будет отсортирована по атрибуту UserStep.sortValue
.
Если ваше приложение запущено, перезапустите его.
Откройте экран деталей пользователя. Теперь порядок UserSteps правильный:
Перестановка колонок таблицы
В настоящее время таблица UserSteps не очень информативна. Давайте удалим колонки Version
и Sort value
и добавим колонку, показывающую название шага.
Удалить колонку просто: выберите ее на панели иерархии Jmix UI и нажмите Delete или удалите элемент непосредственно из XML.
Чтобы добавить колонку, выберите элемент columns
на панели иерархии Jmix UI и нажмите Add → Column на панели инспектора Jmix UI. Появится диалоговое окно Add Column:
Как вы можете видеть, это не позволяет вам добавить название шага. Это связано с тем, что атрибут step
является ссылкой, и вы не определили надлежащий фетч-план для его загрузки.
Выберите контейнер данных userDc
на панели иерархии Jmix UI и нажмите кнопку Edit () либо в свойстве fetchPlan
на панели инспектора Jmix UI, либо в маркере строки редактора XML:
В окне Edit Fetch Plan выберите атрибут steps
→ step
и нажмите на кнопку OK:
Вложенный атрибут будет добавлен в фетч-план в редакторе XML:
<instance id="userDc"
class="com.company.onboarding.entity.User">
<fetchPlan extends="_base">
<property fetchPlan="_base" name="department"/>
<property fetchPlan="_base" name="steps">
<property name="step" fetchPlan="_base"/>
</property>
</fetchPlan>
<loader/>
<collection id="stepsDc" property="steps"/>
</instance>
Теперь коллекция UserSteps будет жадно загружена из базы данных вместе со связанным экземпляром Step
.
Выберите элемент columns
на панели иерархии Jmix UI и нажмите Add → Column на панели инспектора Jmix UI. Диалоговое окно Add Column теперь содержит связанную сущность Step
и ее атрибуты:
Выберите step
→ name
и нажмите на кнопку OK. Новая колонка будет добавлен в конец списка колонок:
<dataGrid id="stepsDataGrid" dataContainer="stepsDc" ...>
<columns>
<column property="dueDate"/>
<column property="completedDate"/>
<column property="step.name"/>
</columns>
Вместо step.name
вы могли бы использовать просто step
. В этом случае в колонке будет отображаться имя экземпляра сущности. Для сущности Step
имя экземпляра получается из атрибута name
, поэтому результат будет таким же.
Вы также можете добавить колонку step без изменения фетч-плана, и пользовательский интерфейс все равно будет работать из-за отложенной загрузки ссылок. Но тогда экземпляры сущности Step будут загружаться отдельными запросами для каждого экземпляра UserStep в коллекции (проблема N+1 запросов).
|
Передвиньте колонку step.name
в начало, перетаскивая элемент на панели иерархии Jmix UI или редактируя XML напрямую:
<dataGrid id="stepsDataGrid" dataContainer="stepsDc" width="100%" height="100%">
<columns>
<column property="step.name"/>
<column property="dueDate"/>
<column property="completedDate"/>
</columns>
</dataGrid>
Нажмите Ctrl/Cmd+S и переключитесь на запущенное приложение. Обновите экран деталей пользователя и убедитесь, что в таблице Steps теперь отображается название шага:
Добавление колонки с компонентом
В этом разделе вы реализуете возможность отмечать выполненный пользователем шаг, устанавливая флажок в строке таблицы. Флажок будет отображаться в дополнительной левой колонке таблицы.
Инжектируйте объект UiComponents
в класс контроллера:
@Autowired
private UiComponents uiComponents;
Вы можете использовать кнопку Inject на верхней панели действий дизайнера, чтобы инжектировать зависимости в контроллеры экранов и бины Spring. |
Добавьте следующий код в обработчика события InitEvent
:
@Subscribe
public void onInit(final InitEvent event) {
timeZoneField.setItems(List.of(TimeZone.getAvailableIDs()));
Grid.Column<UserStep> completedColumn = stepsDataGrid.addComponentColumn(userStep -> { (1)
Checkbox checkbox = uiComponents.create(Checkbox.class); (2)
checkbox.setValue(userStep.getCompletedDate() != null);
checkbox.addValueChangeListener(e -> { (3)
if (userStep.getCompletedDate() == null) {
userStep.setCompletedDate(LocalDate.now());
} else {
userStep.setCompletedDate(null);
}
});
return checkbox; (4)
});
completedColumn.setFlexGrow(0); (5)
completedColumn.setWidth("4em");
stepsDataGrid.setColumnPosition(completedColumn, 0); (6)
}
1 | Метод addComponentColumn() принимает лямбду, которая создает UI-компонент, который должен быть отображен в ячейках колонки. Лямбда принимает экземпляр сущности для данной строки. |
2 | Экземпляр компонента CheckBox создается с помощью фабрики компонентов UiComponents . |
3 | Когда вы нажимаете на флажок, его значение изменяется, и флажок вызывает свой слушатель ValueChangeEvent . Слушатель устанавливает атрибут completedDate у сущности UserStep . |
4 | Лямбда возвращает визуальный компонент, который будет отображаться в ячейках колонки. |
5 | Установка ширины созданной колонки. |
6 | Установка позиции созданной колонки. |
Нажмите Ctrl/Cmd+S и переключитесь на запущенное приложение. Обновите экран деталей пользователя и установите флажки для некоторых строк. Колонка Completed date изменится соответствующим образом:
Изменения в экземплярах UserStep
будут сохранены в базе данных, когда вы нажмете OK на экране. За это отвечает объект экрана DataContext
: он отслеживает изменения во всех сущностях и сохраняет в базе данных измененные экземпляры.
Реагирование на изменения
Когда пользователь создает шаги, отмечает завершенный шаг или удаляет шаг, поле Onboarding status
должно соответствующим образом измениться.
Давайте реализуем реакцию на изменения коллекции пользовательских шагов.
Откройте контроллер UserDetailView
и нажмите Generate Handler в верхней панели действий. Сверните все элементы, затем выберите элементы ItemPropertyChangeEvent
и CollectionChangeEvent
в Data containers handlers
→ stepsDc
:
Нажмите на кнопку OK.
Studio сгенерирует два заглушки метода: onStepsDcItemPropertyChange()
и onStepsDcCollectionChange()
. Реализуйте их, как показано ниже:
@Subscribe(id = "stepsDc", target = Target.DATA_CONTAINER)
public void onStepsDcCollectionChange(final CollectionContainer.CollectionChangeEvent<UserStep> event) {
updateOnboardingStatus(); (1)
}
@Subscribe(id = "stepsDc", target = Target.DATA_CONTAINER)
public void onStepsDcItemPropertyChange(final InstanceContainer.ItemPropertyChangeEvent<UserStep> event) {
updateOnboardingStatus(); (2)
}
private void updateOnboardingStatus() {
User user = getEditedEntity(); (3)
long completedCount = user.getSteps() == null ? 0 :
user.getSteps().stream()
.filter(us -> us.getCompletedDate() != null)
.count();
if (completedCount == 0) {
user.setOnboardingStatus(OnboardingStatus.NOT_STARTED); (4)
} else if (completedCount == user.getSteps().size()) {
user.setOnboardingStatus(OnboardingStatus.COMPLETED);
} else {
user.setOnboardingStatus(OnboardingStatus.IN_PROGRESS);
}
}
1 | Обработчик ItemPropertyChangeEvent вызывается при изменении атрибута сущности. |
2 | Обработчик CollectionChangeEvent вызывается, когда элементы добавляются в контейнер или удаляются из него. |
3 | Получить отредактированный в данный момент экземпляр User . |
4 | Обновить атрибут onboardingStatus . Благодаря привязке данных измененное значение будет немедленно показано UI компонентом. |
Нажмите Ctrl/Cmd+S и переключитесь на запущенное приложение. Обновите экран деталей пользователя и внесите некоторые изменения в таблицу UserSteps. Посмотрите на значение поля Onboarding status
.
Резюме
В этом разделе вы реализовали две функции:
-
Возможность указать отдел для пользователя.
-
Генерация и управление шагами онбординга для пользователя.
Вы узнали, что:
-
Ссылочные атрибуты должны быть добавлены в фетч-план экрана, чтобы избежать проблемы N+1 запросов.
-
Компонент entityComboBox можно использовать для выбора связанной сущности из выпадающего списка. Для этого компонента требуется контейнер коллекции, содержащий элементы списка (опции). Он должен должен быть установлен в
itemsContainer
компонента. -
Взаимосвязь между сущностями
User
иUserStep
является примером композиции, когда экземпляры связанной сущности (UserStep
) могут существовать только как часть ее владельца (User
). Такая ссылка помечается аннотацией @Composition. -
Коллекцию связанных сущностей можно упорядочить, используя аннотацию
@OrderBy
в ссылочном атрибуте. -
Обработчик событий
ClickEvent
компонента button используется для обработки нажатий кнопок. Его можно сгенерировать на вкладке Handlers панели инспектора Jmix UI. -
Метод
getEditedEntity()
экрана деталей сущности возвращает редактируемый экземпляр сущности. -
Интерфейс Notifications используется для отображения всплывающих уведомлений.
-
Интерфейс DataManager можно использовать для загрузки данных из базы данных.
-
Вложенная коллекция связанных сущностей загружается в CollectionPropertyContainer. Его методы
getItems()
иgetMutableItems()
можно использовать для перебора и добавления/удаления элементов в коллекцию. -
DataContext отслеживает изменения в сущностях и сохраняет измененные экземпляры в базе данных, когда пользователь нажимает OK на экране.
-
Таблица UI может иметь колонки, которые отображают произвольные визуальные компоненты.
-
ItemPropertyChangeEvent и CollectionChangeEvent можно использовать для реагирования на изменения в объектах, расположенных в контейнерах данных.