The Name Of My Page"

From Documentation
m
m
Line 1: Line 1:
{{Template:Smalltalk_Author
+
{{Template:Smalltalk_Author|
|author=Olivier Driesbach, Engineer, Cross
+
|author=Robert Wenzel, Engineer, Potix Corporation
|date=January 08, 2016
+
|date=April 2016
|version=ZK Studio 2.0.1
+
|version=ZK 8.0
 
}}
 
}}
 
__TOC__
 
  
 
= Introduction =
 
= Introduction =
  
Since version 6 of ZK, developers can choose to create web applications using the MVVM design pattern as an alternative of the traditional MVC one. If you're not familiar with MVVM, I recommend reading through the following articles as a good starting point:
+
*[[Small_Talks/2015/September/ZK8_Wizard_Example_-_Part_1|Part 1 - Defining the Wizard]]
 +
*[[Small_Talks/2015/September/ZK8_Wizard_Example_-_Part_2|Part 2 - Order Wizard (a more complex example)]]
 +
*[[Small_Talks/2016/February/ZK8_Wizard_Example_-_Part_3|Part 3 - Form Handling and Input Validation]]
 +
*[[Small_Talks/2016/March/ZK8_Wizard_Example_-_Part_4|Part 4 - Styling the Wizard (with Bootstrap)]] (You are here)
  
<ol>
+
To wrap up the Wizard Example Series, here is the final Part showing how things finally fit together when applying a 3rd party CSS framework (Bootstrap) to the wizard.
<li>[http://books.zkoss.org/wiki/Small_Talks/2011/December/MVVM_in_ZK6:in_Contrast_to_MVC MVVM in contrast to MVC]</li>
 
<li>[http://books.zkoss.org/wiki/Small_Talks/2011/November/MVVM_in_ZK_6_-_Design_your_first_MVVM_page Design your first MVVM page]</li>
 
<li>[http://books.zkoss.org/wiki/Small_Talks/2011/November/MVVM_in_ZK_6_-_Design_CRUD_page_by_MVVM_pattern Design  a CRUD page with MVVM]</li>
 
</ol>
 
  
= Purpose of this article =
+
The implementation decisions/techniques used in the previous chapters now become handy. The strict separation between view and viewModel code enables the style overhaul [https://github.com/zkoss-demo/zk-wizard-example/commit/ba276f513ce7ee6c7f5428068deaa4a6ac6e38de without touching any Java code].
 +
Restyling an application still requires a significant amount of work, in which the major task is to adjust the various zul pages and templates to render the markup required by Bootstrap. However, compared to changing both zul and java code, the possibility of introducing errors is much smaller (and even if new problems arise, they are obviously somewhere in the view).
  
As explained in the ZK Advanced section ([http://books.zkoss.org/zk-mvvm-book/8.0/advanced/wire_components.html Chapter 4.4: Wire Components]) of the online documentation, it is not suggested to wire UI components into your ViewModels because you will loose the main advantage of the using the MVVM design pattern: Separate view's concerns from ViewModel operations.
+
Here is a recording of the results (descriptions of the most significant changes will follow below):
  
Unfortunately, if you've experienced the development of a real world ZK application, you've probably found how it is that difficult to fully respect this condition. Especially when you need to open/close a new window at the end of an action.
+
<gflash width="910" height="657">Order-wizard_with_style.swf</gflash>
 +
[[File:Order-wizard_with_style.swf]]
  
This article will show you how it is possible with some java templates using ZK MVVM basic concepts.
+
== Adding the Bootstrap Resources ==
  
= Context =
+
It's straight forward: just add the required css/js resources. ZK offers several ways to do so:
 +
* [http://books.zkoss.org/wiki/ZUML_Reference/ZUML/Processing_Instructions/script script] / [http://books.zkoss.org/wiki/ZUML_Reference/ZUML/Processing_Instructions/link link] directives
 +
* [http://books.zkoss.org/wiki/ZK_Component_Reference/Essential_Components/Script script] / [http://books.zkoss.org/wiki/ZK_Component_Reference/Essential_Components/Style style] components
 +
* [http://books.zkoss.org/wiki/ZK_Client-side_Reference/Language_Definition/javascript javascript] / [http://books.zkoss.org/wiki/ZK_Client-side_Reference/Language_Definition/stylesheet stylesheet] elements in a lang-addon.xml
 +
* [http://books.zkoss.org/wiki/ZK_Configuration_Reference/zk.xml/The_device-config_Element/The_embed_Element embed element in zk.xml]
 +
For simplicity's sake, I just added them to my root page.
  
The full source code of this article is based on a real world application use case. It has been adapted to its minimalistic version in order to be reusable like a conception pattern. It is available as a download at the end of the article.
+
;/wizardexample/src/main/webapp/order.zul [https://github.com/zkoss-demo/zk-wizard-example/blob/part-4/src/main/webapp/order.zul]
  
<ol>
+
<source lang="xml" high="2,4">
=== <li><b>Use case</b></li> ===
+
  <!-- Latest compiled and minified CSS -->
 +
  <?link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" ?>
 +
  <!-- Latest compiled and minified JavaScript -->
 +
  <?script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js" ?>
 +
</source>
  
<ol>
+
Of course, in a real application, you'll likely choose to include the css/js files in your web application (not the topic here).
<li>The user goes to the list's page of persons.</li>
 
<li>The user selects one person.</li>
 
<li>The user clicks on the "Address" button to open a new window.</li>
 
<li>The new window displays the list of adresses available in the system.</li>
 
<li>The user selects one address in the list and click on the "Select" button.</li>
 
<li>The selected adress is attached to the selected person of the first window and the address window is closed automatically.</li>
 
</ol>
 
  
<br/>
+
== Render the UI in Bootstrap Style ==
===<li><b>Known difficulties</b></li> ===
 
  
<ol>
+
Once the layout has been decided (in the ideal case a web designer decided for you and provided static html mockups), you can change your zul files accordingly.
<li>Create the address button on the person's page without coding the URI of the addresses' page into the button's action of the ViewModel.</li>
 
<li>Send the selected address (POJO or identifier) from the address ViewModel to the person ViewModel without having references between each other (separation of concerns).</li>
 
<li>Close the addresses' window without coding the UI stuff in the ViewModel.</li>
 
</ol>
 
  
<br/>
+
Here I'll '''not''' try to make all ZK components look "like" Bootstrap, instead I'll use [[ZK_Component_Reference/XHTML_Components | XHTML components]] and [[ZUML_Reference/ZUML/Namespaces/Native | native elements]] to produce the HTML necessary for Bootstrap. This allows you to use and update the 3rd-party css styles directly whenever needed (or even apply different bootstrap themes later).
=== <li><b>Person and Address (Model, View and ViewModel)</b></li> ===
 
  
'''The Person.java (Model)'''
+
ZK8's reusable templates and shadow elements help again to avoid repeating the more verbose HTML markup.
 +
If necessary the markup structure of an existing ZK component can be adjusted using a [[ZK_Component_Development_Essentials/Creating_a_simple_ZK_Component/Rendering_Widgets/Implementing_Molds | custom mold]] to fit into the desired layout.
  
<source lang="java">
+
=== Page/Wizard Layout ===
package demo.mvvm.person;
 
  
import demo.mvvm.address.Address;
+
;/wizardexample/src/main/webapp/order.zul [https://github.com/zkoss-demo/zk-wizard-example/blob/part-4/src/main/webapp/order.zul#L9]
  
public class Person {
+
Here, the [https://getbootstrap.com/css/#overview-container bootstrap css class (container)] - was added to provide a responsive fixed width layout - nothing else worth mentioning here.
 
private String firstName;
 
private String lastName;
 
private String email;
 
private Address address;
 
 
public Person() {
 
}
 
 
public Person(String firstName, String lastName, String email) {
 
this.firstName = firstName;
 
this.lastName = lastName;
 
this.email = email;
 
}
 
  
// Getters and setters ...
+
<source lang="xml" high="1">
}
+
<x:div class="container"
 +
viewModel="@id('vm') @init('zk.example.order.OrderViewModel')"
 +
validationMessages="@id('vmsgs')"
 +
onBookmarkChange="@command('gotoStep', stepId=event.bookmark)">
 +
...
 
</source>
 
</source>
  
'''The Address.java (Model)'''
+
More interesting are the changes in the wizard template defining the overall look and feel:
  
<source lang="java">
+
;/wizardexample/src/main/webapp/WEB-INF/zul/template/wizard/wizard.zul [https://github.com/zkoss-demo/zk-wizard-example/blob/part-4/src/main/webapp/WEB-INF/zul/template/wizard/wizard.zul]
package demo.mvvm.address;
 
  
public class Address {
+
One of the straightforward changes is applying the [https://getbootstrap.com/css/#buttons-options bootstrap button styles]:
 
private String number;
 
private String line1;
 
private String line2;
 
private String zipCode;
 
private String city;
 
 
public Address() {
 
}
 
 
public Address(String number, String line1, String line2, String zipCode, String city) {
 
this.number = number;
 
this.line1 = line1;
 
this.line2 = line2;
 
this.zipCode = zipCode;
 
this.city = city;
 
}
 
  
// Getters and setters ...
+
<source lang="xml" >
+
    <button zclass="btn btn-default" label="@load(wizardVM.backLabel)" onClick="@command(wizardVM.backCommand)" />
public String getCompleteAddress() {
+
    ...
return getNumber() +" "+ getLine1() +" "+ getZipCode() +" "+ getCity();
+
    <button zclass="btn btn-success pull-right" label="@load(wizardVM.nextLabel)" onClick="@command(wizardVM.nextCommand)" />
}
 
}
 
 
</source>
 
</source>
  
'''The personList.zul (View)'''
+
[[Image:wizard-buttons-before-after.png|frame|original style (left) <-> after bootstrap makeover (right)]]
  
<source lang="xml">
+
'''NOTE''': I use [http://books.zkoss.org/wiki/ZK_Style_Customization_Guide/Look_and_Feel_Customization/Partial_customize_with_Sclass_and_Zclass zclass instead of sclass] to replace ZK's own button class ('''z-button''') using bootstrap's css-classes ('''btn btn-default ...''').
<window id="mainPanel">
 
  
<vlayout viewModel="@id('personVM') @init('demo.mvvm.person.PersonViewModel')">
+
For the progress bar, it was also simple to adapt the [https://getbootstrap.com/components/#progress-label bootstrap progressbar example markup].
+
The outer <n:div> is rendered using the native namespace, since it does not require dynamic updates, while the inner <x:div> uses an xhtml component to enable data binding on the dynamic style and textContent properties. A notifyChange on the '''progress'''-property in our wizardVM object will automatically adjust the label and progressbar width (triggering the css animation provided by bootstrap).
<listbox model="@load(personVM.persons)" selectedItem="@bind(personVM.selectedPerson)">
 
<listhead>
 
    <listheader label="Last name" width="100px" />
 
    <listheader label="First name" width="100px" />
 
    <listheader label="Email" />
 
    <listheader label="Address" />
 
</listhead>
 
<template name="model">
 
    <listitem>
 
        <listcell label="@load(each.lastName)" />
 
        <listcell label="@load(each.firstName)" />
 
        <listcell label="@load(each.email)" />
 
        <listcell label="@load(each.address.completeAddress)" />
 
    </listitem>
 
</template>
 
</listbox>
 
 
<button label="Address" disabled="@load(empty personVM.selectedPerson)" />
 
 
</vlayout>
 
  
</window>
+
<source lang="xml" high="5">
 +
<template name="wizardProgress">
 +
<n:div class="progress">
 +
<x:div class="progress-bar progress-bar-success progress-bar-striped"
 +
style="@load(('width: ' += wizardVM.progress += '%; min-width: 2em;'))"
 +
textContent="@load((wizardVM.progress += '%'))"/>
 +
</n:div>
 +
</template>
 
</source>
 
</source>
 +
* '''Line 5:''' the '''textContent'''-attribute on XHTML components is a new ZK8 feature to allow dynamic text content directly inside any html tag
  
'''The addressList.zul (View)'''
+
In a similar fashion a [https://getbootstrap.com/components/#panels bootstrap panel] can be composed (I assume you have noticed the pattern):
  
 
<source lang="xml">
 
<source lang="xml">
<window title="Addresses" mode="modal" width="800px" closable="true">
+
<div zclass="panel panel-primary"
 
+
viewModel="@id('wizardVM') @init(wizardModel)"
<vlayout viewModel="@id('addressVM') @init('demo.mvvm.address.AddressViewModel')">
+
validationMessages="@id('vmsgs')"
+
onOK="@command(wizardVM.nextCommand)">
<listbox model="@load(addressVM.addresses)" selectedItem="@bind(addressVM.selectedAddress)">
+
<n:div class="panel-heading">
<listhead>
+
<x:h3 class="panel-title" textContent="@load(wizardVM.currentStep.title)"/>
    <listheader label="Number" width="100px" />
+
</n:div>
    <listheader label="Line 1" />
+
<n:div class="panel-body">
    <listheader label="Line 2" />
+
<sh:apply template="@init(empty wrapperTemplate ? 'defaultWizardContentWrapper' : wrapperTemplate)"/>
    <listheader label="Zip Code" width="100px" />
+
</n:div>
<listheader label="City" width="100px" />
+
</div>
</listhead>
 
    <template name="model">
 
        <listitem>
 
            <listcell label="@load(each.number)" />
 
            <listcell label="@load(each.line1)" />
 
            <listcell label="@load(each.line2)" />
 
            <listcell label="@load(each.zipCode)" />
 
            <listcell label="@load(each.city)" />
 
        </listitem>
 
    </template>
 
</listbox>
 
 
<button label="Validate" disabled="@load(empty addressVM.selectedAddress)" />
 
 
</vlayout>
 
 
</window>
 
 
</source>
 
</source>
  
'''The PersonViewModel.java'''
+
=== Basket Layout ===
  
<source lang="java">
+
On the basket page the <grid> was replaced by a [https://getbootstrap.com/css/#tables-striped striped bootstrap table].
package demo.mvvm.person;
+
The shadow element <sh:forEach>  allows you to reuse the same ListModel of BasketItems from the BasketViewModel while only changing the layout and bind expressions. As described above, xhtml and native elements are used to render the markup required by bootstrap.
  
import java.util.List;
+
The [[#tooltip | Tooltips]] are enabled using the Bootstrap JS API. More on that below.
  
import org.zkoss.bind.annotation.Init;
+
In order to make the page a little more responsive to browser width changes, the Basket recommendations on the bottom use the [https://getbootstrap.com/css/#grid Bootstrap grid system] to vary the number of recommendations per row at different display sizes:
import org.zkoss.bind.annotation.NotifyChange;
 
import org.zkoss.zk.ui.event.Event;
 
import org.zkoss.zk.ui.event.EventListener;
 
  
import demo.mvvm.address.Address;
+
;/wizardexample/src/main/webapp/WEB-INF/zul/order/steps/basket.zul [https://github.com/zkoss-demo/zk-wizard-example/blob/part-4/src/main/webapp/WEB-INF/zul/order/steps/basket.zul#L58-L68]
  
public class PersonViewModel {
+
<source lang="xml" high="1,3">
+
<x:div class="row">
protected List<Person> persons;
+
<sh:forEach items="@init(basketVM.recommendedItemsModel)">
private Person selectedPerson;
+
<x:div class="col-lg-4 col-sm-6">
+
<x:div class="well well-sm">
@Init(superclass=true)
+
<sh:apply template="basketItemLabel" item="@init(each)"/>
public void initPersonViewModel() {
+
<button zclass="btn btn-info btn-xs pull-right" iconSclass="z-icon-shopping-cart"
initPersons();
+
onClick="@command('addRecommendedItem', item=each)"
}
+
ca:data-toggle="tooltip" ca:data-placement="right"
+
ca:data-title="${i18n:nls('order.basket.add')}"/>
public List<Person> getPersons() {
+
</x:div>
return persons;
+
</x:div>
}
+
</sh:forEach>
+
</x:div>
public Person getSelectedPerson() {
 
return selectedPerson;
 
}
 
 
public void setSelectedPerson(Person selectedPerson) {
 
this.selectedPerson = selectedPerson;
 
}
 
 
protected void initPersons() {
 
persons = new PersonService().findAll();
 
}
 
}
 
 
</source>
 
</source>
  
'''The AddressViewModel.java'''
+
Here are the results:
 +
[[Image:wizard-basket-before-after.png|center|700px|original style (left) <-> after bootstrap makeover (right)]]
 +
 
 +
=== Form Layout ===
 +
 
 +
The remaining pages previously rendered using a <grid> layout are now implemented using [https://getbootstrap.com/css/#forms-horizontal Bootstrap's horizontal form] layout. This achieves the popular look and responsive sizing/positioning of input elements and their labels. The parameters to the <formRow> template remain identical to the previous version, since the model data hasn't changed.
  
<source lang="java">
+
<source language="xml" high="1, 3">
package demo.mvvm.address;
+
<x:div class="form-horizontal">
 +
<formRow type="static" label="@init(i18n:nls('order.basket'))" value="@init(savedOrder.basket)"/>
 +
<x:div class="alert alert-info" role="alert" textContent="${i18n:nls('order.shippingAddress.hint')}"/>
 +
<formRow type="textbox" label="@init(i18n:nls('order.shippingAddress.street'))"
 +
value="@ref(order.shippingAddress.street)"
 +
error="@ref(vmsgs['p_shippingAddress.street'])"/>  
 +
<formRow type="textbox" label="@init(i18n:nls('order.shippingAddress.city'))"
 +
value="@ref(order.shippingAddress.city)"
 +
error="@ref(vmsgs['p_shippingAddress.city'])"/>
 +
<formRow type="textbox" label="@init(i18n:nls('order.shippingAddress.zipCode'))"
 +
value="@ref(order.shippingAddress.zipCode)"
 +
error="@ref(vmsgs['p_shippingAddress.zipCode'])"/>
 +
</x:div>
 +
</source>
  
import java.util.List;
+
*'''Lines 1,3:''' using bootstrap specifc css classes (form-horizontal, alert, alert-info) on xhtml components
  
import org.zkoss.bind.annotation.Command;
 
import org.zkoss.bind.annotation.Init;
 
  
public class AddressViewModel {
+
Inside the formRow template the restyling continues. I also chose to display validation errors in using a bootstrap css classes.
  
    private List<Address> addresses;
+
<source language="xml" high="1, 17, 23">
    private Address selectedAddress;
+
<x:div sclass="@load(empty error ? 'form-group' : 'form-group has-error')">
 +
<sh:choose>
 +
<sh:when test="@load(type eq 'checkbox')">
 +
<n:div class="col-sm-offset-3 col-sm-9">
 +
<sh:apply template="checkbox"/>
 +
</n:div>
 +
</sh:when>
 +
<sh:otherwise>
 +
<x:label class="col-sm-3 control-label" textContent="@init(label)"/>
 +
<n:div class="col-sm-9">
 +
<sh:apply template="@load(type)"/>
 +
</n:div>
 +
</sh:otherwise>
 +
</sh:choose>
 +
</x:div>
 +
<sh:if test="@load(!empty error)">
 +
<x:div sclass="alert alert-danger well-sm" textContent="@load(error)"/>
 +
</sh:if>
  
    @Init(superclass=true)
+
<template name="checkbox">
    public void initAddressViewModel() {
+
<n:div class="checkbox">
    initAddresses();
+
<checkbox zclass=" " checked="@bind(value)" onCheck="@command(updateCommand)" label="@load(label)"
    }
+
mold="formgroup"/>
   
+
</n:div>
    public List<Address> getAddresses() {
+
</template>
    return addresses;
 
    }
 
 
    public Address getSelectedAddress() {
 
        return selectedAddress;
 
    }
 
 
 
    public void setSelectedAddress(Address selectedAddress) {
+
<template name="textbox">
        this.selectedAddress = selectedAddress;
+
<textbox zclass="form-control" value="@bind(value)" onChange="@command(changeCommand)"/>
    }
+
</template>
   
+
...
    private void initAddresses() {
 
        addresses = new AddressService().findAll();
 
    }
 
}
 
 
</source>
 
</source>
 +
*'''Line 1:''' <row> is replaced by a <x:div> with dynamic styling based depending on the presence of an error message
 +
*'''Line 17:''' conditionally output an error using a [https://getbootstrap.com/components/#alerts Bootstrap alert style]
 +
*'''Line 23:''' apply a custom mold to render the checkbox and its label in the markup required by bootstrap
  
'''The PersonService.java'''
+
[[Image:wizard-form-before-after.png|center|700px|original style (left) <-> after bootstrap makeover (right)]]
  
<source lang="java">
+
==== Custom Checkbox Mold ====
package demo.mvvm.person;
 
  
import java.util.ArrayList;
+
Since bootstrap requires different html markup to render a checkbox inside a horizontal form, I decided to define a custom '''mold''' for this scenario.
import java.util.List;
 
  
import demo.mvvm.Person;
+
The markup requried by Bootstrap.
 
+
<source language="xml" high="1, 17, 23">
public class PersonService {
+
<label>
 
+
<input type="checkbox"> Remember me
public PersonService() {}
+
</label>
 +
</source>
  
public List<Person> findAll() {
+
The corresponding custom mold implementation in
+
;/wizardexample/src/main/resources/web/js/zul/wgt/mold/checkbox-formgroup.js [https://github.com/zkoss-demo/zk-wizard-example/blob/part-4/src/main/resources/web/js/zul/wgt/mold/checkbox-formgroup.js]:
ArrayList<Person> result = new ArrayList<Person>();
+
<source language="javascript">
result.add(new Person("John", "Doe", "john.doe@unknown.com"));
+
function (out) {
result.add(new Person("Oswald", "Cobblepot", "pinguin@linux.com"));
+
var uuid = this.uuid, content = this.domContent_();
result.add(new Person("Marcus", "Brody", "mbrody@indiana.com"));
+
out.push('<label', this.domAttrs_(), ' for="', uuid,'-real">',
result.add(new Person("Thomas", "Anderson", "neo@matrix.com"));
+
'<input type="checkbox" id="', uuid, '-real"', this.contentAttrs_(), '/>',
return result;
+
content,'</label>');
}
 
 
}
 
}
 
</source>
 
</source>
  
'''And the AddressService.java'''
+
As [[ZK_Component_Development_Essentials/Creating_a_simple_ZK_Component/Rendering_Widgets/Implementing_Molds | custom mold]] implementation requires some additional knowledge about the internal structure of a ZK widget, I try to use this approach only if necessary. This is an (often forgotten) available option for any ZK widget in case the rendered markup needs to be customized.
 +
 
 +
The new mold is registered in [https://github.com/zkoss-demo/zk-wizard-example/blob/part-4/src/main/resources/metainfo/zk/lang-addon.xml lang-addon.xml].
  
<source lang="java">
+
When upgrading to a different ZK version later this may need to be adjusted. On the other hand, it does provide the most efficient way to render custom markup, since it only happens on the client side.
package demo.mvvm.address;
 
  
import java.util.ArrayList;
+
== Leverage the Bootstrap JS API ==
import java.util.List;
 
  
import demo.mvvm.Address;
+
=== Bootstrap Tooltips ===
  
public class AddressService {
+
Bootstrap also offers a Javascript-api to enable dynamic effects. On the basket page I use [https://getbootstrap.com/javascript/#tooltips Bootstrap-tooltips] configured with ZK8's data-handler, which can be used throughout the application by simply adding client-side data-attributes.
  
public AddressService() {
+
<source language="xml" high="1,4,8">
}
+
<zk xmlns:sh="shadow" xmlns:x="xhtml" xmlns:n="native" xmlns:ca="client/attribute" xmlns:z="zk">
+
...
public List<Address> findAll() {
+
<button zclass="close" iconSclass="z-icon-times" onClick="@command('removeItem', basketItem=item)"
+
ca:data-toggle="tooltip" ca:data-placement="right" ca:data-title="${i18n:nls('order.basket.remove')}"/>
ArrayList<Address> result = new ArrayList<Address>();
+
...
+
<button zclass="btn btn-info btn-xs pull-right" iconSclass="z-icon-shopping-cart"  
result.add(new Address("21", "Jump Street", "", "10001", "New York City"));
+
onClick="@command('addRecommendedItem', item=each)"  
result.add(new Address("23", "Sec. 1", "Changan E. Rd #7F-2", "10441", "Taipei City"));
+
ca:data-toggle="tooltip" ca:data-placement="right" ca:data-title="${i18n:nls('order.basket.add')}"/>
result.add(new Address("36", "Quai des Orfèvres", "", "75000", "Paris"));
 
result.add(new Address("1600", "Pennsylvania Avenue NW", "", "DC 20500", "Washington"));
 
 
return result;
 
}
 
}
 
 
</source>
 
</source>
  
</ol>
+
An alternative to using "ca:data-title" is to use ZK's tooltiptext property which renders into the title attribute of the resulting DOM element. Since tooltip text is a server side attribute, it also allows the data-binding annotations @init/@load.
  
= Step by step implementation =
+
<source language="xml" high="2">
 +
<span zclass=" " status="@ref(item.status)" sclass="@load(i18n:nlsSub(status, 'style'))"
 +
ca:data-toggle="tooltip" ca:data-placement="top" tooltiptext="@load(i18n:nls(status))">
 +
</source>
  
<ol>
+
=== Implementing the Data-Handler ===
=== <li>Create dynamic interactions with the right conditions</li> ===
 
  
When you design the ZUML structure of your "single one page" application, you need to take care of the design of your ZUL pages if you don't want to have huge files with many components and ViewModel instances loaded in-memory when the end-user interface is not displaying them.  
+
Following the Bootstrap [https://getbootstrap.com/javascript/#tooltips-usage JS documentation] the JS call below is necessary to initialize a tooltip for one or more specified DOM elements.
  
Ideally, in our case, if the end user doesn't click on the "Address" button, we don't want to have the "addressList.zul" file integrated in the ZUML structure because the ViewModel will be created too.
+
<source  language="javascript">
 
+
$('#example').tooltip(options)
A bad integration would look like
+
</source>
  
<source lang="xml">
+
Since ZK pages are dynamic, the selector needs to be dynamic too - element Ids are generated and on top of that, ZK components may be added at any time into the DOM via AJAX. In such cases, the client-side widget lifecycle event w:onBind is a good hook to initialize JS behavior. Prior to ZK8, this was only possible using a client-side event listener.
<window id="mainPanel">
 
  
<div visible="@load(not empty personVM.selectedPerson)">
+
<source  language="xml">
    <include src="otherPage.zul" />
+
<zk xmlns:w="client">
</div>
+
    <button w:onBind="jq(this.$n()).tooltip({placement: 'top'})" tooltiptext="my tooltip"/>
+
</zk>
</window>
 
 
</source>
 
</source>
  
Depending on the root component of the otherPage.zul (a modal window for example), this code will not work and you would have to put the visible attribute condition on the window element.
+
A simple data-handler configuration could look like this in zk.xml.
  
To prevent creating these kinds of visibility dependencies between components, you need to use the include tag.
+
<source  language="xml">
In the CE and EE version of ZK, you can use the apply alternative which is very similar.
+
<data-handler>
 +
<name>tooltip</name>
 +
<script>
 +
function (wgt, placement) {
 +
jq(wgt.$n()).tooltip({placement: placement});
 +
}
 +
</script>
 +
</data-handler>
 +
</source>
  
<source lang="xml" high="9,11">
+
And the usage in a zul file.
<window id="mainPanel">
+
<source language="xml">
 +
<zk xmlns:w="client/attribute">
 +
    <button ca:data-tooltip="top" tooltiptext="my tooltip"/>
 +
</zk>
 +
</source>
  
<vlayout viewModel="@id('personVM') @init('demo.mvvm.person.PersonViewModel')">
+
While this can be used in any page throughout the application, it is still quite limiting and not taking full advantage of the Bootstrap JS API (e.g. new parameters have to be added manually on by one).
+
Since many Bootstrap and other JS effects are "toggled" in a similar way, we can configure a data-handler that covers those at once.
<listbox model="@load(personVM.persons)" selectedItem="@bind(personVM.selectedPerson)">
 
...
 
</listbox>
 
 
<button label="Address" disabled="@load(empty personVM.selectedPerson)" onClick="@command('selectAddress')" />
 
 
<include src="@load(personVM.selectAddress ? 'addressList.zul' : '')" />
 
 
</vlayout>
 
  
</window>
+
<source language="xml" high="5">
 +
<data-handler>
 +
<name>toggle</name>
 +
<script>
 +
function (wgt, dataValue) {
 +
jq(wgt.$n())[dataValue]();
 +
}
 +
</script>
 +
</data-handler>
 
</source>
 
</source>
 +
*'''Line 5:''' calls a JQuery function by its name (dataValue contains the function name, in this case 'tooltip')
  
Now, the "selectAddress" command, will enable/disable a boolean property which is binded in the src attribute of the include tag. The tip is using the empty value to attach/detach the page to include only when necessary. Therefore, the included ViewModel will be instanciated dynamically.
+
This enables the following usage and enables other effects to be enabled with the same data-handler (e.g. modal dialogs see next chapter).
 +
<source  language="xml">
 +
<zk xmlns:w="client/attribute">
 +
    <button ca:data-toggle="tooltip" ca:data-placement="top" ca:data-title="my tooltip"/>
 +
</zk>
 +
</source>
  
<source lang="java" high="17,35,36,37,38,39">
+
This way any "data-*" attributes supported by the tooltip API can be used.
package demo.mvvm.person;
 
  
import java.util.List;
+
=== Bootstrap Modal ===
 +
Using the same generic data-handler from above, the modal dialog integration is already showing the modal dialog.
  
import org.zkoss.bind.annotation.BindingParam;
+
The custom [[http://books.zkoss.org/wiki/ZK_Developer's_Reference/UI_Patterns/Error_Handling#Error_Handling_When_Serving_AU_Requests error page is configured in zk.xml]], different errors can load different dialogs, here I handle the OrderExceptions (thrown when a payment method is not available)
import org.zkoss.bind.annotation.Command;
 
import org.zkoss.bind.annotation.GlobalCommand;
 
import org.zkoss.bind.annotation.Init;
 
import org.zkoss.bind.annotation.NotifyChange;
 
  
import demo.mvvm.address.Address;
+
<source lang="xml" high="2">
 +
<error-page>
 +
<exception-type>zk.example.order.api.OrderException</exception-type>
 +
<location>/WEB-INF/zul/order/error/orderError.zul</location>
 +
</error-page>
 +
</source>
  
public class PersonViewModel {
+
;/wizardexample/src/main/webapp/WEB-INF/zul/order/error/orderError.zul [https://github.com/zkoss-demo/zk-wizard-example/blob/part-4/src/main/webapp/WEB-INF/zul/order/error/orderError.zul#L2]
 
protected List<Person> persons;
 
private Person selectedPerson;
 
private boolean selectAddress;
 
 
@Init(superclass=true)
 
public void initPersonViewModel() {
 
initPersons();
 
selectAddress = false;
 
}
 
 
// Getters and setters ...
 
 
public boolean isSelectAddress() {
 
return selectAddress;
 
}
 
  
public void setSelectAddress(boolean selectAddress) {
+
<source lang="xml" high="2">
this.selectAddress = selectAddress;
+
<zk xmlns:ca="client/attribute" xmlns:z="zul" xmlns:x="xhtml">
}
+
<div sclass="modal fade" ca:data-toggle="modal" ca:data-close-event="hidden.bs.modal" onModalClose="self.detach()" >
+
<div xmlns="native" class="modal-dialog">
@Command
+
...
@NotifyChange("selectAddress")
+
</div><!-- /.modal-dialog -->
public void selectAddress() {
+
</div><!-- /.modal -->
selectAddress = true; // Activate the address selection mode
+
</zk>
}
+
</source>
  
protected void initPersons() {
+
[[Image:wizard-modal-before-after.png|center|700px|original style (left) <-> after bootstrap makeover (right)]]
persons = new PersonService().findAll();
 
}
 
}
 
</source>
 
  
=== <li>Communication between ViewModels</li> ===
+
In order to close the modal dialog I registered another data-handler, which will listen to the "hidden.bs.modal" event (triggered when the modal dialog has been closed and the fade out animation has finished). It then fire the onModalClose to the server side to detach the dialog.
  
The recommended way to establish communication between ViewModels is to use the @GlobalCommand mecanism. Regarding our use case, we need to implement the closing of the address window when the user click on the validate button and the default close button of the window component.
+
<source lang="xml" high="2">
 +
<data-handler>
 +
<name>close-event</name>
 +
<script>
 +
function (wgt, dataValue) {
 +
jq(wgt.$n()).on(dataValue, function() {
 +
wgt.fire("onModalClose", null, {toServer: true});
 +
});
 +
}
 +
</script>
 +
</data-handler>
 +
</source>
  
'''Update the Address button'''
+
I know using an inline event listener is a cheap shortcut here, but performance was not a concern in this example, especially when handling exceptions (as the name says this should be an '''exceptional''' and not the regular case).
 +
Alternatives are to apply a Composer and listen to the event inside a java class and destroy the error dialog. If further details are required, please let me know in the comments section below.
  
<source lang="xml" high="1,10">
+
= Summary =
<window title="Addresses" mode="modal" width="800px" closable="true" onClose="@global-command('cancelAddress')">
 
  
<vlayout viewModel="@id('addressVM') @init('demo.mvvm.address.AddressViewModel')">
+
That's it for now, any ideas what to show next? Waiting for your comments.
 
<listbox model="@load(addressVM.addresses)" selectedItem="@bind(addressVM.selectedAddress)">
 
...
 
</listbox>
 
 
<button label="Validate" disabled="@load(empty addressVM.selectedAddress)"
 
onClick="@global-command('updateAddress', address=addressVM.selectedAddress)" />
 
 
</vlayout>
 
</window>
 
</source>
 
  
'''Below the PersonViewModel updated accordingly'''
+
== Download ==
 +
* The source code for this article can be found in [https://github.com/zkoss-demo/zk-wizard-example/tree/part-4 github (branch: part-4)].
  
<source lang="java" high="33,34,35,36,37,38,40,41,42,43,44">
+
== Running the Example ==
package demo.mvvm.person;
+
Checkout '''part-4'''
  
import java.util.List;
+
    git checkout part-4
  
import org.zkoss.bind.annotation.BindingParam;
+
The example war file can be built with maven:
import org.zkoss.bind.annotation.Command;
 
import org.zkoss.bind.annotation.GlobalCommand;
 
import org.zkoss.bind.annotation.Init;
 
import org.zkoss.bind.annotation.NotifyChange;
 
  
import demo.mvvm.address.Address;
+
    mvn clean package
  
public class PersonViewModel {
+
Execute using jetty:
 
protected List<Person> persons;
 
private Person selectedPerson;
 
private boolean selectAddress;
 
 
@Init(superclass=true)
 
public void initPersonViewModel() {
 
initPersons();
 
selectAddress = false;
 
}
 
 
// Getters and setters ...
 
 
@Command
 
@NotifyChange("selectAddress")
 
public void selectAddress() {
 
selectAddress = true; // Activate the address selection mode
 
}
 
 
@GlobalCommand
 
@NotifyChange({"selectedPerson", "selectAddress"})
 
public void updateAddress(@BindingParam("address") Address address) {
 
getSelectedPerson().setAddress(address);
 
selectAddress = false; // Disable the address selection mode
 
}
 
 
@GlobalCommand
 
@NotifyChange("selectAddress")
 
public void cancelAddress() {
 
selectAddress = false; // Disable the address selection mode
 
}
 
  
protected void initPersons() {
+
    mvn jetty:run
persons = new PersonService().findAll();
 
}
 
}
 
</source>
 
  
</ol>
+
Then access the overview page http://localhost:8080/wizardexample/order.zul
  
= Source code =
+
{{Template:CommentedSmalltalk_Footer_new|
 +
|name=Potix Corporation
 +
}}
  
Click here for the full [https://github.com/Cyrus964/ZKDemoMVVM/archive/master.zip source code]
+
[[Category:Small Talk]]

Revision as of 02:16, 26 April 2016

The Name Of My Page

Author
Robert Wenzel, Engineer, Potix Corporation
Date
April 2016
Version
ZK 8.0

Introduction

To wrap up the Wizard Example Series, here is the final Part showing how things finally fit together when applying a 3rd party CSS framework (Bootstrap) to the wizard.

The implementation decisions/techniques used in the previous chapters now become handy. The strict separation between view and viewModel code enables the style overhaul without touching any Java code. Restyling an application still requires a significant amount of work, in which the major task is to adjust the various zul pages and templates to render the markup required by Bootstrap. However, compared to changing both zul and java code, the possibility of introducing errors is much smaller (and even if new problems arise, they are obviously somewhere in the view).

Here is a recording of the results (descriptions of the most significant changes will follow below):

Adding the Bootstrap Resources

It's straight forward: just add the required css/js resources. ZK offers several ways to do so:

For simplicity's sake, I just added them to my root page.

/wizardexample/src/main/webapp/order.zul [1]
  <!-- Latest compiled and minified CSS -->
  <?link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" ?>
  <!-- Latest compiled and minified JavaScript -->
  <?script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js" ?>

Of course, in a real application, you'll likely choose to include the css/js files in your web application (not the topic here).

Render the UI in Bootstrap Style

Once the layout has been decided (in the ideal case a web designer decided for you and provided static html mockups), you can change your zul files accordingly.

Here I'll not try to make all ZK components look "like" Bootstrap, instead I'll use XHTML components and native elements to produce the HTML necessary for Bootstrap. This allows you to use and update the 3rd-party css styles directly whenever needed (or even apply different bootstrap themes later).

ZK8's reusable templates and shadow elements help again to avoid repeating the more verbose HTML markup. If necessary the markup structure of an existing ZK component can be adjusted using a custom mold to fit into the desired layout.

Page/Wizard Layout

/wizardexample/src/main/webapp/order.zul [2]

Here, the bootstrap css class (container) - was added to provide a responsive fixed width layout - nothing else worth mentioning here.

<x:div class="container"
	 viewModel="@id('vm') @init('zk.example.order.OrderViewModel')" 
	 validationMessages="@id('vmsgs')"
	 onBookmarkChange="@command('gotoStep', stepId=event.bookmark)">
...

More interesting are the changes in the wizard template defining the overall look and feel:

/wizardexample/src/main/webapp/WEB-INF/zul/template/wizard/wizard.zul [3]

One of the straightforward changes is applying the bootstrap button styles:

    <button zclass="btn btn-default" label="@load(wizardVM.backLabel)" onClick="@command(wizardVM.backCommand)" />
    ...
    <button zclass="btn btn-success pull-right" label="@load(wizardVM.nextLabel)" onClick="@command(wizardVM.nextCommand)" />
original style (left) <-> after bootstrap makeover (right)

NOTE: I use zclass instead of sclass to replace ZK's own button class (z-button) using bootstrap's css-classes (btn btn-default ...).

For the progress bar, it was also simple to adapt the bootstrap progressbar example markup. The outer <n:div> is rendered using the native namespace, since it does not require dynamic updates, while the inner <x:div> uses an xhtml component to enable data binding on the dynamic style and textContent properties. A notifyChange on the progress-property in our wizardVM object will automatically adjust the label and progressbar width (triggering the css animation provided by bootstrap).

	<template name="wizardProgress">
		<n:div class="progress">
			<x:div class="progress-bar progress-bar-success progress-bar-striped" 
				style="@load(('width: ' += wizardVM.progress += '%; min-width: 2em;'))"
				textContent="@load((wizardVM.progress += '%'))"/>
		</n:div>
	</template>
  • Line 5: the textContent-attribute on XHTML components is a new ZK8 feature to allow dynamic text content directly inside any html tag

In a similar fashion a bootstrap panel can be composed (I assume you have noticed the pattern):

	<div zclass="panel panel-primary"
			viewModel="@id('wizardVM') @init(wizardModel)"
			validationMessages="@id('vmsgs')"
			onOK="@command(wizardVM.nextCommand)">
		<n:div class="panel-heading">
			<x:h3 class="panel-title" textContent="@load(wizardVM.currentStep.title)"/>
		</n:div>
		<n:div class="panel-body">
			<sh:apply template="@init(empty wrapperTemplate ? 'defaultWizardContentWrapper' : wrapperTemplate)"/>
		</n:div>
	</div>

Basket Layout

On the basket page the <grid> was replaced by a striped bootstrap table. The shadow element <sh:forEach> allows you to reuse the same ListModel of BasketItems from the BasketViewModel while only changing the layout and bind expressions. As described above, xhtml and native elements are used to render the markup required by bootstrap.

The Tooltips are enabled using the Bootstrap JS API. More on that below.

In order to make the page a little more responsive to browser width changes, the Basket recommendations on the bottom use the Bootstrap grid system to vary the number of recommendations per row at different display sizes:

/wizardexample/src/main/webapp/WEB-INF/zul/order/steps/basket.zul [4]
<x:div class="row">
	<sh:forEach items="@init(basketVM.recommendedItemsModel)">
		<x:div class="col-lg-4 col-sm-6">
			<x:div class="well well-sm">
				<sh:apply template="basketItemLabel" item="@init(each)"/>
				<button zclass="btn btn-info btn-xs pull-right" iconSclass="z-icon-shopping-cart" 
					onClick="@command('addRecommendedItem', item=each)" 
					ca:data-toggle="tooltip" ca:data-placement="right" 
					ca:data-title="${i18n:nls('order.basket.add')}"/>
			</x:div>
		</x:div>
	</sh:forEach>
</x:div>

Here are the results:

original style (left) <-> after bootstrap makeover (right)

Form Layout

The remaining pages previously rendered using a <grid> layout are now implemented using Bootstrap's horizontal form layout. This achieves the popular look and responsive sizing/positioning of input elements and their labels. The parameters to the <formRow> template remain identical to the previous version, since the model data hasn't changed.

	<x:div class="form-horizontal">
		<formRow type="static" label="@init(i18n:nls('order.basket'))" value="@init(savedOrder.basket)"/> 
		<x:div class="alert alert-info" role="alert" textContent="${i18n:nls('order.shippingAddress.hint')}"/>
		<formRow type="textbox" label="@init(i18n:nls('order.shippingAddress.street'))" 
			value="@ref(order.shippingAddress.street)"
			error="@ref(vmsgs['p_shippingAddress.street'])"/> 
		<formRow type="textbox" label="@init(i18n:nls('order.shippingAddress.city'))" 
			value="@ref(order.shippingAddress.city)" 
			error="@ref(vmsgs['p_shippingAddress.city'])"/> 
		<formRow type="textbox" label="@init(i18n:nls('order.shippingAddress.zipCode'))" 
			value="@ref(order.shippingAddress.zipCode)"
			error="@ref(vmsgs['p_shippingAddress.zipCode'])"/> 
	</x:div>
  • Lines 1,3: using bootstrap specifc css classes (form-horizontal, alert, alert-info) on xhtml components


Inside the formRow template the restyling continues. I also chose to display validation errors in using a bootstrap css classes.

	<x:div sclass="@load(empty error ? 'form-group' : 'form-group has-error')">
		<sh:choose>
			<sh:when test="@load(type eq 'checkbox')">
				<n:div class="col-sm-offset-3 col-sm-9">
					<sh:apply template="checkbox"/>
				</n:div>
			</sh:when>
			<sh:otherwise>
				<x:label class="col-sm-3 control-label" textContent="@init(label)"/>
				<n:div class="col-sm-9">
					<sh:apply template="@load(type)"/>
				</n:div>
			</sh:otherwise>
		</sh:choose>
	</x:div>
	<sh:if test="@load(!empty error)">
		<x:div sclass="alert alert-danger well-sm" textContent="@load(error)"/>
	</sh:if>

	<template name="checkbox">
		<n:div class="checkbox">
			<checkbox zclass=" " checked="@bind(value)" onCheck="@command(updateCommand)" label="@load(label)" 
				mold="formgroup"/>
		</n:div>
	</template>
	
	<template name="textbox">
		<textbox zclass="form-control" value="@bind(value)" onChange="@command(changeCommand)"/>
	</template>
	 ...
  • Line 1: <row> is replaced by a <x:div> with dynamic styling based depending on the presence of an error message
  • Line 17: conditionally output an error using a Bootstrap alert style
  • Line 23: apply a custom mold to render the checkbox and its label in the markup required by bootstrap
original style (left) <-> after bootstrap makeover (right)

Custom Checkbox Mold

Since bootstrap requires different html markup to render a checkbox inside a horizontal form, I decided to define a custom mold for this scenario.

The markup requried by Bootstrap.

	<label>
		<input type="checkbox"> Remember me
	</label>

The corresponding custom mold implementation in

/wizardexample/src/main/resources/web/js/zul/wgt/mold/checkbox-formgroup.js [5]
function (out) {
	var uuid = this.uuid, content = this.domContent_();
	out.push('<label', this.domAttrs_(), ' for="', uuid,'-real">',
			 '<input type="checkbox" id="', uuid, '-real"', this.contentAttrs_(), '/>',
			 content,'</label>');
}

As custom mold implementation requires some additional knowledge about the internal structure of a ZK widget, I try to use this approach only if necessary. This is an (often forgotten) available option for any ZK widget in case the rendered markup needs to be customized.

The new mold is registered in lang-addon.xml.

When upgrading to a different ZK version later this may need to be adjusted. On the other hand, it does provide the most efficient way to render custom markup, since it only happens on the client side.

Leverage the Bootstrap JS API

Bootstrap Tooltips

Bootstrap also offers a Javascript-api to enable dynamic effects. On the basket page I use Bootstrap-tooltips configured with ZK8's data-handler, which can be used throughout the application by simply adding client-side data-attributes.

<zk xmlns:sh="shadow" xmlns:x="xhtml" xmlns:n="native" xmlns:ca="client/attribute" xmlns:z="zk">
...
<button zclass="close" iconSclass="z-icon-times" onClick="@command('removeItem', basketItem=item)"
	ca:data-toggle="tooltip" ca:data-placement="right" ca:data-title="${i18n:nls('order.basket.remove')}"/>
... 
<button zclass="btn btn-info btn-xs pull-right" iconSclass="z-icon-shopping-cart" 
	onClick="@command('addRecommendedItem', item=each)" 
	ca:data-toggle="tooltip" ca:data-placement="right" ca:data-title="${i18n:nls('order.basket.add')}"/>

An alternative to using "ca:data-title" is to use ZK's tooltiptext property which renders into the title attribute of the resulting DOM element. Since tooltip text is a server side attribute, it also allows the data-binding annotations @init/@load.

<span zclass=" " status="@ref(item.status)" sclass="@load(i18n:nlsSub(status, 'style'))" 
	ca:data-toggle="tooltip" ca:data-placement="top" tooltiptext="@load(i18n:nls(status))">

Implementing the Data-Handler

Following the Bootstrap JS documentation the JS call below is necessary to initialize a tooltip for one or more specified DOM elements.

 $('#example').tooltip(options)

Since ZK pages are dynamic, the selector needs to be dynamic too - element Ids are generated and on top of that, ZK components may be added at any time into the DOM via AJAX. In such cases, the client-side widget lifecycle event w:onBind is a good hook to initialize JS behavior. Prior to ZK8, this was only possible using a client-side event listener.

<zk xmlns:w="client">
    <button w:onBind="jq(this.$n()).tooltip({placement: 'top'})" tooltiptext="my tooltip"/>
</zk>

A simple data-handler configuration could look like this in zk.xml.

<data-handler>
	<name>tooltip</name>
	<script>
		function (wgt, placement) {
			jq(wgt.$n()).tooltip({placement: placement});
		}
	</script>
</data-handler>

And the usage in a zul file.

<zk xmlns:w="client/attribute">
    <button ca:data-tooltip="top" tooltiptext="my tooltip"/>
</zk>

While this can be used in any page throughout the application, it is still quite limiting and not taking full advantage of the Bootstrap JS API (e.g. new parameters have to be added manually on by one). Since many Bootstrap and other JS effects are "toggled" in a similar way, we can configure a data-handler that covers those at once.

	<data-handler>
		<name>toggle</name>
		<script>
			function (wgt, dataValue) {
				jq(wgt.$n())[dataValue]();
			}
		</script>
	</data-handler>
  • Line 5: calls a JQuery function by its name (dataValue contains the function name, in this case 'tooltip')

This enables the following usage and enables other effects to be enabled with the same data-handler (e.g. modal dialogs see next chapter).

<zk xmlns:w="client/attribute">
    <button ca:data-toggle="tooltip" ca:data-placement="top" ca:data-title="my tooltip"/>
</zk>

This way any "data-*" attributes supported by the tooltip API can be used.

Bootstrap Modal

Using the same generic data-handler from above, the modal dialog integration is already showing the modal dialog.

The custom [error page is configured in zk.xml], different errors can load different dialogs, here I handle the OrderExceptions (thrown when a payment method is not available)

	<error-page>
		<exception-type>zk.example.order.api.OrderException</exception-type>
		<location>/WEB-INF/zul/order/error/orderError.zul</location>
	</error-page>
/wizardexample/src/main/webapp/WEB-INF/zul/order/error/orderError.zul [6]
<zk xmlns:ca="client/attribute" xmlns:z="zul" xmlns:x="xhtml">
	<div sclass="modal fade" ca:data-toggle="modal" ca:data-close-event="hidden.bs.modal" onModalClose="self.detach()" >
		<div xmlns="native" class="modal-dialog">
		...
		</div><!-- /.modal-dialog -->
	</div><!-- /.modal -->
</zk>
original style (left) <-> after bootstrap makeover (right)

In order to close the modal dialog I registered another data-handler, which will listen to the "hidden.bs.modal" event (triggered when the modal dialog has been closed and the fade out animation has finished). It then fire the onModalClose to the server side to detach the dialog.

	<data-handler>
		<name>close-event</name>
		<script>
			function (wgt, dataValue) {
				jq(wgt.$n()).on(dataValue, function() {
					wgt.fire("onModalClose", null, {toServer: true});
				});
			}
		</script>
	</data-handler>

I know using an inline event listener is a cheap shortcut here, but performance was not a concern in this example, especially when handling exceptions (as the name says this should be an exceptional and not the regular case). Alternatives are to apply a Composer and listen to the event inside a java class and destroy the error dialog. If further details are required, please let me know in the comments section below.

Summary

That's it for now, any ideas what to show next? Waiting for your comments.

Download

Running the Example

Checkout part-4

   git checkout part-4

The example war file can be built with maven:

   mvn clean package

Execute using jetty:

   mvn jetty:run

Then access the overview page http://localhost:8080/wizardexample/order.zul


Comments



Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.