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());
            }
        }

    }
}

ZK 教學 - 常見錯誤用法 05 - 用 @NotifyChange 通知 ListModel 改變

以 MVVM 方法開發時,我們通常必須要用 @NotifyChange 來通知 ZK ViewModel 中的某個特定 property 更改了,這時 ZK 會重新載入該 property 去更新畫面。但是若是使用 ZK 提供的 ListModel 的實作物件則有例外 (e.g. ListModelList),因為 ZK 元件都會傾聽 ListModel 的變動,因此如果你改變其內容,元件都會知道並只 render 差異的部分,並不需要被通知。

這時若是加上 @NotifyChange 有什麼壞處呢?它反而會使 ZK 重新載入整個 ListModel 並 re-render 其中所含有的資料,如此一來,比起元件內建的機制只 render 差異部分還來得更沒效率。

2016年3月27日 星期日

ZK 教學 - 常見錯誤用法 04 - 使用不正確的分享範圍

ZK 中除了提供 Java EE 標準的共享範圍: application, session, request 之外,還提供 desktopcomponent 範圍。你可透過相對應的範圍物件並呼叫 setAttribute(key, value) 把資料存進去,並在同一個範圍內透過 getAttribute(key, value) 把資料取出來,是很常見的資料傳遞、分享方法。存資料在恰當的範圍,才能共享資料給適當的對象,也不會影響別人的狀態,或產生不必要的垃圾資料。簡介一下 ZK 才有的範圍 (scope):

  • desktop : 大部份情形你可以把這個範圍看作是一個瀏覽器的 tab,同一個 tab 下的元件都屬於同一個 desktop。每次重新載入 (reload) 頁面會重新產生一個新的 desktop。
  • component: 要取得該元件的 reference 才能取得其上的資料,通常是用來存該元件才會使用的暫存資料。
  • execution: 這是 ZK 的名詞,指得其實就是 request。只是因為 ZK 要求 zul 的 HTTP request 跟透過 AJAX 發的 HTTP request (通常是 event)有不同的處理方式,但是要通用化兩者,畢竟都是 HTTP request, 就用 Execution 物件封裝兩種不同的 request。

建議用法如下:

  • 只使用足夠的、最小的範圍共享。如果可以透過 desktop 共享,就不要透過 session。
  • 使用 ZK 開發時,一般會將多個相關的功能實作在同一頁 (這也是使用 AJAX 開發應用程式的常態),所以大部時候,跟頁面相關的資料只需要存在 desktop 範圍就夠了。如果存在 session ,要注意其會影響所有同一個瀏覽器下的 tab。
  • 要注意使用 application 及 session scope 的 event queue 會自動啟動 server push。

2016年3月25日 星期五

ZK 教學 - 常見錯誤用法 03 - 過度使用複雜元件

每個 ZK 元件都會在瀏覽器產生對應的 HTML element ,有些很複雜、有些較簡單,當然越複雜的 HTML 對瀏覽器記憶體、效能的負擔就越大。所以沒有必要時,不要選擇成本較高的元件。

例如只是要把某一群元件包在一個區域,也許要插入新元件或是整群處理時,可以用 <div> 就好,而不要用 <window>

如果只是要將多個元件水平排列,而並不需要控制其對齊位置的時候,則應該使用 <hlayout>即可,而不需要用 <hbox>。同樣的道理,簡單的垂直排列需求可以用 <vlayout> 完成而不需用 <vbox>

以一個內含兩個元件的 <hbox> 為例:

    <hbox>
        <div>hbox</div>
        <div>hbox</div>
    </hbox>

其所產生的 HTML 如下:

<table id="zFLQ5" class="z-hbox" cellpadding="0" cellspacing="0"
    border="0">
    <tbody>
        <tr valign="top">
            <td id="zFLQ5-frame" style="width:100%;height:100%"
                align="left">
                <table id="zFLQ5-real" height="100%" cellpadding="0"
                    cellspacing="0" border="0" style="text-align:left">
                    <tbody>
                        <tr valign="top">
                            <td id="zFLQ6-chdex" style="height:100%">
                                <div id="zFLQ6" class="z-div">
                                    <span id="zFLQ7" class="z-label">
                                        hbox
                                    </span>
                                </div>
                            </td>
                            <td id="zFLQ6-chdex2"
                                class="z-hbox-separator">
                                <img style="height:0;width:0"></td>
                            <td id="zFLQ8-chdex" style="height:100%">
                                <div id="zFLQ8" class="z-div">
                                    <span id="zFLQ9" class="z-label">
                                        hbox
                                    </span>
                                </div>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </td>
        </tr>
    </tbody>
</table>

若是以一個同樣內含兩個元件的 <hlayout> 來比較如:

    <hlayout>
        <div>hlayout</div>
        <div>hlayout</div>
    </hlayout>

其所產生的 HTML 就小很多:

<div id="zFLQ0" class="z-hlayout">
    <div id="zFLQ1-chdex" class="z-hlayout-inner"
        style="padding-right:5px">
        <div id="zFLQ1" class="z-div">
            <span id="zFLQ2" class="z-label">hlayout</span>
        </div>
    </div>
    <div id="zFLQ3-chdex" class="z-hlayout-inner" style="">
        <div id="zFLQ3" class="z-div">
            <span id="zFLQ4" class="z-label">hlayout</span>
        </div>
    </div>
</div>