2017年6月5日 星期一

ZK 教學 - 使用 MVC 或 MVVM?

不管是現在剛開始接觸 ZK 或是用過一陣子的人都可能會遇到的問題是到底要用 MVC 或 MVVM 的方式開發。從 ZK 8 之後又把 MVVM 的能力更佳的擴展,所以 MVVM 會是一個功能更強的開發方式。但 MVC 在使用上比較直覺,仍有其優勢,所以我想決定性的因素會比較是開發者的偏好專案的特性。你可參考下面的比較來決定哪種方式比較適合你。

優勢比較

MVVM

  • ViewModel 較不易受畫面變動影響。
    • 因為 ViewModel 沒有變數直接指向 ZK 元件,因此若是畫面上有元件更改,ViewModel 一般不需要修改
  • ViewModel 較易於在不同頁面中重用。
    • 因為 ViewModel 只包含資料跟業務邏輯,若是不同頁面需要的資料跟業務邏輯相同,就可以重用同一個 ViewModel
    • 例如 A 頁面按按鈕執行搜尋,B 頁面點選單執行同樣的搜尋,則可以重用同一個 ViewModel 中的 command,只是兩個頁面 data binding 寫在不同的元件上。
  • 畫面較易於重用(易於模組化)。
    • 透過 shadow component 跟 template 機制,可以將一段頁面的片段做成可以接受傳入參數
  • 套用 Responsive Design 的成本較低。
    • 通常需要針對不同裝置設計不同版面,但是顯示的資料內容大同小異,因此 ViewModel 多半可以重用
  • 易於套用美工所設計的畫面,或是網路上現成的元件。
  • 易於整合第三方 javascript library 或 widget。
    • 因為 ZK 8 提供了一個可以讓你透過 javascript 去呼叫 ViewModel 中的 command (client side binding) 的方式來跟後端溝通
  • ViewModel 的可測試性較好。
    • 因為 ViewModel 不需要繼承特定類別跟實作特定介面,可以輕易地執行單元測試

MVC

  • 操控元件方式直覺好學、易懂。
    • MVVM 控制元件較不直接,需要了解 zk framework data binding 的行為
  • 可以完全使用元件所提供的所有 API。
    • 仍有極少部分 ZK 元件行為沒辦法用 MVVM 控制。例如產生 Messagebox, Listbox.renderAll(), Popup.open()。但這都可以改用直接控制元件的方式來解決,只是就是非 MVVM 的方法,喜歡維持一致的開發模式的人可能不會喜歡
  • 易於控制客製化元件
    • 這裏是指透過繼承既有元件的 java class 所創造出來的客製化元件,若要能以 MVVM 方式控制,還必須要額外加上 data binding 的設定

比較簡表

MVC MVVM
controller 與畫面的耦合程度 較高 不耦合
如何實作 controller 需繼承ZK的SelectorComposer 不需繼承特定類別或實作特定介面的一個 Java class (POJO)
如何控制 UI 元件 透過元件的 API 透過 data binding
更新 UI 的方式 直接操作元件 透過 ZK 內建機制自動更新(需加上 @NotifyChange)
元件控制的精細程度 很精細,可以完全控制 主要控制元件 attribute
整合後端其他服務的方式 在 controller 中直接呼叫 在 controller 中直接呼叫

參考資料:

ZK 教學中文書 - ZK 教練

2016年開始做了不少場教育訓練,覺得台灣工程師應該會蠻希望有一本中文的教材,因為目前網路上多是零散的文章,英文的文件雖然完整,但是太龐大,大部分的人應該無從下手。因此最近開始撰寫一本教學書:「 ZK 教練」(書名是老婆提供的點子),也為自己在這份工作推出一個代表作。

本書會包含以下幾個特點,幫助讀者更有效地學習:

  • 範例專案
    本書附帶一個範例專案,裡面包含所有書中提到的範例程式。你可以輕易地將整個專案執行在 jetty 上,透過瀏覽器看到執行結果,有助於了解程式碼。

  • 練習專案
    執行 maven 指令可以產生一個練習專案,它以範例專案為基礎,但是刻意移除部分程式碼,好讓讀者練習之用。因此你可以產生範例專案來練習,若真的遇到困難再參考完整程式。

  • 圖片解說
    儘量以各式圖片解說概念。我認同 Head First Design Pattern 一書中提及的理念,學習不是只能靠文字,其他的媒介可以讓大腦得到更多資訊,更增強學習效果。

  • 情境 FAQ
    ZK 目前已經是個頗為成熟的框架,功能很多,因此不熟悉的人常常不知道實務上某個情境要用什麼功能來解決,這裡會列出各個常用的情境並提供連結指向建議使用的 ZK 特性

當然目前本書尚未完成,歡迎給予回饋。

去看看 ZK 教練

2016年11月30日 星期三

disabled 元素 (element) 跟 滑鼠事件 (click event) 的問題

W3C HTML5 規格 :

4.10.19.5 Enabling and disabling form controls: the disabled attribute

A form control that is disabled must prevent any click events that are queued on the user interaction task source from being dispatched on the element.

disabled element 本身不會發出 click event ,而多數瀏覽器會把這事件往父元素送,但 firefox 不會(這個行為似乎沒有規格),因此要注意。

    <div onClick="console.log(event.target)">
        <input type="text" disabled="disabled"
            onClick="console.log(event.target)" />
    </div>
    <div onClick="console.log(event.target)">
        <input type="text" onClick="console.log(event.target)" />
    </div>

以上的例子,在 Chrome 中,點擊第一個 <input> ,只有 div 的 event listener 會印出一次訊息,但 even.target 仍然是 input。
點擊第一個 <input>, input 跟 dive 的 event listener 都會印出訊息。

在 Firefox 中,就什麼都不會印出,因為 event 不會往父元素傳播。

相關討論:

2016年6月11日 星期六

AJAX 網頁卡住?可能是超過瀏覽器同時連線數上限 (max HTTP connection)

若你的程式中使用如 comet 這種 server push 技術的話,就會產生一個不中斷的 HTTP 連線,也就是發出的 AJAX request 不會立即收到伺服器的回覆,一般來說並不會造成問題,但若是使用者開啟多個頁面 (tab) 的話,就會產生多個不中斷連線,而各個瀏覽器對於同一個網站所允許的同時連線數的上限一般不大,多為 6 的左右,請參考 Connections per hostname

一旦達到連線數上限,之後所發出的 AJAX request 都會卡住而不會送出。從瀏覽器的 developer tool 就可以觀察到,你會發現不會顯示任何 request 相關的細節。更詳細的套論可以參考相關文章: Maximum concurrent connections to the same domain for browsers

這個跟 Firefox 的 network.http.max-connections-per-server 不同,這是指 Firefox 本身總共能發出的 HTTP 連線數總量,也就是所有 tab 加起來的數量。

2016年5月29日 星期日

JavaScript 關掉瀏覽器頁面

如果你直接呼叫 window.close() 來關掉目前的頁面的話,你應該會在 console 看到以下訊息:
Scripts may close only the windows that were opened by it.
( 我在 Chrome, Firefox, IE 11 都試過)

主因就是你並沒有用 JavaScript 開啟這個頁面,所以也不能用 JavaScript 關掉它。有個變通的辦法就是:
window.open(location, '_self').close();

行動裝置 Safari back-forward cache 問題

這個問題是這樣:
當你使用行動裝置上的 Safari 瀏覽 A.html 再連到 B.html,這時按瀏覽器上的後退鍵退回前一頁(A.html),這時會發現 A.html 上的 javascript 都不會被執行。根據其他人的經驗,這個問題只發生發生在行動裝置上的 Safari,我在 Mac OSX 10.10 Safari 8 的確無法重現這個問題。

主因是 back-forward cache (bfcache) 造成,這個 cache 會保存 JavaScript 的狀態,也不會再載入一次頁面,因此如果有些初始化的 JavaScript 並不會再被執行一次。

可以參考 http://stackoverflow.com/questions/8788802/prevent-safari-loading-from-cache-when-back-button-is-clicked 的程式碼,寫一段強制重載的 JavaScript 就可以解決這個問題。

以下這個方法我試過沒有用:

<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="pragma" content="no-cache">

2016年4月17日 星期日

ZK 教學 - 瀏覽器頁籤 (tab) 被關掉後進行處理

如果你想要在 browser tab 關掉時去執行一些應用程式邏輯,例如清理資料等等。我們可以傾聽 browser tab 關掉的事件,這件事等同於 ZK 的 desktop 被移除。 ZK 提供一個 DesktopCleanup listener讓你可以傾聽任何一個 desktop 被移除的事件,所以只要發現你所關注的那一頁的 desktop 被移除了,那就是該 tab 被關掉了,你可以在該 listener 中實作你的應用程式邏輯。範例如下:

import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.select.SelectorComposer;
import org.zkoss.zk.ui.select.annotation.Listen;
import org.zkoss.zk.ui.select.annotation.Wire;
import org.zkoss.zk.ui.util.DesktopCleanup;
import org.zkoss.zul.Div;

public class CloseComposer extends SelectorComposer<Component> {

    @Wire
    Div root;

    @Override
    public void doAfterCompose(Component comp) throws Exception {
        super.doAfterCompose(comp);
        //or register in zk.xml
        getPage().getDesktop().addListener(new MyDesktopCleanupListener());
    }

    @Listen("onClick = #exit")
    public void processAfterExit(){
        System.out.println("processAfterClose " + root.getId());
    }

    class MyDesktopCleanupListener implements DesktopCleanup{

        @Override
        public void cleanup(Desktop desktop) throws Exception {
            // 或根據 desktop.getRequestPath() 來判斷是哪一頁被關掉
            if (getPage().getDesktop().equals(desktop)){
                processAfterExit();
                System.out.println(desktop.getRequestPath());
            }
        }

    }
}