這篇是將 Google Web Toolkit 的官方教學文章直接翻譯過來的,內容不是很好閱讀,加減做個記錄。
GWT 的 Editor 架構(framework)可以讓複雜的物件(object graph)中的資料對應到 Editors 上,像是經由 RPC 得到的物件,如果要將其中的資料放到使用者介面(UI)上,就很適合用這個架構。
使用這個 Editor 架構主要的目的有下列幾項:
註一:在一般的程式中,資料要在使用者介面與 object graph 之間互相傳遞時,都會需要撰寫一些程式碼,專門負責傳遞資料,這些很無聊但又不能省略的程式碼,就是所謂的「膠水程式碼」,使用 Editor 架構最主要的目的就是簡化這些程式碼的使用量。
註二:所謂「像是 bean 的物件」指的是物件中的 properties 都可以用 Foo getFoo();
這樣的方法(method)來取得,而 void setFoo(Foo foo);
則可有可無。
Editor 指的是一個可以編輯 bean 的物件,而一個 Editor 中可以包含任意個數的子 Editors,大多數的 Editor 會實作成 Widget,但在這個架構的規範數,並沒有這個限制,程式設計者可以任意實作出自己想要的 Editor 型式。
Driver 是在最上層的控制元件,負責將 bean 連結上 Editor。
這裡首先將 Editor 的架構作一個簡單的介紹,先讓大家有個概念,一般來說使用 Editor 架構大致包含幾個步驟:
flush()
函數將 Editor 中的資料複製回 bean。hasErrors()
與 getErrors()
兩個函數檢查輸入的資料是否有錯誤。以下是一個典型的實作流程:
Step 1
在 gwt.xml
檔中匯入 com.google.gwt.editor.Editor
這個模組。
Step 2
定義一般真正儲存資料的 POJO。
public class Person { Address getAddress(); Person getManager(); String getName(); void setManager(Person manager); void setName(String name); }
Step 3
依照自己的資料結構定義 Editor。
public class PersonEditor extends Dialog implements Editor<Person> { // 許多 GWT Widgets 都有支援 Editor 架構 Label nameEditor; // 建立 Editors 常常只是把既有的 Editors 組合一下而已 AddressEditor addressEditor; ManagerSelector managerEditor; public PersonEditor() { // 實作自己的 Widgets,這裡通常會使用 UiBinder } }
Step 4
接著就把 Editor 加入程式中。
public class EditPersonWorkflow{ // 宣告空的 interface,與 UiBinder 類似 interface Driver extends SimpleBeanEditorDriver<Person, PersonEditor> {} // 建立 Driver Driver driver = GWT.create(Driver.class); void edit(Person p) { // PersonEditor 是一個實作 Editor<Person> 的 DialogBox PersonEditor editor = new PersonEditor(); // 使用頂層的 editor 初始化 driver driver.initialize(editor); // 將物件中的資料複製到 UI 上 driver.edit(p); // 顯示 UI 到螢幕上 editor.center(); } // 這個函數可被其他的 UI 呼叫 void save() { Person edited = driver.flush(); if (driver.hasErrors()) { // 子 editor 回報錯誤 } doSomethingWithEditedPerson(edited); } }
以上就是 Editor 架構的典型實作方式。
Editor 實際上只是一個標記參數的介面(interface)而已,其指定物件中的每個 property 所對應的子 Editor,而指定方式有下面幾種:
最簡單的方式就是要編輯的以物件 property 名稱作為子 Editor 的 field 名稱,或是 property 名稱再加上「Editor」,即 propertyNameEditor
,例如:
class MyEditor implements Editor<Foo> { // 編輯 Foo.getBar() property BarEditor bar; // 編輯 Foo.getBaz() property BazEditor bazEditor; }
其中 BarEditor
與 BazEditor
都是子 Editor 類別,而其 field 名稱則是對應 property 的名稱(即 bar
)或是加上「Editor」(即 bazEditor
)。
第二種方式是以物件 property 名稱或是 property 名稱再加上「Editor」(即 propertyNameEditor
)來定義沒有參數的方法函數,這樣的方式適合用於階層式的 Editor,例如:
interface FooEditor extends Editor<Foo> { // 編輯 Foo.getBar() property BarEditor bar(); // 編輯 Foo.getBaz() property BazEditor bazEditor(); }
最後一種是使用 @Path
語法,這個語法可以使用句點分隔的 property 路徑,標示更複雜的資料結構,可用於一般的 field 或 accessor 方法函數,例如:
class PersonEditor implements Editor<Person> { // 對應到 person.getManager().getName() @Path("manager.name"); Label managerName; }
@Ignored
語法可以讓 Editor 架構忽略指定的項目,不要視為子 Editor。
子 Editor 可以是 null,這種狀況 Editor 架構會自動忽略這些子 Editors。
在所有 Editor<t>
所使用的地方都可以用 IsEditor<editor>>
來替代,透過 IsEditor
介面可以讓你輕易的結合既有的 Editors 來創造新的 Editor,例如大部分的 leaf GWT Widget 都有實作 IsEditor
介面,因此可以直接拿來組合後建立新的 Editor。至於如果要自己實作 IsEditor
介面的話,只要實作 asEditor()
這個方法即可,不會影響到其他的程式碼。
每個 Editor
都有一個 EditorDelegate
,提供 Editor 架構相關的 service 函數:
getPath()
會傳回目前 Editor 在 Editor 階層架構中的路徑。recordError()
會讓 Editor 將輸入驗證的錯誤訊息傳回它的上層 Editors(parent Editors),最後傳到 Driver 中。使用 userData
參數可以將任意的資料放進產生的 EditorError
中一起傳回。subscribe()
可以用來接收在 Editor 之外更新被編輯物件的訊息。除了 Editor
這個介面(interface)之外,在 Editor 架構中還有一些特定的介面可以提供更複雜的功能。
LeafValueEditor
適用在非物件、不可變動(immutable)或是任何不要讓 Editor 架構繼續往下解析的的型別。
setValue()
:設定要被編輯的物件。getValue()
:這個方法會在 Driver 呼叫 flush()
時被呼叫,然後將 Editor 中的值複製到被編輯的 bean 之中。HasEditorDelegate
提供一個具有 EditorDelegate
的 Editor。
setEditorDelegate()
:這個方法會在任何內容初始化動作之前被呼叫。ValueAwareEditor
適用在 Editor 的動作會與其編輯的值相關的的狀況,也就是在其編輯的值變動時,會需要通知這個 Editor 做一些處理的動作。
setEditorDelegate()
:因為 ValueAwareEditor
有繼承 HasEditorDelegate
,所以要實作這個方法。setValue()
:設定要編輯與監控的物件,當此物間變動時就會通知 Editor。EditorDelegate.subscribe()
被呼叫時,Editor 的 onPropertyChange()
或 setValue()
也會隨著被呼叫。flush()
在被 Driver 呼叫時會以 depth-first 的方式處理子 Editor 中的 flush()
,所以 Editors 通常不需呼叫子 Editors 的 flush()
,而 Editor 如果要更換監控的物件,應該要在 flush()
被呼叫之後才可更換,這樣可以讓編輯的流程可以被取消。CompositeEditor
允許任何數量的子 Editors 在執行時動態加入,除了 ValueAwareEditor
所擁有的方法之外,CompositeEditor
還多了以下幾個方法:
createEditorForTraversal()
:呼叫此方法會傳回一個用來計算所有編輯路徑的子 Editor,如果這個 Composite Editor 正在編輯一個 Collection,這個方式可以解決沒有任何子 Editor 可以解查空 Collection 的問題。setEditorChain()
:提供一個可以設定 EditorChain
的管道,讓子 Editors 可以放進 Editor 階層架構中或是從中移除。getPathElement()
:這個方法是提供給 Editor 架構用來計算每個子 Editor 的 EditorDelegate.getPath()
傳回值用的,如果一個 CompositeEditor
正在編輯有索引的資料結構(例如 List
),則此方法就會傳回 [index]
。HasEditorErrors
是表示這個 Editor 可以透過 EditorDelegate.recordError()
接收任何由子 Editor 所產生且未被處理完(unconsumed)的錯誤,而 Editor 可以將已經處理完的 EditorError
錯誤以 EditorError.setConsumed()
標示為處理完成。
GWT 提供了幾個 Editor adapter 類別,簡化一些常用的程式設計結構,大多數的 adapter 都有 of() 這個靜態方法可以直接建立 adapter 物件。
HasDataEditor
可以將 List<T>
轉為 HasData<T>
。HasTextEditor
將 HasText
介面轉為 LeafValueEditor<String>
。TakesValue<String>
取代 HasText
。ListEditor
會維護一個 List<T>
與子 Editors 所組成的 List
之間的同步。ListEditor
在建立時會傳入一個使用者提供的 EditorSource
,而這個 EditorSource
會提供子 Editors(通常是 Widget 的子類別)。List
returned by ListEditor.getList()
will be reflected in calls made to the EditorSource
.OptionalFieldEditor
可以用來編輯 nullable 或是 resettable 的 bean properties。SimpleEditor
可以用來作為一個 headless property EditorTakesValueEditor
可以將 TakesValue<T>
轉換為 LeafValueEditor<T>
ValueBoxEditor
可以將 ValueBoxBase<T>
轉換為 LeafValueEditor<T>
。如果 getValueOrThrow()
方法丟出一個 ParseException
例外,這個例外會經由一個 EditorError
來處理回報。ValueBoxEditorDecorator
是一個簡單的 UI decorator,結合一個 ValueBoxBase
與一個 Label
,顯示任何 ValueBoxBase
所產生的 parse 錯誤。GWT 架構提供了以下幾種頂層的 Driver:
SimpleBeanEditorDriver
RequestFactoryEditorDriver
RequestFactory
整合,以及編輯 EntityProxy
的子類別。EntityProxy
時,這個 Driver 需要一個 RequestContext
用來自動呼叫 RequestContext.edit()
。RequestFactory
的 EventBus
中的 EntityProxyChange
事件,可以支援 subscriptions。參考資料:GWT、On the JVM