Creating a Database-driven Application
Before You Start
Translations are available in English, Français, Español, Italiano and 日本語.
Other Introductions
- Tutorial - A brief tutorial guides you through the most fundamental features and concepts of ZK.
- Creating Hello World with Eclipse and ZK Studio - Step by step to create a Web application from scratch with Eclipse and ZK Studio.
- Creating a simple sightseeing application - A tutorial exploring some key elements of ZK by creating a sightseeing application without using database.
- ZK Essentials - A book which walks you through the key concepts and features by building a working application from the ground up.
A database-driven real-world application
We will show you, step by step, how to develop a simple Web application that uses a database. Although this tutorial is intended for new users of ZK, it is suggested that users must have at least a a basic knowledge about Java. Do not feel panic if you do not have any knowledge of other Web development languages, Java is all you need to know in order to develop Ajax-enabled Web applications with ZK.
In this tutorial, we assume that you have already installed JDK (1.5 or above), and a Servlet container (e.g., Tomcat). For more information, please take a look at Creating Hello World with Eclipse and ZK Studio.
Your first ZK application "To-do" list
Imagine that in order to plan our day better, we need an application which stores events we are going to deal with in the future. Such a Web application would require the use of a database. For the purposes of this tutorial, we'll use the Java database (HSQL DB) which does not require us to install a database server.
Download the ZK Todo source
- Download master branch
Running the sample application without an IDE
- Use the command git clone git@github.com:zkbooks/CADDA.git todo
- OR Download the source and extract to a folder named "todo"
- cd todo
- mvn jetty:run
- Open your browser and navigate to http://localhost:8080/todo.
Running the sample application with Eclipse
- Select File > Import .
- Select "Maven" > "Checkout Maven project from SCM"
- Select "git" and use the URL "git://github.com/zkbooks/CADDA.git"
- Click Finish to import the Web Project.
- Right click on todo project in the explorer and select Run As > Run on Server
- Select Apache > Tomcat v6.0 Server in the server type dialog and click Finish
- A browser will be activated automatically to explore the todo example.
All Scenarios
- Key in related information about an event, and press the Add button to insert it into database.
- Select any row in the table to show event information in the fields below for users to modify, then press Update button to update the event.
- Select any row in the table, and press the Delete button to delete the selected "Event".
Model
In the following paragraphs, the database schema, domain object, and the DAO object will be introduced.
DataBase Schema
A database table which holds our application's data needs the following attributes: event id, event name, event priority and event date.
The database schema is as listed as below:
Field | Type |
---|---|
id | varchar(50) |
name | varchar(50) |
priority | int |
date | date |
Domain Object
When the todo example is run, a corresponding domain object is created. The following source code can be viewed in ToDoEvent.java.
public class TodoEvent {
private String id;
private String name;
private int priority;
private Date date;
public TodoEvent(String id, String name, int priority, Date date) {
this.id = id;
this.name = name;
this.priority = priority;
this.date = date;
}
// getter and setter methods are omitted, please refer to the source code.
}
Data Access Object
In order to easily access the data in our database, a DAO object with the methods of findAll(),delete(),insert() and update() is needed. The following source code can be seen in EventDAO.java when the ToDo example is run.
public class EventDAO {
// The implementation is omitted, please refer to the source code.
public List<TodoEvent> findAll() {
}
public boolean delete(TodoEvent evt) {
}
public boolean insert(TodoEvent evt) {
}
public boolean update(TodoEvent evt) {
}
}
View
This section outlines how one can build the application view from scratch.
Your first ZK component
The first step is to create a file which the file extension must be zul, such as todo.zul, and place this file under the src/main/webapp directory as per the Maven standards. The way you can declare a ZK component is pretty much the same as you declare a component using HTML.
Try declaring your first window component as follows:
<window title="To do list" border="normal">
</window>
Then start your servlet container and use a browser to visit this page, eg. http://localhost:8080/todo/index.zul. The result is shown above, a window with the title of To do List.
Everything in ZK is a component. As such, you can change the title, width, and border of your window as you want. It is quite simple and intuitive. Try to change these attributes, and see the result.
Hierarchical relationships among ZK components
Next, let's try to enrich this page by adding more ZK components. Since the applicaiton displays data in a table, one could utilize a listbox component for data display. To insert a listbox, we have to declare it within the enclosing tags of the window component, as follows:
<window title="To do list" border="normal">
<listbox id="box" multiple="true" rows="5">
</listbox>
</window>
In this example, the listbox component is a child component of the window. Yes, there are hierarchical relationships among ZK components, and one will encounter a UI exception if one tries to declare a component within a wrong context, for example, declaring a window component as a child of a listbox component.
A nested component
A listbox is a nested component, which supports two different kinds of child components, listhead (aka: column of table), and listitem (aka: row of table). Within the declaration of the listbox, we set its id attribute to box. We can now use this attribute for listbox reference.
<window id="win" title="To do list" width="640px" border="normal">
<listbox id="box" multiple="true" rows="5">
<listhead>
</listhead>
<listitem>
</listitem>
</listbox>
</window>
However, this is not finished yet. Let's declare three listheader components within the enclosing tags of listhead as three columns are required -- Item, Priority, and Date -- within the table. Please see the following:
<window id="win" title="To do list" width="640px" border="normal">
<listbox id="box" multiple="true" rows="5">
<listhead>
<listheader label="Item" />
<listheader label="Priority" width="80px" />
<listheader label="Date" width="170px" />
</listhead>
<listitem>
</listitem>
</listbox>
</window>
Since the three columns in the table and each table row also requires three fields, three listcell components need to be declared within the enclosing tags of the listitem component.
<window id="win" title="To do list" width="640px" border="normal">
<listbox id="box" multiple="true" rows="5">
<listhead>
<listheader label="Item" />
<listheader label="Priority" width="80px" />
<listheader label="Date" width="170px" />
</listhead>
<listitem>
<listcell />
<listcell />
<listcell />
</listitem>
</listbox>
</window>
The nested structure of the listbox component is as follows:
listbox +-- listhead | | | +-- listheader | +-- listitem | +-- listcell
Input components
In addition to displaying events in the listbox, event information needs to inputted, including the event name (text value), event priority (numeric value) and the event date (date value). To accomplish that, a textbox, an inbox and a datebox are declared within the enclosing tags of the window component. Please see the following:
<window id="win" title="To do list" width="640px" border="normal">
<listbox id="box" multiple="true" rows="5">
<listhead>
<listheader label="Item" />
<listheader label="Priority" width="80px" />
<listheader label="Date" width="170px" />
</listhead>
<listitem>
<listcell />
<listcell />
<listcell />
</listitem>
</listbox>
Item: <textbox id="txtName" cols="25" />
Priority: <intbox id="txtPriority" cols="1" />
Date: <datebox id="date" cols="8" />
<button id="add" label="Add" />
<button id="update" label="Update" />
<button id="delete" label="Delete" />
</window>
Layout components
To distinguish these input components from the above listbox a groupbox needs to be declared to group these components together. This will draw a border around all the components contained within the groupbox. In this case, they are the input components.
<window id="win" title="To do list" width="640px" border="normal">
<listbox id="box" multiple="true" rows="5">
<listhead>
<listheader label="Item" />
<listheader label="Priority" width="80px" />
<listheader label="Date" width="170px" />
</listhead>
<listitem>
<listcell />
<listcell />
<listcell />
</listitem>
</listbox>
<groupbox>
<caption label="Event" />
Item: <textbox id="name" cols="25" />
Priority: <intbox id="priority" cols="1" />
Date: <datebox id="date" cols="8" />
<button id="add" label="Add" />
<button id="update" label="Update" />
<button id="delete" label="Delete" />
</groupbox>
</window>
In addition to the groupbox component, a caption component is declared to show an Event label along the top of the group box. The caption component works in a similar manner to the HTML legend element.
ViewModel
Our requirements include displaying, adding, editing and deleting events. In the following paragraphs, the interactions between our web application page and the application database will be implemented.
Defining a Controller
The first step is to define an EventViewModel which does not need to extend any class, it is just a POJO.
Associating the ViewModel with the View
To add interaction between the View and ViewModel, the window's apply attribute is set to BindComposer, this is a class in ZK Bind which enables binding on any control which is a child of the control on which it is delcared. For example, now any child of Window will be able to take advantage of MVVM binding.
<window id="win" title="To do list" width="640px" border="normal"
apply="org.zkoss.bind.BindComposer"
viewModel="@id('vm') @init('org.zkoss.todo.event.EventViewModel')">
Additionally the attribute viewModel is set, initializing our POJO using the @init command and assigning an id of "vm" for the POJO using the @id command. This is shown above on line 3.
Displaying data in a View
To display retrieved data from the database in the View requires only three steps:
Associating the data with View
To provide the UI with appropriate data we need a method that returns a list of TodoEvents, this is easily done in the view model which has the following method displayed.
public List<TodoEvent> getEvents() {
return eventDao.findAll();
}
After retrieving the data it needs to be bound to the view.
Binding the data
It is possible to define a UI template for DataBinding to render data into corresponding UI components. This is achieved using the bind commands such as @bind and @load. For more information on these commands please refer [ZK_Developer's_Reference/MVVM/DataBinding#A_whole_new.2C_clean_annotation_expression | here]. For now all that needs to be knows is the @bind commands handles both loading and saving of data from the UI which the load commands handles @loading only.
Asscoiating the model and selectedItem
The first thing that's needed is to load our viewmodel's data, this is exposed to the UI using a method in the ViewModel named "getEvents()". The id of the ViewModel is already set as "vm" as previously discussed, therefore this makes it easy to retrieve the data as follows:
<listbox id="box" multiple="true" rows="5" model="@bind(vm.events)">
The @bind annotation does the legwork as the ViewModel has already been initialized. Additionally a Listbox also has a selectedItem property, it makes sense to also provide this to the ViewModel so the data can be retrieved. To do this the ViewModel is edited to contain a java bean of type TodoEvent to hold the selected item:
public TodoEvent getSelectedEvent() {
return selectedEvent;
}
public void setSelectedEvent(TodoEvent selectedEvent) {
this.selectedEvent = selectedEvent;
}
The relevent bean is then bound to the UI. Not here @bind is used as not only is the loading of the data needed but also when a user changes the selectedItem in the UI this change also needs to be reflected at the backend, @bind provides both loading and saving functionality so that is used. Technically using two separate commands @load & @save and specifying the bean would yield exactly the same result.
<listbox id="box" multiple="true" rows="5" model="@bind(vm.events)" selectedItem="@bind(vm.selectedEvent)">
Displaying the model
The model has been bound and the selectedItem therefore the grid of all the items needs to be populated. This is exceptionally easy using binding.
<listbox id="box" multiple="true" rows="5" model="@bind(vm.events)"
selectedItem="@bind(vm.selectedEvent)">
<listhead>
<listheader label="Item" sort="auto(name)" />
<listheader label="Priority" width="80px"
sort="auto(priority)" />
<listheader label="Date" width="170px" sort="auto(date)" />
</listhead>
<template name="model" var="event">
<listitem value="@load(event)">
<listcell label="@load(event.name)" />
<listcell label="@load(event.priority)" />
<listcell label="@load(event.date)" />
</listitem>
</template>
</listbox>
The code snippet above demonstrates how to display the information row by row utilizing a template. The template is used by setting the name attribute to "model", this is a naming convention when placed inside a listbox. The name can be changed and will need to be if two templates exist. The var attribute is the name of the bean reference for the row object which will be accessed within the template.
As shown in the above code, the listitem and listcell's are bound using the @load command, data will not be changed using the UI here so @bind and @save are redundant. A Listitem and appropriate listcells will be generated for each TodoEvent in Listbox's model, thus is the functionality of using a template within a Listbox.
Adding the sorting feature
The sorting feature can be added easily by specifying the sort attribute with an expression describing how to sort the column. For example, one could specify auto(name)
for the first listheader since it is going to show the name of the todo event. Similarly, auto(priority)
can be used for sorting based on the priority of the todo event.
Synchronizing Views
Previously the selectedItem has been setup to both load and save a TodoEvent bean depending on what item is selected in the Listbox and vice versa. Having achieved this one wants to display the selected events data in the textbox, intbox and date fields at the bottom of the zul file for the purposes of updating. This data must then be bound to those controls.
Eventually one will want to make changes to the data, to make this easier MVVM offers a form concept. This is used in an easy manner, on the groupbox one initializes the form attribute giving it an id of event using the @id command and loading the selectedEvent bean using the @load command. The data can then be bound using the @bind command.
<groupbox id="mygrp"
form="@id('event') @load(vm.selectedEvent)>
<caption label="Event" />
Item:
<textbox id="txtName" cols="25" value="@bind(event.name)" />
Priority:
<intbox id="txtPriority" cols="1" value="@bind(event.priority)" />
Date:
<datebox id="date" cols="8" value="@bind(event.date)" />
When the selectedEvent is changed the informaiton will automatically be changed in the bound controls.
Add, Update and Remove
This isn't enough though, to follow normal use cases the applications needs the ability to add, update and remove items. To make this happen 3 buttons are created next to the textbox, intbox and datebox.
<button id="add" label="Add" onClick="@command('add')" />
<button id="update" label="Update" onClick="@command('update')" disabled="@load(empty vm.selectedEvent)" />
<button id="delete" label="Delete" onClick="@command('delete')" disabled="@load(empty vm.selectedEvent)" />
To link these buttons with a function in the ViewModel the command @command is used with a string identifying the command name, in this case "add", "update" and "delete". The subsequent code in the ViewModel looks like this:
@Command("add")
@NotifyChange("events")
public void add() {
this.newEvent.setId(UUID.randomUUID().toString());
eventDao.insert(this.newEvent);
this.newEvent = new TodoEvent();
}
@Command("update")
@NotifyChange("events")
public void update() {
eventDao.update(this.selectedEvent);
}
@Command("delete")
@NotifyChange({"events", "selectedEvent"})
public void delete() {
//shouldn't be able to delete with selectedEvent being null anyway
//unless trying to hack the system, so just ignore the request
if(this.selectedEvent != null) {
eventDao.delete(this.selectedEvent);
this.selectedEvent = null;
}
}
@Command
The highlighted annotation @command corresponds to the view's command in the button's onClick method. This stats that onClick issue the related command then the binder will trigger a command in the associated ViewModel where one exists. If one does not exist an error will be shown.
@NotifyChange
The other annotation of note is the @NotifyChange which tells the binder that after the function has executed the referred properties will have changed. The specified properties will then be refereshed in the UI to the new values. The @NotifyChange annotation takes either a string or an array given with the format {"string", "string"}.
Putting it all together
Finally, the buttons correspond to a command in the ViewModel, the related properties will refresh, however, the commands still need to act on provided data. As shown in the above code snippet the add command uses a property in the view model named "newEvent" while the add and remove rely on the "selectedEvent" property. Therefore it is important to make sure that the data held in these properties is correct.
Therefore, before performing these commands the form needs to be told where to save the appropriate data.
<groupbox id="mygrp" form="@id('event') @load(vm.selectedEvent) @save(vm.selectedEvent, before='update') @save(vm.newEvent, before='add')>
This is done using the @save command when setting the form attribute. In this case the @save command can be given conditions, such as "before" and "after" certain @comands have been executed. In this case before the update @command has been triggered the current form's TodoEvent representation will be saved into the viewmodel's selectedEvent property. Before the add @command is triggered the form's internal TodoEvent representation will be saved to the viewmodel's newEvent property. These properties are then accessed in the viewmodel functions related to those @commands.
Validating the data
A very common use case is the need to validate the data in the form. ZK provides a convenient way of implementing this using validators and the MVVM's binding engine. To start a validator is added to the form which will handle all the validation necessary.
<groupbox id="mygrp"
form="@id('event') @load(vm.selectedEvent) @save(vm.selectedEvent, before='update') @save(vm.newEvent, before='add') @validator('org.zkoss.todo.event.EventValidator')">
We then create the EventValidator.
public class EventValidator extends AbstractValidator {
public void validate(ValidationContext ctx) {
String name = (String)ctx.getProperties("name")[0].getValue();
Integer priority = (Integer)ctx.getProperties("priority")[0].getValue();
if(name == null || "".equals(name))
this.addInvalidMessage(ctx, "name", "You must enter a name");
if(ctx.getProperties("date")[0].getValue() == null)
this.addInvalidMessage(ctx, "date", "You must specify a date");
if(priority == null || priority < 1 || priority > 10)
this.addInvalidMessage(ctx, "priority", "You must give a priority > 0 && < 10");
}
}
The current property values are retrieved as properties of the TodoEvent, for example line 4 of the above code retrieves the value of the form's internal TodoEvent's name property, line 5 the value of the priority property etc. These values can then be tested to see if they are valid, if they are not valid a message can be added to the validationContext using the function addInvalidMessage where the first value is the context, second value is the key and 3rd value is array of messages. These keys can then be bound at UI to be displayed.
The UI binding would look as follows:
<hlayout>
<label style="color:red" value="@bind(vmsgs['name'])" />
<label style="color:red" value="@bind(vmsgs['priority'])" />
<label style="color:red" value="@bind(vmsgs['date'])" />
</hlayout>
In the above source the labels are bound to properties of the validation message array. To set the id for the validation method array it is added to the component on which the binding and viewmodel are originally initialized, in this case the window:
<window id="win" title="To do list" width="640px" border="normal"
apply="org.zkoss.bind.BindComposer"
viewModel="@id('vm') @init('org.zkoss.todo.event.EventViewModel')"
validationMessages="@id('vmsgs')">
The variable vmsgs can now be accessed anywhere like an array using the key values "name", "priority" and "date" as passed using the addInvalidMessage function.
Conclusion
ZK provides extremely powerful functionality in its databinder to provide developers with a hassle free way of implementing applications. To continue the learning experience please refer to the ZK Essentials book.