ExtJS4.2 - 从 Hello World 到 自定义组件 -01 (为爱女伊兰奋斗)

时间:2021-04-12 01:19:59

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. 项目结构

ExtJS4.2 - 从 Hello World 到 自定义组件 -01 (为爱女伊兰奋斗)

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. 浏览

启动服务器,浏览:/test.jsp

ExtJS4.2 - 从 Hello World 到 自定义组件 -01 (为爱女伊兰奋斗)

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: “标题”,

width: 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,

width: 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-width:1px 0px 1px 0px",

region: "center" // 指示将自身装载到容器的中部

});

return p;

}

// 右边

function getRight() {

var p =  Ext.create("Ext.panel.Panel", {

html : i18n_my.msg.test2_0_4,

width: 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. 浏览

浏览:/test.jsp

注意左右面板的 << 和 >> 按钮;注意左面板与中部的分割线

ExtJS4.2 - 从 Hello World 到 自定义组件 -01 (为爱女伊兰奋斗)

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,

width: 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-width: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. 浏览

浏览:.jsp

点击左边每个子面板,可见其他子面板被这地,被点击的面板则展开

ExtJS4.2 - 从 Hello World 到 自定义组件 -01 (为爱女伊兰奋斗)

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,

width: 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文件 --%>

.js"></script>

4.3.2.4. 浏览

浏览:.jsp

1.初始时,显示的是card布局中第1个成员面板, 上一步 按钮被禁用

ExtJS4.2 - 从 Hello World 到 自定义组件 -01 (为爱女伊兰奋斗)

2.点击 下一步 按钮后,显示第2个成员面板, 上一步、下一步按钮都被启用

ExtJS4.2 - 从 Hello World 到 自定义组件 -01 (为爱女伊兰奋斗)

3.到第4步时,由于这是最后一个面板,所以 下一步 按钮被禁用

ExtJS4.2 - 从 Hello World 到 自定义组件 -01 (为爱女伊兰奋斗)