Web应用程序(Web Applications)
从计算机纪元的黎明刚刚来临開始,不同平台间软件的互用性就一直是关注的焦点。为了尽可能实现用户的最大要求,软件公布者往往将流行软件从一个机器移植到另外一个机器上,这通常要花费数月的辛苦劳动,有时甚至是整个软件在新的硬件或者操作系统上的全然重写。随着计算机功能的不断强大,像C/C++这种语言成为在多种平台上可用的标准,这是更加easy实现,一个程序仅仅编写一次,就能够在所期望的众多系统上编译。仅仅要系统中包括C编译器,软件就能够构建,而且可能会依照预期执行。
然而,当使用图形用户界面(GUI)的软件成为标准化时,就有了另外一个问题。Macs上的GUI和Windows或者Unix上的各种效果大相径庭。当然,有一些所谓的“widget toolkit”,像Tcl/Tk或者wxWidgets,能够绑定到非常多流行的语言,如C++、Python和Perl。开发者使用这些小工具能够创建平台无关的GUI,它们能够在Windows、Linux、Mac OS X和其它操作系统表现出(差点儿)相同的动作。只是,在今天,差点儿全部的机器上都有一个功能尤为强大的GUI解释器:web浏览器(the web browser)。
web浏览器 尽管最初仅仅是为了以一种相对令人舒服的方式显示标记文本,可是今天已经发展到另外一个顶点。它们能够作为令人惊奇的复杂的软件界面,能够媲美那些使用C/C++和其它高级语言编写的软件。这些大部分要归功于JavaScript支持和Web2.0技术。差点儿每个平台上的每个浏览器都支持该脚本语言的一个通用子集,这使得编写平台无关程序变得更加easy。
JavaScript工具箱(JavaScipt Toolkits)
近期,出现了非常多来自于像Yahoo、Google和Dojo等地方的免费的JavaScript工具箱。非常像上面提到的widget toolkits,它们提供了各种各样使用JavaScript编写的小工具,如菜单、日历或者树,而且开发者能够将它们轻而易举地继承到自己的站点或者web程序中。这里有一个不错的小总结。
假设须要为网页添加?一些特效,这些工具是非常不错的,也有非常多长处。它们已经在非常多浏览器上进行过測试,能够在执行在全部标准的浏览器上。当中一些工具还包括了良好的文档,它们使得实现一个奇幻的网页变得轻而易举。然而,它们也可能是非常臃肿的,会为你的网页再添加?数百KB,有时你又不得不等待对新浏览器支持功能的加入?(比如,当中一些还不能在IE7上执行)。另外,非常多公司不会或者不愿意使用开源软件,主要是由于软件责任的缺失(也就是,你必须自己承担使用它的风险),而且在调试方面也有非常多困难。
本系列文章计划作为高级JavaScript技术指南,演示怎样创建多浏览器兼容widget(包括完整的代码),相似前面提到的那些小工具,另外也会详解它们的工作原理。诸如实现如菜单、tab或树等之类的样例在网上随处可见,可是非常多要么是代码编写得极其蹩脚,要么不具备跨浏览器兼容性。关于JavaScript、CSS、DOM和HTML有一个比較好的參考站点是www.w3school.com,假设在本文讨论的演示样例中遇到不熟悉的JavaScript函数或者HTML/CSS标签建议參考此站点。该站点上另一篇关于HTML DOM的指南。将要讨论的内容有弹出式菜单、浮动消息、拖拽对象和XML HTTP(在不重载页面的情况下动态更新页面内容)。
应该指出的是,讨论的函数都已经在Windows XP下IE7和FIrefox2.0上測试通过,可是它们应该能够在当前流行的全部浏览器上执行。不论什么浏览器相关的代码都在讨论过程中特别指出。另外,请紧记,使用JavaScipt和DOM有非常多方法能够实现相同的目的。以下的样例仅仅是给出了一种这个方案,在全部方式中并非必要的最快的或者最好的。全部的样例都根据简单、实现功能而且根据阅读的原则编写。
JavaScript的魔力(The Magic of JavaScript)
我们将要看到的第一个样例是怎样显示一个浮动的文本(或图片),而且在拖动浏览器窗体的滚动栏时仍然会停留在同一个地方。这能够用来在web程序向用户显示一个消息,如“Please wait”,或者在网页添加?一个水印。仅仅须要几行JavaScript代码就能够实现这个效果:
使用以下的两个JavaScript函数能够完毕上面的功能:
var moz = document.getElementById && !document.all;
var intr;
function Message_UpdatePos(msg, dy) ...{
var el = document.getElementById(msg);
if (ie) ...{
el.style.pixelTop = document.body.scrollTop + dy;
}
else if (moz) ...{
el.style.top = window.pageYOffset + dy + ‘px‘;
}
}
function Message_Display(msg, vis, dx, dy) ...{
var el = document.getElementById(msg);
// Position Message
if (ie) ...{
el.style.pixelTop = document.body.scrollTop + dy;
el.style.pixelLeft = document.body.clientWidth - dx;
}
else if (moz) ...{
el.style.top = window.pageYOffset + dy + ‘px‘;
el.style.left = window.innerWidth - dx + ‘px‘;
}
if (vis) ...{ // and display it
el.style.visibility = "visible";
intr = setInterval("Message_UpdatePos(‘" + msg + "‘, " + dy + ")", 1);
}
else ...{ // or hide it
el.style.visibility = "hidden";
if (intr)
clearInterval(intr);
}
}
visibility: hidden; background: red;">This is a test…</span>
Message_Display的參数是:包括消息文本的span标签的id、一个表示显示或者隐藏消息的布尔值,以及消息的显示位置——相对于浏览器窗体右上角的x和y偏移量(在本例中是距离右端700px,顶端50px)。这里的关键所在span标签的style。通过使用绝对定位,我们是消息浮动在页面上,而不是附在页面随着页面滚动而滚动。我们还应该简要地说明一下为什么使用span标签。span和div标签本质都是“什么事情都不干(do nothing)”的标签,在文档中分别被用来描绘水平和垂直的块。正常情况下,它们在页面上都是不可见的,所以能够用来通过特定的样式来包围一块内容,而不用改变整个页面。因此在创建JavaScript小工具,它们是十分方便的。
这个函数的工作流程例如以下。首先,通过getElementById(获得标签指针的最简单方法)获得消息span标签的句柄。接下来,区分Internet Explorer(ie)或Firefox/Mozilla(moz),可是都要实现相同的目的——将消息定位到期望的位置(dx,dy)。使用visibility样式控制消息的显示和隐藏。最后一行须要更进一步的解释JavaScript的定时器(Timer)。
定时器是JavaScript中在某段时间后运行一个函数的有效方式,既能够使用setTimeout仅仅运行一次,也能够使用setInterval循环运行。这些函数是很实用的,由于在JavaScript中没有和sleep等价的命令,全部它们就提供了最好的替代方式。直到指定的时间过去,否则它们是消耗CPU时间的。开发者必须注意不能滥用这些强大的工具,可是仅仅要适当使用就会产生令人印象深刻的效果。在本例中,我们依据參数vis设置定时器运行Message_UpdatePos(msg, dy)。若vis非零,则定时器设置为每1毫秒运行一次。注意,我们必须定时器指针保存在全局变量intr中,由于在多次调用Message_Display时它要一直存在。
最后来看Message_UpdatePos,注意本质上它和Message_Display的开头部分完毕相同的工作,获得消息句柄和更新到页面顶端的y偏移量。因此,当页面滚动时,消息的位置就会滚动事件完毕1ms又一次调整,看起来它好像一直漂浮在右上角的相同位置。注意,假设浏览器在水平方向调整大小(变宽或者变窄),消息并不会移动。作为,看你能否够改动代码使其在该种情况也改变位置。
弹出式菜单(Pop_up Menus)
接下来,也是本文讨论的最后一个小工具,它比上一个样例更加复杂:弹出式菜单。
首先,看一下运行的结果:
For menu click here:
这个样例更加复杂是由于它包括了一个JavaScript函数和一些更加复杂的事件处理。以下是用到的JavaScript代码和HTML:...{
var dx = 0;
if (el.offsetParent) ...{
dx = el.offsetLeft + 8;
while (el = el.offsetParent) ...{
dx += el.offsetLeft;
}
}
return dx;
}
function getElementAbsPosY(el)
...{
var dy = 0;
if (el.offsetParent) ...{
dy = el.offsetTop + el.offsetHeight / 2;
while (el = el.offsetParent) ...{
dy += el.offsetTop;
}
}
return dy;
}
function GetAbsWindowBottom()
...{
// Compute the bottom of the popup window and the bottom of
// the browser window, in absolute co-ordinates - different
// on all browsers but the below should be accurate usually!
var abswindowbottom = 0;
if (typeof(window.innerHeight) == ‘number‘)
abswindowbottom = window.innerHeight;
else if (document.documentElement && document.documentElement.clientHeight)
abswindowbottom = document.documentElement.clientHeight;
else if (document.body && document.body.clientHeight)
abswindowbottom = document.body.clientHeight;
if (typeof(window.pageYOffset) == ‘number‘)
abswindowbottom = abswindowbottom + window.pageYOffset;
else if (document.body && document.body.scrollTop)
abswindowbottom = abswindowbottom + document.body.scrollTop;
else if (document.documentElement && document.documentElement.scrollTop)
abswindowbottom = abswindowbottom + document.documentElement.scrollTop;
return abswindowbottom;
}
function PopupMenu(name, vis)
...{
var el = name + ‘menu‘;
var tag = name + ‘menuroot‘;
if (!document.getElementById(el)) // menu object not found
return;
if (vis == 0) ...{ // hide the menu
document.getElementById(el).style.visibility = ‘hidden‘;
return;
}
// Get menuroot position
var pos = document.getElementById(tag);
var dx = getElementAbsPosX(pos);
var dy = getElementAbsPosY(pos);
// Compare bottom of menu to bottom of window
var abspopupbottom = dy + document.getElementById(el).clientHeight + 10;
var abswindowbottom = GetAbsWindowBottom();
// If menu goes below bottom of window, move it up!
if (abspopupbottom > abswindowbottom)
dy = dy - (abspopupbottom - abswindowbottom);
// Set final menu position and make it appear
document.getElementById(el).style.left = dx + ‘px‘;
document.getElementById(el).style.top = dy + ‘px‘;
if (vis > 0)
document.getElementById(el).style.visibility = ‘visible‘;
}
background-color: #afafaf; padding: 4px;" onmouseover="clearTimeout(tout);" onmouseout="tout=setTimeout(‘PopupMenu(‘test‘, 0);‘, 1500);">
<table>
<tr>
<td><span style="cursor:pointer; color:blue;" onclick="PopupMenu(‘test‘, 0);">Menu Item #1</span></td>
</tr>
<tr>
<td><span style="cursor:pointer; color:blue;" onclick="PopupMenu(‘test‘, 0);">Menu Item #2</span></td>
</tr>
<tr>
<td><span style="cursor:pointer; color:blue;" onclick="PopupMenu(‘test‘, 0);">Menu Item #3</span></td>
</tr>
</table>
</div>
onclick="PopupMenu(‘test‘, 1); tout=setTimeout(‘PopupMenu
(‘test‘, 0);‘, 1500);" >X</span>
首先,我们来看一下菜单。我们能够在div标签中放置不论什么我们喜欢的东西,全部的秘密都在div标签的属性中。特别须要注意的,和前面的样例一样,我们使用绝对定位,所以无论其它文本和图片出现的位置,我们都能够控制菜单的确切位置。我们还要确保菜单開始时visiblity:hidden,而且分配了ID testmenu,这样我们就能够在后面非常easy获得一个句柄。
在div标签中,我们还会遇到setTimeout和clearTimeout函数的第一次使用。它们和上例中的setInterval函数的行为相似,仅仅只是它们在某段时间后仅仅运行一个命令一次。在本例中,它们用来在鼠标移出菜单区域1500毫秒后关闭菜单,或者鼠标又一次移回到菜单区域时(假设仍然处于打开状态)取消退出效果。tout是在JavaScript文件里定义的一个全局变量,用来保存定时器对象的指针。当触发onmouseout事件时(鼠标移动由div标签指定的菜单区域之外)PopMenu(‘test‘, 0)被设定为在1500毫秒后调用(注意我们是怎样转移引號的,由于它们已经在引號之内)。假如在1500毫秒耗尽之前动作没有被取消,这个函数就会被调用,菜单就会消失(就像我们在以下将要看到的那样)。然后,假设onmouseover时间被触发(鼠标移动到菜单上),clearTimeout(tout)就会运行,它会阻止取消定时器,阻止菜单消失,直到用户触发下一次鼠标事件。
在菜单自身内部,我们会包括一些运行其它任务或跳转其它页面的链接。当然,本例中仅仅是一些虚假的链接。然而,注意每个菜单项的onclick事件。在div标签上面,一点击菜单项菜单就会消失(试一下!)。注意,这里没有使用timeout,由于我们想在这样的情况下菜单马上消失。
接下来是由id为testmenuroot的span标签包括的一段HTML代码,表示了菜单实际出现的点击区域。在本例总,我们仅仅是在span标签中间放置了一个X字母,当然,你能够在这里放你喜欢的不论什么东西:button、图片等等。须要注意的另外一个事情是onclick时间,它使菜单显示,而且设置定时器以在1500ms后关闭菜单。然而,我们已经仅仅要用户移动鼠标到菜单区域,定时器就会被取消,而且菜单会一直显示,直到下次鼠标移走。
最后,我们来看JavaScript函数本身,PopupMenu。它依赖的函数有:GetAbsWindowBottom,获得浏览器底部的绝对位置;getElementAbsPosX和getElementAbsPoxY,获得一个元素在绝对坐标系中的x和y位置。最后两个的计算方式是通过迭代加上父元素的偏移量,直到DocumentRoot。这些函数看起来有些过于复杂了,可是它们极有可能是在差点儿每个流行的浏览器上获得这些信息的唯一方式。不幸的是,每个浏览器都使用一个非常不同的方式保存这些信息。
PopupMenu函数有两个输入參数:name和一个表示菜单应该显示或隐藏的标志。关于name,假设菜单被一个标签包围形成,其id由name參数加上“menu”组成(那么在我们的样例中就是“testmenu”,由于參数为“test”)。相似地,假设菜单的左上角定位在id为name加“menuroot”的标签(本例中为“testmenuroot”)上。假设vis參数是0,我们仅仅须要把菜单标签的visibility的样式设置为hidden。否则,我们须要使菜单出如今右側的位置上。首先,我们使用GetElementAbsPos函数获得“menuroot”标签的绝对位置。然后,我们能够把菜单的位置设置为这些值,这就能够让菜单的左上角出如今我们点击以使其弹出的位置。然而,我们还不希望菜单滚动出页面的底端,由于这是非常令人讨厌的。注意多数包括弹出式菜单的widget toolkits都仅仅实现上面的功能,所以我们将尝试实现一个更好的。我们能够计算出绝对坐标系中窗体以及弹出菜单的底部位置,接下来的两行代码完毕了这个任务。注意,菜单的高度由clientHeight属性给出,我们再加上10以使和页面底部留下一些空间。然后我们能够检查弹出菜单是否已经向下超出了浏览器窗体的底部,假设是这样,就会对应地调整它的坐标,如此它的底部就会刚刚接触窗体的底部。最后,我们接着向下看,就是定位弹出式菜单,并使其可见。
总结(Summary)
但愿,这次讨论和这两个样例能够让你领略到使用仅仅几行JavaScript代码就能够完毕的事情,而且揭示了在JavaScript工具内部并没有非常多非常多的魔法。虽然有时候有些棘手,可是通常仅仅须要额外的一点工作来确保函数能够在全部的主流浏览器上运行,而且这些小小的额外付出差点儿永远是值得的。在后面的文章中,将会讨论更加具体的JavaScript widget的演示样例。
Howard Feldman是在位于Montreal, Quebec的Chemical Computing Group一名研究科学家。