diff --git a/src/docs/guide/3.9 The Next Generation Data Binding System.gdoc b/src/docs/guide/3.9 The Next Generation Data Binding System.gdoc index 38dc2a8..9b3f985 100644 --- a/src/docs/guide/3.9 The Next Generation Data Binding System.gdoc +++ b/src/docs/guide/3.9 The Next Generation Data Binding System.gdoc @@ -1,184 +1,7 @@ h3. Introduction ZK Bind is a whole new data binding system with new specifications and implementations. -h3. Features - -h4. A whole new, clean annotation expression -* Same Java style annotation expression: The new ZK annotation is consistent with Java's annotation style. If you know the Java style, you know the ZK Style. -* A set of collaborated annotations: ZK Bind uses a set of annotations to make the use of data binding as intuitive and clear as possible. -** @bind(...): used to bind data and command along with parameters -** @converter(...): used to specify converters along with parameters -** @valiator(...): used to specify validators along with parameters -** @form(...): used to bind a form. - -h4. EL 2.2 flexiable expressions -* ZK Bind accept EL 2.2 syntax expression that you can provide flexible operations easily. -* Bind to bean properties, indexed properties, Map keys seamlessly. -* Bind to component custom attributes automatically. -* Bind to Spring, CDI, and Seam managed bean automatically. -{code:xml} - - - - - -{code} - -h4. One way load only data binding -* Load when bean property changes -* Conditional load @after@/@before@ executing a command -* Multiple conditional load @before@/@after@ executing different/same commands - -{code:xml} - - - - - - - -{code} - -h4. One way save only data binding -* Save when UI component attribute changes -* Multiple save to property of different target beans -* Conditional save before/after executing a command -* Multiple conditional save before/after executing different/same commands - -{code:xml} - - - - - - - - - -{code} - -h4. Initial data binding -* Load when UI components are first added into the binding system -{code:xml} - -{code} - -h4. Two way data binding -* Short expression of both save and load bindings -* Multiple conditional load and save on different back-end beans before/after executing different/same commands -{code:xml} - - - -{code} - -h4. Bind to any attributes of the UI components -* Bind symmetrically to all attributes of UI components -{code:xml} - - -{code} - -h4. Event command binding -* Bridge ZK event to command -* Automatic event listener registration -* Simple command name invocation -{code:xml} - -{code} - -h4. Template/Collection binding -* Binding on Listbox/Grid/Tree/Combobox -* Local variable scope is limited to the container component -* Support index property -{code:xml} - - - - - -{code} - -h4. Form binding -* Middle form binding to avoid affecting back-end data beans -* Submit a form in a whole -* Conditional save for different commands -{code:xml} - - Title: - Artist: - Classical - Composer: - - - -{code} - -h4. Java annotated data dependency tracking -* Controllable load on save -* @NotifyChange to notify property changes -* @DependsOn to specify property change dependency -{code} -@NotifyChange //notify firstname change -public void setFirstname(String fn) { - firstname = fn; -} - -@NotifyChange //notify lastname change -public void setLastname(String ln) { - lastname = ln; -} - -@DependsOn({"firstname","lastname"}) //full name will change if either firstname and/or lastname change -public String getFullname() { - return firstname + " " + lastname; -} -{code} - -h4. Embedded validation cycle -* Bind validator by name or by EL expression -* Embedded system Validator: provide commonly used validators that user can use directly by specifying only the name -* Validate a single property or a form -* Validate on a command -{code:xml} - - - username - password - retype password - -{code} - -h4. Enhanced converter mechanism -* Bind converter by name or by EL expression -* Embedded system Converters: provide commonly used converters that user can use directly by specifying only the name -{code:xml} - -{code} - -h4. org.zkoss.bind.BindComposer -* Ease UI components, binder, and ViewModel association -* Each binder covers only the applied component tree -* Inter-binder communications -{code:xml} - - ... - -{code} - -h4. Bind on demand -* Support dynamically add/remove bindings by API. -* Attached components with binding annotations are automatically managed by the existing binder if covered. -* Detached components that were managed by a binder are automatically removed from the binding management. - -h4. Support seamless MVVM design pattern -* Utilize MVVM design pattern to achieve separation of data and logic from presentation easily. -** UI Design and ViewModel can be implemented in parallel independently. -** Can add new Views or change current View easily. -** Different views for different devices with a common ViewModel. -** Allow unit-test ViewModel independently without UI environments. - -h4. For details +For details [Envisage ZK 6: The Next Generation Data Binding System|http://books.zkoss.org/wiki/Small_Talks/2011/October/Envisage_ZK_6:_The_Next_Generation_Data_Binding_System] diff --git a/src/docs/guide/9.1 Hello MVVM.gdoc b/src/docs/guide/9.1 Hello MVVM.gdoc index d72ca7a..0e180d6 100644 --- a/src/docs/guide/9.1 Hello MVVM.gdoc +++ b/src/docs/guide/9.1 Hello MVVM.gdoc @@ -53,34 +53,36 @@ Apparently, UI designer has to tell binder at least the following things: In ZK Bind, ZK Annotation is utilized to do these jobs: @View@: helloMVVM.gsp {code:xml} - - - + + + {code} -@ViewModel@: HelloViewModel.groovy +@ViewModel@: HelloVM.groovy {code} -class HelloViewModel { +class HelloVM { private String message + public String getMessage() { return message } - @NotifyChange("message") + \@Command \@NotifyChange("message") public void showHello() { message = "Hello World!" } + } {code} Per this example, let's run through the program flow: # When end user press the "Show" button, the onClick event is fired to binder. -# The binder finds that the command name in the ViewModel which is "showHello" as specified in the ZK annotation @bind('showHello'). -# The binder calls the showHello() method in the HelloViewModel and changes the message property. Note that the @NotifyChange("message") Java method annotation on showHello() method in HelloViewModel. It tells the binder that the property message in the HelloViewModel will be changed if this command is called. -# The binder then finds that the attribute value of component label is associated with the changed message property of the HelloViewModel(as specified in ZK annotation @bind(vm.message)). So it loads data from the property vm.message and updates the label's value attribute. The new message "Hello World!" is then shown on the screen and provides the visual feedback to the end user. +# The binder finds that the command name in the ViewModel which is "showHello" as specified in the ZK annotation @command('showHello'). +# The binder calls the showHello() method in the HelloVM and changes the message property. Note that the @NotifyChange("message") Java method annotation on showHello() method in HelloVM. It tells the binder that the property message in the HelloVM will be changed if this command is called. +# The binder then finds that the attribute value of component label is associated with the changed message property of the HelloVM(as specified in ZK annotation @bind(vm.message)). So it loads data from the property vm.message and updates the label's value attribute. The new message "Hello World!" is then shown on the screen and provides the visual feedback to the end user. -In this MVVM implementation with data binding system, the HelloViewModel is a simple POJO and refers none of the UI components. It only exposes the message contents via getMessage() and provides the necessary action logic showHello(). It does not know how these information or action logic will be used. It is the UI designer's job to decide which UI components are to be used in which layout as seen in HelloMVVM.gsp. +In this MVVM implementation with data binding system, the HelloVM is a simple POJO and refers none of the UI components. It only exposes the message contents via getMessage() and provides the necessary action logic showHello(). It does not know how these information or action logic will be used. It is the UI designer's job to decide which UI components are to be used in which layout as seen in HelloMVVM.gsp. h3. The example revised -- Pop Up the Message @@ -124,18 +126,19 @@ h3. Revised MVVM Implementation View: helloMVVM2.gsp {code:xml} - - + + + visible="\@bind(not empty vm.message)"> - + {code} -The advantages of using MVVM design pattern comes in when customers changes their requirements on the View. UI Designers can proceed the changes independently and the HelloViewModel java file does not have to be changed because what the customer required was to change the way how the message is presented not the message itself. Notice that here the show/hide of the modal window is controlled whether the message is empty or not(visible="@bind(not empty vm.message)"). +The advantages of using MVVM design pattern comes in when customers changes their requirements on the View. UI Designers can proceed the changes independently and the HelloVM java file does not have to be changed because what the customer required was to change the way how the message is presented not the message itself. Notice that here the show/hide of the modal window is controlled whether the message is empty or not(visible="@bind(not empty vm.message)"). h3. See more diff --git a/src/docs/guide/9.2 Design your first MVVM page.gdoc b/src/docs/guide/9.2 Design your first MVVM page.gdoc index 2218f6a..ff514a3 100644 --- a/src/docs/guide/9.2 Design your first MVVM page.gdoc +++ b/src/docs/guide/9.2 Design your first MVVM page.gdoc @@ -10,6 +10,7 @@ grails create-domain-class item Modify the @Item@ Domain class: {code} class Item { + static transients = ['totalPrice'] String name String description double price @@ -64,67 +65,72 @@ Modify the ViewModel : {code} class SearchVM { //the search condition - String filter = "*" + String filter = "*"; + //the search result + ListModelList items; + + //the selected item + Item selected; + + def searchService + + + public String getFilter() { + return filter; + } + + \@NotifyChange + public void setFilter(String filter) { + this.filter = filter; + } + + public ListModel getItems() { + if(items==null){ + doSearch(); + } + return items; + } + + \@NotifyChange(["items", "selected"]) + \@Command + public void doSearch(){ + items = new ListModelList(); + items.addAll(searchService.search(filter)); + selected = null; + } + + public Item getSelected() { + return selected; + } + + \@NotifyChange + public void setSelected(Item selected) { + this.selected = selected; + } + + + Converter totalPriceConverter = null; + + public Converter getTotalPriceConverter(){ + if(totalPriceConverter!=null){ + return totalPriceConverter; + } + return totalPriceConverter = new Converter(){ + public Object coerceToBean(Object val, Component component, + BindContext ctx) { + return null;//never called in this example + } + + public Object coerceToUi(Object val, Component component, + BindContext ctx) { + if(val==null) return null; + String str = new DecimalFormat('$ ###,###,###,##0.00').format((Double)val); + return str; + } + + }; + } - //the search result - ListModelList items - - //the selected item - Item selected - - //inject the SearchService - def searchService - - public String getFilter() { - return filter - } - - @NotifyChange - public void setFilter(String filter) { - this.filter = filter - } - - public ListModel getItems() { - if (items == null) { - doSearch() - } - return items - } - - @NotifyChange(["items", "selected"]) - public void doSearch() { - items = new ListModelList() - items.addAll(searchService.search(filter)) - selected = null - } - - public Item getSelected() { - return selected - } - - @NotifyChange - public void setSelected(Item selected) { - this.selected = selected - } - - Converter totalPriceConverter = null - - public Converter getTotalPriceConverter() { - if (totalPriceConverter != null) { - return totalPriceConverter - } - return totalPriceConverter = new Converter() { - public Object coerceToBean(Object val, Component component, BindContext ctx) { - return null//never called in this example - } - - public Object coerceToUi(Object val, Component component, BindContext ctx) { - if (val == null) return null - String str = new DecimalFormat('$ ###,###,###,##0.00').format((Double) val) - return str - } - } - } } {code} @@ -143,7 +149,7 @@ Now, we have to create a ‘search.gsp’ and bind it to the View Model. To do t {code} + apply="org.zkoss.bind.BindComposer" viewModel="\@id('vm')\@init('zkui.mvvm.SearchVM')" > ... @@ -178,24 +184,25 @@ In ZK 6, we introduce a new feature called ‘template’, it is a perfect match - - - + + + {code} -The \@bind(expression) can also apply to ‘model’ attribute of a component. When binding to a model, we have to provide a template, which name is ‘model’, to render each item in the model. In the template, we have to set the name of ‘var’, so we could use it as an item in the template. We also introduce the \@converter(expression) syntax, so you can write a ‘Converter’ to convert data to attribute when loading to component, and convert back when saving to bean. -In the above example, the ‘model’ of listbox binds to ‘vm.items’, and the ‘selectedItem’ binds to ‘vm.selected’ with a template that has responsibility to render each item of the ‘vm.items’. Of course, we could also use \@bind in the template, even bind to a ‘sclass’ attribute of a listcell with a flexible expression ‘item.quantity lt 3?’red’:’’’. Look into ‘item.price’, it is a double, I want it to be shown with an expected format, therefore a built-in converter ‘formatedNumber’ and a format argument ‘###,##0.00’ are used. +The \@load(expression) can also be applied to the model attribute of the listbox component. When binding to a model, we have to provide a template, which is named "model", to render each item in the model. In the template, we have to set the name "var", so we could use it as an item in the template. We also introduce the \@converter(expression) syntax, so you can write a "Converter" to convert data to attributes when loading to components, and convert back when saving to beans. +In the above example, the model of the listbox binds to vm.items while the selectedItem binds to vm.selected with a template that has responsibility to render each entry of the items of vm.items. Of course, we can also use \@load() in the template, or even bind it to the sclass attribute of a listcell with a flexible expression item.quantity lt 3 ? 'red' : ''. Look into item.price. It is a double number. I want it to be shown with an expected format; therefore a built-in converter formatedNumber and a format argument of '###,##0.00' are used. + h3. Binding the selected item -When binding the listbox, we also bind the ‘selectedItem’ of listbox to ‘selected’ property of View Model. You do not need to worry about selectedItem(in which its value type is a Listitem) of listbox being the incorrect data type of the model because binder will convert it to item automatically. By the binding of ‘selectedItem’, when selecting an item in the listbox, the ‘selected’ property will be updated to the selected item and displayed in detail. +When binding the listbox, we also bind the selectedItem of the listbox to selected property of the ViewModel. You do not need to worry about selectedItem (in which its value type is a Listitem) of listbox being the incorrect data type for the model because binder will convert it to item automatically. By the binding of selectedItem, when selecting an item in the listbox, the selected property will be updated to the selected item and displayed in detail. *View : search.gsp* {code} - - + + @@ -203,22 +210,22 @@ When binding the listbox, we also bind the ‘selectedItem’ of listbox to ‘s - Description + Description - Price + Price - Quantity + Quantity - Total Price + Total Price {code} -In the example above, we bind ‘visible’ attribute of groupbox with the expression ‘not empty vm.selected’, so that the groupbox will only appear visible when user selects an item. Oppositely, if no item is selected, this groupbox will not show up. The \@converter() is used again here but with some differences. The converter now comes from the View Model . Of course, a ‘getTotalPriceConverter’ method needs to be prepared in View Model and return a Converter. +In the example above, we bind visible attribute of groupbox with the expression not empty vm.selected, so that the groupbox will only be visible when user selects an item. Oppositely, if no item is selected, this groupbox will not show up. The \@converter() is used again here but with some differences. The converter now comes from the ViewModel . Of course, a getTotalPriceConverter method needs to be prepared in ViewModel and return a Converter. *ViewModel : SearchVM.groovy* {code} @@ -243,18 +250,19 @@ In the example above, we bind ‘visible’ attribute of groupbox with the expre {code} h3. Binding the button action to a command -Now, we have to perform a command of the View Model when clicking on the search button. To do so, add a \@bind in the onClick event of the button. +Now, we have to perform a command of the View Model when clicking on the search button. To do so, add a \@command in the onClick event of the button. {code} - + {code} -The @bind(expression) syntax in the event of a component represents the binding of an event to a command of the View Model. It has some rules. -# The evaluation result of the expression result has to be a ‘String’, +The @command(expression) syntax in the event of a component represents the binding of an event to a command of the ViewModel. It has some rules. + +# The evaluation result of the expression result has to be a 'String', # The string must also be the name of the command -# View model must have an executable method that has the name of the command. +# View model must have an executable method that has the annotation \@Command('commandName'), if value of @Command is empty, binder use the method name as command name by default. -In the case above, onClick is binded with a ‘doSearch’ command. When clicking on the button, binder will go through the command lifecycle (we will talk about this in another smalltalk ,[link when ready]) and execute the doSearch method of view model. +In the case above, onClick is binded with a doSearch command. When clicking on the button, binder will go through the command lifecycle and then execute the doSearch method of the ViewModel. h3. The all:*search.gsp* @@ -270,17 +278,16 @@ h3. The all:*search.gsp* } - + apply="org.zkoss.bind.BindComposer" viewModel="\@id('vm')\@init('zkui6demo.SearchVM')"> Filter : - + - + @@ -288,32 +295,27 @@ h3. The all:*search.gsp* - - - + + + - - + + - - Description - - - Price - - - Quantity - - - Total Price - + Description + Price + Quantity + Total Price @@ -377,7 +379,7 @@ class BootStrap { h3. Downloads ZK UI MVVM Demo -https://github.com/downloads/xiaochong/zkui/zkui-mvvm-demo.zip +[https://github.com/xiaochong/zkui/downloads|https://github.com/xiaochong/zkui/downloads] h3. More...