Upload
hadat
View
303
Download
0
Embed Size (px)
Citation preview
第 2 章 从“Hello World”开始
“Hello World”几乎已经成为所有开发类图书的必用案例,本书也不能免俗。本章将通过
编写“Hello World”程序来让大家对如何使用 Ext JS 进行开发有初步的了解,如 Ext JS 代码
是如何运行的、代码书写风格是怎样的、如何实现本地化等。
2.1 获取 Ext JS 4
要下载 Ext JS 4,可访问地址:http://www.sencha.com/products/Ext JS/download/。本书介绍的版本是 4.1,所以请下载 4.1 Beta 1 这个版本。
文件解压后,在根目录会看到以下目录和文件:
builds 目录 :包含沙盒、Core 和 foundation 3 个压缩好的脚本文件及其调试(debug) ❑
文件。
docs 目录:API 文档目录。 ❑
examples 目录:示例文件目录。 ❑
locale:本地化文件目录。 ❑
pkgs 目录:独立的包文件。 ❑
resources 目录:Ext JS 4 的资源目录,包含了样式定义文件及其需要的图片文件。 ❑
src 目录:Ext JS 4 的源文件目录。 ❑
welcome 目录:Ext JS 欢迎页面所需的资源目录。 ❑
bootstrap.js 文件 :会根据 url 地址自动加载 ext-all.js 或 ext-all-debug.js 文件。此文本只 ❑
在正式发布版本中有,可在 4.0.7 中找到。
ext.js 文件:Ext Core 的库文件。 ❑
ext-all.js 文件:Ext JS 的库文件(压缩)。 ❑
ext-all-debug.js 文件:调试应用程序时使用的 Ext JS 的库文件(不带注释)。 ❑
ext-all-debug-w-comments.js 文件:调试应用程序时使用的 Ext JS 的库文件(带注释)。 ❑
ext-all-dev.js:用于诊断布局问题的 Ext JS 库文件。 ❑
ext-all-dev-w-comments.js:带注释的诊断库文件。 ❑
ext-neptune.js:使用海王星主题的 Ext JS 库文件。 ❑
ext-debug.js 文件:调试使用 Ext Core 开发的程序时的 Ext Core 库文件。 ❑
index.html 文件:Ext JS 4 欢迎页面。 ❑
license.txt 文件:协议说明文本。 ❑
release-notes.html 文件:Ext JS 4 版本发布说明。 ❑
第 2 章 从“Hello World”开始 35
2.2 配置使用 Ext JS 库
要使用 Ext JS,首先要做的是将 Ext JS 包里的 resources 目录、bootstrap.js 文件、ext-all.js 文件和 ext-all-debug.js 文件复制到项目目录。接着在页面中 head 标记部分添加 CSS 和
脚本文件的引用:
<link rel="stylesheet" type="text/css" href="path/resources/css/ext-all.css"/> <script type="text/javascript" src="path/bootstrap.js"></script>
要注意代码中的 path 是 CSS 文件或脚本文件相对于页面文件的路径。例如,页面文件
在根目录,resoureces 目录在根目录下,bootstrap.js 在 js 目录下,就可这样写:
<link rel="stylesheet" type="text/css" href="resources/css/ext-all.css"/> <script type="text/javascript" src="js/bootstrap.js"></script>
如果熟悉 Ext JS 2 或 Ext JS 3 的,会发现 Ext JS 4 不是直接加载 ext-all.js 或 ext-all-debug.js,而是加载了 bootstrap.js,这有什么好处呢?先看看 bootstrap.js 的源代码:
(function() {
var scripts = document.getElementsByTagName('script'), localhostTests = [ /^lo calhost$/, /\b(25[0-5]|2[0-4][0-9]|[01]?[0-9]
[0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(:\d{1,5})?\b/ // IP v4
], host = window.location.hostname, isDevelopment = null, queryString = window.location.search, test, path, i, ln, scriptSrc, match; for (i = 0, ln = scripts.length; i < ln; i++) { scriptSrc = scripts[i].src; match = scriptSrc.match(/bootstrap\.js$/); if (match) { path = scriptSrc.substring(0, scriptSrc.length - match[0].length); break; } }
if (queryString.match('(\\?|&)debug') !== null) { isDevelopment = true; } else if (queryString.match('(\\?|&)nodebug') !== null) { isDevelopment = false; } if (isDevelopment === null) { for (i = 0, ln = localhostTests.length; i < ln; i++) { test = localhostTests[i]; if (host.search(test) !== -1) { isDevelopment = true; break;
36 Ext JS 权威指南
} } }
if (isDevelopment === null && window.location.protocol === 'file:') { isDevelopment = true; }
docu ment.write('<script type="text/javascript" src="' + path + 'ext-all' + ((isDevelopment) ? '-debug' : '') + '.js"></script>');
})();
代码首先使用 getElementsByTagName 获取页面中所有带有 script 标记的元素,然后从
中找出带有 bootstrap.js 的标记,最后将 bootstrap.js 的相对路径取出并保存在变量 path 中。
接着判断 url 的参数中是否有“debug”字符,例如,出现 http://localhost/index.html? debug,则设置 isDevelopment 为 true。否则检测是否有“nodebug”字符,如果有,设置为
false。如果以上两个条件都不符合,isDevelopment 还是初始值 null,就要判断 url 的域名是否
是“localhost”或 IPv4 地址,如果是,则 isDevelopment 设置为 true。如果 isDevelopment 是 null 且当前的 url 的协议是“file :”,则将 isDevelopment 设置为
true。如果 isDevelopment 为 true 时,则加载 ext-all-debug.js 文件,否则加载 ext-all.js 文件。
通过 bootstrap.js,可自动控制加载 ext-all-debug.js 文件或 ext-all.js 文件,不用我们自己去
修改 script 标记,非常方便。但如果你觉得自己修改方便,也可以使用 Ext JS 旧版的方式加载
脚本文件。不过 bootstrap.js 有个缺点,就是把所有 IPv4 地址都划归为使用调试文件的范围,
不太符合国内的情况。因为在内网,应用也多半是使用 IP 地址访问的。不过,根据自己的情
况去修改 bootstrap.js 也是很方便的。
如果想动态加载 Ext JS 库,就需要在页面中先加载 builds 目录下的 Ext-core.js 或 Ext.-core-debug.js 文件,然后在 onReady 函数外添加以下代码:
Ext.Loader.setConfig({enabled: true}); Ext.Loader.setPath({//加载路径配置对象 }); Ext.require([ 'Ext.grid.*', … //需要加载的库
]); Ext.onReady(function(){ //代码
});
Loader 对象的 setConfig 方法设置的 enabled 属性的作用是,开启动态加载的依赖加载
功能。该功能的作用是在加载类库文件的时候,根据其依赖情况加载它需要的类库,其默认
值是 false,这是因为一般情况下 Ext JS 的库文件都是一次性全部加载的,很少用到动态加
载。试想一下,Ext JS 类库有 200 多个文件,在无法预知要加载多少类库的情况下,不断地
向服务器请求 100 多个类库甚至全部 200 多个类库,那是很吓人的,不但增加了服务器的负
担,客户也要一直等待类库的加载,这不是好的方法,所以不推荐使用此方法。
第 2 章 从“Hello World”开始 37
Loader 对象的 setPath 方法是用来设置加载路径的,这在 4.5 节中会讲述。接着就是使用
Ext.require 方法请求加载类库了。
2.3 编写“Hello World”程序
明白了 Ext JS 4 配置后,就可编写“Hello World”程序了。新建一个 HTML 文件 Hello_World.html,加入如代码清单 2-1 所示的代码。
代码清单 2-1 Hello World 程序
<!DO CTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html><head> <title>第 2章 示例 2-1 Hello World</title> <lin k rel="stylesheet" type="text/css" href="../Ext4/resources/css/ext-all.
css"/> <script type="text/javascript" src="../Ext4/bootstrap.js"></script> <script type="text/javascript" src="../Ext4/locale/Ext-lang-zh_CN.js"></script></head><body> <script type="text/javascript"> Ext.onReady(function(){ if(Ext.BLANK_IMAGE_URL.substr(0,4)!="data"){
Ext.BLANK_IMAGE_URL="./images/s.gif"; } //Ext.create('Ext.Viewport',{ new Ext.Viewport({ layout:'fit', items:[{ xtype:"panel", title:"欢迎 ", html :"<h1 style='text-align:center;font-
size:60px;font-weight:bold;'>Hello World</h1>"
}] }); }) </script></body></html>
代码主要包括两部分 :第一部分是在 head 部分配置了 Ext JS 库文件和样式文件的引
用 ;第二部分是在 Ext.onReady 函数中使用 Viewport 定义界面,并在一个面板内显示“Hello World”。
在浏览器中打开页面将看到如图 2-1 所示的结果。
第 2 章 从“Hello World”开始 39
readyEvent.addListener(fn, scope, options); if (Ext.isReady) { readyEvent.fire(); } else if (document.readyState == 'complete') { me.fireDocReady(); } else { me.bindReadyEvent(); }},
在上面的代码中,readyEvent 是 Ext.util.Event 的实例,options.single 的作用是规定 ready-Event 事件只执行一次。接着将函数添加到 Event 实例内的监听事件列表中,最后判断 DOM模型是否已加载完成。如果已加载完成,则调用 fire 方法依次执行监听事件列表中的函数。
这样做的目的是 :当存在多个 onReady 方法时,能保证所有的函数都能执行。如果还没有加
载完成,而 document 对象的 readyState 属性为“complete”,表示文档其实已经加载完成了,
但是没有设置 isReady 属性为 true,那么可调用 fireDocReady 方法,其代码如下:
fireDocReady: function(){ var me = Ext.EventManager;
if (!Ext.isReady) { Ext.isReady = true;
if (document.addEventListener) { docu ment.removeEventListener('DOMContentLoaded', me.fireDocReady,
false); window.removeEventListener('load', me.fireDocReady, false); } else { if (me.readyTimeout !== null) { clearTimeout(me.readyTimeout); } if (me.hasOnReadyStateChange) { document.detachEvent('onreadystatechange', me.checkReadyState); } window.detachEvent('onload', me.fireDocReady); }
Ext.supports.init(); me.onWindowUnload(); me.readyEvent.fire(); }},
在上面的代码中,如果 isReady 不是 true,则将其设置为 true,然后移除文档的监听事件。首
先调用 Ext.supports 的 init 方法检测当前运行环境的信息 ;然后调用 onWindowUnload 方法为
文档绑定 unload 事件,触发后会删除页面的所有元素;最后再调用 readyEvent 的 fire 方法,开
始执行我们定义的代码。
如果文档还没有加载完成,则执行 bindReadyEvent 方法,其代码如下:
40 Ext JS 权威指南
bindReadyEvent: function(){
var me = Ext.EventManager;
if (me.hasBoundOnReady) {
return;
}
if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', me.fireDocReady, false);
window.addEventListener('load', me.fireDocReady, false);
} else {
if (!me.checkReadyState()) {
document.attachEvent('onreadystatechange', me.checkReadyState);
me.hasOnReadyStateChange = true;
}
window.attachEvent('onload', me.fireDocReady, false);
}
me.hasBoundOnReady = true;
},
看懂以上代码就应该很清楚整个执行过程了。在代码中,如果没有在页面中绑定监听事
件,则绑定事件,非 IE 浏览器是绑定“DOMContentLoaded”事件,IE 是绑定 onload 事件。
对于旧版本的 IE,会调用 checkReadyState 方法检查页面是否准备好,因为旧版本 IE 只能使用
替代办法检查 DOMContentLoaded 事件。事件触发后执行 fireDocReady 方法。2
2.5 关于 Ext.BLANK_IMAGE_URL
在 Ext-more.js 文件中可找到 BLANK_IMAGE_URL 属性的默认值和定义。其默认值是:
BLAN K_IMAGE_URL : 'data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='
定义是:
BLAN K_IMAGE_URL : (isIE6 || isIE7) ? 'http:/' + '/www.sencha.com/s.gif' : 'data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='
代码中以“data”开头的数据代表一个 1*1 像素的空白透明图片。因为 IE 6 和 IE 8 不支持这样
格式的图片数据,所以必须为它指定一个图片路径,默认指向 http:///www.sencha.com/s.gif。这个图片有什么用呢?在菜单项的源代码(Item.js)中可找到菜单项的渲染模版代码:
renderTpl: [
'<tpl if="plain">',
'{text}',
'</tpl>',
'<tpl if="!plain">',
'<a class="' + Ext.baseCSSPrefix + 'menu-item-link" href="{href}"
具体信息可访问 http://javascript.nwbox.com/IEContentLoaded/。
第 2 章 从“Hello World”开始 41
<tpl if="hrefTarget">target="{hrefTarget}"</tpl> hidefocus="true"
unselectable="on">',
'<im g src="{icon}" class="' + Ext.baseCSSPrefix + 'menu-item-icon
{iconCls}" />',
'<spa n class="' + Ext.baseCSSPrefix + 'menu-item-text" <tpl
if="menu">style="margin-right: 17px;"</tpl> >{text}</span>',
'<tpl if="menu">',
'<im g src="' + Ext.BLANK_IMAGE_URL + '" class="' + Ext.baseCSSPrefix
+ 'menu-item-arrow" />',
'</tpl>',
'</a>',
'</tpl>'
],
在上面的代码中,Ext.BLANK_IMAGE_URL 被用作显示图片。哈哈,有点想不通为什
么用 1 个 1*1 像素的空白图片来作显示图片吧。别着急,看看它的 CSS 代码就清楚了。在
ext-all-debug.css 文件可找到有关“menu-item-arrow”的代码:
.x-menu-item-arrow { position: absolute; width: 12px; height: 9px; top: 9px; right: 0px; back ground: url('../themes/images/default/menu/menu-parent.gif') no-repeat
center center;}
真相出来了,Ext.BLANK_IMAGE_URL 只是一个占位符,目的是显示背景图片。这样
做主要有以下两个目的:
在更换主题时如果图片是固定的,有两种方法更新图片,一种是覆盖旧图片,一种是 ❑
重新定义图片路径。在你固定主题的情况下,覆盖图片的方法是可行的,但如果要由
用户自定义主题就不行了。重新定义图片的方法不但增加开发人员工作量,而且也不
能实现用户自定义主题功能。使用这种方法,只需要在 CSS 中指定背景图片就行了,
既不需要覆盖图片,也不需要去重新定义图片路径,非常方便实用。
如果使用 div 标记或 span 标记代替 img 标记,那么在这两个标记外增加链接的时候, ❑
因为标记内的内容为空,所以链接就会出现问题。如果在 div 或 span 内加入空格呢?
问题依旧一样,而且还会因为空格造成背景图片难于定位。有兴趣的可以自己做一下
测试。
明白了 Ext.BLANK_IMAGE_URL 的作用,回头再研究一下 2.3 节的示例中有关 Ext.BLANK_ IMAGE_URL 的三行代码。因为当浏览器是 IE 6 或 IE 7 时,图片地址会指向 http:///www.sencha.com/s.gif,如果访问不了 Sencha 网站,就会出现显示问题,所以需要将地址指向本地
地址。如果不是 IE 6 或 IE 7,那么使用默认值就可以了。
42 Ext JS 权威指南
2.6 关于字体
是不是觉得图 2-1 中的“欢迎”两字有点别扭?在目前所有的 Ext 版本中都存在这个问
题。主要是因为在所有版本的 Ext 中,标题这些地方使用的字体大小都是“11px”的,将它们
修改为“12px”就行了。修改方法是将你使用的 CSS 文件里的“11px”全部替换为“12px”。例如,你使用的是 ext-all.css 文件,那就将该文件里的“11px”全部替换为“12px”。
2.7 Ext JS 4 语法
1. 配置对象
Ext JS 的基本语法就是使用树状的配置对象来定义界面,其格式如下:
{ config_options1:value1,
config_options1:value2,
…
config_optionsn:valuen,
layout:{},
items:[
{},//配置对象
{}//配置对象
…
],
listeners:{
//定义事件(根据需要而定)
click:function(){},
dblclick:function(){}
…
}
}
格式中从 config_options1、config_options2 到 config_optionsn 都是 API 文档中对象的
配置项(config options)。很多初学者会错误地认为 API 不全,找不到配置项。事实上 API是完整的,只是有些布局隐含了面板或其他部件,但都在同一配置对象内定义。例如在使用
Accordion 布局的时候,想消除布局标题栏右边的小图标,需要使用 hideCollapseTool 属性,
而该属性是在 Panel 对象里的。在这方面,Ext JS 4 的 API 已经做了调整了,增加了一个其
他配置项(Other Configs)的列表。
属性 layout 可以是对象,也可以是字符。该属性表示在当前容器内使用什么布局来填充
子控件。如果没有特殊要求,直接使用布局的别名作为值,例如,2.3 节的示例中 Viewport 使用了 Fit 布局来放置子控件。如果有特殊要求,则要使用对象来定义该值。例如,如果使用
Hbox 布局,布局内的子控件需要居中对齐,则定义如下:
第 2 章 从“Hello World”开始 43
layout:{ type:'hbox', align:'middle'}
属性 items 是一个数组,可以在里面定义当前控件的子控件,里面可以是 1 个或多个配
置项,根据你的需要而定。例如 2.3 节的示例,在 Viewport 下使用了一个 Panel 面板作为其
面板。属性 listeners 是一个对象,可以在里面绑定事件,对象的属性就是事件名称,属性值
就是要执行的函数。
2. 关于 xtype
在使用 Ext JS 编写应用时,很多时候通过定义 xtype 来指定该位置使用什么组件,例
如 2.3 节的示例中的“xtype:"panel"”,这里指定使用面板作为当前位置的组件。这样做的
主要目的是简化代码。如果没有 xtype,就得先使用变量指向一个组件,然后将其加入父
组件中,或者直接在父组件的定义中加入一堆由 new 关键或 Ext.Create 方法创建的组件。
这不但影响了书写代码的效率,还影响了代码的可读性。有 xtype 存在,就不用担心这些
问题了。
在某些组件下,会默认其内部组件为某些组件,因而也就不需要书写 xtype 语句。例如,
在 2.3 节的示例中把 xtype 去掉,代码也能正常运行,因为面板是 Viewport 内默认的组件。一
般来说,没特别声明,使用的都是 Panel 组件。
定义 xtype,一般使用组件的别名。可在 API 文档的组件说明文档的顶部或 Component 对象的说明中找到组件的 xtype 值。
3. 使用 new 关键字创建对象
在 Ext JS 4 版本之前,一直使用 new 关键字创建对象,其语法如下:
new classname([config])
其中 calssname 是指类名 ;config 是可选参数,为类的配置对象(config options),类型
为 JSON 对象。在 2.3 节的示例中,Ext.Viewport 就是使用该方法创建的。
4. 使用 Ext.create 方法创建对象
Ext.create 方法是新增的创建对象的方法,其语法如下:
Ext.create(classname,[config])
其中 classname 可以是类的全名、别名或备用名 ;config 是可选参数,为类的配置对象
(config options),类型为对象。将 2.3 节的示例中的以下代码:
new Ext.Viewport({
修改为:
Ext.create('Ext.Viewport',{
44 Ext JS 权威指南
效果是一样的。那为什么要增加这样一个方法呢?我们来研究一下 create 方法的源代码。
在 ClassManger.js 文件中,可以找到以下代码:
Ext.apply(Ext, { create: alias(Manager, 'instantiate'), ... })
从代码可知 create 方法其实是 Ext.ClassManager 类的 instantiate 方法的别名,其代码如下:
instantiate: function() {
var name = arguments[0],
nameType = typeof name,
args = arraySlice.call(arguments, 1),
alias = name,
possibleName, cls;
if (nameType != 'function') {
if (nameType != 'string' && args.length === 0) {
args = [name];
name = name.xclass;
}
//省略调试代码
cls = this.get(name);
}
else {
cls = name;
}
if (!cls) {
possibleName = this.getNameByAlias(name);
if (possibleName) {
name = possibleName;
cls = this.get(name);
}
}
if (!cls) {
possibleName = this.getNameByAlternate(name);
if (possibleName) {
name = possibleName;
cls = this.get(name);
}
}
if (!cls) {
//省略调试代码
第 2 章 从“Hello World”开始 45
Ext.syncRequire(name);
cls = this.get(name);
} //省略调试代码
return this.getInstantiator(args.length)(cls, args);},
首先将 name 变量指向从参数中获取的类名,然后判断 name 是不是函数,如果不是,且其不
是字符串,则从 xclass 属性中获取类名。接着使用 get 方法获取对象,并将 cls 变量指向对象。
get 方法的源代码如下:
get: function(name) { var classes = this.classes; if (classes[name]) { return classes[name]; } var root = global, parts = this.parseNamespace(name), part, i, ln; for (i = 0, ln = parts.length; i < ln; i++) { part = parts[i]; if (typeof part != "string") { root = part; } else { if (!root || !root[part]) { return null; } root = root[part]; } } return root;},
代码中的 classes 对象包括了 Ext JS 的所有类,因而代码首先根据 name 从 classes 中寻找类,
如果存在,则返回对象;如果不存在,则说明是用户自定义的类,需要从 Ext.global 中寻找。
查找工作首先要做的是使用 parseNamespace 方法拆解类名,其代码如下:
parseNamespace: function(namespace) {
//省略调试代码
var cache = this.namespaceParseCache;
if (this.enableNamespaceParseCache) {
if (cache.hasOwnProperty(namespace)) {
return cache[namespace];
}
}
var parts = [],
rewrites = this.namespaceRewrites,
root = global,
46 Ext JS 权威指南
rewrite, from, to, i, ln;
for (i = 0, ln = rewrites.length; i < ln; i++) {
rewrite = rewrites[i];
from = rewrite.from;
to = rewrite.to;
if (namespace === from || namespace.substring(0, from.length) === from) {
namespace = namespace.substring(from.length);
if (typeof to !== "string") {
root = to;
} else {
parts = parts.concat(to.split("."));
}
break;
}
}
parts.push(root);
parts = parts.concat(namespace.split("."));
if (this.enableNamespaceParseCache) {
cache[namespace] = parts;
}
return parts;
},
代码中 namespaceParseCache 对象的作用是使用类名作为关键字并指向拆解后的类名数组,
这样,当该类被多次使用时,就可以直接从 namespaceParseCache 对象中获取拆解的类名数
组,而不需要再拆解一次,从而加快运行速度。
通过 enableNamespaceParseCache 属性可配置是否开启 namespaceParseCache 对象的缓存
功能,默认是开启的。如果在 enableNamespaceParseCache 中已存在类名的拆解结果,则返回
结果。
属性 namespaceRewrites 的定义如下:
namespaceRewrites: [{ from: 'Ext.', to: Ext}],
在其他文件中找不到为 namespaceRewrites 添加数据的代码,因而在循环中,如果类名
是以“Ext.”开头的,root 会指向 Ext 对象;如果不是,root 就是初始值,指向 Ext.global。接着,将 root 指向的对象存入 parts 数组,再将拆分类名产生的数组与 parts 数组合并。如
果开启了缓存功能,在 namespaceParseCache 对象中,将以类名为关键字指向 parts 数组,最后
第 2 章 从“Hello World”开始 47
返回 parts 数组。数组返回后,通过循环的方式来查找类定义。因为返回数组(parts)的第 1个数据不是 Ext 对象就是 Ext.golbal 对象,因而变量 root 在第一次循环时,会指向 Ext 对象
或 Ext.golbal 对象。在后续循环中,会根据拆分的类名在 Ext 或 Ext.gdbal 对象中逐层找下
去。如果期间“root[part]”不是对象,说明不存在该类,返回 null。如果找到了,返回 root指向的对象。
如果 cls 不是指向对象,则尝试使用别名方法查找对象。查找时,首先使用 getName-ByAlias 方法将别名转换为类名,其代码如下:
getNameByAlias: function(alias) { return this.maps.aliasToName[alias] || '';},
在 maps 属性中保存了以下 3 个对象:
maps: { alternateToName: {}, aliasToName: {}, nameToAliases: {}},
简单来说,maps 中的对象就是一个对照表,通过它就可轻松地找到类名和别名。对
象 alternateToName 的作用是通过备用名获得类名,它以备用名作为关键字、类名作为值 ;
对象 aliasToName 的作用是通过别名获取类名,它以别名作为关键字、类名作为值 ;对象
nameToAliases 的作用是通过类名获取别名,它以类名作为关键字、别名作为值。在创建类
的时候,会把类名、别名和备用名写入对照表。
如果 getNameByAlias 方法返回的不是空字符串,说明变量 name 保存的是别名,需将
name 修改为类名,然后通过 get 方法获取对象。如果使用别名也找不到对象,则可尝试使用
备用名来查找对象,执行代码与使用别名查找的方式类似。如果还没有找到,则可尝试使用
syncRequire 方法下载对象,其代码如下:3
syncRequire: function() { this.syncModeEnabled = true; this.require.apply(this, arguments); this.refreshQueue(); this.syncModeEnabled = false;},
在上面的代码中,syncModeEnabled 方法可控制 Loader 对象的 require 方法通过同步的方式
去下载对象。
如果实在是找不到,就会抛出异常。最后使用 getInstantiator 方法实例化对象,其代码
如下:
getInstantiator: function(length) { if (!this.instantiators[length]) { var i = length,
相关信息可阅读 4.4.4 节。
48 Ext JS 权威指南
args = [];
for (i = 0; i < length; i++) { args.push('a['+i+']'); }
this .instantiators[length] = new Function('c', 'a', 'return new c('+args.join(',')+')');
}
return this.instantiators[length];},
代码中的数组 instantiators 起缓存作用,因为同样的参数长度产生的匿名函数是一样,没必
要每次都重新创建一次。最后返回的匿名函数如下:
(function anonymous(c, a) {return new c(a[0], a[1], … ,a[n]);})
代码中的 n 就是参数的长度,例如 length 的值为 5,则匿名函数如下:
(function anonymous(c, a) {return new c(a[0], a[1], a[2], a[3], a[4]);})
匿名函数返回后会立即执行,参数 c 指向对象的 cls,a 指向实例化对象时的参数。也就
是说,对象是在匿名函数中实例化的,这样可以保证对象的作用域是安全的。
从以上的分析可以了解到,创建对象的新方法不但可以实现动态加载,而且可以保证作
用域的安全,应该优先使用。
5. 使用 Ext.widget 或 Ext.createWidget 创建对象
Ext.widget 的作用是使用别名来创建对象,其语法与 2.7.2 节介绍的 Ext.create 是一样的,
只是 classname 使用的是对象的别名。Ext.createWidget 是 Ext.widget 的另外一种使用方法而已,
在源代码中的定义如下:
Ext.createWidget = Ext.widget;
Ext.widget 的代码如下:
widget: function(name) { var args = slice.call(arguments); args[0] = 'widget.' + name;
return Manager.instantiateByAlias.apply(Manager, args);},
也就是说,它使用 ClassManager 对象的 instantiateByAlias 方法创建对象,其代码如下:
instantiateByAlias: function() { var alias = arguments[0], args = slice.call(arguments), className = this.getNameByAlias(alias);
if (!className) {
第 2 章 从“Hello World”开始 49
className = this.maps.aliasToName[alias];
//省略调试代码
Ext.syncRequire(className); }
args[0] = className;
return this.instantiate.apply(this, args);},
代码先是根据别名寻找类名,如果找不到就抛出异常或尝试使用 syncRequire 方法加载
类文件,最后调用 instantiate 方法创建对象。
6. 使用 Ext.ns 或 Ext.namespace 定义命名空间
在使用 C# 或 Java 做开发时,很多时候都会使用命名空间来组织相关类和其他类型。在
JavaScript 中并没有提供这样的方式,不过可以通过定义一个全局对象的方式来实现,譬如你
要定义一个“MyApp”的命名空间,你可以这样:
MyApp ={};
这里要注意,不要使用 var 语句去定义全局对象。
在 Ext JS 中,使用 Ext.namespace 方法可创建命名空间,其语法如下:
Ext.namespace(namespace1,namespace2,…,namespacen)
其中 namespace1、namespace2 和 namespacen 都是字符串数据,是命名空间的名字,例如:
//推荐用法
Ext.namespace("MyApp","MyApp.data","MyApp.user");Ext.ns("MyApp","MyApp.data","MyApp.user");//或者,不建议使用
Ext.namespace(,"MyApp.data","MyApp.user");Ext.ns("MyApp.data","MyApp.user");
Ext.ns 只是 Ext.namespace 的简单写法。
在 ClassManager.js 中可找到 Ext.namespace 的 createNamespace 方法的实现代码:
createNamespaces: function() { var root = global, parts, part, i, j, ln, subLn;
for (i = 0, ln = arguments.length; i < ln; i++) { parts = this.parseNamespace(arguments[i]);
for (j = 0, subLn = parts.length; j < subLn; j++) { part = parts[j];
if (typeof part !== 'string') { root = part;
50 Ext JS 权威指南
} else { if (!root[part]) { root[part] = {}; }
root = root[part]; } } }
return root;},
从上面代码可以看到,Ext.namespace 创建的命名空间是保存在 global 对象里的。注意
循环结构,数组长度都是先保存到一个局部变量再使用的,原因是 JavaScript 与 C#、Java等语言不同,每次循环都要计算一次数组长度,这样会降低性能。在第一个循环下,使用了
parseNamespace 方法拆分命名空间的字符串。
在 Ext JS 初始化时,this 指向的是 window 对象,因而 global 指向的是 window 对象。
数组返回到 createNamespace 方法后,开始执行完循环,最后在 window 对象下生成了以
下结构的全局对象:
{ MyApp:{ Data:{} }}
虽然生成的是全局对象,但是在作用域链上,它可以直接在 Ext JS 的作用域链内搜索对
象,而不需要到最外层的全局作用域链搜索对象,这是最大的不同。
在任何编程语言里都不提倡使用全局变量,尤其是在 JavaScript 里,全局对象在每一层的
作用域链里都搜索不到时,才会在全局作用域链里搜索,效率相当低。因此,在 JavaScript里不仅不推荐使用全局变量,而且建议当有一个变量不是在当前作用域定义的,并要多次使
用时,建议将该变量保存在局部变量中,使用局部变量来进行操作,以避免在域链中多次搜
索对象而降低性能。
因此,建议的方法是在 Ext 对象下创建命名空间,譬如创建以下的命名空间:
Ext.ns("Ext.MyApp","Ext.MyApp.data","Ext.MyApp.user");
最好的方法是使用 Ext 已定义的命名空间“Ext.app”,如果是扩展或插件则使用“Ext.ux”。
7. 使用 Ext.define 定义新类
在 Ext JS 3 中,定义新类使用的是 Ext.extend 方法,因为 Ext JS 4 的类系统进行了重新架
构,所以定义新类的方法也修改为了 Ext.define 方法,其语法如下:
Ext.define(classname,properties,callback);
其中:
第 2 章 从“Hello World”开始 51
classname:要定义的新类的类名。 ❑
properties :新类的配置对象,对象里包含了类的属性集对象,表 2-1 列出了常用的属 ❑
性及其说明。
callback:回调函数,当类创建完后执行该函数。 ❑
表 2-1 常用的属性集对象及其说明
属 性 说 明
要继承的类的名称,譬如 subclass 继承自 superclass,则定义如下:
Ext.define("subclass",{
extend extend:"superclass",
…
});
类的备用名称,例如:
Ext.define("subclass",{
alternateClassName:"myclass",
alternateClassName …
});
这样 subclass 就有了一个备用名称“myclass”,在使用 Ext.create 创建对
象时就可使用 myclass 这个名称创建 subclass 实例了
alias 类的别名,用法可参考 alternateClassName 属性
需要使用到的类名数组,在动态加载时会根据该属性去下载类,譬如
subclass。需要使用到 Ext.EventManager 和 Ext.util.MixedCollection,则定
义如下:
requires Ext.define("subclass",{
req u ires:["Ext.EventManager","Ext.util.MixedCollection"],
…
});
构造属性,一般用来初始化类的配置项和调用其父类的方法,例如:
Ext.define("subclass",{
extend:"superclass",
constructor:function(config){
this.initConfig(config);
constructor …
this.callParent("mehtodName");
return this;
},
…
});
52 Ext JS 权威指南
属 性 说 明
为类添加特殊的功能,例如要为 subclass 混合 Ext.util.Observable 和 Ext.util.Animate 功能,则代码如下:
Ext.define("subclass",{
mixins:{
ob:"Ext.util.Observable",
fx:"Ext.util.Animate"
},
constructor:function(config){
var me=this;
me.initConfig(config);
mixins me.mixins.ob.constructor.call(me);
…
return me;
},
fx:function(){
var me=this;
…
return me.mixins.fx.animate.apply(me,arguments);
},
…
});
定义类的配置项,创建时会自动为 config 里的每个属性添加 set 和 get 方法,例如:
Ext.define("subclass",{
config:{
width:100,
height:100
},
constructor:function(config){
config this.initConfig(config);
…
return this;
},
…
});
var mysubclass=new subclass();
mysubclass.setWidth(200);
mysubclass.getHeight();
( 续 )
第 2 章 从“Hello World”开始 53
属 性 说 明
config
代码中,initConfig 方法执行后就会为 congfig 的属性创建 set 和 get 方法,这样创建类的示例后,就可以直接通过 set 和 get 方法读写属性的值,譬
如例子中用 setWidth 设置了配置中 width 的值,使用 getHeight 获取 height的值
定义静态方法,例如:
Ext.define("subclass",{
statics:{
method:function(args){
return new this(args);
}
statics },
constructor:function(config){
this.initConfig(config);
…
},
…
});
var myclass=subclass.method("class");
类的创建过程将在第 4 章讲解,下面我们来实践一下。在 Firefox 中打开 2.3 节的示例,
然后打开 Firebug,在控制台中输入以下代码:
Ext.define("Calculator",{ constructor:function(){ return this; }, plus:function(v1,v2){ return v1+v2; }, minus:function(v1,v2){ return v1-v2; }, multiply:function(v1,v2){ return v1*v2; }, divid:function(v1,v2){ return v1/v2 }});var cal=new Calculator();console.log(cal.plus(87,28)); //115console.log(cal.minus(87,28)); //59console.log(cal.multiply(7,8)); //56console.log(cal.divid(28,7)); //4
代码中定义了一个名称为“Calculator”的类,它包含加、减、乘、除 4 个方法。运行后,
( 续 )
56 Ext JS 权威指南
继续我们的实践,这次要做的是将 HEX、BIN、OCT 这 3 个实现不同进制转换的类混合
到 NewCalculator 类中。第一步要做的是定义 3 个实现进制转换的类:
Ext.define('HEX',{ hex:function(v1){ return v1.toString(16); }});Ext.define('BIN',{ bin:function(v1){ return v1.toString(2); }});Ext.define('OCT',{ oct:function(v1){ return v1.toString(8); }});
然后将 HEX、BIN 和 OCT 三个功能混合到继承自 Calculator 类的 NewCalculator 类中,
这样 NewCalculator 除了实现加减乘除功能外,还能实现十进制到二进制、八进制和十六进
制的转换,代码如下:
Ext.define('NewCalculator',{ extend:'Calculator', mixins:{ Hex:'HEX', Bin:'BIN', Oct:'OCT' }, convert:function(value,type){ switch(type){ case 2: return this.bin(value) break; case 8: return this.oct(value) break; default: return this.mixins.Hex.hex.call(this,value); break; } }});
var ncal=new NewCalculator();console.log(ncal.convert(25,2)); //11001console.log(ncal.convert(25,8)); //31console.log(ncal.convert(25,16)); //19
在代码中,使用 convert 方法可进行进制转换,第 2 个参数 type 表示要转换的进制类型。
调用 mixins 属性定义的混合功能有两种方法 :第一种是二进制和八进制中使用的直接调
60 Ext JS 权威指南
在图 2-6 中,3 个进制转换方法不在原型里,而是直接挂在类节点下。现在大家应该清楚
静态类和其他类的区别了。
2.8 本地化
Ext JS 提供了 45 种语言的本地化文件,在 Ext JS 文件包的 local 中可以找到这些文件。
如果要实现中文简体的本地化,可在页面的 head 部分加入以下语句:
<script type="text/javascript" src="../Ext4/locale/Ext-lang-zh_CN.js"></script>
打开语言包,会看到在 Ext.onReady 函数内有许多判断语句,其作用是判断 Ext 对象是否
存在,如果存在则修改对象的属性,将属性带有的语言信息转换为对应的本地化语言信息。
2.9 为本书示例准备一个模板
为了便于示例的讲解,特意准备了这个模板。在没有特殊说明的情况下,本书后续的示
例都会在此模板的基础上添加额外代码,该模板的代码将不会出现在示例的代码清单里。
新建一个 Templates.html 文件,加入如代码清单 2-2 所示的代码。
代码清单 2-2 模板代码清单
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html><head> <title></title> <link rel="stylesheet" type="text/css" href="../Ext4/resources/css/ext-all.
css"/> <script type="text/javascript" src="../Ext4/bootstrap.js"></script> <script type="text/javascript" src="../Ext4/locale/Ext-lang-zh_CN.js"></
script> <style type="text/css"> /*在此添加样式代码 */ </style></head><body> <!--在此添加 HTML代码 --> <script type="text/javascript"> Ext.onReady(function(){ if(Ext.BLANK_IMAGE_URL.substr(0,4)!="data"){
Ext.BLANK_IMAGE_URL="./images/s.gif"; } //在此添加 Ext JS代码
}); </script></body></html>
第 2 章 从“Hello World”开始 61
模板主要包含了一个页面的完整结构,配置使用了 Ext JS 库,包含简体中文包,并已定
义好 onReady 函数和图片检查。
2.10 本章小结
本章通过一个简单的例子学习了使用 Ext JS 的方法及其基本语法。尤其是在 2.7 节“使用
Ext.define 定义新类”中,我们实践了使用 Ext.define 定义新类的几种方法,目的是让大家熟
悉 Ext JS 中的类定义并了解其中的变化。第 4 章会继续深入探讨类的生成过程,这对了解和
使用 Ext JS 是非常有帮助的。定义模型和创建模型的语法会在第 9 章(数据模型)讲解。