Web應用程式(Web Applications)
從計算機紀元的黎明剛剛來臨開始,不同平台間軟體的互用性就一直是關注的焦點。為了盡可能實作使用者的最大要求,軟體釋出者往往将流行軟體從一個機器移植到另外一個機器上,這通常要花費數月的辛苦勞動,有時甚至是整個軟體在新的硬體或者作業系統上的完全重寫。随着計算機功能的不斷強大,像C/C++這樣的語言成為在多種平台上可用的标準,這是更加容易實作,一個程式隻編寫一次,就可以在所期望的衆多系統上編譯。隻要系統中包含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技術。幾乎每一個平台上的每一個浏覽器都支援該腳本語言的一個通用子集,這使得編寫平台無關程式變得更加容易。
JavaScript工具箱(JavaScipt Toolkits)
如果需要為網頁增加一些特效,這些工具是非常不錯的,也有很多優點。它們已經在很多浏覽器上進行過測試,可以在運作在所有标準的浏覽器上。其中一些工具還包含了良好的文檔,它們使得實作一個奇幻的網頁變得輕而易舉。然而,它們也可能是非常臃腫的,會為你的網頁再增加數百KB,有時你又不得不等待對新浏覽器支援功能的添加(例如,其中一些還不能在IE7上運作)。另外,很多公司不會或者不願意使用開源軟體,主要是因為軟體責任的缺失(也就是,你必須自己承擔使用它的風險),并且在調試方面也有很多困難。
應該指出的是,讨論的函數都已經在Windows XP下IE7和FIrefox2.0上測試通過,但是它們應該可以在目前流行的所有浏覽器上運作。任何浏覽器相關的代碼都在讨論過程中特别指出。另外,請緊記,使用JavaScipt和DOM有很多方法可以實作同樣的目的。下面的例子隻是給出了一種這樣的方法,在所有方式中并不是必要的最快的或者最好的。所有的例子都依據簡單、實作功能并且依據閱讀的原則編寫。
JavaScript的魔力(The Magic of JavaScript)
我們将要看到的第一個例子是如何顯示一個浮動的文本(或圖檔),并且在拖動浏覽器視窗的滾動條時仍然會停留在同一個地方。這可以用來在web程式向使用者顯示一個消息,如“Please wait”,或者在網頁增加一個水印。隻需要幾行JavaScript代碼就可以實作這個效果:
使用下面的兩個JavaScript函數可以完成上面的功能:
var ie = document.all;
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) ...{
// Position Message
el.style.pixelLeft = document.body.clientWidth - dx;
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);
消息本身可以在頁面上允許使用span标簽的任何位置初始化:
<span id="testmsg" style="position: absolute;
visibility: hidden; background: red;">This is a test…</span>
按鈕的實作代碼分别是:
onclick="Message_Display('testmsg', 1, 700, 50); return false;"
顯示消息,以及
onclick="Message_Display('testmsg', 0, 700, 50); return false;"
隐藏消息。
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)
接下來,也是本文讨論的最後一個小工具,它比上一個例子更加複雜:彈出式菜單。
首先,看一下運作的結果:
Menu Item #1
Menu Item #2
Menu Item #3
For menu click here: X
這個例子更加複雜是因為它包含了一個JavaScript函數和一些更加複雜的事件處理。下面是用到的JavaScript代碼和HTML:
function getElementAbsPosX(el)
...{
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;
dy = el.offsetTop + el.offsetHeight / 2;
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';
// 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';
實作菜單的HTML代碼,和前面例子中的span一樣,可以放在頁面中任何允許div标簽的位置:
<div id="testmenu" style="position: absolute; visibility: hidden; color: #aaaaaa; font-style: italic; border: solid thin #888;
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>
<td><span style="cursor:pointer; color:blue;" onclick="PopupMenu('test', 0);">Menu Item #2</span></td>
<td><span style="cursor:pointer; color:blue;" onclick="PopupMenu('test', 0);">Menu Item #3</span></td>
</table>
</div>
增加菜單的初始化代碼:
<span id="testmenuroot" style="cursor:pointer; color:blue;"
onclick="PopupMenu('test', 1); tout=setTimeout('PopupMenu
('test', 0);', 1500);" >X</span>
首先,我們來看一下菜單。我們可以在div标簽中放置任何我們喜歡的東西,所有的秘密都在div标簽的屬性中。特别需要注意的,和前面的例子一樣,我們使用絕對定位,是以不管其他文本和圖檔出現的位置,我們都可以控制菜單的确切位置。我們還要確定菜單開始時visiblity:hidden,并且配置設定了ID testmenu,這樣我們就可以在後面很容易獲得一個句柄。
在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字母,當然,你可以在這裡放你喜歡的任何東西:按鈕、圖檔等等。需要注意的另外一個事情是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的示例。