電腦遊戲製作開發設計論壇 首頁 電腦遊戲製作開發設計論壇
任何可以在PC上跑的遊戲都可以討論,主要以遊戲之製作開發為主軸,希望讓台灣的遊戲人有個討論、交流、教學、經驗傳承的園地
 
 常見問題常見問題   搜尋搜尋   會員列表會員列表   會員群組會員群組   會員註冊會員註冊 
 個人資料個人資料   登入檢查您的私人訊息登入檢查您的私人訊息   登入登入 

Google
學習網頁DOM和SCRIPT的一些心得 II

 
發表新主題   回覆主題    電腦遊戲製作開發設計論壇 首頁 -> 程式概論
上一篇主題 :: 下一篇主題  
發表人 內容
iceShadow
偶而上來逛逛的過客


註冊時間: 2007-05-24
文章: 10

0.00 果凍幣

發表發表於: 2007-6-24, PM 6:04 星期日    文章主題: 學習網頁DOM和SCRIPT的一些心得 II 引言回覆

我準備以一個動態的HTML網頁作為這個篇幅分享的題材,這個網頁有點好玩,因為在它被啟動時,將會把自己的DOM節點構造列印出來成為網頁的內容,為了這個目地,所以還是免不了要瞭解一下一些相關的知識,在上一篇我們談到每一個節點都會具有childNodes性質,聰明的人也許想到了,那利用childNodes集合便可以逐一的探訪整個文件的DOM樹形結構,是的,可是那還是不夠的,因此,接下來,我要介紹的便是傾印一個網頁DOM結構所須具備的基本知識,還有一些程式設計上會面臨到的前置工作→演算與分析,其實網上是有這樣的解析工具,不過這裡我想強調的是DOM的觀念和SCRIPT如果搭配起來的話,其實一些功能是可以完全不用其它程式語言設計就能達成的喲。

※節點名稱、節點值、節點型態

每一個節點,除了會具有一個子節點的集合childNodes,另外也會有nodeName和nodeValue、nodeType這三個性質,nodeName故名思義,就是該節點的名稱,通常對網頁裡的元件來說,節點名稱就是該標籤的名稱。例如:

document.body.nodeName →BODY

但有一些例外的,像文字節點,它的nodeName會是#text,而註解型態(指網頁中<!-- -->的區塊)的節點名稱會是#comment,document物件的節點名稱是#document。而節點值(這也是直翻,雖然翻譯的有點〝離離落落〞,不過就湊合著點用)只有在〝文字型態節點〞或〝註解型態節點〞其值才不為NULL,NULL在這裡代表的意思,簡而言之,就是〝空的〞、〝沒有〞的意思,例如對於上一個篇幅提到的:

<!-- 這是一個來自iceShadow製作的HTML -->

它在DOM中的nodeName(節點名稱)便是#comment(註解型態的節點),而nodeValue(節點值)就是這是一個來自iceShadow製作的HTML,其實在上一篇裡,每一個節點上,我放上的便是該節點的nodeName性質。

而節點型態(nodeType)是代表該節點型態的一個號碼,每種節點都各自有一個代碼,但很不幸的是,微軟的瀏灠器和fireFox對此也是有分歧意見的,以下面這個例子來說:
代碼:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Strict// EN">


微軟瀏灠器對它的解釋是

nodeName:#comment
nodeType:8
nodeValue:CTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Strict// E

fireFox對它的解釋是
nodeName:HTML←iceShadow也不瞭解為什麼它會和<html>標籤的節點名稱相同,或許該去向firefox作者請益一下,或是有高手知道其中原委嗎?
nodeType:10
nodeValue:""

也就是說微軟的瀏灠器只認為那是一個〝註解〞,說到這,經測試的結果,微軟瀏灠器對註解的理解確實是有一點問題的,在我的瀏灠器上只要<!符號開頭,>符號結尾,它就將中間的字元視為註解字元,這或許是微軟瀏灠器一個對於註解處理上的小bug吧?正確的註解起始字符應是以<!--符號起始,然後以-->符號作為結束的,另外對於上述結果,也可以看到fireFox則把它歸類成一個不同於其它節點的型態。

再來談到,傾印網頁文件的DOM構造時,需要注意的排版問題,因為每個節點名稱長度大小並不一致,所以若直接傾印的話,因為節點名稱的長短問題,可能會造成版面的不美觀,考慮到這點,因此,樹形結構列印出來時,是需要排版的,HTML文件裡最能控制排版的當推TABLE(表格)元件了,所以我們有必要也對TABLE元件的構造瞭解一下。

一個內容是空的TABLE(表格),像這樣
代碼:

<table></table>

其實,在DOM觀點下,此節點除了名稱是TABLE以外,微軟的瀏灠器還會幫我們在該節點之下(指TABLE而言)建立一個子節點,就像網頁的主體置於BODY區塊內(BODY節點以下),表格的主體是置於一個節點名稱叫TBODY之下的,所以上面這個空表格,它在微軟的瀏灠器解釋之下的DOM構造會是像這樣:

TABLE
 │
 └TBODY

如果是fireFox瀏灠器,它的做法就有點不同了,如果是一個空表(<table></table>)則fireFox瀏灠器並不會建立TBODY節點,直到我們有建立表格內容,fireFox瀏灠器才會自動幫我們建立,這是兩款瀏灠器在處理網頁內容若有表格時的差別,要注意一下。
---------
如果表格內容是這樣
代碼:

<table>
 <tr>
  <td></td>
  <td></td>
 </tr>
 <tr>
  <td></td>
  <td></td>
 </tr>
</table>

那在微軟瀏灠器之下它的DOM構造便會像這樣

TABLE
 │   
 └TBODY
   │
   ├─TR
   │ │
   │ ├─TD
   │ │
   │ └─TD
   │
   └─TR
     │
     ├─TD
     │
     └─TD

在fireFox瀏灠器之下的DOM構造則是這樣

TABLE
 │   
 ├#text
 │   
 └TBODY
   │
   ├─#text
   │
   ├─TR
   │ │
   │ ├─TD
   │ │
   │ └─TD
   │
   ├─#text
   │
   └─TR
     │
     ├─TD
     │
     └─TD


至此,有一點一直是我不太瞭解的,觀看上述HTML的DOM模型構造,會發現firefox在TBODY及每一個TR節點前都會多了一個文字節點,另外,如果有撰寫網頁的SCRIPT時,當它被置入HEAD區時,在每一個SCRIPT節點前也是會多出一個#text節點,總之fireFox在很多在地方都會莫名多一個#text節點,可是看微軟瀏灠器所解析的DOM構造就沒這個問題,不知道這是不是fireFox設計者在撰寫fireFox瀏灠器時沒注意到的一個bug?如果只是單純的用HTML標籤來寫網頁,那倒不會有什麼太大的問題,但如果用節點觀念,且也會用到childNodes性質時,則要特別注意一下。


以下的方法是微軟瀏灠器和firefox所共有的,如果有不同的部份還會再特別提及

※動態建立元件的方法 createElement

document物件提供了我們一個方法(method),可以動態建立一個元件(element或稱節點),我們只要指定元件的名稱,一切細節至於該怎麼產生這個物件,這些都可以不用煩憂,我們唯一要做的,只是安排這些元件(節點)的階層關係。例如:
代碼:

document.createElement("TABLE"); //產生一個TABLE節點,對應<TABLE>標籤
document.createElement("TBODY"); //產生一個TBODY節點,對應<TBODY>標籤
document.createElement("TR");    //產生一個TR節點,對應<TR>標籤
document.createElement("TD");    //產生一個TD節點,對應<TD>標籤


※動態建立文字節點的方法 createTextNode

另外,文字節點的建立是有別於元件的,document物件也提供我們一個建立文字節點的方法(method),用法如下:
代碼:

document.createTextNode("文字內容"); //產生具有〝文字內容〞的文字節點


※為元件建立階層結構的方法 appendChild

每一個元件除了有子節點集合childNodes、節點名稱nodeName、另外,還有一個叫appendChild的方法(method),它專門用來將指定的節點設為該節點(元件)的子節點,以下面的HTML表格為例來說
代碼:

<TABLE>
 <TR>
  <TD>TestText</TD>
 </TR>
</TABLE>

以JavaScript為例,要產生一個一模一樣的表格的動態方法如下:
代碼:

var domTbl = document.createElement("TABLE");
var domTbd = document.createElement("TBODY");
var domTr = document.createElement("TR");
var domTd = document.createElement("TD");
var domTt = document.createTextNode("TestText");

domTd.appendChild(domTt);
domTr.appendChild(domTd);
domTbd.appendChild(domTr);
domTbl.appendChild(domTbd);

這樣,我們就建立了一個與用標籤寫法所建立的完全一模一樣的表格結構了。最後,我們只需要將這個結構也加入網頁主體區塊之下,便可以讓它顯示了:
代碼:

document.body.appendChild(domTbl);

不知道這樣,大家有沒有對DOM有更深一層的印象了呢?其中,在JavaScript裡只有一種變數宣告,用的是var關鍵字,不管是字元、字串、整數、浮點數、物件、節點(或稱元件)..等等任何變量都是用var關鍵字宣告的,如果有牽扯到兩變數的運算,JavaScript直譯器會自動幫我們處理並套用適當的運算函數,至於它如何依據兩變數的型態而套用何種運算函數這方面我的瞭解也不是很多,不過有一些簡單的原則可以記下來:

1.單純的等號運算,等號左邊的變數會自動轉成等號右邊運算結果的型態
2.兩字串相加時,如A+B則類似strcat( &A, &B),實際上是兩字串的連接
3.如果兩變數都是數值,則進行數值算術運算
4.如果想把數值當成字串使用時,只要令中間的運算元為""(空字串)即令,例A + "" + B
5.在程式的任何地方,變數都是可以重新指定型態的,如果它本來是一個字串,當我們設給它一個數值或物件參考、節點參照..等等,變數會自動〝變成〞該指定的型態。


大概就這些要注意的地方。
------

根據上面原理,我們可以用JavaScript中提供的迴圈結構,很輕易的建立一個4*4的表格,以下是一個示範

代碼:

var maxCol = 4; //四欄
var maxRow = 4; //四列
var tblObj = document.createElement("TABLE");
var tbdObj = document.createElement("TBODY");
var trObj, tdObj, rowCount, colCount;

tblObj.border = 1; //設定表格線寬為1

for ( rowCount=0; rowCount < maxRow ; rowCount++) {
    trObj = document.createElement("TR");
    for ( colCount=0; colCount < maxCol ; colCount++){
      tdObj = document.createElement("TD");
      ttObj = document.createTextNode(rowCount + ", " + colCount);
      tdObj.appendChild(ttObj);    //連結TD節點、文字節點
      trObj.appendChild(tdObj);    //連結TR節點、TD節點
    }
    tbdObj.appendChild(trObj);     //連結TBODY節點、TR節點
}
tblObj.appendChild(tbdObj);        //連結TABLE節點、TBODY節點

//將整個表格結構加到網頁主體
document.body.appendChild(tblObj); // 連結網頁主體、TABLE節點

其實,TABLE物件除了有TBODY節點以外,尚有THEAD(表頭)、TFOOT(表尾)、Caption(標題)等構件可以加入,而且可以直接由TABLE節點下所提供的方法(method)加以建立,不過它們都是可有可無的,且可以由TBODY節點以下適當的TD和TR節點構造所取代,為避免寫出來的SCRIPT因為瀏灠器的不同理解而發生版本配置甚至DOM建構上的誤差,或許只單用TBODY節點下適當的TR、TD節點構造取代是不錯的方式,不管是fireFox或微軟瀏灠器若一個TABLE是有內容的,那麼TBODY一定是一個該TABLE下所會具有的子節點。


這個篇幅的主題是分享iceShadow在撰寫一個可以將網頁自身的DOM結構列印出來的SCRIPT,在撰寫之前確實有很多需要詳細介紹的地方,也沒想到會用到這麼多篇幅,在稍後的篇幅裡還會再繼續提到一些相關知識,在介紹完後,iceShadow會詳細把分析及設計演算過程一一撰寫上來,唔,總有一種感覺,只是因為想介紹一個小主題卻怎麼寫也寫不完。Razz,因為有些地方iceShadow全是憑印象而寫的,因此,對於文中所提及的部份,若有誤謬的地方,也請不吝指正喔。^^||

以上

iceShadow
回頂端
檢視會員個人資料 發送私人訊息
iceShadow
偶而上來逛逛的過客


註冊時間: 2007-05-24
文章: 10

0.00 果凍幣

發表發表於: 2007-6-24, PM 6:27 星期日    文章主題: 學習網頁DOM和SCRIPT的一些心得 II 續.. 引言回覆

再談TABLE,剋服fireFox和微軟瀏灠器之DOM構造不同的動態表格存取方法

※把HTML的元件TABLE當作矩陣的操作方法

在上一篇幅裡談到TABLE元件在DOM中如何用SCRIPT動態建立的方法,其實除了用子集合childNodes的方式可以層層探訪到TABLE節點之下每一個附屬的子節點之外,因為fireFox和微軟瀏灠器對於表格理解上的些微不同,造成用childNodes索引值有可能會發生誤差的問題,因此,在這裡介紹利用TABLE節點本身提供的集合,也可以快速讓我們參照表格中某列某欄的位置,這兩個集合分別是rows、cells,但你必須使用document所提供的方法createElement建立表格元件時(指動態建立),

這兩個子集合瀏灠器才會幫我們自動建立。

以這段HTML表格為例(2*5)
代碼:

<TABLE>
 <TR>
  <TD>A</TD>
  <TD>B</TD>
  <TD>C</TD>
  <TD>D</TD>
  <TD>E</TD>
 </TR>
 <TR>
  <TD>F</TD>
  <TD>G</TD>
  <TD>H</TD>
  <TD>I</TD>
  <TD>J</TD>
 </TR>
</TABLE>


※TABLE元件,以欄為單位的集合cells(微軟瀏灠器支援)

這可能又是一個讓網頁設計者苦惱的訊息,經測試,微軟的瀏灠器才支援TABLE下以一維觀點參照表格的方式,在fireFox中目前來看好像還沒有(以 iceShadow手上有的fireFox,或許是因為版本因素,我不能很確定其它版本的fireFox是否和我所使用的是一樣的),因此,若想撰寫具有相容性的網頁SCRIPT,則還是盡量少用TABLE節點下的cells集合吧(指一維參照方式,二維參照方式則不在此限),不過以下還是對表格的一維參照方式作粗淺的介紹。

─ 一維觀點的表格 ─
如果各位對陣列有印象的話,集合cells所代表的便是二維陣列(表格其實可以視為一個二維陣列)的一維陣列表示法,易知,故上面的表格便會像這樣

┌─┬─┬─┬─┬─┐
│a│b│c│d│e│註:a.b.c等等不是欄位內容喔
├─┼─┼─┼─┼─┤ 只是暫時賦予的代表該欄位TD節點
│f│g│h│i│j│ 的代碼
└─┴─┴─┴─┴─┘


代碼:

table.cells[0]; //→參照到欄位代碼a
table.cells[2]; //→參照到欄位代碼c
table.cells[4]; //→參照到欄位代碼e
table.cells[5]; //→參照到欄位代碼f
table.cells[8]; //→參照到欄位代碼i

其中,table指的是table節點,並不是物件名稱叫table喔。
則易知,在TABLE每列每欄的參照與集合cells索引值的關係如下:

二維觀點的表格
┌─┬─┬─┬─┬─┐
│0│1│2│3│4│
├─┼─┼─┼─┼─┤
│5│6│7│8│9│
└─┴─┴─┴─┴─┘
  ↓  ↓
將二維表格轉換為一維觀點的表格
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│0│1│2│3│4│5│6│7│8│9│
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘

集合cells所直接參照到的節點,其實便是表格的TD節點,透過集合cells我們便可以快速的參照到表格中的TD節點,並操作TD節點,注意,這裡所提到的欄位代碼(a.b.c..)或欄位在集合cells的索引值(0.1.2..)並不是TD節點的內容喔,其實TD節點下,還是有一個文字節點的(以上例),該文字節點的nodeVale性質才是TD節點的文字內容,透過TD節點的子節點集合childNodes便可以探訪該TD節點的文字節點,另外,在TD節點之下是允許再建構其它網頁元件的(例如SPAN、DIV、FONT..等等),所以因此增加了一點探訪上所需要用到的技巧,文字節點在TD節點的子節點集合childNodes索引值位置是根據我們是否還有建立其它網頁元件在表格內容中而變動的,如何保證一定能確定探訪到的節點是文字節點,除了判斷該節點的nodeName是否為#text(文字節點)外,另外也可以藉由直接判斷nodeType是否為3(nodeName為#text,則nodeType為3)。

-------------------

★fireFox和微軟瀏灠器都支援的表格二維參照方式

※以列為主的集合rows(IE & firefox支援)

在TABLE節點下的另一個集合便是rows,rows可以快速的參照到TABLE中的某列,其實rows所參照到的便是TABLE中所有TR的節點,在微軟瀏灠器之下不管是TABLE或TR型態的節點都各自擁有有一個表格欄集合cells,TABLE節點的表格欄集合cells和TR節點的表格欄集合cells不同的地方在,TABLE節點下的表格欄集合cells參照到的是表格全體的表格欄,而TR節點的表格欄集合cells參照的只是它們自己所代表的列,這是關於微軟瀏灠器的部份。如果是在fireFox瀏灠器下,則只有TR節點會有集合cells用以映射該(列)TR節點之下的所有的(欄)TD節點,因此,若說TABLE.cell[欄索引值]是微軟瀏灠器上二維表格的一維陣列表示方式,則TABLE.rows[列索引值].cell[行索引值]便是二維陣列的表示式,且兩者語法同受微軟瀏灠器支援,而fireFox則只支援後者,以上面的例子來說,若用TABLE.rows[列索引值].cell[行索引值]的參照方式,便像是這樣:
代碼:

table.rows[0].cells[0]; //→參照到欄位a
table.rows[0].cells[2]; //→參照到欄位c
table.rows[0].cells[4]; //→參照到欄位e
table.rows[1].cells[0]; //→參照到欄位f
table.rows[1].cells[3]; //→參照到欄位i

-------
※取得DOM某節點參照的方法 getElementById

document物件有提供一個可以快速取得節點參考的方法(method),前提是,我們要設給該元件(或稱節點)一個id識別字,然後透過此id識別字,我們便可以快速取得該節點的參照,其用法如下:
代碼:

document.getElementById("id識別字");

例如:
代碼:

TABLE.id = "my_table"; //先設好該元件的id名稱

或者,用標籤語法事先設定
代碼:

<TABLE ID="my_table">

那麼取得該TABLE節點的參照,就可以用:
代碼:

var tblRef = document.getElementById("my_table");

這樣變量tblRef就得到該table的參照了,其缺點是,如果有表格是10*10列以上,那麼如果要參照每一個TD節點那豈不是要設100個id以上的識別字,所以較技巧的作法,應該只對決定性要參照的地方設下id識別字,以上例,則是設在TABLE節點,然後取得TABLE節點的參照後,再利用TABLE節點提供的集合rows取得TR(列)節點的參照,再經由TR節點下的集合cells(該列的欄位集合)快速參照到表格中的某列某行,如此,又避開fireFox和微軟瀏灠器DOM構造上些微不同的差異,避免可能在使用子節點集合childNodes因索引值問題而產生的取得參照上的錯誤,豈不妙哉。:p

在介紹必要的基礎知識後,接下來就是有點沈悶,而且也有點長的演算和分析了:

首先,要知道的是,一個網頁,在啟動之初,直到文件完全讀取完畢,其DOM的構造才算真正建構完成,如果文件還沒讀取完成,我們就開始解析該文件的DOM構造,勢必會發生一些錯誤,因此,解析函式一定要在文件讀取完成時,才能啟動,也因此勢必要建立一套嚴謹的步驟,以讓整個解析動作不致發生錯誤,歸納如下:

※定義程式的大致流程、步驟

步驟(一)、HTML文件被瀏灠器讀取完成時,才啟動DOM構造解析函式。
步驟(二)、在解析過程時,不得對網頁主體作任何加入或刪除節點的工作,以防止寫入的內容被解析函式當成解析的內容了。
步驟(三)、探訪每一個節點。
步驟(四)、將解析過程得到資訊建構(或轉換)成一個便於視覺上瞭解圖狀結構,以表格對此圖狀結構作排版。
步驟(五)、若整個探訪還沒結束,就持續步驟(三)、(四),直到所有的節點都探訪完畢。
步驟(六)、將解析的結果(該表格的DOM結構)與網頁主體連結,顯示在網頁上。

有了程式的大致流程、步驟,還要有對於流程如何在程式裡實現的對應方法
----------
關於第(一)個步驟,可以將解析函式與body標籤裡的onLoad事件作連結,onLoad事件是在整個文件讀取完成時才會被觸發,因此正好符合需求。

如果是微軟的瀏灠器,只要宣告一個函式
代碼:

function window.onload(){
}

則這個函式在HTML文件讀取完成時,就會被自動呼叫,其實這種宣告函式和事件連結的方法,雖然在微軟的瀏灠器上可以跑,但不見得別的瀏灠器會支援這種宣告方式,所以iceShadow覺得還是少用吧,因此,一個較有相容性的作法像這樣:
代碼:

function htmlInit(){
}

然後在BODY標籤裡寫明連結它
代碼:

<body onLoad="htmlInit()">


又或者,可以直接在寫完函式後,利用這樣的方式達成連結:
代碼:

window.onload = htmlInit;

則不管是微軟的瀏灠器或fireFox在HTML文件讀取完成時,都會自動呼叫htmlInit()函式喔。

在這裡做一個小註解,其實也算是iceShadow的一點小發現,如果不陌生的話,大家知道alert()是用來顯示警示訊息的一個內建函式,可是如果沒有指定參數,這在微軟瀏灠器上是沒什麼問題,可在fireFox上卻是錯誤的喔,而fireFox對於 SCRIPT中若有一行錯誤,整個SCRIPT語法解析也便會發生問題,這便造成SCRIPT不能正常執行了,iceShadow還是花了一些時間才 debug到這個不容易發現的徵結,原因出在iceShadow對fireFox的除錯模式不熟,不知道fireFox有一個JavaScript主控台,它位在功能列的工具項裡,所有JavaScript有關的錯誤訊息都會在那喔。

另外,fireFox和微軟瀏灠器對於事件的連結也有不同的理解,例如以下
代碼:

<body onload="test()">

微軟的瀏灠器認為,不管是window物件或document.body都需要將onload事件與test()函式作連結。
fireFox則不同,fireFox會認為只需要將window物件的onload事件與test()作連結就好,但為何fireFox沒有把body也連結而只選擇window物件呢?iceShadow也不清楚耶。

----------

關於第(二)個步驟,我們只要注意在SCRIPT中不要對所探訪的節點有寫入的動作,就沒太大問題。

----------
關於第(三)個步驟,必須有一個方法,保證可以逐一探訪所有的節點沒有遺漏,我想,那莫過於用遞迴的方式探訪來的簡單了,其方法像這樣:
代碼:

function desDOM(ndRef){
  var nCount;
  for (nCount=0; nCount < nodeRef.childNodes.length ; nCount++){
    desDOM(ndRef.childNodes[nCount]);
  }
}

傳入參數ndRef為欲探訪的節點參照,每次的遞迴皆往更深一層的結構做探訪,且在同一層的遞迴裡作全部的子節點掃描(透過子節點集合childNodes,這樣就保證不會有遺漏或沒有探訪到的節點了,這留待下一個篇幅,再來探討遞迴式的函數設計。

----------
關於第(四)個步驟,這個部份可以說是整個解析函式最難的部份也是整個解析函式的精華,因為我們需要用到表格對我們的輸出結果作排版,雖說表格是能動態建立的,可是在建立時面臨第一個問題,如何確立表格結構有多少列(rows)?和多少儲存格(cells)?畢竟在開始解析每個節點內容,及將之填到表格結構的相對位置前,表格是必須先一步建立的,又每個網頁的內容都不同,這代表連表格rows、cells數量的建立也必須是根據整個文件DOM深度及節點數量而變動的,因此對於表格的建立勢必要進一步詳細的估算、演算。以下面這個解析結果例子為例,想要這樣複雜的輸出版面(如果存在這種DOM樹形結構)

     ←隨網頁的DOM結構深度變化→
0 1 2  3  4  5  6  7  8  9
1 節點┬─子節點1┬→子節點4
2   │     ├→子節點5┬→子節點7
3   │     │     ├→子節點8┬→子節點D
4   │     │     │     ├→子節點E
5   │     │     │     └→子節點F
6   │     │     ├→子節點9┬→子節點G
7   │     │     │     └→子節點H
8   │     │     └→子節點A
9   │     └→子節點6┬→子節點B─→子節點I
A   │           └→子節點C
B   ├→子節點2
C   └→子節點3

隨整個DOM會用到的列數量而變化

那麼對於一個表格來說,定義如下:

1.一個節點名稱佔有一個表格欄
2.表格欄的前一欄位必為一個分支符號
3.唯一前方沒有分支符號的節點是整個樹形結構的根節點(以上例為節點1)
4.保留第一列所有欄位,作為填上該網頁DOM結構深度的號碼用。
5.保留所有的列的第一個欄位,作為填上列的號碼用。
6.表格欄的數量便為整個DOM樹狀結構的深度再乘上2。(註1)
7.表格列的數量為每一層的子節點群數量-1,對每一層做加總。(註2)

(註1)每一列的表格欄數量算法是從這來的:

設想一個網頁的DOM結構像這樣:

父節點┬→子節點1┬→子節點3
   │     └→子節點4
   └→子節點2

這個網頁的DOM結構,要探訪三層,深度為3,在每一個節點前方都需要一個分支符號(以上例,除了父節點以外)。

故總是產生結構深度*2個表格欄(或稱儲存格、欄)便足以容納解析函式的解析結果了。

----

(註2)表格列的數量的算法是從這來的:

首先由最簡單的情況開始:

父節點┬→子節點1
   └→子節點2

此例中,父節點下只有一個子節群,且數量為2,所以會用到兩列。

漸漸複雜的情況:

父節點┬→子節點1┬→子節點3
   │     ├→子節點4
   │     ├→子節點5
   │     └→子節點6
   └→子節點2

就觀察的結果會發現,此例中,表格會到用的列數為(第一個子節點群數量-1)+(第二個子節點群數量-1)其中,還要把會用到欄位號碼的最上一列算下去,故加總的數量還要+1,整個總列數的通用演算法可以延展成第1個子節點群數量+.+第N個子節點群數量-N+1

考慮每一層的第一個子節點皆和上一層的父節點佔到相同的列號,所以在每一個子節點群計算而得的列數還要減上1,很快的可以發現這又是一個遞迴問題,每一個子節點群只要負責計算自己傾印時會佔到的列數,並將它減一後,傳回上一層作遞迴的加總,直到最終遞迴函式又回到父節點時,所得到的數量便是DOM樹形結構傾印於表格中時,會用到的最大列數了。

至此,算是分析出如何產生對應動態表格的一個演算規則了,其餘的部份,因為還要時間撰寫,而且我也想再對TABLE這個網頁上的排版元件作補充,就先寫到這,都留到下個篇幅再討論。

以上

iceShadow
回頂端
檢視會員個人資料 發送私人訊息
iceShadow
偶而上來逛逛的過客


註冊時間: 2007-05-24
文章: 10

0.00 果凍幣

發表發表於: 2007-6-24, PM 6:53 星期日    文章主題: 引言回覆

續上一篇

求得DOM結構深度(表格最大欄數)的遞迴函式
代碼:

/*************************
輸入引數
ndRef:起始節點的參照

傳回值:結構最大深度 maxCols
***************************/
function getMaxCols(ndRef){
  var nNum = ndRef.childNodes.length;
  var maxCols = 1;
  var currCols, nCount;
  if (nNum == 0 ) return maxCols; //遞迴的終止條件
  for (nCount=0; nCount < nNum; nCount++){
    currCols = getMaxCols(ndRef.childNodes[nCount]);
    if (currCols > maxCols) maxCols = currCols;
  }
  return maxCols+1;
}

在設計遞迴函式時,需要很小心的設一個終止遞迴的條件,以上例而言:

1.遞迴的終止條件為「當該節點再也沒有子節點時」遞迴函式便返回代表此節點層數的1。
代碼:

if (nNum == 0 ) return MaxCols;


2.如果該節點還有子節點群,那便再逐一探訪該節點下的子節點群
代碼:

getMaxCols(ndRef.childNodes[nCount]);


3.比較各個子節點中,哪一個深度最大,取最大深度的那一個
代碼:

if (currCols > maxCols) maxCols = currCols;


4.子節點群中最大的深度+1(代表目前這層,故要加1)傳回給上一層的遞迴函式。
代碼:

return maxCols+1;


此外,遞迴函式的設計不獨是高階語言或物件導向語言的專利喔,就連組合語言也是可以進行遞迴設計的,遞迴設計的使用時機通常用來簡化演算法規則,或將分析的行為簡化成簡單的步驟,這地方若有對遞迴函式呼叫在觀念理解上的困難,也歡迎提出討論。

註:
就iceShadow印象中,不知有沒有出錯,有傳回值的副程序是稱「函數」,無傳回值的副程序稱「函式」,在這裡iceShadow皆以函式稱謂。
----

※求得表格最大列數的遞迴函式
代碼:

/****************************
輸入引數:
ndRef:某節點的參照

傳回值:該節點下的DOM在特定表格結構中佔有的最大列數
*****************************/
function getMaxRows(ndRef){
  var nNum = ndRef.childNodes.length;
  var maxRows = 0;
  var nCount;
  if (nNum == 0) return 0; //遞迴的終止條件
  for (nCount=0; nCount < nNum; nCount++) {
    maxRows += getMaxRows(ndRef.childNodes[nCount]);
  }
  return (nNum-1) + maxRows;
}

這個遞迴函式和上一個遞迴函式很像,不同的是,在每一層的節點結構裡,該節點都只要負責傳回該層的子節點群數量-1,然後把下一層子節點傳回來的結果作加總,如此便完成了,最大列數計算。如果還對遞迴不甚瞭解的人,可以比較這一個遞迴函式和上一個遞迴函式有什麼不同喔。

----
※動態建立表格

在之前的篇幅裡有提到動態產生表格的方法,現在我們可以將它定義成一個動態產生表格的函式,並稍加修改,只要指定要產生幾列幾行的表格,它便會自動產生符合我們所需要的表格,並傳回該表格的參照,另外在下面的SCRIPT裡也可以看到如何用SCRIPT設定一個網頁元件的性質進而改變它的構造和外觀喔。
代碼:

/***********************************************
傳入引數
argRows:欲建構的表格列的數量
argCols:欲建構的表格欄的數量

傳回值:table物件的參照
*************************************************/
function createTableObject(argRows, argCols){

//宣告並初始化會使用的變數
  var crtTblObj = document.createElement("TABLE");
  var crtTbdObj = document.createElement("TBODY");
  var crTrObj, crtTdObj, crtTtObj, rowCount, colCount;

//建立表格第一列→數字列
  crtTrObj = document.createElement("TR");
  for (colCount=0; colCount < argCols; colCount++){
    crtTdObj = document.createElement("TD");
    crtTdObj.colSpan = 2; //註:第一列一次合併兩個儲存格
    crtTdObj.align= "center"; //註:第一列的數字在儲存格中置中
    crtTtObj = document.createTextNode(colCount+1);
    crtTdObj.appendChild(crtTtObj);
    crtTrObj.appendChild(crtTdObj);
  }
  crtTbdObj.appendChild(crtTrObj); //不要忘了和TBODY作連結

//建立表格其它列
  for ( rowCount=0; rowCount < argRows ; rowCount++) {
    crtTrObj = document.createElement("TR");
    for ( colCount=0; colCount < (argCols*2) ; colCount++){
      crtTdObj = document.createElement("TD");
      crtTdObj.align="left";
      if (colCount==0) { //每行第1個欄位總是填入列號
        crtTtObj = document.createTextNode(rowCount+1);
        crtTdObj.appendChild(crtTtObj);
      }
      crtTrObj.appendChild(crtTdObj);
    }
    crtTbdObj.appendChild(crtTrObj);
  }
  crtTblObj.appendChild(crtTbdObj);
 
  return crtTblObj;
}


表格的第一列是用在放置節點深度的號碼,所以第一列的建構與其它列獨立出來,且因為第一列很特殊,因為除了第一列以外,之下的任一列都包含著分支符號的欄位和節點名稱的欄位,所以這裡有用到一個表格的合併儲存格的技術,使用的性質是colSpan,它的意思就是要合併幾個儲存格的意思,且因為分支符號的欄位+節點名稱的欄位總是argCols*2,故用argCols個欄位,且每個欄位是二個欄位的合併剛好足以表示這個表格結構,但這得需要對表格對於合併儲存格的構造有深一層的瞭解,在此也可以看到如何動態設定一個節點元件性質的方法,如果知道該性質的名稱,那在SCRIPT裡要設定實在太簡單了,如果有人覺得看起來太晦澀的、也不太好懂的部份,iceShadow會再找機分享一些自己在方面所理解的原則。
其實,這個函式的定義,可以說是完全根據所需要的表格格式而去設計的,目地只是要主程式裡的撰寫不那麼混亂蕪雜,所以設計的很拙也別笑我呀. XD

除了表格第一列之外,表格的其它列的第一個欄位是定義用來放置列號的,所以只要是第一個欄位,就會放入列號文字,在這裡用的是簡單的判斷:
代碼:

if (colCount==0) {
  crtTtObj = document.createTextNode(rowCount+1);
  crtTdObj.appendChild(crtTtObj);
}

當colCount為0時,代表該欄為該列的第一個欄位。

在這三個iceShadow撰寫的程式示範裡,有用到幾個程式技巧:

變數的命名規則:因為〝空白〞(ASCII 32)在變數命名裡,會造成一些演算法語法在解釋上混淆的問題,所以空白並不被提倡用在變數命名裡(雖然那是程式語言最後的理想),所以聰明的人就想到方便方法又不會混淆且可以讓人便於由變數名稱理解變數意義的方法,例如在個別的字義之間+底線來連接各別的字義,如〝my_home〞。還有一種方式是縮寫,例如object→obj,一種方式是對該變數命名的每個各別字義開頭一個字母大寫,以使閱讀的人便於區分,如〝MyHome〞,這種命名技術稱為Pascal casing。另外,還有一種和Pascal casing很類似的命名技術,除了第一個字義的開頭字母小寫之外,其餘的各別字義的第一個字母一律大寫,如〝myHome〞,這種命名技術稱為camel casing,或許是因為習慣的關係,iceShadow採用的是camel casing的變數命名技術(我也是後來才知道原來這種命名方式叫camel casing),其它諸如全取各別字義的第一個字母大寫,並輔助以一個單字縮寫代表該變數的功能,如〝MHobj〞,obj就是object的縮寫..等等,命名的方法有很多很多,iceShadow是覺得用的習慣就好,其實iceShadow有時候也會用底線([bolod]underline)區隔字義且和camel casing命名技術混用在同一個變數命名裡喔,另外,有一點不能不提,JavaScript和VBScript不太一樣喔,JavaScript的變數命名會區分大小寫,VBScript則不會,這是要注意的地方。

縮排:良好的縮排有助閱讀程式(雖然對直譯器/編譯器有沒縮排都沒差)。

註解:對於每個函式,開頭都寫明它是做什麼用,這對於以後的維護將會很方便,看看函式開頭寫什麼,便大致瞭解這個函式是在做什麼用的。在程序必要的地方做註解,這對於日後或許自己會忙到忘了當初自己是怎麼寫某一個程序時,會提供莫大的幫助。

函式的命名規則:同變數命名規則一樣,其實iceShadow是蠻習慣在自己撰寫的,在JavaScript裡函式名稱最後加一個底線(underline)再加上撰寫日期 (date),這樣也方便知道自己是在什麼時候撰寫這個函式,又不怕日後再寫新的程序時(尤其是幾個已經建好的程序組在要互相配合時)名稱會有所衝突,其實在C++裡,也有一種幫我們減輕因函式命名在記憶上造成負擔的方法,叫重載?的樣子,其方式便是透過C++編譯對我們所撰寫的函式進行名稱分裂的管理,但那扯遠了,話回正題,或許除了iceShadow提供的做法之外,有人會有更好的做法也說不定,也歡迎大家都提出來討論。Smile

至此,算是完成部份網頁DOM自我列印的題材,其實並沒有完全談完,也應該要談DOM主要解析函式的遞迴設計,不過解析函式的主體遞迴設計可說比上面單純的求列、求行的遞迴設計複雜許多,本來因為fireFox和微軟瀏灠器的一些問題擱置了不少的時間,iceShadow另外也有重新改寫了網頁DOM自我傾印的程式組,在上面篇幅所提到的完整程式範本在這個主題最後會附上,另外,在上一篇的預告裡,iceShadow本來想在這篇談表格的儲存格的合併觀念和問題,不過好像要用掉不少篇幅的樣子,需要時間撰寫,也因此就先以自己對於JavaScript在類別設計上的瞭解作為這個篇幅的結尾。

※JavaScript的類別/結構

在HTML的腳本語言JavaScript中function關鍵字除了有函數、函式的宣告功能,它有另一個功能便是可以用來宣告一個類別(或稱結構?),方法如下:
代碼:

function my_className(){
  this.dataName =..;
  this.funName = ..;
}

其中my_className為類別的名稱,而任何在JavaScript中以function作的類別內容宣告,都必須加上一個關鍵字this,然後以〝.〞和變數名稱作連結,以代表這個變數資料是屬於這個類別的,變數內容的宣告可以是一個資料(或稱資料成員),也可以是一個函數(或稱函數成員)。


※JavaScrip中的函數成員(Member)

在JavaScript中要指定函數成員是很簡單的事,你可以先寫好一個函數:

例如:
代碼:

function sum_test (a, b){
  return  (a+b);
}

然後
代碼:

function my_class(){
  this.sum = sum_test;
}

這種宣告方式是被接受的,在上面我們宣告一個sum_test的函數,在接下來我們宣告一個類別,並將類別中的sum函數成員指向sum_test函數,乍看之下好像不好理解,其實當我們直接引用該函數名稱,而不加()符號時,則該函數名稱是被當成「函數指標」來使用的,而不是「呼叫該函數」,用這種觀點來看,就很好理解了。

※類別的函數成員對於資料成員的取用

假如,我們在類別中宣告一個資料成員也宣告一個函數成員,那麼函數成員該如何取用該類別的資料成員呢?答案是透過this指標。例如:

代碼:

function my_fun(){
  return this.data;
}

function my_class(){
  this.data = 5;
  this.getData = my_fun;
}


則當我們以my_class作為樣板產生一個物件時
代碼:

var my_object = new my_class();


代碼:

var i = my_object.getData();  // i = 5

// my_object.getData 指向 my_fun() 函式
// 在my_fun()函式被呼叫時,在my_fun()函式裡的this關鍵字參考的便是my_object這個物件。


亦即,當一個函數被宣告為某類別的函數成員時,此函數中的this指標便是指向此類別,在該函數中要取用所屬類別的資料成員時,只需要用this.dataName方式便可以取用了。其實除了這個方法可以直接取用類別的資料成員外,也可以用如下的方式取得類別的資料成員:
代碼:

var i = my_object.data;

它和 my_object.getData() 所取得的資料是一樣的,可是如果不是有必要,建議盡量少這麼做,因為JavaScript中的類別宣告全是 public的,如果養成這種不好的習慣,對將來學習類別觀念中尚有private、protected宣告時,便會造成困擾。所以若要存取類別的資料還是盡量以函數成員來設計取用會比較好。

關於類別在JavaScript中的設計,iceShadow在網頁DOM自我列印的主題的範本裡也有附上一個類別版的範本(在稍後會把整段程式碼都放上來),對JavaScript如何被用來設計類別還不清楚的人,或許也可以參考一下喔。

以上

iceShadow
回頂端
檢視會員個人資料 發送私人訊息
iceShadow
偶而上來逛逛的過客


註冊時間: 2007-05-24
文章: 10

0.00 果凍幣

發表發表於: 2007-6-24, PM 7:07 星期日    文章主題: 引言回覆

再談表格

※表格之儲存格的合併

儲存格的合併有分「行方向」的合併(colSpan),和「列方向」的合併(rowSpan)。

行的欄位合併colSpan意思是:要往右合併幾個儲存格的意思
列的欄位合併rowSpan意思是:要往下合併幾個儲存格的意思

以下我將以三種情況為例,做個概括性的說明,設一個表格像這樣,以一個○代表一個儲存格:

○○○
○○○
○○○

那麼儲存格的合併是根據下面的原則來做的

--------------
一、行的欄位合併

若同一行中的欄位要合併,那麼指定要合併行儲存格的數量參數,必須在位於最左的那一個儲存格指定,其餘被合併的儲存格必須消掉。

例如,要合併如下著色部份的儲存格

●●●
○○○
○○○

除了該行的著藍色部份那一個儲存格要指定colSpan=3以外,其餘被合併的儲存格(著紅色部份)都要消掉,其HTML表格語法像這樣:
<table>
<tr>
<td colSpan=3>1</td><td>2</td><td>3</td>
</tr>
<tr>
<td>4</td><td>5</td><td>6</td>
</tr>
<tr>
<td>7</td><td>8</td><td>9</td>
</tr>
</table>
註:
]藍色字體的部份,是要指定合併幾個「行儲存格」的HTML標籤
紅色字體的部份,是要消掉的HTML標籤

TABLE
 │
 └TBODY
   │
   ├─TR
   │ └─TD(colSpan=3)
   │   └#text
   ├─TR
   │ ├─TD
   │ │ └#text
   │ ├─TD
   │ │ └#text
   │ └─TD
   │   └#text
   └─TR
     ├─TD
     │ └#text
     ├─TD
     │ └#text
     └─TD
       └#text

--------------
二、列的欄位合併

若同一行中的列欄位要合併,那麼指定要合併行儲存格的數量參數,必須在位於最上的那一個儲存格指定,其餘被合併儲存格必須消掉。

例如,要合併如下著色部份的儲存格

○○
○○
○○

除了該列的那一個儲存格要指定rowSpan=3以外,其餘被合併的儲存格都要消掉,其HTML表格語法像這樣
<table>
<tr>
<td rowSpan=3>1</td><td>2</td><td>3</td>
</tr>
<tr>
<td>4</td><td>5</td><td>6</td>
</tr>
<tr>
<td>7</td><td>8</td><td>9</td>
</tr>
</table>
註:
藍色字體的部份,是要指定合併幾個「列儲存格」的HTML標籤
紅色字體的部份,是要消掉的HTML標籤
其DOM構造像這樣

TABLE
 │
 └TBODY
   │
   ├─TR
   │ ├─TD(rowSpan=3)
   │ │ └#text
   │ ├─TD
   │ │ └#text
   │ └─TD
   │   └#text
   ├─TR 
   │ ├─TD
   │ │ └#text
   │ └─TD
   │   └#text
   └─TR 
     ├─TD
     │ └#text
     └─TD
       └#text



三、複合的「行欄位合併」+「列欄位合併」

若一堆儲存格要合併,其合併的結果是m*n的矩形,那麼指定要合併的行儲存格和列儲存格的數量參數,必須在整個矩形最左上的那一個儲存格指定,其餘被合併的儲存格則必須消掉。

例如,要合併如下著色部份的儲存格

○○○

●●

除了左上那一個儲存格要指定colSpan=2和rowSpan=2以外,其餘被合併的儲存格要消掉,其HTML表格語法像這樣
<table>
<tr>
<td>1</td><td>2</td><td>3</td>
</tr>
<tr>
<td>4</td><td colSpan=2 rowSpan=2>5</td><td>6</td>
</tr>
<tr>
<td>7</td><td>8</td><td>9</td>
</tr>
</table>
註:
藍色字體的部份,是要指定合併幾個「行儲存格」和「列儲存格」的HTML標籤
紅色字體的部份,是要消掉的HTML標籤

其DOM構造像這樣

TABLE
 │
 └TBODY
   │
   ├─TR
   │ ├─TD
   │ │ └#text
   │ ├─TD
   │ │ └#text
   │ └─TD
   │   └#text
   ├─TR 
   │ ├─TD
   │ │ └#text
   │ └─TD(colSpan=2 rowSpan=2)
   │   └#text
   └─TR 
     └─TD
       └#text


總之,要要瞭解TABLE的DOM構造之前,在HTML下合併儲存格觀念要必須要先熟練、也先懂喔,如此,寫起SCRIPT才能得心應手。在稍後附上的程式範本裡,如果對DOM構造還有疑問的人,可以在該範本裡的網頁BODY區,寫下自己想要瞭解的HTML標籤結果,然後再執行該網頁,它便會將該網頁新加入的標籤的DOM構造也一同列印出來成為網頁內容喔。

以上

iceShadow
回頂端
檢視會員個人資料 發送私人訊息
iceShadow
偶而上來逛逛的過客


註冊時間: 2007-05-24
文章: 10

0.00 果凍幣

發表發表於: 2007-6-24, PM 7:10 星期日    文章主題: 引言回覆

這部份是寫給想要瞭解範本裡是如何完成DOM結構解析工作的人的一個參考,如果沒有興趣的人也可以跳過喔。

※網頁DOM自我傾印程式的主遞迴函式
代碼:

/****************DOM解析函式的呼叫入口****************
  輸入引數
  ndRef:欲解析的節點
 
  返回值:TABLE物件(一個以TABLE物件作排版的DOM圖狀資料)
***************************************************/

function getDesDOM(ndRef){
   var maxCols = getMaxCols(ndRef); //計算要表格每一列有多少欄位
   var maxRows = getMaxRows(ndRef); //計算要表格有多少列
   var tblObj = createTableObject(maxRows+1, maxCols);
   desDOM(ndRef, tblObj, 0, 0); //進入主遞迴函式,解析節點內容
   return tblObj; //傳回建構好表格物件
}


在真正進入解析DOM節點的主遞迴函式前,有一些前置工作要做,這個函式只做建好足夠容納結果的表格物件後,其後真正的解析工作(填表的動作),便交給主遞迴函式去做,再主遞迴函式做好填表工作後,再回到這個函式時,由這個函式再將填好的表格物件傳回。

------
代碼:

/************DOM解析函式的主遞迴函式********
 輸入引數
  ndRef:欲解析的節點
  tblRef:解析後轉換的圖形結構所要填入的表格物件參照
  rowRef:該節點在表格物件中相對於baseRow的開始列號
  colRef:該節點在表格物件中相對於baseCol的開始欄號
 
  在函式中,會改變tblRef表格物件的內容
  傳回值:無
**************************************/

//全域變數
var baseRow=1; //特定表格的起始列號
var baseCol=1; //特定表格的起始欄號
var rowOff=0;  //子節點群佔有的列偏移量

function desDOM(ndRef, tblRef, rowRef, colRef){
  var nNum = ndRef.childNodes.length;
  var currRow = baseRow +rowRef; //目前這層節點該填在的列
  var currCol = baseCol +colRef; //目前這層節點該填在的欄
  var crtTtObj = document.createTextNode(ndRef.nodeName + "(" + ndRef.nodeType + ")" );
  var nCount;
  var oldRowOff;
  tblRef.rows[currRow+rowOff].cells[currCol].appendChild(crtTtObj);
  if (nNum == 0) return true; //遞迴的終止條件(該節點再無子節點群)
  for (nCount=0; nCount < nNum ; nCount++){
   oldRowOff = drawLine(nCount, nNum, tblRef, currRow, currCol, oldRowOff);
   desDOM(ndRef.childNodes[nCount], tblRef, rowRef+nCount, colRef+2);
  }
  rowOff += (nNum-1); //當結束某一層的探訪時,將列偏移值作加總
}

這個遞迴函式看似很複雜,其實不然,它是依這樣的順序工作的

1.計算目前這層的節點應該填在表格中的列號、欄號
2.在表格的相對位置填入這層節點的nodeName(節點名稱)+nodeType(節點型態)性質
3.如果目前這層已無子節點群,那便跳到步驟7,結束這次探訪。
4.判斷與計算目前這層節點該有的分支符號,並填入表格中的對應位置。
5.逐一對這層節點的所有子節點群作1~6的動作。
6.當子節點群探訪結束時,將子節點群的數量-1,並且和保存列偏移的變數相加。
7.結束函式。

currCol的行欄位定位值的計算是由這樣來的:
每一層節點的子節點群其節點名稱在表格中的位置,總和父節點的節點名稱隔了一個分支符號的欄位,所以在探訪子節點時,總是將它+2,傳給下一層的子節點,便沒太大問題。

currRow的列號定位值的計算是由這樣來的:
同一層的子節點群,在正常情況下,應該是傾印在下一列的,可是如果其中一個節點還有子節點群的話,那麼目前這層的子節點群傾印就不能照這樣計算,因此,在這裡用了一個全域變數rowOff,用以在每一層的子節點群傾印完畢後,便將列偏移值加入rowOff變數裡,也因此保證currRow+rowOff一定可以填在表格中的適當位置。

不過,不管是列定位、行定位或畫分支符號的函式都是根據上上一篇幅裡想要得到的表格輸出結果而設計演算的,如果想要對DOM有不同的表示圖形時,在這裡的列定位、行定位和畫分支符號的函式都要重新再設計才行。至此,網頁自我DOM結構傾印的主題算是告一段落。其實這個主題也算是有點冷門啦,不過對於如果想要瞭解網頁元件的DOM構造的人,我覺得可以將它視為一個不錯的輔助工具。

以上

iceShadow
回頂端
檢視會員個人資料 發送私人訊息
iceShadow
偶而上來逛逛的過客


註冊時間: 2007-05-24
文章: 10

0.00 果凍幣

發表發表於: 2007-6-24, PM 7:16 星期日    文章主題: 網頁DOM自我傾印 函式設計版 引言回覆

附錄一:自我傾印DOM結構的網頁 - 一般函式設計版

將下面兩個部份的代碼,分別存成指定的名稱後,放在同一個資料夾下

HTML文件的頁碼(請存成*.html 或 *.htm)
代碼:

<Script language="JavaScript" src="./domlib.js"></Script>
<Script language="JavaScript">
function htmlInit(){
  var j = getDesDOM(top.document);
  document.body.appendChild( j );
}
</Script>
<html>
<head>
 <meta http-equiv=Content-Type content="text/html; charset=Big5">
 <title>文件物件模型(DOM)傾印</title>
</head>
<body onload="htmlInit()"></body>
</html>


domlib.js檔案的JavaScript碼 (請存成檔名 domlib.js)
代碼:

/********取得節點之下的最大深度***********
   輸入引數
   ndRef:起始節點的參照

   傳回值:結構最大深度 maxCols
*/
function getMaxCols(ndRef){
   var nNum = ndRef.childNodes.length;
   var maxCols = 1;
   var currCols, nCount;
   if (nNum == 0 ) return maxCols;
   for (nCount=0; nCount < nNum; nCount++){
      currCols = getMaxCols(ndRef.childNodes[nCount]);
      if (currCols > maxCols) maxCols = currCols;
  }
  return maxCols+1;
}

/*******取得節點之下的DOM構造在特定表格結構中佔有的列數********
   輸入引數:
   ndRef:某節點的參照
   
   傳回值:該節點下的DOM在特定表格結構中佔有的最大列數
*/
function getMaxRows(ndRef){
  var nNum = ndRef.childNodes.length;
  var maxRows = 0;
  var nCount;
  if (nNum == 0) return 0;
  for (nCount=0; nCount < nNum; nCount++) {
    maxRows += getMaxRows(ndRef.childNodes[nCount]);
  }
  return (nNum-1) + maxRows;
}

/*****建立特定表格物件*******
   傳入引數
    argRows:欲建構的表格列數
    argCols:欲建構的表格行數
   傳回值:用以顯示DOM結構的table物件
*/
function createTableObject(argRows, argCols){
  var crtTblObj = document.createElement("TABLE");
  var crtTbdObj = document.createElement("TBODY");
  var crTrObj, crtTdObj, crtTtObj, rowCount, colCount;

  //建立表格第一列→數字列
  crtTrObj = document.createElement("TR");
  for (colCount=0; colCount < argCols; colCount++){
    crtTdObj = document.createElement("TD");
    crtTdObj.colSpan = 2;
    crtTdObj.align= "center";
    crtTtObj = document.createTextNode(colCount+1);
    crtTdObj.appendChild(crtTtObj);
    crtTrObj.appendChild(crtTdObj);
  }
  crtTbdObj.appendChild(crtTrObj); //不要忘了和TBODY作連結
 
  //建立表格其它列
 for ( rowCount=0; rowCount < argRows; rowCount++) {
    crtTrObj = document.createElement("TR");
    for ( colCount=0; colCount < (argCols*2) ; colCount++){
      crtTdObj = document.createElement("TD");
      crtTdObj.align="left";
      if (colCount==0) { //每行第1個欄位總是填入列號
       crtTtObj = document.createTextNode(rowCount+1);
       crtTdObj.appendChild(crtTtObj);
      }
      crtTrObj.appendChild(crtTdObj);
    }
    crtTbdObj.appendChild(crtTrObj);
  }
  crtTblObj.appendChild(crtTbdObj);
  return crtTblObj;
}


var baseRow=1; //特定表格的起始列號
var baseCol=1; //特定表格的起始欄號
var rowOff=0;  //子節點群佔有的列偏移量

/******DOM解析函式的呼叫入口*******
  輸入引數
  ndRef:欲解析的節點
 
  返回值:TABLE物件(一個以TABLE物件作排版的DOM圖狀資料)
*/
function getDesDOM(ndRef){
   var maxCols = getMaxCols(ndRef);
   var maxRows = getMaxRows(ndRef);
   var tblObj = createTableObject(maxRows+1, maxCols);
   desDOM(ndRef, tblObj, 0, 0);
   return tblObj;
}

/******DOM解析函式的主遞迴函式******
 輸入引數
  ndRef:欲解析的節點
  tblRef:解析後轉換的圖形結構所要填入的表格物件參照
  rowRef:該節點在表格物件中相對於baseRow的開始列號
  colRef:該節點在表格物件中相對於baseCol的開始欄號
 
  在函式中,會改變tblRef表格物件的內容
  傳回值:無
*/
function desDOM(ndRef, tblRef, rowRef, colRef){
   var nNum = ndRef.childNodes.length;
   var currRow = baseRow +rowRef;
   var currCol = baseCol +colRef;
   var crtTtObj = document.createTextNode(ndRef.nodeName + "(" + ndRef.nodeType + ")" );
   var nCount;
  var oldRowOff;
  tblRef.rows[currRow+rowOff].cells[currCol].appendChild(crtTtObj);
   if (nNum == 0) return true;
   for (nCount=0; nCount < nNum ; nCount++){
    oldRowOff = drawLine(nCount, nNum, tblRef, currRow, currCol, oldRowOff);
    desDOM(ndRef.childNodes[nCount], tblRef, rowRef+nCount, colRef+2);
  }
  rowOff += (nNum-1);
}

/******畫分支符號的函式******
  傳入引數
  nNode:子節點群中的第幾個節點?
  mNode:子節點群的數量
  tRef:欲填入分支符號的表格物件參照
  rRef:該節點名稱所在列的基本量
  cRef:該節點名稱所在行的基本量
  oldOff:列偏移量

  在函式中會改變表格參照的內容
  若新、舊的列偏移量不同
  傳回值:新偏移量
*/
function drawLine(nNode, mNode, tRef, rRef, cRef, oldOff){
  var sCount, crtTtObj;
  switch (nNode){
       case 0:
         if (mNode == 1) // 第一個子節點且餘下沒有其它子節點
                   crtTtObj = document.createTextNode("─→");
                 else
                   crtTtObj = document.createTextNode("┬→");
        break;
       case (mNode-1): // 最後一個子節點
                 crtTtObj = document.createTextNode("└→");
        break;
       default:  // 介於第一個子節點和最後一個子節點間
                 crtTtObj = document.createTextNode("├→");
        break;
  }
  tRef.rows[rRef+rowOff+nNode].cells[cRef+1].appendChild(crtTtObj);
 
  if (oldOff != rowOff) { //新舊偏移量的不同,代表上一個節點其子節點群已讓列產生偏移
    for (sCount=oldOff; sCount < rowOff; sCount++){
      crtTtObj = document.createTextNode("│");
      tRef.rows[rRef+sCount+nNode].cells[cRef+1].appendChild(crtTtObj);
    }
    return rowOff;
  }
  return oldOff;
}


iceShadow 在 2007-6-26, PM 1:33 星期二 作了第 1 次修改
回頂端
檢視會員個人資料 發送私人訊息
iceShadow
偶而上來逛逛的過客


註冊時間: 2007-05-24
文章: 10

0.00 果凍幣

發表發表於: 2007-6-24, PM 7:24 星期日    文章主題: 網頁DOM自我傾印 - 類別設計版 引言回覆

附錄二:自我傾印DOM結構的網頁 - 類別設計版

請將以下兩部份的代碼分別存成指定檔名,並放在同一個資料夾下

HTML文件的頁碼(請存成*.html 或 *.htm)
代碼:

<Script language="JavaScript" src="./domclass.js"></Script>
<Script language="JavaScript">
function htmlInit(){

//方式一
  var i = new DOMTable(top.document);
  document.body.appendChild(i.getDesDOM());

//或方式二,兩者擇一而用
  var i = new DOMTable();
  var k = i.getDesDOM(top.document);
  document.body.appendChild(k);
}
</Script>
<html>
<head>
 <meta http-equiv=Content-Type content="text/html; charset=Big5">
 <title>文件物件模型(DOM)傾印</title>
</head>
<body onload="htmlInit()">
</body>
</html>

domclass.js的JavaScript碼(請存成檔名domclass.js)
代碼:

/********DOM解析類別**********
 功能:
 分析傳入的節點,並產生對應的圖狀結構
 
 輸入引數
  ndRef:欲解析的節點
 
 無輸入引數時
  可利用getDesDOM函式成員輸入ndRef
 
 資料成員:
 this.baseRow:特定表格的起始列號
 this.baseCol:特定表格的起始欄號
 this.rowOff:子節點群佔有的列偏移量
 this.tblRef:特定表格的內部參照

 函數成員:
 this.getMaxRows: 取得節點之下的子節點群佔有的最大列數
 this.getMaxCols: 取得節點之下的最大深度
 this.createTableObject: 建立特定表格物件
 this.getDesDOM: 解析函式的入口
 this.desDOM: DOM解析函式的主要遞迴函式
 this.drawLine: 分支符號畫線程式
*/
function DOMTable(ndRef){
   this.baseRow = 1;
   this.baseCol = 1;
   this.rowOff = 0;
   this.tblRef = null;
   
   this.getMaxRows = classDOMTable_getMaxRows_20070531;
   this.getMaxCols = classDOMTable_getMaxCols_20070531;
   this.createTableObject = classDOMTable_createTableObject_20070531;
   this.getDesDOM = classDOMTable_getDesDOM_20070531;
   this.desDOM = classDOMTable_desDOM_20070531;
   this.drawLine = classDOMTable_drawLine_20070531;
   
   if (ndRef != undefined )   this.getDesDOM(ndRef);
}

/********取得節點之下的最大深度***********
   輸入引數
   ndRef:起始節點的參照

   傳回值:結構最大深度 maxCols
*/
function classDOMTable_getMaxCols_20070531(ndRef){
   var nNum = ndRef.childNodes.length;
   var maxCols = 1;
   var currCols, nCount;
   if (nNum == 0 ) return maxCols;
   for (nCount=0; nCount < nNum; nCount++){
      currCols = this.getMaxCols(ndRef.childNodes[nCount]);
      if (currCols > maxCols) maxCols = currCols;
  }
  return maxCols+1;
}

/*******取得節點之下的DOM構造在特定表格結構中佔有的列數********
   輸入引數:
   ndRef:某節點的參照
   
   傳回值:該節點下的DOM在特定表格結構中佔有的最大列數
*/
function classDOMTable_getMaxRows_20070531(ndRef){
  var nNum = ndRef.childNodes.length;
  var maxRows = 0;
  var nCount;
  if (nNum == 0) return 0;
  for (nCount=0; nCount < nNum; nCount++) {
    maxRows += this.getMaxRows(ndRef.childNodes[nCount]);
  }
  return (nNum-1) + maxRows;
}

/*****建立特定表格物件*******
   傳入引數
    argRows:欲建構的表格列數
    argCols:欲建構的表格行數
   傳回值:用以顯示DOM結構的table物件
*/
function classDOMTable_createTableObject_20070531(argRows, argCols){
  var crtTblObj = document.createElement("TABLE");
  var crtTbdObj = document.createElement("TBODY");
  var crTrObj, crtTdObj, crtTtObj, rowCount, colCount;

  //建立表格第一列→數字列
  crtTrObj = document.createElement("TR");
  for (colCount=0; colCount < argCols; colCount++){
    crtTdObj = document.createElement("TD");
    crtTdObj.colSpan = 2;
    crtTdObj.align= "center";
    crtTtObj = document.createTextNode(colCount+1);
    crtTdObj.appendChild(crtTtObj);
    crtTrObj.appendChild(crtTdObj);
  }
  crtTbdObj.appendChild(crtTrObj); //不要忘了和TBODY作連結
 
  //建立表格其它列
 for ( rowCount=0; rowCount < argRows; rowCount++) {
    crtTrObj = document.createElement("TR");
    for ( colCount=0; colCount < (argCols*2) ; colCount++){
      crtTdObj = document.createElement("TD");
      crtTdObj.align="left";
      if (colCount==0) { //每行第1個欄位總是填入列號
       crtTtObj = document.createTextNode(rowCount+1);
       crtTdObj.appendChild(crtTtObj);
      }
      crtTrObj.appendChild(crtTdObj);
    }
    crtTbdObj.appendChild(crtTrObj);
  }
  crtTblObj.appendChild(crtTbdObj);
  return crtTblObj;
}

/******DOM解析函式的呼叫入口*******
  輸入引數
  ndRef:欲解析的節點
 
  返回值:TABLE物件(一個以TABLE物件作排版的DOM圖狀資料)
  若沒啟動解析函式過且也沒指定解析節點,則傳回null
*/
function classDOMTable_getDesDOM_20070531(ndRef){
    var maxRows, maxCols;
    if (this.tblRef == null ) { //如果還沒啟動過解析函式
       if (ndRef != undefined ) {
      maxCols = this.getMaxCols(ndRef);
      maxRows = this.getMaxRows(ndRef);
      this.tblRef = this.createTableObject(maxRows+1, maxCols);
      this.desDOM(ndRef, 0, 0);
    }
   }
   return this.tblRef;
}

/******DOM解析函式的主遞迴函式******
 輸入引數
  ndRef:欲解析的節點
  rowRef:該節點在表格物件中相對於baseRow的開始列號
  colRef:該節點在表格物件中相對於baseCol的開始欄號
 
  在函式中,會改變tblRef表格物件的內容
  傳回值:無
*/
function classDOMTable_desDOM_20070531(ndRef, rowRef, colRef){
   var nNum = ndRef.childNodes.length;
   var currRow = this.baseRow +rowRef;
   var currCol = this.baseCol +colRef;
   var crtTtObj = document.createTextNode(ndRef.nodeName + "(" + ndRef.nodeType + ")" );
   var nCount;
  var oldRowOff;
  this.tblRef.rows[currRow+this.rowOff].cells[currCol].appendChild(crtTtObj);
   if (nNum == 0) return true;
   for (nCount=0; nCount < nNum ; nCount++){
    oldRowOff = this.drawLine(nCount, nNum, currRow, currCol, oldRowOff);
    this.desDOM(ndRef.childNodes[nCount], rowRef+nCount, colRef+2);
  }
  this.rowOff += (nNum-1);
}

/******畫分支符號的函式******
  傳入引數
  nNode:子節點群中的第幾個節點?
  mNode:子節點群的數量
  rRef:該節點名稱所在列的基本量
  cRef:該節點名稱所在行的基本量
  oldOff:列偏移量

  在函式中會改變表格參照的內容
  若新、舊的列偏移量不同
  傳回值:新偏移量
*/
function classDOMTable_drawLine_20070531(nNode, mNode, rRef, cRef, oldOff){
  var sCount, crtTtObj;
  switch (nNode){
    case 0:
     if (mNode == 1) // 第一個子節點且餘下沒有其它子節點
      crtTtObj = document.createTextNode("─→");
     else
      crtTtObj = document.createTextNode("┬→");
     break;
    case (mNode-1): // 最後一個子節點
     crtTtObj = document.createTextNode("└→");
     break;
    default:  // 介於第一個子節點和最後一個子節點間
      crtTtObj = document.createTextNode("├→");
      break;
  }
  this.tblRef.rows[rRef+this.rowOff+nNode].cells[cRef+1].appendChild(crtTtObj);
 
  if (oldOff != this.rowOff) { //新舊偏移量的不同,代表上一個節點其子節點群已讓列產生偏移
    for (sCount=oldOff; sCount < this.rowOff; sCount++){
     crtTtObj = document.createTextNode("│");
     this.tblRef.rows[rRef+sCount+nNode].cells[cRef+1].appendChild(crtTtObj);
    }
    return this.rowOff;
  }
  return oldOff;
}


iceShadow 在 2007-6-26, PM 1:42 星期二 作了第 2 次修改
回頂端
檢視會員個人資料 發送私人訊息
iceShadow
偶而上來逛逛的過客


註冊時間: 2007-05-24
文章: 10

0.00 果凍幣

發表發表於: 2007-6-24, PM 7:35 星期日    文章主題: 使用說明 引言回覆

※使用說明


注意事項:因為js檔內含有繁體中文(Big5)註解故請將HTML頁面編碼字元集設為Big5

代碼:

<meta http-equiv=Content-Type content="text/html; charset=Big5">

不然可能造成無法顯示的問題

--------
domlib.js為DOM樹形傾印的一般函數設計版
使用方法:
請在HTML網頁開頭中加入
代碼:

<Script Language="JavaScript" src="./domlib.js"></Script>

然後,在與onload事件連結的函式裡加上
代碼:

 var tblObj = getDesDom(欲解析的節點);
 document.body.appendChild(tblObj); //將建立的表格加到BODY

如此,便可在網頁讀取完成時,自動解析。
---------

domclass.js為DOM樹形傾印的類別設計版
使用方法:
在HTML網頁中加入
代碼:

<Script Language="JavaScript" src="./domclass.js"></Script>

然後,在與onload事件連結的函式裡加上
代碼:

 var Obj = new DOMTable(欲解析的節點);
 var tblObj = Obj.getDesDOM();
 document.body.appendChild(tblObj); //將建立的表格加到BODY

或選擇這樣
代碼:

 var Obj = new DOMTable();
 var tblObj = Obj.getDesDOM(欲解析的節點);
 document.body.appendChild(tblObj); //將建立的表格加到BODY


如此,便可在網頁讀取完成時,自動解析。

註:網頁的根節點,建議可以傳入top.document,應該就不會有遺漏到的節點了,另外HTML文件和JS檔案要記得放在同一個目錄喔,不然藉由修改HTML文件裡SCRIPT區塊src性質裡的路徑也是可以的。
回頂端
檢視會員個人資料 發送私人訊息
從之前的文章開始顯示:   
發表新主題   回覆主題    電腦遊戲製作開發設計論壇 首頁 -> 程式概論 所有的時間均為 台灣時間 (GMT + 8 小時)
1頁(共1頁)

 
前往:  
無法 在這個版面發表文章
無法 在這個版面回覆文章
無法 在這個版面編輯文章
無法 在這個版面刪除文章
無法 在這個版面進行投票
可以 在這個版面附加檔案
可以 在這個版面下載檔案


Powered by phpBB © 2001, 2005 phpBB Group
正體中文語系由 phpbb-tw 維護製作