Explore the tutorial application

Tutorial Wiki article Tutorial Wiki article

Štítky: zk zk-dl tutorial
  • Step by step tutorial of a simple ToDo application

This tutorial is a full stack enterprise application based on ZK + Spring + Hibernate with ZK-DL additional modules. If you want to use only one of our modules to enrich your architecture, see Examples page for more basic usage.

Explore the application before starting the tutorial. The Category overview page is just a list of task categories with name and description. On this page we demonstrate the simplest usage, it can be created almost mechanically. The actual task list demonstrates some more advanced concepts - usage of checkboxes in a list, enums, custom filtering, change background of a listcell based on data etc.

Note that CSS styles interfere with Liferay (the hosting portal) on this page a little bit - it is only a matter of adding the correct CSS style for both frameworks, but to keep the example simple, we did not add them here. If you run the example yourself, or launch the application standalone , there is no such problem. The library is localized to English and Czech, but it is easy to add another language.

Project setup#

For an easy project setup, please install Apache Maven (you can download it here) or use the embedded Maven in your IDE.

Checkout the tutorial from SVN#

The whole source code is in the ZK-DL project SVN repository. Just check it out with command line SVN, or use your favorite IDE.
 svn checkout https://zk-dl.googlecode.com/svn/trunk/ZKDLTutorial
It contains the complete tutorial as is described here. Although almost the whole source code is available in this tutorial article, it is useful to download functional application for reference or if loose the track. We suggest to create both - full tutorial and empty project from the archetype (next chapter).

Project archetype#

As an alternative, you can start with empty (but fully functional) project. With maven installed, just type (change groupid and package name to your domain and name your app in artifactId):
 # maven archetype example
mvn archetype:generate \
  -DgroupId=cz.datalite \
  -DartifactId=ZkApp \
  -DpackageName=cz.datalite \
  -Dversion=1.0-SNAPSHOT \
  -DarchetypeGroupId=cz.datalite.zk-dl \
  -DarchetypeArtifactId=zk-dl-archetype \
  -DarchetypeVersion=1.0 \
  -DremoteRepositories=[http://zk-dl.googlecode.com/svn/maven2-repo]


# or on windows (interactive, one line)
mvn archetype:generate -Dversion=1.0-SNAPSHOT -DarchetypeGroupId=cz.datalite.zk-dl 
-DarchetypeArtifactId=zk-dl-archetype -DarchetypeVersion=1.0 -DremoteRepositories=[http://zk-dl.googlecode.com/svn/maven2-repo]

This will generate fully functional application. You can use this archetype to jump-start your own projects as well.

Launch the application#

You can run the application with Jetty servlet container. It is embedded, very lightweight and with very quick startup time (~5 sec). What's more, You can launch the application directly from Maven.
 # build the application
mvn install

# run the app with jetty (preconfigured on port 9090)
mvn jetty:run

Open your browser and enter http://localhost:9090/YourAppName (or simply http://localhost:9090/, jetty will offer you available contexts).

You can use any application server you want. Configuration for Apache Tomcat is already a part of the archetype, just deploy the war file to your Tomcat server. For other application servers, you will need to create appropriate database configuration. See WEB-INF/jetty-env.xml or META-INF/context.xml as configuration examples.

Explore the source code#

If you started with the archetype, the source code of the application is for now only a bunch of configuration files and one example ZUL file.

Check used frameworks and their versions in maven pom.xml. Look at available ZK-DL project modules dependencies and check complete list with explanation on the ZK-DL Modules wiki page.

 <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="[http://maven.apache.org/POM/4.0.0"] xmlns:xsi="[http://www.w3.org/2001/XMLSchema-instance"]
              xsi:schemaLocation="[http://maven.apache.org/POM/4.0.0] [http://maven.apache.org/maven-v4_0_0.xsd">]
    <modelVersion>4.0.0</modelVersion>

    <groupId>cz.datalite</groupId>
    <artifactId>MyApp</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    
    <name>MyApp</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <org.springframework.version>3.0.5.RELEASE</org.springframework.version>
        <org.springframework.security.version>3.0.3.RELEASE</org.springframework.security.version>
        <org.zkoss.zk.version>5.0.6</org.zkoss.zk.version>
        <cz.datalite.zk-dl.version>1.1</cz.datalite.zk-dl.version>
        <org.junit.version>4.8.1</org.junit.version>
    </properties>

   ...

There is nothing special here for Spring and Hibernate configuration, check their project sites for more details. The database is preconfigured with local Hypersonic Java database.

There is only one actual application file - index.zul, which contains all-in-one example application. This is only for demonstration purpose and we will not use it in our tutorial (just check it).

IDE integration#

The application is based on maven, so it should be no problem to set it up with your favourite IDE. For Netbeans, the configuration is even part of the project and you can just hit the run or debug button and it will launch with Jetty. Just open your browser manually and enter the app address.

We recommend to use JRebel plugin, it really speeds up the development. JRebel plugin is already part of maven pom.xml configuration file and it will generate required rebel.xml file. You just need to run your application server with JRebel setup (see JRebel configuration).

TODO - wiki page with maven + jetty + JRebel configuration

Service & DAO#

The project is structured as a typical J2EE layered application with interface and implementation on each layer:
  • Model (cz.datalite.tasks.model) - JPA model / @Entity object graph. See some JPA tutorial if you are not familiar with Object-Relational mappings
  • DAO (cz.datalite.tasks.dao) - thin layer on JPA/Hibernate to access the database. This is also the place where you put custom queries
  • Service (cz.datalite.tasks.service) - business logic and transaction demarcation (configured with Spring)
  • Web (cz.datalite.tasks.web) - the actual web page controller, ZK framework with ZK-DL modules

In this example the implementation of the Service and DAO layer is almost always empty - the library and generic implementation takes care of all basic CRUD and finder/paging methods. It is even relevant to discuss, if we need all layers and interface/implementation on each layer. But it depends on the size of your project and other architectonic decisions.

Note that this tutorial is an example of how we build our applications with ZK-DL library. However, the ZK-DL modules are very loosely coupled and you can use any Service/Database layer you want. It has been used with EJB and others. See the Examples page for more basic usage.

Model#

The model is created as a JPA object graph and object-relational mapping is configured with java annotations. You need at least basic knowledge of JPA or Hibernate.

We use mostly anemic domain model (as is common in J2EE applications) - the entity class contains only data fields and basic setters/getters. Business logic is always in the service layer, the model contains only simple methods (like getFullName() - returning firstName + lastName).

The model class is used across all layers - even in the web layer. This creates some coupling, but it simplifies the development a lot.

Lets start with a very simple class - we will make the task category configurable in our application, so we need a CATEGORY database table and a mapped Category JPA entity.

 @Entity
public class Category implements Serializable {
    /** Primary key (autogenerated). */
    @Id
    @GeneratedValue
    private Long idCategory;

    /** Name of the category */
    @NotNull
    private String name;
    
    /** Description of the category */
    private String description;

   .... equals(), hashCode(), toString()

   ... setters/getters
}

If you launch the application now, it will create database table CATEGORY with three columns - primary key, name and description.

  • @GeneratedValue - is part of JPA standard, the database will autoincrement the value every time new entity is created
  • @NotNull (javax.validation.constraints.NotNull) - is part of JSR 303 - Bean Validation standard, we use implementation by Hibernate validator. This will automatically check the constraints before database insert/update, but we will see later how it integrates even with web layer and ZK user form validation.
  • equals() - although it should be simple to implement this method, it is not :-). Look at our implementation, it considers two entities equals only if they are the same object reference (this == obj), or if they have the same primary key. This covers almost all use cases and does not make you think about "natural primary keys" (see Hibernate recommendations for more info)
  • hashCode() - twin brother of equals, same logic
  • toString() - it is a good idea to manually include main columns, it makes any debug/error output much more readable (return "cz.datalite.task.model.Category id=" + this.idCategory + " val=" + name + ""; )
  • setters/getters - usually we add them at the end of the source file, auto-generated by the IDE.

Priority of a task will be a built in list, so make it an Enum:

 public enum Priority {
    HIGH("High"),
    NORMAL("Normal"),
    LOW("Low");

   .. constructor, getter of priority name.

And finally the task definition, it contains name, description, due date/time, priority, category and done:

 @Entity
public class Task implements Serializable {

    /** Primary key (autogenerated). */
    @Id
    @GeneratedValue
    private Long idTask;

    /** Task name */
    @NotNull
    private String name;

    /** Task description */
    private String description;

    /** When is the due of this task */
    @Temporal(value = TemporalType.TIMESTAMP)
    @Future(message = "The task due must be set into future.")
    private Date taskDue;

    /** Priority of this task **/
    @Enumerated(value = EnumType.STRING)
    @NotNull
    private Priority idPriority = Priority.NORMAL;

    /** Category of this task **/
    @ManyToOne
    @ForeignKey(name = "FK_TASK_CATEGORY")
    @NotNull
    private Category idCategory;
    
    /** Is the task done? **/
    @Type(type = "yes_no")  @Column(columnDefinition="CHAR(1) DEFAULT 'Y'")
    boolean done = false;

  .... equals(), hashCode(), toString()

   ... setters/getters

This class contains some more advanced use cases of the same technology - JPA/Hibernate end bean validation:

  • @Temporal(value = TemporalType.TIMESTAMP) - save the date with time
  • @Future - one of standard bean validation annotation, it is used here with a custom message
  • @Enumarated - use this enum type and save it to the database as a string
  • @ManyToOne - dependency between entities (object graph)
  • @ForeignKey - Hibernate specific annotation, if we let the database to be autogenerated (DDL), use this foreign key name
  • @Type - Hibernate specific annotation, map a boolean property to a database column with Y/N values

Please note, that everything used here is part of a standard and is not ZK-DL specific. You can use a model layer like this in any java enterprise application. Used standards here are JPA (Hibernate) and JSR 303 - Bean Validation(Hibernate Validator).

DAO#

There is lot of controversy about DAO usage in modern web applications. JPA EntityManager is sort of generic DAO itself and an additional DAO implementation is usually only delegation to the EntityManager or even a generic implementation (as in our case).

Our GenericDAO class (part of HibernateDAO module) is based on Hibernate recomendations to the DAO. This is generic DAO implementation like many others (see the implementation, it is quite straight-forward).

There are some special methods with parameters DLSearch (container to transfer filter parameters like criterion, sorting, paging and projection) and DLResponse (container for database response):

 /**
* Returns a <code>DLResponse</code> object that includes both the list of
* results like <code>search()</code> and the total length like
* <code>count()</code>.
* @param search search parameters
* @return result list with total length
*/
DLResponse<T> searchAndCount(DLSearch<T> search);
These methods are used to delegate calls from web components (e.g. listbox) directly to a generic database call. We will see more details on the web layer.

Now to the actual DAO implementation. Because we don't need anything special (custom query), we can use generic implementation and the classes will be empty.

 public interface CategoryDAO extends GenericDAO<Category, Long> { 
}

@DAO
public class CategoryDAOImpl extends GenericDAOImpl<Category, Long> implements CategoryDAO {
}


public interface TaskDAO extends GenericDAO<Task, Long> {
}

@DAO
public class TaskDAOImpl extends GenericDAOImpl<Task, Long> implements TaskDAO {
}

The only special annotation here is @DAO - first encounter with Spring (you can use Spring's @Repository interface instead, our @DAO does exactly the same) . Check the GenericDAO interface for more information about available methods on the DAO.

You can even omit the XxxDAOImpl class - if no such spring bean exists, it is created by Spring configuration (but we included it in this example, because the behaviour is then more transparent).

Service#

The main tasks of Service layer are :
  • business logic
  • access the database via DAO
  • transaction demarcation

There is not much business logic in our example application, we just need basic CRUD and finder operations. Same as on DAO, if you do not need anything special, just use generic service and the implementation can be empty.

 public interface CategoryService extends GenericService<Category, Long> {
}

@Service
public class CategoryServiceImpl extends GenericServiceImpl<Category, Long, CategoryDAO> implements CategoryService {
}

public interface TaskService extends GenericService<Task, Long> {
}

@Service
public class TaskServiceImpl extends GenericServiceImpl<Task, Long, TaskDAO> implements TaskService {
}

We make the service available with @Service Spring annotation. The default DAO is autowired via generic type (CategoryDAO, TaskDAO) - the generic service checks this type and tries to find Spring bean of this type (similar to the @Autowired Spring annotation).

Now we have four empty source files for each entity. You may argue, that this is redundant and I will almost agree with you :-). This is the classical layered architecture with interface and implementation on each layer. Now in J2EE 6 the interface is no more required and I will incline to skip it with Spring as well. As for the separate DAO, usage for this layer depends on how big your project is, which ORM technology you use and how many custom SQL queries you have. If you wish, you can create only one Spring bean with the same function (DAO, transaction, business logic)

The Web#

We use MVC web design pattern - each page is split into at least two source files - a ZUL file with view component definition and a java class with controller logic (the model is usually directly a JPA entity). This approach is based on ZK's MVC design pattern with some ZK-DL extensions in No InterWiki reference defined in properties for Wiki called "[http"! (check the wiki, it is not a long one).

Category Overview (basic example of a overview page)#

Although the ZUL file looks complex at first look, it can be almost auto-generated from the entity definition - all you need to fill in is the entity name and which properties you will to display. We could create even more generic components for this exact purpose to shorten the source code, but we think that this size is the correct one - each line has it's purpose and can be changed if you need a customization. It also uses ZK in a standard way (which can be found in official documentation), there is no "black magic" behind.

categoryOverview.zul:

 <?xml version="1.0" encoding="UTF-8"?>
<?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?>
<?init class="cz.datalite.zk.databinder.DLDataBinderInit" root="categoryOverviewWindow"?>

<?page title="Task category overview" id="categoryPage"?>

<zk xmlns="[http://www.zkoss.org/2005/zul">]

    <window id="categoryOverviewWindow" height="100%" apply="${categoryOverviewController}">
        <borderlayout>
            <north>
                <listControl apply="${ctl.listCtl}">
                    <button id="newButton" label="Create new category"/>
                    <button id="openDetailButton" label="Open selected category"/>
                    <button id="deleteButton" label="Delete selected category"/>
                </listControl>
            </north>
            <center>
                <listbox apply="${ctl.listCtl}" vflex="true" selectedItem="@{ctl.category}" selectFirstRow="true"
                         fixedLayout="true">
                    <listhead>
                        <listheader label="Name" sort="db()"/>
                        <listheader label="Description" sort="db()"/>
                    </listhead>
                    <listitem id="listitem" self="@{each=listEach}">
                        <listcell label="@{listEach.name}"/>
                        <listcell label="@{listEach.description}"/>
                    </listitem>
                </listbox>
            </center>
            <south>
                <dlpaging apply="${ctl.listCtl}" pageSize="50" autohide="true"/>
            </south>
        </borderlayout>

    </window>

</zk>
  • <?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?> - ZK's variable resolver - resolve ZUL variable as a Spring bean (by bean name). Main usage is in apply="${categoryOverviewController}" - categoryOverviewController is a spring bean defined in CategoryOverviewController class via @Controller annotation.
  • <?init class="cz.datalite.zk.databinder.DLDataBinderInit" root="categoryOverviewWindow"?> - ZK's databinding mechanism
  • <?page title="Task category overview" id="categoryPage"?> - Page processing instruction - set the page title in the browser
  • <window id="categoryOverviewWindow" height="100%" apply="${categoryOverviewController}"> - apply="${categoryOverviewController}" - use the composer (controller).
  • <borderlayout> - ZK's BorderLayout component - this is a very versatile component, which stretches itself across a whole browser window and makes it look like a desktop application
  • <listControl apply="${ctl.listCtl}"> - ZK-DL listbox controller - user interface controls for the listbox - quick filter and listbox manager (sorting, export to Excel, ...). The apply attribute is used to attach the component to listbox's controller. The same attribute is used in listbox and paging components.
  • <button id="newButton" image="/images/add.png" tooltiptext="Create new record" mold="os"/> - normal ZK button - it is bound to controller via it's id attribute
  • <listbox apply="${ctl.listCtl}" vflex="true" selectedItem="@{ctl.category}" selectFirstRow="true" fixedLayout="true"> - main listbox controller, see DLListbox wiki page for more info and available configuration/attributes
  • <listheader label="Name" sort="db()"/> - listheader component definition and filter/sort/export behaviour of the column
  • <listitem id="listitem" self="@{each=listEach}"> - bind values via ZK's databinding
  • <dlpaging apply="${ctl.listCtl}" pageSize="50" autohide="true"/> - paging controller. Again, with apply="${ctl.listCtl}" we bind all listbox components together with their component controller

Via Spring and the apply window attribute, the ZUL file is connected to it's java controller (CategoryOverviewController). Read DLComposer for more information, we will only describe main features here:

 @Controller
public class CategoryOverviewController extends DLComposer {
    public static final String DETAIL_PAGE = "categoryDetail.zul";

    // Spring services
    @Autowired CategoryService categoryService;

    // Selected item in ZUL
    @ZkModel Category category;

    // Controller for the main list
    @ZkController
    DLListboxController<Category> listCtl = new DLListboxCriteriaController<Category>("CategoryList") {
        @Override
        protected DLResponse<Category> loadData(DLSearch<Category> search) {
            return categoryService.searchAndCount(search);
        }
    };

    /**
* Refresh data in the main list.
*
* @param category select this object in list (mainly after detail window is closed)
*/
    @ZkEvent(event = ZkEvents.ON_REFRESH)
    @ZkBinding
    public void refresh(Category category) {
        listCtl.refreshDataModel();
        if (category != null) listCtl.setSelectedItem(category);
    }

    /**
* Open detail window.
*
* @param payload current event index
*/
    @ZkEvents(events = {
            @ZkEvent(id = "openDetailButton"),
            @ZkEvent(id = "listitem", event = Events.ON_DOUBLE_CLICK),
            @ZkEvent(id = "listitem", event = Events.ON_OK),
            @ZkEvent(id = "newButton", payload = ZkEvents.EVENT_NEW)
    })
    public void openDetail(int payload) {
        ZKHelper.openDetailWindow(self, DETAIL_PAGE, "category",
                payload == ZkEvents.EVENT_NEW ? new Category() : category);
    }

    /**
* Delete selected object category.
*/
    @ZkEvent(id = "deleteButton")
    @ZkConfirm(message = "Are you sure?", title = "Please confirm the action")
    public void delete() {
        categoryService.delete(category);
        Events.postEvent(ZkEvents.ON_REFRESH, self, null);
    }
}

Annotations helps you to emphasize the reason behind a property or a method. If used properly, you can read the controller like a story - start with @Controller annotation on the class - yes, this class is a C in MVC, continue with @Autowired (or @EJB) to wire service beans, seed the @ZkModel properties and create custom @ZkControllers for data driven components, register some @ZkEvents, map some @ZkComponent from V in MVC (to change the visual behaviour directly from controller), and get @ZkParameters to know what to display.

  • @Controller - on the class CategoryOverviewController create Spring bean named "categoryOverviewController". Do you remember the apply="${categoryOverviewController}" property in ZUL?
  • @Autowired CategoryService categoryService - use the Spring service (access database). Do you remember the @Service annotation on CategoryServiceImpl?
  • @ZkModel Category category - make this object accessible from the ZUL file (selectedItem="@{ctl.category}")
  • @ZkController DLListboxController<Category> listCtl - controller for data driven component (e.g. <listControl apply="${ctl.listCtl}"> from ZUL)
  • protected DLResponse<Category> loadData(DLSearch<Category> search) - signature for a method to load data via Hibernate criteria. The search object already contains all filter, sort, paging, ... from attached ZUL components (see DLListbox).
  • return categoryService.searchAndCount(search); - access the database (use generic method).
  • @ZkEvent(event = ZkEvents.ON_REFRESH) - register this method as a event listener on main window
  • @ZkBinding - refresh the binding after the method executes
  • ZKHelper.openDetailWindow(self, DETAIL_PAGE, "category", ... ) - open the detail window using Executions.createComponents(String zulFile)
  • @ZkConfirm - show Yes/No Messagebox before the method executes

It is really recommended to read the DLComposer overview - it will make clear the usage of @ZkXxx annotations.

Category Detail#

A twin brother to the overview page. Open detail of one category and edit it. Same as for the overview page - because there is no custom logic for category detail, the file can be almost auto-generated.

categoryDetail.zul:

 <?xml version="1.0" encoding="UTF-8"?>

<?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?>
<?init class="cz.datalite.zk.databinder.DLDataBinderInit" root="categoryDetailWindow" validator="${validator}"?>

<zk xmlns="[http://www.zkoss.org/2005/zul">]

    <window id="categoryDetailWindow" border="normal" title="Category detail" closable="true" width="700px"
            apply="${categoryDetailController}">

        <grid>
            <rows>
                <row>
                    <label value="Category name"/>
                    <textbox value="@{ctl.category.name}" hflex="1"/>
                </row>
                <row>
                    <label value="Category descripton"/>
                    <textbox value="@{ctl.category.description}" rows="5" hflex="1"/>
                </row>
            </rows>
        </grid>

        <hbox>
            <button id="saveCategoryButton" label="Save"/>
            <button id="cancelCategoryButton" label="Cancel"/>
        </hbox>

    </window>

</zk>

This should be familiar now to you, the only difference is validator="${validator}" - use bean validation with ZK's DataBinder. in this example it validates textbox value for not null. See DataBinding enhancements wiki for more information, validations are also described later in this tutorial.

And the controller for detail ZUL file - CategoryDetailController.java:

 @Controller
public class CategoryDetailController extends DLComposer {
    // Spring services
    @Autowired CategoryService categoryService;

    // Selected item in ZUL
    @ZkModel Category category;

    /**
* Set category according to parameter (or create new entity) <br />
*
* @param category object from the ZK parameter
*/
    @ZkParameter(createIfNull = true)
    public void setCategory(Category category) {
        if (category.getIdCategory() != null)
            this.category = categoryService.get(category.getIdCategory());
        else
            this.category = category;
    }

    /**
* Save the item.
*/
    @ZkEvents(events = {
            @ZkEvent(id = "saveCategoryButton"),
            @ZkEvent(event = Events.ON_OK)
    })
    @ZkBinding(saveBefore = true, loadAfter = false)
    public void save() {
        categoryService.save(category);
        ZKHelper.closeDetailWindow(self, true, category);
    }

    /**
* Close the window
*/
    @ZkEvents(events = {
            @ZkEvent(id = "cancelCategoryButton"),
            @ZkEvent(event = Events.ON_CANCEL)
    })
    public void cancel() {
        ZKHelper.closeDetailWindow(self);
    }
}

New annotations used here:

  • @ZkParameter(createIfNull = true) - read a parameter - it can be GET/POST HTTP parameter or custom attribute from Executions.createComponents.
  • @ZkBinding(saveBefore = true, loadAfter = false) - save the DataBinder before the method executes. This ensures that @NotNull validations are fired on "Save" button

Task Overview (some more advanced use cases)#

Now lets create more complex page for list of tasks. The basic structure is very similar to category overview. We will first list the files and then emphasize new features separately.

taskOvervew.zul:

 <?xml version="1.0" encoding="UTF-8"?>
<?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?>
<?init class="cz.datalite.zk.databinder.DLDataBinderInit" root="taskOverviewWindow"?>

<?page title="Task Overview" id="taskPage"?>

<zk xmlns="[http://www.zkoss.org/2005/zul">]

    <window id="taskOverviewWindow" self="@{define(content)}" height="100%" apply="${taskOverviewController}">

        <borderlayout>
            <north>
                <listControl apply="${ctl.listCtl}">
                    <button id="newButton" label="Create new task"/>
                    <button id="openDetailButton" label="Open selected task"/>
                    <button id="deleteButton" label="Delete selected task"/>
                </listControl>
            </north>
            <center>
                <listbox apply="${ctl.listCtl}" vflex="true" selectedItem="@{ctl.task}" selectFirstRow="true"
                         fixedLayout="true">
                    <listhead>
                        <listheader label="Name" sort="db()"/>
                        <listheader label="Description" sort="db()"/>
                        <listheader label="Due" sort="db()"/>
                        <listheader label="Priority" column="priority" sort="db()"
                            filterComponent="${ctl.priorityFilterComponent}"
                            filterCompiler="${ctl.priorityFilterCompiler}"
                            filterOperators="eq,neq"
                         />
                        <listheader label="Category" sort="db()"/>
                        <listheader label="Done" width="100px" column="done" sort="db()"
                            filterComponent="cz.datalite.zk.components.list.filter.components.BooleanFilterComponent"
                            filterCompiler="cz.datalite.zk.components.list.filter.compilers.BooleanCriterionCompiler"
                            filterOperators="eq"/>

                    </listhead>
                    <listitem id="listitem" self="@{each=listEach}" style="@{listEach.done, converter='ctl.coerceDone'}">
                        <listcell label="@{listEach.name}"/>
                        <listcell label="@{listEach.description}"/>
                        <listcell label="@{listEach.taskDue}"/>
                        <listcell label="@{listEach.priority, converter='ctl.coercePriority'}"/>
                        <listcell label="@{listEach.idCategory.name}"/>
                        <listcell><checkbox checked="@{listEach.done}" readonly="true"/></listcell>
                    </listitem>
                </listbox>
            </center>
            <south>
                <dlpaging apply="${ctl.listCtl}" pageSize="50" autohide="true"/>
            </south>
        </borderlayout>

    </window>

</zk>
 @Controller
public class TaskOverviewController extends DLComposer {
   ... services, selected item, list controller, refresh, open detail ...

    // Filter component is used in normal filter for the user to enter value.
    // Component for enumeration is combobox with it's values, second parametr is property name to show
    @ZkController
    FilterComponent priorityFilterComponent = new EnumFilterComponent(Priority.values(), "name");

    @ZkController
    FilterCompiler priorityFilterCompiler = new EnumCriterionCompiler(Priority.values(), "name");


    /**
* Basic example of Data -> UI Type Converter
* @param done databinder data (see ZUL)
* @return converted value. Here the style property based on data
*/
    public String coerceDone(boolean done)
    {
        if (done)
            return "color: gray; text-decoration: line-through;";
        else
           return "";
    }

    /**
* More advanced use of Data -> UI Type Converter.
*
* @param priority databinder data (see ZUL).
* @param cell - you can add second parameter, which must be exactly the component on
*          which the converter is set
* @return String value. In this example, there is a side efect on the component
*/
    public String coercePriority(Priority priority, Listcell cell)
    {
        if (Priority.HIGH == priority)
            cell.setStyle("background-color: coral;");

        return priority.getName();
    }

Filter component and compiler#

For a normal database column, the listheader component can guess, what the property is and it's datatype. For a column represented by enum, the component can not do the translation automatically, but we need to tell it what the column is and how to create the database query. There is also a difference between Quick filter (simple textbox in top left corner, where we get only a String value) and a Normal filter (separate window with custom filtering component for each query).

First example is with an enumeration - in a Quick filter we want to search by displayed value, in Normal filter we want a combobox with enum selection. Btw. if you omit the definition for enum presented here, some of the functions will work, but you will have to remove the column from Quick filter with quickFilter="false".

 <listheader label="Priority" column="priority" sort="db()"
     filterComponent="${ctl.priorityFilterComponent}"
     filterCompiler="${ctl.priorityFilterCompiler}"
     filterOperators="eq,neq"
   />

    @ZkController
    FilterComponent priorityFilterComponent = new EnumFilterComponent(Priority.values(), "name");

    @ZkController
    FilterCompiler priorityFilterCompiler = new EnumCriterionCompiler(Priority.values(), "name");
  • column="priority" - what the filter property is - the database query then uses something like PRIORITY = x
  • filterComponent - create custom component in the normal filter. In our controller class, we use predefined EnumFilterComponent, where we set list of enum values and the enum name property. Based on this definition, when you open normal filter and select the "Priority" column, you will get combobox with all priorities with label on getName().
  • filterCompiler - how to create the database query for this column. Here we use Hibernate criteria, so again predefined EnumCriterionCompiler will help us. Look at it's implementation - for normal filter it just passes selected enum to Hiberate (it correctly handles the value), for the Quick filter it searches text value in enum names.
  • filterOperators - which operators are available for this filter (Normal filter). Because the component presents list, only equals and not equals have any meaning (see DLFilterOperator for complete list of operator values).

Similar situation is with boolean values. Although it may seem simple to search by boolean value, just remember the column definition: @Type(type = "yes_no") @Column(columnDefinition="CHAR(1) DEFAULT 'Y'"). And this is only one way how to map boolean value to a database column.

 <listheader label="Done" width="100px" column="done" sort="db()"
        filterComponent="cz.datalite.zk.components.list.filter.components.BooleanFilterComponent"
        filterCompiler="cz.datalite.zk.components.list.filter.compilers.BooleanCriterionCompiler"
        filterOperators="eq"/>
  • BooleanFilterComponent - create checkbox to visually set true/false.
  • BooleanCriterionCompiler - for normal filter, it just passes the boolean value to Hibernate (and Hibernate can handle it correctly). For Quick filter, it needs to decide from the String value, what is true and what false - look at the implementation, it translates "yes", "y", "true", "no", "n", "false" to boolean values, otherwise it just ignores the filter.

Our library has several utility classes for base use cases. Just look at their implementation to create your own component and compiler. Look at DLListbox wiki article for more information.

Customization of Conversion between the Data Source and UI Components#

This is based on ZK's Type Converter, but with some customization for more easier usage (see our DataBinding enhancements).

The first usage is just Data -> UI conversion via a controller method. We have a boolean property "done" and based on it's value we want to create a css style and set the property with databinding.

 <listitem id="listitem" self="@{each=listEach}" style="@{listEach.done, converter='ctl.coerceDone'}">

    /**
* Basic example of Data -> UI Type Converter
* @param done databinder data (see ZUL)
* @return converted value. Here the style property based on data
*/
    public String coerceDone(boolean done)
    {
        if (done)
            return "color: gray; text-decoration: line-through;";
        else
           return "";
    }

A more advanced usege of Data -> UI conversion is with a side effect. We could have set the liscell label property directly with @{listEach.priority.name), but we need a converter to influence the component (and set it's background colour in this case):

 <listcell label="@{listEach.priority, converter='ctl.coercePriority'}"/>

    /**
* More advanced use of Data -> UI Type Converter.
*
* @param priority databinder data (see ZUL).
* @param cell - you can add second parameter, which must be exactly the component on
*          which the converter is set
* @return String value. In this example, there is a side efect on the component
*/
    public String coercePriority(Priority priority, Listcell cell)
    {
        if (Priority.HIGH == priority)
            cell.setStyle("background-color: coral;");

        return priority.getName();
    }

You can put anything in a listcell#

Have a look at the checkbox here:
 <listheader label="Done" width="100px" column="done" sort="db()"
      ....
      ....
   <listcell><checkbox checked="@{listEach.done}" readonly="true"/></listcell>
There is nothing special about listcell component and you can put anything inside it. Just remember, then the listheader will than not be able to guess what the entity property is and it's datatype, so you need to set it manually in a listheader (in this case it is column="done", no need for the datatype because of filterCompiler).

Task Detail - usage of more components#

 ... directives, zk, window, open grid, ...

                <row>
                    <label value="Due"/>
                    <datebox value="@{ctl.task.taskDue}" format="M/d/yy HH:mm" width="120px"/>
                </row>
                <row>
                    <label value="Priority"/>
                    <lovbox apply="${ctl.lovboxPriorityCtl}" mold="rounded"
                        createQuickFilter="false" createPaging="false" clearButton="false"
                        selectedItem="@{ctl.task.priority}"
                        labelProperty="name"/>

                </row>
                <row>
                    <label value="Category"/>
                    <lovbox apply="${ctl.lovboxCategoryCtl}" mold="rounded" hflex="1"
                        selectedItem="@{ctl.task.idCategory}"
                        labelProperty="name" descriptionProperty="description"/>

                </row>
                <row>
                    <label value="Done"/>
                    <checkbox checked="@{ctl.task.done}"/>
                </row>

   ... end grid, ok/cancel buttons, end window
 @Controller
public class TaskDetailController extends DLComposer {
    
    ... Spring service, selected item, parameter, ok/cancel events

    @ZkController
    DLLovboxController<Category> lovboxCategoryCtl = new DLLovboxGeneralController<Category>(
            new DLListboxCriteriaController<Category>(Category.class.getName() + "#lovboxCategoryCtl") {

        @Override
        protected DLResponse<Category> loadData(final DLSearch<Category> search) {
            return categoryService.searchAndCount(search);
        }
    });

    @ZkController
    DLLovboxController<Priority> lovboxPriorityCtl = new DLLovboxGeneralController<Priority>(
            new DLListboxSimpleController<Priority>(Priority.class.getName() + "#lovboxPriorityCtl") {
                @Override
                protected DLResponse<Priority> loadData(List<NormalFilterUnitModel> filter, 
                                              int firstRow, int rowCount, List<DLSort> sorts) {
                    return DLFilter.filterAndCount(filter, Arrays.asList(Priority.values()), firstRow, rowCount, sorts);
                }
            }
    );
}

Components#

In the category example, we used only a simple textbox component. Here we have datebox, checkbox and lovbox (more about it in the next chapter). Look at ZK's input components for complete list, you can use any of them.

One module of our library is the ZKComponents components module, where we do some customizations of ZK's original components. Be careful if you use this module, it may change the behaviour a little bit. Java class of each component has prefix DL (e.g. DLButton, DLLabel, ...), check the javadoc of each component, it contains list of extensions/customzations.

The main difference in behaviour is the property "readonly". We changed the meaning here to really "be visible and accessible, but not changeable", which differs to ZK's implementation for components like datebox or combobox. With this behaviour, we can than use method like ZKHelper.setReadonly() to set a whole user form to readonly mode.

Lovbox#

This component is similar to a combobox but similar to our listbox component, it is database driven. Actually, it is based on the listbox component, combined with ZK's popup.

In the ZUL, you can use any popup property and several others - see DLLovbox source code for list of properties, they are documented.

 <lovbox apply="${ctl.lovboxPriorityCtl}" mold="rounded"
      createQuickFilter="false" createPaging="false" clearButton="false"
      selectedItem="@{ctl.task.priority}"
      labelProperty="name"/>
If you want, you can include child listbox element inside the lovbox and configure the behaviour as for any other listbox.

The controller reflects the nesting of listbox in a lovbox:

 @ZkController
    DLLovboxController<Category> lovboxCategoryCtl = new DLLovboxGeneralController<Category>(
            new DLListboxCriteriaController<Category>(Category.class.getName() + "#lovboxCategoryCtl") {

        @Override
        protected DLResponse<Category> loadData(final DLSearch<Category> search) {
            return categoryService.searchAndCount(search);
        }
    });
just use any listbox controller implementation you wish and put it as a constructor parameter of the lovbox..

Monitor the application#

Last but not least, you need to check the application at runtime. Setup of monitor is already part of the artifact and the tutorial appication (see WEB-INF/applicationContext-zkmonitor.xml for Spring configuration and WEB-INF/zk.xml for listeners and richlet mapping).

The monitor is available on the address http://yourapp/zk/ZKMonitor, check http://zk.datalite.cz/ZKDLTutorial-1.1/zk/ZKMonitor for this running tutorial. Just hit the "Enable monitor" button and everythig in your session will be monitored. Use the application for a while and then hit "refresh" to see the results.

See the ZKStatistics - Performance Monitoring of ZK Applicaiton for details.

0 Přílohy
69299 Zobrazení
Průměr (0 Hlasů)
Komentáře