# GWT Wiki

GWT 與其 ecosystem 的研究心得與實作筆記。

## Contributor

（依字母順序排列）

* [Crystally](https://github.com/Crystally)
* [Jongsking](https://github.com/Jongsking)
* [PsMonkey](http://www.psmonkey.org)

歡迎到 [GitHub](https://github.com/PsMonkey/GwtWiki) 發 pull request，加入 contributor 的行列 \囧/


# GWT

## compatible 規範

符合下列條件的 class 才能在 GWT 內使用：

* primitive data type
* array（陣列元素也必須 compatible）
* enum
* JRE emulation class（包含可用 method）
* 繼承 compatible class
* 所有 field 都符合 compatible 原則

另外，`synchronized` 與 `finalize()` 加了不會出問題，但是 GWT 會直接忽略。

*（沒有考慮撰寫 emulate class 的招數）*

reference：

* JRE compatibility：<http://www.gwtproject.org/doc/latest/DevGuideCodingBasicsCompatibility.html>
* JRE emulation class： <http://www.gwtproject.org/doc/latest/RefJreEmulation.html>

### RPC（serializable）規範

* 滿足 compatible 規範
* implement `IsSerializable` 或是 `Serializable`
* 如果是某個 class 的 inner class，則必須得是 static class（可以不是 public）
* 有 default（沒有參數）constructor，任何 access modifier 均可（private 也無所謂）。

  &#x20; 或是根本沒有任何 constructor。
* 所有 field 都符合 RPC 原則。
  * 例外：final field（不會在 GWT RPC 中傳遞）
  * 例外：transient field（接收端會得到 null）

*（沒有考慮 custom serialization 的招數）*

備註：enum 只傳遞名稱。例如 `FooEnum.FOO`，就只會傳 `FOO`，裡頭的 field 值一概忽略。

reference：

* RPC： <http://www.gwtproject.org/doc/latest/DevGuideServerCommunication.html#DevGuideSerializableTypes>

#### client code 常見炸點 ##\#

假設 `Foo` 是符合 RPC 規範的 class，`fooList` 是 `ArrayList<Foo>`，

* Collections.unmodifiableList(fooList)
* fooList.subList()

上述是因為回傳值的 instance 的 class 不符合 RPC 規範，所以無法過 GWT RPC。

### 雜項

* 預設設定下，在 production mode 的 assert 是不會被觸發的。

  &#x20; （[參考資料](http://www.gwtproject.org/doc/latest/FAQ_Client.html#How_do_I_enable_assertions?)）

## Generator

因為哏太多而且不是正常的開發流程，所以另外開 [GwtGenerator](https://github.com/psmonkey/gwt-wiki/tree/e52cbcce19c5974f24402fcfbfa6574318d72779/GWT/GwtGenerator.md)。

## Deferred Binding

`when-type-is` 的 class 沒有限定一定要 interface（官方文件好像也沒特別註明）， 目前測試用 abstract class 也沒有問題。

## JSNI

`$wnd` 是對應到 JS 的 `window`，也就是說 JSNI 的 `$wnd.alert()` 就等於 JS 的 `window.alert()`。 如果需要 JS 的全域變數，基本上就只能讓 `$wnd` 多加一個變數來處理， 實際上這等同在 host page 的 `<script>` 宣告一個變數：

```
//host page script block
var value = "foo";
const wtf = "wtf";    //用 const 宣告會取不到值，原因不明...... Orz

//in JSNI
$wnd.newGlobalArray = []; //JS 可以亂加 field 不要意外，延伸出來的 JS 哏這裡略過不提
$wnd.alert($wnd.value);    //alert 視窗的訊息會是「foo」
$wnd.alert($wnd.wtf);    //alert 視窗的訊息會是「undefine」
```

`$doc` 是對應到 JS 的 `document`。 注意，如果在 JSNI 裡頭直接操作 `document`，並不會是 host page 的 `document` instance， 而是 host page 裡頭某個 iframe 裡頭的 `document`。 這背後機制暫時不明... \[死]

在 JSNI 裡頭用 `[]` 產生一個 class array 作為回傳值，基本上是不可行， 因為（以 Java 的觀點）缺乏 class 的資訊，而且到了執行期才會真正炸 cast 錯誤。 目前找到最簡單的方法就是自己用 Java 重新包一次... ＝＝"，例如：

```java
//目的：不是要取得整個 entry，而是只要 entry 裡頭的 resource

public native FooJSO[] errorResult() /*-{
    var result = [];

    for (var i = 0; i < this.entry.legnth; i++) {
        result.push(this.entry[i].resource);
    }

    return result;
}-*/;

// ======== //

private final native Foo result(int index) /*-{ return this.entry[index].resource; }-*/;

public List<FooJSO> getResult() {
    ArrayList<FooJSO> result = new ArrayList<>();

    for (int i = 0; i < getSize(); i++) {
        result.add(result(i));
    }

    return result;
}
```

### JSON 相關

client side 的 GWT 將 JSON 字串轉回（偽）Java instance 的議題。

> **注意 ######**
>
> 請不要忘記這是 JS、這是 JS、這是 JS（很重要所以要講三次）， 所以隨便 casting 是很合理的（謎之聲：教練我想寫 Java \[淚目]）。

#### Overlay Type ##\#

Overlay Type 的基礎是 `JavaScriptObject`。 繼承 `JavaScriptObject` 的 class 必須：

* `package` 等級的 class modifier
* 有 `protected` 等級的 default constructor
* 不能有 instance field
* 要嘛 class 宣告成 final，要嘛每個 method（即使不是 native）宣告成 final

主要是用 JSNI 的 getter：

```java
public final native String getId() /*-{ return this.id; }-*/;
```

對一個 JSON 字串作 `JsonUtils.safeEval()` 就會得到 `JavaScriptObject` 或其子孫，端看泛型怎麼指定。 後續呼叫 `JavaScriptObject.cast()` 也可以轉成想要的（繼承 JavaScriptObject）的 class。

**轉換 tip ####**

* 務必在 production mode 作實際測試，會有 SDM 與 production mode 結果不同的情況。
* 無法作 autoboxing
  * 所以即使 `T` 給 primitive type 的 wrapper，

    &#x20; 仍然無法用泛型 `public native <T> getField(String fieldName) /*-{ return this[fieldName]; }-*/;`。
* primitive type、enum（名稱一樣應該就 OK）可以直接轉換。
  * 無法轉換 `long`，compiler 會報錯，用 `Long` 就沒問題
  * 但是這很容易導致一個炸點，在實際使用時還是有可能被當成字串，例如：

    ```java
      public final native int fakeInt() /*-{
          return this.foo == "" ? 0 : this.foo;
      }-*/;

      public final native Integer realInt() /*-{
          return @java.lang.Integer::valueOf(Ljava/lang/String;)(this.foo == "" ? "0" : this.foo);
      }-*/;

      public final void test() {
          Console.log((fakeInt() + 1) + " != " + (realInt() + 1));
      }
    ```

    呼叫 `test()` 會得到如「11 != 2」這種結果。 所以保險起見還是統統 ~~拿去作雞精~~ 轉成標準 class instance。 **注意：** 只有 production mode 才會炸，SDM 結果正常。
* `Date` 轉換：

  ```java
    private final native String fooDate() /*-{ return this.fooDate; }-*/;

    public final Date getFooDate() {
        //JSON 的 datetime 標準據說是用 ISO-8601
        //但是真的用這個 formate 卻無法處理如 "2012-12-21" 這種只有前半段符合的字串 ＝＝"
        //return DateTimeFormat.getFormat(PredefinedFormat.ISO_8601).parse(fooDate());

        //所以... 直接用 Date constructor，底層實作是直接用 JS 解... Orz
        return new Date(fooDate());
        //當然也可以直接寫在 JSNI 裡頭，不過寫起來好囉唆... [蓋牌]
    }
  ```

**炸點 ####**

假設收到這樣一個 JSON 字串

```
["ID", "DATA"]
```

然後這段程式碼就會炸奇怪的問題：

```
HashMap<String, JavaScriptObject> map = new HashMap<>();
JsArrayMixed array = JsonUtils.safeEval(json);
map.put(array.getString(0), array.getObject(1));    //這邊不會有事
JavaScriptObject foo = map.get(array.getString(0));    //這行會炸 casting 問題

WtfJSO wtf = new WtfJSO(array.getObject(1));    //constructor 參數的型態也是 JavaScriptObject
//上面這樣就不會出問題... WTF?
```

#### 相關工具 ##\#

* Online Prettify：<http://json.parser.online.fr/>
* Online Prettify：<http://jsonviewer.stack.hu/>

## UI 相關

### UiBinder

用 UiBinder 弄畫面，執行時一直炸找不到 fooWidget class 的錯誤（但是 Eclipse 沒有 compile error）， 先單純用程式 new FooWidget() 出來，通常就會知道 FooWidget 錯在哪裡了。 （2.5.1 時常見不小心用了 generic 的 `<>` 就炸了，但是 Eclipse 不會炸錯誤 Orz）

#### ui:import ##\#

似乎是 ui.xml 中可以使用 enum 的唯一方法 （[ui:import ref](http://stackoverflow.com/questions/9492658/can-i-use-enum-values-as-field-values-inside-uibinder-template)）。

### I18N

#### gwt.xml ##\#

在 `gwt.xml` 當中要加上有要實作的語系

```markup
    <extend-property name="locale" values="zh_CN"/>
```

至於設定 default locale 沒啥用

```markup
    <set-property-fallback name="locale" value="en"/>
```

個人覺得沒啥用，倒是指定了語系就得要有 `extend-property` 然後也找得到對應的 properties 檔。 而且 default 的 properties 檔案還是不能少...... ＝＝"

掛 `<collapse-all-properties />` 好像不受影響（還是我誤會了這個設定值的意義...... Orz）

#### 實際操作 ##\#

內建原生機制無法徵測 browser 的語系，必須在 host page 給 `<meta>`

或是在 URL 掛上 query string

```
http://127.0.0.1/hostPage.html?locale=zh_CN
```

會優先使用 query string 的設定值。

在 SDM 下，加上 query string 之後、甚至重新 compile 好像都不會順利切換語系。 另外開一個新的 tab 測試會比較實在。

### Image

用 `ImageResource` 的 `Image` 如果要調整大小，

```java
    new Image(imgResource);    //size 太大會留白
    new Image(imgResource.getSafeUri());    //OK
```

沒有 attach 就不會觸發 `LoadEvent`，不過即使 `setVisible(false)` 也沒關係。

### GWT 活跳跳的證據

* 2011 年
  * <https://plus.google.com/+RayCromwell/posts/ivVepvxCu3g> &#x20;

    &#x20; Ray Cromwell 列出使用 GWT 的 Google Product。
* 2013 年：
  * <https://plus.google.com/+ThomasBroyer/posts/iCq9E2Wk3Jz> &#x20;

    &#x20; 新版的 Google Drive 的 Sheet 使用 GWT 撰寫
  * <http://www.slideshare.net/cromwellian1/gwtcreate-keynote-san-francisco> &#x20;

    &#x20; Ray Cromwell 在 Gwt.Create 報告 Google Drive 使用 GWT 撰寫的投影片（p.6）
* 2014 年：
  * <https://news.ycombinator.com/item?id=8552296#up_8554339> &#x20;

    &#x20; Google Inbox、Google Calendar 是 GWT（70%）+ Closure（30%）
* ？：<https://bar.foo/docs.html> &#x20;

  ```
    詭異的 Google 官方網站，證實 Google Document 使用 GWT
  ```

註：Ray Cromwell 是 Googler、現任 GWT 委員長。


# Super Dev Mode

> ## Super Dev Mode \#

修改 gwt.xml，目前測試結果是一定得要重開 SDM。（DevMode 好像可以不用）

`Dev Mode On` bookmark 內容主要是把現在的頁面卡一段 `<script src="http://localhost:9876/dev_mode_on.js" />`， 所以 code server 的 IP / port 號改變的話 bookmark 就得重拉一次。

### RPC 突發異常的解法

如果時常發生 GWT RPC 無法運作，JSP container 炸出來的錯誤訊息大意是：

1. 找不到 `UNIQUE_ID.gwt.rpc` 這個檔案
2. `FOO_POJO` 沒有繼承 `IsSerializable`，沒辦法 serialize

那麼在 JSP container 啟動的參數加上 `-Dgwt.codeserver.port=PORT_NUMBER`（預設值是 9876）， 基本上就可以正常運作。

背後的原理在於 `RemoteServiceServlet` 有針對 SDM 作特製， 會試圖嘗試從 code server（`http://localhost:PORT_NUMBER/policies/UNIQUE_ID.gwt.prc`）取得 RPC policy 相關檔案。

Reference：

* 官方解釋：<https://code.google.com/p/google-web-toolkit/issues/detail?id=7522#c12>
* RemoteServiceServlet commit：<https://gwt.googlesource.com/gwt/+/9faa03e06112984dd4ac5ff100a2515c2d3f74f8/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java>


# Generator

> ## GWT Generator \#
>
> 環境：GWT 2.6、Maven、SDM

## 前置準備

`pom.xml` 要多加上（一般的 GWT 開發不需要這個 dependency）：

com.google.gwtgwt-devprovided

假設 interface 叫做 `FooBinder`（GWT 官方似乎喜歡用 binder 結尾）， 則需要一個 extends `Generator` 的 class，假設叫做 `FooBinderGenerator`， 然後 `gwt.xml` 要加上：

```
<generate-with class="PKG_NAME.FooBinderGenerator">
    <when-type-assignable class="PKG_NAME.FooBinder"/>
</generate-with>
```

## 撰寫 tip

* 修改 generator 必須重新啟動 code server 才會執行新的版本。
* 可以使用 `System.out.println()`，也可以用 `generate()` 傳入的 `TreeLogger` 參數，

  &#x20; 都會出現在 console 中。

## Reference

* <http://www.gwtproject.org/doc/latest/DevGuideCodingBasicsDeferred.html>
* <http://christiangoudreau.wordpress.com/2013/05/06/how-to-efficiently-write-gwt-generators/>


# 瀏覽器差異

> ## 瀏覽器差異 \#

## Video

* GWT 2.7（理論上跟 GWT 版本無關）

在 PC（Chrome 48）上，沒設定 `autoplay` 會顯示第 1 個 frame的畫面，設定 `autoplay` 會自動開始播放。

在 mobile device（Chrome 48）上，沒設定 `autoplay` 會顯示一片黑，設定 `autoplay` 也不會自動開始播放。 在 `CanPlayThroughHandler` 當中也沒辦法呼叫 `Video.play()`，必須在其他外部 event handler 才能呼叫。


# 相關工具

## Windows

開發階段會產生大量的暫存檔，而且不會自己消失， 所以固定執行下面這個 bat 檔，以刪除（已知）與 GWT 有關的暫存檔

```
cd %LocalAppData%\temp
del ImageResourceGenerator*.*
del gwt*.*
del uiBinder_com.*
for /d %%p in ("gwt*") do rmdir /s /q %%p
for /d %%p in ("ResourceProvider*") do rmdir /s /q %%p
```


# GXT

自己寫的 composite 要能自動調整大小， 要注意是 extend GXT 的 `com.sencha.gxt.widget.core.client.Composite`， 而不是 GWT 的 `com.google.gwt.user.client.ui.Composite`

在 ui.xml 下，要知道某個 widget 下可以卡哪些特殊 tag，就去挖 source code，看看有沒有 `@UiChild`。 例如 `ContentPanel` 有 `addButton()` 跟 `addTool()` 掛 `@UiChild`，所以就可以這樣寫

```markup
    <c:ContentPanel>
        <c:button><f:Foo /></c:button>
        <c:tool><f:Foo /></c:tool>
    </c:ContentPanel>
```

因為 GXT 的 component 都是這樣處理的（GWT 反而不是... WTF）， 所以當然就可以自動建立清單啦... 參見 [UiChildList.md](/gxt/uichildlist)。

`FooTabPanel` extends `TabPanel` 不明瞭為什麼這樣還是會去 call 到 `TabPanel.add()`：

```
//in FooTabPanel
@Override
@UiChild(tagname="tab")
public void add(IsWidget widget, TabItemConfig config) {
    Window.alert("不會出現的訊息");
}

//in ui.xml
<foo:FooTabPanel>
    <foo:tab><b:TextButton text="FOO" /></foo:tab>
</foo:FooTabPanel>
```

如果 method 名字改成不是 `add()` 就沒問題......

## browser event handling

* `sinkEvents()` 傳入 component 打算處理的 event 代碼

  &#x20; （參見 `com.google.gwt.user.client.Event`）
* override `onBrowserEvent()`（最好還是做 `super.onBrowserEvent()`

如果要處理的是鍵盤輸入行為，照著做但是沒反應， 試試看 `getElement().setTabIndex(0)`， 參考自 GXT 的 `Menu`，原理不明... \[遮臉]

（上面這段其實跟 GXT 沒啥關係，都是 GWT 的哏。 只是已經 N 年沒有用過 GWT 的 component 了，所以寫到這來 XD）

另外 GXT 有一個 `KeyNav` 可以處理鍵盤輸入。 但也不按照 `sinkEvents()` 的流程走，寫法也是各種 WTF， 不要浪費時間用它 XD

## store & value provider

store 就是資料來源，value provider 就是決定顯示 vo 資料的方法

ui.xml 裡頭一定只能

```markup
    <ui:with type="com.sencha.gxt.data.shared.TreeStore" field="store" />
    <ui:with type="com.sencha.gxt.core.client.ValueProvider" field="valueProvider" />
```

不能

```markup
    <ui:with type="foo.MyTreeStore" field="store" />
    <ui:with type="foo.MyValueProvider" field="valueProvider" />
```

甚至宣告時

```java
    //正常
    @UiField(provider=true)    TreeStore<Foo> store = new MyTreeStore();

    //不行
    @UiField(provider=true)    MyTreeStore store = new MyTreeStore();
```

幹這什麼黑魔法。

## XTemplate 相關

`XTemplates` 的 method 回傳內容一定是 `SafeHtml`， `@XTemplate` 的靜態內容基本上不會作 HTML convert， 但是動態內容（大括號內的東西）就一定會作轉換。

### 自訂 property formatter

`@XTemplate` 中的語法是 `{wtf:foo}`，`wtf` 是某個變數 or 某個變數的某個 property， `foo` 是 formatter 的名稱，後頭還可以帶參數（參數能不能抓參數的值還不確定）。 要作到這個功能，首先得要弄出一個 formatter 像這樣：

```java
    public WtfFormatter implements Formatter<Wtf> {
        @Override
        public String format(Wtf data) {
            return "WTF?";    //看要作什麼處理......
        }
    }
```

然後還得要有一個提供 static method 的 class 像這樣：

```java
    public FormatterUtil {
        //這邊如果叫 getFormat() 等一下會很省事，可是我不喜歡一個 formatter 就要開一個 class
        public static WtfFormatter wtfName(){
            return new WtfFormatter();
        }
    }
```

然後 `XTemplates` 要掛上 `@FormatterFactories` 像這樣：

```java
    @FormatterFactories(
        @FormatterFactory(
            factory=FormatterUtil.class, 
            method=@FormatterFactoryMethod(name="foo", method="wtfName")
        )
    )
```

必須要透過 `@FormatterFactory` 來告訴 generator 要去哪裡找 formatter 的 method， 所以要給 class 跟 method。 method 的部份再透過 `@FormatterFactoryMethod` 來指定是哪個 method（`method`）、 以及在 `XTemplates` 當中叫啥名字（`name`）。


# Component

`Component` 基本上是 GXT widget 的共同祖先。

`Component` 有提供 `enable()`、`disable()` 也有 setter 形式的 `setEnable()`。 也就是說，有別於 GWT 只有某些 widget 才有 `setEnable()`， 所有的 GXT widget 都可以有 enable / disable 的狀態。 各式 container 的 `setEnable()` 會連小孩也一起呼叫 `setEnable()`。

## Container 系

（謎之聲：為什麼 `TabPanel` 不是... ＝＝?）

### Container

所有的 widget 都可以設定 `HasMargins` 的 layout data， 無論是加在哪一個 container 上都會有 margin 的效果。 這是因為 `Container.onInser()` 會特別檢查 `child.getLayoutData()` 是否為 `HasMargins` 並作處理。

### SimpleContainer

會自動讓小孩的大小跟自己的大小一樣

### BorderLayoutContainer

假設 `northLD` 的 size 設定為 100、split 設定為 true（北邊區域可以動態調整大小）， 調整北邊區域的大小會改變 `northLD.getSize()` 值。

### ContentPanel

在 ui.xml 當中作 `contentPanel.addButton()` 的方法：

```markup
    <core:ContentPanel>
        <core:button>
            <foo:Foo />
        <core:button>
        <core:button>
            <foo:Foo />
        <core:button>        
    </core:ContentPanel>
```

原來一個 `<core:button>` 只能塞一個 widget，我之前都誤會它了 \[炸]。

### Dialog

要知道使用者對 `Dialog` 做了什麼事情，是掛 `addDialogHideHandler()` 這個 handler。 從 `DialogHideEvent.getHideButton()` 可以知道是按下哪個 button。 不知道該說巧妙還是該說 WTF...... Orz

#### PredefinedButton 的 I18N ##\#

從 API 上來看，要改變 `PredefinedButton`（對應的 `TextButton`）的顯示字串， 就是透過 `setDialogMessages()` 設定 `DialogMessages`。 實際上，這個 API 根本有問題...... \[怒]，

以 `ConfirmMessageBox` 為例，流程大概是

```
constructor
    setPredefinedButtons()
        清空既有 button
        createButtons()
            getText()
                讀 getDialogMessages() 設定對應字串
```

`setDialogMessages()` 純粹是 setter，沒有重設顯示字串， 所以 new 完 instance 再 `setDialogMessages()` 也沒意義。

最直白的 workaround 是

```
msgBox.getButton(PredefinedButton.OK).setText("OK 的啦");
```

但是被嫌棄這樣違背 API 設計 / OO 原則（阿就原本 API 設計爛阿幹 T\_\_T）， 所以找出第二種 workaround

```
msgBox.setDialogMessages(myDialogMessages);
msgBox.setPredefinedButton(PredefinedButton.YES, PredefinedButton.NO);
```

簡單地說，就是再次呼叫 `setPredefinedButtons()`、重新製造 button， 此時就會用 `myDialogMessages` 來設定顯示字串啦。

雖然這某種程度還是破壞了 `ConfirmMessageBox` 的封裝， 不過從程式碼當中可以清楚知道到底有哪些 button， 然後學理上 `myDialogMessages` 整個 project 只會有一份，好像也不錯啦。

### AccordionLayoutContainer

小孩只能是 `ContentPanel`，而且（應該說「所以」？） `ContentPanel` 還能塞 `AccordionLayoutAppearance` 來改變 `ContentPanel` 的外觀。

### HBox / VBox

做了 `setPack()`，會發現小孩的 layoutData 不管怎麼設定 flex 值， 都不會有預其中的大小變化，而是以小孩原本的大小呈現。 目前這是實驗結果，還沒追 source code \[死]。

### HorizontalLayoutContainer / VerticalLayoutContainer

`HorizontalLayoutData` 的 width 數值邏輯為 （`VerticalLayoutData` 的 height 比照辦理）：

* x > 1：小孩的寬度就是 x
* x = -1：小孩自己決定自己的寬度（容易錯，使用注意）
* x < -1：小孩的寬度是「爸爸的寬度加上 x」

  &#x20; 換句話說，就是只留 -x 的寬度給兄弟姊妹
* 0 < x <= 1：小孩的寬度是「爸爸**剩下**的寬度乘上 x」。

舉實際的例子，假設一個 `HorizontalLayoutContainer` 的寬度是 1000， 底下有四個小孩、layoutData 的 width 設定值與實際計算完的寬度分別為：

* 100→100
* 0.5→(1000 - 100 - 200 - 600) \* 0.5 = 50
* -1（實際上是 200）→200
* -400→600

> **備註 ######**
>
> GXT API 文件上針對 `0 < x <= 1` 這段沒有說明仔細，導致跟實際結果有出入。 以上是追 source code 的結果，詳情可以參閱 `HorizontalLayoutContainer.doLayout()`， 因為 `0 < x <=1` 的需求，所以要兩次迴圈才有辦法決定（如果有小孩設定 -1 可能不只）。 第一次迴圈決定 `pw`，然後第二次迴圈才依照 `pw` 實際指定小孩的寬度。

## 輸入系

### ComboBox

label 必須是 unique 的， 這是因為 `ComboBox.getValue()` 會用當下的 `getText()` 去反找實際的 item instance， 所以如果有兩個 item 是 label 是一樣的， 會導致選第二個 item 後的 `getValue()` 還是得到第一個 item instance。 以 UI 面來說，這是沒有問題的──使用者本來就無法分辨他到底是選到哪一個...... Orz

`setTriggerAction(TriggerAction.ALL)` 可以在使用者再次要求下拉選單時顯示所有值。 像 `TimeField` 就非常需要... Orz

要注意 ComboBox 的顯示值（或著說 `LabelProvider.getLabel()` 的回傳值）不能是 `\n` 結尾。 顯示上是不會有問題、也會觸發 `SelectionEvent`， 但是在 onblur 的時候不會觸發 `ValueChangeEvent`、ComboBox 的值會消失。

如果 item 的值是 null，顯示時不會觸發 `LabelProvider.getLabel()`， 在 expand 時選單會多一個高度很詭異的空白行。

`setEditable(false)` 好像直接包含 `setSelectOnFocus()` 與 `setForceSelection()` 的功能， 此乃實驗結果，有待 trace code 證明 \[死]。 另外，如果搭配 `FieldLabel` 使用，居然點 `FieldLabel` 的 label 也會觸發 focus 效果， 這什麼黑魔法阿阿阿阿... Orz

如果 extend `ComboBox`，然後想要在裡頭用自己定義的 enum `PKG_NAME.Direction`， 在 Eclipse 當中會預設使用 `com.google.gwt.i18n.client.HasDirection.Direction` （還不用 import... WTF ＝＝"）。 目前唯一合理的解釋是 `ComboBox` 的祖先 `ValueBaseField` 有實作 `HasDirectionEstimator` 跟 `AutoDirectionHandler.Target`， 裡頭有 import `com.google.gwt.i18n.client.HasDirection.Direction`， 可是這還是不科學阿阿阿...... Orz

详细说明 `setTriggerAction()`、`setForceSelection()`、`setEditable()`、 `setSelectOnFocus()` 的作用和相互影响。

* setTriggerAction()是否显示全部下拉框中全部内容。 如果是 TriggerAction.QUERY，只能显示根据 ComboBox 内容筛选过后的内容。 如果是 TriggerAction.ALL，在输入的时候会筛选对应内容， 但是在按下下拉框按钮的时候会显示全部内容。 默认值：TriggerAction.QUERY
* setForceSelect()是否允许 ComboBox 中的值不在下拉框中。 如果是 true，ComboBox 输入的值不在下拉框中，lose focus 后会清空。 （如果使用setValue()设定一个不在下拉框的输入值不会清空） 默认值：false
* setEditable()是否允许 ComboBox 中直接输入值。 如果是 false，无法在 ComboBox 中直接输入值。 默认值：true
* setSelectOnFocus()是否在 focus 的时候出现反白的效果。 如果是 true，会在 focus 的时候选中 ComboBox 中的值，出现反白的效果 (只在鼠标点击的时候出现效果，松开后效果消失)，使用 Tab 键进入 ComboBox 不会出现这个效果。 默认值：false
* 打V表示符合描述。打X表示不符合描述。 （包括使用 setValue() 的情况）

|  setTriggerAction() | setForceSelect() | setEditable() | setSelectOnFocus() | 下拉框只显示 ComboBox 筛选过内容 | 允许 ComboBox 中的值不在下拉框中 | 允许 ComboBox 中输入值 | 在 focus 的时候不出现反白的效果 |
| :-----------------: | :--------------: | :-----------: | :----------------: | :-------------------: | :-------------------: | :--------------: | :-----------------: |
| TriggerAction.QUERY |       true       |      true     |        true        |           V           |           X           |         V        |          X          |
| TriggerAction.QUERY |       true       |      true     |        false       |           V           |           X           |         V        |          V          |
| TriggerAction.QUERY |         X        |     false     |          X         |           V           |           V           |         X        |          V          |
| TriggerAction.QUERY |       false      |      true     |        true        |           V           |           V           |         V        |          X          |
| TriggerAction.QUERY |       false      |      true     |        false       |           V           |           V           |         V        |          V          |
|  TriggerAction.ALL  |       true       |      true     |        true        |           X           |           X           |         V        |          X          |
|  TriggerAction.ALL  |       true       |      true     |        false       |           X           |           X           |         V        |          V          |
|  TriggerAction.ALL  |         X        |     false     |          X         |           X           |           V           |         X        |          V          |
|  TriggerAction.ALL  |       false      |      true     |        true        |           X           |           V           |         V        |          X          |
|  TriggerAction.ALL  |       false      |      true     |        false       |           X           |           V           |         V        |          V          |

### DateField

#### 觸發 setValue() 的來源 ##\#

**DatePicker ####**

* 当 new 一个 `DateField` 时，就 new 了一个 `DateCell` 和一个 `DateTimePropertyEditor`；
* 当使用者点击画面上的 `DateCell` 时，会通过 `DateCell.onTriggerClick()` 呼叫 `DateCell.expand()`，在这里 new 了一个 `DatePicker`；
* 因为在 `DatePicker` 的 `Constructor` 中实现了 `sinkEvents()`，而且在 `onBrowserEvent()` 中实现了 `ONCLICK|ONMOUSEOVER` 等的具体操作，所以当画面上发生 ONCLICK Event 则会呼叫 `DatePicker.onClick(Event)`；

**按下 today #####**

* 使用者在出现的 date picker 上点击【today】时，作 `addSelectHandler()` 让 select event 发生时去呼叫 `selectToday()`，结果呼叫 `DatePicker.setValue(Date, boolean)`，设置 `value` 的值；

**點擊日曆上的日期 #####**

* 当使用者触发 ONCLICK Event 时，呼叫 `DatePicker.onClick(Event)`，若触发为当月的某一天则呼叫 `DatePicker.onDayClick(XElement)`；然后通过 `DatePicker.handleDateClick(XElement, String)` 呼叫 `DatePicker.setValue(Date, boolean)`，设置 `value` 的值；

**直接輸入字串 ####**

* 当 new 一个 `DateField` 时，就 new 了一个 `DateTimePropertyEditor` 和一个 `DateCell`；
* 当使用者在 text 中输入数据时，呼叫 `ValueBaseField.setValue(T)`，实际上是呼叫了 `CellComponent.setValue(C, boolean, boolean)`，在这里设置 `value` 的值；
* (确实没有找到回到 `DatePicker.setValue()` 的地方，但确实这里应该会做 `resetTime()`，我也没有找到在什么时候做 `resetTime()` 的，/(ㄒoㄒ)/\~\~)

**setValue() 的處理 ####**

* 在 `DatePicker.setValue(date, true)` 時會作 `this.value = new DateWrapper(date).resetTime()`，

  &#x20; `resetTime()` 的 source code 與結果有點對不起來。

  &#x20; 就結果而言，會將指定日期的時間部份設定為零點零分零秒。

## 其他

### Grid

#### GridView ##\#

`GridView.setForceFit()`，如果設定 `true` 則 Grid 不會出現橫向 scroll bar； 沒有作 `setFixed(true)` 的 column 會依設定 width 為比例分配剩餘的寬度。

`GridView` 可以設定 `GridViewConfig`，藉此設定 row / column 的 style── 正確的說是加掛 style name。 實務上很吃 CSS 技能所以不好用，還有導致其他 style 亂掉的風險。 要處理 style 還是用 `ColumnConfig.setCell()`， 在自訂的 cell 裡頭設計比較容易且影響範圍可控。

可以透過 `GridView.getScroller()` 控制 `Grid` 的 scroll 狀態。 當 `refresh()` 結束之後不想強制 scroll 回左上角，就必須得靠這招。

**GroupingView ####**

如果希望資料初次載入後呈現 collapse 的樣子， 在操作完 store 之後對 view 作 `collapseAllGroups()` 是不正確的作法， 因為之後只要再對 view 作 `refresh()`， 原本沒有 expand 的 group 也會 expand。 正確作法是在 grid 初始時作 `GroupingView.setStartCollapsed(true)`。

#### AbstractGridEditing ##\#

`setEditableGrid()` 如果傳入 null 值是表示移除 edit 的效果， 因為一開始會作 `groupRegistration.removeHandler()`，然後在傳入值不為 null 才會掛相關 handler。 這在初始化設定（尤其搭配 ui.xml）時要特別注意 \[淚目]

使用者的編輯行為會透過 `GridEditing` 影響 `Grid` 的 `Store`， 實際作為是增加 `Change` 跟 `Record`。 目前看起來，`Store` 只有提供兩個對應的 method：`commitChanges()` 跟 `rejectChanges()` 來清空 `modifiedRecords`→才會讓畫面上的 dirty 樣子消失（應該是 `StoreUpdateEvent` 的結果）。 `commitChanges()` 跟 `rejectChanges()` 都會觸發 `StoreUpdateEvent`， 實驗結果：`rejectChanges()` 如果影響 n 個 item，就會炸 n 個 `StoreUpdateEvent`。

從 `Store.getModifiedRecords()` 可以得到有改變（dirty）的 item：

```java
    for (Store<Foo>.Record r : store.getModifiedRecords()) {
        Foo fooInstance = r.getModel();
        //此時 fooInstance 的內容為初始值，可以趁機作一些事情（？）
    }
```

要讓 `fooInstance` 的內容變成現在的值，有幾種作法：

* `r.commit(boolean)`：單筆
* `store.commitChanges()`：整個 store
* 根據 `Record` / `Change` 來修改 `fooInstance` \[暈]

至於要發 RPC、萬一 RPC 失敗要能 rollback... 目前還沒有想法 \[死]

### LabelToolItem

`LabelToolItem` 轉換成 DOM 就只是一個 `div` 包一個字串，當然有掛一些 css（沒有特別的內容）， 除了 package name 很奇怪之外，基本上應該可以視為 GXT 版的 label。 （謎之聲：之前測試不知道是測試到哪裡去了... (艸 ）

### TabPanel

在 ui.xml 當中只能這樣寫

```markup
    <ui:with field="tabPanelConfig" type="com.sencha.gxt.widget.core.client.TabItemConfig">
        <ui:attributes text="tab 名字" />
    </ui:with>
    <gxt:TabPanel>
        <gxt:child config="tabPanelConfig">
            <foo:Foo />
        </gxt:child>
    </gxt:TabPanel>
```

有點討厭，不過就算自己設計好像也沒辦法改變什麼 Orz

### Toggle

如果只是把 `HasValue` 加到 `Toggle` 當中、畫面也還沒進行任何操作， 畫面顯示是會正常的，但是 `toggle.getValue()` 會是 null。 所以 `toggle.add()` 完還是 `toggle.setValue()` 一下比較保險。

### DrawComponent

獨立出去紀錄在 [DrawComponent](/gxt/drawcomponent)。

## DnD

`new DragSource(dSource)` 會讓 `dSource` 變成可以 drag 的 widget， 實際上要看實作，像 `TreeDragSource` 就是讓 tree 的 item 變成可以 DnD。 `new DropTarget(dTarget)` 會讓 `dTarget` 變成可 drop 的對象。 在沒有特別的設定下（也就是 default group），任何的 `DropTarget` 都會接收任何 `DragSource` 的 DnD 行為。 反過來說，如果 `dSource.setGroup("5566")`，那麼 `dTarget` 必須也要是 5566 這個 group， 才有辦法接收源自於 `dSource` 的 DnD 行為。

如果 `parent`、`child` 這兩個 widget 都是 drop target（不考慮 group）， `parent` 跟 `child` 同時在畫面上、且 `parent` 包含 `child`， 則如果在 `child` 上作 drop 的動作，只會觸發 `child` 的 `DndDropHandler`、 而不會觸發 `parent` 的 `DndDropHandler`。


# DrawComponent

## DrawComponent

可以視為 GXT 的 canvas，更正確地說是 canvas container / wrapper。 實際上的 canvas 是 `Surface`，會用 deferred binding 的方法抽換實做， 目前大抵上預設是 `SVG`，遇到 IE6～8 會替換成 `VML`， 另外還有隱藏實驗版（JavaDoc 找不到）的 `Canvas2d` 版。 因為 wrapper 過，所以基本上可以不用理會底層實做，GXT 會提供一個一致的 API。

目前建議都作 DrawComponent.setBackground(Color.NONE)， 真的要 background 再自己指定一個 `RectangleSprite`。 不然（似乎是在） resize 的時候，background 的 sprite 會莫名其妙跑到前面去， 讓某些 sprite 被蓋掉、即使那些 sprite 給了很大的 z-index。

`ImageSprite` 沒設定過座標，在視覺上會等同於在 (0, 0)。 不過這會影響 `getBBox()` 回傳的座標仍然是 (Double.NaN, Double.NaN)， 導致 `DrawComponent` 的 sprite handler 會無法觸發。 其他 sprite 應該大同小異，所以簡單地說就是：「`Sprite` 都要設定座標」。

### 判斷游標離開 DrawComponent

雖然游標仍然在 `DrawComponent` 上，不過如果上頭有多個 sprite， 游標在離開 sprite 就會呼叫 `DrawComponent.onMouseOut()`。 所以，要真正判斷游標是否離開 DrawCompont 的方法是

```java
@Override
public void onMouseOut(Event event) {
    super.onMouseOut(event);

    if (getElement().getBounds().contains(event.getClientX(), event.getClientY())) {
        //表示還在 DrawComponent 內
    }
}
```

## Chart

`chart.redrawChart()` 之類的繪圖時刻如果炸 `java.lang.NegativeArraySizeException` 之類狀況， 先檢查一下 chart 的大小是否合理。如果 chart 沒有大小（1 \* 1），那麼炸 exception 好像也很合理。

`NumericAxis.clear()`（實際是 `CartesianAxis.clear()`）不會清 fields， 但是不清 fields 好像也不會怎樣？細節不明 Orz

如果做了 `NumericAxis.setMaximum()` 等動作， 則相關的 series 必須要設定正確的 `setYAxisPosition()`， 否則會出現 series 跟 axis 對不上的各種靈異狀況。 反之如果 axis 沒做、series 不用設定也不會出問題。

### TimerAxis

如果用 `TimeAxis` 作 X 軸（不確定作 Y 軸會怎樣 XD）， 直接設定 `setStartDate()` 跟 `setEndDate()` 就會幫你過濾掉不在時間範圍內的 data， 不用自己整理 store。

`TimerAxis` 的時間區間過濾機制會影響 chart 的 `getCurrentStore()`（`substore`）的值。 這在「清空 / 重塞 store、又變更其他 axis」的前提下，`chart.redrawChart()` 會炸。 因為 render 實際處理的是 `getCurrentStore()`，如果舊的 store 格式跟新的 axis 格式對不上，就會出錯。 解法是在變更其他 axis 之後作 `TimerAxis.drawAxis(false)`（傳入 true / false 好像沒差）， 在 `drawAxis()` 裡頭的 `applyData()`（`TimeAxis` 有 override）會重新計算 / 設定 `currentStore`。 當然這有點浪費，因為在 `chart.render()` 裡頭其實會對各個 axis 作 `drawAxis(false)`， 只能說理論上沒辦法控制 `TimerAxis` 一定要先作，`applyData()` 是 protected 所以沒辦法直接呼叫 前提觸發的條件又不是很 general，只好覆蓋新鮮的肝臟，結束這一回合 T\_\_T。

### LineSeries

預設情況下，若第 n 個點的值為 `Double.NaN`， 則會直接連起 n - 1 跟 n + 1。 設定 `setGapless(false)` 就會斷掉形成 gap。

### Legend

`Legend` 的資料來源是從 `Series.setYField()` 傳入的 `ValueProvider` 的 `getPath()`。


# UiChild List

> GXT 版本：3.1.0

## com.sencha.gxt.widget.core.client.container #\#

### AbstractHtmlLayoutContainer ##\#

* child
  * 實際 method：add
  * 可出現次數：不限制
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### AccordionLayoutContainer ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### BorderLayoutContainer ##\#

* center
  * 實際 method：setCenterWidget
  * 可出現次數：1
* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* east
  * 實際 method：setEastWidget
  * 可出現次數：1
* north
  * 實際 method：setNorthWidget
  * 可出現次數：1
* south
  * 實際 method：setSouthWidget
  * 可出現次數：1
* west
  * 實際 method：setWestWidget
  * 可出現次數：1
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### BoxLayoutContainer ##\#

* child
  * 實際 method：add
  * 可出現次數：不限制
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### CardLayoutContainer ##\#

* child
  * 實際 method：add
  * 可出現次數：不限制
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### CenterLayoutContainer ##\#

* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### Container ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### CssFloatLayoutContainer ##\#

* child
  * 實際 method：add
  * 可出現次數：不限制
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### FlowLayoutContainer ##\#

* child
  * 實際 method：add
  * 可出現次數：不限制
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### HBoxLayoutContainer ##\#

* child
  * 實際 method：add
  * 可出現次數：不限制
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### HorizontalLayoutContainer ##\#

* child
  * 實際 method：add
  * 可出現次數：不限制
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### HtmlLayoutContainer ##\#

* child
  * 實際 method：add
  * 可出現次數：不限制
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### InsertContainer ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### InsertResizeContainer ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### NorthSouthContainer ##\#

* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* north
  * 實際 method：setNorthWidget
  * 可出現次數：1
* south
  * 實際 method：setSouthWidget
  * 可出現次數：1
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### PortalLayoutContainer ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* portlet
  * 實際 method：add
  * 可出現次數：不限制

### ResizeContainer ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### SimpleContainer ##\#

* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### VBoxLayoutContainer ##\#

* child
  * 實際 method：add
  * 可出現次數：不限制
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### VerticalLayoutContainer ##\#

* child
  * 實際 method：add
  * 可出現次數：不限制
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### Viewport ##\#

* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* widget
  * 實際 method：setWidget
  * 可出現次數：1

## com.sencha.gxt.widget.core.client.cell #\#

### CellComponent ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

## com.sencha.gxt.widget.core.client.toolbar #\#

### FillToolItem ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### LabelToolItem ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### PagingToolBar ##\#

* child
  * 實際 method：add
  * 可出現次數：不限制
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### SeparatorToolItem ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### ToolBar ##\#

* child
  * 實際 method：add
  * 可出現次數：不限制
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

## com.sencha.gxt.widget.core.client.grid #\#

### ColumnFooter ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### ColumnHeader ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### Grid ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### GridSplitBar ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### Group ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### Head ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### LiveToolItem ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

## com.sencha.gxt.widget.core.client.box #\#

### AbstractInputMessageBox ##\#

* button
  * 實際 method：addButton
  * 可出現次數：不限制
* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* tool
  * 實際 method：addTool
  * 可出現次數：不限制
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### AlertMessageBox ##\#

* button
  * 實際 method：addButton
  * 可出現次數：不限制
* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* tool
  * 實際 method：addTool
  * 可出現次數：不限制
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### AutoProgressMessageBox ##\#

* button
  * 實際 method：addButton
  * 可出現次數：不限制
* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* tool
  * 實際 method：addTool
  * 可出現次數：不限制
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### ConfirmMessageBox ##\#

* button
  * 實際 method：addButton
  * 可出現次數：不限制
* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* tool
  * 實際 method：addTool
  * 可出現次數：不限制
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### MessageBox ##\#

* button
  * 實際 method：addButton
  * 可出現次數：不限制
* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* tool
  * 實際 method：addTool
  * 可出現次數：不限制
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### MultiLinePromptMessageBox ##\#

* button
  * 實際 method：addButton
  * 可出現次數：不限制
* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* tool
  * 實際 method：addTool
  * 可出現次數：不限制
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### ProgressMessageBox ##\#

* button
  * 實際 method：addButton
  * 可出現次數：不限制
* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* tool
  * 實際 method：addTool
  * 可出現次數：不限制
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### PromptMessageBox ##\#

* button
  * 實際 method：addButton
  * 可出現次數：不限制
* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* tool
  * 實際 method：addTool
  * 可出現次數：不限制
* widget
  * 實際 method：setWidget
  * 可出現次數：1

## com.sencha.gxt.widget.core.client.treegrid #\#

### TreeGrid ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

## com.sencha.gxt.widget.core.client.grid.editing #\#

### RowEditorComponent ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

## com.sencha.gxt.widget.core.client.form #\#

### AdapterField ##\#

* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### BigDecimalField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### BigDecimalSpinnerField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### BigIntegerField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### BigIntegerSpinnerField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### CellField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### CheckBox ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### ComboBox ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### DateField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### DoubleField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### DoubleSpinnerField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### DualListField ##\#

* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### Field ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### FieldLabel ##\#

* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### FieldSet ##\#

* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### FileUploadField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### FloatField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### FloatSpinnerField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### FormPanel ##\#

* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### HtmlEditor ##\#

* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### IntegerField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### IntegerSpinnerField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### ListField ##\#

* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### LongField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### LongSpinnerField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### NumberField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### PasswordField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### Radio ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### ShortField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### ShortSpinnerField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### SimpleComboBox ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### SpinnerField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### StoreFilterField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### StringComboBox ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### TextArea ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### TextField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### TimeField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### TriggerField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### TwinTriggerField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### ValueBaseField ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

## com.sencha.gxt.widget.core.client.tree #\#

### Tree ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

## com.sencha.gxt.widget.core.client.menu #\#

### AdapterMenuItem ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### CheckMenuItem ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* submenu
  * 實際 method：setSubMenu
  * 可出現次數：1

### ColorMenu ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### DateMenu ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### HeaderMenuItem ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### Item ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### Menu ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### MenuBar ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### MenuBarItem ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* menu
  * 實際 method：setMenu
  * 可出現次數：1

### MenuItem ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* submenu
  * 實際 method：setSubMenu
  * 可出現次數：1

### SeparatorMenuItem ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

## com.sencha.gxt.widget.core.client.button #\#

### ButtonBar ##\#

* child
  * 實際 method：add
  * 可出現次數：不限制
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### ButtonGroup ##\#

* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### CellButtonBase ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* menu
  * 實際 method：setMenu
  * 可出現次數：1

### IconButton ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### SplitButton ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* menu
  * 實際 method：setMenu
  * 可出現次數：1

### TextButton ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* menu
  * 實際 method：setMenu
  * 可出現次數：1

### ToggleButton ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* menu
  * 實際 method：setMenu
  * 可出現次數：1

### ToolButton ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

## com.sencha.gxt.widget.core.client.info #\#

### Info ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

## com.sencha.gxt.widget.core.client #\#

### AutoProgressBar ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### CollapsePanel ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### ColorPalette ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### Composite ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### ContentPanel ##\#

* button
  * 實際 method：addButton
  * 可出現次數：不限制
* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* tool
  * 實際 method：addTool
  * 可出現次數：不限制
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### DatePicker ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### Dialog ##\#

* button
  * 實際 method：addButton
  * 可出現次數：不限制
* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* tool
  * 實際 method：addTool
  * 可出現次數：不限制
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### FramedPanel ##\#

* button
  * 實際 method：addButton
  * 可出現次數：不限制
* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* tool
  * 實際 method：addTool
  * 可出現次數：不限制
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### Header ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### ListView ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### ModalPanel ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### PlainTabPanel ##\#

* child
  * 實際 method：add
  * 可出現次數：不限制
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### Popup ##\#

* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### Portlet ##\#

* button
  * 實際 method：addButton
  * 可出現次數：不限制
* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* tool
  * 實際 method：addTool
  * 可出現次數：不限制
* widget
  * 實際 method：setWidget
  * 可出現次數：1

### ProgressBar ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### ResizeHandle ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### Slider ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### SplitBar ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### Status ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### TabPanel ##\#

* child
  * 實際 method：add
  * 可出現次數：不限制
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### WidgetComponent ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### Window ##\#

* button
  * 實際 method：addButton
  * 可出現次數：不限制
* child
  * 實際 method：add
  * 可出現次數：1
* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1
* tool
  * 實際 method：addTool
  * 可出現次數：不限制
* widget
  * 實際 method：setWidget
  * 可出現次數：1

## com.sencha.gxt.widget.core.client.grid.filters #\#

### ListMenu ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### RangeMenu ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

## com.sencha.gxt.widget.core.client.tips #\#

### QuickTip ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### Tip ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1

### ToolTip ##\#

* contextmenu
  * 實際 method：setContextMenu
  * 可出現次數：1


# trace log

> ## source code trace 筆記 \#

主要以 package 為單位作紀錄，檔名開頭均省略 `com.sencha.gxt`。


# chart.client.draw

## DrawComponent

基本上可以視為 `Surface` 的 wrapper，也就是把 Surface 給 component 化。 維護 `Sprite[]` 的責任在 `Surface` 身上。

### 運作原理

假設實際繪圖動作只有 `Surface.draw()`， 由此從 call hierarchy 反推 `DrawComponent` 的行為， 則繪圖動作進入點為 `render()`←`redrawSurface()` / `redrawSurfaceForce()`。 （`redrawSurfaceForce()` 暫時不理他，因為只有 `SVG.getHiddenSVG()` 確定有用到，不是 cross-surface 所以追下去沒啥意義）。 `redrawSurface()` 的觸發點只有一個：`onResize()`。

什麼時候會觸發 `onResize()` 呢？（好像不該在這裡追？） 基本上 `Component.setPixelSize()` 跟 `Component.setSize()` 都會。 在某些情況下（細節略 \[逃]），`setSize()` 會導向 `setPixelSize()`。 再後頭就先不追了...... Orz

### method

#### constructor ##\#

預設大小是 500 \* 500，底色是白色

`createSurface()`（就是 `Surface.create()`）會直接對 `surface.width` 給值（跟 drawComponent 一樣大） 然後在 `setPixelSize()`→`setSurfaceSize()` 又會改成 drawComponent 扣掉 border、padding 的大小，去呼叫 `surface.setWidth()`？ 這有點 WTF 阿？

#### render() ##\#

毫無反應，呼叫 `surface.draw()`

#### onResize() ##\#

* super.onResize()（基本上只是調 mask 大小）
* redrawSurface()

#### redrawSurface() / redrawSurfaceForce() ##\#

都是呼叫 render，只是 redrawSurface() 用 scheduleDeferred() 的方式呼叫。

## Surface

> 暫時只討論 child class 是 `SVG` 的狀況 `VML` 是肯定不會去管他的 \[茶]，`Canvas2D` 等變成正式版再說。

### method

#### draw() ##\#

基本上只有處理 background color，其他 sprite 還是要看實做 `SVG.draw()`（最後會再繞回 `SVG.renderSprite()`）。 在這裡可以發現，background color 也是由一個撐滿大小的 `RectangleSprite` 來做出效果。

#### renderAll() ##\#

* 對每一個 Sprite 作 `SVG.renderSprite()`

## SVG

`DomSurface` 先跳過...

### method

#### draw() ##\#

* 呼叫 `Surface.draw()`
* 確保 `surfaceElement` 初始化。

  &#x20; 這邊會發現 SVG 層也有一個 bgRect ＝＝"
* `Surface.renderAll()`

#### renderSprite() ##\#

* `surfaceElement` 如果是 null 就 return。

  &#x20; 問題是為什麼 `surfaceElement` 會是 null @\_@?
* 如果 sprite 的 `XElement` 不存在，則 `createSprite(sprite)`
* 如果 sprite 都沒有改變，就 return。
  * 反之，一定會作 `applyAttributes()` 然後 clear dirty flag，

    至於會不會作 `applyZIndex()` 跟 `transform()` 則是看對應 dirty flag。

#### createSprite() ##\#

從這裡可以看得出來，`SVG` 基本上只能 supprot：

* `CircleSprite`
* `EllipseSprite`
* `ImageSprite`
* `PathSprite`
* `RectangleSprite`
* `TextSprite`

呼叫 `applyZIndex()`

#### applyZIndex ##\#

基本上 SVG 並沒有 z-index 的屬性，純粹就是先掛在 DOM 上的就在上面，後掛在 DOM 的就在下面。 於是乎 `applyZIndex()` 的內容（看起來）就純粹是以 `Sprite` 的 `zIndex` 來排出上下的關係。

## Sprite

預設 `zIndex` 是 10

又有 `Sprite(Sprite)` 又有 `copy()`，實在令人困惑。

乍看之下 `Sprite` 裡頭沒有定義座標 `(X, Y)` 的 field 有點不合理， 但是看到 `CircleSprite`，因為是圓心所以是 `(centerX, centerY)` 就釋懷了， 等到看到 `PathSprite` 這神奇的 sprite 就完全崩潰了...... \[死]


# 瀏覽器差異

> ## 瀏覽器差異 \#

## ChangeEvent

* GWT 2.6
* GXT 3.1

`CheckBox` 的 `ChangeEvent`（GWT）發生時，`CheckBox.getValue()` 會有差異：

* Chrome（35）：變更之前的值
* FireFox（30）：變更之後的值，個人認為這個比較合理  \[遠目]

搭配 `ValueChangeEvent`，例如

```java
    @UiHandler("fooCheckBox")
    void handleChange(ChangeEvent ce) {
        Window.alert("change event");
    }

    @UiHandler("fooCheckBox")
    void handleValueChange(ValueChangeEvent<Boolean> vce) {
        Window.alert("value change event");
    }
```

* Chrome（35）：先出現 change event、後出現 value change event
* FireFox（30）：順序顛倒

最後，以開發上來說，應該只要管 `ValueChangeEvent` 就好， `ValueChangeEvent.getValue()` 的回傳值，至少目前測起來 Chrome 跟 FireFox 是一致的， 所以這個瀏覽器差異不知道什麼時候才會被炸到 \[遠目]。


# 3rd Party


# gwt-jackson

> ## gwt-jackson

* repo：<https://github.com/nmorel/gwt-jackson>
* jackson-annotation：<https://github.com/FasterXML/jackson-annotations>

## Immutable class

```Java
public class Card {
	public final Suit suit;
	public final int number;

	@JsonCreator
	public Card(@JsonProperty("suit") Suit suit, @JsonProperty("number") int number) {
		this.suit = suit;
		this.number = number;
	}
}
```

## 沒有 \_etter 的 class

```Java
@JsonAutoDetect(fieldVisibility=JsonAutoDetect.Visibility.ANY)
public class Deck {
	private List<Card> sequence = Lists.newArrayList();
}
```


# guava

以下均省略 `com.google.common.MODULE_NAME`（全小寫）， （`Concurrent` 是省略 `com.google.common.util.concurrent`）。

* Annotations
* Base
  * Annotations
* Cache
  * Annotations
  * Base
  * Collect
  * (util) Concurrent
* Collect
  * Annotations
  * Base
  * Math
  * Primitives
* Escape
  * Annotations
  * Base
* Html
  * Annotations
  * Escape
* Io
  * Annotations
  * Base
  * Math
  * Primitives
* Math
  * Annotations
  * Base
  * Primitives
* Net
  * Annotations
  * Base
  * Escape
* Primitives
  * Annotations
  * Base
* (util) Concurrent
  * Annotations
  * Base
  * Collect
* Xml
  * Annotations
  * Base
  * Escape

要偷懶的話就 inherits `Cache` 可以涵蓋 75% 的 moudule， 只剩下 `Escape`、`Html`、`Net`、`Xml` 沒有包進去。 或是可以考慮 `com.google.common.ForceGuavaCompilation` 這個 XD。


# jqm4gwt

> ## jqm4gwt \#

* 官網：<https://github.com/jqm4gwt/jqm4gwt>
* repo：<https://github.com/jqm4gwt/jqm4gwt>

## Component

均使用預設的 CSS。

### JQMList

`clear()` 真的是全部清光光，items 跟 dividers 都要重新給。

### JQMListItem

![JQMListItem](/files/-M3xmmkuS9l9wxqgb7fk)

* `setImage()`、`setIcon()`、`setThumbnail()` 互斥。

  &#x20; 都有作 `setImage()`，但是分別有不一樣的 CSS class 設定。

  * `setIcon()` 的視覺效果好悽慘，這真的能用嗎？
* 如果所屬的 `JQMList` 做了 `list.getElement().addClassName("jqm4gwt-list-static-item-img-right");`：
  * `addSecondaryImage()` / `setSecondaryImage()` 會跟 `setCount()` 互斥。
  * 只有最後一個 secondary image 會出現在右邊

## Project Setup

官方文件有點殘缺，自己重寫一次...... ＝＝"

以下使用 standalone 的方式。

1. `pom.xml` 加入

   ```markup
    <dependency>
        <groupId>com.sksamuel.jqm4gwt</groupId>
        <artifactId>jqm4gwt-standalone</artifactId>
        <version>1.4.6.Final</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>com.sksamuel.jqm4gwt</groupId>
        <artifactId>jqm4gwt-library</artifactId>
        <version>1.4.6.Final</version>
    </dependency>
   ```

   * 官方文件沒有特別註明要加 `jqm4gwt-library` 這個 dependency
   * `jqm4gwt-standalone` 一定要在 `jqm4gwt-library` 前面。

     > jqm4gwt-standalone must be the first in java build path order, before jqm4gwt-library
2. `foo.gwt.xml` 當中加入

   ```markup
    <inherits name='com.sksamuel.Jqm4gwt' />
   ```
3. （如果是後來才導入 jqm4gwt）清除 `/webapp/foo`
4. 跑一次 `mvn install`，這樣產生 `/foo` 目錄中才會出現 `css`、`js` 的目錄。
5. 把產生出來的 `/foo` 複製回 `/webapp/foo`

### Host Page 調整

#### head ##\#

解決 mobile device 上頭字體會超級小的問題：

```markup
<meta name="viewport" content="width=device-width, minimal-ui, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
```

#### body ##\#

解決 Safari 無法正常顯示（Chrome / Firefox 都沒問題）：

```markup
<div data-role="page" id="start"></div>
```


