Atlas用戶端腳本提供了數個繼承 于Sys.UI.Control的類,從簡單如Sys.UI.Button,到複雜如Sys.UI.Data.ListView,在一定程度上友善了開發 人員,另外可以使用Declarative Syntax也可謂一大進步。但是一般僅僅使用Atlas提供的那些類是遠遠不夠的,開發人員必須自行使用Atlas進行擴充,并且将自己的擴充融入到 Atlas模型中去。
一般來說:使用Client Script開發Atals的Control,需要進行以下幾步:
1、編寫一個類,并繼承于Sys.UI.Control或其子類。
2、重載getDescriptor方法提供對于類成員的描述。
3、重載initialize方法添加初始化代碼。
4、重載dispose方法添加銷毀該控件時代碼。
5、添加該類對于Declarative Syntax的支援。
這裡以一個Culture Selector的開發作為例子,直覺和詳細地說明上述這些步驟,也能提到編寫時需要注意的一些細節。
我們一步一步地來實作這個控件:
Jeffz.UI.CultureSelector類的代碼架構如下:
1

Type.registerNamespace("Jeffz.UI");
2

3

Jeffz.UI.CultureSelector = function(associatedElement)
4
{
5
Jeffz.UI.CultureSelector.initializeBase(this, [associatedElement]);
6
7
...
8
9
this.get_culture = function()
10
{
11
return _culture;
12
}
13
14
this.set_culture = function(value)
15
16
if (!this.get_isInitialized())
17
{
18
_culture = value;
19
return;
20
}
21
22
if (_culture == value)
23
24
25
26
27
var cancelArgs = new Sys.CultureCancelEventArgs(value);
28
this.cultureChanging.invoke(this, cancelArgs);
29
canceled = cancelArgs.get_canceled();
30
31
if (!canceled)
32
33
34
this.__updateCulture();
35
this.raisePropertyChanged('culture');
36
37
38
this.__changeSelectedOption(_culture);
39
40
41
this.get_isLoading = function()
42
43
return _isLoading
44
45
46
this.cultureChanging = new Type.Event(this);
47
48
...
49
}
50

Jeffz.UI.CultureSelector.registerClass('Jeffz.UI.CultureSelector', Sys.UI.Control);
作為示範,在這裡Culture Selector直接繼承于Control控件,而不是繼承于Sys.UI.Select控件,以避免Select紛繁的現有成員。上面定義了一個RW屬 性(culture),一個隻讀屬性(isLoading)和一個事件(culturechanging),并且填寫了Atlas中繼承的基礎代碼。這樣 的架構存在于所有Sys.UI.Control之類中。其中屬性的寫法,事件的寫法等等,都是Atlas的規範。如果違反了這種規範,運作就可能不正确。
在定義屬性時,需要注意可能當時還沒有執行initialize方法,需要“特别對待”(16-20行)。另外,為了支援binding,在合适的時候需要調用this.raisePropertyChanged方法,這一點,在編寫整個類的時候都需要注意。
另外,在cultureChanging事件裡使用了CancelEventArgs,這樣可以響應這個事件并取消Culture改變。
這是個相當重要的方法。在Atlas中,一個類是靠Sys.TypeDescriptor類的執行個體來描述自己的對外成員。Atlas在得到該執行個體後即可 以進行一些操作,例如非常著名的“Binding”。Atlas中從一個類獲得Sys.TypeDescriptor對象的方式有幾種,其中對于實作了 Sys.ITypeDescriptorProvider接口的類,就會調用接口中的getDescriptor方法來獲得類成員的描述。Atlas中的 類大都實作了這個接口,因為它在Sys.Component中就被實作了。自然,對于繼承了Sys.Control的類都有這個方法,我們需要重載它來提 供對于自身的描述。代碼如下:
this.getDescriptor = function()
var td = Jeffz.UI.CultureSelector.callBaseMethod(this, 'getDescriptor');
td.addProperty("culture", String);
td.addProperty("isLoading", Boolean, true);
td.addEvent("cultureChanging", true);
return td;
首先,調用父類的getDescriptor方法獲得描述父類成員的對象。然後調用各個方法來加入類自身的描述。
Sys.TypeDescriptor添加Property描述的方法定義如下:
Sys.TypeDescriptor.addProperty(propertyName, type, isReadonly, associateAttribute1, associateAtribute2...);
第一個參數為屬性名,第二個為屬性類型,從String,Boolean,Object,Array等javascript内定類型到自定義 的類或者接口都能作為屬性的類型。不同的類型會有不同的處理方式,閱讀代碼可以幫助我們了解更多,更好地掌握這些方法。如果該屬性為隻讀屬性,則需要将第 三個參數設為true。一般來說,隻需要了解這三個闡述即可。associateAttribute是可以任意附加于該屬性的描述,這片文章不對其作用進 行分析。
Sys.TypeDescriptor還有添加Event描述的方法,定義如下:
Sys.TypeDescriptor.addEvent(eventName, supportsActions)
第一個參數為事件名,第二個參數如果設為true,則該方法還能夠支援各個Action。
另外,Sys.TypeDescriptor也有addMethod和addAttribute方法,它們和addEvent一樣都是非常簡單的方法,大家可以通過閱讀Atlas代碼來知道它們的定義和使用方式。
一個Control必須經過初始化工作才能使用,是以,最後必須調用它的initialize方法才能生效。自然,重載initialize方法也是編寫一個control的基本要素之一。這個方法代碼如下:

this.initialize = function()
Jeffz.UI.CultureSelector.callBaseMethod(this, 'initialize');
while (this.element.options.length > 0)
this.element.options.remove(0);
var cultures = ["en-US", "zh-CN", "fr-FR", "ko-KR"];
for (var i = 0; i < cultures.length; i++)
var option = document.createElement("option");
option.text = cultures[i];
option.value = cultures[i];
this.element.options.add(option);
_selectionChangedHandler = Function.createDelegate(this, this.__onSelectionChanged);
this.element.attachEvent('onchange', _selectionChangedHandler);
_loadCompleteHander = Function.createDelegate(this, this.__loadComplete);
if (_culture.toLowerCase() != Sys.CultureInfo.Name.toLowerCase())
this.__updateCulture();

Jeffz.UI.CultureSelector.registerBaseMethod(this, 'initialize');
首先需要調用父類的initialize方法進行初始化,然後再執行自己的代碼。在初始化代碼中往往會處理控件狀态(5-17行),attach控件事件(19-20行),并做一些函數封裝等其餘工作(22到28行)。
在作函數封裝時,一個最常用的方法就是Function.createDelegate。該方法會傳回一個回調函數,定義如下:
Function.createDelegate(instance, method)
如果直接将一個函數交給一些HTML控件作為事件的回調函數,在事件發生時this所引用的對象就是該控件,而不是我們自定義的對象,例如把 一個函數交給window.setTimeout後,this就變成的window。Function.createDelegate則解決了這一點,一 般要響應HTML控件的回調時,都使用該方法比較妥當。它保證了method參數所引用的方法被調用時,this所引用的一定是instance參數對 象。
Sys.Component也實作了Sys.IDisposable接口。是以重載dispose方法也是銷毀對象,釋放資源的重要做法。代碼如下:
this.dispose = function()
if (_selectionChangedHandler)
this.element.detachEvent('onchange', _selectionChangedHandler);
_selectionChangedHandler = null;
_loadCompleteHander = null;
Jeffz.UI.CultureSelector.callBaseMethod(this, 'dispose');
Jeffz.UI.CultureSelector.registerBaseMethod(this, 'dispose');
一般來說,要做的就是detach事件,釋放引用等等。記得在最後要調用父類的dispose函數。
到現在,CultureSelector類已經寫得差不多了,我們先來使用一下。首先是HTML:
HTML代碼
注意ScriptManager将EnableScriptGlobalization設為了false,因為Culture的選擇将由我們來控制。
然後定義一些javascript函數:
javascript代碼
可以看到init函數内通過javascript使用了Jeffz.UI.CultureSelector。并将一個Label的visible屬性和cultureSelector的isLoading屬性通過binding聯系了起來。
最後是Atlas Xml Scripts:
<script type="text/xml-script">
<page xmlns:jeffz="http://www.jeffzlive.net">
<components>
<application load="init" />
<timer interval="1000" tick="onTick" enabled="true" />
</components>
</page>
</script>
最後,就要添加對于Xml Script的支援了。實作這一步其實可以非常的簡單,隻要如下一句代碼:
Sys.TypeDescriptor.addType('script', 'cultureSelector', Jeffz.UI.CultureSelector);
這樣,我們就可以在代碼裡使用像使用<lable />一般地使用<cultureSelector />了。但是為了避免沖突,最好我們還是加上自己的prefix比較好。首先看一下Sys.TypeDescriptor.addType的定義:
Sys.TypeDescriptor.addType(tagPrefix, tagName, type);
于是我們隻需要使用第一個參數就能夠加上一個prefix:
Sys.TypeDescriptor.addType('jeffz', 'cultureSelector', Jeffz.UI.CultureSelector);
顯然,如果prefix被設為了script那麼則可以省略。
現在,我們就能夠通過Atlas Xml Scripts來方面地使用CultureSelector控件了。很自然,上面的init方法也就不需要了。Atlas Xml Scripts代碼如下:
<jeffz:cultureSelector id="cultureSelector" cultureChanging="changing" />
<label id="loading" visibilityMode="Collapse">
<bindings>
<binding dataContext="cultureSelector" dataPath="isLoading" property="visible" />
</bindings>
</label>
需要注意的是,既然使用了prefix,那麼我們就需要添加一個xml namespace,如上例。這樣,這個xml才能被正确地解析。現在的效果和之前javascript方法相同。
可能有人會問(比如我在剛接觸Declarative Syntax時就很想知道),這個XML是如何被解析的呢?我們能否控制呢?答案是肯定的。事實上,Atlas現在是通過 Sys.UI.Control.parseFromMarkup方法來解析這個xml element。Atlas在遇到代表一個類型的xml element時,會去找這個類型的“靜态”方法parseFromMarkup去解析。如果沒有找到,則會沿着繼承樹一路向上。顯然,如果我們需要自定 義xml解析方式的話,我們隻需要定義一個為Sys.UI.CultureSelector定義一個parseFromMarkup即可。例如:
Sys.UI.CultureSelector.parseFromMarkup = function(type, node, markupContext)
這就是相當進階的作法了,比較複雜,超出了我想在這篇文章中談論的問題。可能我會在以後寫一系列文章讨論一些深入Atlas的話題,把我研究 Atlas各方面的心得和大家分享一下。感興趣的朋友現在可以閱讀一下Atlas中現有的方法實作。閱讀代碼能夠幫助我們了解Atlas的工作方式,即使 遇到了問題,往往也能很快地解決。
本文轉自 jeffz 51CTO部落格,原文連結:http://blog.51cto.com/jeffz/60939,如需轉載請自行聯系原作者