В предыдущем посте мы видели, как 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 .