一、它支持平稳退化吗
<li>
<a href="images/fireworks.jpg" onclick="showPic(this); return false;" title="A fireworks display">Fireworks</a>
</li>
可以发现,在没有javascript的情况下,浏览器会沿着href属性给出的链接前进,虽然用户体验比用javascript的效果要逊色,但网页的基本功能未受到损害。即,它支持平稳退化。
二、它的javascript与HTML标签是分离的吗
<li>
<a href="images/fireworks.jpg" onclick="showPic(this); return false;" title="A fireworks display">Fireworks</a>
</li>
很明显,onclick事件处理函数是直接插入到HTML中的,因此不是分离的。理想情况下,应该是在外部文件里完成加添onclick事件处理函数的工作。
首先需要把js代码移动HTML,但需要把js代码与HTML标签关联起来,可以分别给每个图片添加同一个class属性,但过于复杂。我们可以发现图片链接都包含在ul元素里,因此值需要给ul元素设置一个id。
<ul id="imagegallery">
<li>
<a href="#" title="A fireworks display">Fireworks</a>
</li>
<li>
<a href="images/coffee.jpg" title="A cup of coffee">Coffee</a>
</li>
<li>
<a href="images/rose.jpg" title="A red rose">Rose</a>
</li>
<li>
<a href="images/bigben.jpg" title="The famous clock">Big Ben</a>
</li>
</ul>
1.添加事件处理函数
需要编写一个函数关联到onclick事件上。
【函数任务】
- 检查当前浏览器是否理解getElemenTagName
- 检查当前浏览器是否理解getElementsByTagName
- 检查当前页面是否存在一个id为imagegallery的元素
- 遍历imagegalley元素中所有的链接
- 设置onclick事件,让它有相关链接被点击时完成以下事件:把这个链接作为参数传递给showPic函数,并且取消链接被点击时的默认行为。
(1)检查点
首先,需要检查浏览器是否理解getElementsByTagName和getElementById的方法,此外,还需要检查是否存在id等于imagegalley的元素,如果不存在这个元素,那么函数将无需执行。
if(!document.getElementsByTagName) return false;
if(!document.getElementById) return false;
if(!document.getElementById("imagegallery")) return false;
(2)变量里有什么
使用变量gallery来保存图片库。
使用变量links来保存图片库的a标签
var gallery = document.getElementById("imagegallery");
var links = gallery.getElementsByTagName("a");
(3)遍历
接着,需要遍历处理links数组中的各个元素,可以使用for循环来完成。
for(var i=0;i<links.length;i++){
}
(4)改变行为
接下来,需要完成的操作是改变links数组中各个元素的行为。首先定义了一个匿名函数,它把links[i]元素的onclick事件处理函数指定为这个匿名函数,这个匿名函数的所有操作将在links[i]元素对应的链接被点击时执行。
传递给showPic函数的参数是this,this代表links[i],表示此时与onclick方法关联的那个元素。此外还需要禁用有关链接的默认行为。
links[i].onclick = function(){
showPic(this);
return false;
}
(5)完成javascript函数
for(var i=0;i<links.length;i++){
links[i].onclick = function(){
showPic(this);
return false;
}
}
2.共享onload事件
我们必须执行prepareGallery函数才能对onclick事件进行绑定,如果立即执行这个函数,它将无法工作,因为在HTML文档加载之前执行脚本DOM是不完整的。
应该让这个函数在网页加载完毕之后执行,网页加载完成之后会触发一个onload事件,我们需要把prepareGallery函数绑定在这个事件上。
window.onload = prepareGallery;
假设需要绑定两个函数,如果把它们逐一绑定到onload事件上,只有最后一个才被实际执行。
有一种解决方案:先创建一个匿名函数来容纳这两个函数,然后把这个匿名函数绑定到onload事件上。
window.onload = function(){
firstFunction();
secondFunction();
}
还有一个最佳解决方案:编写额外的代码,即addLoadEvent函数,它有一个参数,打算在页面加载完毕时执行的函数名。
【该函数要完成的操作】
- 把现有的winow.onloa事件处理函数的值存入变量oldload
- 如果在这个处理函数上没有绑定函数,就把新函数添加给它
- 如果在这个处理函数上已经绑定函数,就把新函数追加到现有指令的末尾。
function addLoadEvent(func){
var oldonload = window.onload;
if (typeof window.onload != 'function'){
window.onload = func;
}else {
window.onload = function(){
oldonload();
func();
}
}
}
如果需要把prepareGallery与onlad事件绑定,只需要一行代码。
addLoadEvent(prepareGallery);
三、不要做太多的假设
showPic函数负责完成两件事:一是找出id属性值为placeholder的图片并修改其src属性;二是找出id是description的元素并修改其第一个子元素的nodeValue属性。
第一件事实这个函数必须完成的任务,第二件事只是补充。因此,决定把检查工作分成两个步骤:主要placeholder图片存在,即使description元素不存在,切换新图片的操作也照常进行。
function showPic(whichpic){
if (!document.getElementById("placeholder")) return false;
var source = whichpic.getAttribute("href");
var placeholder = document.getElementById("placeholder");
placeholder.setAttribute("src",source);
if (document.getElementById("description")){
var text = whichpic.getAttribute("titile");
var description = document.getElementById("description");
description.firstChild.nodeValue = test;
}
return false;
}
改进后的showPic函数不在假设HTML文档里肯定有placeholder图片和description元素,即使文档里没有placeholder图片,也不会发生错误。
可是还有一个问题:如果把placeholder图片删除,那么将会出现,无论点击imagegalley清单里的哪个链接,都不会产生任何反应。这意味着脚本不能实现平稳退化。
问题在于prepareGallery函数做出了一个假设:showPic函数肯定正常返回,因此该函数取消了onclick事件的默认行为。
实际上,是否要返回false以取消onclick事件的默认行为,应该有showPic函数决定:如果图片切换成功,返回true,如果不成功,返回false。
因此,应该在返回前验证showPic函数的返回值,以便决定是否阻止默认行为
links[i].onclick = function(){
return !showPic(this)
}
现在,如果showPic返回true,我们就返回false,浏览器就不会打开默认链接;如果showPic返回false,我们就认为图片没有更新,于是返回true以让默认行为发生。
四、优化
每个链接都有一个title属性,为检查其是否存在:
if (whichpic.getAttribute("title")){
var text = whichpic.getAttribute("title");
}else{
var text = "";
}
检查placeholder元素是否存在,切为一张图片,可以使用nodeName属性来检查:
if (placeholder.nodeName != "IMG") return false;
注意:nodeName属性总是返回一个大写字母的值,即使元素在HTML文档中是小写的。
检查description元素的第一个子元素是否为一个文本节点:
if (description.firstChild.nodeType == 3){
description.firstChild.nodeValue = text;
}
在增加了几项测试之后,showPic函数的代码变得更多了。在实际工作中, 需要自己决定是否需要这些检查。理想情况下,脚本不应该对HTML文档的结构和内容有太多假设
五、键盘访问
prepareGallery函数的核心代码是:
links[i].onclick = function(){
return !showPic(this);
当这个链接被点击时,showPic函数开始执行。
但用户还可以使用键盘来操作,有个onkeypress的事件处理函数,它是专门用来处理键盘事件的,按下键盘的任意键都会触发该事件。
为了让onkeypress事件与onclick事件触发同样的行为,可以把onclick事件的所有功能付给onkeypress。
links[i].onkeypress = links[i].onclick;
小心使用onkeypress:
使用这个事件处理函数很容易出问题,用户每按下一个按键都会触发它。绑定在onkeypress事件上的处理函数上返回的是false,那些只是用键盘访问的用户将永远无法离开当前链接
然而,使用Tab键移动到某个链接然后按下回车键的动作也会触发onclick事件,因为这里不只采用onclick事件处理函数。
六、DOM Core 和HTML-DOM
至此,在编写javascript代码时用到了以下几个DOM方法:
getElementById、getElementsByTagName、getAttribute、setAttribute
这些方法都是DOM Core的组成部分,支持DOM的任何一种程序设计语言都可以使用它们。
在使用javascript语言和DOM为HTML文件编写脚本时,还有许多属性可供选择。
HTML-DOM提供了一个forms对象。
document.getElementsTagName("forms")
可以简化为:document.forms
HTML-DOM还提供了许多描述HTML元素的属性,如src
element.getAttribute("src")
可以简化为:element.src
这些方法和属性可以相互替换,同样的操作可以使用DOM Core和HTML-DOM来实现。通常HTML-DOM会更加简短,但它们只能用来处理Web文档。
完整代码:
html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Image Gallery</title>
<link rel="stylesheet" href="styles/layout.css" media="screen" />
</head>
<body>
<h1>Snapshots</h1>
<ul id="imagegallery">
<li>
<a href="images/fireworks.jpg" title="A fireworks display">Fireworks
<img src="images/small_fireworks.jpg" alt= "Fireworks" />
</a>
</li>
<li>
<a href="images/coffee.jpg" title="A cup of coffee">Coffee
<img src="images/small_coffee.jpg" alt = "coffee" />
</a>
</li>
<li>
<a href="images/rose.jpg" title="A red rose">Rose
<img src="images/small_rose.jpg" alt = "rose" />
</a>
</li>
<li>
<a href="images/bigben.jpg" title="The famous clock">Big Ben
<img src="images/small_bigben.jpg" alt = "bigben" />
</a>
</li>
</ul>
<img id="placeholder" src="images/peng.jpg" alt="my peng" />
<p id="description" >Choose an image.</p>
<script type="text/javascript" src="scripts/myjs.js"></script>
</body>
</html>
外部javascript:
function addLoadEvent(func){
var oldonload = window.onload;
if (typeof window.onload != 'function'){
window.onload = func;
}else {
window.onload = function(){
oldonload();
func();
}
}
}
function prepareGallery(){
if(!document.getElementsByTagName) return false;
if(!document.getElementById) return false;
if(!document.getElementById("imagegallery")) return false;
var gallery = document.getElementById("imagegallery");
var links = gallery.getElementsByTagName("a");
for(var i=0;i<links.length;i++){
links[i].onclick = function(){
return !showPic(this);
}
}
}
function showPic(whichpic){
if (!document.getElementById("placeholder")) return false;
var source = whichpic.getAttribute("href");
var placeholder = document.getElementById("placeholder");
if (placeholder.nodeName != "IMG") return false;
placeholder.setAttribute("src",source);
if (document.getElementById("description")){
var text = whichpic.getAttribute("title") ? whichpic.getAttribute("title") : "";
var description = document.getElementById("description");
if (description.firstChild.nodeType == 3){
description.firstChild.nodeValue = text;
}
}
return true;
}
addLoadEvent(prepareGallery);