Chapter 5: Handling User Input"

From Documentation
Line 149: Line 149:
 
== Handle Events in a Composer ==
 
== Handle Events in a Composer ==
  
The example application has 2 functions, save and reload, which are both triggered by clicking a button. Here we demonstrate the way to add an event listener on the component in <tt>@Listen</tt> annotation instead of calling a method (mentioned in chapter 4).  
+
The example application has 2 functions, save and reload, which are both triggered by clicking a button. In this section, we demonstrate the way to add an event listener defined in a composer in <tt>@Listen</tt> annotation instead of calling a method (mentioned in chapter 4).
 +
 
 +
[[File:Tutorial-ch5-save.png | center | 600px]]
 +
<div style="text-align:center">'''Click "Save" button'''</div>
 +
 
  
 
An event listener method should be public, with void return type, and has either no parameter or one parameter of the specific event type (corresponding to the event listened) with <tt>@Listen</tt> in a composer. You should specify event listening rule in the annotation's element value. Then ZK will "wire" the method to the specified components for specified events. ZK provides various options to specify in the annotation, please refer to [[ZK Developer%27s Reference/MVC/Controller/Wire Event Listeners]].
 
An event listener method should be public, with void return type, and has either no parameter or one parameter of the specific event type (corresponding to the event listened) with <tt>@Listen</tt> in a composer. You should specify event listening rule in the annotation's element value. Then ZK will "wire" the method to the specified components for specified events. ZK provides various options to specify in the annotation, please refer to [[ZK Developer%27s Reference/MVC/Controller/Wire Event Listeners]].

Revision as of 08:03, 18 January 2013

Target Application

This chapter we will demonstrate a common scenario: collecting user input in form-based page. The target application looks as follows:

Tutorial-ch5-app.png

There is a personal profile form with 5 different fields. Click the "Save" button saves users input and click the "Reload" button loads previous saved data back to the form. We will show how to implement these functions in both MVC and MVVM approach.

MVC Approach

Under this approach, we implement all event handling and presentation logic in a composer instead of in <zscript/> and make a zul clear from codes. This approach make the responsibility of each role (Model, View, and Controller) more cohesive and allows you to control components directly. It is very intuitive and very flexible.

View

With the concept and technique we talked in last chapter, it should be easy to construct a form like user interface as follows. The zul file below is included in the Center of the Border Layout.

chapter5/profile-mvc.zul

<?link rel="stylesheet" type="text/css" href="/style.css"?>
<window apply="org.zkoss.tutorial.chapter5.mvc.ProfileViewController" 
	border="normal" hflex="1" vflex="1" contentStyle="overflow:auto">
	<caption src="/imgs/profile.png" sclass="fn-caption" label="Profile (MVC)"/>
	<vlayout>
		<grid width="500px">
			<columns>
				<column align="right" hflex="min"/>
				<column/>
			</columns>
			<rows>
				<row>
					<cell sclass="row-title">Account :</cell>
					<cell><label id="account"/></cell>
				</row>
				<row>
					<cell sclass="row-title">Full Name :</cell>
					<cell>
						<textbox id="fullName" constraint="no empty: Plean enter your full name" width="200px"/>
					</cell>
				</row>
				<row>
					<cell sclass="row-title">Email :</cell>
					<cell>
						<textbox id="email" 
						constraint="/.+@.+\.[a-z]+/: Please enter an e-mail address" width="200px"/>
					</cell>
				</row>
				<row>
					<cell sclass="row-title">Birthday :</cell>
					<cell><datebox id="birthday" constraint="no future" width="200px"/></cell>
				</row>
				<row>
					<cell sclass="row-title">Country :</cell>
					<cell>
						<listbox id="country" mold="select" width="200px">
							<template name="model">
								<listitem label="${each}" />
							</template>
						</listbox>
					</cell>
				</row>
				<row>
					<cell sclass="row-title">Bio :</cell>
					<cell><textbox id="bio" multiline="true" hflex="1" height="200px" /></cell>
				</row>
			</rows>
		</grid>
		<hlayout>
			<button id="saveProfile" label="Save"/>
			<button id="reloadProfile" label="Reload"/>
		</hlayout>
	</vlayout>
</window>
  • Line 4: Caption can be used to build compound header with image for a Groupbox and Window.
  • Line 5: Vlayout is a light-weight layout component which arranges child components vertically without splitter, align, pack support.
  • Line 13: Cell is used inside Row, Hbox, or Vbox for fully controlling style and layout.
  • Line 19, 26, 31: Specify constraint attribute of a input component can activate client-side validation feature. Then, the input component will show error message when input value doesn't match specified constraint rule. ''Textbox'' and ''Datebox'' has different syntax of constraint rule.
  • Line 49: Hlayout, like Vlayout, but arrange child components horizontally.

Display a Collection of Data

In the profile form, there is a drop-down list that contains a list of country.

Tutorial-ch5-collection.png
Country List

It is made by Listbox in "select" mold. The Listbox's data is a list of string provided by the composer and we use a Template to define how to render each object of the data list.

extracted from chpater5/profile-mvc.zul

...
				<row>
					<cell sclass="row-title">Country :</cell>
					<cell>
						<listbox id="country" mold="select" width="200px">
							<template name="model">
								<listitem label="${each}" />
							</template>
						</listbox>
					</cell>
				</row>
...
  • Line 5: Some components have multiple molds, and each mold has its own style.
  • Line 6 - 8: Template component can create its child components repeatedly upon the data model of parent component, Listbox, and doesn't has a corresponding visual widget itself. The ${each} is an implicit variable that you can use without declaration inside Template, it represents one object of the data list. We specify this variable at component attributes to control how to render data list. In our example, we just make it as a Listitem's label.


Now that let's see how the composer provides the data list to the Listbox. In ZK, all data components are designed to accept a separate data model that contains data to be rendered. You only have to provide such a data model and a data component will render the data as specified in Template. This increase the data model's reusability and decouple the data from a component's implementation. For a Listbox, we can provide a ListModelList

Initialize data for a Listbox

public class ProfileViewController extends SelectorComposer<Component>{
	...
	@Wire
	Listbox country;

	@Override
	public void doAfterCompose(Component comp) throws Exception{
		super.doAfterCompose(comp);
		
		ListModelList<String> countryModel = new ListModelList<String>(CommonInfoService.getCountryList());
		country.setModel(countryModel);
		
		refreshProfileView();
	}

	...
}
  • Line 10: Create a ListModelList object with a list of String
  • Line 11: Provide prepared data model object to the component by setModel().
  • Line 13: This method reloads the saved data to initialize components, and we will describe the detail in next section.

Handle Events in a Composer

The example application has 2 functions, save and reload, which are both triggered by clicking a button. In this section, we demonstrate the way to add an event listener defined in a composer in @Listen annotation instead of calling a method (mentioned in chapter 4).

Tutorial-ch5-save.png
Click "Save" button


An event listener method should be public, with void return type, and has either no parameter or one parameter of the specific event type (corresponding to the event listened) with @Listen in a composer. You should specify event listening rule in the annotation's element value. Then ZK will "wire" the method to the specified components for specified events. ZK provides various options to specify in the annotation, please refer to ZK Developer's Reference/MVC/Controller/Wire Event Listeners.

Listen "Save" button's clicking

public class ProfileViewController extends SelectorComposer<Component>{

	@Listen("onClick=#saveProfile")
	public void doSaveProfile(){
		...
	}
	...
}
  • Line 3: The @Listen will make doSaveProfile() be invoked when a user clicks a component whose id is "saveProfile".



Listen "Save" button's clicking

public class ProfileViewController extends SelectorComposer<Component>{

	//wire components
	@Wire
	Label account;
	@Wire
	Textbox fullName;
	@Wire
	Textbox email;
	@Wire
	Datebox birthday;
	@Wire
	Listbox country;
	@Wire
	Textbox bio;

	@Listen("onClick=#saveProfile")
	public void doSaveProfile(){
		UserCredential cre = authService.getUserCredential();
		User user = userInfoService.findUser(cre.getAccount());
		if(user==null){
			//TODO handle un-authenticated access 
			return;
		}
		
		//apply component value to bean
		user.setFullName(fullName.getValue());
		user.setEmail(email.getValue());
		user.setBirthday(birthday.getValue());
		user.setBio(bio.getValue());
		
		Set<String> selection = ((Selectable)country.getModel()).getSelection();
		if(!selection.isEmpty()){
			user.setCountry(selection.iterator().next());
		}else{
			user.setCountry(null);
		}
		
		userInfoService.updateUser(user);
		
		Clients.showNotification("Your profile is updated");
	}
	...
}
  • Line 20:
  • Line 27:
  • Line 32:
  • Line 41:


public class ProfileViewController extends SelectorComposer<Component>{


	//wire components
	@Wire
	Label account;
	@Wire
	Textbox fullName;
	@Wire
	Textbox email;
	@Wire
	Datebox birthday;
	@Wire
	Listbox country;
	@Wire
	Textbox bio;

	...
	@Listen("onClick=#reloadProfile")
	public void doReloadProfile(){
		refreshProfileView();
	}

	private void refreshProfileView() {
		UserCredential cre = authService.getUserCredential();
		User user = userInfoService.findUser(cre.getAccount());
		if(user==null){
			//TODO handle un-authenticated access 
			return;
		}
		
		//apply bean value to UI components
		account.setValue(user.getAccount());
		fullName.setValue(user.getFullName());
		email.setValue(user.getEmail());
		birthday.setValue(user.getBirthday());
		bio.setValue(user.getBio());
		
		((Selectable)country.getModel()).addToSelection(user.getCountry());
	}
}

MVVM Approach

Introduction