3.5.3.3. DataContext
Интерфейс DataContext
позволяет отслеживать изменения в сущностях, загружаемых на клиентский уровень. Отслеживаемые сущности помечаются как "грязные" при любом изменении их атрибутов, и DataContext
сохраняет грязные экземпляры на Middleware при вызове его метода commit()
.
Внутри DataContext
сущность с некоторым идентификатором будет представлена как единственный объект, вне зависимости от того, где и сколько раз она использована в графах других объектов.
Чтобы сущность отслеживалась, её необходимо поместить в DataContext
с помощью метода merge()
. Если контекст не содержит экземпляра сущности с таким же идентификатором, то контекст создает новый экземпляр и копирует в него состояние переданного. Если контекст уже содержит экземпляр сущности с таким же идентификатором, он копирует в имеющегося состояние переданного и возвращает. Данный механизм позволяет всегда иметь в контексте не более одного экземпляра сущности с конкретным идентификатором.
При помещении сущности в контекст методом merge
весь граф объектов с корнем в данной сущности также помещается в контекст. То есть все связанные сущности, включая коллекции, становятся отслеживаемыми.
Главный принцип использования метода |
Пример помещения сущности в DataContext
:
@Inject
private DataContext dataContext;
private void loadCustomer(Id<Customer, UUID> customerId) {
Customer customer = dataManager.load(customerId).one();
Customer trackedCustomer = dataContext.merge(customer);
customersDc.getMutableItems().add(trackedCustomer);
}
Для одного экрана и всех его вложенных фрагментов может существовать только один экземпляр DataContext
. Он создаётся автоматически, если в XML-дескрипторе экрана существует элемент <data>
.
Элемент <data>
может содержать атрибут readOnly="true"
, в этом случае будет использована специальная "no-op"-реализация, в которой не будут отслеживаться изменения в сущностях и, следовательно, улучшится быстродействие экрана. Экраны просмотра списков, автоматически создаваемые в Studio, по умолчанию имеют read-only data context, поэтому если вам нужно отслеживать изменения и сохранять грязные сущности в браузере, удалите XML-атрибут readOnly="true"
.
- Получение DataContext
-
DataContext
экрана можно получить в его контроллере используя инжекцию:@Inject private DataContext dataContext;
Если имеется ссылка на некоторый экран, то получить его
DataContext
можно с помощью классаUiControllerUtils
:DataContext dataContext = UiControllerUtils.getScreenData(screenOrFrame).getDataContext();
UI-компонента может получить
DataContext
текущего экрана следующим образом:DataContext dataContext = UiControllerUtils.getScreenData(getFrame().getFrameOwner()).getDataContext();
- Родительский DataContext
-
Сущности
DataContext
могут образовывать отношения предок-потомок. Если у экземпляраDataContext
есть родительский контекст, он будет сохранять измененные сущности в своего предка вместо того, чтобы сразу отправлять их на Middleware. Эта особенности позволяет редактировать композитные сущности, где дочерние сущности должны сохраняться только вместе с родительской. Если атрибут сущности снабжён аннотацией @Composition, фреймворк автоматически установит родительский контекст для экрана редактирования этого атрибута, чтобы изменённая сущность атрибута могла быть сохранена только вместе с основной сущностью.Подобное поведение можно легко настроить вручную для любой сущности или экрана.
Если вы программно открываете экран редактирования сущности, который должен сохранять изменения в data context текущего экрана, используйте метод
withParentDataContext()
builder’а:@Inject private ScreenBuilders screenBuilders; @Inject private DataContext dataContext; private void editFooWithCurrentDataContextAsParent() { FooEdit fooEdit = screenBuilders.editor(Foo.class, this) .withScreenClass(FooEdit.class) .withParentDataContext(dataContext) .build(); fooEdit.show(); }
Если вы открываете простой экран с помощью бина
Screens
, определите в нём сеттер, принимающий data context родительского экрана:public class FooScreen extends Screen { @Inject private DataContext dataContext; public void setParentDataContext(DataContext parentDataContext) { dataContext.setParent(parentDataContext); } }
Этот метод вы сможете использовать при создании экрана:
@Inject private Screens screens; @Inject private DataContext dataContext; private void openFooScreenWithCurrentDataContextAsParent() { FooScreen fooScreen = screens.create(FooScreen.class); fooScreen.setParentDataContext(dataContext); fooScreen.show(); }
Убедитесь, что для родительского data context не задан атрибут
readOnly="true"
. В противном случае при попытке использовать его как предка другого контекста будет выброшено исключение.