ExtJS4.2 - 从 Hello World 到 自定义组件 - 01
经验、概述、项目搭建、国际化、HelloWorld、布局
—— 为爱女伊兰而奋斗
——少走弯路,简单才是王道
1. 写在前面
我接触ExtJS已有两年多时间,不过之前一直用ExtJS3,近几日因为工作需要,才开始使用ExtJS4,鉴于目前手头工作不多,故将一些感受写下,希望能对其他朋友有所帮助。
我在2011年前都没有系统的使用过WEB前端UI框架,其实那会儿,dwr、extjs、jquery-ui都是交替红火着,不过我是个懒惰而又保守的人,加之自己之前也用 html+css+jsavascript+el+tag 自己写过一些东西,也就没去碰这些东西,只是出于项目需要,零散的用过一些UI组件,如日历组件、树组件等。
2011年,作为一个项目的技术架构师,应客户的要求,前端必须采用ExtJS,于是我就硬着头皮上了。初次接触ExtJS的感觉,是完全颠覆了传统WEB前端开发方式,JSP页面上空空如也,内容都在js文件中,让人大脑发蒙。从 Hello World 一路写下去,布局、单表维护、树、选择型弹窗、复杂UI,写了两个星期,才算适应了这种开发方式,终于恍然大悟,明白应该如何使用ExtJS4进行WEB前端开发。
2013年初,本来想接触ExtJS4,然而,另一个项目要求前端必须采用JQuery Easy UI,然后就把ExtJS4丢到一边去了。在此我不得不赞赏JQuery Easy UI这个UI框架,可谓小身材大智慧,几十MB的体积,却包含了强大的功能。我只花了两天,就搭出了框架:分页搜索、查删增改、多选型下拉列表、树型下拉列表、表格型下拉列表、自定义验证、菜单、导航、布局、复杂UI;并且JQuery Easy UI的开发模式也比较吻合传统WEB前端开发方式,我对它非常认可,因此打算放弃ExtJS了。不过在此我还是必须指出,与Ext相比,JQuery Easy UI在稳定性,界面的专业化程度方面,还是稍逊一筹,不过,它依然是非常好的一个WEB前端UI组件框架,本人大力推荐!
几天前,由于一个项目的客户要求采用ExtJS4,于是我终于捡起这个东西,起初发现它的代码与ExtJS3比较,似乎改变很大,但是深入进去看看,其实架构还是在那里,只是很多类、组件被重构,但是代码更精简了一些。在呈现后的页面上审查元素,发现元素渲染方面也有了优化。比如一个表格的单元格,之前包了几层div,现在就是1个td加1个div。由于有了ExtJS3的基础,因此花了3天,我就从HelloWorld开始,一步步做到封装了一个查删增改的组件。
在此我想分享一下自己使用ExtJS做开发的一点经验:
1. 首先有心理准备,从传统WEB页面开发方式转为使用ExtJS方式,类似于从 C 开发转为 Java 开发,ExtJS方式是面向对象、组件化的,JSP文件只用于引入资源,页面上呈现的任何元素,都是ExtJS的组件,必须一个个创建,然后拼装组合。
一般而言,我们页面上的元素是这样组织的
视图
面板
组件
此外,一个弹出窗口的元素则是这样组织的
窗口
面板
组件
2. 此外,如果希望利用ExtJS的方便和美观,却又指望它速度快,那是不现实的。友情提示:谷歌浏览器解析ExtJS是最快的,最差的是IE浏览器。
3. 开发第一步,对ExtJS4做一个概览,也就是说,了解它是什么、适应于哪些场景、整体结构是怎样的,有了这个全局观念,开发中就心中有数。千万不要一开始就让自己纠结于细节中。
4. 然后应该开始着手写程序,从 Hello World 开始,不要嫌简陋,一步一步来,目的是大致了解使用ExtJS4开发的方式。不至于说起来头头是道,一动手就抓瞎。不过需要注意的是,ExtJS4的demo,多是在 Ext.onReady 中一路写下来,初学者看了,往往写了一个页面,到下一个页面,就不知道如何把这个页面的东西移过去。因此,我建议采用代码切割的方式,更容易从全局把握。
5. 布局、表格、表单、分页及查删增改、树型及表格型下拉列表、菜单、导航,这些都一一写过,就会有所感觉
6. 之后可以开始写自定义类、组件,扩展Ext的一些东西
7. 通过ExtJS4的API了解常用类、组件,了解其属性、方法、事件
可以看到,一直到7,才开始进入ExtJS4的细节了解,我再次强调,一定要从全局开始,逐步深入局部细节,并且,作为开发人员,绝大部分细节不是你需要了解的,开发中需要的、常用的,才需要认真研究一番。
2. 开发前
2.1. 下载
1.下载ext-4.2.1-gpl
http://cdn.sencha.com/ext/gpl/ext-4.2.1-gpl.zip
2.下载extjs4帮助文档
3.下载 org.json.jar
2.2. 配置tomcat的server.xml文件
主要是配置:URIEncoding , 防止get访问时中文参数出错
<Connector URIEncoding="UTF-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
2.3. 项目框架搭建
1.Eclipse - 新建动态WEB项目(test)
2.将 org.json.jar 加入 WEB-INF/lib 下
3.将 ext-4.2.1-gpl 引入项目
4.书写过滤器处理字符集、国际区域;书写监听器处理共享属性
5.书写servlet处理查删增改
6.书写all_zh_CN.js文件处理国际化公共资源
7.书写imp.jsp文件处理前端共享资源
1.1. 项目结构
2.4. 过滤器代码
package test.common;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class EncodingFilter implements Filter {
private String encoding;// 编码,默认:utf-8
public EncodingFilter() {
}
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 编码
request.setCharacterEncoding(encoding);
response.setCharacterEncoding(encoding);
try {
((HttpServletResponse) response).setContentType("text/html;charset=" + encoding);
} catch (Exception ignore) {
}
// 国际区域
String strLocale = null;
// 取会话中的语言版本
strLocale = (String)((HttpServletRequest)request).getSession().getAttribute("strLocale");
// 如果会话中没有指定,则取浏览器的语言版本
if (strLocale == null) {
strLocale = request.getLocale().toString();
}
request.setAttribute("strLocale", strLocale);
chain.doFilter(request, response);
}
public void init(FilterConfig fConfig) throws ServletException {
encoding = fConfig.getInitParameter("encoding");
encoding = encoding==null?"UTF-8":encoding.trim();
encoding = "".equals(encoding)?"UTF-8":encoding;
}
}
2.5. 监听器代码
package test.common;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class ApplicationListener implements ServletContextListener {
public ApplicationListener() {
}
public void contextInitialized(ServletContextEvent arg0) {
ServletContext application = arg0.getServletContext();
application.setAttribute("webRoot", application.getContextPath());// WEB应用根目录
application.setAttribute("pageSize", 20);// 分页尺寸
}
public void contextDestroyed(ServletContextEvent arg0) {
}
}
2.6. 查删增改Servlet代码
package test.action;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class TestAction extends HttpServlet {
private static final long serialVersionUID = 1L;
private static JSONArray datas;
public TestAction() {
super();
iniDatas(); // 初始化数据
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
myProcess(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
myProcess(request, response);
}
// 处理入口
private void myProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 处理类型
String myProcessType = request.getParameter("myProcessType");
myProcessType = myProcessType==null?"0":myProcessType.trim();
myProcessType = "".equals(myProcessType)?"0":myProcessType;
if("0".equals(myProcessType)) {
myProcess_pageQuery(request, response); // 分页搜索
} else if("1".equals(myProcessType)) {
myProcess_save(request, response); // 添加或更新
} else if("2".equals(myProcessType)) {
myProcess_delete(request, response); // 删除
} else if("9".equals(myProcessType)) {
myProcess_submit(request, response); // 其他 - 测试表单提交
}
}
/** 分页搜索
*
* 输出 JSON对象,结构
* {
* total: 111,// 总行数
* root: // 数据
* [
* {
* id: "记录ID;自增型",
* name: "姓名",
* sex: "性别;F|M",
* tel: "电话",
* addr: "地址",
* email: "Email"
* },
* ...
* ]
* }
*/
private void myProcess_pageQuery(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 重写 Ext Store 的 beforload 事件传递的自定义参数
String name = request.getParameter("name");
String tel = request.getParameter("tel");
// Ext Store Proxy 自动传递的分页参数
int start = 1;
int limit = 0;
try {
start = Integer.parseInt(request.getParameter("start"));
limit = Integer.parseInt(request.getParameter("limit"));
} catch (Exception ignore) {
}
JSONObject joAll = getDatas(name, tel, start, limit);
PrintWriter out = response.getWriter();
out.println(joAll);
}
/** 添加或更新
*
* 输出 JSON对象,结构
* {
* success: true|false,// 是否成功
* msg: "消息"
* }
*/
private void myProcess_save(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String id = request.getParameter("id");
String sex = request.getParameter("sex");
String name = request.getParameter("name");
String tel = request.getParameter("tel");
String addr = request.getParameter("addr");
String email = request.getParameter("email");
id = id==null?"":id.trim();
PrintWriter out = response.getWriter();
JSONObject jo = new JSONObject();
String result = null;
if("".equals(id)) {
result = add(name, sex, tel, addr, email);
} else {
result = upd(id, name, sex, tel, addr, email);
}
try {
if("OK".equals(result)) {
jo.put("success", true);
} else {
jo.put("success", false);
jo.put("msg", result);
}
} catch (JSONException ignore) {
}
out.println(jo.toString());
}
/** 删除
*
* 输出 JSON对象,结构
* {
* success: true|false,// 是否成功
* msg: "消息"
* }
*/
private void myProcess_delete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String ids = request.getParameter("ids");
PrintWriter out = response.getWriter();
JSONObject jo = new JSONObject();
String result = del(ids);
try {
if("OK".equals(result)) {
jo.put("success", true);
} else {
jo.put("success", false);
jo.put("msg", result);
}
} catch (JSONException ignore) {
}
out.println(jo.toString());
}
/** 其他 - 测试表单提交
*
* 输出 JSON对象,结构
* {
* success: true|false,// 是否成功
* msg: "消息"
* }
*/
private void myProcess_submit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Map<String,Object> map = request.getParameterMap();
StringBuffer sb = new StringBuffer();
Set<Entry<String,Object>> ets = map.entrySet();
Object[] vals = null;
for(Entry<String,Object> et:ets) {
sb.append(et.getKey()).append(": ").append(joinAry((Object[])et.getValue())).append("<br/>");
}
PrintWriter out = response.getWriter();
JSONObject jo = new JSONObject();
try {
jo.put("success", true);
jo.put("msg", sb.toString());
} catch (JSONException ignore) {
}
out.println(jo.toString());
}
// 添加;返回 OK 表示成功,否则为失败消息
private String add(String name, String sex, String tel, String addr, String email) {
name = name==null?"":name.trim();
tel = tel==null?"":tel.trim();
sex = sex==null?"":sex.trim();
sex = "F".equals(sex)?"F":"M";
addr = addr==null?"":addr.trim();
email = email==null?"":email.trim();
if("".equals(name) || name.length()>20 ||
"".equals(tel) || !tel.matches("^\d{10,12}$") ||
!"".equals(email) && !email.matches("\w+@\w+[.][a-z]+") ||
addr.length()>50 ) {
return "非法操作";
}
int len = datas.length();
JSONObject jo = new JSONObject();
try {
jo.put("id", len+1);
jo.put("name", name);
jo.put("sex", sex);
jo.put("tel", tel);
jo.put("addr", addr);
jo.put("email", email);
} catch (JSONException ignore) {
}
datas.put(jo);
return "OK";
}
// 更新;返回 OK 表示成功,否则为失败消息
private String upd(String id, String name, String sex, String tel, String addr, String email) {
name = name==null?"":name.trim();
tel = tel==null?"":tel.trim();
sex = sex==null?"":sex.trim();
sex = "F".equals(sex)?"F":"M";
addr = addr==null?"":addr.trim();
email = email==null?"":email.trim();
if("".equals(name) || name.length()>20 ||
"".equals(tel) || !tel.matches("^\d{10,12}$") ||
!"".equals(email) && !email.matches("\w+@\w+[.][a-z]+") ||
addr.length()>50 ) {
return "非法操作";
}
JSONObject jo = null;
int len = datas.length();
String id2 = null;
boolean flag = false;
for (int i = 0; i < len; i++) {
try {
jo = datas.getJSONObject(i);
id2 = jo.isNull("id")?"":jo.getString("id");
id2 = id2==null?"":id2.trim();
if(id2.equals("") || id2.equals("D") || !id2.equals(id)) {
continue;
}
jo.put("name", name);
jo.put("sex", sex);
jo.put("tel", tel);
jo.put("addr", addr);
jo.put("email", email);
flag = true;
break;
} catch (JSONException ignore) {
continue;
}
}
return flag?"OK":"没有该数据{"+id+"}";
}
// 删除;返回 OK 表示成功,否则为失败消息
private String del(String ids) {
if("".equals(ids)) {
return "非法操作";
}
ids = ","+ids+",";
JSONObject jo = null;
int len = datas.length();
String id2 = null;
boolean flag = false;
for (int i = 0; i < len; i++) {
try {
jo = datas.getJSONObject(i);
id2 = jo.isNull("id")?"":jo.getString("id");
id2 = id2==null?"":id2.trim();
if(id2.equals("") || id2.equals("D") || ids.indexOf(","+id2+",")<0) {
continue;
}
jo.put("id", "D");// 标志删除
flag = true;
} catch (JSONException ignore) {
continue;
}
}
return flag?"OK":"没有这些数据{"+ids+"}";
}
/* 分页搜索
*
* @param name姓名
* @param tel电话
* @param start起始索引号
* @param limit结束索引号
*
* @return JSON对象,结构
* {
* total: 111,// 总行数
* root: // 数据
* [
* {
* id: "记录ID;自增型",
* name: "姓名",
* sex: "性别;F|M",
* tel: "电话",
* addr: "地址",
* email: "Email"
* },
* ...
* ]
* }
*/
private JSONObject getDatas(String name, String tel, int start, int limit) {
name = name==null?"":name.trim();
int len = datas.length();
JSONObject jo = null;
String name2 = null;
String tel2 = null;
JSONArray ja = new JSONArray();
String id2 = null;
for(int i=0;i<len;i++) {
try {
jo = datas.getJSONObject(i);
id2 = jo.isNull("id")?"":jo.getString("id");
if(id2.equals("") || id2.equals("D")) {
continue;
}
if(!"".equals(name)) {
name2 = jo.isNull("name")?"":jo.getString("name");
name2 = name2==null?"":name2.trim();
if(name2.indexOf(name)<0) {
continue;
}
}
if(!"".equals(tel)) {
tel2 = jo.isNull("tel")?"":jo.getString("tel");
tel2 = tel2==null?"":tel2.trim();
if(tel2.indexOf(tel)<0) {
continue;
}
}
ja.put(jo);
} catch (JSONException ignore) {
}
}
limit = limit<=0?20:limit;
int total = ja.length();
start = start>=total?total-1:(start<0?0:start);
int end = start+limit-1;
end = end>=total?total:(end<start?start:end);
JSONArray ja2 = new JSONArray();
for(int i=0;i<total;i++) {
if(i>=start && i<=end) {
try {
ja2.put(ja.getJSONObject(i));
} catch (JSONException ignore) {
}
}
}
JSONObject joAll = new JSONObject();
try {
joAll.put("total", total);
joAll.put("root", ja2);
} catch (JSONException ignore) {
}
return joAll;
}
/* 初始化数据,数据结构
* [
* {
* id: "记录ID;自增型",
* name: "姓名",
* sex: "性别;F|M",
* tel: "电话",
* addr: "地址",
* email: "Email"
* },
* ...
* ]
*/
private void iniDatas() {
if(datas==null) {
datas = new JSONArray();
JSONObject jo = null;
for(int i=0;i<3000;i++) {
jo = new JSONObject();
try {
jo.put("id", i+1);
jo.put("name", "无名氏"+(i+1));
jo.put("sex", i%4==0?"F":"M");
jo.put("tel", "152"+(int)((99999999-11111111+1)*Math.random()+11111111));
jo.put("addr", "无名小镇"+(int)((9999-1111+1)*Math.random()+1111)+"号");
jo.put("email", "wuming"+(int)((999999-111111+1)*Math.random()+111111)+"@wuming.com");
} catch (JSONException ignore) {
}
datas.put(jo);
}
}
}
// 辅助方法:数组的 join
private String joinAry(Object[] ary) {
int len = ary==null?0:ary.length;
if(len==0) {
return "";
}
StringBuffer sb = new StringBuffer();
for(Object o:ary) {
sb.append(o).append(", ");
}
int ix = sb.lastIndexOf(",");
if(ix>=0) {
sb.deleteCharAt(ix);
}
return sb.toString();
}
}
2.7. web.xml代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>test</display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- 应用程序启动监听器 -->
<listener>
<listener-class>test.common.ApplicationListener</listener-class>
</listener>
<!-- 查删增改Servlet -->
<servlet>
<description></description>
<display-name>TestAction</display-name>
<servlet-name>TestAction</servlet-name>
<servlet-class>test.action.TestAction</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TestAction</servlet-name>
<url-pattern>/test.do</url-pattern>
</servlet-mapping>
<!-- 编码过滤器 -->
<filter>
<display-name>编码过滤器,有1个参数可配置,具体见该类说明</display-name>
<filter-name>EncodingFilter</filter-name>
<filter-class>test.common.EncodingFilter</filter-class>
<init-param>
<description>编码</description>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>*.do</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>*.html</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>*.htm</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>*.xml</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>*.js</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>*.css</url-pattern>
</filter-mapping>
</web-app>
2.8. all_zh_CN.js代码
var i18n_all = {
title: {
dataList: "数据表格",
addOpe: "添加操作",
editOpe: "编辑操作",
delOpe: "删除操作",
guide: "第 {0} 步,共 {1} 步"
},
lable: {
},
btn: {
pre: "上一步",
next: "下一步",
add: "添加",
edit: "编辑",
del: "删除",
save: "保存",
reset: "重置"
},
msg: {
addConfirm: "是否确认添加",
addOK: "添加成功",
addErr: "添加失败",
editConfirm: "是否确认更新",
editOK: "编辑成功",
editErr: "编辑失败",
delConfirm: "是否确认删除",
delOK: "删除成功",
delErr: "删除失败"
}
};
2.9. imp.jsp代码
<%@ page language="java"
contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%-- EXT-CSS文件 --%>
<link rel="stylesheet" type="text/css"
href="${webRoot }/res/extjs-4.2.0/resources/css/ext-all.css" />
<%-- EXT-JS引导文件(根据模式决定调入 ext_all.js 或 ext_all_dev.js) --%>
<script type="text/javascript"
src="${webRoot }/res/extjs-4.2.0/bootstrap.js"></script>
<%-- EXT-国际化文件,strLocale 在过滤器中已设置 --%>
<script type="text/javascript"
src="${webRoot }/res/extjs-4.2.0/locale/ext-lang-${strLocale }.js"></script>
<script>
// 定义3个变量,用于 .js 文件
var global_webRoot = "${webRoot}";// WEB应用根目录 - 在监听器中已设置
var global_pageSize = "${pageSize}";// 分页大小 - 在监听器中已设置
var global_strLocale = "${strLocale}";// 国际区域 - 在过滤器中已设置
</script>
<script type="text/javascript"
src="${webRoot }/locale/common/all_${strLocale }.j"></script>
3. Hello World
3.1. 开发
3.1.1. 创建 /locale/test_zh_CN.js文件
var i18n_test = {
title: {
test1: "伊兰.ExtJS4专区"
},
lable: {
},
btn: {
},
msg: {
test1: "你好!欢迎光临伊兰.ExtJS4专区!"
}
};
3.1.2. 创建 /test01/test.js文件
var i18n_my = i18n_test; // 国际化对象,见 /locale/test/test_zh_CN.js 文件
//Ext.application({// 程序入口;作用类似 Ext.onReady ;用于 MVC 架构
//name : "HelloExt",
//launch : function() {
//var p = getPanel();
//
//Ext.create("Ext.container.Viewport", {
//layout : "fit",
//items : [
//p
//]
//});
//}
//});
// 入口
Ext.onReady(function(){ // 程序入口,页面加载完后会自动调用 Ext.onReady
// 获取面板
var p = getPanel();
// 通过视图呈现面板
// Viewport 为顶层容器,以页面 body 元素作为载体,呈现内容
Ext.create("Ext.container.Viewport", {
layout : "fit", // fit 布局:填满容器
items : [ // Viewport包含的内容
p
]
});
});
// 创建并返回面板
function getPanel() {
//var p = new Ext.panel.Panel({// 普通加载
//// ...
//});
var p = Ext.create("Ext.panel.Panel", {// 动态加载
title : i18n_my.title.test1,
html : i18n_my.msg.test1
// 如果这个 Panel 不是通过 Viewport 呈现,则可通过 renderTo 指定呈现载体
//,renderTo: Ext.getBody()// 以页面 body 元素作为呈现载体
//,renderTo: "d01"// 以ID为 d01 的元素作为呈现载体
});
// 返回面板
return p;
}
3.1.3. 创建 /test01/test.jsp文件
<%@ page language="java"
contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ include file="/common/imp.jsp"%><%-- 共享文件 --%>
<%-- 本页面国际化JS文件 --%>
<script src="${webRoot }/locale/test/test_${strLocale }.js"></script>
<%-- 本页面功能JS文件 --%>
<script src="${webRoot }/test01/test.js"></script>
<%-- <div id="d01"></div> --%>
3.1.4. 浏览
启动服务器,浏览:http://localhost:8080/test/test01/test.jsp
3.2. 讲解
3.2.1. Ext.application 和 Ext.ready
这2个方法将在Ext加载完毕后自动调用
其中,Ext.application是ExtJS4引入的新方式,用于ExtJS4的MVC架构
3.2.2. Ext.container.Viewport
顶层容器,视图组件,可以包含其他容器类组件
以 body 元素作为呈现载体
主要配置为:
layouot: “布局”,
items: [ 成员,通常为面板 ]
3.2.3. Ext.panel.Panel
面板,容器类组件,可以包含其他组件,包括容器类组件
如果不放在视图内,必需用 renderTo 指定呈现载体
主要配置为:
title: “标题”,
500, // 宽度
height: 300, // 高度
items: [], // 成员
tbar: [], // 工具栏
bbar: [], // 状态栏
buttons: [], // 按钮
listeners: { // 事件监听器
activate: function(panel, eopt) {
// 面板激活时的处理代码,panel表示面板自身
},
show: function(panel, eopt) {
// 面板呈现时的处理代码,panel表示面板自身
},
..
}
3.2.4. new ***({}) 与 Ext.create(“***”, {});
创建Ext对象
区别犹如java中的 new *** 与 Class.forName(“***”).newInstance()
也就是说,后者是动态创建,这是ExtJS4的亮点,实现了按需加载
4. 布局
Viewport也好,Panel也好,在默认情况下,它们只能有1个成员,如果有多个成员,只会呈现第一个;那么,如何使它们能拥有和呈现多个成员呢?那么,这就要靠布局来实现。布局,就是用来组织多个成员的。
默认的布局方式,一般是 fit ,表示成员填充整个容器
4.1. 边界(border)
4.1.1. 概述
如果容器需要划分为几个部分,如上中下,或左右等,凡涉及到上下左右中的,都使用边界布局,这也是最常用的布局
边界布局的名称是 border ,它将容器划分为 上下左右中 五大块,其中,只有 中 部是必需的
容器内的成员通过 region 属性,指示将自身装载到容器的哪个部分
上、下:分别为 north 、 south;这两个部分必须且只能规定高度
左、右:分别为 west、 east;这两个部分必须且只能规定宽度
中:为 center;高度和宽度都为自动,自动填充容器剩余的部分
4.1.2. 开发
4.1.2.1. 修改 /locale/test_zh_CN.js文件
var i18n_test = {
...,
msg: {
...,
test2_0_1: "这里是 north",
test2_0_2: "这里是 west",
test2_0_3: "这里是 center (伊兰.ExtJS4专区.边界布局)",
test2_0_4: "这里是 east",
test2_0_5: "这里是 south"
}
};
4.1.2.2. 创建 /test02/test.js文件
var i18n_my = i18n_test; // 国际化对象,见 /locale/test/test_zh_CN.js 文件
// 入口
Ext.onReady(function(){
var p1 = getTop();
var p2 = getLeft();
var p3 = getCenter();
var p4 = getRight();
var p5 = getBottom();
Ext.create("Ext.container.Viewport", {
layout : "border", // 边界布局
items : [
p1,
p2,
p3, // 只有中部是必需的;中部的大小是自动的,不可指定的
p4,
p5
]
});
});
// 头部
function getTop() {
var p = Ext.create("Ext.panel.Panel", {
html : i18n_my.msg.test2_0_1,
height: 80, // 底部必须且只能指定高度
border: false,
region: "north" // 指示将自身装载到容器的头部
});
return p;
}
// 左边
function getLeft() {
var p = Ext.create("Ext.panel.Panel", {
html : i18n_my.msg.test2_0_2,
200, // 左边必须且只能指定宽度
collapsible: true, // 面板标题栏将出现一个 >> 图标,点击将折叠或展开面板
split: true, // 该面板的宽度将可用鼠标调整
region: "west" // 指示将自身装载到容器的左部
});
return p;
}
// 中部
function getCenter() {
var p = Ext.create("Ext.panel.Panel", {
html : i18n_my.msg.test2_0_3,
bodyStyle: "border-1px 0px 1px 0px",
region: "center" // 指示将自身装载到容器的中部
});
return p;
}
// 右边
function getRight() {
var p = Ext.create("Ext.panel.Panel", {
html : i18n_my.msg.test2_0_4,
120, // 右边必须且只能指定宽度
collapsible: true, // 面板标题栏将出现一个 >> 图标,点击将折叠或展开面板
region: "east" // 指示将自身装载到容器的右部
});
return p;
}
// 底部
function getBottom() {
var p = Ext.create("Ext.panel.Panel", {
html : i18n_my.msg.test2_0_5,
height: 40, // 底部必须且只能指定高度
border: false,
region: "south" // 指示将自身装载到容器的底部
});
return p;
}
4.1.2.3. 创建 /test02/test.jsp文件
<%@ page language="java"
contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%-- 共享文件 --%>
<%@ include file="/common/imp.jsp"%>
<%-- 本页面国际化JS文件 --%>
<script src="${webRoot }/locale/test/test_${strLocale }.js"></script>
<%-- 本页面功能JS文件 --%>
<script src="${webRoot }/test02/test.js"></script>
4.1.2.4. 浏览
浏览:http://localhost:8080/test/test02/test.jsp
注意左右面板的 << 和 >> 按钮;注意左面板与中部的分割线
4.2. 手风琴(accordion)
4.2.1. 概述
我们常见页面右边是导航栏,手风琴布局可以实现
手风琴布局的名称是 accordion
每个成员面板都支持展开和折叠。
在任何时间里,只有一个成员面板处于展开状态,其他都被折叠。
注意:只有Ext.Panels和所有Ext.panel.Panel子项才可以使用accordion布局
4.2.2. 开发
4.2.2.1. 修改 /locale/test_zh_CN.js文件
var i18n_test = {
...,
msg: {
...,
// 届时将使用 Ext.String.format 方法填充 {0} 参数
test2_1_0: "这里是 {0} 的导航栏",
test2_1_1: "系统管理模块",
test2_1_2: "权限管理模块",
test2_1_3: "基础数据管理模块",
test2_1_4: "任务调度管理模块",
test2_1_5: "数据同步管理模块"
}
};
4.2.2.2. 创建 /test02/test1.js文件
var i18n_my = i18n_test; // 国际化对象,见 /locale/test/test_zh_CN.js 文件
// 入口
Ext.onReady(function(){
var p1 = getLeft();
var p2 = getCenter();
Ext.create("Ext.container.Viewport", {
layout : "border",
items: [
p1,
p2
]
});
});
// 左边 - 整体
function getLeft() {
// 获取5个成员面板
var p1 = getLeft01();
var p2 = getLeft02();
var p3 = getLeft03();
var p4 = getLeft04();
var p5 = getLeft05();
// 创建并返回手风琴布局面板
var p = Ext.create("Ext.panel.Panel", {
layout: "accordion", // 手风琴布局
title: i18n_my.title.test2_1,
200,
collapsible: true,
split: true,
region: "west",
items: [
p1,
p2,
p3,
p4,
p5
]
});
return p;
}
// 左边 - 第 1 个成员
function getLeft01() {
var tt = i18n_my.msg.test2_1_1;
var p = Ext.create("Ext.panel.Panel", {
title: tt,
html : Ext.String.format(i18n_my.msg.test2_1_0, tt),
border: false
});
return p;
}
// 左边 - 第 2 个成员
function getLeft02() {
var tt = i18n_my.msg.test2_1_2;
var p = Ext.create("Ext.panel.Panel", {
title: tt,
html : Ext.String.format(i18n_my.msg.test2_1_0, tt),
border: false
});
return p;
}
// 左边 - 第 3 个成员
function getLeft03() {
var tt = i18n_my.msg.test2_1_3;
var p = Ext.create("Ext.panel.Panel", {
title: tt,
html : Ext.String.format(i18n_my.msg.test2_1_0, tt),
border: false
});
return p;
}
// 左边 - 第 4 个成员
function getLeft04() {
var tt = i18n_my.msg.test2_1_4;
var p = Ext.create("Ext.panel.Panel", {
title: tt,
html : Ext.String.format(i18n_my.msg.test2_1_0, tt),
border: false
});
return p;
}
// 左边 - 第 5 个成员
function getLeft05() {
var tt = i18n_my.msg.test2_1_5;
var p = Ext.create("Ext.panel.Panel", {
title: tt,
html : Ext.String.format(i18n_my.msg.test2_1_0, tt),
border: false
});
return p;
}
// 中部
function getCenter() {
var p = Ext.create("Ext.panel.Panel", {
html : i18n_my.msg.test2_0_3,
bodyStyle: "border-1px 0px 1px 0px",
region: "center"
});
return p;
}
4.2.2.3. 创建 /test02/test1.jsp文件
<%@ page language="java"
contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%-- 共享文件 --%>
<%@ include file="/common/imp.jsp"%>
<%-- 本页面国际化JS文件 --%>
<script src="${webRoot }/locale/test/test_${strLocale }.js"></script>
<%-- 本页面功能JS文件 --%>
<script src="${webRoot }/test02/test1.js"></script>
4.2.2.4. 浏览
浏览:http://localhost:8080/test/test02/test1.jsp
点击左边每个子面板,可见其他子面板被这地,被点击的面板则展开
4.3. 卡片(card)
4.3.1. 概述
我们常见向导界面,卡片布局可以实现
卡片布局的名称是 card
在任何时间里,只有一个成员面板处于激活状态,其他都被隐藏。
需要自行开发导航按钮,以导航到不同的成员面板,实现 上一步、下一步 的功能
注意:由于card布局需要配合导航按钮,而Viewport不具备buttons配置,故不宜直接将Viewport配置为card布局,而应用一个Panel作为card布局的总容器,再将这个Panel加入到Viewport
4.3.2. 开发
4.3.2.1. 修改 /locale/test_zh_CN.js文件
var i18n_test = {
...,
msg: {
...,
test2_2_02: "现在进行第 {0} 步操作..."
}
};
4.3.2.2. 创建 /test02/test2.js文件
var i18n_my = i18n_test; // 国际化对象,见 /locale/test/test_zh_CN.js 文件
var g_ix = 0; // 索引,决定card布局中,哪个成员面板被显示
// 入口
Ext.onReady(function(){
var p1 = getLeft();
var p2 = getCenter();
Ext.create("Ext.container.Viewport", {
layout : "border",
items: [
p1,
p2
]
});
});
// 左边 - 整体
function getLeft() {
// 获取5个成员面板
var p1 = getLeft01();
var p2 = getLeft02();
var p3 = getLeft03();
var p4 = getLeft04();
var p5 = getLeft05();
// 创建并返回手风琴布局面板
var p = Ext.create("Ext.panel.Panel", {
layout: "accordion", // 手风琴布局
title: i18n_my.title.test2_1,
200,
collapsible: true,
split: true,
region: "west",
items: [
p1,
p2,
p3,
p4,
p5
]
});
return p;
}
// 左边 - 第 1 个成员
function getLeft01() {
var tt = i18n_my.msg.test2_1_1;
var p = Ext.create("Ext.panel.Panel", {
title: tt,
html : Ext.String.format(i18n_my.msg.test2_1_0, tt),
border: false
});
return p;
}
// 左边 - 第 2 个成员
function getLeft02() {
var tt = i18n_my.msg.test2_1_2;
var p = Ext.create("Ext.panel.Panel", {
title: tt,
html : Ext.String.format(i18n_my.msg.test2_1_0, tt),
border: false
});
return p;
}
// 左边 - 第 3 个成员
function getLeft03() {
var tt = i18n_my.msg.test2_1_3;
var p = Ext.create("Ext.panel.Panel", {
title: tt,
html : Ext.String.format(i18n_my.msg.test2_1_0, tt),
border: false
});
return p;
}
// 左边 - 第 4 个成员
function getLeft04() {
var tt = i18n_my.msg.test2_1_4;
var p = Ext.create("Ext.panel.Panel", {
title: tt,
html : Ext.String.format(i18n_my.msg.test2_1_0, tt),
border: false
});
return p;
}
// 左边 - 第 5 个成员
function getLeft05() {
var tt = i18n_my.msg.test2_1_5;
var p = Ext.create("Ext.panel.Panel", {
title: tt,
html : Ext.String.format(i18n_my.msg.test2_1_0, tt),
border: false
});
return p;
}
// 中部 - 整体
function getCenter() {
// 按钮:上一步
var btnPre = Ext.create("Ext.button.Button", {
id: "btnPre",
text: i18n_all.btn.pre,
disabled: true,
handler: function() {
g_ix--;
showPanel();
}
});
// 按钮:下一步
var btnNext = Ext.create("Ext.button.Button", {
id: "btnNext",
text: i18n_all.btn.next,
handler: function() {
g_ix++;
showPanel();
}
});
// 获取4个成员面板
var p1 = getCenter01();
var p2 = getCenter02();
var p3 = getCenter03();
var p4 = getCenter04();
// 创建并返回卡片式布局面板
var p = Ext.create("Ext.panel.Panel", {
id: "center1",
layout: "card",
title: i18n_my.title.test2_2,
region: "center",
items: [
p1,
p2,
p3,
p4
],
buttons: [
btnPre,
btnNext
]
});
return p;
}
// 中部 - 第 1 个成员
function getCenter01() {
var tt = 1;
var p = Ext.create("Ext.panel.Panel", {
title: Ext.String.format(i18n_all.title.guide, tt, "4"),
html : Ext.String.format(i18n_my.msg.test2_2, tt),
border: false
});
return p;
}
// 中部 - 第 2 个成员
function getCenter02() {
var tt = 2;
var p = Ext.create("Ext.panel.Panel", {
title: Ext.String.format(i18n_all.title.guide, tt, "4"),
html : Ext.String.format(i18n_my.msg.test2_2, tt),
border: false
});
return p;
}
// 中部 - 第 3 个成员
function getCenter03() {
var tt = 3;
var p = Ext.create("Ext.panel.Panel", {
title: Ext.String.format(i18n_all.title.guide, tt, "4"),
html : Ext.String.format(i18n_my.msg.test2_2, tt),
border: false
});
return p;
}
// 中部 - 第 4 个成员
function getCenter04() {
var tt = 4;
var p = Ext.create("Ext.panel.Panel", {
title: Ext.String.format(i18n_all.title.guide, tt, "4"),
html : Ext.String.format(i18n_my.msg.test2_2, tt),
border: false
});
return p;
}
// 导航到某个面板
function showPanel() {
// 处理索引号,激活索引号对应的面板
g_ix = g_ix<0?0:(g_ix>3?3:g_ix);
Ext.getCmp("center1").getLayout().setActiveItem(g_ix);
// 根据索引号,设置 上一步、下一步 按钮的可用性
Ext.getCmp("btnPre").setDisabled(g_ix<=0);
Ext.getCmp("btnNext").setDisabled(g_ix>=3);
}
4.3.2.3. 创建 /test02/test2.jsp文件
<%@ page language="java"
contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%-- 共享文件 --%>
<%@ include file="/common/imp.jsp"%>
<%-- 本页面国际化JS文件 --%>
<script src="${webRoot }/locale/test/test_${strLocale }.js"></script>
<%-- 本页面功能JS文件 --%>
<script src="${webRoot }/test02/test2.js"></script>
4.3.2.4. 浏览
浏览:http://localhost:8080/test/test02/test2.jsp
1.初始时,显示的是card布局中第1个成员面板, 上一步 按钮被禁用
2.点击 下一步 按钮后,显示第2个成员面板, 上一步、下一步按钮都被启用
3.到第4步时,由于这是最后一个面板,所以 下一步 按钮被禁用