之所以先从这里开始,因为前面七个章节都是在讲Javascript本身的运作原理和问题为主。而我需要快一点开始对MooTools的探索,因为我的同事关心的只是我能否尽快完成我的任务。
Chapter 8: Elements
Families and Trees
这个问题首先要澄清的是Element的含义。因为跳过了前面的章节,所以有一点令人困惑。于是我插入一些解释在这里,在之前的书里我们了解到,DOM技术实际上是基于XML的,它只是针对HTML做了一些更为细致的扩展,这种扩展包括增加一些方法,等等。此刻提到的Element,指的是DOM树中的一个节点,它代表了一个具体的HTML标签片段。那么在MooTools里,Element是一个Javascript环境里的新扩展的类型(就像Array和String),它是被MooTools增加的,实际上MooTools的做法是扩展浏览器的Javascript环境里对DOM的节点类的定义,于是在纯Javascript的环境下HTMLElement的类型,到了MooTools里通常是用Element来表达和处理的,它有点像jQuery里Wrapped Set的概念。因为这涉及到MooTools的机制,所以暂时只说这么多。
Is My DOM Ready Yet?
DOM Scripting with MooTools
Selecting Elements
The ID Selector
MooTools提供一个方法:document.id()来通过ID获取一个元素,它有它的简写形式,即是$()。它接收的参数就是元素的ID值本身,它获取的是这个元素对象的原件的引用。
另外,MooTools重写了document.getElementById()方法,令它返回的类型不再是HTMLElement,而是MooTools的Element类型。
CSS Based Selectors
MooTools将DOM标准中的两个方法的功能:document.getElementsByTagName()与document.getElementsByClassName()整合到了同一个方法中:$$()。
与上面的方法不同,它接收的参数字符是一个CSS选择器。所以它的格式需要是诸如:#idname,.classname,tagname,同时,它返回的结果不是Element,而是一个Elements类型,它实际上是个Element的数组,同时也是由于这个原因,$$()方法返回的Elements对象是一个新建的对象引用。即是说$$('sameCSSSelector')===$$('sameCSSSelector')将返回false。
除此以外,MooTools还给Element类提供了两个成员方法:getElements()与getElement()。
在一个Element上呼叫getElements(),并传入一个CSS选择器作为参数,将返回这个Element包含的HTML中,符合选择器的元素集合。getElement()则只会返回集合中第一个Element,不过这个方法有一些特别的使用技巧,在CSS选择器中,你可以用联合符号来将不同的两个选择器联合起来,比如,默认来讲,一个空格表示的是祖孙关系的联合,“div p”表示的是div标签里的所有直接的和非直接的p标签。但是除了这种联合关系,CSS标准里还有其他关系比如相邻兄弟关系,用“+”表示。而getElement()方法在默认情况下是以祖孙关系工作的,可是我们可以通过在参数中提供其他的联合符号来改变它的工作方式,比如:
someElement.getElement('+')....
Relation-Based Selector
MooTools提供了如下方法:getPrevious(),getNext(),getAllPrevious(),getAllNext(),getChildren(),getFirst(),getLast(),getParent(),getAllParent()。
An Elemental Segue
getElement()返回的值是一个Element或者null,而$$()和getElements()返回的总是一个有效的Elements,只是它可能包含0个或多个元素。另外,如果在一个Elements上直接呼叫Element的方法,它实际的做法是在数组内的每个Element上呼叫该方法,将返回值放在一个数组中,将数组返回。还有,MooTools给Elements类提供了each()方法,它的做法同jQuery里的相似,将作为参数传入的函数作用在每个Element上。
Moving Elements Around
inject
它的最基本的使用方法是在被移动对象上呼叫这个方法,将目标作为参数:someElement.inject('target')。另外一种形式是在第二个参数中指定位置,它可以是:top,bottom,after或者before;后两者将把对象作为兄弟节点插入到目标的前或者后。
replaces
关于这个方法,需要注意的是不要在一个Elements类型上使用它,因为会抛出错误。目标对象只会被注销一次。
wraps
这个方法将目标包含进呼叫对象里,即是说,传入的参数指定的是被包含的对象,它也支持指定位置,通过第二个参数:top或者bottom。
grab
上面的方法是将呼叫者作为操作的客体,将参数作为目标。下面的两个方法则是倒过来,传入的参数作为被移动的对象,就是grab()和adopt()。前者用于单个Element;而后者用于一个集合,比如将一个ul标签下的所有li标签都移动到另一个ul标签下面。
Modifying Element Objects
Working with Attributes
MooTools提供了三个函数用来操作元素的属性:setProperty(),getProperty()和removeProperty()。前两者还可以同时作用于多个属性:
var link = $('home-link');
link.setProperties({
'href': 'otherhome.html',
'target': '_blank',
'super-weird-attrib': 'wow'
});
Working with Styles
MooTools提供了两个函数用来操作元素的CSS样式:setStyle()和getStyle()。前者还有一个变本setStyles()可以同时作用于多个属性:
var div = $('wrapper');
div.setStyles({
'background-color': '#FFF',
'height': '200px'
});
Get, Set, and Erase
MooTools提供了用于操作属性的一组方法,即是这节的小标题所展示的。set()方法的基本格式为:
$('wrapper').set('style', 'background-color: #000');
$('wrapper').set('styles', {'background-color': '#000', 'height': '200px'});
因为set()方法并不能够理解所有的属性名(它可以理解像html、class、style这些基本的属性),于是当它遇到它不认识的属性名时,它便会呼叫setProperty()方法,并将参数传给它。它也可以同时用于多个属性:
$('wrapper').set({
'html': '<p>Hello!</p>',
'class': 'greeting',
'styles': {
'background-color': '#000',
'color': '#FFF'
},
'fancy-attrib': 'magical'
});
至于get()与erase()方法,它们的工作方式相同。erase()的效果等同于使用set()时将null传递给第二个参数。
Creating Elements
当然基于目前介绍的内容来看,最简单的达到这个目的的做法是设定一个元素的html属性,实际上是修改它的innerHTML值。可是这显然不是这里要介绍的方法,关于这个目的,MooTools首先提供的是clone()方法:
var newItem = $('list').getElement('li').clone();
newItem.set('text', 'Item C').inject('list');
但是既然Element在MooTools里是一个类型,那么我们可以利用它的构造函数来创建一个Element,它的简单形式是:
var newItem = new Element('li');
newItem.inject('list');
当然,它也有一个复杂的用法,即是将所有的属性传入,构造函数会自动通过调用set()方法来设定这些属性:
new Element('div', {
'id': 'wrapper',
'class': 'container',
'html': '<p>Hello!</p>',
'styles': {
'background': '#000',
'color': '#FFF',
'font-size': '12px'
},
'data-name': 'wrapper div'
});
Destorying Elements
MooTools里面与这个功能相关的有三个方法,首先是destroy(),它会彻底地清除一个Element;其次是dispose(),它只会把一个Element从DOM树中移除,并不会将它从内存中彻底清掉;最后是empty(),它则是把呼叫者的子Element都彻底清除。
(部分暂略)
Element Storage
有些时候我们需要在一些元素上存储一些我们自定义的数据,当然我们可以使用前面介绍的set()方法和get()方法,可是它们的局限是,被存储的数据只能以字符的形式。为了解决这个问题,MooTools提供了store()和retrieve()方法。
Chapter 9: Selector Engines
(部分暂略)
Chapter 10: Events
The MooTools Event System
Attaching Event Handlers
在MooTools里专门设计用来完成这个目的的方法是addEvent(),用法:
var links = $$('a');
links.addEvent('click', function(event){
console.log('I was clicked');
});
还有同时绑定多个事件的addEvents():
var item = $('item');
item.addEvents({
'click': function(event){
console.log('Clicked');
},
'dblclick': function(event){
console.log('Double-Clicked');
},
'focus': function(event){
console.log('Focused');
}
});
但是由于MooTools自身的机制和set()方法的运行方式,下面的做法也可以:
var item = $('item');
item.set('events', {
'click': function(event){
console.log('Clicked');
},
'dblclick': function(event){
console.log('Double-Clicked');
},
'focus': function(event){
console.log('Focused');
}
});
所以,在创建Element时将绑定事件写进构造函数也是可以的:
var div = new Element('div', {
events: {
'click': function(event){
console.log('Clicked');
}
}
});
Preventing Default Action
MooTools提供和DOM标准相同名称的方法,即:Event类的preventDefault()方法,来实现这个功能。
Stopping Event Propagation
MooTools提供和DOM标准相同名称的方法,即:Event类的stopPropagation()方法,来实现这个功能。
Stopping Events All Together
MooTools提供一个特别的Event类的方法:stop(),它的作用等于同时呼叫前面两个方法。
Detaching Event Handlers
MooTools提供的方法是:removeEvent()和removeEvents()。
Dispatching Events
MooTools提供了fireEvent()来处理这个功能,这个方法支持一个可选的第二个参数:
var handler = function(event){
console.log(event.foo); // 'bar'
};
var link = $('main');
link.addEvent('click', handler);
// dispatch a click event
setTimeout(function(){
link.fireEvent('click', {foo: 'bar'});
}, 5000);
它也有另外一个形式令你可以传递更多的参数:
var handler = function(a, b){
console.log(a); // 'foo'
console.log(b); // 'bar'
};
var link = $('main');
link.addEvent('click', handler);
// dispatch a click event
setTimeout(function(){
link.fireEvent('click', ['foo', 'bar']);
}, 5000);
Event System Internals
Chapter 11: Request
The MooTools Request Class
在介绍MooTools的相关类之前,先整理下Javascript自身的Ajax功能的缺陷:
首先XHR对象的初始化与构造是分离的;请求的成功与失败都是放在同一个事件里;超时事件不是原生态支持;对于收到的文本,XHR并不能区分不同类型,我们要自己动手做。其实之所以提及这些是暗示MooTools是基于这些方面来考虑并设计的。MooTools提供了Request类来进行Ajax请求,实际上它是对XHR的包装与抽象。在后面的代码示例中,我们将用MooTools的做法来实现下面这个Javascript里的XHR请求:
window.addEvent('domready', function(){
var notify = $('notify'),
data = 'name=Mark&age=23';
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://foo.com/comment/', true);
xhr.onreadystatechange = function()
{
if (xhr.readyState == 4)
{
if (xhr.status >= 200 && xhr.status < 300)
{
notify.set('html', xhr.responseText);
}
else
{
notify.set('html', '<strong>Request failed, please try again.</strong>');
}
}
};
xhr.setRequestHeader('Accept', 'text/html');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.setRequestHeader('Content-Length', data.length);
xhr.send(data);
notify.set('html', '<strong>Request sent, please wait.</strong>');
// timeout
setTimeout(function()
{
xhr.abort();
notify.set('html', '<strong>Request timeout, please try again.</strong>');
}, 5000);
});
创建新请求
现在我们来用Request类型的构造函数来创建一个Request。
var request = new Request({
url: 'http://foo.com/comment/',
method: 'post',
async: true
});
在Request构造函数中有很多选项,而它们中很多有默认值,比如method的默认值就是post,async的默认值就是true。所以上面的代码中可以忽略它们:
var request = new Request({
url: 'http://foo.com/comment/'
});
刚刚讲过Request是对XHR的封装,所以XHR是Request的一个成员属性,而可以通过xhr来访问它。
添加请求头数据
每个HTTP请求都有一些header这段会影响数据的传递方式,XHR请求也是一样,而在给Request对象设定header值是通过setHeader()方法:
var request = new Request({
url: 'http://foo.com/comment/'
});
request.setHeader('Accept', 'text/html');
request.setHeader('Content-Type', 'application/x-www-form-urlencoded');
request.setHeader('Content-Length', data.length);
其实这些header的值也是可以通过构造函数传递进去的:
var request = new Request({
url: 'http://foo.com/comment/',
headers: {
'Accept': 'text/html',
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': data.length
}
});
其实Request有一个属性:urlEncoded,把它设定为true的效果等同于刚刚同时设定三个header值,而它的默认值是true,所以上面的代码可以简化成:
var request = new Request({
url: 'http://foo.com/comment/'
});
发送数据
Request的send()方法用来发送请求:
var data = 'name=Mark&age=23';
var request = new Request({
url: 'http://foo.com/comment/'
});
request.send(data);
当然,也可以通过构造函数传递参数来指定发送的数据:
var data = 'name=Mark&age=23';
var request = new Request({
url: 'http://foo.com/comment/',
data: data
});
另外,data的格式不仅限于string,也可以是一个object:
var request = new Request({
url: 'http://foo.com/comment/',
data: {
'name': 'Mark',
'age': 23
}
});
将一个请求发送出去,只需要简单地呼叫send()方法。不过MooTools给它提供很多灵活性,send()方法可以接收不同的发送地址和发送的数据,来发送一个与在构造函数中指定的数据不同的请求,而sand()接收的数据不会用来覆盖这个请求的原始设定,即构造函数中指定的设定。具体地讲,send()接收的参数可以有两种形式,一种是单纯的字符串,这时send()会把它理解为发送的数据;也可以是一个对象,而这个对象有三个属性:url、method和data,data又是一个对象,携带发送给服务器的数据变量。这样的机制可以使我们能够重复利用同一个Request对象发送多次不同请求:
var request = new Request({
link: 'chain',
onSuccess: function(){
console.log(this.response.text);
}
});
request.send({url: '/index.html', method: 'get'});
request.send({url: '/comments', method: 'post', data: {name: 'Mark'}});
书中有一个强调,就是不能够把一个单纯对象形式的data对象传递给send()方法,它将会不理解这个参数,比如这种形式:{varA:valueA, varB:valueB}。
这个话题还涉及到一个要讨论的问题,就是Request的发送模式,即是说当一个Request对象正在忙于当前的一个请求时,而下一个请求被执行,这时Request对象的反应。MooTools提供了link参数给构造函数来指定这个机制,它可以有三个值:ignore、cancel和chain。它们的字面含义已经解释它们的运作原理了。
绑定事件函数
在MooTools里,Request对象会发布五个主要事件:
request事件发生在请求被发送后;
complete事件发生在请求完成后;
success事件发生在请求成功后;
failure事件发生在请求失败后;
cancel事件发生在请求被中断后。
Request类有一个方法叫isSuccess(),它用来判断一个请求是否成功,MooTools给我们提供定制这个方法的能力,这样我们可以定制如何去判断一个XHR请求是否成功:
var request = new Request({
method: 'get',
url: 'http://foo.com/index.html',
isSuccess: function(){
return this.status == 200;
}
});
success事件的处理函数会收到两个参数:第一个是去掉script代码块(出于安全考虑,如果想修改这个行为,需要指定evalScript参数为true,这样在收到返回数据后,script区块内的脚本会被自动执行;另外还有个evalResponse参数,如果它为true那么MooTools会将整个responseText当做是脚本代码,并在加载后自动运行)的XHR对象的responseText,第二个是XHR对象的responseXML的值。而failure事件的处理函数会收到一个参数,即是xhr对象本身。另外,如果你需要。也可以通过Request的response属性访问未被处理的原始返回数据。下面是一个将事件处理函数绑定到一个Request对象的代码:
var notify = $('notify');
var request = new Request({
url: 'http://foo.com/comment/',
data: {
'name': 'Mark',
'age': 23
}
});
request.addEvents({
'request': function(){
notify.set('html', '<strong>Request sent, please wait.</strong>');
},
'success': function(){
notify.set('html', this.response.text);
},
'failure': function(){
notify.set('html', '<strong>Request failed, please try again.</strong>');
}
});
除了使用addEvents()来绑定事件以外,我们也可以在构造函数中声明这些事件,在事件名前加一个前缀“on”,来作为传递的参数。
var request = new Request({
url: 'http://foo.com/comment/',
data: {
'name': 'Mark',
'age': 23
},
timeout: 5000,
onRequest: function(){
notify.set('html', '<strong>Request sent, please wait.</strong>');
},
onSuccess: function(){
notify.set('html', this.response.text);
},
onFailure: function(){
notify.set('html', '<strong>Request failed, please try again.</strong>');
},
onTimeout: function(){
notify.set('html', '<strong>Request timeout, please try again.</strong>');
}
});
超时处理
Request类有一个方法:cancel(),它可以用来中断一个处理中的请求,其实相当于使用XHR的abort()方法。而被中止了的请求对象会发出一个cancel事件。于是我们可以利用这个机制来处理超时:
var notify = $('notify');
var request = new Request({
url: 'http://foo.com/comment/',
data: {
'name': 'Mark',
'age': 23
}
});
request.addEvents({
'request': function(){
notify.set('html', '<strong>Request sent, please wait.</strong>');
},
'success': function(){
notify.set('html', this.response.text);
},
'failure': function(){
notify.set('html', '<strong>Request failed, please try again.</strong>');
},
'cancel': function(){
notify.set('html', '<strong>Request timeout, please try again.</strong>');
}
});
setTimeout(function(){
request.cancel();
}, 5000);
但实际上有更为简便的做法,即在构造函数中通过timeout参数来指定该请求的超时时间:
var request = new Request({
url: 'http://foo.com/comment/',
data: {
'name': 'Mark',
'age': 23
},
timeout: 5000
});
Subclassing Request
如果我们要发送一个请求,而这个请求期望的回复是以JSON格式,那么基于以上的内容,我们大致会这样来写:
var request = new Request({
url: 'myfile.json',
method: 'get',
headers: {
'Accept': 'application/json'
},
onSuccess: function(text){
var obj = JSON.decode(text);
if (obj)
{
console.log(obj.name);
}
else
{
console.log('Improper JSON response!');
}
}
}).send();
实际上MooTools里给Request做了很多子类,其中一个是Request.JSON,如果使用这个类,上面的代码可以简化成:
var request = new Request.JSON({
url: 'myfile.json',
method: 'get',
onSuccess: function(obj){
console.log(obj.name);
},
onFailure: function(){
console.log('Improper JSON response!');
}
}).send();