前言
本文是对ZK开发过程中必须掌握的关键知识点的总结,针对目前对新版本zk-6.5.2
关于ZK是什么参见前一篇博客 《ZK(The leading enterprise Ajax framework)入门指南》 http://blog.csdn.net/daquan198163/article/details/9304897
1. 页面布局
ZK具有非常高的开发效率(以至于可以取代HTML用来完成高质量的Fast Prototyping),最主要缘自它采用的页面布局技术——ZUL;
采用XML语言以声明式的方式创建用户界面——XML UI技术并不是ZK的独创,AndroidUI、JavaFX、MicrosoftSilverlight和MozillaXUL等开发
框架都采用了这种技术,这是目前最先进的构造用户界面技术;
ZUL只不过是为所有ZK组件指定了一个对应的XML Element,然后通过这些Element以声明式的方式定义页面由哪些组件以什么样的方式构成。
相对于Java编程式方式,这种声明式方式的优点十分明显:
- 直观:ZUL代码结构与页面布局完全一致(而且必须一致),ZUL元素的嵌套关系就是页面组件的嵌套关系,ZUL元素的前后并列关系就
- 是页面组件的前后摆放;而Java编程方式与最终页面却没有这种一致性;
- 代码简洁:由于XML在表达页面布局时语义的先天优势(一致性),同样的页面用ZUL比Java代码量要少得多;
直观、简洁的代码意味着容易理解、容易编写、容易修改维护、不容易出错,因此带来开发效率上的巨大优势。
值得注意的是,上述ZUL相对于Java编程的优势也适用于JS,比如EXT、DOJO等JS UI框架。
1.1. 布局组件
1.1.1. 东西南北中布局Borderlayout
Borderlayout将屏幕划分成东西南北中五个区域,如下图所示,其灵活性可以实现绝大多数系统的首页整体布局。
首先纵向看,要指定N和S的高度,剩下中间部分就是W C E的高度;然后水平看,N S宽度百分百,中间部分指定W E的宽度后,
剩下的部分就是C了。
由于Center大小不是自己决定的,当里面摆放组件过多显示不全时,可以指定autoscroll="true"产生滚动条。
1.1.2. 基本布局
Borderlayout适合于实现大的页面结构布局,在页面局部最常见的需求就是如何将各种组件有序的摆放:有时需要水平摆放有时需要垂直摆放,
还要考虑居中居左居右问题、摆不下的问题(摆放不下时要有滚动条);
ZK提供了更细粒度的布局组件——hBox/vBox/hlayout/vlayout用于实现这些常见需求;
hlayout和hBox(h代表horizon水平)用于水平布局,vlayout和vBox(v代表vertical垂直)用于垂直布局,它们是最常用的的容器组件,里面可以
放任意多的组件;
-
HBox and VBox provide more functionalities such as splitter,align and pack.
-
However,their performance is slower,
-
so it is suggested to use Hlayout and Vlayout if you'd like to use them a lot in a UI,unless you need the features that only HBox and VBox support.
1.2. 各种容器组件
1.2.1. groupBox
企业应用往往需要在一个页面中显示大量信息或组件,如果随意摆放或只是简单的罗列,会让用户感觉很混乱难以使用,用户体验不好。
groupBox顾名思义就是用来分组布局的组件,它就像收纳盒一样可以把页面组件分门别类的摆放,标题栏可以清晰的标识分类名称,而且可收缩。
//create a window programmatically and use it as a modal dialog.
Window window = (Window)Executions.createComponents(
"/widgets/window/modal_dialog/employee_dialog.zul"
,
null
,
null
);
window.doModal();
|
<
window
id
=
"modalDialog"
title
=
"Coffee Order"
border
=
"normal"
width
=
"460px"
apply
=
"demo.window.modal_dialog.EmployeeDialogController"
position
=
"center,center"
closable
=
"true"
action
=
"show: slideDown;hide: slideUp"
>
<
vlayout
>
………………
</
vlayout
>
</
window
>
|
1
2
3
4
5
6
7
8
9
10
11
|
public
class
SearchController
extends
SelectorComposer<Component> {
public
void
search(Event event){
Button searchButton = (Button) event.getTarget();
String keyword = keywordBox.getValue();
List<Car> result = carService.search(keyword);
}
|
<
window
id
=
"mywin"
>
<
button
label
=
"Save"
forward
=
"onSave"
/>
<
button
label
=
"Cancel"
forward
=
"onCancel"
/>
<
listitem
self
=
"@{each=p1}"
forward
=
"onDoubleClick=mywin.onDetail(each.prop1)"
>
</
window
>
|
1
2
3
4
5
|
<
comboBox
selectedItem
=
"@bind(fx.userTypeForCc)"
readonly
=
"true"
model
=
"@load(vm.userTypeForCcList)"
itemRenderer
=
"com.xxx.ctrl.renderer.ComboitemRenderer4UserTypeCc"
/>
class ComboitemRenderer4UserTypeCc implements ComboitemRenderer<
USER_TYPE_FOR_CC
> {
@Override
public void render(Comboitem item,USER_TYPE_FOR_CC data,int index) throws Exception {
item.setLabel(data.getText());
}
}
|
<
grid
model
=
"@bind(vm.orders) @template(vm.type='foo'?'template1':'template2')"
>
<
template
name
=
"template1"
>
<!-- child components -->
</
template
>
<
template
name
=
"template2"
>
<!-- child components -->
</
template
>
</
grid
>
<
grid
model
=
"@bind(vm.orders) @template(each.type='A'?'templateA':'templateB')"
>
<
template
name
=
"templateA"
>
<!-- child components -->
</
template
>
<
template
name
=
"templateB"
>
<!-- child components -->
</
template
>
</
grid
>
|
form
=
"@id('fx') @load(vm.user) @save(vm.user,before='submit') @validator(vm.validator)"
>
………………
………………
<
button
id
=
"btn_submit"
label
=
"提交"
onClick
=
"@command('submit')"
/>
………………
|
<
menuitem
label
=
"创建Xxx"
onClick
=
"@command('openXxxForm',id=each.id)"
/>
|
@Command
public
void
openXxxForm(
@BindingParam
(
"id"
) String roleId) {
|
<
window
id
=
"winEditUser"
apply
=
"org.zkoss.bind.BindComposer"
viewmodel
=
"@id('vm') @init('com.xxx.ctrl.UserFormDialogCtrl')"
validationMessages
=
"@id('vmsgs')"
form
=
"@id('fx') @load(vm.user) @save(vm.user,before='submit') @validator(vm.validator)"
>
<
label
value
=
"@load(vmsgs['password'])"
sclass
=
"red"
/>
<
label
value
=
"@load(vmsgs['contactInfo.email'])"
sclass
=
"red"
/>
………………
|
public
org.zkoss.bind.Validator getValidator() {
return
validator;
}
|
<
listBox
id
=
"dataListBox"
mold
=
"paging"
pageSize
=
"20"
multiple
=
"true"
checkmark
=
"false"
emptyMessage
=
"搜索结果为空"
width
=
"100%"
vflex
=
"true"
>
<
listhead
menupopup
=
"auto"
width
=
"100%"
sizable
=
"false"
>
………………
</
listhead
>
<
template
name
=
"model"
>
<
listitem
style
=
"cursor:hand;cursor:pointer;"
>
<
label
value
=
"${forEachStatus.index+1}"
/>
<
label
value
=
"${each.userName}"
/>
|
List carsModel =
new
ListModelList<Car>(carService.findAll());
dataListBox.setModel(carsModel);
|
<
hlayout
height
=
"90%"
vflex
=
"true"
>
<!-- 设置vflex="true"否则没有垂直滚动条,导致显示不全;设置hflex="true"否则会出现讨厌的水平滚动条 -->
<
tree
id
=
"orgTree"
vflex
=
"true"
hflex
=
"true"
height
=
"100%"
width
=
"100%"
model
=
"@bind(vm.organTreeModel)"
multiple
=
"false"
checkmark
=
"false"
zclass
=
"z-tree"
>
<
treecols
>
<
treecol
hflex
=
"3"
label
=
"描述"
/>
</
treecols
>
<
template
name
=
"model"
>
<
treeitem
open
=
"@load(each.data.open)"
selected
=
"@bind(each.data.selected)"
>
<
treerow
onClick
=
"@command('selectOrganNode',organId=each.data.id)"
>
<
treecell
label
=
"${each.data.organName}"
/>
<
treecell
label
=
"${each.data.description}"
/>
<
treecell
label
=
"${each.data.userAmount}"
/>
</
treerow
>
</
treeitem
>
</
template
>
</
tree
>
</
hlayout
>
|
<
treerow
onRightClick
=
"@command('openTreeMenu',paramEvent=event,reportId=each.data.id,image=each.data.image)"
>
|
1
2
3
4
5
6
7
8
|
@Command
public
void
openTreeMenu(
@BindingParam
(
"paramEvent"
) Event paramEvent,
@BindingParam
(
"reportId"
) String reportId,
@BindingParam
(
"image"
) String image) {
if
(StringUtils.isEmpty(image)) {
reportIdForRightClick = reportId;
}
}
|
@VariableResolver
(DelegatingVariableResolver.
class
)
public
class
AbcCtrl
extends
SelectorComposer<Window> {
@WireVariable
private
AbcService abcService;
|
<
dependency
org
=
"org.zkoss.zk"
name
=
"zkspring-security"
rev
=
"3.1.1"
conf
=
"compile;runtime"
transitive
=
"false"
/>
<
dependency
org
=
"org.springframework.security"
name
=
"spring-security-core"
rev
=
"3.1.4.RELEASE"
conf
=
"compile;runtime"
/>
<
dependency
org
=
"org.springframework.security"
name
=
"spring-security-acl"
rev
=
"3.1.4.RELEASE"
conf
=
"compile;runtime"
/>
<
dependency
org
=
"org.springframework.security"
name
=
"spring-security-taglibs"
rev
=
"3.1.4.RELEASE"
conf
=
"compile;runtime"
/>
<
dependency
org
=
"org.springframework.security"
name
=
"spring-security-config"
rev
=
"3.1.4.RELEASE"
conf
=
"compile;runtime"
/>
<
dependency
org
=
"org.springframework.security"
name
=
"spring-security-web"
rev
=
"3.1.4.RELEASE"
conf
=
"compile;runtime"
/>
|
<
listener
>
<
listener-class
>org.springframework.security.web.session.HttpSessionEventPublisher</
listener-class
>
</
listener
>
……………………
<
filter
>
<
filter-name
>springSecurityFilterChain</
filter-name
>
<
filter-class
>org.springframework.web.filter.DelegatingFilterProxy</
filter-class
>
</
filter
>
<
filter-mapping
>
<
filter-name
>springSecurityFilterChain</
filter-name
>
<
url-pattern
>/*</
url-pattern
>
</
filter-mapping
>
|
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
beans:beans
xmlns
=
"http://www.springframework.org/schema/security"
xmlns:beans
=
"http://www.springframework.org/schema/beans"
xmlns:xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:zksp
=
"http://www.zkoss.org/2008/zkspring/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/security
http://www.zkoss.org/2008/zkspring/security
>
<
http
auto-config
=
'true'
access-denied-page
=
"/error.html"
>
<
intercept-url
pattern
=
"/images/**"
access
=
"IS_AUTHENTICATED_ANONYMOUSLY"
/>
<
intercept-url
pattern
=
"/login.html*"
access
=
"IS_AUTHENTICATED_ANONYMOUSLY"
/>
<
intercept-url
pattern
=
"/pages/admin/**"
access
=
"ROLE_ADMIN"
/>
<
intercept-url
pattern
=
"/pages/**"
access
=
"IS_AUTHENTICATED_ANONYMOUSLY"
/>
<
intercept-url
pattern
=
"/**"
access
=
"IS_AUTHENTICATED_ANONYMOUSLY"
/>
<
form-login
login-page
=
"/login.html"
authentication-failure-url
=
"/login.html?login_error=1"
default-target-url
=
"/main.html"
always-use-default-target
=
"true"
/>
<!-- Following is list of ZK Spring Security custom filters. They needs to be exactly in the same order as shown below
in order to work. -->
<
custom-filter
ref
=
"zkDesktopReuseFilter"
position
=
"FIRST"
/>
<
custom-filter
ref
=
"zkDisableSessionInvalidateFilter"
before
=
"FORM_LOGIN_FILTER"
/>
<
custom-filter
ref
=
"zkEnableSessionInvalidateFilter"
before
=
"FILTER_SECURITY_INTERCEPTOR"
/>
<
custom-filter
ref
=
"zkLoginOKFilter"
after
=
"FILTER_SECURITY_INTERCEPTOR"
/>
</
http
>
<
authentication-manager
>
<
authentication-provider
>
<
user-service
properties
=
"classpath:/properties/security-users.properties"
/>
</
authentication-provider
>
</
authentication-manager
>
<
zksp:zk-event
login-template-close-delay
=
"1"
path-type
=
"ant"
>
<
zksp:intercept-event
event
=
"onClick"
path
=
"//**/cmdBtn_*"
access
=
"ROLE_ADMIN"
/>
<
zksp:intercept-event
event
=
"onClick"
path
=
"//**/menu_*"
access
=
"ROLE_ADMIN"
/>
<
zksp:intercept-event
event
=
"onClick"
path
=
"//**/treemenu_*"
access
=
"ROLE_ADMIN"
/>
<
zksp:intercept-event
event
=
"onClick"
path
=
"//**/btn_*"
access
=
"ROLE_USER"
/>
<
zksp:intercept-event
path
=
"/**"
access
=
"IS_AUTHENTICATED_ANONYMOUSLY"
/>
<
zksp:form-login
login-page
=
"/login.html"
/>
</
zksp:zk-event
>
</
beans:beans
>
|
<
language-config
>
<
addon-uri
>/WEB-INF/lang-addon.xml </
addon-uri
>
</
language-config
>
|
<
component
>
<
component-name
>button</
component-name
>
<
extends
>button</
extends
>
<
property
>
<
property-name
>autodisable</
property-name
>
<
property-value
>self</
property-value
>
</
property
>
</
component
>
|
<
library-property
>
<
name
>org.zkoss.theme.preferred</
name
>
<
value
>sapphire</
value
>
</
library-property
>
|
<listitem label=
"语言/Local"
value=
""
/>
<listitem label=
"English"
value=
"en"
/>
</listBox>
@Listen
(
"onSelect = #localSelector"
)
public
void
onSelectLocal(Event event) {
Object localName = ((ListBox) event.getTarget()).getSelectedItem().getValue();
logger.debug(
"选择语言区域【"
+ localName +
"】"
);
CookieUtils.setLocal(Executions.getCurrent(),(String) localName);
Locale locale = Locales.getLocale((String) localName);
Executions.getCurrent().getSession().setAttribute(Attributes.PREFERRED_LOCALE,locale);
Executions.sendRedirect(
null
);
}
|
<
listener
>
<
listener-class
>com.xxx.base.LocalInterceptor</
listener-class
>
</
listener
>
|
public
class
LocalInterceptor
implements
RequestInterceptor {
@Override
public
void
request(org.zkoss.zk.ui.Session sess,Object request,Object response) {
String localName = CookieUtils.getLocal((HttpServletRequest) request);
Locale locale = Locales.getLocale(localName);
((HttpServletRequest) request).getSession().setAttribute(Attributes.PREFERRED_LOCALE,locale);
}
}
public
class
CookieUtils {
/**
*/
static
String THEME_COOKIE_KEY =
"zktheme"
;
static
String LOCAL_COOKIE_KEY =
"zkLocal"
;
public
static
String getLocal(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if
(cookies ==
null
)
return
""
;
for
(
int
i =
0
; i < cookies.length; i++) {
Cookie c = cookies[i];
if
(LOCAL_COOKIE_KEY.equals(c.getName())) {
String theme = c.getValue();
if
(theme !=
null
)
return
theme;
}
}
return
""
;
}
public
static
void
setLocal(Execution exe,String localName) {
Cookie cookie =
new
Cookie(LOCAL_COOKIE_KEY,localName);
cookie.setMaxAge(
60
*
60
*
24
*
30
);
// store 30 days
String cp = exe.getContextPath();
// if path is empty,cookie path will be request path,which causes problems
if
(cp.length() ==
0
) {
cp =
"/"
;
}
cookie.setPath(cp);
((HttpServletResponse) exe.getNativeResponse()).addCookie(cookie);
}
}
|
welcome=Welcome
theme.sapphire=sapphire
theme.silvertail=silvertail
|
welcome=欢迎
theme.sapphire=蓝色
theme.silvertail=银灰
|
<
listitem
label
=
"${labels.theme.sapphire}"
value
=
"sapphire"
/>
|
1
2
3
4
5
6
7
8
9
10
11
|
@Command
@NotifyChange
({
"boardAttachmentList"
,
"boardAuditInfoList"
})
public
void
boardUploadFile(
@ContextParam
(ContextType.TRIGGER_EVENT) UploadEvent event)
throws
IOException {
Media media = event.getMedia();
QualityPlanAttachment qualityPlanAttachment =
new
QualityPlanAttachment();
qualityPlanAttachment.setFileName(media.getName());
qualityPlanAttachment.setFileSize((
long
) media.getByteData().length);
qualityPlanAttachment.setContent(media.getByteData());
|
@Command
public
void
boardDownloadFile(
@BindingParam
(
"fileId"
) String fileId)
throws
UnsupportedEncodingException {
………………
String fileName = attachment.getFileName();
byte
[] content=attachment.getByteArray();
Filedownload.save(content,ZkUtils.encodingFileName(fileName));
}
public
static
String encodingFileName(String fileName)
throws
UnsupportedEncodingException {
HttpServletRequest httpRequest = (HttpServletRequest) Executions.getCurrent().getNativeRequest();
String browserName = Servlets.getBrowser(httpRequest);
if
(StringUtils.equalsIgnoreCase(
"gecko"
,browserName)) {
//firefox
fileName =
new
String(fileName.getBytes(
"UTF-8"
),
"ISO8859-1"
);
}
else
{
//ie浏览器
fileName = URLEncoder.encode(fileName,
"UTF-8"
);
}
return
fileName;
}
|
<
dependency
org
=
"org.zkoss.zkforge"
name
=
"ckez"
rev
=
"3.6.4.0"
conf
=
"runtime"
/>
|
<
ckeditor
toolbar
=
"Basic"
value
=
"@bind(fx.content)"
hflex
=
"true"
width
=
"90%"
height
=
"95px"
/>
|
<?
component
name
=
"progressBar"
extends
=
"hlayout"
class
=
"com.xxx.component.ProgressBarHlayout"
?>
<
progressBar
progress
=
"@load(vm.plan1.progressPercentageValue)"
widthValue
=
"120"
height
=
"9px"
/>
|
public
class
ProgressBarHlayout
extends
Hlayout
implements
IdSpace {
private
static
final
int
VALUE_LABEL_WIDTH =
0
;
//30
@Wire
Div progressDiv;
@Wire
Div grayDiv;
String defaultStyle =
"position:absolute;left:0px;z-index:1;background:"
;
public
ProgressBarHlayout() {
Executions.createComponents(
"/pages/common/progressBar.zul"
,
this
,
null
);
Selectors.wireVariables(
this
,Selectors.newVariableResolvers(getClass(),Hlayout.
class
));
Selectors.wireComponents(
this
,
false
);
this
.setSpacing(
"0"
);
}
………………
public
void
setProgress(
int
progress) {
progressDiv.setWidth(progress * (widthValue - ProgressBarHlayout.VALUE_LABEL_WIDTH) /
100
+
"px"
);
this
.setTooltiptext(progress +
"%"
);
// progeressValueLabel.setValue(progress + "%");
// progeressValueLabel.setWidth(ProgressBarHlayout.VALUE_LABEL_WIDTH + "px");
// progeressValueLabel.setStyle("font-size:11px");
if
(progress <=
30
) {
progressDiv.setStyle(defaultStyle +
"#CD3D38;"
);
}
else
if
(progress >=
80
) {
progressDiv.setStyle(defaultStyle +
"#69CD4B;"
);
}
else
{
progressDiv.setStyle(defaultStyle +
"#CF9E25;"
);
}
}
|
<
zk
>
<!-- <label id="progeressValueLabel" vflex="true" /> -->
<
div
id
=
"progressDiv"
style
=
"position:absolute;left:0px;z-index:1;background:;"
/>
<
div
id
=
"grayDiv"
style
=
"position:relative;top:0px;left:0px;background:#D5CCBE;"
/>
</
zk
>
|
声明:
<?
component
name
=
"myImage"
class
=
"XX.MyImage"
?>
定义组件:
public class MyImage extends Image implements AfterCompose {
public void setMycontent(byte[] mycontent) {
if (null!=mycontent) {
try {
this.setContent(new AImage("t",mycontent));
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用组件:
<
myImage
mycontent
=
"@load(item.photo)"
width
=
"300px"
height
=
"300px"
/>
|
<
include
chartInfo
=
"@load(node)"
hflex
=
"true"
vflex
=
"true"
width
=
"@load(node.BoxWidthPx)"
src
=
"@load('/pages/common/componentAbc.zul')"
/>
|
import
org.zkoss.bind.annotation.Init;
………………
@VariableResolver
(DelegatingVariableResolver.
class
)
public
class
ComponentAbc {
@Init
public
void
init(
@ExecutionArgParam
(
"chartInfo"
) IndicatorChartInfo chartInfo,
@ExecutionArgParam
(
"maxMold"
) Boolean maxMold) {
this
.chartInfo = chartInfo;
this
.maxMold = maxMold;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
protected
static
ZatsEnvironment env;
@BeforeClass
public
static
void
init() {
env =
new
DefaultZatsEnvironment(
"./zats"
);
//加载zats目录下的web.xml
env.init(
"./web"
);
}
@AfterClass
public
static
void
end() {
Zats.end();
}
@After
public
void
after() {
Zats.cleanup();
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@H_489_4046@
34
|
import
java.util.List;
import
junit.framework.Assert;
import
org.apache.log4j.Logger;
import
org.junit.Test;
import
org.zkoss.zats.mimic.Client;
import
org.zkoss.zats.mimic.ComponentAgent;
import
org.zkoss.zats.mimic.DesktopAgent;
public
class
HomeTest
extends
BaseZatsTestCase {
Logger logger = LoggerUtil.getLogger();
@Test
public
void
testHome() {
Client client = env.newClient();
DesktopAgent desktop = client.connect(
"/pages/home.zul"
);
//打开 |