19. Dynamic HTML Applications

在一剛開始,網站的技術只有HTML (Hypertext Markup Language)。所有的網頁都是靜態的,但這已能滿足當時網站的需求。接著,出現一種在伺服器端執行的外部應用程式Common Gateway Interface (CGI) ,第一次能讓非靜態內容的網頁執行運作。動態網頁技術接下來的發展便是在客戶端執行的腳本語言(scripting languages)常式或是在瀏覽器內執行的巨集程式,如微軟的Visual Basic, Scripting Edition (VBScript) ,或是遵守ECMAScript規格的腳本語言。微軟已發展出新一代的專利技術,用來製作瀏覽器能觀看的動態網頁內容,如ActiveX控制項及ActiveX文件,而其他的廠商則多投注焦點於使用Java撰寫的applet程式類型。最令人感興趣,功能最強大,又最廣泛地被接受的伺服器端技術是用server-side scripting和Active Server Pages (ASP)。而在客戶端創作動態網頁最具威力的方法則是使用Dynamic HTML (DHTML) 。

然而,這些技術全都有一些缺陷在。CGI應用程式並不是很有效率,當它們在處理上百個客戶端時並沒有足夠的擴充性,而且,對於大型的網際網路或企業網路的應用程式而言,顯得功能不足。Client-side scripting對於Visual Basic的程式設計師而言絕對是較適合的,特別是當程式設計師使用VBScript。但遺憾地,VBScript目前並不被網景公司的Netscape Navigator瀏覽器支援,所以,您應當只在企業網路型態的裝置(intranet installation)上使用它。Netscape Navigator也不支援ActiveX控制項及ActiveX文件。很多網路開發者認為使用Active Server Pages (ASP) 傳送純HTML格式的動態網頁到任何瀏覽器上是最好的方法,但建立和維護一個以ASP為基礎的大型應用程式絕不會是個輕鬆的工作也卻是事實。另外,當使用人數增加時,在伺服器上撰寫腳本語言的解決方案的效率會降低。

Visual Basic 6在撰寫Internet程式方面提出兩種新穎的方式,它們可能就是您的答案,因為它們結合了Internet的彈性及Visual Basic語言的簡易性。DHTML應用程式是在客戶端機器Microsoft Internet Explorer中所執行的同處理序元件 (DLL) ,它會捕捉由DHTML網頁上元件所產生的事件,例如當使用者按下一個按鈕或是點選一個超連結。IIS應用程式(即WebClasses) 是在伺服器上執行的DLL,它是在Microsoft Internet Information server (IIS)中執行,並且處理由客戶端瀏覽器所發出的請求。兩個方法都可以看做是全新的技術,因為它們很簡單的就擴充了原本的功能。而且它們讓您撰寫Internet應用程式時可以使用Visual Basic語言完整的功能(而不只是簡單的腳本語言),在整合發展環境(IDE)中偵錯,並且產生最佳化且具可調適性的原始碼(相對於較沒效率的腳本語言), 這些功能對程式設計人員來說是很動人的。

在這一章,筆者將說明如何創作DHTML的應用程式。(筆者會在第20章節闡述 WebClasses 。) 但在深入這最有趣的部分前,我們先重溫一下幾個基本的觀念。

HTML概述
 

在您可以開始建立一個DHTML應用程式前,您至少該知道一些HTML的基本原理,如一個HTML網頁是如何被創作出來的,及那些最重要且最常用的HTML標記為何。如果您已經很熟悉HTML的語法,您可以很放心地快速跳過這一節。

雖然HTML語言擁有它自己的邏輯,但它並不像高階的程式語言,如Visual Basic,這般地有組織。因為這樣,很多程式設計師發現HTML的語法規則有點奇怪。如同往常,這不會影響我們實際動手作一下練習,所以筆者準備了DHTML Cheap Editor來讓您們撰寫一個HTML片段,並且可以馬上來看瀏覽器是如何反應。筆者使用WebBrowser控制項來撰寫這個軟體 ,而它的功能不少於ActiveX控制項內嵌的Internet Explorer。

儘管這個簡單的editor不能與微軟的Front Page,甚至最簡單的HTML editor免費軟體相比較,但它確實提供一些有用的特點。第一,它能讓您按一個按鍵從editor切換到上一個視窗畫面 (F4切換到editor,F5切換到預覽視窗)。第二,它提供快速鍵組合讓您能增加常用的HTML標記甚或複雜的結構如表格及控制項。所有的快速鍵都列在Insert功能表中,如圖19-1所示。這個editor的程式碼都在隨書光碟中,歡迎您添加額外的HTML標記或其他功能去驗證它。


 

圖19-1 兩個DHTML Cheap Editor的例子。一個在編輯模式,一個在預覽模式。

標題及段落
 

一個HTML網頁是一個包含純文字及標記的檔案,它會告訴瀏覽器這一網頁該如何在用戶端的螢幕上被呈現出來。底下是一個基本的HTML網頁架構:

<HTML>
<HEAD>
<TITLE>The title of this page</TITLE>
</HEAD>
<BODY>
Welcome to HTML
</BODY>
</HTML>

這段程式會在空白網頁的上方顯示"Welcome to HTML"字串。請注意網頁所有的要素都被一對標記包起來,且每一個標記都被用括號括起來。例如,網頁的標題-Internet Explorer在它的主畫面顯示的那串標題-是被<TITLE>及</TITLE>標記所包起來。<BODY>及</BODY>標記則將網頁內顯示的文字包起來。

典型地,一個網頁的內容有一個或多個標題。HTML支援六種等級的標題,等級一標題對應最重要的主標題,等級六標題對應最不重要的標題。標題在給予網頁每小節標題時很有用:

<BODY>
<H1>This is a level 1 heading</H1>
Some normal text here
<H2>This is a level 2 heading</H2>
Text under level 2 heading
</BODY>

在HTML語言中有一個很重要的特性,那就是在原始文字中的換行字元並不會影像網頁的呈現方式。除了標題及少數的標記會自動加入CR-LF,要換新的一列,插入<P>標記是一個方法,如:

This is a paragraph. <P>As is this. <P>

<P>標記可阻止一個新的列及在兩個段落之間插入一個空白列。如果您要打斷一個現行的列但不想要一個多餘的空白列,您可以使用<BR>標記:

This paragraph is subdivided<BR>into two distinct lines. <P>

在兩個段落之間加一個水平線,您也可以使用<HR>標記:

Two paragraphs separated<HR>by a horizontal line<p>

<PRE>及</PRE>這一對標記是一個例外:任何在這兩個標記中的文字會以monospaced文字來呈現(典型是Coruier字型) ,而且所有在其中的CR-LF字元組會正確的輸出。這組標記通常用來插入一段純文字(例如,原始程式碼列表) :

<PRE>First line
second line</PRE>

在預設中,所有的文字都是靠左排列,但您可以使用<CENTER>及 </CENTER>來將文字置中:

<CENTER>A centered paragraph<P>
Another centered paragraph</CENTER>

說明

如果您正在使用DHTML Cheap Editor,您可以將任何原始碼反白後按下Ctr+T,然後在Input Box打CENTER加入一對標記來讓它們置中 。您可以應用同樣的方法加入其他的標記,如<PRE>和</PRE>。其他在insert功能表中的指令─舉例來說,Bold、Italic字體、超連結指令─在選取的文字範圍頭尾會自動地加上一對標記。


在HTML製作以圓點或數字型式的項目清單是很容易的。要製作以原點型式的項目清單,您可在各別的項目頭尾加上一組<LI></LI>標記,並在整個的項目清單頭尾加上<UL></UL>標記:

<UL>
<LI>First bulleted paragraph</LI>
<LI>Second bulleted paragraph</LI>
<LI>Third bulleted paragraph</LI>
</UL>

製作以數字形式的項目清單大致也是以同樣的方式。唯一的差異點是以<OL></OL>這對標記加在整個項目清單的頭尾:

<OL>
<LI>First you must do this. </LI>
<LI>Then you must do that. </LI>
</OL>

特性(Attributes)
 

大部分的HTML標記能嵌進一些特別的attribute來改變圍在標記中文字的呈現方式。例如,依原設定值,標示文件資訊的標題文字置左,但您可利用ALIGN attribute更改排列的狀況:

<H1 ALIGN=center>This is a centered level 1 heading</H1>
<H2 ALIGN=right>This is right-aligned level2 heading</H2>

TEXT特性定義網頁內的文字顏色。如果您在BODY標記使用這個特性,會影響到整個的網頁:

<BODY BGCOLOR="cyan" TEXT="#FF0000">
Text on this page is red over a cyan background.
</BODY>

您可以利用#RRGGBB格式指定顏色特性,這種方式類似Visual Basic的RGB功能,或著您可以使用以下所列十六種Internet Explorer可接受的顏色:Black、Maroon、Green、Olive、Navy、Purple、Teal、Gray、Silver、Red、Lime、Yellow、Blue、Fuchsia、Aqua、White。BODY標記也支援其他的特性,如LINK(超連結時使用),ALINK(改變游標下的超連結)及VLINK(標記已參觀過的超連結)。

您可用<B>及</B>這一對標記設定被這對標記所包含的文字字體為粗體字。同樣地,您也可以利用<I>及</I>這對標記讓文字字體顯示為斜體字:

<B>This text is boldface.</B><P>
<I>This text is italic.</I><P>
<B>This sentence in boldface has an <I>italicized</I> word in it . </B>

您可以使用<U>及</U>這對標記將文章標底線。但這通常都不是好主意,因為底線的特性應該給超連結使用。您能使用<P>及</P>這對標記包住整個章節,然後讓特性適用到整個章節,如以下的範例:

<P ALIGN=center> Centered Paragraph </P>

調整設定文字的屬性,可用<FONT>標記,它有三種屬性:FACE、SIZE及COLOR。COLOR屬性的詳細用法與先前所述相同。FACE屬性是指字體的名字。這個屬性甚至可以接受a comma-delimited list of name。萬一使用者端的系統沒有安裝您優先選擇展示的字體,還可用第二選擇的字體:

<FONT FACE="Arial, Helvetica" SIZE=14 COLOR="red"> Red text </FONT>

這個陳述式試圖使用Arial字體,若使用者的系統未安裝Arial字體,則它回復到Helvetica字體。SIZE屬性是指字體點數大小。這個特性也能接受+、-符號,指出與字體預設值大小相對應的關係:

Text in regular size <P>
<FONT SIZE=+4> Text 4 points taller</FONT><P>
<FONT SIZE=-2> Text 2 points smaller</FONT>

圖片(Images)
 

要插入圖片到HTML網頁,您需要<IMG>標記,它的SRC屬性會指出要被顯示的圖片存放路徑﹔這個路徑可以是直接或是與目前網頁相對的路徑。例如:接下來的程式碼載入一個與HTML檔案存放在相同目錄下的GIF圖片:

<IMG SRC="mylogo.gif">

圖片通常為GIF或JPEG格式。GIF圖片可以是交錯的,讓瀏覽器在第一次下傳時先下載直線的圖素(pixels),後面則下傳剩餘的線條。

就如同您可以對文字做的,您可以用<IMG>標記配合<CENTER>及</CENTER>標記或是ALIGN屬性,平行置中地放置圖片。若知道要被下傳的圖片大小,您可用WIDTH和HEIGHT屬性,這樣瀏覽器就能在尚未下傳圖片前,也能正確地放置環繞在圖片周圍的文章。圖片的寬高是用圖素來表達:

This is a right-aligned image 200 pixels wide and 100 pixels high.
(一個靠右圖片,其大小為200個圖素寬與100個圖素高)
<IMG ALIGN=right WIDTH=200 HEIGHT=100 SRC="mylogo.gif">

如果必要,瀏覽器會放大或縮小原始的圖片來符合您所指定的大小。這種特性常在插入圖形化元件去分隔網頁區域時顯露出來。例如您可利用圖片產生漸層背景和只有少許圖素的HEIGHT屬性創作出平行分隔。

要控制在圖片周圍要留多少空白邊,您可用HSPACE及VSPACE這些特性,分別用於水平及垂直邊。邊界框的預設值為兩個圖素,但您可以隱藏起來(不要設定BORDER屬性)或是設定不同的寬度:

A right-aligned image with 10 pixels of horizontal white space and 20 pixels of vertical space.

(一個靠右圖片,其邊界框為水平方向為10個圖素與垂直方向為20個圖素)
<IMG VSPCE=20 ALIGN=right SRC="myolgo.gif" HSPCE=10>

最後,ALT屬性是用在提供圖片一些文字說明﹔這個說明會在瀏覽器正在下載圖片時顯示出來。而且當使用者關掉圖片的話,這個說明文字會完全取代圖片。

超連結(Hyperlink)
 

HTML支援三種的超連結:連結到同一網頁中不同位置、連結到同一伺服器內的不同網頁、及連結到網際網路上不同網域的網頁。在各種情況,您可用<A>及</A>這對標記定義文章的哪個部分要被顯示出來並標上底線。這些標記常伴隨有HERF屬性,它會指向超連結的目標:

Click <A HREF="PageTwo.htm">here</A> to proceed to the next page, or click
<A HREF="oc.htm">here</A> to go to the table of contents.

若超連結的目的地在同一網頁內,那您需要給它一個標籤,這個做法是使用<A>標記及NAME屬性:

<A NAME="Intro">Introduction</A>

(您也可以不需要在<A>及</A>之間插入字串。)這個標記如anchor,可以被置在目標的HTML原始程式碼的第一行前。指向同一網頁中的anchor,您可以在HERF屬性使用#符號。

Click <AHREF="#Intro">here</A> to go to the introduction.

(請注意:隨書光碟內的Cheap DHTML Editor範例不支援在同一網頁中的超連結)您可利用以下的語法超連結到另一個網頁中的anchor:

Click <A HREF="Chap1.htm#Intro">here</A> to go to the book's introduction.

提供完整的URL您就可以超連結到不同主機上的網頁:

Jump to the <A HREF="http://www.vb2themax.com/index.htm">
VB-2-The-Max</A> Web site.

您也可以用圖片作超連結。語法跟前面相同,您只要將圖片插入<A></A>這對標記內即可,如下面的程式碼所示。

<A HREF="http://www.vb2themax.com"><IMG SRC="mylogo.gif"></A>

您可以在影像地圖上建立可點選的鏈結。這個影像地圖可以區分不同的區塊,且每個都連結到不同的地方。關於這部分更進一部的技術超出本章的範圍,故筆者將不會在這裡詳述影像地圖。

表格(Tables)
 

HTML語言擁有豐富的標記及關鍵字可以製作產生表格。在HTML中表格是非常重要的,因為它提供了可以精確地擺放文章及圖片的方法。所有與表格相關的資料是以<TABLE>及</TABLE>這對標記包含起來。每一新的列是被<TR>標記圍住,而欄是用<TD>。您也能使用<TH>標記在標題列上。而</TR>、</TD>、</TH>這些結束用的標記則可選擇是否使用。接下來的例子是兩欄及三列的表格,而每一個前面都有標題列:

<TABLE BORDER=1>
<TR>
   <TH> HeadRow 1, Column 1</TH>
   <TH> HeadRow 1, Column 2</TH>
</TR><TR>
   <TD> Row 1, Column 1</TD>
   <TD> Row 1, Column 2</TD>
</TR><TR>
   <TD> Row 2, Column 1</TD>
   <TD> Row 2, Column 2</TD>
</TR></TABLE>

BORDER屬性定義邊框的寬度,如果沒有指定這個屬性,則表格不會顯示邊框。您可用BORDERCOLOR屬性改變邊框的顏色,及用BORDERCOLORLIGHT及BORDERCOLORDARK屬性製造3D立體效果。表格可以有背景顏色(利用BGCOLOR屬性),及可利用BACKGROUND屬性指定背景圖片。

在表格的儲存格中可以包含文章、圖片,或是兩者都有。您可用ALIGN屬性改變表格內容的水平放置排列方式(置左、置中、置右),及用VALIGN屬性控制垂直版面的排列(置上、置中、置下)。基本上,預設的表格的寬度足以顯示表格的內容,但您仍可利用WIDTH和HEIGHT屬性重新設定 ,它們的單位是圖素。就WIDTH屬性而言,您可依表格寬度的百分比作設定。大部分的屬性都可適用於<TR>、<TD>、<TH>等標記。接下來的例子顯示如何應用這些標記,結果顯示於圖19-2。

<TABLE BORDER=1>
<TR >
   <TH HEIGHT=100> A row 100 pixels tall</TH>
   <TH> <IMG SRC="mylogo.gif"></TH>
</TR>
<TR>
   <TD WIDTH=200 HEIGHT= 90 ALIGN=center VALIGN=bottom> 
   Text aligned to center, bottom</TD>
   <TD WIDTH=50%> This cell takes half of the table's width. </TD>
</TR>
<TR VALIGN=bottom>
   <TD> This row is bottom-aligned.</TD>
   <TD ALIGN=right> This one is right-aligned.</TD>
</TR></TABLE>

表格內可包含超連結或是可作超連結的圖片。表格預設的寬度足夠顯示內容,但您也可用<TABLE>標記的WIDTH屬性指定寬度或是與視窗大小的百分比例。

<TABLE BORDER=1 WIDTH=90%>


 

圖19-2 一個包含圖片以及不同格式、排列方式的表格

樣式(Styles)
 

樣式提供一種HTML標記在HTML網頁表現的方式。如果您不定義樣式,則標題會以預設的屬性顯示出來,例如,<H1>標題是黑色14點高的New Times Roman字體。您可用環繞的<FONT>標記調整預設的設定值,如以下的程式碼:

<FONT FACE="Arial" SIZE=20 COLOR="red"><H1>Level 1 Heading</H1></FONT>

第一個碰到的問題是若您不要等級一的標題以預設的屬性呈現,則您必須個別更新每一個<H1>標記。若您以後要更改顏色或字體大小,則要重新修正這些標記。

相反地,若您定義並套用樣式,只定義一次<H1>標記,就可以影響整份文件。您可以再進一步地將樣式定義存到一個分離的檔案-Cascading Style Sheet file(CSS)-這樣所有構成應用程式的HTML網頁都能應用到。這種做法提供一個有效的方法可以分開HTML內容及其要表現的外觀,且能容易地單獨調整其中的網頁設定。 將樣式獨立存在於分離的檔案是普遍的做法,為能清楚地說明,接下來的例子中筆者將在HTML網頁中使用樣式定義。

您可使用<STYLE>和</STYLE>標記重新定義樣式。例如,您可看看如何重新定義<H1>和<H2>標記:

<STYLE>
H1 {FONT-FAMILY=Arial; FONT-SIZE=20; COLOR="red"}
H2 {FONT-FAMILY=Arial; FONT-SIZE=16; FONT-STYLE=italic; COLOR="green"}
</STYLE>
<H1>This is a Red heading</H1>
<H2>This is a Green italic heading</H2>

您想要重新定義的標記的名字是依據大括號內以分號分隔條列的Attribute=value。大多數情況下,您也可以忽略雙引號內的字串值 - 例如,當您正在設定顏色的屬性時。您可以同時在單獨的<STYLE>,</STYLE>標記內重新定義一堆想要改變的標記。

樣式工作表甚至可讓您的定義取決於上下文的行為。拿上面<H2>標記的定義例子來說,全部等級二的標題都會顯示為綠色斜體字。像這樣的樣式事實上會使標題內已定義的<I>標記效果無效,因為字體已是斜體。您可以將<H2>及</H2>內的<I>標記指定為正常的紅色字體(非斜體字)來解決這種情況。您可增加一段樣式定義強制執行這個動作(增加的部分為粗體字):

<STYLE>
H1 {FONT-FAMILY=Arial; FONT-SIZE=20; COLOR="red"}
H2 {FONT-FAMILY=Arial; FONT-SIZE=16; FONT-STYLE=italic; COLOR="green"}
H2 I {FONT-STYLE=normal; COLOR="blue"}
</STYLE>
<H2>This is a heading with a <I>Normal Blue</I> portion in it </H2>

重新定義給顯示標記的外觀而給予一個新名稱,取而代之的,您可以用STYLE屬性來為一個某個標記建立樣式,就如下面的程式碼:

<H3 STYLE="FONT-STYLE=bold;COLOR=blue">A blue and bold Level 3 Heading</H3>

樣式工作表一個很大的特色是它可以讓您新增加新類型的樣式屬性。這樣,您可以根據意義在網頁內標記特定的項目,並在網頁別處或(更好是)在另外獨立的樣式工作表指定它表現的方式。這種方法與您用文書處理器如Microsoft Word定義一個新樣式相似。例如,假設有些標題是書的標題,而您希望在HTML網頁上這些書的標題是綠色的粗體字。您所要做的就是建立書標題的樣式類型,然後當需要時利用CLASS屬性去施行它:

<STYLE>
.booktitle {FONT-FAMILY=Arial; FONT-STYLE=bold; COLOR="green"}
</STYLE>
<H3 CLASS=booktitle>Programming Microsoft Visual Basic 6</H3>

當CLASS屬性搭配<DIV>和</DIV>標記應用在網頁內特別的樣式類型時 ,更顯出這個功能帶來的好處,可在網頁內應用特別的樣式類型。(更多有關<DIV>標記的資訊請參閱本章稍後提到的 〈標記(Tags)〉 小節)

<STYLE>
.listing {FONT-FAMILY=Courier New; FONT-SIZE=12}
</STYLE>

<DIV CLASS=listing>
' A Visual Basic listing <BR>
Dim x As Variant
</DIV>

最後,這裡提供一個儲存樣式定義到另一個獨立檔案的方法,它使用 @import指令:

<STYLE>
@import URL("http://www.vb2themax.com/stylesheet.css");
</STYLE>

表單(Forms)
 

HTML表單讓使用者可以在網頁上輸入資料。表單可以包含控制項,包括單列或是多列的text boxes、check boxes、rado buttons、push buttons、list boxes以及combo boxes。這些控制項不能與Visual Basic的相比,但就大部分的目的而言已是功能足夠了。所有表單的控制項都必須由<FORM>及</FORM>標記前後包住。<FORM>標記接受好幾種屬性,其中最重要的是NAME屬性,因為若您想從腳本語言的常式中存取控制項,則您要給表單一個名字。您可以將控制項放置在表單之外,用的時機為,例如,當您計畫透過script執行控制項卻不想讓內容傳送到網站上時。

表單的大部分控制項是利用<INPUT>標記插入。TYPE屬性決定控制項的類型,而NAME屬性決定控制項的名稱。例如,接下來的程式碼建立了一個含有核取方塊控制項的表單:

<FORM NAME=l<formnamel_>
<INPUT TYPE=Checkbox NAME=Shipped CHECKED>The product has been shipped.<BR>
</FORM>

NAME屬性大致與Visual Basic控制項的屬性相同。在控制項中CHECKED屬性作用是將被選取的地方做記號。跟在>字元符號的文字與控制項的標題相符,但除非HTML注意到,否則它只是網頁中跟在控制項後的文字罷了。

NAME屬性對選擇鈕控制項而言是重要多了,因為所有相同名稱的控制項屬於同名稱但選項互相排斥的同一群組。您可加入CHECKED屬性讓您可以在這個群組中選擇其中一個控制項:

Select the type of malfunction observed:<BR>
<INPUT TYPE=Radio NAME="Problem". CHECKED>Wrong Results<BR>
<INPUT TYPE=Radio NAME="Problem".>Fatal Error<BR>
<INPUT TYPE=Radio NAME="Problem">General Protection Fault<BR>

HTML支援三種型態的按鈕:Submit button, Reset button, 以及一般可程式化button。前兩種按鈕很相像,唯一不同的只有TYPE屬性值:

<INPUT TYPE=Submit VALUE="Submit">
<INPUT TYPE=Reset VALUE="Reset values">

在這兩種情況下,VALUE屬性決定按鈕的標題。Submit button作功用是將表單內所有的控制項內容傳回伺服器。Reset button的作用是清除表單上所有的內容並回復到初始設定值狀態。第三種型態的按鈕通常與script一起結合使用,如筆者在下一段的說明。

HTML表單可包含三種型態的文字方塊控制項:標準的單列控制項,密碼控制項,及多列控制項。單列控制項有個相當於文字(Text)的TYPE屬性,可包含指定控制項的初始內容的VALUE屬性及支援SIZE屬性(控制項所能顯示的字元數)和MAXLENGTH屬性(最大字串長度):

Enter book title: <BR>
<INPUT TYPE=Text NAME="BookTitle" SIZE=40 MAXLENGTH=60 
VALUE="Programming Microsoft Visual Basic 6">

密碼控制項與一般的文字方塊作用相同且支援相同的屬性。它相當於Visual Basic中PasswordChar屬性已被設定註上星號的文字方塊,

Enter your password:
<INPUT TYPE=Password NAME="UserPwd" SIZE=40 MAXLENGTH=60><BR>

TextArea控制項相當於Visual Basic的multiline TextBox控制項。然而,在一般規則中這個控制項是個例外,因為它使用<TEXTAREA>標記而不是<INPUT>標記;您可用ROWS和COLS屬性決定TEXTAREA的大小,及可加進在</TEXTAREA>標記前的初使設定內容:

<TEXTAREA NAME="Comment" ROWS=5 COLS=30 MAXLENGTH=1000>
Enter your comments here.
</TEXTAREA>

在<TEXTAREA>和</TEXTAREA>標記間的文字是包含換行的。如果直條列的寬度比控制項的寬度還寬,則使用者必須靠移動捲軸去看最右邊的文字。

HTML表單支援可單一選擇及多重選擇的list box控制項,在HTML裡稱之為Select控制項。Select控制項用<SELECT>及</SELECT>標記來定義東西,它接受SIZE屬性去指定控制項的高度(有多少列),若控制項接受多重選擇,則它也接受MULTIPLE屬性。每個清單的個別選項都需要<OPTION></OPTION>這對標記。如果這個項目是預設被選定的您可插入SELECT屬性,及插入VALUE屬性去指定表單中當按下確定執行時要被傳送回伺服器的字串。接下來的程式碼製作了有四列的多重選項的控制項,初使設定為第一個項目會被突顯出來:

<SELECT NAME="Products"_ SIZE=4 MULTIPLE>
    <OPTION SELECTED VALUE=1>Computers</OPTION>
    <OPTION VALUE=2>Monitors</OPTION>
    <OPTION VALUE=3>Hard disks</OPTION>
    <OPTION VALUE=4>CD-ROM drives</OPTION>
</SELECT>

如果您忽略不用MULTIPLE屬性並指定SIZE=1(或忽略不用),SELECT控制項會變為combo box控制項。

撰寫腳本Scripting
 

現在您已經知道如何準備HTML網頁及HTML表單,要瞭解scripting也是很容易的。

首先,您需要<SCRIPT>及</SCRIPT>標記來在HTML文件中保留一塊區域給您的scirpt程式,例如底下的程式碼:

<SCRIPT LANGUAGE="VBScript">
    ' Your VBScript code goes here.
</SCRIPT>

您也可以使用LANGUAGE屬性來指定其他的scirpt語言-舉例而言JavaScript -但為了本書的讀者,筆者所有的範例都是使用VBScript。

VBScript vs. Visual Basic for Applications
 

VBscript語言是Visual Basic for Applications (VBA)延伸的子集合並且它與它較具功能的親戚有幾個不同點:


說明

所有在本書中的範例都是以VBScript版本3.0完成的。在本書付梓時,VBScipt 5正在beta測試。這個新版本支援類別,property程序,物件變數及New運算子。它也允許複雜的搜尋及取代能力。VBScript 5將會與Internet Explorer 5一起發行。


在網頁載入時執行程式
 

大多數的情況中,在<SCRIPT>及</SCRIPT>標記中的常式是由網頁上某個地方來呼叫的。然而,程式可以被放置到任何常式外,這樣它會再網頁被下再完畢且還沒顯示在瀏覽器視窗中時被執行:

<SCRIPT LANGUAGE="VBScript">
    MsgBox "About to display a page"
</SCRIPT>

您可以在Window物件的onload事件中撰寫程式來達到同樣的結果,如下面的程式:

<SCRIPT LANGUAGE="VBScript">
' A variable declared outside any routine is global to the page.
Dim loadtime
Sub Window_onload()
    ' Remember when the page has been loaded.
    loadtime = Now()
End Sub
</SCRIPT>

存取表單的控制項
 

VBScipt程式可以存取表單中任何的控制項,使用formname.controlname語法,並且也可以使用句點來讀取或更改控制項屬性,如同一般的Visual Basic。下面的程式將告訴您如何在表單載入時,指定字串到TextBox控制項的VALUE屬性中:

<FORM NAME="DataForm">
<INPUT TYPE=Text NAME="UserName" VALUE="">
</FORM>
<SCRIPT LANGUAGE="VBScript">
DataForm.UserName.Value = "Francesco"
</SCRIPT>

假使您想要在網頁載入時存取表單上的控制項,<SCRIPT>必須要在<FORM>標記的後面; 否則,script會試圖引用一個還未存在的控制項。您可以由CheckBox控制項的Checked屬性來取得它的狀態,以及使用Select控制項的SelectedIndex屬性來取得所選擇項目的index。要檢查radio button的狀態,可以使用下面的語法:

If DataForm.RadioButton.Item(0).Checked Then ...

您常會需要使用VBScipt程式來對控制項產生的事件做出反應。例如,buttons,CheckBox及RadioButton控制項會在使用者按下它們時產生onclick事件。您可以像在標準Visual Basic一樣對這些事件做出反應。下面的範例使用了一個TextBox,一個Button以及兩個RadioButton控制項; 當push button按下時,這個程式會根據RadioButton目前的選擇來把TextBox的內容轉為大寫或小寫:

<FORM NAME="DataForm">
<INPUT TYPE=Text NAME="UserName" VALUE=""><BR>
<INPUT TYPE=Radio NAME="Case" CHECKED>Uppercase
<INPUT TYPE=Radio NAME="Case">Lowercase<BR>
<INPUT TYPE=BUTTON NAME="Convert" VALUE="Convert">
</FORM>
<SCRIPT LANGUAGE="VBScript">
Sub Convert_Onclick()
    If DataForm.Case.Item(0).Checked Then
        DataForm.UserName.Value = UCase(DataForm.UserName.Value)
    Else
        DataForm.UserName.Value = LCase(DataForm.UserName.Value)
    End If
End Sub
</SCRIPT>

要在使用者使用控制項時執行某段VBScript常式的另一個方式就是在定義控制項時加入onclick屬性,並且將它指到當控制項被按下時要執行的程式。例如,下面的程式定義了兩個RadioButtons,當它們被按下時,會更換TextBox控制項中的內容:

<FORM NAME="UserData">
<INPUT TYPE=Text NAME="UserName" VALUE=""><BR>
<INPUT TYPE=Radio NAME="Case" onClick="Convert(0)" CHECKED>Uppercase<BR>
<INPUT TYPE=Radio NAME="Case" onClick="Convert(1)">Lowercase<BR>
</FORM>
<SCRIPT LANGUAGE="VBScript">
Sub Convert(index)
    If index = 0 Then
        UserData.Username.Value = UCase(UserData.Username.Value)
    Else
        UserData.Username.Value = LCase(UserData.Username.Value)
    End If
End Sub
</SCRIPT>

通常,onclick屬性的值就是要被呼叫的程序名稱,以及它的引數(本例中是index),但是它可以是任何合法的VBScript程式。

TextBox、TextArea及Select控制項會在使用者鍵入文字或是選擇新項目時產生onchange事件。

Scripts通常用在執行時期時在Select控制項中加入項目。要達成這個目的所要做的動作順序可能會困擾Visual Basic程式設計人員:您必須先使用Document物件的CreateElement方法來設定它的Text及Value屬性,最後把它加入到Select控制項中的Options集合中。下面的範例會建立一個使用Select控制項及push button的表單。在啟始時,Select控制項只包含了一個項目,但您可以按下button來再增加兩個新的項目:

<FORM NAME="UserForm">
<SELECT NAME="Countries" SIZE=1>
    <OPTION VALUE=1>US</OPTION>
</SELECT>
<INPUT TYPE=BUTTON NAME="AddCountries" VALUE="Add Countries">
</FORM>
<SCRIPT LANGUAGE="VBScript">
Sub AddCountries_onclick()
    Dim e 
    Set e = Document.createElement("OPTION")
    e.Text = "Italy"
    e.Value = 2
    Userform.Countries.Options.Add e
    Set e = Document.createElement("OPTION")
    e.Text = "Germany"
    e.Value = 3
    Userform.Countries.Options.Add e
End Sub
</SCRIPT>

產生HTML程式
 

VBScript讓您動態產生新的HTML網頁,此時會使用Document物件的Write方法。筆者將在稍後解釋Document物件(以及其他可以讓HTML程式設計人員使用的物件),但底下簡單的範例可以讓您先有一個概念:

<FORM NAME="UserData">
<INPUT TYPE=Text NAME="Rows" VALUE="10">
<INPUT TYPE=Text NAME="Cols" VALUE="10"><BR>
<INPUT TYPE=Button NAME="Generate" VALUE="Generate Table">
</FORM>
<SCRIPT LANGUAGE="VBScript">
Sub Generate_onclick()
    Dim rows, cols
    ' We need to store these values in variables before the form is 
    ' destroyed when a new document is created.
    rows = UserData.Rows.Value
    cols = UserData.Cols.Value

    Document.Open
    Document.Write "<H1>Multiplication Table</H1>"
    Document.Write "<TABLE BORDER=1>"
    For r = 1 to rows
        Document.Write "<TR>"
        For c = 1 to cols
            Document.Write "<TD> "  & (r*c) & " </TD>"
        Next
        Document.Write "</TR>"
    Next
    Document.Write "</TABLE>"
End Sub
</SCRIPT>

這個程式會建立一個新的HTML網頁,它包含一個乘法表,而它的大小是使用者在兩個text box控制項中決定的。(如圖19-3) 一旦您使用Document物件的Open方法時,UserData表單就不再存在,所以在您建立新的網頁時必須先用區域變數rows及cols來儲存這兩個控制項的值。


 

圖19-3 一個會動態依據所指定的行列來建立乘法表的HTML網頁

我們在這裡結束了對HTML及VBScript的快速課程。現在您可以深入到Dynamic HTML並且體會它帶來的強大的彈性及增加的威力。

DYNAMIC HTML概述
 

很多書致力於介紹Dynamic HTML(DHTML),如果您真的對製作DHTML程式非常有興趣,筆者強烈建議您去買一本來看。因為筆者已作過純HTML的介紹,在這章節筆者只會概述這個語言最重要的的一些特性。

理論上,Dynamic HTML應是HTML 4.0-HTML的下一版本。事實上,Microsoft Internet Explorer和Netscape Navigator目前支援不同版本的DHTML,所以寫一個能讓這兩個瀏覽器同時都能運作的很好的DHTML並不容易。然而,就Visual Basic程式設計師的某些觀點來看,這並不會是非常重要的議題,因為事實上DHTML應用程式需要Internet Explorer 4.01 Service Pack 1或稍後版本,而且它們不能在另一個瀏覽器內運作。這並不是DHTML語言本身的問題,而是因為只有最新版本的Microsoft瀏覽器才能把DHTML顯露出來,雖然使用Visual Basic 6撰寫的DLL可以捕捉它們並作出反應。

主要功能
 

Dynamic HTML與平常的HTML並沒有完全的不同。所有舊有的標記依然被支援,網頁內的scripts可以使用之前版本相容的擴充物件模型,如此它們便可繼續像以前一樣運作。就某種意義來說及過分簡化的風險,我們可以說一般的HTML與Dynamic HTML之間真正的差異是當從遠端伺服器下載網頁後瀏覽器如何詮釋這些網頁。

在DHTML的新特色中,底下幾點特別值得一提:

  • 動態地重畫網頁,意味您可以改變樣式,顏色及網頁元件的屬性 - 包含是否是可見的 - 而且網頁可以自己自動地重畫不需要使用者再次地下載。這意味更快的回覆時間,伺服器較少的工作負荷,以上所有,是真正的動態行為。
     
  • DHTML物件模型提供您可以存取網頁的任何元件,包括標記、圖片及文字段落,若有需要也可直接存取單獨的文字、字元。如此您就可以巧妙地處理網頁外觀最細微的細節。
     
  • 更多的屬性延展擴充了樣式及樣式表,因此可讓您對網頁元件有更多的控制。
     
  • 您可以強制元件的絕對位置,這意味著如果有必要,您可以以最高精準度來編排您的網頁版面。此外,每個元件都有z-index屬性(很像Visual Basic的Zorder屬性)可以模擬3-D效果。因為元件的座標能被機動地模組化,它能夠用簡單的script製造動畫效果。
     
  • 新的事件模組增加靈活性讓網頁的script可以處理使用者的動作。這包含事件沸升功能(event bubbling feature),它可以script更方便的處理事件。
     
  • 視覺過濾器(Visual filters)提供很多吸引人的方法來處理展現網頁上的元件,它並允許您製作陰影及3D效果的文字。Transition fiters讓網頁有忽明忽暗的效果。Internet Explorer提供十三種內建的transition filters,但您也可以用其他廠商的filters。
     
  • DHTML有很多改進超越傳統HTML的地方,像是製作表格時更好的控制項及支援新增加的圖形格式(如PNG,Portable Network Graphics,GIF格式的後繼者)。
     

讓我們來看看一些重要特點的細節。

標記(Tags)
 

您已經看過如何用<DIV></DIV>標記組織多個元件及製作一個通用樣式的網頁。例如,您可利用這些標記製作一個與其他元件不同且含有文字及背景顏色的矩形區塊:

<DIV STYLE=is WIDTH=300; HEIGHT=100; COLOR=white; BACKGROUND=red;">
A red block with white text<BR>
Another line in the same block
</DIV>

當您正在用DHTML工作時,您可能需要處理比標題或段落還小的項目。您可用<SPAN></SPAN>這組標記引用這樣的項目,這組標記可分割元件成較小的部分,如此每個部分可以有不同的特性:

<DIV STYLE="WIDTH=300; HEIGHT=150; COLOR=white; BACKGROUND=red;">
A red block with white text<BR>
<SPAN STYLE="COLOR=yellow">Some words in yellow,</SPAN>
<SPAN STYLE="COLOR=blue">Other words in blue</SPAN>
</DIV>

<DIV>與<SPAN>標記之間很重要的不同在於前者會在結束的</DIV>標記後加上換列字元(carriage return),這表示您不能在同一行繼續插入文字。相反地,</SPAN>標記並不插入carriage return,所以,就以前面的那段程式碼為範例,它是產生兩行的文字而不是三行。當您看到如何用script製作動態網頁時,<DIV>和<SPAN>標記的重要性會更為明顯。

<BUTTON>及</BUTTON>標記讓您可在表單加上更多功能的按鈕控制項。當標準的<INPUT TYPE=Button>標記只能支援一個標題時,新的標記讓您可以在文字中嵌入任何東西,包括圖片:

<BUTTON ID="Button"  STYLE="height=80; width=180">
Click Here
<IMG SRC="www.vb2themax.com/mylogo.gif">
</BUTTON>

DHTML包含一些可在其他控制項周圍畫上框格的框架控制項。這樣的控制項是用<FIELDSET>標記來製作,並用<LEGEND>標記來指定標題。事實上,這個控制項比Visual Basic的counterpart還有威力,因為在<LEGEND>和</LEGEND>這對標記間您幾乎可以嵌入任何東西:

<FIELDSET>
<LEGEND>Select a product<IMG SRC="mylogo.gif" ></LEGEND>
<INPUT TYPE=Radio NAME="Product" CHECKED>Tape
<INPUT TYPE=Radio NAME="Product">Music CD
<INPUT TYPE=Radio NAME="Product">Videotape
</FIELDSET>

Dynamic HTML也在某些標記中新增了幾個特性。例如,TABINDEX特性讓您可以準確地指定網頁上面控制項的駐點移動順序(tab order),就如Visual Basic屬性作的一樣。ACCESSKEY特性與一些型態的網頁元件運作,可以提供容易選取的Alt+key快速鍵使用。不同的是DHTML不會反白被選取的項目-您必須自己動手作。當不能反白被選取的項目這點看起來似乎是個缺陷時,事實上在建立您的使用者介面時它提供了一個很大的彈性:

' A "Click Here" button that you click using the Alt+H key combination
(一標題為 "按此"的按鈕,具有Alt+key快速鍵操作功能)
<BUTTON ID="Button1" ACCESSKEY="H" >Click <B>H</B>ere</BUTTON>

最後,DISABLED特性讓您可選擇性地disable(及reenable)控制項和其他元件。您只要記得它的運作跟Visual Basics Enabled屬性相反:

<INPUT TYPE=Radio ID="optMusicCD" NAME="Product" DISABLED>Music CD
<SCRIPT LANGUAGE="VBScript" >
Sub Button1_onclick()
    ' Reenable the option button.
    optMusicCD.disabled = False
End sub
</SCRIPT>

屬性(Properties)
 

Dynamic HTML新增了一些<STYLE>標記的屬性。這些屬性很有用處,但最重要的是新增加可以撰寫腳本的特點,因為它們可讓一個script routine移動、隱藏、及改變與網頁元件相關聯的z-order,從而使網頁成為真正的動態網頁。

Position屬性允許您可以準確地放置元件到網頁上﹔這個屬性預設值被設定為靜態(static),這表示元件的定位是依據HTML使用的規則。但若您設定position屬性為絕對的(absolute),您可以使用left及top屬性來指定物件的相對於視窗左上角的座標。這裡有個例子顯示一個紅色背景的矩形方塊及裡面的白色文字。這個矩形寬度是300個圖素及高度是150個圖素:

<DIV STYLE="POSITION=absolute; TOP=50; LEFT=100; WIDTH=300; HEIGHT=150; 
COLOR=white; BACKGROUND=red;"=>A red block with white text</DIV>

如果一個物件被包含在另一個物件中-舉例來說,另一個<DIV>部分-left及top座標則是相對於收納器的左上角。例如,接下來的這段程式碼製作一個紅色矩形方塊而一個藍色矩形方塊在它裡面:

<DIV STYLE="POSITION=absolute; TOP=100; LEFT=100; WIDTH=300; HEIGHT=150; 
COLOR=white; BACKGROUND=red;">
Outer rectangle
    <DIV STYLE="POSITION=absolute; TOP=20; LEFT=40; WIDTH=220; HEIGHT=110; 
    COLOR=white; BACKGROUND=Blue;">Inner rectangle</DIV>
</DIV>

如果position設定為「相對(relative) 」,left和top屬性意指相對於在網頁上該物件左上角的之前元件座標。典型使用相對模式的時機是用來將某部分文字或是圖片,移到距離網頁上最後一段文字某個地方。

A string of text followed by a green rectangle
<DIV STYLE="POSITION=relative; TOP:10; LEFT=0; WIDTH=300; HEIGHT=10; 
BACKGROUND=green;"></DIV>

當網頁上的物件有相重疊的時候,您可以用z-order屬性去決定它們的外觀,z-order值較高的會顯示在值較低的上層:

<DIV STYLE="POSITION=absolute; TOP=100; LEFT=100; WIDTH=300; 
HEIGHT=150; COLOR=white; BACKGROUND=red; Z-INDEX=2">
This rectangle overlaps the next one.</DIV>
<DIV STYLE="POSITION=absolute; TOP=120; LEFT=120; WIDTH=300; 
HEIGHT=150; COLOR=white; BACKGROUND=green; Z-INDEX=1"></DIV>

您不可以使用z-order屬性來改變物件及它的收納器z-order關係,因為收納器一定會顯示在它包含物件的後面。假使省略了z-order屬性,則物件的顯示順序會依照它在HTML原始碼中顯示的順序來呈現。(也就是每個物件會蓋在程式中較早出現的物件上面)

Visibility屬性決定要顯示的物件是哪一個。它選擇設定值是hidden或visible。當透過script來控制時這個屬性會是最有用的。另一個非常有魅力的屬性是display:當您將它設為none,這個物件將不會被看見且瀏覽器會回覆原本被這物件佔據的空間,並會重新佈置在網頁上的另一個物件(除非它們是絕對定位)。您可以設定display屬性為一個空的字串就可使這個物件再被顯現,請看本章後面的 "第一個範例:動態功能表" 。

屬性及Scriting
 

「動態」在Dynamic HTML表示您可以即時地(at run time) 更改網頁的特性,瀏覽器能馬上呈現網頁的內容而無須從伺服器重新下傳網頁。為了這個理由,您一定要創作script程序以展現DHTML的潛力。

您可以程式來控制網頁上任何項目的任何特性(attribute),倘若這個項目可以在程式中被引用。在純HTML您只能引用少數的項目-例如,表單上的控制項-但在Dynamic HTML,您能引用任何一個有ID特性的項目。例如,下面的程式碼有具有rectangle ID的<DIV>部分,它有一個按鈕,當被按下後,會執行一個VBScript常式去改變<DIV>這部分的背景顏色:

<DIV ID="rectangle" STYLE="POSITION=absolute; LEFT=100; 
TOP=50; WIDTH=200; HEIGHT=100; BACKGROUND=red">
Click the button to change background color
</DIV>
<FORM>
<INPUT TYPE=BUTTON NAME="ChangeColor" VALUE="Change Color">
</FORM>
<SCRIPT LANGUAGE="VBScript" >
' Randomly change the color of the rectangle.
Sub ChangeColor_onclick( )
    Rectangle.style.background ="#" & RndColor( ) & RndColor( ) & RndColor( )
End Sub
?'Return a random two-digit hexadecimal value.
Function RndColor( )
    RndColor = Right("0" & Hex(Rnd * 256), 2)
End Function
</SCRIPT>

您必須透過居中的樣式物件來使用background屬性。因為background是STYLE特性其中的一個屬性。相同地,您可控制這個「樣式物件(style object) 」的其他屬性,像這些:

調整項目的定位及大小,您可以由left、top、width及height屬性傳回的字串中取出px。因為回應的是數值化的值 ,利用posxxxx會是比較好的選擇。下面的例子告訴您如何把元件移到右邊:

rectangle.style.posLeft = rectangle.style.posLeft + 10

如果屬性並未在STYLE特性中被定義,它的回應會是Null。Posxxxx屬性是這規則中的例外,因為它們總是回應數值化的值。


說明

必須使用style.color和style.backgroundColor屬性來調整網頁上任何元件的文字及背景顏色,除了Document物件外,它應用fgcolor和bgcolor屬性。


文字屬性及方法
 

因為DHTML文件是動態的,您常會即時修改它的內容。有幾種方式

作即時修改- 例如,利用TextRange物件(本章稍後會述說到)。大部分可見的網頁元件都會支援四種屬性及兩種方法,這讓這個工作較容易些。

這四種屬性分別是innerTex、outerText、innerHTML和outerHTML。InnerText以純文字型態傳回元件內文件的內容。(所有HTML標記都會被自動地過濾掉。) outerText傳回的值與InnerText一樣,但當您指派一個字串,得到的結果將會不一樣。InnerHTML屬性傳回開始及結束標記間的HTML原始碼而outerHTML屬性則會傳回元件的HTML原始碼,包含了開始及結束標記本身。

為了體驗一下這些屬性,讓我們定義一些包含HTML標記的元件,像是:

<H1 ID=Heading1>Level <I>One</I> Heading</H1>

在瀏覽器上它的結果是 Level One Heading 。現在我們來看看應用在這個元件時,前面的屬性會傳回什麼東西:

MsgBox Heading1.innerText  ? Level One Heading
MsgBox Heading1.outerText  ? Level One Heading
MsgBox Heading1.innerHTML  ? Level <I>One</I> Heading
MsgBox Heading1.outerHTML  ? <H1 ID=Heading1>Level <I>One</I> Heading</H1>

指派value給innerText取代在開始及結束標記間的文字﹔這新value不合語法,所以它不應該包含HTML標記。例如,陳述式:

Heading1.innerText = " A New Heading"

完全換掉在<H1>及</H1>標記間的文字,瀏覽器顯現的是新標題 A New Heading 。即使outerText屬性總是傳回與innerText屬性相同的字串,當被指定新的值時,它的行為表現是不一樣的,因為這個動作也會影響周圍的標記。

因此底下的陳述式:

Heading1.outerText = " A New Heading"

實際上破壞了<H1>和</H1>標記且轉換標題元件成純文字(plain text)(除非它是被包圍在另一對標記內)。還有什麼比現在物件沒有ID特性因而無法被程式引用更糟,所以您再也無法程式編製地存取它 。因此,outerText屬性實際上的運用是很有限的,且在大部分的案例中,您只會在要刪除元件周圍的標記時會使用它:

' A reusable VBScript routine
Sub DeleteOuterTags(anyElement)
    anyElement.outerText = anyElement.innerText
End Sub

如果您想要更換網頁某部分標記內的內容且包含一些HTML文字,您應該用元件的innerHTML屬性,就如這行的程式碼:

Heading1.innerHTML = "A <U>New</U> Heading"

在這個案例,傳送到這個屬性的字串會被解譯且所有的HTML標記都會影響結果。例如,在先前的程式在瀏覽器上顯現的結果會是 A New Heading 

這組最後一個屬性是outerHTML,作用像innerHTML,但替代物也會影響周圍的標記。這表示您可以更改元件的型態及ID,而且您也可以改變標題的層級和內容的格式,例如,下面的動作:

Heading1.outerHTML = "<H2 ID=Heading1>Level <U>Two</U> Heading</H2>"

或是您可用這個程式碼將標題放置在中央:

Heading1.outerHTML = "<CENTER>" & Heading1.outerHTML & "</CENTER>"

幸虧VBScript的字串控制能力,您可以建立一個重覆使用的常式來讓您改變網頁標題的層級卻不用改變它的ID及內容:

Sub ChangeHeadingLevel(element, newLevel)
    html = element.outerHTML
    pos1 = Instr(UCase(html), "<H")
    level = Mid(html, pos1 + 2, 1)
    pos2 = InstrRev(UCase(html), "</H & level, -1, 1)
    ' You must type the next two lines as a single statement.
    html = Left(html, pos1 + 1) & newLevel & Mid(html, pos1 + 3, 
        pos2 - pos1) & newLevel & Mid(html, pos2 + 4)
    element.outerHTML = html
End Sub

若您更改元件的ID,您為它而寫的事件程序就再也不會有效。為此理由,您應該要保持相同的ID,或是動態地加入程式碼以管理新元件的事件。並且記住不是所有的可見(visible)元件都支援這四個屬性,最知曉的是表格儲存格(只有innerTex t和innerHTML屬性)。

筆者已經講解過讓您可以更換(replace)文件的的部分的四個屬性,大部分的元件也支援兩個方法(method)讓您可以增加(add)新內容到文件。InsertAdjacentText方法馬上地在元件開始標記或結束標記的前或後插入一段純文字。InsertAdjacentHTML方法也是一樣,但它的引數是合乎語法的且所有的HTML都可以正確地辨識並影響結果。這有一些例子:

' Append plain text at the end of the heading.
Heading1.insertAdjacentText "BeforeEnd". " (added dynamically)"
' As above, but appends italicized text.
Heading1.insertAdjacentHTML "BeforeEnd". " <I>(added dynamically)</I>"
' Add new text before the first word of the heading.
Heading1.insertAdjacentText "AfterBegin", "This is a "
' Add a level 2 heading immediately after this heading.
Heading1.insertAdjacentHTML "AfterEnd.."<H2>New Level 2 Heading</H2>"
' Insert italicized text right before this heading.
Heading1.insertAdjacentHTML "BeforeBegin". "<I>Introducing...</I>"

事件
 

每個已具有ID特性的網頁元件都能產生事件。DHTML事件與Visual Basic事件縱使名稱不同,但其大體上卻很相似。所有的DHTML事件開始於兩個字元on,像是onclick、onkeypress及onchange。例如,超連結被點選,您可以以接下來的VBScript程式碼捕捉使用者的動作:

Click <A ID="Details" HREF="www.vb2themax.com/details">here</A> for details
Sub Details_onclick()
    MsgBox "About to be transferred to another site"
End Sub

DHTML事件處理與Visual Basic的處理方式不同。第一,事件程序不帶引數。第二,事件是被產生事件的物件接受(就像innermost of a set of Russian dolls),依次傳到所有包含產生該事件的物件的網頁元件。這個特性,稱為「事件沸升(event bubbling) 」,會在後面的章節解釋。

所有在事件內可取得有意義的引數都能視為是事件物件的屬性。例如,當接收到onkeypress事件,透過event.keycode屬性您能判斷哪個按鍵是被按下,您也可以透過設定這個屬性為0而把這個按鍵「吃」掉。例如,看看您如何可以把輸入文字方塊控制項的文字轉換成大寫字母:

<INPUT TYPE=Text NAME="txtCity" VALUE="">
<SCRIPT LANGUAGE="VBScript">
Sub txtCity_onkeypress( )
    window.event.keycode = Asc(UCase(Chr(window.event.keycode)))
End Sub
</SCRIPT>

在任何的事件程序內部,您可以使用Me關鍵字來取得產生該事件的物件引用,如同接下來的這段程式碼:

Sub txtCity_onkeypress( )
    ' Clear the text box if the spacebar is pressed.
    If window.event.keycode = 32 Then 
        Me.Value = ""
        window.event.keycode = 0      ' Also eat the key.
    End If
End Sub

事件沸升(Event bubbling)
 

DHTML事件特色中的事件沸升讓您在網頁多個地方處理一個事件,這不是Visual Basic所能達到的。DHTML事件第一個被使用者啟動的物件接收,接著這個事件會傳到它的收納器,然後是收納器的收納器等等,直到傳到階層中最高的標記。例如,若使用者按下表格內的超連結,onclick事件第一個會在超連結物件中觸發的,接著是表格及Body物件,最後是Document物件。

接下來的例子使用事件沸升特性撰寫一個事件程序來管理當三個不同但已被<DIV>標籤群組的TextBox控制項按下按鍵的動作。這個例子同時展示事件會產生給Body物件(已標示ID特性)及Document物件:

<BODY ID="Body">
<DIV ID=Textboxes>
<INPUT TYPE=Text NAME="txtName"VALUE="">
<INPUT TYPE=Text NAME="txtCity" VALUE="">
<INPUT TYPE=Text NAME="txtCountry" VALUE="">
</DIV>
<SCRIPT LANGUAGE="VBScript">
Sub Textboxes_onkeypress( )
    ' Convert to uppercase.
    window.event.keycode = Asc(UCase(Chr(window.event.keycode)))
End Sub
Sub Body_onkeypress( )
    ' The Body element also gets the event.
End Sub
Sub Document_onkeypress( )
    ' The Document element also gets the event.
End Sub
</SCRIPT>
</BODY>

將event.cancelBubble屬性設定為True,您可以取消任何事件程序的沸升。例如,您在Body_onclick程序將這屬性設定成True,Document物件不會收到這個事件。

在一連串的任何事件程序中,您能利用詢問event.srcElement屬性以觸發事件的元件引用。這樣可以允許您建立一般廣義的事件程序並同時計算特殊的案例,如同以下的例子:

Sub Textboxes_onkeypress( )
    ' Convert all textboxes to uppercase except txtName.
    If window.event.srcElement.Name <> "txtName" Then
        window.event.keycode = Asc(UCase(Chr(window.event.keycode)))
    End If
End Sub

不要將srcElement屬性跟Me關鍵字混淆,它會傳回產生事件的物件引用給事件程序。這兩個物件只有在內部被事件沸升(event bubbling)機制觸發的第一個事件程序才會重疊發生。

取消預設反應
 

大部分在網頁元件上的使用者動作會產生預設的結果。例如,滑鼠按一下超連結會跳到另一個HTML網頁,當按下按鍵TextBox控制項內的文字會被加入控制項的即時內容中。您可以設定event.returnValue屬性為False以取消這些預設反應,例如:

Click <A ID="Link1" HREF="http://www.vb2themax.com">here</A>
<SCRIPT LANGUAGE="VBScript">
Sub Link1_onclick( )
    ' Prevent the hyperlink from firing.
    window.event.returnValue = False
End Sub
</SCRIPT>

另一個取消事件預設反應的方法是將事件程序轉換成Function並將傳回值設為False,如這樣:

Function Link1_onclick( )
    Link1_onclick = False
End Function

計時器事件(Timer events)
 

即使HTML不提供Timer控制項,它還是可以建立某段時間固定執行的常式。您可以從兩種型態的timer常式之中選擇:一種會重複地觸發,另一種只會觸發一次。(這是標準HTML的功能,所以您不需在這部分用DHTML。)您可用window物件的setTimeout(只啟動一次的timers)或setInterval(一般的timers)方法啟動timer常式。這些方法有相似的語法:

通常您會從內部的window_onload常式或是外部任何的常式呼叫這些方法。(在這兩種情況,方法會在當網頁被下傳時就被執行。)例如,下面的程式碼是每秒將按鈕往右移動20個圖素兩次。

window.setTimeout "routinename", milliseconds, language
window.setInterval "routinename", milliseconds, language

通常您會從內部的window_onload常式或是外部任何的常式呼叫這些方法。(在這兩種情況,方法會在當網頁被下傳時就被執行。)例如,下面的程式碼是每秒將按鈕往右移動20個圖素兩次。

通常您會從內部的window_onload常式或是外部任何的常式呼叫這些方法。(在這兩種情況,方法會在當網頁被下傳時就被執行。)例如,下面的程式碼是每秒將按鈕往右移動20個圖素兩次。

<INPUT TYPE=BUTTON NAME="Button1" VALUE="Button Caption"
    STYLE="POSITION=absolute" >
<SCRIPT LANGUAGE="VBScript">
' This line is executed when the page is loaded.
window.setInterval "TimerEvent", 500, "VBScript"
' The following routine is executed every 500 milliseconds.
Sub TimerEvent( )
    Button1.style.posLeft = Button1.style.posLeft+5
End Sub
</SCRIPT>

您可以用clearTimeout或clearInterval個別地取消setTimeout或setInterval方法的作用。

事件摘要
 

我們可以依功能將DHTML事件再分割成幾個種類。

Keyboard事件包括onkeypress、onkeydown和onkeyup。這些事件與Visual Basic相同名稱的事件相似。事件物件的keycode屬性包含按下鍵的代碼,而且您可以用事件物件的altKey、ctrlKey及shiftKey屬性閱讀shift keys的狀態。

Dynamic HTML支援跟Visual Basic一樣的滑鼠事件,包括onclick、 ondblclick、onmousedown、onmouseup和onmousemove。當使用者在一個獲得駐點(focus)的push button按下Enter鍵,onclick事件同時也會觸發。在滑鼠事件內部,您可以查詢event.button屬性來得知哪個按鈕被按下了。(您所得到的bit-coded value與在Visual Basic滑鼠事件裡接收到的引數類似。)

有幾個DHTML事件並沒有完全對應到在Visual Basic的事件:onmouseover會在當滑鼠游標進入到一個元件時觸發,onmouseout會在當滑鼠離開那個元件時觸發。在這些事件程序內部,您可以用fromElement和 toElement屬性去得知哪些元件已被進入或是離開。

Onfocus和onblur事件與Visual Basic的GotFocus和LostFocus事件類似,但當駐點移到另一個視窗或另一個應用程式時也會觸發。Onchange事件與Visual Basic的事件也相似,但它只有在駐點離開控制項時才會觸發。

當使用者在網頁上按鍵並開始選擇一段文字或是其他元件,onselectstart事件就會觸發﹔當滑鼠移動其被選取的部分也跟著改變,會觸發onselect事件。當拖曳作業發生時會觸發ondragstart事件:藉由捕捉這個事件,您能夠取消它的預設反應動作 - 也就是在別處複製被選取的文字。

有幾個事件是於整個網頁都可使用的。當網頁的狀態改變時onreadystatechange事件會觸發(例如,當已完成下傳且網頁要成為互動式的狀態時)。當網頁重新改變大小時會觸發onresize事件。onunload和onbeforeunload事件很像Visual Basic的Unload和QueryUnload,當使用者因瀏覽另一個網頁或結束瀏覽器時會觸發這些事件。當捲動網頁(或是網頁元件)捲軸會觸發onscroll事件。當使用者按下F1鍵會觸發onhelp事件。當發生script錯誤或是下傳網頁元件(例如圖片)失敗時會觸發onerror事件。

有少數的事件無法由DHTML Visual Basic應用程式來捕捉:onabort(使用者按下瀏覽器工具列上的Stop按鈕),onreset(使用者按下Reset按鈕),及onsubmit(使用者按下Submit按鈕)。

DHTML物件模型
 

要寫出具效益的Dynamic HTML應用程式,您必須要熟悉存放DHTML網頁的瀏覽器所提供的物件模組。圖19-4顯示完整的Windows物件階層。筆者將不會一一解析這個階層下的每個屬性、方法和事件﹔反而,筆者會專注在那些對Visual Basic程式設計師而言最感興趣及有用的物件。


 

圖19-4 DHTML物件模型

Window物件
 

Window物件是DHTML階層的根。它表示用來顯示HTML網頁(它是用Document物件表示)的視窗內部。

屬性
 

如果一個視窗包含框架(frames),您可用包含其他其他Window物件的Frames集合存取它們。會有好幾屬性回傳的Window物件的引用。如果這個視窗本身就在框架內,您可以給它的收納器視窗一個含parent屬性的引用。top屬性會傳回最上層的視窗的引用。一般而言,您無法預料到哪個視窗已被後面兩個屬性所引用,因為也許使用者可能不是由您應用程式網頁中的框架來載入您的網頁。 opener屬性會傳回開啟目前視窗的視窗引用。

您可用closed屬性來查詢視窗的開啟或關閉狀態。status屬性設定及傳回瀏覽器狀態列上所顯示的文字,defaultStatus是狀態列預設的字串。

方法
 

Window物件跟幾個方法有關聯。open方法會在視窗中下載新文件,而showModalDialog方法會在強制性(modal)視窗中載入HTML網頁:

' Jump to another page.
window.open "http://www.vb2themax.com/tips"

您可用close方法關閉視窗。少部分其他其他方法-如alert、confirm和prompt -顯示訊息對話方塊和文字輸入方塊對話方塊,但通常用Visual Basic的MsgBox和InputBox指令會得到更好的結果。

focus方法與Visual Basic的SetFocus方法相似。blur方法將輸入駐點移到下一個視窗,就好像使用者已按下Tab鍵一樣。scroll方法接受一組x-y座標而且捲動視窗以確定在瀏覽器上可以看見特定的某點:

' Scroll the window to the top.
window.scroll 0, 0

對您的程式而言execScript方法增加了很多彈性,因為它可以讓您加入一段script code且直接執行。接下來的例子顯示用這個方法加幾行程式碼執行一個簡單的計算機(請看圖19-5)

Insert your expression here:
<INPUT TYPE=Text NAME="Expression"VALUE=""><BR>
Then click to evaluate it:
<INPUT TYPE=BUTTON NAME="Evaluate" VALUE="Evaluate">
<INPUT TYPE=Text NAME="Result" VALUE="">
<SCRIPT LANGUAGE="VBScript">
Sub Evaluate_onclick
    If Expression.Value = "" Then
        MsgBox "Please enter an expression in the first field"
        Exit Sub
    End If
    On Error Resume Next
    window.execScript "Result.value = "  & Expression.Value, "VBScript"
    If Err Then
        MsgBox "An error occurred - please type a valid expression" 
    End If
End Sub
</SCRIPT>


 

圖19-5 用DHTML程式碼建立的計算器範例。

別忘了要在execScript功能的第二個引數傳入"VBScript"字串,因為預設的語言是JavaScript。

這個功能強大的方法甚至可以加上script程序到網頁中。接下來的例子示範這項特色,它會依據使用者在TextBox控制項中輸入的值來建立一個表格。(這在Visual Basic而言是非常難做的!)

Enter an expression:   FN(x) = 
<INPUT TYPE=Text NAME="Expression" VALUE="x*x"><P>
Click here to generate a table of values:
<INPUT TYPE=BUTTON NAME="CreateTable"  VALUE="Create Table">
<SCRIPT LANGUAGE="VBScript" >
Sub CreateTable_onclick( )
' Create the FUNC( ) function. 
' (Enter the following two lines as a single VBScript statement,)
window.execScript "Function FUNC(x): FUNC = " 
    & Expression.Value & " : End Function", "VBScript"
' Create the HTML code for the table.
code = " <H1>Table of values for FN(x) = "  & Expression.Value & "</H1>"
code = code & " <TABLE BORDER>"
code = code & " <TR><TH>  x  </TH><TH>  FN(x)  </TH></TR>"
For n = 1 To 100
    code = code & " <TR><TD> "  & n & "  </TD>"
    code = code & " <TD> "  & FUNC(n) & "  </TD></TR>"
Next
code = code & "</TABLE>"

' Write the code to a new HTML page.
window.document.clear
window.document.open
window.document.write code
window.document.close
End Sub
</SCRIPT>

筆者已經解說過Windows物件其餘的方法:setInterval、setTimeout、clearInterval和clearTimeout。(請看本章前面的 〈計時器事件〉 )

History物件
 

History物件指出目前使用者已參觀過的所有URL。它只具有一種屬性和三種方法。

length屬性傳回儲存在物件裡的URL數量。back和forward方法下載history清單中前一個和下一個URL的網頁,在瀏覽器上的工具列點選一下back和forward按鈕也是相同的效果。要在網頁上增加按鈕以達到相同的功能,這些方法是很有用的,如這裡所示:

Sub cmdPrevious_onclick( )
    window.history.back
End Sub

這物件的另一個方法就是go,它能下載在history清單裡的第N個網頁:

' Display the third page in the history list.
window.history.go 3

Navigator物件
 

Navigator物件代表瀏覽器應用程式和提供有關相容性的訊息。appCodeName、appName和appVersion屬性會回傳程式碼(code)名稱、產品名稱及瀏覽器的版本訊息。若瀏覽器支援cookie則cookieEnabled屬性傳回True值;userAgent是以字串型態回傳給伺服器HTTP請求的瀏覽器名稱。您可以用下面的VBScript程式碼來顯示一些關於您的瀏覽器的訊息:

' Dynamically create an HTML page with all the requested information.
Set doc = window.document
Set nav = window.navigator
doc.open
doc.write "<B>appCodeName</B> = "  & nav.appCodeName & "<BR>" 
doc.write "<B>appName</B> = "  & nav.appName & "<BR>" 
doc.write "<B>appVersion</B> = "  & nav.appVersion & "<BR>"
doc.write "<B>cookieEnabled</B> = "  & nav.cookieEnabled & "<BR>" 
doc.write "<B>userAgent</B> = "  & nav.userAgent & "<BR>" 
doc.close

其他幾個屬性會傳回有關瀏覽器的資訊,包括cpuType、userLanguage、systemLanguage及plateform。如果瀏覽器支援Java語言,則唯一的方法javaEnabled會傳回True值。

Navigator物件也具有兩個集合:mimeTypes集合包含瀏覽器有支援的所有文件及檔案的型態,plugins集合包含網頁中的全部物件。

Location物件
 

Location物件顯示瀏覽器目前顯示網頁的URL。它最重要的屬性是href,它會傳回完整的URL字串。其他的屬性則包含部分的URL字串:hash(在符號#之後的部分)、hostname (主機名稱)、host (URL的hostname:port 部分)、port(連接埠號碼)、protocol(URL包含協定名稱的第一部份)、search (在URL?符號後的部分)。

這個物件也具有三種方法:assign(下載不同的網頁)、replace(下載網頁並取代目前的history清單)、reload(重新下載網頁)。

Screen物件
 

Screen物件具有跟螢幕有關的屬性,但不提供任何方法。width和height屬性是螢幕以像素(pixels)為單位的寬高尺寸,當您要決定擺放一個新視窗時這非常有用。colorDepth屬性是有所支援色彩的bit數,且通常使用到的情況是當伺服器端含有幾個相似的圖片而您想要下載最適合使用者電腦的顯示卡的那個圖片。bufferDepth是一個可寫入的屬性,瀏覽器藉以顯示會跟隨off-screen buffer顏色深淺改變的圖片。這個屬性讓您可顯示不同於原始色彩濃度的圖片。updateInterva l屬性能被指定在一定的間隔時間讓瀏覽器重畫網頁。(尤其當您正在作動畫效果時想減低閒歇閃爍情況時有幫助。)

Internet Explorer也支援availWidth及avaiHeight屬性,它們會回傳沒被看見的工作列(如Microsoft Windows和Microsoft Office工作列)佔住的螢幕的大小,而如有必要,可以使用fontSmoothingEnabled布林屬性來指定瀏覽器是否該用滑順(smoother)的字體。

Event物件
 

在本章前面部分筆者已經解說了Event物件的許多特色。這個物件與VBScript事件程序共同使用來閱讀及可能地修改事件引數,指定哪些預設的動作該被取消,及取消event bubbling。Event物件只具有屬性,沒有方法。

有四對屬性會回傳滑鼠游標的座標。screenX及screenY屬性提供您與螢幕左上角相距的位置;clientX及clientY提供您與瀏覽器視窗左上角相距的位置;x及y提供您觸發事件的物件收納器的位置;offserX及offserY提供您觸發事件的物件的位置。第九個屬性,button,以bit-fielded值型態(1=left button, 2=right button, 4=middle button) 傳回滑鼠按鈕的狀態。

有四個屬性必須與鍵盤的狀態共同使用:keycode是被按的按鍵的ASCII碼(您可以設定它來改變按鍵),不論是altKey、ctrlKey或shiftKey都會傳回相對應的shift key的狀態。

有三種屬性會傳回網頁上元件的引用。scrElement物件是原始觸發事件的項目;假使您使用階層中較高物件的事件程序來捕捉這個事件,它可能會與Me物件有所不同。在onmouseout和onmouseover事件中,fromElement及toElement屬性分別傳回正在離開或進入的元件。

您可以設定cancelBubble屬性為False以取消事件沸升(event bubbling)。而設定returnValue屬性為False可以取消與事件相關聯的預設反應動作。 Type屬性傳回不含on開頭的事件名稱字元(如click、focus等)。

Document物件
 

Document物件顯示瀏覽器目前下載的網頁內容。就屬性、方法、事件和功能性而言,它可能是最豐富的DHTML物件。

屬性
 

有幾個屬性會傳回關於下載的網頁及文件的狀態的資訊。title屬性包含文件的標題(由<TITLE>標記定義的字串;URL有網頁的Uniform Resource Locator(如 http://www.vb2themax.com );domain傳回文件的安全網域;lastModified傳回最近文件被編輯的日期和時間。referrer屬性代表是由哪個URL進來目前的網頁。

有些屬性會設定或傳回色彩的值。例如,fgcolor和bgcolor提供網頁的文字及背景色彩;改變這些屬性會馬上地影響反應到網頁(除了已被特別定義的區域的顏色設定之外)。

有三個屬性會控制超連結的顏色:linkColor回傳的顏色表還沒被參觀過的鏈結;vLinkColor告訴您的顏色代表已被參觀過的鏈結,alinkColor傳回的顏色表正在活動的鏈結(這是當游標正在某個超鏈結位置時按下滑鼠按鈕)。

有些屬性會傳回網頁上的其他物件的引用。body屬性讓您引用到Body物件;parentWindow傳回文件所屬的Windows物件的引用;location是父Window提供的Location物件引用;activeElement傳回具有駐點的網頁元件引用。(當您才剛下載完網頁,這個屬性會傳回Body元件的引用。)

readyState屬性傳回一個字串來敘述目前文件下載的狀態。這是一個非常有價值的資訊,因為若您引用一個物件-像是圖片 -這可以避免當仍在下載網頁時發生錯誤:

If document.readyState = " complete" Then
    ' Fill the text box control only if the page has been completely
    ' downloaded.
    MyTextBox.Value = " Good morning dear user!"
End If

這個屬性是如此重要以致Document物件當值改變時會觸發一個特別的事件,onReadyStateChange。因為這個事件,您不必持續地測試這個屬性去判斷何時動作網頁元件時是安全的。

方法
 

我們已看過幾個Document物件的方法-clear、open、write和close這些我們已用過的方法動態地去製作新的HTML網頁。writeln方法是write方法的變體,它會增加新的一列及支援multiple引數:

document.writeln "First Line<BR>", "Second Line"

請記住如果輸出是純HTML,增加換列字元通常沒有作用,除非您插入一組<PRE>和</PRE>標記或<TEXTAREA></TEXTAREA>標記。

elementFromPoint方法會傳回對應到某一座標所指定的元件(因此它與Visual Basic控制項提供的HitTest方法相似。)您可以在滑鼠事件程序裡用這個方法已顯示在滑鼠游標下的物件的敘述:

Sub Document_onmousemove()
    On Error Resume Next     ' Not all elements have a Name property.
    ' Fill a text box with the description of the element under the mouse cursor.
    Set element = document.elementFromPoint(window.event.x, window.event.y)
    Select Case element.Name
        Case "txtUserName"
            txtDescription.Value = " Enter your username here"
        Case "txtEmail" 
            txtDescription.Value = " Enter your e-mail address here"
        ' And so on.
    End Select
End Sub

在本章的前面,筆者有為您說明如何使用Document的createElement方法來建立新的Option物件並且在執行時期動態地填入一個Select控制項。您也可以使用這個方法來建立新的<IMG>標記及<AREA>標記。(後者會建立影像圖,然而它並不在本章的範圍。)

子集合
 

Document物件提供了幾個子集合,讓您列舉出所有在網頁中的元件。這些集合不是沒有交集的,所以一個元件可能同時屬於一或多個集合。舉例而言,all集合包含所有在文件body上的標記及元件,但是Document物件也提供了anchors、images 及links集合,這些集合從名稱就可看出它們的意義。scripts集合包含所有<SCRIPT>標記,forms是所有存在的表單集合,而styleSheets包含所有在文件中定義的樣式表。還有一些集合筆者沒有在本章提及,例如frames、embeds及plugins集合。

使用這些集合的方法跟使用Visual Basic的集合很相像。您可以在集合中以元件的索引(集合是以零做基底zero-based),或是key(大多數的情況,key就是元件的名稱) 來取得該元件。最關係重大的不同點在於DHTML集合具有length屬性來代替Count屬性。您可以使用For Each...Next迴圈來列舉出集合中所有的項目,並且使用它的tagName屬性來判斷元件的型態:

' Print the tags of all the items in the page.
For Each x In document.all
    text = text & x.tagName & ","
Next
text = Left(text,Len(text) ? 2)        ' Drop the last comma.
MsgBox text

若您只想取得某個標記的元件,您可以使用它的tags方法來過濾集合,它接收您要過濾的標記名稱:

' Display the names of all <INPUT> elements.
For Each x In document.all.tags(inINPUTl )
    text = text & x.Name & ","
next
MsgBox Left(text,Len(text) -2)        ' Drop the last comma.

tags方法會傳回一個集合,所以您可以使用變數來儲存它的傳回值以便將來使用,或者您可以查詢它的length屬性:

Set imgCollection = document.all.tags("IMG")
MsgBox "Found " & imgCollection.length & " images."

forms集合特殊點在於它還具有一個子elements集合,它會傳回所有在這個表單上的控制項。一個網頁可以包含多個表單,即使所有在本章的範例都只使用了一個表單:

' List the names of all the controls on the first form in the page.
For Each x In document.forms(0).elements
    text = text & x.name & ","
Next
MsgBox Left(text,Len(text) - 2)
' Move the input focus on the control named "txtUserName"
' of the form named "UserData."
document.forms("UserData").elements("txtUserName").focus

Selection物件
 

Selection物件代表目前在網頁上被使用者反白的區域。它唯一的屬性就是type,它會傳回一個字串來告訴所選擇的元件是哪種類型的。(有none或text兩個選擇。)

Selection物件提供三個方法。empty方法會取消選擇,並且把它的type屬性轉為none。clear方法會刪除選擇的內容。假使選擇包含了文字、控制項或是一整個表格,它們會完全地由網頁上移除,而網頁會自動更新顯示。(然而若只是選擇了部分的表格,empty並不會刪除表格):

' Delete the selected portion of the document 
' when the user presses the "C" key.
Sub Document_onkeypress()
    If window.event.keycode = Asc("C") Then
        document.selection.clear
    End If
End Sub

Selection物件的最後一個方法就是createRange,它會傳回一個TextRange物件的引用,該物件是用來描述目前選擇的文字。筆者將會在下面一節為您解釋什麼是TextRange物件。

TextRange物件
 

TextRange物件代表文件的一部份。它可以是目前使用者選擇的區域或是程式所定義的區域。TextRange物件讓您存取網頁某一部份的內容-不論是HTML原始碼或是顯示給使用者的文字-並且提供了幾個方法來讓您定義它範圍的大小及位置:

就如之前我們所看到的,您可以從Selection物件來建立TextRange屬性,或者您也可以使用Body物件、Button、TextArea或是TextBox元件的createTextRange方法:

Set bodyTxtRange = document.body.createTextRange
Set inputTxtRange = document.all("txtUserName").createTextRange

屬性
 

TextRange物件只提供兩個屬性,text及htmlText。前者可以讓您設定或傳回文件中由TextRange物件所定義部分的本文內容,但是您不可以指定它的格式。而後者則是一個唯讀的屬性,它會使用HTML格式來傳回文件某部分內容。下面的VBScript程式會當使用者按下C鍵時顯示所選擇文字的HTML內容,並且當使用者按下U鍵時,將文字轉換為大寫:

Sub Document_onkeypress()
    If window.event.keycode = Asc("C") Then
        MsgBox document.selection.createRange.htmlText
    ElseIf window.event.keycode = Asc("U") Then
        ' Type the following two-line statement as one line.
        document.selection.createRange.text = 
            UCase(document.selection.createRange.text)    
    End If
End Sub

htmlText會傳回在語句構造上正確的HTML程式碼。舉例而言,假使TextRange物件只使用了<B>標記做開頭來顯示粗體字,則htmlText會傳回包含以</B>標記結束的正確HTML,所以您可以放心地在同樣或是其他的文件中重覆使用它,而不會有任何問題。若在區域中有<SCRIPT>標記也會一併傳回。

text屬性會傳回在TextRange物件的文字,但是只有當這個區域沒有在延伸區域的部分使用了不同特性時,指定值給這個屬性才有作用。

方法
 

TextArea物件提供了27種方法,但是筆者只會為您解釋最常用的幾個。第一個要熟悉的方法就是select,它會顯示TextRange物件,如此它便可以被選擇:當您要在對物件做動作時,同時讓物件做出視覺的反應,便可以使用這個方法。

moveStart、moveEnd及move方法可以改變啟始點、結束點或是區域兩者的端點位置。您可以使用底下的程式指定數目來改變文字、單字或是整個句子的點數:

' Extend the selection 10 characters to the right.
Set selRange = document.selection.createRange
selRange.moveEnd "character", 10
' Extend it one word to the left. 
' (Negative values move toward the beginning of the document.)
selRange.moveStart "word", -2
selRange.select
' Extend it one sentence to the right. (The value "1" can be omitted.)
selRange.moveEnd "sentence"
selRange.select
' Restore it as it was.
selRange.move "textedit"

collapse方法會縮小TextRange方法的啟始點(假使引數為True)或結束點(假使引數為False)大小:

selRange.collapse True     ' Reduce the range to its starting point.

當您想要TextRange物件在網頁上某個元件上面移動,您可以使用moveToElementText方法。只有在TextRange已經包含這個元件時,這個方法才有作用,所以您通常會從body元件建立一個TextRange物件,然後將它縮小到所想要的元件,如底下的程式碼:

' Create a TextRange corresponding to the "MyControl" element.
Set range = document.body.createTextRange
range.moveToElementText document.all("MyControl")

您可以使用moveToPoint方法讓TextRange移到指定的x-y座標,滑鼠座標是一個典型例子:

' Retrieve the word the user clicked on.
Sub Document_onclick()
    Set range = document.body.createTextRange
    range.moveToPoint window.event.x, window.event.y
    range.expand "word"
    MsgBox range.text
End Sub

使用findText方法可以讓TextRange在網頁上指定的文字字串上面移動。在它最簡單的模式使用了一個引數,那就是要被搜尋的字串,假使這個搜尋成功,則會傳回True (在這種情況,range會移到搜尋到的文字上),否則會傳回False:

Set range = document.body.createTextRange
If range.findText("ABC") Then
    range.select
Else
    Msgbox "Text not found"
End If

關於TextRange物件其餘的方法,必較值得提及的有scrollIntoView(確保在瀏覽器視窗中,text range是可見的)、parentElement (傳回一個完全包含text range的元件引用)、pasteHTML(使用HTML程式碼來取代text range的內容),以及duplicate(建立一個指到相同range的新TextRange物件)。

Table物件
 

在Dynamic HTML,table就跟在純HTML中所定義的完全相同,也就是一組<TABLE>及</TABLE>標記,以及一系列的<TR>及<TD>標記。在DHTML中,table真正的不同點在於提供了rows及cells集合,它可以讓您分別存取儲存格而不需要為它們指定ID特性。更精確地說,table物件提供一個rows集合,而每個row物件又提供了cells集合。下面的程式片段會取出table的內容,並以tab-delimied的字串來顯示,這樣一來就可以用在匯出檔案:

Set table = document.all("Table1")
For each thisRow in table.rows
    For each thisCell In thisRow.cells
        text = text & thisCell.innerText & Chr(9)
    Next
    ' Replace the last tab char with a CR-LF pair.
    text = Left(text, Len(text) - 1) & Chr(13) & Chr(10)
Next
MsgBox text

您可以使用下面的語法來直接引用一個儲存格:

' Modify the first cell in the third row. (Row and column indices are
' zero-based.)
table.rows(2).cells(0).innerText = "New Value"

因為單獨的儲存格並不支援innerHTML屬性,所以要改變某一個儲存格的特性,您必須建立一個TextRange物件,然後使用pasteHTML方法來代替:

Set thisCell = table.rows(2).cells(0)
Set range = document.body.createTextRange
range.moveToElementText thisCell
range.pasteHTML "<B>New Value in Boldface</B>"

更令人興奮的是您具有新增rows及columns的能力,我們在此要感謝table物件的insertRow方法,以及row物件的insertCell方法:

' Add a row as the fifth row of the table.
set newRow = table.insertRow(4)
' Insert a cell in the first column,and set its contents.
set newCell = newRow.insertCell(0)
newCell.innerText = "New Cell in Column 1"
' Add other cells,using a more concise syntax.
newRow.insertCell(1).innerText = "New cell in Column 2"
newRow.insertCell(2).innerText = "New cell in Column 3"

您也可以使用row物件的deleteCell方法及table物件的deleteRow方法來分別刪除儲存格或是一整列。table、row及cell物件有一些通用的屬性─例如align、vAlign及borderClor ─讓您來設定它們內容的格式。

DHTML網頁設計師
 

關於Visual Basic的大新聞就是您可以使用您喜好的語言來撰寫DHTML程式碼,感謝DHTML網頁設計師。就像其他的設計師,DHTML網頁設計師提供視覺部分(HTML網頁) 以及程式碼部分。當您編譯這類程式時,會產生一個在Internet Explorer 4.01或其後版本中執行的ActiveX DLL。能夠存取使用Visual Basic撰寫且編譯過的DLL有很多好處:

  1. 網頁的外觀與管理它的程式碼分開,這讓程式設計人員與網頁製作者能夠有更好的分工。
  2. 原始碼被保護:程式碼被包裝在DLL檔中,而無法在網頁中看到它的內容。
  3. 一般而言,被Visual Basic編譯過的程式會比使用VBScript或其他的腳本語言來得快。當您在編譯成原生碼時,作某些最佳化的校調,更可以看到速度的改進。
  4. 您不需猜測階層中每個物件的屬性及方法名稱,因為「自動完成程式碼」功能會幫助您。事件程序的語法也是同樣的, DHTML網頁設計師會為您建立它。
  5. DHTML網頁設計師與發展環境有很好的整合,因此您可以使用「屬性」視窗來更改任何元件的啟始屬性,而不用插入隱祕的HTML標記。

簡介DHTML網頁設計師
 

要引導您進入DHTML網頁設計師最快速的方法就是在 專案畫廊 中選擇 DHTML應用程式 範本。這個範本會建立一個DHTML網頁設計師的實體,以及一個標準的BAS模組,它包含了一些有用的常式。在一個典型的DHTML應用程式中,您將會建立數個DHTML網頁設計師,每一個都是構成您程式的一個DHTML網頁。

圖19-6顯示DHTML網頁設計師,在左邊的是 樹狀檢視 窗格,而在右邊的是 明細部份 窗格。這兩個窗格實際上代表網頁中兩個不同的部分:在 樹狀檢視 窗格中顯示一個HTML網頁裡面所有組成元件的分層結構,而在 明細部份 窗格中您可以查看(並可排列) 這些元件,就像是表單中的控制項一般。設計是不提供存取在網頁HTML背後程式碼,因此您不可以直接的增加腳本函式或是HTML標記。幸運地,您不再需要使用腳本語言因為您使用了Visual Basic,而您可以使用外部的編輯器先製作好HTML的網頁然後再匯入到設計師中。


 

圖19-6 DHTML網頁設計師

當設計師作用時,在工具箱視窗中會出現新的頁籤,它包含了您可以放在 明細部份 窗格上面的HTML控制項。所有目前為止我們所看到的控制項都會包含在工具箱中,除此之外還有一些新的控制項:hidden TextBox控制項、InputImage控制項及FileUpload控制項。為了簡化程式設計人員的工作,單行的Select控制項及多行(預設為可多選的)的List控制項分別使用兩個不同的圖示來代表,即使它們轉成的HTML標記都是一樣的。工具箱中也包含一些其他的元件,HorizontalRule元件(用來畫出水平線) 以及Hyperlink元件。您也可以選擇部分的文字,然後按下工具列中的 把選取範圍轉為超連結 按鈕來建立一個超連結。

此外,若您撰寫了一些像Web address格式的文字(例如www.microsoft.com),設計師會自動把它轉換成超連結。

如您所知,HTML網頁可以包含ActiveX控制項,而DHTML網頁設計師也支援這項能力。您可以在網頁中放進一個外部的ActiveX控制項,例如TreeView或是使用Visual Basic寫成的ActiveX控制項,它必須編譯成獨立的OCX檔案。您不可以使用內建的Visual Basic控制項,也不可使用目前專案的私有UserControl物件。

在DHTML網頁設計師最上面的工具列(如圖19-7所示)讓您可以更改文字或是目前所選擇元件的格式。在左邊數來的第二個combo box是所有目前網頁所定義的樣式,包含這個網頁所引用的外部串接樣式表(Cascading Style Sheet)。因為在DHTML網頁設計師中您不能定義樣式,這個combo box所包含元件,只能是由其他功能較強編輯器所製作的外部HTML網頁所匯入而來。


 

圖19-7 DHTML網頁設計師的工具列

使用DHTML網頁設計師的屬性按鈕,您可以決定是否要把建立的網頁儲存為目前專案的一部份,或是存成分開的HTM檔案。兩個選項各有其優點,但因為設計師無法比上其他功能強大的HTML編輯器,例如Microsoft FrontPage,筆者建議您選擇後者,如此便可使用外部的HTML編輯器來美化您的網頁。

 啟動編輯器 的按鈕可以讓您使用外部的編輯器來編輯目前的網頁。預設的編輯器為記事本,它雖然很難可以稱為HTML編輯器,但許多的HTML程式設計人員仍然使用它。您可以在Visual Basic整合發展環境中的 選項 對話方塊中的 進階 頁籤來定義比較好的編輯器。

只有當您把網頁存在外部的HTM檔案時,您才可以使用外部的編輯器來編輯它。當您按下這個按鈕時,Visual Basic會自動儲存最近更新的內容,並執行外部編輯器Visual Basic會持續檢查檔案的日期及時間,一旦您在編輯器中儲存網頁,Visual Basic會立刻詢問您是否要更新DHTML網頁設計師的內容。

就像其他的設計師,您可以在控制項上按下滑鼠按鈕(兩個窗格都一樣),然後按下F4按鍵來帶出屬性視窗。在DHTML網頁設計師中,您可以更改任何元件的特性,包含純文字。筆者建議您先建立一個空的HTML網頁,並且放入幾個工具箱中控制項的實體,然後按下F4來熟悉它們的屬性。在屬性視窗中,您可以根據設計師所使用名稱分類來讀取每個元件的型態。例如許多從工具箱拖來的控制項都是DispIHTMLInputElement 的型態,而根據它們的型態屬性更進一步分類 (可以是text、password、image等)。超連結元件的類別是DispHTMLAnchorElement。表格是DispHTMLTable類別,而它們包含的元件是DispHTMLTableCell類別。

說到表格,在建立及編輯表格時您有很多選項- 例如,您可以使用設計師工具列中的下拉式功能表,或是在 明細部份 窗格中在表格上按下右鍵。快顯式功能表包含了屬性命令,它會帶出如圖19-8的屬性對話方塊。在這個對話方塊中,您可以設定很多的特性,您也可以分割成多個的列或欄。請您也注意到在工具列上的按鈕可以讓您在設計時期顯示或是隱藏邊框,而不用去實際地更改Border特性。在設計時期讓表格顯示邊框,通常可以簡化您的編輯工作。


 

圖19-8 DispHTMLTable物件的屬性對話方塊

假使您在外部ActiveX控制項上按下滑鼠按鈕並且按下F4按鍵,您會得到支援HTML特性的列表,而不是該控制項平常的屬性。要編輯外部ActiveX控制項的內建屬性,您必須在它上面按下滑鼠右鍵,帶出屬性對話方塊。List及Select DHTML控制項同樣支援自訂的屬性對話方塊,因此您可以透過它來指定控制項包含的元件列表。

設計師可以使用兩種定位模式,相對的及絕對的。在相對模式,瀏覽器會在重新改變大小時自動改變網頁中所有元件的位置,就像其他非動態的HTML網頁一樣。而在絕對模式,元件會停在您所放下的地方。在工具列上,有兩個按鈕會改變定位模式:一個改變目前的模式,另一個會改變目前選擇元件的模式。在選擇文字元件時,不可使用後者,因為您只能按下Enter鍵增加空白列來更改文字元件的位置,就跟在文字處理器一樣。因為超連結本身就是文字元件,它也是有同樣的限制。其他所有的元件都可以使用滑鼠來移動,但您必須使用它們的邊框來移動。您可以使用設計師工具列上的 物件層次 子選單來更改網頁上元件的z-order位置。

在設計師內部設定文字屬性並不是最直覺的動作。事實上,<P>元件起初並不提供任何font或style屬性。要強迫它提供這些特性,您必須使用最上面的工具列來該變它的外觀- 舉例而言,更改字型大小。當您該改某段文字的標準外觀,在 樹狀檢視 的<P>元件中會有一個子<FONT>元件出現。您可以選擇這個新元件並且按下F4來顯示屬性視窗,接下來再更改其他的特性,例如color及face。

DHTML元件程式設計
 

要發揮DHTML的動態功能,您必須在網頁及其元件所發生的事件撰寫程式碼。在Visual Basic 6 DHTML應用程式中,您必須撰寫程式來對網頁及它的元件所。發生的事件作出反應,如同您在表單上控制項的程式碼。當您編譯這個應用程式,Visual Basic會建立一或多個HTM檔案以及一個包含DLL,您在事件程序中所撰寫的程式碼編譯好後就存在該DLL檔中。這個DLL將會被載入到Internet Explorer的位址空間中,並且捕捉瀏覽器的DHTML事件。

所有的Visual Basic 6 DHTML事件實際上都是ActiveX DLL應用程式,它們的執行緒模式是公寓模型執行緒。您不應該在單執行緒的ActiveX專案中使用DHTML網頁設計師。當您編譯應用程式時所產生的HTM檔案,它們所包函的OBJECT標記都會有一個對應DLL的引用。當使用者第一次瀏覽這個網頁,DLL會自動地從server下載,並且安裝到用戶端的系統中。就跟您在HTML網頁中使用ActiveX控制項時,也會先要下載,這個動作完全相同。

DHTMLPage物件
 

DHTMLPage物件代表在DLL中對應到一特定的HTM網頁的元件。就像所有的物件,它會在第一次使用這個網頁時發生Initialize事件,而在DLL被卸載前發生Terminate事件。它也具有另外兩個事件,那就是Load及Unload,它們會分別在網頁載入及卸載時發生。

DHTMLPage物件具有四個執行時期的屬性。SourceFile屬性是包含要被建立網頁原始碼的HTM檔案路徑,假使您不是使用外部編輯器來編輯這個網頁,那這個屬性會是空字串。BuildFile屬性是編譯過程中建立的HTM檔案路徑,它必須與DLL一起部署。(它會被初設為SourceFile屬性中所設定的路徑。) AsynchLoad屬性指定哪一個網頁要非同步的載入(請參閱本章稍後的 〈非同步地載入網頁〉 一節)。為id屬性指定值可以讓網頁可程式化。在屬性視窗中,您將會發現第五個屬性,Public,但您實際上並不會接觸到它,因為它被設定為True而且不可以被更改。(您不能擁有private的DHTMLPage物件。)

上述的屬性只能在設計時期使用。在執行時期,DHTMLPage提供另一組不同的屬性:BaseWindow、Document以及DHTMLEvent (圖19-9)。它們各別傳回一個引用給所有重要的DHTML Window、Document以及Event物件,所以它們連結了Visual Basic程式及動態HTML物件模式。請注意當您由DHTML網頁設計師中存取DHTML物件時,不能夠從HTML網頁中的腳本語言來存取設計師。網頁不會記得它是被DLL來處理的。


 

圖19-9 DHTMLPage物件的執行時期屬性。

在DHTML模組中,您可以直接在程式中引用DHTMLPage物件的屬性,就像您可在表單程式碼模組中也可以使用表單屬性一樣。底下的程式說明了這個概念:

' (This code must run inside a DHTMLPage code module.)
' Change the background color of the page.
Document.bgcolor = "red"
' Retrieve the state of the Alt key inside an event procedure.
If DHTMLEvent.altKey Then...

id屬性
 

並不是所有的網頁元件都可以結合事件程序。為了要可程式化,網頁元件必須要有一個存有值的id屬性。這個id就是您在程式中要使用元件時,元件的名稱。這個需求可能會誤導Visual Basic程式設計人員,因為許多的HTML元件也提供一個Name屬性,它通常在純HTML程式寫作中沒什麼意義。就像筆者在本章開頭的HTML教學中所解釋的,Name屬性幾乎用在會互相排斥的Option控制項。在DHTML應用程式中,即使在同一Option控制項群組中的每個元件,都需要不同的id值(假使您想要分別參考它們)。

在標準的DHTML下,不同控制項不一定要有不同的id屬性。然而在Visual Basic撰寫的DHTML應用程式中,網頁中所有的id都必須是唯一的(unique)。當您將已存在的.hml檔案匯入到設計師時,Visual Basic會檢查所有的id值,若有必要,它會自動地在它們後面增加一個數字,來確保網頁中的值都是唯一的。所以當您要匯入一個HTM網頁時,請記得再檢查每個元件的id。

不是網頁中所有的元件都必須要有id屬性。大多數的情況是沒有:只有那些您要撰寫程式的元件才必須為它們指定id。在最左邊窗格的 樹狀檢視 中,以黑體字來顯示這種可程式化的元件。設計是會自動為每個您從工具箱拉來的元件及控制項指定id。要為某個元件指定id,請先在 樹狀檢視 中選擇它,切換到屬性視窗,然後為id屬性指定一個唯一值。您也可以在屬性視窗上面的combo box控制項中選擇網頁元件( 使用這個方式來更改DHTMLPage物件本身的屬性)。此外,您也可以透過All屬性或是Document物件其他集合來存取網頁元件的屬性及方法。

第一個範例: 動態的功能表
 

要讓您看看,學習DHTML程式設計所能發揮的能力,讓我們來建立一個特殊的範例:一個由許多項目所組成的動態功能表,它的項目會在您點選功能表標題時顯示或者消失,而當您的滑鼠移到它們上面時會以粗體來顯示。

剛開始,我們建立一個新的DHTML網頁,存到HTM檔案中,並且增加一些圖19-10所顯示的段落。設定這些段落的id屬性分別為MainMenu、MenuItem1、MenuItem2及MenuItem3。您可以在工具列中先更改這段落的字型大小,此時設計師會幫您建立<FONT>項目,然後您可以更改它來更換段落顏色。


 

圖19-10 第一個DHTML應用程式:動態功能表。

現在您可以在這些項目中撰寫程式碼。為網頁元件撰寫程式碼就像在一般表單中撰寫控制項程式碼一樣:在元件上雙點選(在 樹狀檢視 窗格中)來存取程式碼視窗,然後在右邊的combo box中選擇事件程序。您也可以使用F7功能鍵,或是在設計師視窗中的元件上按下滑鼠右鍵,選擇功能表中的 檢視程式碼 命令來叫出程式碼視窗。底下是您應該在程式模組中輸入的程式。(您也可以由隨書光碟中載入範例應用程式。)

Private Sub DHTMLPage_Load()
    ' Make the submenu choices invisible when the page loads.
    SetVisibility False
End Sub
' Change the display attribute of all the menu items.
Private Sub SetVisibility(newValue As Boolean)
    MenuItem1.Style.display = IIf(newValue, "", "none")
    MenuItem2.Style.display = IIf(newValue, "", "none")
    MenuItem3.Style.display = IIf(newValue, "", "none")
End Sub
' When the MainMenu paragraph is clicked,
' switch menu items from hidden to visible and back.
Private Function MainMenu_onclick() As Boolean
    If MenuItem1.Style.visibility = "hidden" Then
        SetVisibility True
    Else
        SetVisibility False
    End If
End Function
' Change the boldface attribute of the element under the mouse, but only
' if this element is one of the three MenuItem  paragraphs.
Private Sub Document_onmouseover()
    Select Case DHTMLEvent.srcElement.innerText
        Case "Click here", "Acknowledgments", "Table of contents", _
            "Appendix"
            DHTMLEvent.srcElement.Style.fontWeight = "800"
    End Select
End Sub
' Restore the original font attribute when the mouse leaves the element.
Private Sub Document_onmouseout()
    Select Case DHTMLEvent.srcElement.innerText
        Case "Click here", "Acknowledgments", "Table of contents", _
            "Appendix"
            DHTMLEvent.srcElement.Style.fontWeight = ""
    End Select
End Sub

極為容易地,上面的程式是一個告訴您如何使用DHTML特性的好例子。因為所有的選單項目行為都是一樣的,所以在它們的onmouseover及onmouseout事件程序中不需使用重覆的程式碼。事實上,使用動態DHTML的事件沸升特性以及在Document層次中捕捉事件會比較好。這是在一般的Visual Basic表單中所無法達到的。

然而,這個方法也不是沒有缺點,因為您要確定onmouseover及onmouseout事件要被所感興趣的四個<P>元件所產生,而不是網頁上其他的元件。Event物件提供一個srcElement屬性,它會傳回事件來源物件的引用。問題發生在於:您要如何確定這個物件是否為組成功能表四個<P>項目之一?起初筆者相信可以將這四個項目的id屬性與傳回DHTMLEvent.srcElement.id屬性作比較,但是令筆者驚訝的,筆者發現後面的屬性總是傳回一個空字串,因此不能用在此處。幸運地,您可以使用innerText屬性來解決這個問題。假使在網頁上有多個的元件具有相同的innerText屬性值,您可以指定一個不重複的Name給它們,然後使用這個數性來找出哪一個元件產生這個事件。

使用DIV及SPAN標記
 

大多數的情況下,您不需要依靠這個以id、innerText或其他屬性為基礎的不常用技巧來找出您感興趣的事件,因為在Dynamic HTML中您可以建立只有包含您感興趣項目的文件區域,來精確的界定出事件沸升程序的範圍。假如您沒有收納器來放置那些您想要由它們接收事件的元件,您可以使用<DIV>及</DIV>這組標記將您感興趣的元件分組。

以動態功能表範例而言,您必須建立一個DIV節區來存放四個功能表項目。這非常簡單:在DHTML網頁設計師右邊的窗格中,先選擇這四個段落然後按下工具列中第二列左邊數來第三個按鈕。這個動作會建立一個DIV節區,但您必須為它指定一個非空的id屬性,讓它可程式化。所以在屬性視窗中打入DynMenu,然後到程式碼視窗中鍵入底下的程式:

Private Sub DynMenu_onmouseover()
    DHTMLEvent.srcElement.Style.fontWeight = "800"
End Sub
Private Sub DynMenu_onmouseout()
    DHTMLEvent.srcElement.Style.fontWeight = ""
End Sub

如您所見,您不需要檢查srcElement.innerText屬性,因為您已經確定事件是從這四個<P>項目所發生。

為了練習,讓我們來看看您如何使用<SPAN>標記,當要參考到HTML網頁某一小部份時,它非常有用。假設您想要在功能表開啟時更改MainMenu元件的文字為" Click here to close the menu",先選擇後面四個單字,然後按下設計師工具列的第四個按鈕把這個小部份轉為<SPAN>節區。要在程式碼中參考這個節區,您必須為<SPAN>物件指定一個id (例如,CloseMenu) 然後將程式更改如下。(增加的陳述式以粗體來表示。)

Private Function MainMenu_onclick() As Boolean
    If MenuItem1.Style.visibility = "hidden" Then
        SetVisibility True
        MenuClose.Style.visibility ="visible"
    Else
        SetVisibility False
        MenuClose.Style.visibility = "hidden"
    End If
End Function

如您所見,Dynamic HTML以小量的程式達到引人注目的效果。

DHTML事件程序
 

假使您仔細觀察範例程式,您會注意到有很多(不是全部)事件常式是函數,而不是程序。如筆者在之前〈取消預設效果〉小節中所解釋的,所有DHTML事件需要一個傳回值,假使為False,就會取消該事件的預設動作。要傳回一個值,事件程序必須宣告為函數。

在DHTML網頁設計師中,事件傳回值的方式與在HTM檔案中腳本語言常式中所用的技巧有所不同。在VBScript,您必須明確地將程序傳回的值設定為False來取消某個事件的預設動作,或者您也可將event.returnValue屬性設為False來達到同樣的結果。然而在VisualBasic中,False是任何函數的預設值,DHTML事件也不例外。換句話說,假使您撰寫一個事件程序,如果您不想取消預設動作,您必須明確地將它的傳回值設為True。

筆者用一個示範來解說這個概念,假設您有一個超連結,而您想要讓使用者瀏覽該URL時先作要求確認。底下是您必須在Hyperlink物件的onclick事件程序中撰寫的程式:

Private Function Hyperlink1_onclick() As Boolean
    If MsgBox("Do you really want to jump there?", vbYesNo) = vbYes Then
        Hyperlink1_onclick = True
    End If
End Function

說明

將DHTMLEvent.returnValue屬性設為True並沒有作用。


MSHTML程式庫
 

所有的DHTML應用程式都包含一個對MSHTML類別程式庫的引用,該程式庫包含所有組成Dynamic HTML物件模型的物件。您可能需要花一點時間來認識這個龐大的程式庫─Internet Explorer 5附的版本包含大約280個類別及介面!

它的元件名稱可能也與您想像的有所不同。例如,Window物件對應到HTMLWindow2類別,Document物件由HTMLDocument類別所衍生、Event物件是CeventObj的類別等等。筆者沒有足夠的頁面來為您介紹所有的類別,以及它們的屬性、方法及事件,所以筆者只能建議您花點時間利用物件瀏覽器來看看每個物件有關的特性。

DHTML應用程式
 

當寫作Visual Basic 6 DHTML應用程式時,您必須解決一個新的類別問題。在本節中筆者將為您稍微描述它們。

瀏覽其他的網頁
 

您可以在網頁上放置一或多個的超連結,來讓使用者輕易的瀏覽其他的網頁,此時要注意在它們onclick事件程序中避免傳回False。然而,假使您以動態的方式來建立目標URL,便不能在設計時期為<HREF>標記中指定超連結,此時您必須使用下面其中一個方式:

  • 您可以使用Window物件的Navigate方法來取得使用DHTMLPage全域物件BaseWindow屬性的物件引用。底下是您必須執行的程式碼:

     
    ' Note: This is an absolute URL.
    BaseWindow.Navigate http://www.vb2themax.com
  • 假使您在Hyperlink物件中的onclick事件程序中,您可以更改物件的href屬性,並確定事件程序傳回True:

     
    Private Function Hyperlink1_onclick() As Boolean
        ' This code assumes that the global InternetIsUnavailable
        ' variable has been set to True if you're connected to the Internet
        ' and False if you're navigating on your private intranet.
        If InternetIsUnavailable Then
            Hyperlink1.href = "localpage.htm"
        End If
        ' In all cases, you need to return True to enable the jump.
        Hyperlink1_onclick = True
    End Function
  • 最後,您可以在DHTML應用程式中使用下面的語法來引導使用者到其他網頁:

     
    BaseWindow.Navigate "DHTMLPage2.htm"

它的引數是您儲存目標DHTML網頁的HTM檔案名稱。(上面的程式假設組成應用程式的所有HTM檔案都存放在Web server上相同目錄中。)

不論您選擇哪種方式,您必須注意如何使用相對路徑或是絕對路徑。通常,在您應用程式所有對其他網頁的URL-不管它們是否與DHTMLPage設計師有關-應該都是相對的,因此您可以很容易地部署您的應用程式到新的Web site,而不需重新編譯原始碼。相反地,所有指到不是在您網站上網頁的URL,都必須以http://為開頭。

非同步載入網頁
 

當DHTML網頁第一次再程式中引用到時,會發生Initialize事件。您可以使用這個元件來專門啟始區域性的變數。因為既然所有網頁的元件都還未被建立起來,假使您引用它們,就會發生錯誤。

在預設中,當網頁完全地由Web server下載時,DHTMLPage物件會啟動。此時,這個物件會產生Load事件。因為此時所有的元件都已經存在了,所以引用它們不會發生任何問題。然而,使用這個簡單方法的問題在於,一旦下載一個非常複雜的網頁(例如很大的圖檔),要完全下載完畢會花費很多時間。除非網頁被完全地下載,使用者會無法有任何動作,因為在網頁上的元件不會對使用者的動作產生反應。

您可以設定DHTMLPage物件的AsynLoad屬性為True,來啟動非同步的下載。在這個情況下,當下載過程開始時就會發生Load事件,此時網頁上所有的元件還未完全下載完畢。這代表您可以在元件還沒下載前就可以引用它,這將會造成錯誤。當您使用非同步下載的功能時,可以使用底下的技巧:

  • 一般來說,您不應該應該在事件程序中因用到任何物件,除非這個事件是由該物件發生的。您甚至不能引用在在網頁上發生事件物件之前出現的物件,因為瀏覽器是亂數選擇載入的元件。
     
  • 假使您的應用程式邏輯必須使用到其他的物件,請在程式中加入On Error陳述式來防止意料之外的錯誤。
     
  • 直到Document的readyState屬性傳回complete值之前,不要存取任何物件(除了發生該事件的物件)您可以在存取任何物件之前先測試這個屬性,或者您可以等到Document的onreadystatechange事件時,藉時再來檢查這個屬性。
     

大多數的時間中,您必須混合使用這三個技巧。例如,當網頁是被非同步載入時,不要在DHTMLPage_Load執行關鍵性的程式,而使用Document_onreadystatechange事件來取代:

Private Sub Document_onreadystatechange()
If Document.readyState = "complete" Then
        ' Here you can safely access all the elements in the page.
    End If
End Sub

假使您沒有辦法等待onreadystatechange事件,那您就必須保護您的程式,使它們不會在當使用者存取不存在的物件時所產生意料之外的錯誤,或者,您也可以使用下面的常式:

' A reusable function that checks whether an element is available
Function IsAvailable(ByVal id As String) As Boolean
    On Error Resume Next
    id = Document.All(id).id
    IsAvailable = (Err = 0)
End Function

舉例來說,直到功能表項目準備好前,我們應該忽略任何在MainMenu元件上點選的動作:

Private Function MainMenu_onclick() As Boolean
    If Not IsAvailable("MenuItem1") Then Exit Function
    ' Don't execute this code if the menu items aren't available yet.
    ...
End Function

狀態管理
 

DHTML應用程式與一般的Visual Basic應用程式有一個很大的不同點:因為使用者可以自由的在網頁間瀏覽-包含那些您不提供超連結的網頁-您無法確定每個網頁被瀏覽的順序。這個情況跟Visual Basic程式模型成對比,Visual Basic可以讓您來決定每個表單該在什麼時候顯示。

在DHTML與Visual Basic應用程式另一個不同關鍵在於Internet應用程式是非狀態(stateless)的,HTTP協定並無法在不同的請求間儲存資訊;假使需要的話, 必須完全依賴您來維持狀態。您可以使用DHTML應用程式範本專案中所包含的modDHTML.Bas模組中PutProperty及GetProperty常式來達成。底下是這兩個常式去掉一些說明列後的原始碼:

Sub PutProperty(objDocument As HTMLDocument, strName As String, _
    vntValue As Variant, Optional Expires As Date)
    objDocument.cookie = strName & "=" & CStr(vntValue) & _
        IIf(CLng(Expires) = 0, "","; expires=" & _
        Format(CStr(Expires), "ddd,dd-mmm-yy hh:mm:ss") & " GMT") 
End Sub
Function GetProperty(objDocument As HTMLDocument,strName As String) _
As Variant
    Dim aryCookies() As String
    Dim strCookie As Variant
    On Local Error GoTo NextCookie
    ' Split the document cookie object into an array of cookies.
    aryCookies = Split(objDocument.cookie, ";")
    For Each strCookie In aryCookies
        If Trim(VBA.Left(strCookie, InStr(strCookie, "=") - 1)) = _
            Trim(strName) Then
            GetProperty = Trim(Mid(strCookie, InStr(strCookie, "=") + 1))
            Exit Function
        End If
NextCookie:
        Err = 0
    Next strCookie
End Function

如您所見,兩個常式都只是對Document物件cookie屬性的一個介面,所以為了某些目的(例如列舉出所有定義過的cookies),您可以在程式中直接存取這個屬性。要把這些值永久保存下來,需呼叫PutProperty常式:

' Store the name of the user in the "UserName" cookie.
PutProperty Document, "UserName", txtUserName.Value

您也可以設定cookie的到期期限,例如:

' The user password is valid for one week.
PutProperty Document, "UserPwd", txtPassword.Value, Now() + 7

假如您沒有為cookie設定到期期限,則它會在瀏覽器被關閉,Session結束時自動被刪除。您可以使用GetProperty函數來取得cookie:

' This returns an empty string if the cookie doesn't exist.
txtUserName.Value = GetProperty(Document, "UserName")

在Visual Basic光碟上的PropBag.vpb範例程式會示範如何在專案中使用這兩個常式在兩個網頁間傳送資料。


說明

當您在安裝Internet Explorer 5的系統上執行PropBag.vbp範例撰案時會產生錯誤。這個錯誤是因為瀏覽器物件模型的少許相異而產生的。您可以在程式中將WindowBase.Documen以t呼叫PutProperty。及 GetProperty常式的Document來替代。筆者正在測試較新beta版本的Internet Explorer 5,所以在官方正式版本公佈時可能這個錯誤會消失。


典型地,我們會在Unload事件中來儲存網頁的狀態(state)。不要等到Terminate事件,因為當這個事件發生時,網頁已經被摧毀了,而您再也不能引用到它的元件。這就跟Initialize事件的情況相似。

最後一點必須注意的是:PropBag.vbp範例應用程式可能會讓您誤以為在DHTML應用程式中,每當兩個網頁傳送資料時都需要使用cookie,但這並不是必須的。事實上,當您直接呼叫您應用程式中其他網頁時-使用本章之前〈 Navigating to Other Pages〉小節中所使用的方法之一─您只需要在ActiveX DLL專案中使用全域變數來儲存這些值。您真正需要使用到cookie (在modDHTML.Bas模組中透過常式直接或是非直接使用) 只有當您希望這些值能在非直接呼叫的其他網頁使用,或是您希望這個值能在持續的sessions中保存。(在後面的情況,您必須為PutProperty常式中的Expires引數指定一個適當的值。)

建立元件
 

當您在Visual Basic撰寫程式時,您不應該忘記您可以使用所有Dynamic HTML能發揮的威力。為了讓您更瞭解在同一應用程式中,同時使用Visual Basic及DHTML所能夠做到的,筆者將為您展示如何使用Visual Basic去查詢ADO資料來源,然後在瀏覽器中使用許多能更改已經載入到瀏覽器內容的HTML方法,來動態建立結果表格。(請參閱本章節之前 〈文字屬性及方法〉 小節)。

當您計畫在執行時期填入網頁某部分時,例如,填入資料庫查詢結果,您必須在適當的地方放上<DIV> 節區。這個節區必須使用有值的id屬性,如此您便可以在程式中引用它。圖19-11顯示一個典型的搜尋頁,它使用兩個TextBox控制項讓使用者輸入搜尋條件,以及一個Search按鈕來開始查詢。


 

圖19-11 簡單的搜尋網頁。

在按鈕後面接著一個空的<DIV>節區(所以看不見) ,它的id為divResults。當使用者按下這個按鈕時,Visual Basic程式執行查詢並建立一個ADO Recordset:

' Edit this constant to match your directory structure.
Const DB_PATH = "C:\Program Files\Microsoft Visual Studio\Vb98\Biblio.mdb"

Private Function cmdSearch_onclick() As Boolean
    Dim rs As New ADODB.Recordset
    Dim conn As String, sql As String
    Dim AuthorSearch As String, TitleSearch As String
    Dim resText As String, recIsOK As Boolean, recCount As Long

    On Error GoTo Error_Handler
    ' Prepare the query string.
    AuthorSearch = txtAuthor.Value
    TitleSearch = txtTitle.Value
    sql = "SELECT Author, Title, [Year Published] AS Year FROM Titles " _
        & "INNER JOIN ([Title Author] INNER JOIN Authors " _
        & "ON [Title Author].Au_ID = Authors.Au_ID) " _
        & "ON Titles.ISBN = [Title Author].ISBN"
    ' You can filter author names right in the SQL query string.
    If Len(AuthorSearch) Then
        sql = sql & " WHERE Author LIKE '" & AuthorSearch & "%'"
    End If
    ' Open the Recordset.
    conn = "Provider=Microsoft.Jet.OLEDB.3.51;Data Source=" & DB_PATH
    rs.Open sql, conn, adOpenStatic, adLockReadOnly

此時,您開始建立一個表格,它會有一個標題列來顯示欄位的名稱:

' Prepare the header of the table.
    resText = "<TABLE BORDER>" _
        & "<TR ALIGN=left>" _
        & "<TH WIDTH=150>Author</TH>" _
        & "<TH WIDTH=300>Title</TH>" _
        & "<TH WIDTH=80>Year</TH>" _
        & "</TR>" & vbCrLf

您可以使用迴圈來過濾出那些沒有包含在Title欄位指定字串的紀錄(假使使用者實際上在txtTitle控制項中輸入一些文字)。每一筆符合條件的紀錄,會使用下面的程式在表格中加入一列:

Do Until rs.EOF
        recIsOK = True
        ' Filter out unwanted records.
        If Len(TitleSearch) Then
            If InStr(1, rs("Title"), TitleSearch, vbTextCompare) = 0 Then 
                recIsOK = False
            End If
        End If
        ' If the record meets the search criteria, add it to the page.
        If recIsOK Then
            recCount = recCount + 1
            resText = resText & "<TR>" _
                & "<TD>" & rs("Author") & "</TD>" _
                & "<TD>" & rs("Title") & "</TD>" _
                & "<TD>" & rs("Year") & "</TD>" _
                & "</TR>" & vbCrLf
        End If
        rs.MoveNext
    Loop
    rs.Close

當Recordset完全處理完後,您必須簡單地加入</TABLE>標記,並且準備一個訊息來告知共找到多少筆紀錄。底下是這個常式剩下的部分:

If recCount = 0 Then
        ' If no record matched the search criteria, drop the table.
        resText = "<I>No record matches the search criteria</I>"

    Else
        ' Otherwise add the number of found records and complete the table.
        resText = "Found " & recCount & IIf(recCount = 1, _
            " record", " records") & ".<P>" & vbCrLf & resText _
            & "</TABLE>" & vbCrLf
    End If
    ' Substitute the current contents of the divResults section.
    divResults.innerHTML = resText
    Exit Function
    
Error_Handler:
    MsgBox "Error #" & Err.Number & vbCr & Err.Description, vbCritical
End Function

圖19-12顯示這個程式執行成功查詢後的結果。您可以使用數不盡的方式來使第一版更完善 - 舉例而言您可以為傳回的筆數加入最大值的限制,然後使用Next及Previous按鈕來讓使用者在不同的結果網頁中瀏覽。(這裡有點建議:只有當需要時才在網頁上顯示Next或是Previous按鈕。)


 

圖19-12 一個成功的資料庫查詢結果。

當您要動態的新增控制項時(而不是純文字元件),有一個問題必須解決,那就是如何在程式中引用它們,並且捕捉它們的事件。為了示範這點,筆者將告訴您如何在結果表格中,在每個元件的右邊加入兩個控制項:一個CheckBox控制項讓使用者為這個訂單加入指定的書名,以及一個Button控制項讓使用者查詢更詳細的資料,例如封面的圖片、內容的列表等。

要在程式建立表格時動態見控制項並不困難,事實上您只要確定每個新的控制項都指定一個唯一的(unique) id屬性,稍後將會使用這個id值來引用該控制項。底下的程式會把每個符合條件的紀錄加到表格列中( 增加的列用粗體字代表):

recCount = recCount + 1
bookmarks(recCount) = rs.Bookmark
resText = resText & "<TR>" _
    & "<TD>" & rs("Author") & "</TD>" _
    & "<TD>" & rs("Title") & "</TD>" _
    & "<TD>" & rs("Year") & "</TD>" _
    & "<TD><INPUT TYPE=BUTTON ID=cmdDetails" & Trim$(recCount) _
    & " VALUE=""Details""></TD>" _
    & "<TD><INPUT TYPE=Checkbox ID=Buy" & Trim$(recCount) _
    & " NAME=Buy?></TD>" _
    & "</TR>" & vbCrLf

bookmarks陣列儲存所有符合條件紀錄的書籤;它被定義為模組階段(module-level)的變數,所以它可以由任何在DHTMLPage模組中的常式來存取。

接下的步驟是捕捉Detail按鈕的onclick事件,剛開始看似不可能達成,因為它是被動態地產生而且在DHTML網頁設計師中並沒有它們的程式碼。很幸運地,我們要感謝事件沸升功能,您只要捕捉Document物件的onclick事件,然後檢查是不是由您動態產生的控制項所引發的就可解決:

Private Function Document_onclick() As Boolean
    Dim index As Long, text As String
    ' Not all the elements support the Name or ID property.
    On Error GoTo Error_Handler
    ' Check the ID of the element that fired the event.
    If InStr(DHTMLEvent.srcElement.id, "cmdDetails") = 1 Then
        ' Retrieve the index of the button.
        index = CLng(Mid$(DHTMLEvent.srcElement.id, 11))
        ' Move the Recordset's pointer to that element.
        rs.Bookmark = bookmarks(index)
        ' Show the title of the selected book. (This is just a demo!)
        MsgBox "You requested details for title " & rs("Title")
    Else
        ' Return True to enable the default action of Checkbox controls.
        Document_onclick = True
    End If
End Function

請注意這裡如何測試onclick事件是否由Detail按鈕所產生,並且要如何取得該控制項的索引。

您接下來的工作就是準備所有被選擇訂購的書籍清單,這可以由下面的程式片段來達成:

Dim text As String
For index = 1 To UBound(bookmarks)
    If Document.All("Buy" & Trim$(index)).Checked Then
        rs.Bookmark = bookmarks(index)
        text = text & rs("Title") & vbCr
    End If
Next
If Len(text) Then
    text = "Confirm the order for the following title(s)" & vbCr & text
    If MsgBox(text, vbYesNo + vbExclamation) = vbYes Then
        ' In a real application, you would insert the code that processes
        ' the order right here.
        MsgBox "Order filed!", vbInformation
    Else
        MsgBox "Order canceled!", vbCritical
    End If
End If

要獲得更多的資訊,請參閱隨書光碟中的範例應用程式。這個專案包含兩個不同的DHTMLPage模組:一個用來做簡單的搜尋,另一個用來建立較複雜的網頁,它是由Button及CheckBox控制項組成的表格。


 

圖19-13 一個動態建立自己控制項的DHTML網頁。

測試DHTML應用程式
 

DHTML應用程式的好處就是您可以在整合發展環境中測試您的程式,這些工具使得偵錯Visual Basic應用程式變成簡單的工作。您可能因為習慣了這些除錯工作而忽略了一些重要的細節:您正在發展環境中執行您的DHTML應用程式,但是Internet Explorer的行為就像是您已經把程式編譯成一個在Internet Explorer的位置空間執行的ActiveX DLL。這個神奇的小魔術是由VB6Debug DLL來達成的,它可以在Visual Basic安裝路徑中找到。請小心不要刪除它,否則您將不能做類似跨行程(cross-process)的除錯。

當您要測試DHTML應用程式時,您可以使用所有在 專案屬性 對話方塊中 偵錯 頁籤的選項,如圖19-14。這個頁籤是Visual Basic 6所新增的,它在標準的EXE專案中是不可使用的,因為它只在打算發展給client程式使用的ActiveX元件時有用 (例如Internet explorer)。

這些提供的選項(筆者將做簡短的介紹) 大幅地簡化測試這類型元件的工作,因為它自動開始要使用這個元件的client應用程式。當目前專案在發展環境中執行時,您可以有四個動作來選擇:

 等待元件建立完成 這是預設動作:Visual Basic整合發展環境會等待client要求COM子系統建立這個元件。

 啟動元件 啟動在目前專案中定義的一個元件,並且讓它決定該做些什麼。DHTML網頁設計師預設的行為是載入HTM原始檔到Internet Explorer,如此一來,元件就會稍後自動啟動。假使您選擇UserControl或是UserDocument,Visual Basic會建立一個含有這些元件引用的暫時HTM網頁,然後把它載入到瀏覽器中;這個選項可以讓您測試控制項在瀏覽器中的行為。在這個combo box中您所選擇的元件並不與您在 一般 頁籤指定的啟動物件相衝突。例如,您可以選擇一個DHTML網頁設計師當作啟動物件,並且仍然可以在實體建立時自動執行Sub Main程序。

 啟動程式 這個選項讓您指定當您的專案執行時所要啟動的執行檔路徑。當您知道所選擇的程式會建立這個元件實體時,請選擇這個選項。例如您可以在Visual Basic中建立另一個應用程式來建立正在發展的元件實體。

 以下面的URL啟動瀏覽器 您可以啟動預設的瀏覽器並且讓它載入一個HTML網頁。這個選項讓您能夠測試已存在HTM網頁中的ActiveX控制項或是DLL引用。它相對於當您選擇 啟動元件 時,Visual Basic會自動地建立一個空白的網頁。

這一頁中也包含一個check box讓您決定是否要使用已經存在的瀏覽器(假使已經有一份實體在執行),或是要在每次執行專案時另外建立一份新的實體。

要讓Internet explorer自動建立在整合發展環境中開發的ActiveX DLL實體,Visual Basic會在HTM網頁開頭加入一個<OBJECT>標記,它會含有所有在DHTML網頁設計師中定義的元件:

<OBJECT 
ID="DHTMLPage"_ CLASSID="clsid:8F0A368F-C5BC-11D2-BAC5-0080C8F21830" 
WIDTH=0 HEIGHT=0></OBJECT>


 

圖19-14 「專案屬性」對話方塊中的「偵錯」頁籤。

部署DHTML應用程式
 

一旦您已經完全測試好您的DHTML應用程式,您必須為它準備一個可散發的封裝。這個封裝由下面的元件所組成:

您可以使用「封裝暨部署精靈」來建立可散發的封裝,它可以由Visual Basic增益集或是以直接執行來啟動。底下是您應該執行的步驟:

  1.  封裝暨部署精靈 最上面的欄位中,選擇DHTML專案,並且按下 封裝 按鈕。假使精靈發現DLL檔案比其他原始程式碼檔案來得舊,將會詢問您是否要重新編譯這個專案。
  2. 在封裝類型頁面,選擇Internet封裝,並按下 下一步 
  3. 在封裝資料夾頁面,輸入您想要精靈放置所產生散發封裝的路徑。
  4. 在包含的檔案頁面,您將會看到組成這個應用程式的檔案清單,包含Visual Basic及OSE Automation程式庫,但是並不包含.htm檔案及應用程式所需的資料檔案。
  5. 在檔案來源頁面(如圖19-15),指定一個用來下載每個檔案的網站。在預設下,所有Visual Basic、ADO及其他的系統檔會從Microsoft Web網站下載,這通常是最好的選擇。

     

    圖19-15 「封裝暨部署精靈」的檔案來源頁面。
  6. 在安全性設定頁面,您決定在DLL的元件是否為Script安全性以及起始化安全性 (要獲得更多關於這方面的說明,請參閱第十七章的 〈下載元件〉 小節)。
  7. 在這個精靈最後的頁面,您可以為目前的腳本指定一個名稱,它可以讓您在以後使用同樣的設定對專案進行再次包裝。

這個精靈會建立一個新的目錄用來放置CAB檔案(它包含DLL) 以及屬於您應用程式所有的HTM檔案。您現在所需要的是將這些檔案部署到Web server上,您可以使用「封裝暨部署精靈」來達成這項工作:

  1. 按下 部署 按鈕,並且選擇您在上面步驟七所輸入的腳本名稱。
  2. 在部署方法頁面,選擇 Web發行 
  3. 在部署項目頁面,選擇要被部署的檔案。在您第一次執行這個精靈時,通常會部署除了那些在Microsoft Web site上外的所有檔案,但是在部署作業時,您可以省略在這一段時間裡未更動過的檔案。
  4. 在其他部署項目頁面,您可以選取想要部署的附加檔案或資料夾。在這裡您選擇所有相關的檔案, 例如圖片檔、資料檔、WAV檔..等等。
  5. 在Web發行網站頁面(如圖19-16),您必須指定要將包裝傳送到的Web網站的URL (例如www.yoursite.com)。您也需要輸入所使用的Web發行通訊協定(FTP或是HTTP Post)。若您需要在部署完畢後解開CAB檔案,請選擇 展開並安裝伺服端封包檔 。當您按下 下一步 時,精靈會詢問您是否要將關於這個網站的資訊儲存到登錄資料庫中。

     

    圖19-16 「封裝暨部署精靈」的Web發行網站頁面
  6. 在精靈最後的頁面中,您可以為這個部署腳本取個名稱,然後按下完成按鈕來結束部署作業。

當部署完成後,從您的系統移除ActiveX DLL,並且使用您的瀏覽器來瀏覽應用程式的主HTM網頁。此時瀏覽器應該下載CAB檔案,安裝DLL,並且啟動您編譯過的DHTML應用程式。瀏覽器知道DLL要從哪一個網站下載,因為 封裝暨部署精靈 會為所有在HTM網頁中的<OBJECT>標記補上CODEBASE屬性。(精靈加入的文字以粗體來顯示。)

<OBJECT CODEBASE=Search.CAB#Version1,0,0,0 
ID="DHTMLPage2" CLASSID="clsid:8F0A368F-C5BC-11D2-BAC5-0080C8F21830" 
WIDTH=0 HEIGHT=0></OBJECT>

正如您在上面HTML片段中所見, 封裝暨部署精靈 產生不正確的CODEBASE屬性;版本號碼應該是使用等號作為前導字元。您可以手動來編輯它,就像是:

<OBJECT CODEBASE=Search.CAB#Version=1,0,0,0

疑難解決
 

筆者以一些能建立出更好的DHTML應用程式小技巧來作為本節的結尾:

遠端資料服務
 

在之前的範例中,筆者為您說明了DHTML應用程式如何使用ADO去執行對MDB資料庫的查詢,並且在HTML網頁中以表格來顯示結果。然而,當建立一個真實世界的Internet應用程式時,您明顯地不能使用在範例中的方法,因為資料庫並不在本地且您沒有它的路徑。

另外一個您必須解決的是,client是以Web瀏覽器使用非狀態(stateless)HTTP協定來溝通,這代表在瀏覽器連續的請求間,無法保持資訊。這與使用ADO方式產生明確的對比,ADO需要client一直與資料來源連線,從登錄一直到連結關閉。ADO物件中比較接近非連線狀態概念的就是disconected的Recordsets,它透過optimistic batch updates來更新資料。

您如何讀寫在遠端Web server的資料庫呢?遠端資料服務(Remote Data Services,簡稱RDS)提供這個問題的答案。您可以使用下面兩種方法之一:使用bound DHTML控制項或是「純」的ADO程式。

DHTML資料連結
 

要在HTML網頁上顯示資料來源資料最簡單的方式是在網頁上放置一個RDS.DataControl物件,並讓一或多的控制項跟它連結。這個方式概念上與一般Visual Basic表單上的資料連結控制項一樣,但是實際動作卻不相同。

建立RDS.DataControl物件
 

首先您必須要做的是在HTML網頁上增加一個RDS.DataControl。這個物件是一個由RDS程式庫提供的ActiveX元件,而您可以在網頁中使用下面的<OBJECT>標記將它放置到網頁中:

<OBJECT CLASSID=clsid:BD96C556-65A3-11D0-983A-00C04FC29E33 
    ID=dcPublishers HEIGHT=1 WIDTH=1>
    <PARAM NAME="Server" VALUE="http://www.yourserver.com">
    <PARAM NAME="Connect" VALUE="DSN=Pubs">
    <PARAM NAME="SQL" VALUE="SELECT * FROM Publishers">
</OBJECT>

您至少必須設定三個RDS.DataControl物件的屬性:Server屬性是存放資料來源的server URL,而Connect屬性指出server上哪一個資料來源,SQL則是查詢字串。您也可以動態的建立RDS.DataControl,它會用在當網頁已經被載入,而您要在執行時期指定這些屬性時。您可以在Window_onload事件中或是在任何VBscript程序外面使用純VBScript程式來動態地建立RDS.DataControl物件:

<SCRIPT LANGUAGE=lIVBScriptl 
' This code executes when the page loads.
Dim dcPublishers
Set dcPublishers = CreateObject("RDS.DataControl")
dcPublishers.Server = "http://www.yourserver.com"
dcPublishers.Connect = "DSN=Pubs"
dcCustomer.SQL = "SELECT * From Publishers"
dcCustomer.Refresh
</SCRIPT>

Server屬性可以指到一個HTTP URL位址,或是一個使用安全協定的HTTPS (Secure Hypertext Transfer Protocol) URL位置。兩種情況下,URL都可以包含一個連接埠的號碼。假使您透過DCOM來取得資料,您可以指定存放資料來源的機器名稱。最後,假使您使用本地的資料庫(典型地,在之前除錯的過程中),您可以為這個屬性中指定一個空字串,或是由<OBJECT>標記中省略。假使您沒有特別指定server,RDS.DataControl物件會以in-process物件方式建立實體。所有在隨書CD中的範例程式都使用一個本地的NWind.mdb,所以這個屬性都是空白的。請記得當您把這個應用程式移到區域網路或是intranet上時,要為這個屬性指定一個有意義的值。

連結DHTML元件
 

您可以將許多類型的DHTML元件連結到RDS.DataControl物件,有一些列於表19-1中。所有連結的元件都提供三個屬性:

  • DATASRC是元件所要連結的RDS.DataControl名稱,它是以#符號來開頭- 例如 #dcPublishers。 (它對應到Visual Basic資料連結控制項的DataSource屬性。)
     
  • DATAFLD是這個元件連結的資料來源欄位名稱。(對應到Visual Basic的DataFiled屬性。)
     
  • DATAFORMATAS可以是text或是HTML,依據是否要將來源欄位的內容轉成純文字或是HTML程式碼。這個屬性預設值是text,您只能在支援innerHTML屬性的控制項上使用HTML設定值。
     

底下是一個連結到之前所建立dcPublishers RDS.DataControl的TextBox控制項範例:

Publisher Name: <BR>
<INPUT ID="txtPubName" DATASRC="#dcPublishers" DATAFLD="Pub_Name"><BR>
City: <BR>
<INPUT ID="txtCity" DATASRC="#dcPublishers" DATAFLD="City"><BR>
Element Bound Property Updatable
A bref No
BUTTON innerText/innerHTML Yes
DIV innerText/innerHTML Yes
IMG src No
INPUT valueorchecked(depending on the TYPE attribute) Yes
trial="1"SELECT the text of the selected OPTION tag Yes
SPAN innerText/innerHTML Yes
TEXTAREA value Yes
表19-1 一些可以連結到RDS.DataControl物件的HTML元件。

瀏覽並更新Recordset
 

不像標準的ADO資料控制項,RDS.DataControl物件並沒有可看見的介面,所以您必須自己提供瀏覽Recordset的按鈕。這類的按鈕使用了RDS.DataControl物件所提供的Recordset方法。下面的VBScript程式碼假設您已經建立了四個名為btnMovexxxx的按鈕,並加上btnDelete及btnAddNew控制項:

Sub btnMoveFirst_onclick()
    dcPublishers.Recordset.MoveFirst
End Sub
Sub btnMovePrevious_onclick()
    dcPublishers.Recordset.MovePrevious
    If dcPublishers.Recordset.BOF Then dcPublishers.Recordset.MoveFirst

End Sub
Sub btnMoveNext_onclick()
    dcPublishers.Recordset.MoveNext
    If dcPublishers.Recordset.EOF Then dcPublishers.Recordset.MoveLast
End Sub
Sub btnMoveLast_onclick()
    dcPublishers.Recordset.MoveLast
End Sub
Sub btnDelete_onclick()
    dcPublishers.Recordset.Delete
    dcPublishers.Recordset.MoveNext
    If dcPublishers.Recordset.EOF Then dcPublishers.Recordset.MoveLast
End Sub
Sub btnAddNew_onclick()
    dcPublishers.Recordset.AddNew
End Sub

RDS.DataControl物件使用離線的Recordsets,所以所有您在資料連結控制項中做的更改都被暫存在本地。當您準備要將更改的資料傳送到資料來源時,可以執行RDS.DataControl物件的SubmitChanges方法。您通常會在Window_onunload事件中,或是按鈕的onclick事件中呼叫這個方法:

Sub btnUpdate_onclick()
    dcPublishers.SubmitChanges
End Sub

您可以使用CancelUpdate方法來放棄所有為尚未寫入的更新資料。在隨書光碟中,您會找到一個應用程式,它使用HTML控制項來連接到本地NWind.mdb的Customers資料表; 您可能必須更改RDS.DataControl的Connect屬性,來讓它指到一個在您系統上合法的路徑。

所有的連結控制項都會發生兩個事件,您可以使用網頁中的腳本語言或是使用DHTML應用程式的Visual Basic程式來捕捉它們。onbeforeupdate事件會發生在當被更改的值要從控制項傳送到資料來源之前;假使您不取消它,這個控制項會在更新動作完成後立即發生onafterupdate事件。您可以使用這些事件來對使用者在資料連結控制項中輸入的資料作驗證,如圖19-17。


 

圖19-17 這個DHTML應用程式使用了資料連結控制項,並且在瀏覽按鈕中使用了VBScript程式碼。

表格連結
 

假使您比較喜歡使用表格來顯示查詢結果,您可以使用DHTML表格特殊連結功能。此時,您必須在<TABLE>標記中指定DATASRC屬性,並且準備表格中的一列儲存格來包含使用指定了適當DATAFLD屬性的<SPAN>標記。底下的程式是來自隨書光碟中的範例程式(圖19-18)。

<TABLE DATASRC="#dcCustomers" BORDER=1>
 <THEAD><TR>   
    <TH>Company Name</TH>
    <TH>Address</TH>
    <TH>City</TH>
    <TH>Region</TH>
    <TH>Country</TH>
  </TR></THEAD>
 <TBODY><TR>   
    <TD><B><SPAN DATAFLD="CompanyName"></SPAN><B></TD>
    <TD><SPAN DATAFLD="Address"></SPAN></TD>
    <TD><SPAN DATAFLD="City"></SPAN></TD>
    <TD><SPAN DATAFLD="Region"></SPAN></TD>
    <TD><SPAN DATAFLD="Country"></SPAN></TD>
  </TR> </TBODY>
</TABLE>

RDS.DataControl物件會為每筆在來源中的紀錄產生一列新的儲存格。為了產生這樣的列,RDS.DataControl使用包含<TBODY>及</TBODY>標記的HTML樣本。您可以使用標準的HTML標記來為每個欄設定格式及排列方式。例如,範例應用程式使用粗體來顯示CompanyName欄位。


 

圖19-18 資料連結的DHTML表格會自動改變它們的欄位,以最適當的方式來顯示它們的儲存格。

更多關於RDS.DataControl物件
 

RDS.DataControl物件提供幾個其他的屬性及方法,讓您微調您的應用程式。例如,InternetTimeout屬性讓您以毫秒來設定HTTP傳送時可使用的時間,而SortColumn及SortDirection屬性讓您對底下的Recordset做資料排序:

' Sort on the City field in ascending order.
dcPublishers.SortDirection = True
dcPublishers.SortColumn = "City"
dcPublishers.Reset

將FilterColumn、FilterCriterion及FilterValue屬性一起搭配使用,可以過濾所取得的資料:

' Display only U.S. publishers.
dcPublishers.FilterColumn = "Country"
' FilterCriterion supports the following operators: <  <=  >  >=  =  <>.
dcPublishers.FilterCriterion = "="
dcPublishers.FilterValue = "USA"
dcPublishers.Reset

在預設狀態下,RDS.DataControl執行查詢及取回Recordset是非同步的。您可以使用ExecuteOptions來控制如何執行查詢的動作,它可以是1-adcExecSync或是2-adcExecAsync。同樣地,您可以使用FetchOptions屬性來決定如何取出Recordset,它可以是:1-adcFetchUpFront (同步執行,當Recordset完全建立後,控制項才回到應用程式中); 2-adcFetchBackground(當第一批資料傳回時,控制項就會回到應用程式,而剩下的資料使用非同步來取得); 或是3-adcFetchAsync(預設模式,所有的資料是在背景中取得)。

當RDS.DataControl使用非同步的方法時,您必須檢查ReadyState屬性,它會傳回下面的值: 2-adcReadyStateLoaded (Recordset已打開,但尚未取得任何資料);3-adcReadyStateInteractive(正在建立Recordset); 4-adcReadyStateComplete(Recordset已經完全取得資料)。一旦當這個屬性接收到新的值,RDS.DataControl會產生onreadystatechange事件。您可以使用Cancel方法來取消同步作業。

當有錯誤產生,而沒有VBScript正在執行時,RDS.DataControl物件會產生onerror事件。

使用RDS物件
 

儘管資料連結可以建立出很好的程式原型,大多數的情況您必須自己撰寫程式,假使您想要控制整個處理過程。RDS程式庫包含幾個物件,它們可以讓您能夠在使用非狀態的協定下,讓離線的client交換資料。更精確地說,當您要發展以RDS為基礎的應用程式時,可以使用三個不同程式庫中的物件。(如圖19-19。)

  • RDS.DataSpace是一個在client應用程式中執行的元件,扮演與存在資料的Server連結。這個物件是由Microsoft Remote Data Services library所提供(Msadco.dll).。
     
  • RDSServer.DataFactory是在server上執行的元件。它用來查詢資料來源並且將client傳來的資料更新到資料來源中。這個物件是由Microsoft Remote Data Services Server library(Msadcf.dll)所提供。您不需要在client工作站中安裝這個程式庫。
     
  • RDS.DataControl (已在之前小節中敘述) 是一個可以放置到HTML網頁中的ActiveX元件。它允許您將網頁上一或多個元件連結到遠端的資料來源。這個物件包含在Msadco.dll程式庫中,而RDS.DataSpace及RDSServer.DataFactory都有提供這個功能。
     
  • ADOR.Recordset 的功能與ADO Recordset相似,但它使用較少的資源,也因此它比較適合在瀏覽器中執行,且瀏覽器也不需要像ADO那麼強大的功能。這個物件由Microsoft ActiveX Data Object Recordset library (Msador15.dll)所提供。ADOR程式庫也包含了Field及Property物件,但沒有Connection及Command物件。當安裝Internet Explorer同時也自動地安裝了這個程式庫,所以您不需要先去下載它們再安裝到client工作站上。
     

說明

只是讓您對ADOR程式庫與一般的ADO程式庫的輕重有些概念,請比較下面的事實: Msador15.dll檔案大小大約為37KB,而功能完整的Msado15.dll檔案大小為332KB。



 

圖19-19 一個典型的RDS session中所有的物件。

建立連結
 

假使您已經習慣了使用ADO的方式,那麼使用RDS來建立連結的方式可能會讓您在一開始感到很奇怪,且覺得複雜是不必要的。但是這種做法有它內部的邏輯,並且可以提供很多的彈性。

在Visual Basic整合發展環境測試這些程式之前,需要在設定引用項目對話方塊中增加RDS及ADOR程式庫的引用。接下來的步驟是建立一個RDS.DataSource物件的實體;使用它的CreateObject方法來建立遠端RDSServer.DataFactory物件的實體。您不需要增加一個對RDSServer程式庫的引用,因為您將要把CreateObject方法傳回值指定給一般的Object變數中。

Dim ds As New RDS.DataSpace
Dim df As Object
Set df = ds.CreateObject("RDSServer.DataFactory",_
    "http://www.yourserver.com")

說明

本節中所有的範例程式都是使用標準Visual Basic應用程式或是DHTML網頁設計師模組來完成的。您只要將As子句換做Dim陳述式,並且將New關鍵字以CreateObject方法來取代便可以很容易的將它們轉換為在HTML網頁中的腳本語言。


在RDS.DataSpace的CreateObject方法中的第二個引數可以是HTTP或是HTTPS URL位址,網路中另外一台機器的名稱,或是空字串(假如您要建立的DataFactory物件實體與執行程式機器是在同一台上)。

當您獲得一個合法的RDSServer.DataFactory物件引用,您可以使用它的Query方法來取得包含查詢結果的Recordset物件:

Dim rs As ADOR.Recordset
Set rs = df.Query("DSN=Pubs","SELECT * FROM Publishers")

在Query方法中的第一個引數是DataFactory使用來與資料來源連接的連接字串,所以您可以使用所有在ADO.Connection物件ConnectionString屬性的引數。請不要忘記這個連接字串將會被已經在server上執行的元件所使用(所以您不需要太長的timeout值),並且確定您所指到的DSN及連接屬性對這個server來說是有效的。

顯示及更新資料
 

您可以像使用一般ADO Recordset來使用ADOR.Recordset物件,因為兩者間只有些許的不同(請參閱ADO文件來獲取更多細節)。您可以瀏覽這個Recordset並且更動它的欄位,但所有的更該都先被暫存在本地。因為您沒有連結的控制項,所以您必須自己在網頁提供用來移動Recordset的程式。然而,即使控制項沒有連結到資料來源,您還是可以使用dataFld屬性來解決。事實上,您可以在控制項dataFld屬性中指定您想要顯示的欄位名稱,然後使用下面的常式來往前往後移動資料:

' You can reuse these routines in any DHTMLPage module.
Sub GetFieldData()
    ' Move data from the Recordset to the fields in the page.
    ' All the "pseudo-bound" controls have a nonempty DataFld property,
    ' so you just need to iterate on the "all" collection.
    Dim ctrl As Object
    On Error Resume Next
    For Each ctrl In Document.All
        If Len(ctrl.dataFld) = 0 Then
            ' Empty or unsupported DataFld property.
        Else
            ' Append an empty string to account for Null values.
            ctrl.Value = rs(ctrl.dataFld) & ""
        End If
    Next
End Sub
Sub PutFieldData()
    ' Move data from the fields in the page to the Recordset.
    Dim ctrl As Object
    On Error Resume Next
    For Each ctrl In Document.All
        If Len(ctrl.dataFld) = 0 Then
            ' Empty or unsupported DataFld property
        ElseIf rs(ctrl.dataFld) & "" <> ctrl.Value Then
            ' Don't update the Recordset if it isn't necessary.
            rs(ctrl.dataFld) = ctrl.Value
        End If
    Next
End Sub

感謝這些常式讓我們很容易撰寫用來瀏覽資料的按鈕。例如底下是當使用者按下Next按鈕時執行的程式碼:

Private Function btnMoveNext_onclick() As Boolean
    PutFieldData                   ' Save current values.
    rs.MoveNext                   ' Move to the next record.
    If rs.EOF Then rs.MoveLast     ' Go back if you moved too far.
    GetFieldData                   ' Display the current record.
End Function

當您準備好要傳送已更改的資料回server時,您必須呼叫RDSServer.DataFactory物件的SubmitChanges方法。這個方法需要一個連接字串並且還需要一個傳回資料來源的Recordset引用:

' Specify that you want to marshal only modified values.
rs.MarshalOptions = adMarshalModifiedOnly
' Send modified values to the server.
df.SubmitChanges conn, rs

假使有任何一筆資料產生了衝突,SubmitChanges方法就會失敗。在這個情況下,RDS程式庫處理的方式遠不如ADO程式庫來得精密,因為在ADO程式庫中您可以分別的對每一筆衝突來處理。

自訂商業物件
 

遠端資料服務技術允許您使用超過一種方式來在server與client間傳送Recordset。事實上,DataSource的CreateObject方法可以用來建立任何在Web server上的ActiveX元件實體。就某種意義來說,您可以把RDS看做是對HTTP及HTTPS協定的DCOM延伸。這個新技術為勇於創新的程式設計人員開啟了另一個新世界。

更正確地說,RDSServer.DataFactory物件只是眾多可以在Web server上建立實體的元件之一,而它讓我們特別的注意是因為它提供了RDS封裝。但當您進入發展真實世界的應用程式時,您會發現這個元件有一個缺點:一旦當client建立一個對server的連結,只要提供正確的使用者名稱及密碼,它可以查詢在server上的任何資料庫。事實上,使用嘗試錯誤法,client可能有機會取得別人的使用者名稱及密碼。這個安全體制對Web server來言是不夠的,它讓Web server處於世界上所有瀏覽器的威脅中。


說明

RDS允許(很有限的)自訂RDSServer.DataFactory物件的行為,透過名稱為MSDFMAP.Handler預設的handler object或是透過您所提供的自訂handler object。您可以編輯在Windows目錄中的msdfmap.ini組態檔案,來控制預設的handler。請使用編輯器打開這個檔案來對這個物件有更進一步的認識,並且參閱RDS文件來獲得更多細節。


解決這個安全問題的方法是建立一個自訂的ActiveX元件,並且將它安裝到Web server上。這項的元件可以提供─透過它的屬性及方法-只有您想要對外公開的資料。此外,因為client工作站是透過這個自訂元件來存取資料庫,您可以具備所有三層式架構所帶來的好處:

  • 在client應用程式中的程式碼會很簡單,因為自訂元件可以提供高階的存取及處理資料的方法。
     
  • clinet無法看到server上資料庫的實體架構,所以您可以更改資料庫的實作方式而不用擔心會影響到client。
     
  • 這個元件可以在結果傳回到client之前先在server本地處理資料,這通常會有較好的總體效能。
     

撰寫自訂元件
 

一個要能夠被RDS.DataSpace物件的CreateObject方法建立實體的自訂元件與一般的ActiveX元件並沒有什麼不同,所以您可以使用在第16章所學習的來建立這些元件。這個元件應該提供方法來讓client執行查詢,或是傳回新的或是更改過的紀錄給這個元件。

在隨書光碟中,您會發現一個名為NWindFactory.Shipper的簡單ActiveX DLL。這個元件讓Web client查詢安裝在server電腦上NWind.mdb資料庫中的Shippers資料表。這個元件只提供三個方法:GetShippers會傳回離線的ADOR.Recordset,它包含所有在Shippers資料表中的紀錄,UpdateShippers 會將所傳入的ADOR.Recordset更新到資料表中,而GetEmptyShippers會傳回一個空的ADOR.Recordset讓使用者插入新的貨主資訊。底下是這個元件的完整程式碼:

' This is the path of the NWind.mdb database on the server.
Const DBPATH = "C:\Program Files\Microsoft Visual Studio\Vb98\NWind.mdb"
Dim conn As String
Private Sub Class_Initialize()
    ' Initialize the connection string.
    conn = "Provider=Microsoft.Jet.OLEDB.3.51;Data Source=" & DBPATH
End Sub
' Return the Shippers table in a Recordset object.
Function GetShippers() As ADOR.Recordset
    Dim rs As New ADOR.Recordset
    ' Query the Shippers table.
    rs.CursorLocation = adUseClient
    rs.Open "SELECT * FROM Shippers", conn, adOpenStatic, _
        adLockBatchOptimistic
    ' Disconnect the Recordset.
    Set rs.ActiveConnection = Nothing
    Set GetShippers = rs
End Function
' Update the Shippers table with data contained in a Recordset.
Function UpdateShippers(rs As ADOR.Recordset) As Boolean
    On Error Resume Next
    rs.ActiveConnection = conn           ' Reconnect the Recordset.
    rs.UpdateBatch                       ' Perform the updates.
    Set rs.ActiveConnection = Nothing    ' Disconnect it once again.
    UpdateShippers = (Err = 0)           ' Return True if everything is OK.
End Function
' Return an empty Recordset.
Function GetEmptyShippers() As ADOR.Recordset
    Dim rs As New ADOR.Recordset
    ' Retrieve an empty Recordset from the Shippers table.
    rs.CursorLocation = adUseClient
    ' Notice the WHERE clause in the following SQL SELECT command.
    rs.Open "SELECT * FROM Shippers WHERE 0", conn, adOpenStatic, _
        adLockBatchOptimistic
    ' Disconnect the Recordset.
    Set rs.ActiveConnection = Nothing
    Set GetEmptyShippers = rs
End Function

為了更好的結果,您應改使用 執行時無使用者介面  公寓模型執行緒 來編譯您自訂的ActiveX DLL元件。這兩個選項都在專案屬性對話方塊中的 一般 頁籤中。

註冊元件
 

要讓ActiveX自訂元件能夠透過RDS來安裝,您還需要多一到程序。並不是所有安裝在server的元件都能夠被Internet client建立實體,因為這將很難做出安全設定。只有那些在server上登錄資料庫中某一個機碼下的元件可以透過RDS建立實體。更精確地說,您必須在server的登錄資料庫中建立底下的機碼:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\W3SVC\Parameters\
    ADCLaunch\<servername.classname>

請注意這個機碼沒有設定值。此外,一個給RDS的元件必須標記為Safe For Scripting以及Safe For Initialization,這代表在登錄資料庫中增加兩個機碼,如第十七章的 〈下載元件〉 小節所解釋的。

當您正在建立元件的安裝封裝,最好準備一個REG檔案來自動的加入登錄資料庫。例如下面的程式片段是範例程式NWindFactory.Shippers元件的REG檔案。第一個項目註明這個元件可以透過RDS.DataSpace的CreateObject方法來建立實體,剩下的兩個項目註明它是Safe For Scripting及Safe For Initialization settings的。當您為自己的元件建立一個REG檔案,您必須將"NWindFactory.Shippers"更換為元件的ProgID,以及元件CLSID{03C410F7-C7FD-11D2-BAC5-0080C8F21830}的字串。

REGEDIT4
[HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\W3SVC\Parameters\
    ADCLaunch\NWindFactory.Shippers]
[HKEY_CLASSES_ROOT\CLSID\{03C410F7-C7FD-11D2-BAC5-0080C8F21830}\
    Implemented Categories\{7DD95801-9882-11CF-9FA9-00AA006C42C4}]
[HKEY_CLASSES_ROOT\CLSID\{03C410F7-C7FD-11D2-BAC5-0080C8F21830}\
Implemented Categories\{7DD95802-9882-11CF-9FA9-00AA006C42C4}]

您不需要為RDSServer.DataFactory物件建立一個機碼,因為更改登錄資料庫是server上RDS安裝封裝的一部份。

使用元件
 

經由RDS使用自訂元件與使用RDSServer.DataFactory物件相似。您只需要為您的元件透過RDS.DataSpace物件的CreateObject方法來建立出一個實體,然後使用您元件的方法來取得並更動Recordset.。因為您的client不需要直接的對資料庫做查詢,它們只需要引用ADOR輕量程式庫,而不需要完整功能的ADO程式庫。

圖19-20 展示這個示範的client應用程式。它的三個TextBox控制項是動態地與從元件取得的Recordset連結。底下是應用程式主表單程式碼的一部份。

' Modify this constant to point to your Web server,
' or use an empty string to connect to a local component.
Const WEB_SERVER = "www.yourserver.com"
Dim ds As New RDS.DataSpace
Dim myObj As Object
Dim rs As ADOR.Recordset
Private Sub Form_Load()
    ' Create the remote component.
    Set myObj = ds.CreateObject("NWindFactory.Shippers", WEB_SERVER)
End Sub
Private Sub cmdGetShippers_Click()
    ' Ask the component to query the table and then return a Recordset.
    Set rs = myObj.GetShippers()
    ' Bind the controls to this Recordset.
    SetDataSource rs
End Sub
Private Sub cmdGetEmptyShippers_Click()
    ' Ask the component to create an empty Recordset.
    Set rs = myObj.GetEmptyShippers()
    ' Bind the controls to this Recordset.
    SetDataSource rs
End Sub
Private Sub cmdUpdateShippers_Click()
    ' This optimizes the update operation.
    rs.MarshalOptions = adMarshalModifiedOnly    
    ' Pass the updated Recordset to the component, and test the result.
    If myObj.UpdateShippers(rs) Then
        MsgBox "Update successful", vbExclamation
    Else
        MsgBox "Unable to update" , vbCritical
    End If
End Sub
Sub SetDataSource(obj As Object)
    ' Use the Recordset as a data source for the fields.
    Set txtShipperID.DataSource = obj
    Set txtCompanyName.DataSource = obj
    Set txtPhone.DataSource = obj
End Sub


 

圖19-20 一個使用動態連結控制項的範例應用程式。

您可以使用Recordset的MarshalOptions屬性來讓更新處理更具效益;假使您將這個屬性設定為1-adMarshalModifiedOnly,則只有那些被更改,新增,或刪除的紀錄會被傳回到server。假使更新作業失敗,您可以檢查每筆Recordset紀錄的Status屬性來找出哪邊發生問題。對於所有沒有成功更新的紀錄,這個屬性傳回的值會跟adRecUnmodified不同。

一個RDS元件只可以處理包含一個結果集(result set)的Recordset,因此,舉例而言,您不可以傳回給client由預存程序所傳回的多個結果集。同樣地,您可以傳回一個包含陣列或是多維陣列的Variant,但您不可以傳回包含多個結果集的陣列。另一方面,傳回階層的Recordset是沒有關係的。


說明

請不要忘記,當您提供自訂的元件來讓使用者對Web server上的資料庫做查詢維護時,並不表示您已經解決了所有安全性的問題。例如,一個client可以透過標準的RDSServer.DataFactory物件來連接,一旦他(她)們知道合法的登入名稱及密碼,您的資料就危險了。因為這個原因,您可能決定刪除登錄資料庫中ADCLaunch機碼中對應的項目,來關閉RDSServer.DataFactory物件的遠端建立實體(remote instantiation)的功能。


DHTML編輯控制項
 

很少Visual Basic程式設計人員知道Microsoft已經以DHTML編輯控制項的型式公開了部分DHTML網頁設計師當作基礎的技術。這個控制項可以免費的由Microsoft網站下載,URL為 http://www.microsoft.com/workshop/author/dhtml/edit/down-load .。asp (這個網站也包含了可以用在Integer Explorer之前版本中的控制項版本)。這個控制項具備了所有DHTML網頁設計師右邊 明細部份 窗格的功能,因此它讓您可以為您的應用程式加入一個Dynamic HTML編輯器。

安裝
 

執行您所下載的EXE檔,並選擇一個安裝目錄。在解壓縮過程結束時,您會發現幾個檔案,包含完整的文件說明以及一寫有趣的範例。您也會發現一些包含的檔案,但它們對Visual Basic程式設計人員來說是不感興趣的。(這個封裝也包含C++的版本)

執行Visual Basic整合發展環境,按下Ctrl+T按鍵來帶出所有已經安裝過的ActiveX控制項列表,然後選擇新的DHTML編輯控制項元件(DHTML Edit Control component)。這個作業會在工具箱視窗加入兩個新的圖示。每個圖示代表不同控制項風味:一個是完整的版本,另一個是標記為Safe For Scripting及Safe For Initialization,而它不允需一些動作。例如,儲存檔案。通常,您會在Visual Basic應用程式中使用之前的版本,而在HTML網頁或是在瀏覽器執行的DHTML應用程式中使用後者。

要體會這個控制項可您為您帶來什麼,請在表單上放置一個實體,並且執行這個程式。您可以在這個控制項視窗中輸入任何文字,就跟一個標準的TextBox一樣。然而,它並不像標準的TextBox控制項,您可以為選取的文字設定格式,如粗體、斜體及底線(分別使用Ctrl+B、Ctrl+I及Ctrl+U組合鍵)。這個控制項透過快速鍵提供許多的動作:您可以使用Ctrl+L來插入一個超連結,使用Ctrl+T及Ctrl+Shift+T組合鍵來增加或減少段落縮排,而使用Ctrl+F組合鍵可以顯示尋找對話方塊。這個控制項也支援多層次的復原及重複功能,分別使用Ctrl+Z及Ctrl+Y組合鍵,並且一些在網頁上移動元件的拖放能力。

屬性及方法
 

然而,DHTML編輯控制項的剩餘功能只能透過它的方法及屬性來達成。舉例而言,您可以分別使用NewDocument、LoadDocument及SaveDocument方法來建立一個新的文件,載入一個存在的HTM檔案或是將控制項的內容儲存到檔案中(後面兩個方法也可以顯示一個選擇檔案的通用對話方塊)。或者您可以使用LoadURL方法從URL載入一個HTM檔案,如下:

DHTMLEdit1.LoadURL = http://www.vb2themax.com/index.htm

您可以指定字串到DocumentHTML屬性中,而不用檔案來載入或儲存HTML原始程式碼。這個屬性可以讓您很有效率的儲存或是由資料庫欄位中取出格式化的文件,或是建立一個精緻的的DHTML編輯器來讓您輸入存HTML原始碼,這個功能是DHTML網頁設計師所沒有提供的。為了練習,您可以修改隨書CD中的DHTMLEd.vbp專案,將WebBrowser控制項以DHTML編輯控制項來取代。這邊有個小小的警告:當文件正在載入時,使用DocumentHTML屬性會發生錯誤,您可以使用Busy屬性來測試這個情況。

DHTML編輯控制項也可以在預覽模式中使用,此時您可以察看您建立的網頁在瀏覽器中是如何呈現。您可以將BrowserMode屬性設為True或False來切換預覽模式。

DHTML編輯控制項使用獨特的方式來支援格個指令。它使用一個指令來代表所有選項,而不是提供很多的屬性及方法,透過all-in-one的ExecCommand方法來取得所有的指令,它的第一個引數是一個常數,用來告訴這個方法該做什麼。筆者數過它包超過50個指令,包括更改文字特性,插入刪除表格儲存格,執行剪下貼上作業,更改元件的z-order或是排列方式,等等。舉例而言,下面的程式告訴您如何更改目前選取文字的字型大小:

' The second argument suppresses the default dialog box.
' The third argument is the new font's size. 
DHTMLEdit1.ExecCommand DECMD_SETFONTSIZE, OLECMDEXECOPT_DONTPROMPTUSER, fs

DHTML編輯控制項的DOM屬性會傳回控制項中網頁的Document物件引用。感謝這個屬性,您可以對編輯的文件做一些虛擬的動作。舉例而言,您可以使用下面的程式更改HTML網頁的背景顏色:

DHTMLEdit1.DOM.bgColor = "red"

DHTML編輯控制項也提供幾個事件讓您對正在編輯文件的使用者做出反應。最重要的事件是DisplayChange,它會在使用者選擇新的元件或是移動插入點時發生。典型地,我們會在這個事件發生時更新狀態列或是工具列上按鈕的狀態。當網頁完全被載入且已經準備被編輯時,會發生DocumentComplete事件。ShowContextMenu及ContextMenuAction事件讓您決定當使用者在元件上按下滑鼠右鍵及選取功能表上的命令時該做什麼事。

筆者很驚訝這個控制項有如此多的範例程式。VBEdit.vpb是一個給HTML網頁完整的WYSIWYG(所見即所得) 的編輯器,而且它的原始碼給您一個機會來嘹解如何使用DHTML編輯控制項的功能。(如圖19-21) VBDom.vpb專案說明如何存取在控制項上文件的Document Object Model。最後,在Web的子目錄中,您會發現有很多放置DHTML編輯控制項的HTML網頁範例。


 

圖19-21 VBEdit.vbp範例應用程式。

這一章我們花了很長的時間,但還是有一些領域沒有講解到。您已經瞭解什麼是HTML及Dynamic HTML,如何使用新的HTML網頁設計師所提供的強大功能,以及如何在Internet上使用RDS及remote Automation所帶來的好處。現在您已經準備好接受最後的課程:您可以建立在Web server上執行的Visual Basic應用程式。下一章我們將探討這個主題。