這一期的 掌握 Dojo 系列 文章將主要講述 Dojo 工具包的 UI 組件庫 - Dijit 中類型多樣的 Widget 及其使用方法。
Dijit 簡介
從 Dojo 0.9 開始,Dojo 把 Widget 從 Dojo 的核心包中分離出來,組成 Dijit 。 Dojo 在 Dijit 中為 Widget 家族添加了多位成員,增強了 Widget 的實力的同時也加快了其在頁面中的載入速度。
為改善 Widget 的外觀,Dijit 提供了多套樣式主題,比如默認綁定的樣式主題 Tundra,提高頁面可訪問性的樣式主題 A11y,以及其它兩種可供選擇的主題 Soria 和 Nihilo 。並且開發人員還可以根據自身項目的需求開發個性化的主題。同時 Dijit 對國際化和針對殘障人士的可訪問性的支持成度很高。可以說 Dijit 已成為 Dojo 工具包中三輛馬車之一。
本文將會詳細介紹 Dijit 中的 Widget 的使用。由於 Dojo Widget 數量眾多,不能一一介紹。為此從功能的角度把 Widget 分為三類:表單 Widget,布局 Widget,高級 Widget 。在每個類別中選擇代表性的 Widget 結合示例加以介紹。
表單 Widget 的使用
在有用戶概念的 Web 應用中,註冊是一項必不可少的功能,同時註冊也是有點枯燥的任務。有個不爭的事實就是:沒人喜歡填表單——無論是網上還是網下。所以設計有效的頁面表單不是件容易的事情。如果不能改變註冊表單存在的事實,那麼就要改變表單枯燥令人生厭的現狀,讓表單或整個註冊過程變得輕鬆省力。為實現這個目標,Web 開發人員會把較長的註冊表單設計成標籤的形式;當用戶填錯信息時,利用 Javascript 和正則表達式的結合給用戶一些提示信息;設計方便的日期選擇組件等等。可是這些提高用戶友好性的努力往往會給開發人員增加很大的工作量,並且確保這些組件的瀏覽器無關性也不是一件容易的事情。
有沒有更簡便的方法來開發 Web 表單呢?當然有!Dijit 為 Web 開發人員提供了一系列的表單 Widgets,利用這些表單 Widgets,Web 開發人員可以輕鬆的設計出功能強大、用戶友好性高的表單。
表單 Widget 簡介
可以說每一個 HTML 表單控制項都可以在 Dijit 找到與其對應的表單 widgets 。下面列表列出了目前 Dijit 提供的 Form Widgets:
所有的這些表單 widgets 都可以放置在 HTML 的 [form] 標籤內,也可以放在 dijit.form.Form widget 內,甚至可以放在 [form] 標籤外。這些表單 widgets 擁有標準的 HTML 控制項的所有屬性和方法,在實際開發中可以完全取代標準的 HTML 控制項。同時它們都繼承於 dijit.form._FormWidget,所以這些表單 widgets 還統一擁有一些附加的屬性和方法。下表是它們共有的屬性和方法:
屬性 | 屬性類別 | 描述 |
disabled | Boolean | 判斷此 widget 是否響應用戶的輸入,如果為真則此 widget 不響應用戶的輸入。可以用方法setAttribute("disabled", true/false)來改變此屬性值。 |
intermediateChanges | Boolean | 判斷在調用 setValue 方法后是否立即引發 onChange 事件。 |
tabIndex | Integer | 當用戶點擊 tab 鍵在 widget 中切換時,可以通過此屬性來設置 widget 獲得焦點的順序。 |
方法 | 描述 | |
focus | 在 widget 上設置 focus | |
getValue | 獲得 widget 的值 | |
setValue | 設置 widget 的值 | |
reset | 重置 widget 的值為初始值 | |
undo | 恢復 widget 的值為之前最後一次更改的值 |
表單 Widget 使用示例
接下來,我們將通過一個網站註冊表單案例的實現來介紹表單 widget 的使用方法。此案例的場景是一個電子商務類型網站的用戶註冊頁面。表單的設計概要需求為:
這個註冊頁面設計成兩步三屏組成:設置用戶名和密碼,填寫個人資料,註冊完成。這三屏由 Slider widget 作為進度標尺串聯而成,下面分步介紹這些表單 widgets 。
在由圖 1 可以看到所需要填寫的域有用戶名、密碼、設置密碼保護問題、答案等。在處理表單提交的數據時,經常會碰到非法數據,此時就需要對數據的合法性進行校驗。目前主流應用都會採用伺服器端驗證和客戶端驗證相結合的方案以兼顧網站安全性和用戶友好性。 Dijit 的 TextBox 家族提供了 Validation, Currency, Number, Date, Time 等 Widgets 。可以說它們是標準 text 標籤的功能加強版,在方便用戶輸入的同時,加強了數據的合法性校驗能力。
對於需要用戶輸入的文本域,ValidationTextBox widget 提供了強大的正則匹配功能,正好符合此需求;對於設置密碼保護問題這一項,需要為用戶提供備選項,同時又允許用戶輸入自己的希望輸入的內容。本案例選擇了用 ComboBox widget 來實現。所以在第一步中應用到了三類 widgets:Form widget,TextBox widget 和 ComboBox widget 。可以說這三個 widget 的用法都相對簡單,下面來看一下它們用法。
首先看 Form widget 的使用方法,清單 1 是實例化 Form widget 的代碼。
<form dojoType="dijit.form.Form" id="registerForm" action="showPost.php" execute="showSteps(4);alert('Execute form w/values:\n'+dojo.toJson(arguments[0],true));" > |
從清單 1 中可以看到實例化 Form widget 首先要聲明dojoType="dijit.form.Form",同時這裡應用了 Form widget 的擴展點:execute 。這個擴展點就相當於 HTML 的 form 控制項的事件 onSubmit,會在用戶提交表單時執行一些 Javascript 方法。本案例中調用了一個方法 showSteps() 來顯示完成頁面,同時把用戶填寫的表單數據用 alert 的方式列印出來。
設置好 Form widget 之後就需要向其中添加 ValidationTextBox widget 和 ComboBox widget,清單 2 是用戶名、密碼、確認密碼、設置密碼保護問題、答案等域的實現代碼。
<div id="firststep"> <div class="formSteps"> <span>設置用戶名和密碼</span> </div> <div id="step1" class="formAnswer"> <label class="firstLabel" for="name">用戶名 *</label> <input type="text" id="name" name="name" class="medium" dojoType="dijit.form.ValidationTextBox" required="true" trim="true" invalidMessage="請輸入用戶名!"/> <br> <label class="firstLabel" for="password">密碼 *</label> <input type="password" id="password" name="password" class="medium" dojoType="dijit.form.ValidationTextBox" required="true" regExp="[a-zA-Z]\w{5,17}" promptMessage="密碼必須以字母開頭,長度在6~18之間,並且只能包含字元、數字和下劃線。" invalidMessage="請確認密碼以字母開頭,只能包含字元、數字和下劃線,同時長度在6~18之間!"/> <br> <label class="firstLabel" for="validate">確認密碼 *</label> <input type="password" id="validate" name="validate" class="medium" dojoType="dijit.form.ValidationTextBox" required="true" validator="return this.getValue() == dijit.byId('password').getValue()" invalidMessage="請確認兩次輸入密碼一致!"/> <br> <label class="firstLabel" for="pwdquestion">設置密碼保護問題 *</label> <select dojoType="dijit.form.ComboBox" value="" id="pwdquestion" autocomplete="true" hasDownArrow="true" > <option></option> <option>您的寵物名字是什麼?</option> <option>北京奧運開幕式是哪天?</option> </select> <br /> <label class="firstLabel" for="answer">答案 *</label> <input type="text" id="answer" name="answer" class="medium" dojoType="dijit.form.ValidationTextBox" required="true" trim="true" invalidMessage="請輸入答案!"/> <br> </div> <center> <button dojoType="dijit.form.Button" onclick="showSteps(2)" iconClass="dijitEditorIcon dijitEditorIconSave" type=button> 下一步 </button> </center> </div> |
在清單 2 中有四處用到了 ValidationTextBox widget,其中用戶名和答案這個兩個文本域的驗證相對簡單,只是通過屬性required="true"來設置此域是必須填寫的。同時通過設置屬性 invalidMessage 來設置錯誤提示信息。不過請讀者注意,這裡的只是做了一個簡單的 Demo,以演示 Widgets 的用法。在實際應用中應需要從客戶端和伺服器端兩方面對用戶名等表單數據的合法性進行校驗。
下圖是用戶沒有填寫用戶名時,顯示的錯誤提示信息。錯誤提示信息會以 Dijit 的一個高級 widget - Tooltip 為載體顯示出來,開發人員可以通過 ValidationTextBox widget 的屬性 tooltipPosition[] 來設置 Tooltip 顯示的位置,關於 Tooltip 的用法會在本文第四部分:高級 Widget 的使用中介紹。
看到這裡,有的讀者可能會問,難道 ValidationTextBox widget 就只能設置文本域是否必填么?不是的,它還有更炫的利用正則匹配進行驗證的機制。通過密碼和確認密碼這兩個文本域的應用可以初步了解 ValidationTextBox widget 相對高級的驗證方式。
ValidationTextBox widget 一般採用正則表達式來驗證,利用正則表達式強大的匹配功能,可以說 ValidationTextBox widget 可以滿足目前各種常用輸入域的格式要求。比如 IP 地址的驗證,URL 的驗證,Email 地址的驗證,密碼格式的驗證等等。清單 2 中密碼輸入域的驗證就是採用了正則表達式匹配驗證。
ValidationTextBox widget 提供了兩種引用正則表達式的方式:直接引用和通過調用方法返回正則表達式的方式引用。在清單 2 中 ID 為 password 的 ValidationTextBox widget 採用的是第一種引用正則表達式的方式:直接設置屬性 regExp 的值為正則表達式,如regExp="[a-zA-Z]\w{5,17}"。這裡的正則表達式要匹配的是以字母開頭([a-zA-Z]),後接 4 到 16 個任意單一字元(\w 表示任意單一字元 , 同 [a-zA-Z0-9])。 ValidationTextBox 另外一種方式:設置屬性 regExpGen 的值為返回正則表達式的方法名,如regExpGen="dojox.regexp.emailAddress"。方法 dojox.regexp.emailAddress 中提供了域名列表,更明確的限制了域名的合法性。建議簡單的驗證可以選擇設置regExp屬性,而較為複雜的驗證選擇設置regExpGen屬性,這樣可以提供較為複雜的正則表達式組合。
與用戶名輸入域相比,密碼輸入域還增加了一個方法:promptMessage 。這是 ValidationTextBoxes widget 獲得焦點時彈出的輔助提示信息,而 invalidMessage 是在用戶輸入不合法時的即時提示。圖 3 是這兩種提示信息的比較圖。當密碼輸入域獲得焦點時,會彈出輔助提示信息:密碼必須以字母開頭,長度在 6~18 之間,並且只能包含字元、數字和下劃線。此時輸入域處在編輯的狀態,並且顏色沒有改變。當用戶進行輸入未符合要求時,會彈出錯誤提示信息:請確認密碼以字母開頭,只能包含字元、數字和下劃線,同時長度在 6~18 之間!此時輸入域為黃色,並且在輸入框末尾有警示圖標。
另外在第一步中還有一個 widget:ComboBox 。可以說 ComboBox widget 一個自動完成、輔助用戶輸入的文本輸入域。它是 [select] 組合框和 [text] 輸入域的結合體:既可以像 [select] 組合框那樣提供一列可選值,也可以像在 [text] 輸入域里那樣輸入用戶想要輸入的任何值。首先來了解一下 ComboBox widget 的屬性:
屬性 | 屬性類別 | 描述 |
autoComplete | Boolean true | 判斷是否自動完成用戶輸入的內容。當值為真時,用戶輸入部分字元串,ComboBox widget 會把能匹配上的可選值列出,如果游標離開此 widget,會顯示第一個匹配的選項值。 |
hasDownArrow | Boolean true | 判斷是否現實下拉按鈕。 |
ignoreCase | Boolean true | 判斷是否忽略大小寫(針對英文的輸入)。 |
pageSize | Integer Infinity | 此屬性可以設置下拉列表顯示的條數,如果出現多頁的情況,會在下拉列表中顯示” Previous choices ”和” More choices ”按鈕。用戶可以通過點擊這兩個按鈕來查找選項。 |
query | Object {} | 設置查詢表達式以過濾’ store ’里的選項。 |
searchAttr | String name | 設置查找的匹配表達式 |
searchDelay | boolean true | 當用戶輸入內容後到 Dojo 開始查找用戶輸入值的匹配項之間的間隔時間。 |
store | Object | 數據提供對象的一個引用。 .Dijit 中一般應用 JSON 格式的數據。 |
清單 2 中的 ComboBox widget 實例用到了兩個屬性 autoComplete 和 hasDownArrow 。這兩個屬性已經表 2 中介紹過了,這裡不再多說。 ComboBox widget 的另一亮點就是可以從外部文件動態載入選項,並且提供了屬性來過濾選項,同時可以設置下拉列表每頁顯示選項的數量。
第一步就講到這裡,下面來看一下第二頁中用到的 widgets 。
在圖 4 中可以看到需要填寫的項為:真實姓名、郵編、手機號。在這裡仍然選擇 ValidationTextBox widget 。性別項為單選項,本例選擇了 RadioButton widget 。出生日期項需要用戶按照 MM/DD/YYYY 的格式輸入日期,以往 Web 開發人員可能會需要寫大段的 JS 代碼來實現一個日期控制項,並且還要寫一堆日期格式的驗證代碼。現在 Dijit 提供了一個 DateTextBox widget,一行代碼搞定所有的工作。對於“省”這一項,選擇的是 FilteringSelect widget 。可以說 FilteringSelect 跟 ComboBox 非常類似,不過 FilteringSelect 不允許用戶輸入可選項之外的值。因為市名特別多,並且不易收集完整,所以這個 Demo 中選擇使用了 ComboBox widget 來實現。在給出用戶一定的可選項的同時,允許用戶自己輸入。最後同意條款的複選框理所當然的選擇了 CheckBox widget 。
由於在第一步中已經介紹過 ValidationTextBox widget,這裡不再贅述。 RadioButton widget 和 CheckBox widget 同屬於 Button 類型的 widget,其使用非常方便:只需要在 HTML 標準的 checkbox / radio 控制項上加上 dojoType 的屬性,值是” dijit.form.CheckBox ”或者” dijit.form.RadioButton ”。此處要注意的是聲明 dojoType 時,大小寫敏感。清單 3 中圖 4 中使用 CheckBox widget 的代碼示例:
<input type="checkBox" name="agreement" id="ag" value="ag" dojoType="dijit.form.CheckBox" /> <label for="ag">我同意並接受以下條款</label> |
當 Dojo 解析到控制項的屬性 dojoType 為 dijit.form.CheckBox 時,會應用 dijit.form.CheckBox 中定義的 templateString(清單 4)替換掉源代碼中 [checkbox] 的定義增加了一些屬性和方法,然後輸出到頁面(清單 5)。
<div class=\"dijitReset dijitInline\" waiRole=\"presentation\"\n\t> <input\n\t \t type=\"${type}\" name=\"${name}\"\n\t\t class=\"dijitReset dijitCheckBoxInput\"\n\t\t dojoAttachPoint=\"focusNode\"\n\t \t dojoAttachEvent=\"onmouseover:_onMouse,onmouseout:_onMouse,onclick:_onClick\"\n/> </div>\n |
<div wairole="presentation" class="dijitReset dijitInline dijitCheckBox" role="wairole:presentation" widgetid="ag"> <input type="checkbox" dojoattachevent="onmouseover:_onMouse,onmouseout:_onMouse,onclick:_onClick" dojoattachpoint="focusNode" class="dijitReset dijitCheckBoxInput" name="agreement" id="ag" value="ag" tabindex="0" style="-moz-user-select: none;" pressed="false"/> </div> |
由清單 4 和清單 5 可以看出,Dojo 接管了標準 [checkbox] 控制項的三個事件:onmouseover,onmouseout 和 onclick 。分別代替為 _onMouse 和 _onClick,以此更改 Checkbox 的表現形式。
提示:
如果有興趣可以在文件 _FormWidget.js 中找到 _onMouse 方法的定義,在 Button.js 文件中找到 _onClick 方法的定義。
圖 5 是完成頁面的截圖,非常簡單,只顯示了註冊完成的提示信息。不過這步讓包括了一個我們還沒有介紹的 widget:Slider widget 。其實 Slider 貫穿了三步,在每屏都會顯示註冊的進度,可以說是表單 Widget 中比較獨特的一個,下面詳細介紹了 Slider Widget 的使用。
Slider Widget 的使用
與 CheckBox widget 相比,Slider widget 較為複雜,至少在標準的 HTML 標籤庫中是沒有 Slider 控制項的。 Slider widget 的使用卻如同使用 CheckBox widget 一樣的方便。用戶可以用滑鼠點擊、拖拉、中鍵滾動或者用鍵盤的上、下、左、右按鍵來選擇 Slider widget 的刻度。
Silder 是由六個 widgets 組成,分別是:dijit.form.HorizontalSlider, dijit.form.VerticalSlider,dijit.form.HorizontalRule, dijit.form.VerticalRule,dijit.form.HorizontalRuleLabels, dijit.form.VerticalRuleLabels 。這裡可以“望文生義”一下:從 widgets 的名字可以看出 dijit.form.HorizontalSlider, dijit.form.VerticalSlider 分別是水平方向和垂直方向的 slider,提供可調節大小的標尺。其屬性如下表:
屬性 | 屬性類別 | 描述 |
clickSelect | boolean true | 判斷是否可以點擊進度條來更改刻度值 |
discreteValues | integer Infinity | 在最大值與最小值之間可以設置的最大值 |
intermediateChanges | Boolean false | 判斷在調用 setValue 方法時是否直接觸發 onChange 事件。如果為’ true ’,則每次執行 setValue 方法時都會觸發 onChange 事件;如果為’ false ’,則 onChange 事件只有在自己被調用時才會被觸發。 |
maximum | integer 100 | 可設置的最大刻度值 |
minimum | integer 0 | 可設置的最小刻度值 |
pageIncrement | integer 2 | 點擊鍵盤按鍵 pageup/pagedown 時一次變化的刻度值 |
showButtons | boolean true | 判斷是否在刻度條兩端顯示增加和減小的按鈕 |
dijit.form.HorizontalRule, dijit.form.VerticalRule 可以為 HorizontalSlider 和 VerticalSlider 設置標識線,其屬性如下:
屬性 | 屬性類別 | 描述 |
container | Node containerNode | 設置要連接的父節點 |
count | Integer 3 | 標識線的數量 |
ruleStyle | String | 為個別標識線設置 CSS style |
當在頁面中有多個 dijit.form.HorizontalRule widgets 時,通過設置 container 值來區分他們,比如清單 6 中第一個 dijit.form.HorizontalRule widget 設置container="topDecoration",而第二個設置container="bottomDecoration"。這樣用戶就可以看到在標尺的上下兩方各有一組標識線。同時設置屬性 count 的值來確定標識線的個數,譬如清單 6 中第一個 dijit.form.HorizontalRule widget 的 count 的值為 6,則在標尺上方會有六個標識線,每兩個標識線間距是總長的 1/5 。
dijit.form.HorizontalRuleLabels, dijit.form.VerticalRuleLabels 可以為標尺提供刻度標籤,其屬性如下:
屬性 | 屬性類別 | 描述 |
labels | Array [] | 文本類型的數組,存放要展現的標籤值 |
labelStyle | String | 為個別標籤設置 CSS style |
dijit.form.HorizontalRuleLabels 是繼承 dijit.form.HorizontalRule,所以 RuleLabels 類型的 widgets 都具有 Rule 類型 widgets 的屬性,同時也有自己特有的屬性。通過 labels 屬性,開發人員可以隨心設置刻度標籤的值。
Dijit 提供了兩種方式來設置標籤值,第一種就是直接設置 labels 屬性的值為一數組或者返回數組的 Javascript 方法;第二種方式就是在頁面標記為 dijit.form.HorizontalRuleLabels 的標籤內部設置 <li>,這也正是清單 6 中採用的方式。當 labels 屬性值為空時,Slider 會自動搜索本節點內部的 <li> 標籤,取出 <li> 標籤節點的 innerHTML 作為單個標籤值。
下面通過實例來看 Slider widget 的使用。
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Dijit HorizontalSlider Example</title> <style type="text/css"> @import "dojoPath/dijit/themes/tundra/tundra.css"; @import " dojoPath/dojo/resources/dojo.css" </style> <script type="text/javascript" src="dojoPath/dojo/dojo.js" djConfig="parseOnLoad: true"></script> <script type="text/javascript"> dojo.require("dojo.parser"); dojo.require("dijit.form.Slider"); </script> </head> <body class="tundra"> <div id="horizontalSlider" dojoType="dijit.form.HorizontalSlider" onChange="dojo.byId('sliderinput').value=dojo.number.format(arguments[0]/100,{places:1, pattern:'#%'});" handleSrc= "http://o.aolcdn.com/dojo/1.0.0/dijit/themes/tundra/images/preciseSliderThumb.png" value="10" minimum="0" maximum="100" discreteValues="100" intermediateChanges="true" showButtons="true" style="width:50%;"> <ol dojoType="dijit.form.HorizontalRuleLabels" container="topDecoration" style="height:1em;font-size:75%;color:gray;"> <li> </li> <li>20%</li> <li>40%</li> <li>60%</li> <li>80%</li> <li> </li> </ol> <div dojoType="dijit.form.HorizontalRule" container="topDecoration" count=6 style="height:5px;" ></div> <div dojoType="dijit.form.HorizontalRule" container="bottomDecoration" count=3 style="height:5px;" ></div> <ol dojoType="dijit.form.HorizontalRuleLabels" container="bottomDecoration" style="height:1em;font-size:75%;color:gray;"> <li>0%</li> <li>50%</li> <li>100%</li> </ol> </div> Slider Value:<input readonly id="sliderinput" size="4" value="10.0%"> </body> </html> |
在清單 6 中可以看到 Slider,Rule,RuleLabels 的應用。 dijit.form.HorizontalSlider 是把 dojoType 屬性設置在’ DIV ’標籤內的,由於intermediateChanges="true",onChange 事件會在調用 setValue 方法時自動觸發。這裡設置的 onChange 事件是更新 id 為’ sliderinput ’的一個只讀域的值,效果如圖 6 所示。在實際項目中 Slider widgets 可以應用到多個模塊中,比如圖片的瀏覽和縮放,多頁面間的轉換,文本長度的控制等等。
布局 Widget 的使用
Web 應用的頁面布局一直是令 web 開發者頭疼的一件事情。在 Web 2.0 的時代,Web 頁面布局的設計越來越多樣化,僅僅依靠 Tables 和 CSS 控制的頁面布局已難滿足用戶的需求。為此 Dijit 提供了一系列的布局 Widgets 輔助 Web 開發人員實現複雜的頁面布局。
布局 Widget 簡介
首先來瀏覽一下布局 Widgets 的成員。 Dijit 中提供的布局 Widgets 可以分為三類:
在設計頁面布局時,首先應選擇頁面整體的框架:上下兩欄、左右兩欄、上中下三欄、左中右三欄、上一欄下兩欄、上下左右中五欄等。在以往的 Div + CSS 設計布局時,雖然可以輕鬆得做到前五種布局的實現,但如果要實現最後一種五欄的布局,卻有些困難。並且設計后的布局間的比例或者每欄的大小都是固定的,當一欄的內容超出欄寬 / 高時,只能通過左右拉條或者下拉條的拖動來顯示超出的內容。可以說既麻煩又不美觀。
在 Dijit 的布局 widgets 中,對齊方式容器類的 BorderContainer widget 提供了一套簡單的 API,可以在頁面中設置上下左右中五欄的內容 ?? 也就是設置屏面的內容,甚至可以嵌套設計。同時在每兩個相鄰的屏面間有一分割的組件,可以調節屏面的大小。
為解決頁面內容多,導致出現左右或者上下拖動條的情況,Dijit 提供了堆疊類容器。無論是 AccordionContainer,TabContainer,還是 StackContainer 它們實現的功能是一樣的:把內容分成多個屏面,每次只顯示一屏的內容,要想顯示其它屏的內容,需要點擊那屏的標題欄。
如果說前面這兩類布局 widgets 為頁面提供了骨架和骨骼的話,那麼屏面類 widgets 就是填充這些骨架的真材實料。下面簡單介紹這三類布局 widgets 的屬性、方法以及用法等。
BorderContainer widget
首先介紹頁面的骨架:對齊方式容器。前面已經說過 LayoutContainer 和 SplitContainer widgets 在 Dojo1.1 中被標註為不推薦使用,所以此類 widget 首推 BorderContainer 。可以說 BorderContainer 是 LayoutContainer 的升級版,同時集成了 SplitContainer 優點:為用戶提供可拖動的邊界。
像之前提過的那樣,BorderContainer 可以向五個區域輸出:上下左右中。同時這五個區域也有兩種不同的擺設,可以通過屬性 design 來設置。如果 design 為“ headline ”,上下兩個區域的寬度就會與 BorderContainer 的寬度相同;如果 design 為“ sidebar ”,左右兩個區域的高度就會和 BorderContainer 的高度相同。下面是這兩種設計形式的示意圖,其中第一張圖片的 design 屬性為“ headline ”。
清單 7 是圖 7 中 design 屬性值為“ headline ”的實現代碼,直接更改 design 屬性值為” sidebar ”就可以實現第二張圖的效果。同時從清單 7 中還可以注意到
<style type="text/css"> html, body, #main{ width: 100%; /* make the body expand to fill the visible window */ height: 100%; } </style> <div dojoType="dijit.layout.BorderContainer" design="headline" id="main"> <div dojoType="dijit.layout.ContentPane" region="leading" splitter="true" style="background-color: #acb386; width: 100px;"> leading </div> <div dojoType="dijit.layout.ContentPane" region="top" style="background-color: #b39b86; height: 100px;"> top bar </div> <div dojoType="dijit.layout.ContentPane" region="center" style="background-color: #f5ffbf; padding: 10px;"> main panel </div> <div dojoType="dijit.layout.ContentPane" region="bottom" style="background-color: #b39b86; height: 100px;" splitter="true"> bottom bar </div> <div dojoType="dijit.layout.ContentPane" region="trailing" style="background-color: #acb386; width: 100px;" splitter="true"> trailing </div> </div> |
通過清單 7 也可以看到樣式的定義中設置了屬性 width 和 height,這裡也是要注意的地方:BorderContainer 節點需要設置 width 和 height 。同時左右兩個子節點可以設置寬度,而上下兩個子節點可以設置高度。中間區域的子節點無需設置大小,刨去四個邊界區域佔有的空間,剩下的就是中間區域。
當需要調節區域大小時,spliter 屬性就派上用場了。當開發人員希望用戶可以自己調節屏面大小時,可以設置此屬性值為“ true ”。這樣在兩屏面間就會出現一個可以拖拽的邊界。同時如果開發人員不希望用戶自己調節屏面大小,就可以設置此屬性為“ false ”。
TabContainer widget
當一個頁面內容較多,而用戶不希望像看十米長卷一樣一直向下托動滾動條來瀏覽頁面時,應用堆疊容器 widgets 是一個比較不錯的選擇。將功能類似的一些信息放在同一個標籤頁內,用戶可以方便的在不同的標籤頁之間切換,並且可以關閉不想要的標籤頁。 AccordionContainer,TabContainer,StackContaine 三個 widgets 實現的功能相同,只是表現形式不同,這裡通過 TabContainer 的介紹來了解一下堆疊容器 widgets 的使用。
首先看一下實現一個 TabContainer 所需要的元素。 TabContainer 實現的功能就是包含多個內容面,而一次只顯示一個。為了可以選擇用戶需要的內容面,就需要為每一個內容面配備一個標籤。就像平時自己整理文檔一樣,每個標籤上面都有標題來標註此類文檔的用途。在現實生活中,有時候不需要某份文檔了,就會把此文檔粉碎。為模仿此操作就需要為每個標籤配備一個關閉按鈕(可選的)。那好,到目前為止構建一個 TabContainer 所需的元素湊齊了:內容面,標籤,關閉按鈕。那剩下來就是技術活:關聯這三個元素,TabContainer 已經為開發者做好了這項工作,開發人員所需要的就是創建 TabContainer 和需要的 ContentPane 。下面來看一代碼片段:
<div id="mainTabContainer" dojoType="dijit.layout.TabContainer" style="width:500px;height:100px"> <div id="FirstPane" dojoType="dijit.layout.ContentPane" selected="true" title="The first pane"> The first pane. Can put text,picture or dialog here! </div> <div id="SecondPane" dojoType="dijit.layout.ContentPane" title="The second pane" closable="true"> The second pane! You can close me, Cool ha! </div> </div> |
在清單 8 中的代碼片斷是一個簡單的 TabContainer 例子,聲明了一個 TabContainer widget 和兩個 ContentPane widget 。其中 TabContainer 的聲明很方便,直接設置dojoType="dijit.layout.TabContainer"就可以,省下來的工作就是聲明 ContentPane 。 ContentPane widget 會有一些特殊的屬性,比如closable="true"。這是標籤是否附帶關閉按鈕的標示,如果為“ true ”則標籤上會顯示關閉按鈕。同時標籤的內容是通過 title 屬性來設置的,圖 8 是清單 8 在瀏覽器中的輸出。
如果希望不同的標籤顯示的是單獨的頁面文件時,可以設置 dijit.layout.ContentPane 的屬性 href 。譬如<div id=" Third Pane" dojoType="dijit.layout.ContentPane" href= " test.html " title="The third pane">。
ContentPane widget
ContentPane 是所有布局 widgets 的基石,其他的任何一個布局 widgets 都可以用 ContentPane 作為內容或者子 widget 的載體。同時 ContentPane 也可以單獨使用,可以盛放文本、圖片、圖表,甚至其它 widgets 。首先看一下 ContentPane wdiget 的屬性和方法。
屬性 | 屬性類別 | 描述 |
errorMessage | String Locale dep. | 錯誤提示信息,可以在 loading.js 文件中更改默認信息。 |
extractContent | Boolean false | 當取回的內容是頁面時,判斷是否抽取頁面標籤 <body> … <//body> 內的可見的內容。 |
href | String | 當前實現內容的超鏈接。如果在構造 ContentPane widget 的時候設置此項,就可以在 widget 顯示的時候載入數據。 |
isLoaded | Boolean false | 設置載入狀態。 |
loadingMessage | String Locale dep. | 載入時顯示的信息,同 errorMessage 一樣可以在 loading.js 文件中更改默認信息。 |
parseOnLoad | Boolean true | 解析取回的內容,如果有 widgets 的聲明,會實例化 widgets 。 |
preload | Boolean false | 強制載入數據。 |
preventCache | Boolean false | 判斷是否緩存取回的外部數據。 |
refreshOnShow | Boolean false | 在本 widget 從隱藏到展現時,判斷是否刷新數據。 |
方法 | 描述 |
cancel() | 取消進行中的內容下載 |
refresh() | 強制刷新 |
resize(/* String */size) | 此方法可以重新設置 widget 的大小。 |
setContent(/*String|DomNode|Nodelist*/data) | 代替原有的內容,替換為新的內容。這個方法經常用到,可以動態向 ContentPane 中輸入其它 widgets 。 |
setHref(/*String|Uri*/ href) | 替換原有的超鏈接,通過 XHR 的形式非同步獲取數據,然後重置此 widget 中的內容。 |
表 6 中列出的都是 ContentPane 作為一個單獨的 widget 使用時的屬性。如果把 ContentPane 作為其它布局 widgets 的子節點,就需要為不同的布局 widget 增加不同的屬性。譬如在清單 7 中的region="top"和清單 8 中的closable="true"等。
布局 Widget 使用示例
這部分通過一個簡單的頁面布局示例把這三中布局 widgets 串連在一起。示例的需求是這樣的:一個頁面需要分為四個區域,上下左和中;頭部為頁面標題,左側為導航欄,底部為另外一個導航欄,中間部分顯示詳細信息。圖 9 本示例的效果圖。
為把頁面分為四部分,需要採用 BorderContainer widget 。設置其屬性 design 為“ headline ”,同時創建 4 個子節點,屬性 rigion 分別為“ left ”“ top ”“ buttom ”“ center ”。其中左側欄為導航欄,選擇 AccordionContainer ;上欄只是顯示標題,所以直接安放一個 ContentPane ;底部也是一個導航欄,這裡選擇 TabContainer 來實現;中間部分顯示左側導航欄的詳細信息,選擇 ContentPane 。那麼這些 widgets 的層次關係就如下面的列表:
根據這些 widgets 的層次關係就很容易創建這個頁面的布局。清單 9 是本示例的實現代碼:
<style type="text/css"> html, body, #main{ width: 100%; /* make the body expand to fill the visible window */ height: 100%; overflow: hidden; /* erase window level scrollbars */ padding: 0 0 0 0; margin: 0 0 0 0; font: 10pt Arial,Myriad,Tahoma,Verdana,sans-serif; } </style> <div dojoType="dijit.layout.BorderContainer" design="headline" id="main"> <div dojoType="dijit.layout.AccordionContainer" duration="200" region="left" style="background-color: #acb386; width: 400px;" splitter="true" style="float: left; width: 400px; height: 300px; overflow: hidden"> <div dojoType="dijit.layout.AccordionPane" title="First pane of AccordionContainer"> <p><a href="#">the first link</a><br /> <a href="#">the second link</a><br /> <a href="#">the third link</a></p> </div> <div dojoType="dijit.layout.AccordionPane" title="Second pane of AccordionContainer"> <p> <a href="#">the first link</a><br /> <a href="#">the second link</a><br /> <a href="#">the third link</a></p> </div> <div dojoType="dijit.layout.AccordionPane" title="Third pane of AccordionContainer"> <p> <a href="#">the first link</a><br /> <a href="#">the second link</a><br /> <a href="#">the third link</a></p> </div> </div> <div dojoType="dijit.layout.ContentPane" region="top" style="background-color: #ACBFD0; height: 100px;"> <h1>Page Title</h1> </div> <div dojoType="dijit.layout.ContentPane" region="bottom" style="background-color: #ACBFD0; height: 200px;" splitter="true"> <div id="mainTabContainer" dojoType="dijit.layout.TabContainer" style="width: 100%; height: 20em;"> <div id="tab1" dojoType="dijit.layout.ContentPane" href="tab1.html" title="Tab 1"> </div> <div id="tab2" dojoType="dijit.layout.ContentPane" href="tab2.html" refreshOnShow="true" title="Tab 2" selected="true"></div> </div> </div> <div dojoType="dijit.layout.ContentPane" region="center" style="background-color: #f5ffbf; padding: 10px;"> main panel to display different contents according to the selected pane of left bar. </div> </div> |
高級 Widget 的使用
本部分會簡單介紹一下高級 Widget 的使用。高級 Widget 簡介
Dijit 在提供一些基本的 widgets 的同時也提供了一些高級功能的 widgets 。比如說 Editor,ProgressBar,Tooltip,ColorPaletee,Tree,Dialog 等。這裡把這些高級 widgets 分為兩類:用戶輔助 Widget,高級編輯和顯示 Widget。
用戶輔助 Widget 包括:
高級編輯和顯示 Widget 包括:
下面通過 Tooltip 的介紹來幫助讀者認識高級 Widget 的使用。
Tooltip Widget 的使用
目前大多數應用中,每個頁面的控制項都比較多,而僅僅通過控制項顯示的名稱是不足以讓用戶了解各種控制項的作用和一組相同類型控制項間的區別。標準的 HTML 控制項會提供 title 屬性。當用戶滑鼠停留在設置好 title 屬性值的控制項上時,瀏覽器會彈出一個提示,內容就是屬性 title 的值。
但是 title 屬性有自己的不足之處,比如樣式單一,只能顯示文本,長文本在 Firefox 中不能完全顯示等等。所以有不少開發人員自己動手創建自定義的提示工具。其中有的方案是把提示信息放在層標籤中,在 onmouseOver 事件中調用顯示層標籤的方法,在 onmouseOut 事件中調用隱藏層標籤的方法,以達到模仿 title 屬性的目的;同時也有方案在頁面初始化的時候收集所有設置了 title 屬性的控制項,取到 title 的值,經過一定的處理顯示在自定義的提示框中。
Dojo 提供了一個更美觀,實用的輔助提示 widget:Dijit.Tooltip 。其樣式定義在單獨的樣式文件中,開發人員可以自己修改。同時也提供了一些強大的功能,比如可以顯示圖片、圖表和從伺服器請求得到的數據等,可以控制顯示的時間和出現持續的時間。
實例化 Tooltip 的方法有多種,除了所有 widget 都具備的declaratively和programmatically方式外,還可以調用 Dijit 提供的方法dijit.showTooltip(/*String*/ innerHTML, /*DomNode*/ aroundNode, /*String[]?*/ position)顯示 Tooltip,調用dijit.hideTooltip = function(aroundNode)來隱藏 Tooltip 。在後面的實例中會涉及到這三種方法。
在看實例之前可以先瀏覽一下 Tooltip 的屬性:
屬性 | 屬性類別 | 描述 |
connectId | String | 要掛載 Tooltip 的控制項的 Id,可以為用逗號分隔的多個 Id 。 |
label | String | 要顯示的提示信息 |
showDelay | Integer 400 | Tooltip 顯示之前等待的時間,毫秒級 |
<script type="text/javascript" src="dojo/dojo.js" djConfig="parseOnLoad: true"></script> <script type="text/javascript"> dojo.require("dojo.parser"); dojo.require("dijit.Tooltip"); var prTooltip = new dijit.Tooltip( {label:'This tooltip is instantiated in programmatically way',connectId:'programmatically'}, document.createElement('div')); function showTooltip(labelText, nodeId, position){ dijit.showTooltip(labelText, dojo.byId(nodeId),position); } function hideTooltip(nodeId){ dijit.showTooltip(dojo.byId(nodeId)); } </script> <div dojoType="dijit.Tooltip" connectId="declaratively" label="This tooltip is instantiated in declaratively way"> </div> <div id="declaratively">Declaratively</div> <br /> <div id="programmatically">Programmatically</div> <br /> <div id="callMethods" onmouseOver="javascript:showTooltip('This tooltip is instantiated in calling method way','callMethods',['after','before']);" onmouseOut="javascript:hideTooltip('callMethods');">Calling Methods</div> |
在清單 10 中分別應用了三種實例化的方法。 Declaratively方式實例化 Tooltip 時除了要聲明 dojoType 為 dijit.Tooltip,還要設置另外兩個特殊的屬性:connectId 和 label 。如表 4 所示,屬性 connectId 的值是要關聯 Tooltip 的控制項 Id,屬性 label 的值為提示信息的內容。
Programmatically方式實例化 Tooltip 如var prTooltip = new dijit.Tooltip( {label:'programmatically',connectId:'programmatically'}, document.createElement('div')); 第一個參數是 Tooltip 特殊的屬性值:label,connected,positions 等;第二個參數是要實例化的控制項,可以創建一個新的控制項,也可以應用已存在的控制項(用 dojo.byId(someId) 來定位要實例化為 Tooltip 的控制項)。
有時候頁面需要實例化的 Tooltip 非常多,曾經遇見過一個數據報表頁面有五百多個鏈接要掛載 Tooltip !這時無論用 Declaratively方式還是Programmatically方式都會導致頁面數據量猛增,達到 MB 級,在 web 應用中這是不可以接受的。在這種情況下選用第三種實例化的方式可以在很大程度上減少頁面的大小。
結束語
在富客戶端的時代,web 開發人員都在尋找或創造可以彰顯個性的 UI 組件。為避免 web 開發人員重複造輪子,Dojo 提供了大量實用的 Widget 。開發人員所需要做的就是慷慨接受 Dojo 的這份大禮。本文介紹了各類 Widget 的基本使用方法,如果讀者希望了解創建個性化的 Widget,Dijit 的主題等,請讀下篇文章。(責任編輯:A6)
[火星人 ] 掌握 Dojo 工具包,第 5 部分: Dojo 的 UI 組件庫 - Dijit已經有1061次圍觀