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()
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