EXT简介
无论你是Ext库的新手,抑或是想了解Ext的人,本篇文章的内容都适合你。本文将简单地介绍Ext的几个基本概念,和如何快速地做出一个动态的页面并运行起来,假设读者已具备了一些JavaScript经验和初级了解HTML Dom。
目錄
• 1 下载Ext
• 2 开始!
• 3 Element:Ext的核心
• 4 获取多个DOM的节点
• 5 响应事件
• 6 使用Widgets
o 6.1 MessageBox
o 6.2 Grid
o 6.3 还有更多的..
• 7 使用Ajax
o 7.1 PHP
o 7.2 ASP.Net
o 7.3 Cold Fusion
下载Ext
如果你未曾下载过,那应从这里下载最新版本的Ext http://extjs.com/downloads 。
针对你的下载需求,有几个不同的弹性选项。通常地,最稳定的版本,是较多人的选择。下载解包后,那个example文件夹便是一个探索Ext的好地方!
开始!
Download Example File
• IntroToExt.zip
我们将使用Ext,来完成一些JavaScript任务。
Zip文件包括三个文件:ExtStart.html, ExtStart.js和ExtStart.css。解包这三个文件到Ext的安装目录中(例如,Ext是在“C:\code\Ext\v1.0”中,那应该在"v1.0"里面新建目录“tutorial”。双击ExtStart.htm,接着你的浏览器打开启动页面,应该会有一条消息告诉你配置已完毕。如果是一个Javascript错误,请按照页面上的指引操作。
在你常用的IDE中或文本编辑器中,打开ExtStart.js看看:
Ext.onReady可能是你接触的第一个方法。这个方法是指当前DOM加载完毕后,保证页面内的所有元素能被Script引用(reference)。你可删除alert()那行,加入一些实际用途的代码试试:
Element:Ext的核心
大多数的JavaScript操作都需要先获取页面上的某个元素(reference),好让你来做些实质性的事情。传统的JavaScript方法,是通过ID获取Dom节点的:
var myDiv = document.getElementById('myDiv');
这毫无问题,不过这样单单返回一个对象(DOM节点),用起来并不是太实用和方便。为了要用那节点干点事情,你还将要手工编写不少的代码;另外,对于不同类型浏览器之间的差异,要你处理起来可真头大了。
进入Ext.element 对象。元素(element)的的确确是Ext的心脏地带,--无论是访问元素(elements)还是完成一些其他动作,都要涉及它。Element的 API是整个Ext库的基础,如果你时间不多,只是想了解Ext中的一两个类的话,Element一定是首选!
由ID获取一个Ext Element如下(首页ExtStart.htm包含一个div,ID名字为“myDiv”,然后,在ExtStart.js中加入下列语句):
Ext.onReady(function() {var myDiv = Ext.get('myDiv');});
再回头看看Element对象,发现什么有趣的东东呢?
• Element包含了常见的DOM方法和属性,提供一个快捷的、统一的、跨浏览器的接口(若使用Element.dom的话,就可以直接访问底层DOM的节点。);
• Element.get()方法内置缓存处理(Cache),多次访问同一对象效率上有极大优势;
• 内置常用的DOM节点的动作,并且是跨浏览器的定位的位置、大小、动画、拖放等等(add/remove CSS classes, add/remove event handlers, positioning, sizing, animation, drag/drop)。
这意味着你可用少量的代码来做各种各样的事情,这里仅仅是一个简单的例子(完整的列表在ElementAPI中)。
继续在ExtStart.js中,在刚才我们获取好myDiv的位置中加入:
获取多个DOM的节点
通常情况下,想获取多个DOM的节点,难以依靠ID的方式来获取。有可能因为没设置ID,或者你不知道ID,又或者直接用ID方式引用有太多元素了。这种情况下,你就会不用ID来作为获取元素的依据,可能会用属性(attribute)或CSS Classname代替。基于以上的原因,Ext引入了一个功能异常强大的Dom Selector库,叫做DomQuery。
DomQuery可作为单独的库使用,但常用于Ext,你可以在上下文环境中(Context)获取多个元素,然后通过Element接口调用。令人欣喜的是,Element对象本身便有Element.selcect的方法来实现查询,即内部调用DomQuery选取元素。这个简单的例子中, ExtStart.htm包含若干段落(
标签),没有一个是有ID的,而你想轻松地通过一次操作马上获取每一段,全体执行它们的动作,可以这样做:
// 每段高亮显示
Ext.select('p').highlight();
DomQuery的选取参数是一段较长的数组,其中包括W3C CSS3 Dom选取器、基本XPatch、HTML属性和更多,请参阅DomQuery API文档以了解这功能强大的库个中细节。
响应事件
到这范例为止,我们所写的代码都是放在onReady中,即当页面加载后总会立即执行,功能较单一——这样的话,你便知道,如何响应某个动作或事件来执行你希望做的事情,做法是,先分配一个function,再定义一个event handler事件处理器来响应。我们由这个简单的范例开始,打开ExtStart.js,编辑下列的代码:
加载好页面,代码依然会执行,不过区别是,包含alert()的function是已定义好的,但它不会立即地被执行,是分配到按钮的单击事件中。用浅显的文字解释,就是:获取ID为'myDottom'元素的引用,监听任何发生这个元素上被单击的情况,并分配一个function,以准备任何单击元素的情况。
正路来说,Element.select也能做同样的事情,即作用在获取一组元素上。下一例中,演示了页面中的某一段落被单击后,便有弹出窗口:
这两个例子中,事件处理的function均是简单几句,没有函数的名称,这种类型函数称为“匿名函数(anonymous function)”,即是没有名的的函数。你也可以分配一个有名字的event handler,这对于代码的重用或多个事件很有用。下一例等效于上一例:
到目前为止,我们已经知道如何执行某个动作。但当事件触发时,我们如何得知这个event handler执行时是作用在哪一个特定的元素上呢?要明确这一点非常简单,Element.on方法传入到even handler的function中(我们这里先讨论第一个参数,不过你应该浏览API文档以了解even handler更多的细节)。在我们之前的例子中,function是忽略这些参数的,到这里可有少许的改变,——我们在功能上提供了更深层次的控制。必须先说明的是,这实际上是Ext的事件对象(event object),一个跨浏览器和拥有更多控制的事件的对象。例如,可以用下列的语句,得到这个事件响应所在的DOM节点:
注意得到的e.target是DOM节点,所以我们首先将其转换成为EXT的Elemnet元素,然后执行欲完成的事件,这个例子中,我们看见段落是高亮显示的。
使用Widgets
(Widget原意为“小器件”,现指页面中UI控件)
除了我们已经讨论过的核心JavaScript库,当前的Ext亦包括了一系列的最前端的JavaScirptUI组件库。文本以一个常用的widget为例子,作简单的介绍。
MessageBox
比起略为沉闷的“HelloWolrd”消息窗口,我们做少许变化,前面我们写的代码是,单击某个段落便会高亮显示,现在是单击段落,在消息窗口中显示段落内容出来。
在上面的paragraphClicked的function中,将这行代码:
Ext.get(e.target).highlight();
替换为:
这里有些新的概念需要讨论一下。在第一行中我们创建了一个局部变量(Local Variable)来保存某个元素的引用,即被单击的那个DOM节点(本例中,DOM节点指的是段落paragrah,事因我们已经定义该事件与< p>标签发生关联的了)。为什么要这样做呢?嗯...观察上面的代码,我们需要引用同一元素来高亮显示,在MessageBox中也是引用同一元素作为参数使用。
一般来说,多次重复使用同一值(Value)或对象,是一个不好的方式,所以,作为一个具备良好OO思维的开发者,应该是将其分配到一个局部变量中,反复使用这变量!
现在,为了我们接下来阐述新概念的演示,请观察MessageBox的调用。乍一看,这像一连串的参数传入到方法中,但仔细看,这是一个非常特别的语法。实际上,传入到MessageBox.show的只有一个参数:一个Object literal,包含一组属性和属性值。在Javascript中,Object Literal是动态的,你可在任何时候用{和}创建一个典型的对象(object)。其中的字符由一系列的name/value组成的属性,属性的格式是[property name]:[property value]。在整个Ext中,你将会经常遇到这种语法,因此你应该马上消化并吸收这个知识点!
使用Object Literal的原因是什么呢?主要的原因是“可伸缩性(flexibility)”的考虑",随时可新增、删除属性,亦可不管顺序地插入。而方法不需要改变。这也是多个参数的情况下,为最终开发者带来不少的方便(本例中的MessageBox.show())。例如,我们说这儿的foo.action方法,有四个参数,而只有一个是你必须传入的。本例中,你想像中的代码可能会是这样的foo.action(null, null, null, 'hello').,若果那方法用Object Literal来写,却是这样, foo.action({ param4: 'hello' }),这更易用和易读。
Grid
Grid是Ext中人们最想先睹为快的和最为流行Widgets之一。好,让我们看看怎么轻松地创建一个Grid并运行。用下列代码替换ExtStart.js中全部语句:
这看上去很复杂,但实际上加起来,只有七行代码。第一行创建数组并作为数据源。实际案例中,你很可能从数据库、或者WebService那里得到动态的数据。接着,我们创建并加载data store, data store将会告诉Ext的底层库接手处理和格式化这些数据。接着,我们定义一个column模型,用来轻松地调配Grid的每一列参数。最后我们生成这个Grid,传入data store和column模型两个对象,进行渲染并选好第一行。不是太困难吧?如果一切顺利,完成之后你会看到像这样的:
当然,你可能未掌握这段代码的某些细节(像MemoryProxy究竟是什么?)但先不要紧,这个例子的目的是告诉你,用少量的代码,创建一个富界面的多功能的UI组件而已——这是完全可能的,只要读者您有兴趣学习。这儿有许多学习Grid的资源。Ext Grid教程、交叉Gird演示和Grid API文档。
还有更多的..
这只是冰山一角。还有一打的UI Widgets可以供调用,如 layouts, tabs, menus, toolbars, dialogs, tree view等等。请参阅API文档中范例演示。
使用Ajax
在弄好一些页面后,你已经懂得在页面和脚本之间的交互原理(interact)。接下来,你应该掌握的是,怎样与远程服务器(remote server)交换数据,常见的是从数据库加载数据(load)或是保存数据(save)到数据库中。通过JavaScript异步无刷新交换数据的这种方式,就是所谓的Ajax。Ext内建卓越的Ajax支持,例如,一个普遍的用户操作就是,异步发送一些东西到服务器,然后,UI元素根据回应(Response)作出更新。这是一个包含text input的表单,一个div用于显示消息(注意,你可以在ExtStart.html中加入下列代码,但这必须要访问服务器):
<div id="msg" style="visibility: hidden"></div>
Name: <input type="text" id="name" /><br />
<input type="button" id="oKButton" value="OK" />
接着,我们加入这些处理交换数据的JavaScript代码到文件ExtStart.js中(用下面的代码覆盖):
这种模式看起来已经比较熟悉了吧!先获取按钮元素,加入单击事件的监听。在事件处理器中(event handler),我们使用一个负责处理Ajax请求、接受响应(Response)和更新另一个元素的Ext内建类,称作UpdateManager。 UpdateManager可以直接使用,或者和我们现在的做法一样,通过Element的load方法来引用(本例中该元素是id为“msg“的 div)。当使用Element.load方法,请求(request)会在加工处理后发送,等待服务器的响应(Response),来自动替换元素的 innerHTML。简单传入服务器url地址,加上字符串参数,便可以处理这个请求(本例中,参数值来自“name”元素的value),而text值是请求发送时提示的文本,完毕后显示那个msg的div(因为开始时默认隐藏)。当然,和大多数Ext组件一样,UpdateManager有许多的参数可选,不同的Ajax请求有不同的方案。而这里仅演示最简单的那种。
PHP
最后一个关于Ajax隐晦的地方就是,服务器实际处理请求和返回(Resposne)是具体过程。这个过程会是一个服务端页面,一个Servlet,一个 Http调度过程,一个WebService,甚至是Perl或CGI脚本,即不指定一个服务器都可以处理的http请求。让人无法预料的是,服务器返回什么是服务器的事情,无法给一个标准的例子来覆盖阐述所有的可能性。(这段代码输出刚才我们传入'name'的那个值到客户端,即发送什么,返回什么)。
使用Ajax的真正挑战,是需要进行适当的手工编码,并相应格式化为服务端可用接受的数据结构。有几种格式供人们选择(最常用为JSON/XML)。正因 Ext是一种与服务器语言免依赖的机制,使得其它特定语言的库亦可用于Ext处理Ajax服务。只要页面接受到结果是EXT能处理的数据格式,Ext绝不会干涉服务器其他的事情!要全面讨论这个问题,已超出本文的范围。推荐正在Ajax环境下开发的您,继续深入阅读Ext Ajax教程。
下一步是?
现在你已经一睹Ext其芳容,知道她大概能做些什么了。下面的资源有助您进一步深入了解:
EXT源码概述
揭示源代码
Javascript是一门解释型的语言,意味着在运行之前代码是没有经过编译的。按照这种理论,在你网站上所发播的Ext代码是我们看的懂的(human-readible)。我这里说“理论上”,是因为实际情况中,很多源代码是经过某些自动化步骤的处理,生成很小几行的文件最终发布的,通过剔除空白符号和注释,或混淆等的方法,以减小文件大小。
仔细看看EXT标准源码ext-core.js,你会发现这是一段超长的源码。这是刚才提及的自动化步骤生成的结果--对浏览器来说不错!可是对于我们是难以阅读的。
ext-core.js
...
接着看下去的是ext-core-debug.js (注意在文件名后面加上-debug的JS文件), 我会发现是全部已格式化好的源代码。这个文件是配合调时器所使用的,像Firebug的工具能够可以让你一步一步地、一行一行地调试代码。你也会发现文件的体积将近大了一倍之多,这便是没有压缩或混淆的缘故。
ext-core-debug.js
...
该调试版本可以在调试阶段方便地检查EXT库运行到哪一步,但是你还是会错过一个有价值的...代码注释!要完整地看到代码,就要阅读真正的原始代码!
发布Ext源码时的一些细节
你在download得到的压缩文档,包含在这些文件夹之中的,有一source的子目录。在这个文件夹里面,正如所料,是全部的EXT的源文件,遵从Lesser GNU (LGPL) 开源的协议。对于EXT开发者来说应该非常适合。
用你日常使用文本编辑器打开源代码的任意一个文件(推荐有高亮显示的编辑器,或是在这里full-featured IDE看看),便可以开始我们的探险!
我应该从哪里开始?
Ext代码库里面包含了许多各种各样的文件,甚至令人觉得有点望而生畏。好在,Ext是一个通过充分考虑后而设计的JavaScript库,--在底层的代码为各项应用提供稳健的基础如跨浏览器的各种DOM操控,使得在上层的类classes运行于一个较高级的抽象层面(class 一术语与我们已习惯的Java和C++语言稍微有所不同,但一些概念如继承则可是如此类推去理解的--有关面向对象的JavaScript的更多资料,请参见Introduction to object-oriented (OO) JavaScript)。
这意味着,当浏览源码的时候,采取“自顶向下(bottom-up)”还是“自下向顶(top-down)”的方式,都是无关紧要的。你所熟悉API里面的代码已经是属于最高的抽象层面的范畴,你可以根据你的兴趣,顺着这些你熟悉的API逐步深入。但是你若赞同我的看法,并打算深入了解其个中原理,最理想的地方是从底层代码开始。
适配器Adapters
浏览器读取第一个源文件,当中的一个任务就是创建Ext对象本身。 Ext.js
Ext = {};
Ext成型于YahooUI的Javascript库的扩展。在当时,Ext须依赖YUI的底层代码来处理跨浏览器的问题。现在ExtJS已经是独立、免依赖的库了(standalone ),你可将YUI替换为另外你所选择javascript库,如prototype、jQuery、或者是这些之中的最佳选择,-Ext自带的底层库。负责将这些库(包括Ext自带的底层库)映射为Ext底层库的这部分代码,我们称之为适配器(Adapters)。这部分源码位于source/adapter的子目录。当项目引入Ext的时候便需要选择好你准备使用的适配器。
核心Core
source/core中的文件是构建于适配器API之上的“相对”最底层的源码。有些的源码甚至“底层”到直接为独立库的代码直接使用。这意味着应先了解和学习这整个库,再学习剩余的部分也不迟。要了解Ext的各种“Magic”和核心层面,就应该把重点放在source/core 目录下的各个源代码。
Javascript中的作用域(scope)
事前准备
学习本教程的最佳方法是随手准备好Firefox中的工具Firebug。这样使得您可以即刻测试教程的例子。
如果机子上还没有FireFox和FireBug,就应该尽快安装一套来用。
定义
作用域scope
1.(名词)某样事物执行、操作、拥有控制权的那么一个区域 [1]
2. (名词) 编写程序时,程序之中变量的可见度;例如,一个函数能否使用另外一个函数所创建的变量。[2]
可是这能够说明什么问题呢? 每当有人在说“这是作用域的问题”或“作用域搞错了”的时候,那就是说某个函数运行起来的时候,找不到正确变量的位置。这样我们便知道应该从哪一方面入手,查找出问题所在。
正式开始
实际上每一个你定义的函数都是某个对象的方法。甚至是这样的写法:
function fn() {
alert(11);
}
老兄你不是故弄玄虚吧~。做一个这样的演示可真得是简单得要命。没错!本例不需要任何Javascript文件,服务器或html。你只要打开 firefox,弹出firebug,点击console tab。在Firefox状态栏上面看到有>>>提示的地方就可以输入了。
输入:
function fn() { alert(11); };
然后回车。一切安然...你刚才做的实际上是定义了一个函数fn。接着试试:
fn();
然后回车。得到11的警告窗口?还不错吧?接着试试:
window.fn();
this.fn();
得到一样的结果吧?这是因为函数fn是window对象的一个方法,在第二行的"this"的作用域实际指向了windows对象。不过多数情况中你不需要像这样window.myFunction(...)地调用函数,这样太麻烦了,程序员工作起来会很不方便。
window对象
window 对象总是存在的,你可理解其为一个浏览器窗口对象。它包含了其它所有的对象如document 和所有的全局变量。
你可以打开Firebug,切换到 Script 页面并在Firebug右侧的New watch expression... 里面输入 window。观察window对象究竟有什么在里面。
接着,尝试找出我们之前定义过的fn函数。
另外,每个frame或iframe拥有其自身的window对象,其自身的全局空间。
理解作用域
接下的内容开始有点复杂了。切换到Firebug Console标签页然后输入:
var o1 = {testvar:22, fun:function() { alert('o1: ' + this.testvar); }};
var o2 = {testvar:33, fun:function() { alert('o2: ' + this.testvar); }};
结果是什么?你声明了o1 和 o2两个对象,分别都有一些属性和方法,但值不同。
接着试试:
fun();
window.fun();
this.fun();
出错了,是吧?因为window对象(等价于this)并没有fun的方法。试一试下面的:
o1.fun();
o2.fun();
22和33出来了?非常好!
接下来这部分的内容最复杂啦。基于这个原始的函数,如果对象的数量多的话,你必须为每个对象加上这个函数-明显是重复劳动了。这样说吧,o1.fun写得非常清晰的而且为了搞掂它已经占用了我一个星期的开发时间。想象一下代码到处散布着this变量,怎么能不头疼?如果要将调用(执行)的o1.fun方法但this会执行o2,应该怎么实现呢?试一试下面的:
o1.fun.call(o2);
明白了吗?当执行o1的fun方法时你强行将变量this指向到o2这个对象,换句话说,更加严谨地说:o1.fun的方法在对象o2的作用域下运行。
当运行一个函数,一个对象的方法时,你可将作用域当作this值的变量。
变量的可见度
变量的可见度和作用域的关系非常密切。我们已经了解到,可在任何对象的外部,声明变量,或在全局的函数(函数也是变量的一种)也可以,更严格说,它们是全局对象window的属性。 全局变量在任何地方都可见;无论函数的内部还是外部。如果你在某一个函数内修改了一个全局变量,其它函数也会得知这个值是修改过的。
对象可以有它自己的属性(像上面的testvar),这些属性允许从内部或是外部均是可见的。试:
alert(o1.testvar); // 从外部访问o1的属性testvar
从内部访问的演示可在两个测试对象的fun方法找到。
用关键字var在内部声明,相当于声明局部变量(局部声明也是在一条链上,即Scope Chain 作用域链上,Frank注):
将得到什么?对了,55。声明在函数fn2的变量i是一个本地变量(局部变量),和等于44的全局变量i 44没什么关系。 But:
alert(i);
这会访问全局变量i,显示44。
希望本文能帮助读者彻底理解作用域变量可见性的含义。
EXT程序规划入门
事前准备
本教程假设你已经安装好ExtJS库。安装的目录是extjs 并位于你程序的上一级目录。如果安装在其它地方你必须更改路径,更改示例文件中script标签的src的属性。
需要些什么?
除ExtJS库本身外,我们还需要两个文件:
• applayout.html
• applayout.js
先看看一份html文档,比较精简。并附有详细说明:
applayout.html
开头的两行声明了文档的类型。程序可以不用doctype,但是这样的话浏览器可能默认其为Quirks怪僻类型,会导致处理跨浏览器这一问题上出现差异。
我们采用HTML 4.01 Transitional的文档类型,该类型在主流浏览器上支持得不错。当然,你也可以根据你的需求采用其它类型的doctype,但是记住别忘了要加上doctype。
接着指定HTML头部的Content-Type。做这些事情其实很琐碎,但总是有益处。
然后引入EXT的样式,适配器和EXTJS本身。有两种版本的ExtJS:
• ext-all.js - 不能直接阅读,加载时更快,用于产品发布
• ext-all-debug.js - 便于阅读,开发阶段使用,
开发阶段的时候便需要引入debug的版本。
applayout.js这个文件就是我们的程序,紧跟着的是本地化的脚本,这里可以换成Extjs翻译好的版本
跟着我们开始分配事件句柄(event handler),使得在文档全部加载完毕后,程序就可以初始化(运行)。
下面的这一行:
Ext.onReady(myNameSpace.app.init, myNameSpace.app);
可这样说:当文档全部加载完毕,就运行myNameSpace.app的init方法,规定的作用域是myNameSpace.app。
然后是标题,头部的结尾,body(当前空)和结束标签。
文档的解说就说到这儿了。
applayout.js
// 文件底部
文件最开始的几行是注释,说明该文件的主要内容,作者,作者相关的资讯。没有任何注释的程序也可以正常的运行,-但请记住:每次写的程序要容易给人看得懂的 Always write your application as if you would write it for another.当你回头看你几个月前写的代码,发现你根本不记得自己写过什么的时候,就会明白这个道理,并养成编码的好习惯。接着是要指向你服务器的填充图片,如不指定则默认extjs.com。每次运行程序的时候都访问extjs.com,不推荐这样,应该先修改这个常量值指向到本地。
现在自定义命名空间。将所有变量和方法都划分到一个全局对象下管理,这样的好处是避免了变量名冲突和由不同函数干扰了全局变量的值。名字(namespace)可按自己的方案选择。
整段代码的重点是,我们创建了 myNameSpace对象的属性app,其值是一个函数立刻运行之后的返回值。
如果运行我们的代码:
var o = function() {
return {p1:11, p2:22};
}();
实际上我们创建了一个匿名函数(没有名字的函数),经过解释(预编译?)之后让它立刻运行(注意函数后面的())。最后将函数返回的对象(注意此时是一个object变量)分配到变量o。我们的程序便是这种思路去写的。
你可以把私有变量和私有函数直接定义在function和return这两个声明之间,但是请切记:此时不能访问任何html页面中的元素,那会导致错误,因为这段代码在加载时页面的head中就运行了,而这时候html页面中的其它元素还没有被加载进来。
另外一方面,函数init,是由匿名函数返回的对象的一个方法而已。它会在文档全部加载后才运行。换言之整个DOM树已经是可用的了。
一切都还好吧~如果能正确运行http://yourserver.com/applayout/applayout.html,不出现什么错误的话将出现一个警告。
接下来是利用这个空白的模板,讨论本文的重点。
公开Public、私有Private、特权的Privileged?
让我们加入一些有趣内容到程序中去吧。在页面applayout.html的body标签中加入:
<div id="btn1-ct"></div>
空白的div会当作按钮的容器来使用。然后在applayout.js加入下来代码:
// 文件底部
变量btn1 和privVar1 是私有的。 那意味着在程序外部他们是不能够被访问的,而且也不可见。私有函数btn1Handler也是同样道理。
另外一个方面,btn1Text是公共变量所以可以被程序外部访问或者是修改(我们稍后将会演示)。
函数init是特权的,即是私有变量和公共变量两者都可以被它访问到。在本例中,公共变量this.btn1Text和私有函数btn1Handler都能够被特权函数init所访问。同时,相对外界来说,它也属于公共成员的一种。
Ok,运行看看。能看到左上角的按钮吗?很好,点击它。得到一个privVar1=11的警告。这说明私有函数能访问私有变量。
但是在第二个警告中遇到了this.btn1Text=undefined的问题,这好像不应该这样。个中原因是因为位于事件句柄(event handler)中的变量this没有正确指向到我们的程序。你需要用scope:this 指明这个作用域(这里的this关键字所指示的scope应该是btn1变量):
刷新一下,可以了吧?
重写公共变量
在applayout.js底部加入下列代码:
Ext.apply(myNameSpace.app, {
btn1Text:'Taste 1'
});
// 文件底部
这代码是用来干什么的呢?首先它创建了我们的程序对象然后改变(重写)公共变量btn1Text的值。运行后将看到按钮上文字的变化。
把文本都放到公共变量,以便于以后的国际化翻译工作,而不需要修改程序的内部代码。
当然你也可以将其它的值例如尺寸、样式、等等的总之是可自定义的选项都放到公共变量中。免于在数千行代码之中为查找某个颜色而费劲。
重写(Overriding)公共函数
接着更改一下代码,让它读起来是这样的:
// end of file
我们这里将init重写了一篇,主要是在原来代码的基础上加入异常控制,以便能够捕获异常。试运行一下看还有没有其它的变化?
嗯 是的,出现了btn1Handler 未定义的错误。这是因为新的函数虽然尝试访问私有变量但它实际是不允许的。正如所见,原init是特权函数可访问私有空间,但新的init却不能。之所以不能访问私有空间,是因为:禁止外部代码No code from outside,哪怕是尝试重写特权方法。
DomQuery基础
本教程旨在为读者了解怎样利用单例对象Ext.DomQuery,浏览穿梭于DOM树之中和获取对象,提供一个起点。
DomQuery基础
DomQuery的select函数有两个参数。第一个是选择符字符(selector string )而第二个是欲生成查询的标签ID(TAG ID)。
本文中我准备使用函数“Ext.query”但读者须谨记它是“Ext.DomQuery.select()”的简写方式。
这是要入手的html:
第一部分:元素选择符Selector
假设我想获取文档内所有的“span”标签:
扩展EXT组件
要创建的扩展是一个在文字前面能够显示图标的这么一个Ext.form.Combobox。将其中一个功能举例来说,就是要在一块选择里,国家名称连同国旗一并出现。
我们先给扩展起个名字,就叫Ext.ux.IconCombo。
文件的创建
首要的步骤是准备好开发中将会使用的文件。需下列文件:
• iconcombo.html: 新扩展将会使用的 html markup
• iconcombo.js: 程序javascript代码
• Ext.ux.IconCombo.js: 扩展的javascript文件
• Ext.ux.IconCombo.css: 扩展样式表
EXT的布局(Layout)
Ext的layout布局对于建立WEB程序尤为有用。关于布局引擎(layout engine),区域管理器(region manager)的教程将分为几部分,本文是第一篇,为您介绍如何创建区域,如何增加版面到这些区域。
布局引擎(layout engine)这一功能早已在EXT前个ALPHA实现了。 Jack Slocum对于怎样环绕某一区域,给与指定区域管理的策略,和建立界面的问题,在他的第一、第二篇关于跨浏览器的WEB2.0布局功能的博客中,进行过讨论。定义一个DOM元素的边界(edge),使之一个布局的边框(border)--这种做法使得创建“富界面”客户端UI的开发更进一大步。
布局管理器(layout manager)负责管理这些区域。布局管理的主要的用户组件是BorderLayout类。该类为EXT开发富界面的程序提供了一个切入点。Layout的含意是划分好一些预定的区域。可用的区域分别有south, east, west, north,和center。每一个BorderLayout对象都提供这些区域但只有center要求必须使用的。如果你在单独一个区域中包含多个面板,你可通过NestedLayoutPanel 类套嵌到BorderLayout 实例中。
注意事项:本教程的每个文件都是.html和.js格式的。教程每一步都有演示,你也可以下载这些文件在编辑器(zip格式提供在这里)中看看发生什么事。
面板(Panel)是区域管理(region management)的另外一个组件。面板提供了这么一个地方,可为您的EXT器件(widget)、加载的HTML,嵌入的IFrames、或者是你日常在HTML页面上摆放的随便一样东西。NestedLayoutPanel也是一个面板,只不过用于链接多个BorderLayout的区域,其它的面板包括内容面板 ContentPanel,Grid面板 GridPanel,和Tree面板 TreePanel。
简单的例子
下面的layout包含 north, south, east, west,和center的区域,而且每个区域包含一个ContentPanel,各区域之间使用得了分隔条分割开。
当前的例子是将ContentPanel加入到所有区域中。由调用mainLayout.beginUpdate()开始。beginUpdate ()告诉BorderLayout对象在执行endUpate()方法之前,先不要对加入的对象排版布局。这样的好处是避免了ContentPanel有对象加入时,导致UI的刷新,改进了整体的用户体验。执行beginUpdate()之后,加入五个ContentPanel对象到区域。所有的 ContentPanel对象(除中间的那个外),都设置是可关闭的(closbale)。所有的ContentPanel对象也都设置为自动适配它们的父元素。最后执行endUpdate()渲染layout。
InternetExploer注意事项:BorderLayout所容纳的元素必须有一个SIZE以便正确渲染。典型地你无须为document.body 指明size,因为document.body通常是有size的了(大多数情况,-除非你在浏览器上什么也看不到)。但是如果你将layout连同容器放到现有的web页面上(‘可能是DIV),那么DIV的size应该先指明以便正确渲染。如下列显示正常:
好,让我们趁热打铁,看看完整的layout是怎样的。假设ext是一子目录叫做ext-1.0,父目录下面的代码。
simple.html:
simple.js:
加入内容
上面的例子做的layout,除了可移动分割栏外,功能还不强大。需要加入些内容。有几种的办法加入内容。如果您直接加入内容到DIV中(ContentPanel绑定的那个),ContentPanel对象会对div里面的内容进行渲染。尽管试试!我们会更改html内容加入 center-div中。
simple2.html:
This is some content that will display in a panel
when a ContentPanel object is attached to the div.
除此之外,还可以利用ContentPanel对象带有的function加载数据。可用的方法有几种,这里我们使用其中两种:setContent() 与 setUrl()。setContent()允许您直接从JavaScipt程序中插入HTML。setUrl(),允许您从服务端得到数据加入ContentPanel中。
我们原来的例子中,ContentPanel对象创建的时候是匿名的(anonymous)。这没问题,但要引用它们,你需要遍历区域管理器所分配的对象以获得引用的对象。这不是最好的办法,所有我的做法是分配一个变量给ContentPanel然后便可直接引用。
simple3.js:
我们现在从现有的页面动态加载内容。但是这里有个问题。若果内容页面积过大而撑破页面的话将没有意义了。我们提供了一些配置属性以解决这类问题。当 fitToFrame为true时,就自动配置autoScroll。内容一旦溢出就会出现滚动条。另外一个涉及InternetExploer的问题, 是中间的内容的样式没有生效,原因是一些浏览器支持动态样式而一些不支持,要较好地解决上述问题,推荐使用Iframe标签。
用IFRAME标签做布局可灵活地处理,我们准备在DOM中直接操纵IFRAME.这里IFRAME成为面板的容器,以填入中间区域的内容
设置一下 IFRAME的滚动条并放到中间的页面。.
simple4.html:
simple4.js:
[code="java"]Simple = function() {
var northPanel, southPanel, eastPanel, westPanel, centerPanel;
return {
init : function() {
var mainLayout = new Ext.BorderLayout(document.body, {
north: {
split: true, initialSize: 50
},
south: {
split: true, initialSize: 50
},
east: {
split: true, initialSize: 100
},
west: {
split: true, initialSize: 100
},
center: {
}
});
mainLayout.beginUpdate();
mainLayout.add('north', northPanel = new Ext.ContentPanel('north-div', {
fitToF
无论你是Ext库的新手,抑或是想了解Ext的人,本篇文章的内容都适合你。本文将简单地介绍Ext的几个基本概念,和如何快速地做出一个动态的页面并运行起来,假设读者已具备了一些JavaScript经验和初级了解HTML Dom。
目錄
• 1 下载Ext
• 2 开始!
• 3 Element:Ext的核心
• 4 获取多个DOM的节点
• 5 响应事件
• 6 使用Widgets
o 6.1 MessageBox
o 6.2 Grid
o 6.3 还有更多的..
• 7 使用Ajax
o 7.1 PHP
o 7.2 ASP.Net
o 7.3 Cold Fusion
下载Ext
如果你未曾下载过,那应从这里下载最新版本的Ext http://extjs.com/downloads 。
针对你的下载需求,有几个不同的弹性选项。通常地,最稳定的版本,是较多人的选择。下载解包后,那个example文件夹便是一个探索Ext的好地方!
开始!
Download Example File
• IntroToExt.zip
我们将使用Ext,来完成一些JavaScript任务。
Zip文件包括三个文件:ExtStart.html, ExtStart.js和ExtStart.css。解包这三个文件到Ext的安装目录中(例如,Ext是在“C:\code\Ext\v1.0”中,那应该在"v1.0"里面新建目录“tutorial”。双击ExtStart.htm,接着你的浏览器打开启动页面,应该会有一条消息告诉你配置已完毕。如果是一个Javascript错误,请按照页面上的指引操作。
在你常用的IDE中或文本编辑器中,打开ExtStart.js看看:
Ext.onReady可能是你接触的第一个方法。这个方法是指当前DOM加载完毕后,保证页面内的所有元素能被Script引用(reference)。你可删除alert()那行,加入一些实际用途的代码试试:
Ext.onReady(function() {
alert("Congratulations! You have Ext configured correctly!");
});
Element:Ext的核心
大多数的JavaScript操作都需要先获取页面上的某个元素(reference),好让你来做些实质性的事情。传统的JavaScript方法,是通过ID获取Dom节点的:
var myDiv = document.getElementById('myDiv');
这毫无问题,不过这样单单返回一个对象(DOM节点),用起来并不是太实用和方便。为了要用那节点干点事情,你还将要手工编写不少的代码;另外,对于不同类型浏览器之间的差异,要你处理起来可真头大了。
进入Ext.element 对象。元素(element)的的确确是Ext的心脏地带,--无论是访问元素(elements)还是完成一些其他动作,都要涉及它。Element的 API是整个Ext库的基础,如果你时间不多,只是想了解Ext中的一两个类的话,Element一定是首选!
由ID获取一个Ext Element如下(首页ExtStart.htm包含一个div,ID名字为“myDiv”,然后,在ExtStart.js中加入下列语句):
Ext.onReady(function() {var myDiv = Ext.get('myDiv');});
再回头看看Element对象,发现什么有趣的东东呢?
• Element包含了常见的DOM方法和属性,提供一个快捷的、统一的、跨浏览器的接口(若使用Element.dom的话,就可以直接访问底层DOM的节点。);
• Element.get()方法内置缓存处理(Cache),多次访问同一对象效率上有极大优势;
• 内置常用的DOM节点的动作,并且是跨浏览器的定位的位置、大小、动画、拖放等等(add/remove CSS classes, add/remove event handlers, positioning, sizing, animation, drag/drop)。
这意味着你可用少量的代码来做各种各样的事情,这里仅仅是一个简单的例子(完整的列表在ElementAPI中)。
继续在ExtStart.js中,在刚才我们获取好myDiv的位置中加入:
myDiv.highlight(); //黄色高亮显示然后渐退
myDiv.addClass('red'); // 添加自定义CSS类 (在ExtStart.css定义)
myDiv.center(); //在视图中将元素居中
myDiv.setOpacity(.25); // 使元素半透明
获取多个DOM的节点
通常情况下,想获取多个DOM的节点,难以依靠ID的方式来获取。有可能因为没设置ID,或者你不知道ID,又或者直接用ID方式引用有太多元素了。这种情况下,你就会不用ID来作为获取元素的依据,可能会用属性(attribute)或CSS Classname代替。基于以上的原因,Ext引入了一个功能异常强大的Dom Selector库,叫做DomQuery。
DomQuery可作为单独的库使用,但常用于Ext,你可以在上下文环境中(Context)获取多个元素,然后通过Element接口调用。令人欣喜的是,Element对象本身便有Element.selcect的方法来实现查询,即内部调用DomQuery选取元素。这个简单的例子中, ExtStart.htm包含若干段落(
标签),没有一个是有ID的,而你想轻松地通过一次操作马上获取每一段,全体执行它们的动作,可以这样做:
// 每段高亮显示
Ext.select('p').highlight();
DomQuery的选取参数是一段较长的数组,其中包括W3C CSS3 Dom选取器、基本XPatch、HTML属性和更多,请参阅DomQuery API文档以了解这功能强大的库个中细节。
响应事件
到这范例为止,我们所写的代码都是放在onReady中,即当页面加载后总会立即执行,功能较单一——这样的话,你便知道,如何响应某个动作或事件来执行你希望做的事情,做法是,先分配一个function,再定义一个event handler事件处理器来响应。我们由这个简单的范例开始,打开ExtStart.js,编辑下列的代码:
Ext.onReady(function() {
Ext.get('myButton').on('click', function(){
alert("You clicked the button");
});
});
加载好页面,代码依然会执行,不过区别是,包含alert()的function是已定义好的,但它不会立即地被执行,是分配到按钮的单击事件中。用浅显的文字解释,就是:获取ID为'myDottom'元素的引用,监听任何发生这个元素上被单击的情况,并分配一个function,以准备任何单击元素的情况。
正路来说,Element.select也能做同样的事情,即作用在获取一组元素上。下一例中,演示了页面中的某一段落被单击后,便有弹出窗口:
Ext.onReady(function() {
Ext.select('p').on('click', function() {
alert("You clicked a paragraph");
});
});
这两个例子中,事件处理的function均是简单几句,没有函数的名称,这种类型函数称为“匿名函数(anonymous function)”,即是没有名的的函数。你也可以分配一个有名字的event handler,这对于代码的重用或多个事件很有用。下一例等效于上一例:
Ext.onReady(function() {
var paragraphClicked = function() {
alert("You clicked a paragraph");
}
Ext.select('p').on('click', paragraphClicked);
});
到目前为止,我们已经知道如何执行某个动作。但当事件触发时,我们如何得知这个event handler执行时是作用在哪一个特定的元素上呢?要明确这一点非常简单,Element.on方法传入到even handler的function中(我们这里先讨论第一个参数,不过你应该浏览API文档以了解even handler更多的细节)。在我们之前的例子中,function是忽略这些参数的,到这里可有少许的改变,——我们在功能上提供了更深层次的控制。必须先说明的是,这实际上是Ext的事件对象(event object),一个跨浏览器和拥有更多控制的事件的对象。例如,可以用下列的语句,得到这个事件响应所在的DOM节点:
Ext.onReady(function() {
var paragraphClicked = function(e) {
Ext.get(e.target).highlight();
}
Ext.select('p').on('click', paragraphClicked);
});
注意得到的e.target是DOM节点,所以我们首先将其转换成为EXT的Elemnet元素,然后执行欲完成的事件,这个例子中,我们看见段落是高亮显示的。
使用Widgets
(Widget原意为“小器件”,现指页面中UI控件)
除了我们已经讨论过的核心JavaScript库,当前的Ext亦包括了一系列的最前端的JavaScirptUI组件库。文本以一个常用的widget为例子,作简单的介绍。
MessageBox
比起略为沉闷的“HelloWolrd”消息窗口,我们做少许变化,前面我们写的代码是,单击某个段落便会高亮显示,现在是单击段落,在消息窗口中显示段落内容出来。
在上面的paragraphClicked的function中,将这行代码:
Ext.get(e.target).highlight();
替换为:
var paragraph = Ext.get(e.target);
paragraph.highlight();
Ext.MessageBox.show({
title: 'Paragraph Clicked',
msg: paragraph.dom.innerHTML,
width:400,
buttons: Ext.MessageBox.OK,
animEl: paragraph
});
这里有些新的概念需要讨论一下。在第一行中我们创建了一个局部变量(Local Variable)来保存某个元素的引用,即被单击的那个DOM节点(本例中,DOM节点指的是段落paragrah,事因我们已经定义该事件与< p>标签发生关联的了)。为什么要这样做呢?嗯...观察上面的代码,我们需要引用同一元素来高亮显示,在MessageBox中也是引用同一元素作为参数使用。
一般来说,多次重复使用同一值(Value)或对象,是一个不好的方式,所以,作为一个具备良好OO思维的开发者,应该是将其分配到一个局部变量中,反复使用这变量!
现在,为了我们接下来阐述新概念的演示,请观察MessageBox的调用。乍一看,这像一连串的参数传入到方法中,但仔细看,这是一个非常特别的语法。实际上,传入到MessageBox.show的只有一个参数:一个Object literal,包含一组属性和属性值。在Javascript中,Object Literal是动态的,你可在任何时候用{和}创建一个典型的对象(object)。其中的字符由一系列的name/value组成的属性,属性的格式是[property name]:[property value]。在整个Ext中,你将会经常遇到这种语法,因此你应该马上消化并吸收这个知识点!
使用Object Literal的原因是什么呢?主要的原因是“可伸缩性(flexibility)”的考虑",随时可新增、删除属性,亦可不管顺序地插入。而方法不需要改变。这也是多个参数的情况下,为最终开发者带来不少的方便(本例中的MessageBox.show())。例如,我们说这儿的foo.action方法,有四个参数,而只有一个是你必须传入的。本例中,你想像中的代码可能会是这样的foo.action(null, null, null, 'hello').,若果那方法用Object Literal来写,却是这样, foo.action({ param4: 'hello' }),这更易用和易读。
Grid
Grid是Ext中人们最想先睹为快的和最为流行Widgets之一。好,让我们看看怎么轻松地创建一个Grid并运行。用下列代码替换ExtStart.js中全部语句:
Ext.onReady(function() {
var myData = [
['Apple',29.89,0.24,0.81,'9/1 12:00am'],
['Ext',83.81,0.28,0.34,'9/12 12:00am'],
['Google',71.72,0.02,0.03,'10/1 12:00am'],
['Microsoft',52.55,0.01,0.02,'7/4 12:00am'],
['Yahoo!',29.01,0.42,1.47,'5/22 12:00am']
];
var ds = new Ext.data.Store({
proxy: new Ext.data.MemoryProxy(myData),
reader: new Ext.data.ArrayReader({id: 0}, [
{name: 'company'},
{name: 'price', type: 'float'},
{name: 'change', type: 'float'},
{name: 'pctChange', type: 'float'},
{name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
])
});
ds.load();
var colModel = new Ext.grid.ColumnModel([
{header: "Company", width: 120, sortable: true, dataIndex: 'company'},
{header: "Price", width: 90, sortable: true, dataIndex: 'price'},
{header: "Change", width: 90, sortable: true, dataIndex: 'change'},
{header: "% Change", width: 90, sortable: true, dataIndex: 'pctChange'},
{header: "Last Updated", width: 120, sortable: true,
renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
]);
var grid = new Ext.grid.Grid('grid-example', {ds: ds, cm: colModel});
grid.render();
grid.getSelectionModel().selectFirstRow();
});
这看上去很复杂,但实际上加起来,只有七行代码。第一行创建数组并作为数据源。实际案例中,你很可能从数据库、或者WebService那里得到动态的数据。接着,我们创建并加载data store, data store将会告诉Ext的底层库接手处理和格式化这些数据。接着,我们定义一个column模型,用来轻松地调配Grid的每一列参数。最后我们生成这个Grid,传入data store和column模型两个对象,进行渲染并选好第一行。不是太困难吧?如果一切顺利,完成之后你会看到像这样的:
当然,你可能未掌握这段代码的某些细节(像MemoryProxy究竟是什么?)但先不要紧,这个例子的目的是告诉你,用少量的代码,创建一个富界面的多功能的UI组件而已——这是完全可能的,只要读者您有兴趣学习。这儿有许多学习Grid的资源。Ext Grid教程、交叉Gird演示和Grid API文档。
还有更多的..
这只是冰山一角。还有一打的UI Widgets可以供调用,如 layouts, tabs, menus, toolbars, dialogs, tree view等等。请参阅API文档中范例演示。
使用Ajax
在弄好一些页面后,你已经懂得在页面和脚本之间的交互原理(interact)。接下来,你应该掌握的是,怎样与远程服务器(remote server)交换数据,常见的是从数据库加载数据(load)或是保存数据(save)到数据库中。通过JavaScript异步无刷新交换数据的这种方式,就是所谓的Ajax。Ext内建卓越的Ajax支持,例如,一个普遍的用户操作就是,异步发送一些东西到服务器,然后,UI元素根据回应(Response)作出更新。这是一个包含text input的表单,一个div用于显示消息(注意,你可以在ExtStart.html中加入下列代码,但这必须要访问服务器):
<div id="msg" style="visibility: hidden"></div>
Name: <input type="text" id="name" /><br />
<input type="button" id="oKButton" value="OK" />
接着,我们加入这些处理交换数据的JavaScript代码到文件ExtStart.js中(用下面的代码覆盖):
Ext.onReady(function(){
Ext.get('oKButton').on('click', function(){
var msg = Ext.get('msg');
msg.load({
url: [server url], //换成你的URL
params: 'name=' + Ext.get('name').dom.value,
text: 'Updating...'
});
msg.show();
});
});
这种模式看起来已经比较熟悉了吧!先获取按钮元素,加入单击事件的监听。在事件处理器中(event handler),我们使用一个负责处理Ajax请求、接受响应(Response)和更新另一个元素的Ext内建类,称作UpdateManager。 UpdateManager可以直接使用,或者和我们现在的做法一样,通过Element的load方法来引用(本例中该元素是id为“msg“的 div)。当使用Element.load方法,请求(request)会在加工处理后发送,等待服务器的响应(Response),来自动替换元素的 innerHTML。简单传入服务器url地址,加上字符串参数,便可以处理这个请求(本例中,参数值来自“name”元素的value),而text值是请求发送时提示的文本,完毕后显示那个msg的div(因为开始时默认隐藏)。当然,和大多数Ext组件一样,UpdateManager有许多的参数可选,不同的Ajax请求有不同的方案。而这里仅演示最简单的那种。
PHP
<? if(isset($_GET['name'])) {
echo 'From Server: '.$_GET['name'];
}
?>
ASP.Net
protected void Page_Load(object sender, EventArgs e)
{
if (Request["name"] != null)
{
Response.Write("From Server: " + Request["name"]);
Response.End();
}
}
Cold Fusion
<cfif StructKeyExists(url, "name")>
<cfoutput>From Server: #url.name#</cfoutput>
</cfif>
最后一个关于Ajax隐晦的地方就是,服务器实际处理请求和返回(Resposne)是具体过程。这个过程会是一个服务端页面,一个Servlet,一个 Http调度过程,一个WebService,甚至是Perl或CGI脚本,即不指定一个服务器都可以处理的http请求。让人无法预料的是,服务器返回什么是服务器的事情,无法给一个标准的例子来覆盖阐述所有的可能性。(这段代码输出刚才我们传入'name'的那个值到客户端,即发送什么,返回什么)。
使用Ajax的真正挑战,是需要进行适当的手工编码,并相应格式化为服务端可用接受的数据结构。有几种格式供人们选择(最常用为JSON/XML)。正因 Ext是一种与服务器语言免依赖的机制,使得其它特定语言的库亦可用于Ext处理Ajax服务。只要页面接受到结果是EXT能处理的数据格式,Ext绝不会干涉服务器其他的事情!要全面讨论这个问题,已超出本文的范围。推荐正在Ajax环境下开发的您,继续深入阅读Ext Ajax教程。
下一步是?
现在你已经一睹Ext其芳容,知道她大概能做些什么了。下面的资源有助您进一步深入了解:
EXT源码概述
揭示源代码
Javascript是一门解释型的语言,意味着在运行之前代码是没有经过编译的。按照这种理论,在你网站上所发播的Ext代码是我们看的懂的(human-readible)。我这里说“理论上”,是因为实际情况中,很多源代码是经过某些自动化步骤的处理,生成很小几行的文件最终发布的,通过剔除空白符号和注释,或混淆等的方法,以减小文件大小。
仔细看看EXT标准源码ext-core.js,你会发现这是一段超长的源码。这是刚才提及的自动化步骤生成的结果--对浏览器来说不错!可是对于我们是难以阅读的。
ext-core.js
/*
* Ext JS Library 1.1
* Copyright(c) 2006-2007, Ext JS, LLC.
* licensing@extjs.com
*
* http://www.extjs.com/license
*/
Ext.DomHelper=function(){var _1=null;var _2=/^(?:br|frame...
Ext.Template=function(_1){if(_1 instanceof Array){_1...
...
接着看下去的是ext-core-debug.js (注意在文件名后面加上-debug的JS文件), 我会发现是全部已格式化好的源代码。这个文件是配合调时器所使用的,像Firebug的工具能够可以让你一步一步地、一行一行地调试代码。你也会发现文件的体积将近大了一倍之多,这便是没有压缩或混淆的缘故。
ext-core-debug.js
/*
* Ext JS Library 1.1
* Copyright(c) 2006-2007, Ext JS, LLC.
* licensing@extjs.com
*
* http://www.extjs.com/license
*/
Ext.DomHelper = function(){
var tempTableEl = null;
var emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i;
var tableRe = /^table|tbody|tr|td$/i;
...
该调试版本可以在调试阶段方便地检查EXT库运行到哪一步,但是你还是会错过一个有价值的...代码注释!要完整地看到代码,就要阅读真正的原始代码!
发布Ext源码时的一些细节
你在download得到的压缩文档,包含在这些文件夹之中的,有一source的子目录。在这个文件夹里面,正如所料,是全部的EXT的源文件,遵从Lesser GNU (LGPL) 开源的协议。对于EXT开发者来说应该非常适合。
用你日常使用文本编辑器打开源代码的任意一个文件(推荐有高亮显示的编辑器,或是在这里full-featured IDE看看),便可以开始我们的探险!
我应该从哪里开始?
Ext代码库里面包含了许多各种各样的文件,甚至令人觉得有点望而生畏。好在,Ext是一个通过充分考虑后而设计的JavaScript库,--在底层的代码为各项应用提供稳健的基础如跨浏览器的各种DOM操控,使得在上层的类classes运行于一个较高级的抽象层面(class 一术语与我们已习惯的Java和C++语言稍微有所不同,但一些概念如继承则可是如此类推去理解的--有关面向对象的JavaScript的更多资料,请参见Introduction to object-oriented (OO) JavaScript)。
这意味着,当浏览源码的时候,采取“自顶向下(bottom-up)”还是“自下向顶(top-down)”的方式,都是无关紧要的。你所熟悉API里面的代码已经是属于最高的抽象层面的范畴,你可以根据你的兴趣,顺着这些你熟悉的API逐步深入。但是你若赞同我的看法,并打算深入了解其个中原理,最理想的地方是从底层代码开始。
适配器Adapters
浏览器读取第一个源文件,当中的一个任务就是创建Ext对象本身。 Ext.js
Ext = {};
Ext成型于YahooUI的Javascript库的扩展。在当时,Ext须依赖YUI的底层代码来处理跨浏览器的问题。现在ExtJS已经是独立、免依赖的库了(standalone ),你可将YUI替换为另外你所选择javascript库,如prototype、jQuery、或者是这些之中的最佳选择,-Ext自带的底层库。负责将这些库(包括Ext自带的底层库)映射为Ext底层库的这部分代码,我们称之为适配器(Adapters)。这部分源码位于source/adapter的子目录。当项目引入Ext的时候便需要选择好你准备使用的适配器。
核心Core
source/core中的文件是构建于适配器API之上的“相对”最底层的源码。有些的源码甚至“底层”到直接为独立库的代码直接使用。这意味着应先了解和学习这整个库,再学习剩余的部分也不迟。要了解Ext的各种“Magic”和核心层面,就应该把重点放在source/core 目录下的各个源代码。
Javascript中的作用域(scope)
事前准备
学习本教程的最佳方法是随手准备好Firefox中的工具Firebug。这样使得您可以即刻测试教程的例子。
如果机子上还没有FireFox和FireBug,就应该尽快安装一套来用。
定义
作用域scope
1.(名词)某样事物执行、操作、拥有控制权的那么一个区域 [1]
2. (名词) 编写程序时,程序之中变量的可见度;例如,一个函数能否使用另外一个函数所创建的变量。[2]
可是这能够说明什么问题呢? 每当有人在说“这是作用域的问题”或“作用域搞错了”的时候,那就是说某个函数运行起来的时候,找不到正确变量的位置。这样我们便知道应该从哪一方面入手,查找出问题所在。
正式开始
实际上每一个你定义的函数都是某个对象的方法。甚至是这样的写法:
function fn() {
alert(11);
}
老兄你不是故弄玄虚吧~。做一个这样的演示可真得是简单得要命。没错!本例不需要任何Javascript文件,服务器或html。你只要打开 firefox,弹出firebug,点击console tab。在Firefox状态栏上面看到有>>>提示的地方就可以输入了。
输入:
function fn() { alert(11); };
然后回车。一切安然...你刚才做的实际上是定义了一个函数fn。接着试试:
fn();
然后回车。得到11的警告窗口?还不错吧?接着试试:
window.fn();
this.fn();
得到一样的结果吧?这是因为函数fn是window对象的一个方法,在第二行的"this"的作用域实际指向了windows对象。不过多数情况中你不需要像这样window.myFunction(...)地调用函数,这样太麻烦了,程序员工作起来会很不方便。
window对象
window 对象总是存在的,你可理解其为一个浏览器窗口对象。它包含了其它所有的对象如document 和所有的全局变量。
你可以打开Firebug,切换到 Script 页面并在Firebug右侧的New watch expression... 里面输入 window。观察window对象究竟有什么在里面。
接着,尝试找出我们之前定义过的fn函数。
另外,每个frame或iframe拥有其自身的window对象,其自身的全局空间。
理解作用域
接下的内容开始有点复杂了。切换到Firebug Console标签页然后输入:
var o1 = {testvar:22, fun:function() { alert('o1: ' + this.testvar); }};
var o2 = {testvar:33, fun:function() { alert('o2: ' + this.testvar); }};
结果是什么?你声明了o1 和 o2两个对象,分别都有一些属性和方法,但值不同。
接着试试:
fun();
window.fun();
this.fun();
出错了,是吧?因为window对象(等价于this)并没有fun的方法。试一试下面的:
o1.fun();
o2.fun();
22和33出来了?非常好!
接下来这部分的内容最复杂啦。基于这个原始的函数,如果对象的数量多的话,你必须为每个对象加上这个函数-明显是重复劳动了。这样说吧,o1.fun写得非常清晰的而且为了搞掂它已经占用了我一个星期的开发时间。想象一下代码到处散布着this变量,怎么能不头疼?如果要将调用(执行)的o1.fun方法但this会执行o2,应该怎么实现呢?试一试下面的:
o1.fun.call(o2);
明白了吗?当执行o1的fun方法时你强行将变量this指向到o2这个对象,换句话说,更加严谨地说:o1.fun的方法在对象o2的作用域下运行。
当运行一个函数,一个对象的方法时,你可将作用域当作this值的变量。
变量的可见度
变量的可见度和作用域的关系非常密切。我们已经了解到,可在任何对象的外部,声明变量,或在全局的函数(函数也是变量的一种)也可以,更严格说,它们是全局对象window的属性。 全局变量在任何地方都可见;无论函数的内部还是外部。如果你在某一个函数内修改了一个全局变量,其它函数也会得知这个值是修改过的。
对象可以有它自己的属性(像上面的testvar),这些属性允许从内部或是外部均是可见的。试:
alert(o1.testvar); // 从外部访问o1的属性testvar
从内部访问的演示可在两个测试对象的fun方法找到。
用关键字var在内部声明,相当于声明局部变量(局部声明也是在一条链上,即Scope Chain 作用域链上,Frank注):
i = 44;
function fn2() {
var i = 55;
alert(i);
}
fn2();
将得到什么?对了,55。声明在函数fn2的变量i是一个本地变量(局部变量),和等于44的全局变量i 44没什么关系。 But:
alert(i);
这会访问全局变量i,显示44。
希望本文能帮助读者彻底理解作用域变量可见性的含义。
EXT程序规划入门
事前准备
本教程假设你已经安装好ExtJS库。安装的目录是extjs 并位于你程序的上一级目录。如果安装在其它地方你必须更改路径,更改示例文件中script标签的src的属性。
需要些什么?
除ExtJS库本身外,我们还需要两个文件:
• applayout.html
• applayout.js
先看看一份html文档,比较精简。并附有详细说明:
applayout.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css" href="../extjs/resources/css/ext-all.css">
<script type="text/javascript" src="../extjs/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../extjs/ext-all-debug.js"></script>
<script type="text/javascript" src="applayout.js"></script>
<!-- 本地化的脚本引用在这里 -->
<script type="text/javascript">
Ext.onReady(myNameSpace.app.init, myNameSpace.app);
</script>
<title>Application Layout Tutorial</title>
</head>
<body>
</body>
</html>
开头的两行声明了文档的类型。程序可以不用doctype,但是这样的话浏览器可能默认其为Quirks怪僻类型,会导致处理跨浏览器这一问题上出现差异。
我们采用HTML 4.01 Transitional的文档类型,该类型在主流浏览器上支持得不错。当然,你也可以根据你的需求采用其它类型的doctype,但是记住别忘了要加上doctype。
接着指定HTML头部的Content-Type。做这些事情其实很琐碎,但总是有益处。
然后引入EXT的样式,适配器和EXTJS本身。有两种版本的ExtJS:
• ext-all.js - 不能直接阅读,加载时更快,用于产品发布
• ext-all-debug.js - 便于阅读,开发阶段使用,
开发阶段的时候便需要引入debug的版本。
applayout.js这个文件就是我们的程序,紧跟着的是本地化的脚本,这里可以换成Extjs翻译好的版本
跟着我们开始分配事件句柄(event handler),使得在文档全部加载完毕后,程序就可以初始化(运行)。
下面的这一行:
Ext.onReady(myNameSpace.app.init, myNameSpace.app);
可这样说:当文档全部加载完毕,就运行myNameSpace.app的init方法,规定的作用域是myNameSpace.app。
然后是标题,头部的结尾,body(当前空)和结束标签。
文档的解说就说到这儿了。
applayout.js
/**
* Application Layout
* by Jozef Sakalos, aka Saki
* http://extjs.com/learn/Tutorial:Application_Layout_for_Beginners_(Chinese)
*/
// 填充图片的本地引用
Ext.BLANK_IMAGE_URL = '../extjs/resources/images/default/s.gif';
// 创建命名空间
Ext.namespace('myNameSpace');
// 创建应用程序
myNameSpace.app = function() {
// 元素还没创建,未能访问
// 私有变量
// 私有函数
// 公共空间
return {
// 公共的属性,如,要转换的字符串
// 公共方法
init: function() {
alert('应用程序初始化成功。');
}
};
}(); // 程序底部
// 文件底部
文件最开始的几行是注释,说明该文件的主要内容,作者,作者相关的资讯。没有任何注释的程序也可以正常的运行,-但请记住:每次写的程序要容易给人看得懂的 Always write your application as if you would write it for another.当你回头看你几个月前写的代码,发现你根本不记得自己写过什么的时候,就会明白这个道理,并养成编码的好习惯。接着是要指向你服务器的填充图片,如不指定则默认extjs.com。每次运行程序的时候都访问extjs.com,不推荐这样,应该先修改这个常量值指向到本地。
现在自定义命名空间。将所有变量和方法都划分到一个全局对象下管理,这样的好处是避免了变量名冲突和由不同函数干扰了全局变量的值。名字(namespace)可按自己的方案选择。
整段代码的重点是,我们创建了 myNameSpace对象的属性app,其值是一个函数立刻运行之后的返回值。
如果运行我们的代码:
var o = function() {
return {p1:11, p2:22};
}();
实际上我们创建了一个匿名函数(没有名字的函数),经过解释(预编译?)之后让它立刻运行(注意函数后面的())。最后将函数返回的对象(注意此时是一个object变量)分配到变量o。我们的程序便是这种思路去写的。
你可以把私有变量和私有函数直接定义在function和return这两个声明之间,但是请切记:此时不能访问任何html页面中的元素,那会导致错误,因为这段代码在加载时页面的head中就运行了,而这时候html页面中的其它元素还没有被加载进来。
另外一方面,函数init,是由匿名函数返回的对象的一个方法而已。它会在文档全部加载后才运行。换言之整个DOM树已经是可用的了。
一切都还好吧~如果能正确运行http://yourserver.com/applayout/applayout.html,不出现什么错误的话将出现一个警告。
接下来是利用这个空白的模板,讨论本文的重点。
公开Public、私有Private、特权的Privileged?
让我们加入一些有趣内容到程序中去吧。在页面applayout.html的body标签中加入:
<div id="btn1-ct"></div>
空白的div会当作按钮的容器来使用。然后在applayout.js加入下来代码:
/**
* Application Layout
* by Jozef Sakalos, aka Saki
* http://extjs.com/learn/Tutorial:Application_Layout_for_Beginners_(Chinese)
*/
// 填充图片的本地引用
Ext.BLANK_IMAGE_URL = '../extjs/resources/images/default/s.gif';
// 创建命名空间
Ext.namespace('myNameSpace');
// 创建应用程序
myNameSpace.app = function() {
// 元素还没创建,未能访问
// 私有变量
var btn1;
var privVar1 = 11;
// 私有函数
var btn1Handler = function(button, event) {
alert('privVar1=' + privVar1);
alert('this.btn1Text=' + this.btn1Text);
};
// 公共空间
return {
// 公共的属性,如,要转译的字符串
btn1Text: 'Button 1'
// 公共方法
, init: function() {
btn1 = new Ext.Button('btn1-ct', {
text: this.btn1Text
, handler: btn1Handler
});
}
};
}(); //程序底部
// 文件底部
变量btn1 和privVar1 是私有的。 那意味着在程序外部他们是不能够被访问的,而且也不可见。私有函数btn1Handler也是同样道理。
另外一个方面,btn1Text是公共变量所以可以被程序外部访问或者是修改(我们稍后将会演示)。
函数init是特权的,即是私有变量和公共变量两者都可以被它访问到。在本例中,公共变量this.btn1Text和私有函数btn1Handler都能够被特权函数init所访问。同时,相对外界来说,它也属于公共成员的一种。
Ok,运行看看。能看到左上角的按钮吗?很好,点击它。得到一个privVar1=11的警告。这说明私有函数能访问私有变量。
但是在第二个警告中遇到了this.btn1Text=undefined的问题,这好像不应该这样。个中原因是因为位于事件句柄(event handler)中的变量this没有正确指向到我们的程序。你需要用scope:this 指明这个作用域(这里的this关键字所指示的scope应该是btn1变量):
btn1 = new Ext.Button('btn1-ct', {
text: this.btn1Text
, handler: btn1Handler
, scope: this
});
刷新一下,可以了吧?
重写公共变量
在applayout.js底部加入下列代码:
Ext.apply(myNameSpace.app, {
btn1Text:'Taste 1'
});
// 文件底部
这代码是用来干什么的呢?首先它创建了我们的程序对象然后改变(重写)公共变量btn1Text的值。运行后将看到按钮上文字的变化。
把文本都放到公共变量,以便于以后的国际化翻译工作,而不需要修改程序的内部代码。
当然你也可以将其它的值例如尺寸、样式、等等的总之是可自定义的选项都放到公共变量中。免于在数千行代码之中为查找某个颜色而费劲。
重写(Overriding)公共函数
接着更改一下代码,让它读起来是这样的:
Ext.apply(myNameSpace.app, {
btn1Text:'Taste 1'
, init: function() {
try {
btn1 = new Ext.Button('btn1-ct', {
text: this.btn1Text
, handler: btn1Handler
, scope: this
});
}
catch(e) {
alert('错误: "' + e.message + '" 发生在行: ' + e.lineNumber);
}
}
});
// end of file
我们这里将init重写了一篇,主要是在原来代码的基础上加入异常控制,以便能够捕获异常。试运行一下看还有没有其它的变化?
嗯 是的,出现了btn1Handler 未定义的错误。这是因为新的函数虽然尝试访问私有变量但它实际是不允许的。正如所见,原init是特权函数可访问私有空间,但新的init却不能。之所以不能访问私有空间,是因为:禁止外部代码No code from outside,哪怕是尝试重写特权方法。
DomQuery基础
本教程旨在为读者了解怎样利用单例对象Ext.DomQuery,浏览穿梭于DOM树之中和获取对象,提供一个起点。
DomQuery基础
DomQuery的select函数有两个参数。第一个是选择符字符(selector string )而第二个是欲生成查询的标签ID(TAG ID)。
本文中我准备使用函数“Ext.query”但读者须谨记它是“Ext.DomQuery.select()”的简写方式。
这是要入手的html:
<html>
<head>
<script type="text/javascript" src="../js/firebug/firebug.js"></script>
</head>
<body>
<script type="text/javascript" src="../ext/ext-base.js"></script>
<script type="text/javascript" src="../ext/ext-core.js"></script>
<div id="bar" class="foo">
I'm a div ==> my id: bar, my class: foo
<span class="bar">I'm a span within the div with a foo class</span>
<a href="http://www.extjs.com" target="_blank">An ExtJs link</a>
</div>
<div id="foo" class="bar">
my id: foo, my class: bar
<p>I'm a P tag within the foo div</p>
<span class="bar">I'm a span within the div with a bar class</span>
<a href="#">An internal link</a>
</div>
</body>
</html>
第一部分:元素选择符Selector
假设我想获取文档内所有的“span”标签:
// 这个查询会返回有两个元素的数组因为查询选中对整个文档的所有span标签。
Ext.query("span");
// 这个查询会返回有一个元素的数组因为查询顾及到了foo这个id。
Ext.query("span", "foo");
注意刚才怎么传入一个普通的字符串作为第一个参数。
按id获取标签,你需要加上“#”的前缀:
// 这个查询会返回包含我们foo div一个元素的数组!
Ext.query("#foo");
按class name获取标签,你需要加上“.”的前缀:
/*这个查询会返回有一个元素的数组,
包含与之前例子一样的div但是我们使用了class name来获取*/
Ext.query(".foo");
你也可以使用关键字“*”来获取所有的元素:
// 这会返回一个数组,包含文档的所有元素。
Ext.query("*");
要获取子标签,我们只须在两个选择符之间插入一个空格:
// 这会返回有一个元素的数组,包含p标签的div标签
Ext.query("div p");
// 这会返回有两个元素的数组,包含span标签的div标签
Ext.query("div span");
还有三个的元素选择符,待后续的教程会叙述。 ""
如果朋友你觉得这里说得太简单的话,你可以选择到DomQuery 文档看看,可能会有不少收获:)
第二部分:属性选择符Attributes selectors
这些选择符可让你得到基于一些属性值的元素。属性指的是html元素中的href, id 或 class。
// 我们检查出任何存在有class属性的元素。
// 这个查询会返回5个元素的数组。
Ext.query("*[class]"); // 结果: [body#ext-gen2.ext-gecko, div#bar.foo, span.bar, div#foo.bar, span.bar]
现在我们针对特定的class属性进行搜索。
// 这会得到class等于“bar”的所有元素
Ext.query("*[class=bar]");
// 这会得到class不等于“bar”的所有元素
Ext.query("*[class!=bar]");
// 这会得到class从“b”字头开始的所有元素
Ext.query("*[class^=b]");
//这会得到class由“r”结尾的所有元素
Ext.query("*[class$=r]");
//这会得到在class中抽出“a”字符的所有元素
Ext.query("*[class*=a]");
第三部分: CSS值元素选择符
这些选择符会匹配DOM元素的style属性。尝试在那份html中加上一些颜色:
<html>
<head>
<script type="text/javascript" src="../js/firebug/firebug.js"></script>
</head>
<body>
<script type="text/javascript" src="../ext/ext-base.js"></script>
<script type="text/javascript" src="../ext/ext-core.js"></script>
<div id="bar" class="foo" style="color:red;">
我是一个div ==> 我的id是: bar, 我的class: foo
<span class="bar" style="color:pink;">I'm a span within the div with a foo class</span>
<a href="http://www.extjs.com" target="_blank" style="color:yellow;">An ExtJs link with a blank target!</a>
</div>
<div id="foo" class="bar" style="color:fushia;">
my id: foo, my class: bar
<p>I'm a P tag within the foo div</p>
<span class="bar" style="color:brown;">I'm a span within the div with a bar class</span>
<a href="#" style="color:green;">An internal link</a>
</div>
</body>
</html>
基于这个CSS的颜色值我们不会作任何查询,但可以是其它的内容。它的格式规定是这样的:
元素{属性 操作符 值}
注意我在这里是怎么插入一个不同的括号。
所以,操作符(operators)和属性选择符(attribute selectors)是一样的。
// 获取所以红色的元素
Ext.query("*{color=red}"); // [div#bar.foo]
// 获取所有粉红颜色的并且是有红色子元素的元素
Ext.query("*{color=red} *{color=pink}"); // [span.bar]
// 获取所有不是红色文字的元素
Ext.query("*{color!=red}");
//[html, head, script firebug.js, link, body#ext-gen2.ext-gecko,
// script ext-base.js, script ext-core.js, span.bar,
//a www.extjs.com, div#foo.bar, p, span.bar, a test.html#]
// 获取所有颜色属性是从“yel”开始的元素
Ext.query("*{color^=yel}"); // [a www.extjs.com]
// 获取所有颜色属性是以“ow”结束的元素
Ext.query("*{color$=ow}"); // [a www.extjs.com]
// 获取所有颜色属性包含“ow”字符的元素
Ext.query("*{color*=ow}"); // [a www.extjs.com, span.bar]
第四部分:伪类选择符Pseudo Classes selectors
仍然是刚才的网页,但是有所不同的只是新加上了一个UL元素、一个TABLE元素和一个FORM元素,以便我们可以使用不同的伪类选择符,来获取节点。
<html>
<head>
<script type="text/javascript" src="../js/firebug/firebug.js"></script>
</head>
<body>
<script type="text/javascript" src="../ext/ext-base.js"></script>
<script type="text/javascript" src="../ext/ext-core.js"></script>
<div id="bar" class="foo" style="color:red; border: 2px dotted red; margin:5px; padding:5px;">
I'm a div ==> my id: bar, my class: foo
<span class="bar" style="color:pink;">I'm a span within the div with a foo class</span>
<a href="http://www.extjs.com" target="_blank" style="color:yellow;">An ExtJs link with a blank target!</a>
</div>
<div id="foo" class="bar" style="color:fushia; border: 2px dotted black; margin:5px; padding:5px;">
my id: foo, my class: bar
<p>I'm a P tag within the foo div</p>
<span class="bar" style="color:brown;">I'm a span within the div with a bar class</span>
<a href="#" style="color:green;">An internal link</a>
</div>
<div style="border:2px dotted pink; margin:5px; padding:5px;">
<ul>
<li>Some choice #1</li>
<li>Some choice #2</li>
<li>Some choice #3</li>
<li>Some choice #4 with a <a href="#">link</a></li>
</ul>
<table style="border:1px dotted black;">
<tr style="color:pink">
<td>1st row, 1st column</td>
<td>1st row, 2nd column</td>
</tr>
<tr style="color:brown">
<td colspan="2">2nd row, colspanned! </td>
</tr>
<tr>
<td>3rd row, 1st column</td>
<td>3rd row, 2nd column</td>
</tr>
</table>
</div>
<div style="border:2px dotted red; margin:5px; padding:5px;">
<form>
<input id="chked" type="checkbox" checked/><label for="chked">I'm checked</label>
<br /><br />
<input id="notChked" type="checkbox" /><label for="notChked">not me brotha!</label>
</form>
</div>
</body>
</html>
接着:
/*
this one gives us the first SPAN child of its parent
*/
Ext.query("span:first-child"); // [span.bar]
/*
this one gives us the last A child of its parent
*/
Ext.query("a:last-child") // [a www.extjs.com, a test.html#]
/*
this one gives us the second SPAN child of its parent
*/
Ext.query("span:nth-child(2)") // [span.bar]
/*
this one gives us ODD TR of its parents
*/
Ext.query("tr:nth-child(odd)") // [tr, tr]
/*
this one gives us even LI of its parents
*/
Ext.query("li:nth-child(even)") // [li, li]
/*
this one gives us A that are the only child of its parents
*/
Ext.query("a:only-child") // [a test.html#]
/*
this one gives us the checked INPUT
*/
Ext.query("input:checked") // [input#chked on]
/*
this one gives us the first TR
*/
Ext.query("tr:first") // [tr]
/*
this one gives us the last INPUT
*/
Ext.query("input:last") // [input#notChked on]
/*
this one gives us the 2nd TD
*/
Ext.query("td:nth(2)") // [td]
/*
this one gives us every DIV that has the "within" string
*/
Ext.query("div:contains(within)") // [div#bar.foo, div#foo.bar]
/*
this one gives us every DIV that doesn't have a FORM child
*/
Ext.query("div:not(form)") [div#bar.foo, div#foo.bar, div]
/*
This one gives use every DIV that has an A child
*/
Ext.query("div:has(a)") // [div#bar.foo, div#foo.bar, div]
/*
this one gives us every TD that is followed by another TD.
obviously, the one that has a colspan property is ignored.
*/
Ext.query("td:next(td)") // [td, td]
/*
this one gives us every LABEL that is preceded by an INPUT
*/
Ext.query("label:prev(input)") //[label, label]
扩展EXT组件
要创建的扩展是一个在文字前面能够显示图标的这么一个Ext.form.Combobox。将其中一个功能举例来说,就是要在一块选择里,国家名称连同国旗一并出现。
我们先给扩展起个名字,就叫Ext.ux.IconCombo。
文件的创建
首要的步骤是准备好开发中将会使用的文件。需下列文件:
• iconcombo.html: 新扩展将会使用的 html markup
• iconcombo.js: 程序javascript代码
• Ext.ux.IconCombo.js: 扩展的javascript文件
• Ext.ux.IconCombo.css: 扩展样式表
iconcombo.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css" href="../extjs/resources/css/ext-all.css">
<link rel="stylesheet" type="text/css" href="Ext.ux.IconCombo.css">
<script type="text/javascript" src="../extjs/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../extjs/ext-all-debug.js"></script>
<script type="text/javascript" src="Ext.ux.IconCombo.js"></script>
<script type="text/javascript" src="iconcombo.js"></script>
<!-- A Localization Script File comes here -->
<script type="text/javascript">Ext.onReady(iconcombo.init, iconcombo);</script>
<title>Ext.ux.IconCombo Tutorial</title>
</head>
<body>
<div style="position:relative;width:300px;top:24px;left:64px;font-size:11px">
<div>Icon combo:</div>
<div id="combo-ct"></div>
</div>
</body>
</html>
该文件来自教程Ext程序规划入门 的轻微修改。
iconcombo.js
/**
* Ext.ux.IconCombo Tutorial
* by Jozef Sakalos, aka Saki
* http://extjs.com/learn/Tutorial:Extending_Ext_Class
*/
// 引用本地空白文件
Ext.BLANK_IMAGE_URL = '../extjs/resources/images/default/s.gif';
// 创建程序
iconcombo = function() {
// 公共空间
return {
// public properties, e.g. strings to translate
// public methods
init: function() {
var icnCombo = new Ext.ux.IconCombo({
store: new Ext.data.SimpleStore({
fields: ['countryCode', 'countryName', 'countryFlag'],
data: [
['US', 'United States', 'x-flag-us'],
['DE', 'Germany', 'x-flag-de'],
['FR', 'France', 'x-flag-fr']
]
}),
valueField: 'countryCode',
displayField: 'countryName',
iconClsField: 'countryFlag',
triggerAction: 'all',
mode: 'local',
width: 160
});
icnCombo.render('combo-ct');
icnCombo.setValue('DE');
}
};
}(); // end of app
// end of file
我们在这个文件中创建IconCombo,以便可以进行扩展和测试。
Ext.ux.IconCombo.js
// Create创建用户的扩展(User eXtensions namespace (Ext.ux))
Ext.namespace('Ext.ux');
/**
* Ext.ux.IconCombo 扩展类
*
* @author Jozef Sakalos, aka Saki
* @version 1.0
*
* @class Ext.ux.IconCombo
* @extends Ext.form.ComboBox
* @constructor
* @param {Object} config 配置项参数
*/
Ext.ux.IconCombo = function(config) {
// 调用父类的构建函数
Ext.ux.IconCombo.superclass.constructor.call(this, config);
} // Ext.ux.IconCombo构建器的底部
// 进行扩展
Ext.extend(Ext.ux.IconCombo, Ext.form.ComboBox, {
}); // 扩展完毕
// 文件底部
运行到这一步,实际这是一个没有对Ext.form.ComboBox新加任何东西的空扩展。我们正是需要这个完成好的空扩展,再继续下一步。
Ext.ux.IconCombo.css
.x-flag-us {
background-image: url(../img/flags/us.png);
}
.x-flag-de {
background-image: url(../img/flags/de.png);
}
.x-flag-fr {
background-image: url(../img/flags/fr.png);
}
路径可能根据你所在的国旗放置目录有所不同。国旗的资源可在here下载。
Let's go
So far so good!如果你浏览iconcombo.html应该会发现一个包含三个选项的标准combo,而德国的那个是选中的...是吧?不过还没有图标...
现在正是开始工作。在调用父类构建器之后加入下列行:
this.tpl = config.tpl ||
'<div class="x-combo-list-item">'
+ '<table><tbody><tr>'
+ '<td>'
+ '<div class="{' + this.iconClsField + '} x-icon-combo-icon"></div></td>'
+ '<td>{' + this.displayField + '}</td>'
+ '</tr></tbody></table>'
+ '</div>'
;
在这一步,我们将默认combox box的模版重写为iconClsField模版。
现在加入Ext.ux.IconCombo.css中的样式文件:
.x-icon-combo-icon {
background-repeat: no-repeat;
background-position: 0 50%;
width: 18px;
height: 14px;
}
不错!可以测试一下了,刷新的页面,还好吧!?嗯,列表展开时那些漂亮的图标就出来了。。还有。。我们不是要在关闭时也出现图标的吗?
在构建器中加入创建模版的过程:
this.on({
render:{scope:this, fn:function() {
var wrap = this.el.up('div.x-form-field-wrap');
this.wrap.applyStyles({position:'relative'});
this.el.addClass('x-icon-combo-input');
this.flag = Ext.DomHelper.append(wrap, {
tag: 'div', style:'position:absolute'
});
}}
});
加入 事件render的侦听器,用于调整元素样式和创建国旗的div容器。如后按照下列方式进行扩展:
// 进行扩展
Ext.extend(Ext.ux.IconCombo, Ext.form.ComboBox, {
setIconCls: function() {
var rec = this.store.query(this.valueField, this.getValue()).itemAt(0);
if(rec) {
this.flag.className = 'x-icon-combo-icon ' + rec.get(this.iconClsField);
}
},
setValue: function(value) {
Ext.ux.IconCombo.superclass.setValue.call(this, value);
this.setIconCls();
}
}); // 扩展完毕
新增 setIconCls函数并重写setValue函数。我们还是需要父类的setValue的方法来调用一下,接着再调用setIconCls的函数。最后,我们应该在文件Ext.ux.IconCombo.css加入下列代码:
.x-icon-combo-input {
padding-left: 26px;
}
.x-form-field-wrap .x-icon-combo-icon {
top: 3px;
left: 6px;
}
完成
最后再刷新一下,如果一切顺利,那这个就是新的Ext.ux.IconCombo扩展! 希望你能在此基础上扩展更多的组件!
谢谢Brian Moeskau提醒,使得能进一步精简Ext.ux.IconCombo 代码,才称得上最终版本。最终代码和CSS为:
Ext.ux.IconCombo.js
// Create user extensions namespace (Ext.ux)
Ext.namespace('Ext.ux');
/**
* Ext.ux.IconCombo Extension Class
*
* @author Jozef Sakalos
* @version 1.0
*
* @class Ext.ux.IconCombo
* @extends Ext.form.ComboBox
* @constructor
* @param {Object} config Configuration options
*/
Ext.ux.IconCombo = function(config) {
// call parent constructor
Ext.ux.IconCombo.superclass.constructor.call(this, config);
this.tpl = config.tpl ||
'
{'
+ this.displayField
+ '}
'
;
this.on({
render:{scope:this, fn:function() {
var wrap = this.el.up('div.x-form-field-wrap');
this.wrap.applyStyles({position:'relative'});
this.el.addClass('x-icon-combo-input');
this.flag = Ext.DomHelper.append(wrap, {
tag: 'div', style:'position:absolute'
});
}}
});
} // end of Ext.ux.IconCombo constructor
// extend
Ext.extend(Ext.ux.IconCombo, Ext.form.ComboBox, {
setIconCls: function() {
var rec = this.store.query(this.valueField, this.getValue()).itemAt(0);
if(rec) {
this.flag.className = 'x-icon-combo-icon ' + rec.get(this.iconClsField);
}
},
setValue: function(value) {
Ext.ux.IconCombo.superclass.setValue.call(this, value);
this.setIconCls();
}
}); // end of extend
// end of file
Ext.ux.IconCombo.css
css
/* application specific styles */
.x-flag-us {
background-image:url(../img/flags/us.png);
}
.x-flag-de {
background-image:url(../img/flags/de.png);
}
.x-flag-fr {
background-image:url(../img/flags/fr.png);
}
/* Ext.ux.IconCombo mandatory styles */
.x-icon-combo-icon {
background-repeat: no-repeat;
background-position: 0 50%;
width: 18px;
height: 14px;
}
.x-icon-combo-input {
padding-left: 25px;
}
.x-form-field-wrap .x-icon-combo-icon {
top: 3px;
left: 5px;
}
.x-icon-combo-item {
background-repeat: no-repeat;
background-position: 3px 50%;
padding-left: 24px;
}
EXT的布局(Layout)
Ext的layout布局对于建立WEB程序尤为有用。关于布局引擎(layout engine),区域管理器(region manager)的教程将分为几部分,本文是第一篇,为您介绍如何创建区域,如何增加版面到这些区域。
布局引擎(layout engine)这一功能早已在EXT前个ALPHA实现了。 Jack Slocum对于怎样环绕某一区域,给与指定区域管理的策略,和建立界面的问题,在他的第一、第二篇关于跨浏览器的WEB2.0布局功能的博客中,进行过讨论。定义一个DOM元素的边界(edge),使之一个布局的边框(border)--这种做法使得创建“富界面”客户端UI的开发更进一大步。
布局管理器(layout manager)负责管理这些区域。布局管理的主要的用户组件是BorderLayout类。该类为EXT开发富界面的程序提供了一个切入点。Layout的含意是划分好一些预定的区域。可用的区域分别有south, east, west, north,和center。每一个BorderLayout对象都提供这些区域但只有center要求必须使用的。如果你在单独一个区域中包含多个面板,你可通过NestedLayoutPanel 类套嵌到BorderLayout 实例中。
注意事项:本教程的每个文件都是.html和.js格式的。教程每一步都有演示,你也可以下载这些文件在编辑器(zip格式提供在这里)中看看发生什么事。
面板(Panel)是区域管理(region management)的另外一个组件。面板提供了这么一个地方,可为您的EXT器件(widget)、加载的HTML,嵌入的IFrames、或者是你日常在HTML页面上摆放的随便一样东西。NestedLayoutPanel也是一个面板,只不过用于链接多个BorderLayout的区域,其它的面板包括内容面板 ContentPanel,Grid面板 GridPanel,和Tree面板 TreePanel。
简单的例子
下面的layout包含 north, south, east, west,和center的区域,而且每个区域包含一个ContentPanel,各区域之间使用得了分隔条分割开。
var mainLayout = new Ext.BorderLayout(document.body,
{
north: {
split: true, initialSize: 50
},
south: {
split: true, initialSize: 50
},
east: {
split: true, initialSize: 100
},
west: {
split: true, initialSize: 100
},
center: {
}
});
这是一个非常基本的layout,只是分配了东南西北中间的区域、分隔条、设置一下初始尺寸,并最迟定义中间区域。本例中, BorderLayout被绑定到"document.body"这个DOM元素,其实BorderLayout还可以绑定到任何一个封闭的DOM元素。定义好BorderLayout之后,我们加入ContentPanel对象(基于本例)。
mainLayout.beginUpdate();
mainLayout.add('north', new Ext.ContentPanel('north-div', {
fitToFrame: true, closable: false
}));
mainLayout.add('south', new Ext.ContentPanel('south-div', {
fitToFrame: true, closable: false
}));
mainLayout.add('east', new Ext.ContentPanel('east-div', {
fitToFrame: true, closable: false
}));
mainLayout.add('west', new Ext.ContentPanel('west-div', {
fitToFrame: true, closable: false
}));
mainLayout.add('center', new Ext.ContentPanel('center-div', {
fitToFrame: true
}));
mainLayout.endUpdate();
当前的例子是将ContentPanel加入到所有区域中。由调用mainLayout.beginUpdate()开始。beginUpdate ()告诉BorderLayout对象在执行endUpate()方法之前,先不要对加入的对象排版布局。这样的好处是避免了ContentPanel有对象加入时,导致UI的刷新,改进了整体的用户体验。执行beginUpdate()之后,加入五个ContentPanel对象到区域。所有的 ContentPanel对象(除中间的那个外),都设置是可关闭的(closbale)。所有的ContentPanel对象也都设置为自动适配它们的父元素。最后执行endUpdate()渲染layout。
InternetExploer注意事项:BorderLayout所容纳的元素必须有一个SIZE以便正确渲染。典型地你无须为document.body 指明size,因为document.body通常是有size的了(大多数情况,-除非你在浏览器上什么也看不到)。但是如果你将layout连同容器放到现有的web页面上(‘可能是DIV),那么DIV的size应该先指明以便正确渲染。如下列显示正常:
好,让我们趁热打铁,看看完整的layout是怎样的。假设ext是一子目录叫做ext-1.0,父目录下面的代码。
simple.html:
simple.js:
Simple = function() {
return {
init : function() {
var mainLayout = new Ext.BorderLayout(document.body, {
north: {
split: true, initialSize: 50
},
south: {
split: true, initialSize: 50
},
east: {
split: true, initialSize: 100
},
west: {
split: true, initialSize: 100
},
center: {
}
});
mainLayout.beginUpdate();
mainLayout.add('north', new Ext.ContentPanel('north-div', {
fitToFrame: true, closable: false
}));
mainLayout.add('south', new Ext.ContentPanel('south-div', {
fitToFrame: true, closable: false
}));
mainLayout.add('east', new Ext.ContentPanel('east-div', {
fitToFrame: true, closable: false
}));
mainLayout.add('west', new Ext.ContentPanel('west-div', {
fitToFrame: true, closable: false
}));
mainLayout.add('center', new Ext.ContentPanel('center-div', {
fitToFrame: true
}));
mainLayout.endUpdate();
}
};
}();
Ext.EventManager.onDocumentReady(Simple.init, Simple, true);
加入内容
上面的例子做的layout,除了可移动分割栏外,功能还不强大。需要加入些内容。有几种的办法加入内容。如果您直接加入内容到DIV中(ContentPanel绑定的那个),ContentPanel对象会对div里面的内容进行渲染。尽管试试!我们会更改html内容加入 center-div中。
simple2.html:
This is some content that will display in a panel
when a ContentPanel object is attached to the div.
除此之外,还可以利用ContentPanel对象带有的function加载数据。可用的方法有几种,这里我们使用其中两种:setContent() 与 setUrl()。setContent()允许您直接从JavaScipt程序中插入HTML。setUrl(),允许您从服务端得到数据加入ContentPanel中。
我们原来的例子中,ContentPanel对象创建的时候是匿名的(anonymous)。这没问题,但要引用它们,你需要遍历区域管理器所分配的对象以获得引用的对象。这不是最好的办法,所有我的做法是分配一个变量给ContentPanel然后便可直接引用。
simple3.js:
Simple = function() {
var northPanel, southPanel, eastPanel, westPanel, centerPanel;
return {
init : function() {
var mainLayout = new Ext.BorderLayout(document.body, {
north: {
split: true, initialSize: 50
},
south: {
split: true, initialSize: 50
},
east: {
split: true, initialSize: 100
},
west: {
split: true, initialSize: 100
},
center: {
}
});
mainLayout.beginUpdate();
mainLayout.add('north', northPanel = new Ext.ContentPanel('north-div', {
fitToFrame: true, closable: false
}));
mainLayout.add('south', southPanel = new Ext.ContentPanel('south-div', {
fitToFrame: true, closable: false
}));
mainLayout.add('east', eastPanel = new Ext.ContentPanel('east-div', {
fitToFrame: true, closable: false
}));
mainLayout.add('west', westPanel = new Ext.ContentPanel('west-div', {
fitToFrame: true, closable: false
}));
mainLayout.add('center', centerPanel = new Ext.ContentPanel('center-div', {
fitToFrame: true
}));
mainLayout.endUpdate();
northPanel.setContent('This panel will be used for a header');
westPanel.setContent(' ');
centerPanel.setUrl('index.html');
centerPanel.refresh();
}
};
}();
Ext.EventManager.onDocumentReady(Simple.init, Simple, true);
我们现在从现有的页面动态加载内容。但是这里有个问题。若果内容页面积过大而撑破页面的话将没有意义了。我们提供了一些配置属性以解决这类问题。当 fitToFrame为true时,就自动配置autoScroll。内容一旦溢出就会出现滚动条。另外一个涉及InternetExploer的问题, 是中间的内容的样式没有生效,原因是一些浏览器支持动态样式而一些不支持,要较好地解决上述问题,推荐使用Iframe标签。
用IFRAME标签做布局可灵活地处理,我们准备在DOM中直接操纵IFRAME.这里IFRAME成为面板的容器,以填入中间区域的内容
设置一下 IFRAME的滚动条并放到中间的页面。.
simple4.html:
simple4.js:
[code="java"]Simple = function() {
var northPanel, southPanel, eastPanel, westPanel, centerPanel;
return {
init : function() {
var mainLayout = new Ext.BorderLayout(document.body, {
north: {
split: true, initialSize: 50
},
south: {
split: true, initialSize: 50
},
east: {
split: true, initialSize: 100
},
west: {
split: true, initialSize: 100
},
center: {
}
});
mainLayout.beginUpdate();
mainLayout.add('north', northPanel = new Ext.ContentPanel('north-div', {
fitToF