В предыдущем посте мы видели, как Ajax Framework ZK использует контроллер, вдохновленный селектором CSS, для подключения компонентов пользовательского интерфейса в View и прослушивания их событий. Согласно этому шаблону ZK MVC компоненты пользовательского интерфейса в представлении не должны быть привязаны к каким-либо методам контроллера или объектам данных. Гибкость использования шаблонов селектора в качестве средства отображения состояний и событий View на контроллер делает код более адаптивным к изменениям.
MVVM приближается к разделению концерна в обратном направлении. В соответствии с этим шаблоном в контроллере размещаются модель представления и механизм связывания. Связыватель отображает запросы от View к логике действия в View-Model и обновляет любое значение (данные) с обеих сторон, позволяя View-Model быть независимой от любого конкретного View.
Анатомия МВВМ в ЗК 6
Ниже приведена принципиальная схема шаблона MVKM ZK 6:
Вот некоторые дополнительные моменты, которые не показаны на диаграмме:
BindComposer:
- реализует стандартные интерфейсы контроллера ZK (Composer & ComposerExt)
- реализация по умолчанию достаточна, никаких изменений не требуется
Посмотреть:
- сообщает связующему, какой метод вызывать и какие свойства обновлять в View-Model
Вид-модель:
- просто POJO
- связь со связующим осуществляется через аннотацию Java
MVVM в действии
Рассмотрим задачу отображения упрощенного инвентаря без знания точной разметки пользовательского интерфейса. Инвентарь — это набор предметов, поэтому у нас есть объектное представление такого:
|
1
2
3
4
5
6
7
8
|
public class Item { private String ID; private String name; private int quantity; private BigDecimal unitPrice; //getters & setters} |
Также имеет смысл ожидать, что элемент в списке может быть выбран и обработан. Таким образом, основываясь на наших знаниях и предположениях, мы можем пойти дальше и реализовать View-Model.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
public class InventoryVM { ListModelList<Item> inventory; Item selectedItem; public ListModelList<Item> getInventory(){ inventory = new ListModelList<Item>(InventoryDAO.getInventory()); return inventory; } public Item getSelectedItem() { return selectedItem; } public void setSelectedItem(Item selectedItem) { this.selectedItem = selectedItem; }} |
Здесь у нас есть типичный POJO для реализации View-Model, данные с их геттерами и сеттерами.
Посмотреть реализацию, «Возьми один»
Теперь предположим, что позже мы узнали, что требования к представлению — это просто табличное отображение:
Возможная наценка для достижения пользовательского интерфейса, как указано выше:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<window title='Inventory' border='normal' apply='org.zkoss.bind.BindComposer' viewModel='@id('vm') @init('lab.zkoss.mvvm.ctrl.InventoryVM')' > <listbox model='@load(vm.inventory)' width='600px' > <auxhead><auxheader label='Inventory Summary' colspan='5' align='center'/> </auxhead> <listhead> <listheader width='15%' label='Item ID' sort='auto(ID)'/> <listheader width='20%' label='Name' sort='auto(name)'/> <listheader width='20%' label='Quantity' sort='auto(quantity)'/> <listheader width='20%' label='Unit Price' sort='auto(unitPrice)'/> <listheader width='25%' label='Net Value'/> </listhead> <template name='model' var='item'> <listitem> <listcell><label value='@load(item.ID)'/></listcell> <listcell><label value='@load(item.name)'/></listcell> <listcell><label value='@load(item.quantity)'/></listcell> <listcell><label value='@load(item.unitPrice)'/></listcell> <listcell><label value='@load(item.unitPrice * item.quantity)'/></listcell> </listitem> </template> </listbox></window> |
Давайте уточним немного о наценке здесь.
- В строке 1 мы применяем BindComposer по умолчанию к компоненту Window, что делает все дочерние компоненты Window объектом эффекта BindComposer.
- В следующей строке мы указываем BindComposer, какой класс View-Model создавать, и даем экземпляру View-Model идентификатор, чтобы мы могли ссылаться на него.
- Так как мы загружаем коллекцию данных в Listbox, в строке 3 мы присваиваем свойству инвентаризации нашего экземпляра View-Model, который является коллекцией объектов Item, атрибут Listbox «model».
- В строке 12 мы используем модель нашего компонента Template. Шаблон перебирает свои вложенные компоненты в соответствии с моделью, которую он получает. В этом случае у нас есть 5 элементов списка, которые составляют строку в списке.
- В каждом Listcell мы загружаем свойства каждого объекта и отображаем их в метках.
Через систему привязки ZK мы смогли получить доступ к данным в нашем экземпляре View-Model и загрузить их в View с помощью аннотаций.
Посмотреть реализацию, возьми два
Предположим, позже в процессе разработки было решено, что текущий табличный дисплей занимает слишком много места в нашей презентации, и теперь нас просят показать детали элемента только тогда, когда элемент выбран в комбинированном списке, как показано ниже:
Хотя и представление, и поведение (детали отображаются только по выбору пользователя) отличаются от нашей предыдущей реализации, класс View-Model не нуждается в значительном изменении. Поскольку детали элемента будут отображаться только тогда, когда он выбран в Combobox, очевидно, что нам нужно обработать событие «onSelect», давайте добавим новый метод doSelect :
|
01
02
03
04
05
06
07
08
09
10
11
12
|
public class InventoryVM { ListModelList<Item> inventory; Item selectedItem; @NotifyChange('selectedItem') @Command public void doSelect(){ } //getters & setters} |
Метод, аннотированный @Command, позволяет нам вызываться из нашей разметки по его имени, в нашем случае:
|
1
|
<combobox onSelect='@command('doSelect')' > |
Аннотация @NotifyChange (‘selectedItem’) позволяет автоматически обновлять свойство selectedItem всякий раз, когда пользователь выбирает новый элемент в комбинированном списке. Для наших целей не требуется никакой дополнительной реализации для метода doSelect. С этим небольшим изменением мы можем теперь видеть, как эта слегка измененная View-Model будет работать с нашей новой разметкой:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
<window title='Inventory' border='normal' apply='org.zkoss.bind.BindComposer' viewModel='@id('vm') @init('lab.zkoss.mvvm.ctrl.InventoryVM')' width='600px'> ... <combobox model='@load(vm.inventory)' selectedItem='@bind(vm.selectedItem)' onSelect='@command('doSelect')' > <template name='model' var='item'> <comboitem label='@load(item.ID)'/> </template> <comboitem label='Test'/> </combobox> <listbox visible='@load(not empty vm.selectedItem)' width='240px'> <listhead> <listheader ></listheader> <listheader ></listheader> </listhead> <listitem> <listcell> <label value='Item Name: ' /> </listcell> <listcell> <label value='@load(vm.selectedItem.name)' /> </listcell> </listitem> <listitem> <listcell> <label value='Unit Price: ' /> </listcell> <listcell> <label value='@load(vm.selectedItem.unitPrice)' /> </listcell> </listitem> <listitem> <listcell> <label value='Units in Stock: ' /> </listcell> <listcell> <label value='@load(vm.selectedItem.quantity)' /> </listcell> </listitem> <listitem> <listcell> <label value='Net Value: ' /> </listcell> <listcell> <label value='@load(vm.selectedItem.unitPrice * vm.selectedItem.quantity)' /> </listcell> </listitem> </listbox> ...</window> |
- В строке 4 мы загружаем инвентарь сбора данных в атрибут модели Combobox, чтобы он мог итеративно отображать идентификатор каждого объекта Item в модели данных, используя компонент Template, объявленный в строке 7.
- В строке 5 атрибут selectedItem указывает на последний выбранный элемент в этом списке объектов Item.
- В строке 6 мы сопоставили событие onSelect с методом doSelect View-Model
- В строке 12 мы делаем список, содержащий детали элемента видимым, только если свойство selectedItem в View-Model не пусто (selectedItem останется пустым, пока элемент не будет выбран в комбинированном списке).
- Свойства selectedItem затем загружаются для заполнения списка.
резюмировать
В соответствии с шаблоном MVVM наш класс View-Model предоставляет свои данные и методы связывателю; нет ссылки на какой-либо конкретный компонент View. Реализации View осуществляют доступ к данным или вызывают обработчики событий через связыватель.
В этом посте мы знакомимся только с основами работы механизмов MVKM в ZK. Связыватель явно не ограничивается только загрузкой данных из View-Model. Помимо сохранения данных из View в ViewModel, мы также можем внедрить преобразователи и валидаторы данных в сочетание соединений View и View-Model. Шаблон MVVM также может работать в сочетании с моделью MVC. То есть мы можем также связывать компоненты и прослушивать fired-события через механизм селектора MVC, если мы хотим это сделать.
Мы углубимся в некоторые из этих тем позже.
Ссылка: первый взгляд на MVVM в ZK 6 от нашего партнера по JCG Лэнса Лу в блоге Tech Dojo .


