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>
.
Элемент <data>
может содержать атрибут readOnly="true"
, в этом случае будет использована специальная "no-op"-реализация интерфейса DataContext
, в которой не будут отслеживаться изменения в сущностях и, тем самым, не оказывает влияние на производительность. Экраны просмотра списков, автоматически создаваемые в Studio, по умолчанию имеют read-only data context, поэтому если вам нужно отслеживать изменения и сохранять грязные сущности в экране списка, удалите XML-атрибут 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();
}
Убедитесь что родительский контекст определен без атрибута readOnly="true" . Такой контекст вызовет исключение при попытке использовать его в качестве родительского для другого контекста.
|
События и обработчики
В этом разделе описываются события жизненного цикла 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());
}