DataContext
Интерфейс DataContext позволяет отслеживать изменения в сущностях, загружаемых на уровень UI. Отслеживаемые сущности помечаются как "грязные" при любом изменении значений их атрибутов, и DataContext сохраняет грязные экземпляры при вызове его метода save().
Внутри DataContext сущность с некоторым идентификатором будет представлена единственным объектом, вне зависимости от того, где и сколько раз она использована в графах сущностей, находящихся в данном контексте.
Чтобы сущность отслеживалась, ее необходимо поместить в DataContext с помощью метода merge(). Если контекст не содержит экземпляра сущности с таким же идентификатором, то контекст создает новый экземпляр и копирует в него состояние переданного. Если контекст уже содержит экземпляр сущности с таким же идентификатором, он копирует в имеющийся экземпляр состояние переданного и возвращает имеющийся экземпляр. Данный механизм позволяет всегда иметь в контексте не более одного экземпляра сущности с конкретным идентификатором.
При помещении сущности в контекст методом merge() весь граф объектов с корнем в данной сущности также помещается в контекст. То есть все связанные сущности, включая коллекции, становятся отслеживаемыми.
Главный принцип использования метода merge() заключается в том, чтобы продолжать работать с возвращенным из метода экземпляром, забывая про переданный. В большинстве случаев возвращенный экземпляр будет другим. Единственное исключение – если в merge() передан экземпляр объекта, ранее возвращенный другим вызовом merge() или find() этого же контекста.
|
Пример помещения сущности в DataContext:
@ViewComponent
private CollectionContainer<Department> departmentsDc;
@Autowired
private DataManager dataManager;
@ViewComponent
private DataContext dataContext;
private void loadDepartment(Id<Department> departmentId) {
Department department = dataManager.load(departmentId).one();
Department trackedDepartment = dataContext.merge(department);
departmentsDc.getMutableItems().add(trackedDepartment);
}
Для некоторго экрана существует только один экземпляр DataContext. Он создается автоматически, если в XML-дескрипторе экрана есть элемент <data>.
Загрузчики данных, определенные в XML, автоматически помещают загруженные сущности в DataContext, если у них нет атрибута readOnly="true". По умолчанию загрузчики данных в экранах списков сущностей, создаваемых Studio, включают этот атрибут. Поэтому, если вам нужно отслеживать изменения и сохранять измененные сущности в экранах списков, удалите атрибут readOnly="true" у соответствующих загрузчиков.
Получение DataContext
-
DataContextэкрана можно получить в его контроллере используя инжектирование:@ViewComponent private DataContext dataContext; -
Если имеется ссылка на некоторый экран, то получить его
DataContextможно с помощью классаViewControllerUtils:private void sampleMethod(View sampleView) { DataContext dataContext = ViewControllerUtils.getViewData(sampleView).getDataContext(); // ... }
Родительский DataContext
Сущности DataContext могут образовывать отношения предок-потомок. Если у экземпляра DataContext есть родительский контекст, он будет сохранять измененные сущности в своего предка вместо того, чтобы сразу отправлять их в хранилище данных. Эта особенность позволяет редактировать агрегаты, когда дочерние сущности должны сохраняться только вместе с родительской. Если атрибут сущности снабжен аннотацией @Composition, фреймворк автоматически установит родительский контекст для экрана деталей этого атрибута, чтобы измененная сущность атрибута сохранялась в контекст сущности-владельца.
Подобное поведение можно легко настроить вручную для любой сущности или экрана.
Если вы программно открываете экран деталей сущности, который должен сохранять изменения в data context текущего экрана, используйте метод withParentDataContext():
private void detailViewWithCurrentDataContextAsParent() {
DialogWindow<DepartmentDetailView> dialogWindow = dialogWindows.detail(this, Department.class)
.withViewClass(DepartmentDetailView.class)
.withParentDataContext(dataContext)
.build();
dialogWindow.open();
}
События и обработчики
В этом разделе описываются события жизненного цикла DataContext, на которые можно подписаться в контроллерах экрана.
|
Чтобы сгенерировать заглушку обработчика в Jmix Studio, выберите элемент данных в XML-коде дескриптора экрана или на панели структуры Jmix UI и используйте вкладку Handlers панели инспектора Jmix UI. В качестве альтернативы вы можете воспользоваться кнопкой Generate Handler на верхней панели контроллера экрана. |
SaveDelegate
По умолчанию DataContext сохраняет измененные и удаленные объекты с помощью метода DataManager.save(SaveContext). Обработчик saveDelegate позволяет настраивать логику сохранения данных, что особенно полезно при работе с DTO сущностями. Например, вы можете сохранить измененные объекты с помощью специального сервиса:
@Autowired
private DepartmentService departmentService;
@Install(target = Target.DATA_CONTEXT)
private Set<Object> saveDelegate(final SaveContext saveContext) {
return departmentService.saveEntities(
saveContext.getEntitiesToSave(),
saveContext.getEntitiesToRemove());
}
Обработчик saveDelegate должен возвращать Set сохраненных экземпляров. Если это невозможно, верните исходные экземпляры из saveContext.getEntitiesToSave() или просто пустой Set. Не возвращайте удаленные экземпляры. Возвращенные экземпляры будут помещены обратно в DataContext, и экран продолжит работу с обновленным состоянием.
ChangeEvent
Это событие отправляется, когда DataContext обнаруживает изменения в отслеживаемой сущности, в контекст помещается новый экземпляр, или при удалении сущности.
@Subscribe(target = Target.DATA_CONTEXT)
public void onChange(final DataContext.ChangeEvent event) {
log.debug("Changed entity: " + event.getEntity());
}
PreSaveEvent
Это событие отправляется перед сохранением изменений.
В слушателе этого события можно добавлять произвольные экземпляры сущностей в сохраняемые коллекции, возвращаемые методами getModifiedInstances() и getRemovedInstances(), например:
@Subscribe(target = Target.DATA_CONTEXT)
public void onPreSave(final DataContext.PreSaveEvent event) {
event.getModifiedInstances().add(department);
}
Вы также можете предотвратить сохранение, используя метод события preventCommit(), например:
@Subscribe(target = Target.DATA_CONTEXT)
public void onPreSave2(DataContext.PreSaveEvent event) {
if (checkSomeCondition()) {
event.preventSave();
}
}
PostSaveEvent
Это событие отправляется после сохранения изменений.
Из слушателя этого события можно получить коллекцию сохраненных сущностей, возвращенных из DataManager или собственного save delegate. Эти сущности уже помещены в DataContext. Например:
@Subscribe(target = Target.DATA_CONTEXT)
public void onPostSave(final DataContext.PostSaveEvent event) {
log.debug("Saved: " + event.getSavedInstances());
}