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

    }
}

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。