8.2. CRUD示例

CRUD几乎是所有应用都要涉及的部分。本示例是个极其普通的应用,登录后用户可以进行用户信息的增删查改。此示例并不想一下子就把OperaMasks的所有酷绚功能秀出。然而,它演示了OperaMasks中的LiteBean如何与页面进行打交道,使开发人员快速学会使用OperaMasks来进行开发。

此示例首页显示一个非常简单的表单,它只有两个输入字段和一个登录按钮。在表单上填写内容并提交,一旦输入数据被提交后就会在后台的UserDao持有的User列表进行查找,若用户名及密码验证成功,则跳转到用户列表页面上,在用户列表页面上可以对用户进行增删查改。

示例涉及OperaMasks特性主要包括IoVC(视图控制反转),事件机制,导航,校验,DataGrid展现,关于OperaMasks特性的介绍可以参看手册的其他部分。

示例界面如下图所示:

CRUD登录界面

图 8.3. CRUD登录界面


CRUD管理界面

图 8.4. CRUD管理界面


8.2.1. 了解代码

本示例由两个Facelets页面及两个与页面对应的LiteBean组成,还包括实体类User和它操作类UserDao与及权限过滤器LoginFilter,当然还有相关的配置文件。

8.2.1.1. LoginBean.java

LoginBean主要演示的是基本的IoVC应用、导航与及事件。

package org.operamasks;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpSession;

import org.operamasks.faces.annotation.Action;
import org.operamasks.faces.annotation.Bind;
import org.operamasks.faces.annotation.ManagedBean;
import org.operamasks.faces.annotation.ManagedBeanScope;
import org.operamasks.faces.annotation.ManagedProperty;

@ManagedBean(name = "loginBean", scope = ManagedBeanScope.REQUEST)
public class LoginBean {
	1@Bind
	private String name;

	@Bind
	private String password;

	2@ManagedProperty(value = "#{userDao }")
	private UserDao userDao;
	/**
	 * 登录成功则将username写到session中
	 * 
	 * @return 登录成功则返回页面,否则返回null跳转到当前页面
	 */
	3@Action
	public String login() {
		FacesContext ctxt = FacesContext.getCurrentInstance();
		for (User user : userDao.listAllUsers()) {
			if (name.equals(user.getName())
					&& password.equals(user.getPassword())) {
				HttpSession session = (HttpSession)ctxt.getExternalContext().getSession(true);
				session.setAttribute("username", name);
				4return "view:redirect:userManage.xhtml";
			}
		}
		5ctxt.addMessage(null, new FacesMessage("用户名不存在或者密码不正确!"));
		ctxt.renderResponse();
		return null;
	}
}
1

@Bind注解标注的成员变量与页面中指定相同 id 的构件进行绑定。如果在@Bind中不声明id,就以成员变量的名称作为匹配构件的id。此示例中的绑定的构件为login.xhtml中的id为name的文本输入框。

2

@ManagedProperty告诉OperaMasks引擎将userDao的实例注入到本地userDao成员变量,本示例userDao用于模拟数据库及其操作,实际应用中应将userDao的代码改为真实操作数据库的代码。

3

@Action注解标注的方法,响应指定id的构件事件。本示例中是绑定了id为login的登录按钮,当按钮onclick事件发生时响应此方法。

4

OperaMasks的导航方式是将用户要访问的页面直接返回并跳转,使用OperaMasks可以实现零配置的导航规则,当然你也可以在faces-config.xml文件里配置导航规则,关于OperaMasks的导航可以参看第 16 章 导航

1

向FacesContext中增加FacesMessage列表中添加提示消息,在login.xhtml页面中标签<h:messages globalOnly="true"></h:messages>接收此提示。

8.2.1.2. login.xhtml

<?xml version="1.0" encoding="UTF-8"?>
<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
	xmlns:layout="http://www.apusic.com/jsf/layout" xmlns:w="http://www.apusic.com/jsf/widget"
	xmlns:h="http://java.sun.com/jsf/html" renderKitId="AJAX">
	<w:head>
	<style>
		.x-info-message {
			color: red;
		}

		body {
			font-size: 12px;
			font-family: arial, 宋体;
		}
		
		table {
			font-size: 12px;
		}
	</style>
	</w:head>
	<w:page title="OperaMasks CRUD示例">
		<span style="font-size: 40px;">OperaMasks CRUD示例</span>
		<br/>
		<span style="font-size: 20px;">CRUD登录界面演示</span>
		<br/><br/>
		<w:panelBox style="height:100px;width:250px;">
			<w:form clientValidate="true">
				<layout:panelGrid columns="2">
					<h:outputText value="用户名:" />
					1<w:textField id="name" required="true" validateEvents="onblur" width="165">
						2<f:validateLength minimum="2"></f:validateLength>
					</w:textField>
					<h:outputText value="密码:" />
					<w:textField id="password" inputType="password" required="true" validateEvents="onblur" width="165">
						<f:validateLength minimum="2"></f:validateLength>
					</w:textField>
					<layout:cell colspan="2" align="center">
						3<w:button label="登录" id="login" />
					</layout:cell>
				</layout:panelGrid>
				<h:messages globalOnly="true"></h:messages>
			</w:form>
		</w:panelBox>
		<br />
		(初始用户名和密码分别为:<font style="font-size: 20px;font-weight: bold;">admin,admin</font>)
	</w:page>
</f:view>
1

id为name的输入框,与LoginBean的name属性对应。

2

<f:validateLength/>内嵌于<w:textField/>,提供对<w:textField/>的校验支持,<w:textField/>声明了校验时机onblur,在textField失去焦点的时候执行此校验。

3

id为login的登录按钮,与LoginBean的login方法对应。

在OperaMasks中,页面与LiteBean对应,用户无需关心如何两者之间的数据如何传递,只需要在后台LoginBean直接操作绑定的对象或者数据,如下图所示,用户只需编辑代码name属性,并标注@Bind注解,则对name设值后则可以直接显示到页面上;login方法上标@Action注解后,当id为login按钮响应后则可以直接调用,而无进行其他代码的编写。

LoginBean.java与login.xhtml

图 8.5. LoginBean.java与login.xhtml


8.2.1.3. UserManage.java

package org.operamasks;

import java.util.ArrayList;
import java.util.List;

import javax.faces.context.FacesContext;
import javax.servlet.http.HttpSession;

import org.operamasks.faces.annotation.Action;
import org.operamasks.faces.annotation.BeforeRender;
import org.operamasks.faces.annotation.Bind;
import org.operamasks.faces.annotation.DataModel;
import org.operamasks.faces.annotation.Inject;
import org.operamasks.faces.annotation.ManagedBean;
import org.operamasks.faces.annotation.ManagedBeanScope;
import org.operamasks.faces.annotation.ManagedProperty;
import org.operamasks.faces.annotation.SaveState;
import org.operamasks.faces.component.grid.impl.UIDataGrid;
import org.operamasks.faces.component.layout.impl.UIWindow;
import org.operamasks.faces.user.ajax.PartialUpdateManager;
import org.operamasks.faces.user.ajax.UpdateLevel;

@ManagedBean(name = "userManageBean", scope = ManagedBeanScope.REQUEST)
public class UserManageBean {
	
	@Inject
	private PartialUpdateManager update;
	
	1@ManagedProperty
	public static final String EMAIL_ADDRESS_REGEXP = "^[A-Za-z0-9_]+(?:[.-][A-Za-z0-9_]+)*@[A-Za-z0-9]+(?:[.-][A-Za-z0-9]+)*.[A-Za-z]{2,5}$";
	
	@ManagedProperty
	public static final String TELEPHONE_REGEXP = "^[0-9-]+$";
	

	// 注入对User实体的操作对象userDao
	@ManagedProperty(value = "#{userDao }")
	private UserDao userDao;

	// 显示在页面上的用户名
	@Bind
	private String username;

	// 标志用户操作
	private enum Operation {
		NONE, CREATE, UPDATE, DELETE, SEARCH
	}
	
	2@SaveState
	private Operation operation;

	// 查询类型,包括用户名,邮箱,电话号码
	@Bind
	@SaveState
	private int type;

	// 查询内容
	@Bind
	@SaveState
	private String content;

	// 从session中取出用户名
	3@BeforeRender
	public void beforeRender(boolean isPostback) {
		FacesContext ctxt = FacesContext.getCurrentInstance();
		HttpSession session = (HttpSession) ctxt.getExternalContext().getSession(true);
		Object name = session.getAttribute("username");
		if (name != null) {
			username = name.toString();
		}
	}

	// 注销,清空session中的username,并跳转到login.xhtml
	@Action(id = "logout")
	public String logout() {
		FacesContext ctxt = FacesContext.getCurrentInstance();
		HttpSession session = (HttpSession) ctxt.getExternalContext().getSession(true);
		session.removeAttribute("username");
		return "view:redirect:login.xhtml";
	}

	// 绑定页面dataGrid表格
	4@Bind
	private UIDataGrid grid;

	// dataGrid的数据模型
	5@DataModel(id = "grid")
	public List<User> getUsers() {
		if (operation == Operation.SEARCH) {
			if (content == null || "".equals(content)) {
				return userDao.listAllUsers();
			}
			List<User> result = new ArrayList<User>();
			result = findUser(type, content);
			operation = Operation.NONE;
			return result;
		} else {
			return userDao.listAllUsers();
		}
	}


	// 查询用户
	public List<User> findUser(int type, String content) {
		switch (type) {
		case 0:
			return userDao.findUserByName(content);
		case 1:
			return userDao.findUserByEmail(content);
		case 2:
			return userDao.findUserByTelephone(content);
		default:
			return null;
		}
	}

	// 查询按钮
	@Action(id = "search")
	public void search() {
		operation = Operation.SEARCH;
		grid.reload();
	}

	// 绑定页面弹出窗口
	6@Bind(id = "detailDialog")
	protected UIWindow detailDialog;

	@Bind
	private String name;

	@Bind
	private String password;

	@Bind
	private String sex;

	@Bind
	private String birthday;

	@Bind
	private String telephone;

	@Bind
	private String email;

	// 点击确认按钮,根据用户的Operation来确定是更新或者是创建User对象
	@Action(id = "ok")
	public void ok() {
		User user = new User(name, password, sex, birthday, telephone, email);
		if (operation == Operation.UPDATE) {
			userDao.update(user);
		} else {
			userDao.create(user);
		}
		detailDialog.close();
		grid.reload();
	}

	// 点击取消按钮,将编辑窗口关闭
	@Action(id = "cancel", immediate = true)
	public void cancel() {
		detailDialog.close();
	}

	// 增加
	7@Action(id = "create")
	public void create() {
		operation = Operation.CREATE;
		detailDialog.setTitle("增加用户");
		name = "";
		password = "";
		sex = "";
		birthday = "";
		telephone = "";
		email = "";
		update.markUpdate(true, UpdateLevel.Data, "userForm");
		detailDialog.show();
	}

	// 修改
	@Action(id = "update")
	public void update() {
		User user = getSelectedUser();
		if (user == null) {
			return;
		}
		operation = Operation.UPDATE;
		detailDialog.setTitle("修改用户");

		name = user.getName();
		password = user.getPassword();
		sex = user.getSex();
		birthday = user.getBirthday();
		telephone = user.getTelephone();
		email = user.getEmail();
		update.markUpdate(true, UpdateLevel.Data, "userForm");
		detailDialog.show();
	}

	// 删除
	@Action(id = "delete")
	public void delete() {
		User user = getSelectedUser();
		if (user == null) {
			return;
		}
		operation = Operation.DELETE;
		detailDialog.setTitle("删除用户");
		userDao.delete(user);
		grid.reload();
	}

	public User getSelectedUser() {
		Object object[] = grid.getSelectedValues();
		if ((object != null) && (object.length >= 1)) {
			User user = (User) object[0];
			return user;
		}
		return null;
	}

}
1

@ManagedProperty注解使得属性无需编写get和set方法也可以通过EL表达式来引用。

2

@SaveSate注解用于保存状态,这里保存用户的操作类型,对于create、search和update的采用不同的处理方法。

3

从session的属性中获取用户信息,显示到页面。OperaMasks提供对session的操作,当然还可以操作request及response等。

4

绑定id为grid的DataGrid构件,通过此构件,用户可以对DataGrid进行很多的操作,如create或者update方法中调用到DataGrid构件的getSelectedValues()方法等。

5

@DataModel为DataGrid的数据模型,此数据模型为DataGrid提供数据,<w:dataGrid>下定义的<w:outputColumn>标签根据些DataModel来进行数据表格的渲染。

6

绑定detailDialog弹出窗口,通过这个窗口对数据进行增加及修改。

7

绑定事件,与LoginBean中的login方法一样。

8.2.1.4. userManage.xhtml

<?xml version="1.0" encoding="UTF-8"?>
<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
	xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
	xmlns:h="http://java.sun.com/jsf/html" xmlns:ajax="http://www.apusic.com/jsf/ajax"
	renderKitId="AJAX">
	<w:head cache="false">
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
		<style>

		body {
			font-size: 12px;
			font-family: arial, 宋体;
		}
			table {
				font-size:12px;
			}
		</style>
		<script>
		 	//<![CDATA[
		 		function formatSex(v,m,r,rIndex){
		 			if(v == 0){
		 				return "男";
		 			}else {
		 				return "女";
		 			}
		 		}
		     //]]>
		 </script>
	</w:head>
	<w:page title="OperaMasks CRUD示例">
		<span style="font-size: 40px;">OperaMasks CRUD示例</span><br/>
		<span style="font-size: 20px;">CRUD管理界面演示</span><br/>
		<br/>
		欢迎你:<h:outputText id="username"></h:outputText><h:commandLink id="logout" style="font-size:14px;" value="注销"></h:commandLink>
		<br/>
		<br/>
		<w:form>
			<layout:panelGrid columns="3">
				<w:combo id="type">
					<f:selectItem itemValue="0" itemLabel="用户名"/>
					<f:selectItem itemValue="1" itemLabel="邮箱"/>
					<f:selectItem itemValue="2" itemLabel="电话"/>
				</w:combo>
				<w:textField id="content"></w:textField>
				<w:button id="search" label="查询" />
			</layout:panelGrid>
		</w:form>
		<w:form>
			1<w:dataGrid id="grid" paged="true" rows="5" toolBarPosition="bottom" height="200" width="600">
				<w:toolBar target="grid">
					<w:button id="create" label="增加" />
					<w:button id="update" label="修改" />
					<w:button id="delete" label="删除" />
				</w:toolBar>
				<w:outputColumn id="name" header="用户名"></w:outputColumn>
				<w:outputColumn id="password" header="密码"></w:outputColumn>
				<w:outputColumn id="sex" header="性别" clientFormatter="formatSex"></w:outputColumn>
				<w:outputColumn id="birthday" header="生日"></w:outputColumn>
				<w:outputColumn id="telephone" header="电话"></w:outputColumn>
				<w:outputColumn id="email" header="邮箱"></w:outputColumn>
			</w:dataGrid>
		</w:form>
		<w:form id="userForm" clientValidate="true">
			2<layout:window modal="true" id="detailDialog" width="210" height="225">
				<layout:panelGrid columns="2">
					<h:outputText value="用户名:"></h:outputText>
					<w:textField id="name" required="true" validateEvents="onblur">
						<f:validateLength minimum="2"></f:validateLength>
					</w:textField>
					<h:outputText value="密码:"></h:outputText>
					<w:textField id="password" required="true" validateEvents="onblur">
						<f:validateLength minimum="2"></f:validateLength>
					</w:textField>
					<h:outputText value="性别:"></h:outputText>
					<w:combo width="136" id="sex" required="true">
						<f:selectItem itemValue="0" itemLabel="男" />
						<f:selectItem itemValue="1" itemLabel="女" />
					</w:combo>
					<h:outputText value="生日"></h:outputText>
					<w:dateField id="birthday" width="136"></w:dateField>
					<h:outputText value="电话:"></h:outputText>
					<w:textField id="telephone">
						3<w:validateRegexp message="请输入正确的电话号码" pattern="#{userManageBean.TELEPHONE_REGEXP }"></w:validateRegexp>
					</w:textField>
					<h:outputText value="邮箱:"></h:outputText>
					<w:textField id="email" required="true" validateEvents="onblur">
						<w:validateRegexp message="请输入正确的邮箱地址" pattern="#{userManageBean.EMAIL_ADDRESS_REGEXP }"></w:validateRegexp>
					</w:textField>
					<w:button label="确定" id="ok" />
					<w:button label="取消" id="cancel" />
				</layout:panelGrid>
			</layout:window>
		</w:form>
		<ajax:conversationActivator />
	</w:page>
</f:view>
1 页面标志id为"grid"的dataGrid构件,在UserManageBean可以直接操作dataGrid。
2 页面标志id为"detailDialog"的window构件,在UserManageBean可以直接操作window。
3

<w:validateRegexp>为OperaMasks提供的校验方式,通过pattern指定匹配表达式,如果输入值与匹配表达式不匹配,则将错误信息返回页面,OperaMasks 2.3 及其后续版本中提供加强的客户端校验机制,关于校验的更多信息,请参考第 17 章 校验与转换

在这个页面中,可以看到DataGrid是一个功能强大,并且非常绚丽的构件,通过构件的绑定,可以直接操作构件的方法。在增加,修改,删除等按钮中,我们将DataGrid的数据模型改变,并调用DataGrid的reload()方法,将DataGrid刷新。

UserManage.java与userManage.xhtml

图 8.6. UserManage.java与userManage.xhtml


8.2.1.5. operamasks.xml

faceletes页面 是怎样和 LiteBean 关联起来的呢?在 WEB-INF下有一个 operamasks.xml:

<operamasks-config xmlns="http://www.operamasks.org/IoVC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.operamasks.org/IoVC http://www.operamasks.org/schema/operamasks.xsd ">
  <view-mapping>
    <url-pattern>*</url-pattern>
    <model-bean>#{~view}Bean</model-bean>
  </view-mapping>
</operamasks-config>

此处配置是指:任何一个页面,所对应的LiteBean是:将页面首字母小写,再加上一个后缀“Bean”。换言之:login.xhtml对应的LiteBean名称为:loginBean。关于配置文件的更多信息,请参考第 29 章 配置文件详解

8.2.2. 总结

OperaMasks中LiteBean与页面一一对应,构件与后台LiteBean的属性也是一一对应的,在LiteBean操作的是页面的构件,操作起来非常的简单。

在OperaMasks中,提供了IoVC的支持,只要构件上标注了唯一的id属性,并在后台LiteBean的属性写上@Bind注解(如果属性名与构件id不同,则需要在@Bind注解上标明id属性)实现绑定,这是OperaMasks框架中实现的,用户直接使用。当然,你也可以通过EL表达式来进行绑定,如:value="#{userManageBean.name}",

@Action与button绑定在一起,当按钮按下时,响应这个方法,只配置@Action即可实现方法的调用。

OperaMasks的事件中通过return返回某个页面值实现页面的导航,关于OperaMasks的导航方式可以参考第 16 章 导航

登录成功后的页面包含DataGrid构件,DataGrid拥有和EXT一样的风格,非常的优美。在OperaMasks中,除DataGrid外,还有很多很漂亮的构件,如Tree等,这些构件的实现使开发人员的开发简单到只要拖动构件到页面上就可以完成很大部分的工作,使开发人员的开发真正的转移到业务逻辑上的开发。

如果你开发过C/S架构的程序,更容易理解页面和后台Bean之间的关系,它们的关系相当于窗体和对应的窗体类一样,只不过OperaMasks是基于Web的。