- загрузить данные в таблицу
- сохранить данные с привязкой формы
- удалить записи и обновить вид программно
Ключевым отличием реализации ZK MVVM от ZK MVC является то, что мы не обращаемся к компонентам пользовательского интерфейса и не манипулируем ими непосредственно в классе контроллера (ViewModel). В этом посте мы увидим, как мы можем делегировать некоторые манипуляции с пользовательским интерфейсом клиентскому коду, а также как передать параметры из View в ViewModel.
Задача
Создайте функцию обновления для нашей простой функции инвентаризации CRUD. Пользователи могут редактировать записи на месте в таблице, и им предоставляется выбор обновить или отменить внесенные изменения. Измененные записи выделены красным.
ZK Особенности в действии
- ZK Клиентские API
- ZK Style Class
- MVVM: передать параметры из View в ViewModel
Реализация в шагах
Включите редактирование на месте в списке, чтобы мы могли редактировать записи:
| 1 2 3 4 5 6 7 8 | <listcell>       <textboxinplace="true"value="@load(each.name)"...</textbox>   </listcell>   ....   <listcell>       <doubleboxinplace="true"value="@load(each.price)"...</textbox>   </listcell>   ... | 
- inplace = ”true” отображает элементы ввода, такие как Textbox, без границ, отображая их в виде простых меток; границы появляются, только если выбран элемент ввода
- строки 2, 6, «каждый» относится к каждому объекту Item в сборе данных
  После того, как запись отредактирована, мы хотим дать пользователям возможность обновить или отменить изменение. 
  Кнопки «Обновить» и «Отменить» должны быть видны только в том случае, если пользователь внес изменения в записи списка.  Сначала мы определяем функции JavaScript для отображения и скрытия кнопок «Обновить» и «Отменить»: 
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 | <toolbar>    ...    <spanid="edit_btns"visible="false"...>        <toolbarbuttonlabel="Update".../>        <toolbarbuttonlabel="Discard".../>    </span></toolbar>    <scripttype="text/javascript">        function hideEditBtns(){     jq('$edit_btns').hide();        }          function showEditBtns(){      jq('$edit_btns').show();        }    </script>    ... | 
- строка 2, мы обертываем Update и Discard и устанавливаем видимость в false
- в строках 9, 13 мы определяем функции, которые скрывают и показывают кнопки « Обновить» и « Отменить»
- в строке 11, 15 мы используем селектор jQuery jq (‘$ edit_btns’), чтобы получить виджет ZK, идентификатор которого равен «edit_btns»; обратите внимание, что шаблон селектора для идентификатора виджета ZK равен ‘$’, а не ‘#’
Когда записи в списке будут изменены, мы сделаем видимыми кнопки «Обновить / Отменить» и сделаем измененные значения красными. После нажатия кнопки «Обновить» или «Отменить» мы хотели бы снова скрыть кнопки
Поскольку это чисто пользовательский интерфейс, мы будем использовать клиентские API ZK:
| 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 | <style>   .inputs { font-weight: 600; }   .modified { color: red; }</style>...    <toolbarxmlns:w="client">    ...    <spanid="edit_btns"visible="false"...>         <toolbarbuttonlabel="Update"w:onClick="hideEditBtns()".../>         <toolbarbuttonlabel="Discard"w:onClick="hideEditBtns()".../>    </span>    </toolbar>    <scripttype="text/javascript">        //show hide functions        zk.afterMount(function(){            jq('.inputs').change(function(){            showEditBtns();            $(this).addClass('modified');     })        });    </script>    ...    <listcell>       <doubleboxinplace="true"sclass="inputs"value="@load(each.price)"...</textbox>   </listcell>   ... | 
- В строке 2 мы указываем класс стиля для наших элементов ввода (Textbox, Intbox, Doublebox, Datebox) и назначаем его атрибуту sclass элементов ввода, например. строка 26; sclass определяет класс стиля для виджетов ZK
- В строке 18-20 мы получаем все входные элементы, сопоставляя их имя в классе и назначая обработчик события onChange. После изменения значения в элементе ввода кнопки «Обновить / Отменить» станут видимыми, а измененное значение будет выделено красным цветом.
- строка 17, zk.afterMount запускается при создании виджетов ZK
- В строке 6 мы указываем пространство имен клиента, чтобы мы могли зарегистрировать прослушиватели событий onClick на стороне клиента с синтаксисом «w: onClick». Обратите внимание, что мы все еще можем зарегистрировать наш обычный приемник событий onClick, который обрабатывается на сервере одновременно.
- в строке 9, 10 мы назначаем клиентский приемник события onClick; функция hideEditBtns будет вызвана, чтобы сделать кнопки снова невидимыми
Определите метод для хранения измененных объектов Item в коллекции, чтобы изменения могли обновляться в пакетном режиме, если пользователь решит это сделать:
| 1 2 3 4 5 6 7 8 9 | publicclassInventoryVM {    privateHashSet<Item> itemsToUpdate = newHashSet<item>();    ...    @Command    publicvoidaddToUpdate(@BindingParam("entry") Item item){        itemsToUpdate.add(item);    } | 
- В строке 6 мы аннотируем этот метод как командный метод, чтобы его можно было вызывать из View
- строка 7, @BindingParam («entry») Элемент Item связывает произвольно названный параметр, называемый «entry»; мы ожидаем, что параметр будет иметь тип Item
Создайте метод для обновления изменений, внесенных в представлении, в модель данных.
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | publicclassInventoryVM {    privateList<Item> items;    privateHashSet<Item> itemsToUpdate = newHashSet<item>();    ...    @NotifyChange("items")    @Command    publicvoidupdateItems() throwsException{        for(Item i : itemsToUpdate){            i.setDatemod(newDate());            DataService.getInstance().updateItem(i);        }        itemsToUpdate.clear();        items = getItems();    } | 
Когда внесены изменения в записи Listbox, вызовите метод addToUpdate и передайте ему отредактированный объект Item, который, в свою очередь, сохраняется в коллекции itemsToUpdate.
| 1 2 3 4 5 6 7 8 | <listitem> <listcell>  <doubleboxvalue="@load(each.price)                 @save(each.name, before='updateItems')"                  onChange="@command('addToUpdate',entry=each)"/> </listcell> ...</listitem> | 
- @save (each.name, before = ‘updateItems’) гарантирует, что измененные значения не будут сохранены, пока не будет вызван updateItems (т. е. когда пользователь нажимает кнопку «Обновить»)
Наконец, когда пользователь нажимает кнопку «Обновить», мы вызываем метод updateItems для обновления изменений в модели данных. Если нажать Discard, мы вызываем getItems, чтобы обновить список без применения каких-либо изменений.
| 1 2 3 4 | ... <toolbarbuttonlabel="Update"onClick="@command('updateItems')".../> <toolbarbuttonlabel="Discard"onClick="@command('getItems')".../> ... | 
В двух словах
- В соответствии с шаблоном MVVM мы стремимся сохранить код ViewModel независимым от любых компонентов View
- Поскольку у нас нет прямой ссылки на компоненты пользовательского интерфейса в коде ViewModel, мы можем делегировать код манипуляции пользовательским интерфейсом (в нашем примере кода, показать / скрыть, изменение стиля) клиенту с помощью клиентских API ZK.
- Мы можем использовать селекторы jQuery и API на стороне клиента ZK
- Мы можем легко передавать параметры из View в ViewModel с помощью @BindingParam
Далее мы еще немного поговорим о ZK Styling, а затем рассмотрим валидаторы и конвертеры MVVM.
ViewModel (ZK в действии [0] ~ [3]):
| 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | publicclassInventoryVM { privateList<Item> items; privateItem newItem; privateItem selected; privateHashSet<Item> itemsToUpdate = newHashSet<Item>();  publicInventoryVM(){}  //CREATE @NotifyChange("newItem") @Command publicvoidcreateNewItem(){  newItem = newItem("", "",0, 0,newDate()); }  @NotifyChange({"newItem","items"}) @Command publicvoidsaveItem() throwsException{  DataService.getInstance().saveItem(newItem);  newItem = null;  items = getItems(); }   @NotifyChange("newItem") @Command publicvoidcancelSave() throwsException{  newItem = null; }  //READ @NotifyChange("items") @Command publicList<Item> getItems() throwsException{  items = DataService.getInstance().getAllItems();  for(Item j : items){   System.out.println(j.getModel());  }  Clients.evalJavaScript("zk.afterMount(function(){jq('.inputs').removeClass('modified').change(function(){$(this).addClass('modified');showEditBtns();})});"); //how does afterMount work in this case?  returnitems; }  //UPDATE @NotifyChange("items") @Command publicvoidupdateItems() throwsException{  for(Item i : itemsToUpdate){   i.setDatemod(newDate());   DataService.getInstance().updateItem(i);  }  itemsToUpdate.clear();  items = getItems(); }  @Command publicvoidaddToUpdate(@BindingParam("entry") Item item){  itemsToUpdate.add(item); }  //DELETE @Command publicvoiddeleteItem() throwsException{  if(selected != null){     String str = "The item with name \""+selected.getName()+"\" and model \""+selected.getModel()+"\" will be deleted.";   Messagebox.show(str,"Confirm Deletion", Messagebox.OK|Messagebox.CANCEL, Messagebox.QUESTION,     newEventListener<Event>(){     @Override     publicvoidonEvent(Event event) throwsException {      if(event.getName().equals("onOK")){       DataService.getInstance().deleteItem(selected);       items = getItems();       BindUtils.postNotifyChange(null, null, InventoryVM.this, "items");      }     }   });     } else{   Messagebox.show("No Item was Selected");  }  }  publicItem getNewItem() {  returnnewItem; } publicvoidsetNewItem(Item newItem) {  this.newItem = newItem; } publicItem getselected() {  returnselected; } publicvoidsetselected(Item selected) {  this.selected = selected; }} | 
Вид (ZK в действии [0] ~ [3]):
| 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 | <zk> <style>  .z-toolbarbutton-cnt { font-size: 17px;} .edit-btns {border: 2px  solid #7EAAC6; padding: 6px 4px 10px 4px; border-radius: 6px;}  .inputs { font-weight: 600; } .modified { color: red; } </style> <scripttype="text/javascript">  function hideEditBtns(){ jq('$edit_btns').hide(); }  function showEditBtns(){ jq('$edit_btns').show(); }  zk.afterMount(function(){ jq('.inputs').change(function(){  $(this).addClass('modified'); showEditBtns(); }) }); </script> <windowapply="org.zkoss.bind.BindComposer"  viewModel="@id('vm') @init('lab.sphota.zk.ctrl.InventoryVM')"  xmlns:w="client">  <toolbarwidth="100%">   <toolbarbuttonlabel="Add"    onClick="@command('createNewItem')"/>   <toolbarbuttonlabel="Delete"    onClick="@command('deleteItem')"    disabled="@load(empty vm.selected)"/>   <spanid="edit_btns"sclass="edit-btns"visible="false">    <toolbarbuttonlabel="Update"     onClick="@command('updateItems')"w:onClick="hideEditBtns()"/>    <toolbarbuttonlabel="Discard"     onClick="@command('getItems')"w:onClick="hideEditBtns()"/>   </span>  </toolbar>  <groupboxmold="3d"   form="@id('itm') @load(vm.newItem) @save(vm.newItem, before='saveItem')"   visible="@load(not empty vm.newItem)">   <captionlabel="New Item"></caption>   <gridwidth="50%">    <rows>     <row>      <labelvalue="Item Name"width="100px"></label>      <textboxvalue="@bind(itm.name)"/>     </row>     <row>      <labelvalue="Model"width="100px"></label>      <textboxvalue="@bind(itm.model)"/>     </row>     <row>      <labelvalue="Unit Price"width="100px"></label>      <decimalboxvalue="@bind(itm.price)"       format="#,###.00"constraint="no empty, no negative"/>     </row>     <row>      <labelvalue="Quantity"width="100px"></label>      <spinnervalue="@bind(itm.qty)"       constraint="no empty,min 0 max 999:        Quantity Must be Greater Than Zero" />     </row>     <row>      <cellcolspan="2"align="center">       <buttonwidth="80px"label="Save"mold="trendy"        onClick="@command('saveItem')"/>       <buttonwidth="80px"label="Cancel"mold="trendy"        onClick="@command('cancelSave')"/>      </cell>     </row>    </rows>   </grid>  </groupbox>  <listboxselectedItem="@bind(vm.selected)"model="@load(vm.items) ">   <listhead>    <listheaderlabel="Name"sort="auto"hflex="2"/>    <listheaderlabel="Model"sort="auto"hflex="1"/>    <listheaderlabel="Quantity"sort="auto"hflex="1"/>    <listheaderlabel="Unit Price"sort="auto"hflex="1"/>    <listheaderlabel="Last Modified"sort="auto"hflex="2"/>   </listhead>   <templatename="model">    <listitem>     <listcell>      <textboxinplace="true"width="110px"sclass="inputs"       value="@load(each.name) @save(each.name, before='updateItems')"       onChange="@command('addToUpdate',entry=each)">      </textbox>     </listcell>     <listcell>      <textboxinplace="true"width="110px"sclass="inputs"       value="@load(each.model) @save(each.model, before='updateItems')"       onChange="@command('addToUpdate',entry=each)"/>     </listcell>     <listcell>      <intboxinplace="true"sclass="inputs"       value="@load(each.qty) @save(each.qty, before='updateItems')"       onChange="@command('addToUpdate',entry=each)"/>     </listcell>     <listcell>      <doubleboxinplace="true"sclass="inputs"format="###,###.00"       value="@load(each.price) @save(each.price, before='updateItems')"       onChange="@command('addToUpdate',entry=each)"/>     </listcell>     <listcelllabel="@load(each.datemod)"/>    </listitem>   </template>  </listbox> </window></zk> | 
Ссылка: ZK в действии [3]: MVVM — Совместная работа с ZK Client API от нашего партнера JCG Лэнса Лу в блоге Tech Dojo
