天天看點

Notes On <Pro JavaScript with MooTools> - 02

之是以先從這裡開始,因為前面七個章節都是在講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();
           

繼續閱讀