Chapter 9: All about Events
現狀:
事件機制最終被納入DOM标準中,但是由于其坎坷的發展曆史,目前不同的浏覽器對事件的實作各不相同。
事件流:
事件的傳遞分為冒泡階段(bubbling),和捕獲階段(capturing),在DOM标準中,兩個過程都是被支援的。其機制與Actionscript很相似。
事件處理函數/偵聽器:
基本的方式有兩種,一種是在HTML标簽中直接添加偵聽器:
<div οnclick="alert('I was clicked')"></div>
另一種是在script标簽中通過給屬性指派為一個函數引用:
oDiv.onclick = function () {
alert("I was clicked");
};
後者必須使用全部小寫的英文來指定屬性名。
IE的處理是通過使用attachEvent()與detachEvent()方法;而在DOM中,用addEventListener()與removeEventListener()函數。通過這些函數,一個事件可以與多個偵聽器關聯,也就是一個事件發生後,多個處理函數被執行。另外,用addEventListener()與removeEventListener()時,第三個參數必須相同,即都為true或都為false,這個參數是指定處理事件的階段,在冒泡階段或者捕獲階段。預設為false,即冒泡階段。
事件對象:
在IE與DOM标準中,偵聽器接收事件對象的方式有一些不同,IE要通過window對象,而在DOM中,Event對象則是作為一個獨立的參數。
IE下
oDiv.onclick = function () {
var oEvent = window.event;
}
DOM下
oDiv.onclick = function () {
var oEvent = arguments[0];
}
或
oDiv.onclick = function (oEvent) {
}
在IE與DOM中,Event對象支援的屬性與方法也不同。
事件的種類:
滑鼠事件
在事件處理函數裡,可以通過this引用觸發事件的HTML對象。
鍵盤事件
HTML事件
包含很多,比如load,unload,編輯元件的select事件,控件的change事件,表單的submit,reset事件,focus事件,window的resize和scroll事件等等。
還有DOM變更事件。
HTML事件
多數浏覽器尚未支援。
跨浏覽器的事件:
作者在這一節介紹了一個方案來實作跨浏覽器的Js代碼,并且也給出了示例代碼,他建立了一個EventUtil對象,并用它作為一個容器包住一些Javascript通常會用到的基本功能,所有判斷浏覽器而執行的不同代碼已經被包裝在這個對象裡,然後它提供一個唯一的接口,隻需要通過調用它們就可以得到在不同浏覽器獲得相同效果。
不過因為這個讨論有時效性,眼下不同浏覽器的差別應該有所不同,是以暫時忽略這一節。
REFs:
Events Supported in Javascript
Chapter 10:Advanced DOM Techniques
編輯樣式:
在DOM中,每個HTML元素都有一個style對象作為其屬性,而它能夠套用的所有樣式都可以通過style對象的屬性通路,比如:
var oDiv = document.getElementById("div1");
oDiv.style.border = "1px solid black";
<div id="div1" style="background-color: red; height: 50px; width: 50px" οnmοuseοver="this.style.backgroundColor = 'blue'" οnmοuseοut="this.style.backgroundColor = 'red'">
</div>
除此以外,style對象也有一些方法用來操作樣式屬性:getPropertyValue(propertyName),getPropertyPriority()等。
以上方法隻能獲得内嵌的元素樣式,不能夠獲得style标簽内的樣式或者外部引用的樣式,想通路外部樣式,需要通過document.styleSheets集合,不過在實際應用中,它并不常見。
有一個概念為computed style,即是計算後所得的實際樣式,即使這個樣式是通過外部樣式檔案生效的,這個樣式在任何實作中都是個隻讀的屬性,在IE中,通過currentStyle來通路,在DOM中,通過document.defaultView.getComputedStyle()方法。
innerText與innerHTML:
通過實驗,Chrome與IE還有AIR-HTMLLoader一樣,定義了innerText與innerHTML,但是Firefox隻是定義了innerHTML。規則參加原著315頁。
outerText與outerHTML:
Range:
它實際上指的是DOM中的某些節點,包括這些節點的範圍。因為IE與DOM标準的巨大差異,多數程式員都選擇放棄這一特性。是以我也跳過這節。
Chapter 11: Forms and Data Integrity
表單基礎:
一些重要的屬性諸如:method,action。還有幾種輸入控件:text,radio,file,submit,reset,hidden等等,注意,image也是其中之一,而且它的作用同submit相同。
有時候,可以通過for屬性将label與控件綁定在一起:
<label for="selAge">Age:</label><br />
<select name="selAge" id="selAge">
<option>18-21</option>
<option>22-25</option>
<option>26-29</option>
<option>30-35</option>
<option>Over 35</option>
</select>
編輯表單裡的元素:
獲得表單的引用可以通過document.getElementById(),document.forms[0]或者document.["formz"]。至于表單裡的元素,可以通過元素名作為屬性直接通路,也可以通過elements集合。
表單裡的控件有一些通用的屬性或方法:disabled,form,blur(),focus(),blur,focus。
送出表單有兩種辦法,一種是用表單裡的送出控件:
<input type="submit" value="Submit" /> <!-- submit button -->
<input type="image" src="submit.gif" /> <!-- image button -->
它們的作用一模一樣。這種做法的好處是,表單對象将會收到submit事件,于是可以通過定義該事件的處理函數來在送出之前做一些處理,比如驗證資料,甚至也可以取消送出。
unction handleSubmit() {
var oEvent = EventUtil.getEvent();
oEvent.preventDefault();
}
<form method="post" action="javascript:alert('Submitted')" οnsubmit="handleSubmit()">
另一種送出的方式是調用表單的方法submit(),該方法執行不會令表單收到事件。
reset與submit的不同之處在于,執行reset()函數,表單會收到reset事件。
文本框控件:
blur事件與change事件的差別,當該控件失去焦點時,就會收到blur事件,但是隻有當失去焦點同時文本内容又變化了才會收到change事件;而且在後者的情況下blur事件先于change事件發生。
這一節介紹了一些常見的文本編輯會用到的功能,包括一些輔助使用者操作的功能,像當使用者完成某個控件的編輯後自動跳轉到下一個控件,自動選擇控件的所有文字;也包括一些驗證的功能,比如限制使用者可以輸入的字元,而且我發現,文本框控件居然還有onpaste事件。
隻有字母鍵才會觸發keypress事件。但所有的鍵都會觸發keydown事件。
清單控件:
增加,移除,修改,排序。這節的最後作者提供了一個文字輸入自動提醒比對選擇的功能,這個功能在網上已經很常見,而且作者也将目前為止在書中提供的示例代碼都通過這裡例子組織在一起,比如TextUtil.js和ListUtil.js,這兩個檔案網上已經有人釋出了:
Chapter 12: Sorting Tables
這是一個很特定的話題,這一章作者主要想介紹的是Array的一些進階操作,還有Table的一些方法,當然,同時還有程式設計思路。暫時略過這一章。
Chapter 13: Drag and Drop
利用作業系統自身的拖拽功能:
作者給出一個使用作業系統的簡單例子,這裡隻用到dragstart,drag和dragend事件:
<html>
<head>
<title>System Drag And Drop Example</title>
<script type="text/javascript">
function handleDragDropEvent(oEvent) {
var oTextbox = document.getElementById("txt1");
oTextbox.value += oEvent.type + "\n";
}
</script>
</head>
<body>
<form>
<p>Try dragging the image.</p>
<p><img src="images/smiley.gif" alt=""
οndragstart="handleDragDropEvent(event)"
οndrag="handleDragDropEvent(event)"
οndragend="handleDragDropEvent(event)" /></p>
<p><textarea rows="10" cols="25" readonly="readonly"
id="txt1"></textarea></p>
</form>
</body>
</html>
在Chrome,Firefox以及AIR-HTMLLoader上測試後發現,現在的版本也都支援了。當然我用的是Windows XP。
下面再測試下drag target那個程式:
<html>
<head>
<title>System Drag And Drop Example</title>
<script type="text/javascript">
function handleDragDropEvent(oEvent) {
var oTextbox = document.getElementById("txt1");
oTextbox.value += oEvent.type + "\n";
}
</script>
</head>
<body>
<form>
<p>Try dragging the text from the left textbox to the right one.</p>
<p>
<input type="text" value="drag this text" />
<input type="text" οndragenter="handleDragDropEvent(event)"
οndragοver="handleDragDropEvent(event)"
οndragleave="handleDragDropEvent(event)"
οndrοp="handleDragDropEvent(event)" />
</p>
<p>
<textarea rows="10" cols="25" readonly="readonly"
id="txt1"></textarea>
</p>
</form>
</body>
</html>
在三個浏覽器上測試,雖然都實作了,可是Firefox的做法與另外兩個不同,它是複制一份文本到目标,而另外兩個則是剪切。
作者提到,隻有text控件才是可以對其釋放拖拽的,預設情況下其他HTML元素不可以,如果想要做到,就需要覆寫預設操作,按作者所說,因為隻有IE實作了通過作業系統的拖拽,那麼就隻需要寫IE下的修改預設行為,通過:
oEvent.returnValue = false;
但是實際上,通過下面的實驗,我發現Firefox、Chrome和AIR-HTMLLoader也可以的。這是書中394頁的展示dataTransfer的示例代碼:
<html>
<head>
<title>System Drag And Drop Example</title>
<script type="text/javascript">
function handleDragDropEvent(oEvent) {
switch(oEvent.type) {
case "dragover":
case "dragenter":
oEvent.returnValue = false;
//oEvent.preventDefault();
break;
case "drop":
alert(oEvent.dataTransfer.getData("text"));
}
}
</script>
</head>
<body>
<p>Try dragging the text from the textbox to the red square.
It will show you the selected text when dropped.</p>
<p><input type="text" value="drag this text" />
<div style="background-color: red; height: 100px; width: 100px"
οndragenter="handleDragDropEvent(event)"
οndragοver="handleDragDropEvent(event)"
οndrοp="handleDragDropEvent(event)"></div></p>
</body>
</html>
事實上,這段代碼在IE、Chrome還有AIR-HTMLLoader下都工作良好,在Firefox下,則需要将
oEvent.returnValue = false;
修改為:
oEvent.preventDefault();
Firefox才不會出錯,但是Firefox的行為也與另外兩個大相徑庭,它是将拖拽的文字當成一個連接配接在原視窗開啟。
dataTransfer的兩種用法就是或者用來傳遞文本,或者用來傳遞連接配接:
oEvent.dataTransfer.setData("text", "some text");
var sData = oEvent.dataTransfer.getData("text");
oEvent.dataTransfer.setData("URL", "http://www.wrox.com/");
var sURL = oEvent.dataTransfer.getData("URL");
在程式員沒有明确指定指派的情況下,隻有當被拖拽的對象是一個連接配接時,後者才可以被使用。從上面的實驗可見,Chrome與Firefox也都實作了這個對象。
dropEffect 和effectAllowed是用來一起使用指定當使用者把某個對象拖到另一個對象上并釋放時,到底執行什麼操作。可以什麼也不做,可以複制或者移動,還有可以打開連接配接,就像Firefox在上一個實驗裡那樣。使用的規則是:dropEffect用來指定到底做什麼,它必須在目标對象的ondragenter事件中使用,而在此之前,必須通過effectAllowed聲明可以執行哪些操作,它必須在被拖拽對象的ondragstart事件中使用。
至于各個浏覽器的支援情況,就得看實驗了。
<html>
<head>
<title>System Drag And Drop Example</title>
<script type="text/javascript">
function handleDragDropEvent(oEvent) {
switch(oEvent.type) {
case "dragstart":
oEvent.dataTransfer.effectAllowed = "move";
break;
case "dragenter":
oEvent.dataTransfer.dropEffect = "move";
oEvent.returnValue = false;
//oEvent.preventDefault(); //Firefox
break;
case "dragover":
oEvent.returnValue = false;
//oEvent.preventDefault(); //Firefox
break;
case "drop":
oEvent.returnValue = false;
//oEvent.preventDefault(); //Firefox
oEvent.srcElement.innerHTML =
oEvent.dataTransfer.getData("text");
}
}
</script>
</head>
<body>
<p>Try dragging the text in the textbox to the red square.
The text will be "moved" to the red square.</p>
<p><input type="text" value="drag this text"
οndragstart="handleDragDropEvent(event)" />
<div style="background-color: red; height: 100px; width: 100px"
οndragenter="handleDragDropEvent(event)"
οndragοver="handleDragDropEvent(event)"
οndrοp="handleDragDropEvent(event)"></div>
</p>
</body>
</html>
這段代碼在IE和Chrome裡運作正常,但在Firefox裡和AIR-htmlloader沒有反應。
上面是如何自己建立拖拽目标對象,下面是關于如何建立被拖拽對象,按作者所講,這個也是IE獨有,即dragDrop()函數,給我的感覺是它有點像Actionscript裡的startDrag()。作者認為最好的使用時機是在onmousemove事件處理函數裡。
模拟拖拽:
這個方法适合于所有浏覽器,但是當然也有一些限制。這個設計的思路是将被拖動和目标對象設定為絕對位置,然後讓被拖動對象的位置在mousemove事件處理函數裡跟着滑鼠的坐标移動。既然上面讨論的方法在多數浏覽器裡都可行,那麼這裡就不多說了。
利用zDraggable:
用第三方庫zDraggable。
Chapter 14: Error Handling
(略)遲一些再回來這裡。
Chapter 15: XML in JavaScript
現在流行的用于Javascript與伺服器端互動的資料格式是JSON,它是針對Js設計的,快過于XML。是以這一章的實際用處不大,故此略過。
Chapter 16: Client-Server Communication
Cookies:
各個語言關于cookie的操作函數都很直接和簡單,是以沒啥好說的,需要用時看下手冊就行了。
隐藏Frames:
這個辦法明顯過時。
HTTP請求:
這個就是Ajax的雛形了。核心概念就是一個叫XML HTTP Request的對象。首先是如何在不同浏覽器中建立它:
if (typeof XMLHttpRequest == "undefined" && window.ActiveXObject) {
function XMLHttpRequest() {
var arrSignatures = ["MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0",
"MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP",
"Microsoft.XMLHTTP"];
for (var i=0; i < arrSignatures.length; i++) {
try {
var oRequest = new ActiveXObject(arrSignatures[i]);
return oRequest;
} catch (oError) {
//ignore
}
}
throw new Error("MSXML is not installed on your system.");
}
}
于是可以這樣使用上面的函數:
var oRequest = new XMLHttpRequest();
建立之後,發起一個請求,這個請求可以是同步的:
oRequest.open("get", "example.txt", false);
oRequest.send(null);
也可以是異步:
oRequest.open("get", "example.txt", true);
oRequest.onreadystatechange = function () {
if (oRequest.readyState == 4) {
alert("Status is " + oRequest.status + " (" + oRequest.statusText + ")");
alert("Response text is: " + oRequest.responseText);
}
}
oRequest.send(null);
在異步情況下,必須要指定onreadystatechange方法。
如果請求的是一個XML檔案,那麼對象的responseXML屬性就會被浏覽器填充:
oRequest.open("get", "example.xml", false);
oRequest.send(null);
alert("Status is " + oRequest.status + " (" + oRequest.statusText + ")");
alert("Response text is: " + oRequest.responseText);
alert("Tag name of document element is: " +
oRequest.responseXML.documentElement.tagname);
通過XMLHTTPRequest的方法可以操作header屬性:
getResponseHeader()
getAllResponseHeaders()
setRequestHeader()
可以發起一個GET請求:
function addURLParam(sURL, sParamName, sParamValue) {
sURL += (sURL.indexOf("?") == -1 ? "?" : "&");
sURL += encodeURIComponent(sParamName) + "=" + encodeURIComponent(sParamValue);
return sURL;
}
var sURL = "http://www.somwhere.com/page.php";
sURL = addURLParam(sURL, "name", "Nicholas");
sURL = addURLParam(sURL, "book", "Professional JavaScript");
oRequest.open("get", sURL, false);
或者POST請求:
function addPostParam(sParams, sParamName, sParamValue) {
if (sParams.length > 0) {
sParams += "&";
}
return sParams + encodeURIComponent(sParamName) + "="
+ encodeURIComponent(sParamValue);
}
var sParams = "";
sParams = addPostParam(sParams, "name", "Nicholas");
sParams = addPostParam(sParams, "book", "Professional JavaScript");
oRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
oRequest.open("post", "page.php", false);
oRequest.send(sParams);
下面咱們來做個小實驗,先寫個PHP,輸出寫簡單的字元串:
<?php
function add_odd_numbers($x, $y) {
assert('!(($x % 2) && ($y % 2))');
return ($x + $y);
}
$answer_one = add_odd_numbers(3, 5);
$answer_two = add_odd_numbers(2, 4);
echo "3 + 5 = $answer_one\n";
echo "2 + 4 = $answer_two\n";
?>
然後寫個HTML使用XMLHTTPRequest來通路這個PHP:
<html>
<head>
<title>XHR Example</title>
</head>
<body>
<script type="text/javascript">
xhr = new XMLHttpRequest();
xhr.open("get", "http://localhost/f/t2.php", false);
xhr.send(null);
alert("Status is " + xhr.status + " (" + xhr.statusText + ")");
document.writeln(xhr.responseText);
</script>
</body>
</html>
實驗結果來看,IE8也開始支援XMLHTTPRequest對象了,不需要用ActiveX也能得到一樣的效果,上面的程式在我的四個浏覽器裡都運作正常。
即時連接配接請求:
這個做法的實質是Javascript調用Java的類接口來實作的,而真正工作的是Java,可能是J2EE版本裡的Applet。總之這個做法很少見。
智能HTTP請求:
這節裡作者提供一個跨浏覽器方案可以用統一的代碼實作上面讨論的Javascript同server通訊。
Chapter 17: Web Services
Chapter 18: Interacting with Plugins
使用插件的好處:
流行的插件:
MIME類型:
嵌入插件:
偵測插件:
Java小程式:
Flash影片:
這一節有提到Javascript如何與Flash裡的Actionscript互動,可是這裡的Actionscript的版本是2.0,以這本書出版的年代來看,當時還沒推出Actionscript3.0。是以這一節的實際意義也被大打折扣了。
ActiveX 控件:
Chapter 19: Deployment Issues
Chapter 20: The Evolution of JavaScript