在網頁設計中輸入欄位連動是很常見的情境,例如有員工編號及員工姓名兩個欄位,當使用者在輸入員工編號後,系統需自動帶出員工姓名。一般最直覺做法是利用<input>的onchange或onblur事件,在使用者輸入完成後送出AJAX呼叫向伺服器查詢後設定姓名欄位。程式範例如下: (展示)

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>傳統OnChange觸發</title>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.js"></script>
    <script>
        $(function () {
            $("#txtEmpNo").change(function () {
                $.get(
                    "DataSrc.ashx",
                    { key: $(this).val() },
                    function (res) {
                        $("#txtEmpName").val(res);
                    });
            });
        });
    </script>
    <style>
        .fld {
            width: 80px;
        }
    </style>
</head>
<body>
員編: 
<input id="txtEmpNo" class="fld"/> 
<input id="txtEmpName" readonly class="fld" style="background: #CCC" />
<input type="button" value="查詢" />
</body>
</html>

預期的操作流程是: 使用者輸入員工編號,右方灰底唯讀欄位帶出員工姓名供確認員編無誤,接著再按下查詢。

但是因為帶出姓名的邏輯放在change事件,使用者輸入124後,必須按Tab或點選其他網頁元素才會觸發AJAX呼叫。不熟操作的使用者,可能會在輸入完124後停止動作,卻苦等不到姓名出現,摸索一段時間才學到"多按一下Tab"的撇步。

為了改善操作流暢度,我們試著將查詢姓名的邏輯由onchange事件移到onkeyup按鍵事件,如此使用者按完鍵,毋需按Tab或點選其他欄位,系統就會直接帶出姓名。這種設計,應用於編號長度固定的情境還算完美,因為在keyup事件中可以檢查已輸入的文字長度,等待編碼完整再送出查詢。但若有效資料長度不一,就會產生困擾。例如: 在我們的範例程式中,1代表Transparent、11代表Blue、114代表Purple,於是在輸入114的過程,左側的欄位會依序冒出Transparent、Blue及Purple(見下方動畫展示),這意味著程式觸發了無效查詢,也干擾了使用者操作。(展示)

開發世界高手如雲,便有人想出巧妙解法 -- 使用者在輸入過程一般會先有連續打字的階段,輸入完畢後則會出現停頓,因此程式便可依按鍵間隔偵測使用者是否已輸入完畢,待全部輸入完畢後再送出查詢。透過這個技巧,電腦瞬間通人性,不需要多按鍵,又知道等輸入完成再查詢,提供更好的使用者體驗。

要使用JavaScript實踐構想並不困難,只需要在每次keyup事件時,不要直接送出AJAX,而是以window.setTimeout預定一段時間再送出(例如: 1秒),而在設定setTimeout時要留下識別,以便下次keyup時,先以clearTimeout清除上次預約的AJAX動作,再設定新的預約AJAX動作。如此,若按鍵後未滿一秒又按鍵,前次的AJAX查詢就會被取消,直到打完字停頓超過一秒沒按任何鍵,AJAX查詢才會真的送出。這段邏輯寫起來就會像下面這樣:

       $(function () {
            var hnd;
            $("#txtEmpNo").keyup(
                function () {
                    //取消前一次預定的查詢
                    window.clearTimeout(hnd);
                    //延遲一秒後才查詢,若這一段內又輸入其他字元
                    //則此一預約執行會被上一行程式取消
                    var value = $(this).val();
                    hnd = window.setTimeout(function () {
                        $.get(
                            "DataSrc.ashx",
                            { key: value },
                            function (res) {
                                $("#txtEmpName").val(res);
                            });
                    }, 1000);
            });
        });

操作過程如以下動畫所示,不需要額外Tab或點選,在輸入完成後又能自動帶出姓名,此種設計是不是更善解人意呢? (展示)

為了解說原理,我們剛才用setTimeout、clearTimeout自己造了輪子。但實務上不用這麼麻煩,網路已有現成Plugin可以實現前述構想,它有個術語叫Debounce(借用自電子學術語,指在接收訊息時避免發生誤動作的濾波機制),簡單列出幾個jQuery Plugin:

以下是改用doTimeout Plugin後的寫法: (展示)

        $(function () {
            $("#txtEmpNo").keyup(
                function () {
                    $(this).doTimeout("findEmpName",
                        1000, function () {
                            $.get(
                                "DataSrc.ashx",
                                { key: $(this).val() },
                                function (res) {
                                    $("#txtEmpName").val(res);
                                });
                        });
            });
        });

學會這招,大家不妨檢視自己專案中採用onchange、onblur觸發AJAX連動欄位的場合,看看有無調整的需要,試著讓自己的網頁作品更貼心吧!


Comments

# by Ga

這真的是個好東西耶, 以前都沒想到@@

# by player

我在jqGrid裡用了類似這種功能 可是好難寫 花了一天才實驗出來 formatter unformatter edittype='custom' editoptions 的 custom_element 與 ustom_value 通通都用上了

# by monkey

想問大大個問題 "DataSrc.ashx" 這是 擷取哪個地方...看不太懂,不知道能請大大指導一下嗎?? 看起來好像是擷取資料表內的資料??但是....為什麼會是這樣的名字不明白??

# by Jeffrey

to monkey, ASHX是ASP.NET裡Generic Handler(泛型處理常式)的副檔名,你可以想像成一個不包含HTML UI,呼叫後傳回Text或JSON的ASP.NET網頁(參考: http://www.dotblogs.com.tw/threeday0905/archive/2011/01/07/20648.aspx)。在本範例中,DataSrc.ashx的角色是一支網頁程式,接收Request["key"]傳入的員工編號,查詢資料來源傳回員工姓名,由於我們的焦點在於Client端處理,將其視為黑盒子即可。

# by monkey

回版主大大:那如果用以上的code我用 php 製作,更改成與您一樣的方式,當使用者輸入資料就可以帶出相關資料(去擷取資料庫),這樣他要換去 get 哪裡... 這裡有點不太明白,懇請大大指教,感謝

# by Jeffrey

to monkey, 抱歉,不會寫PHP無法直接給範例,大致的方向是寫一個DataSrc.php取代DataSrc.ashx,在PHP中用$_GET['key']取得傳入參數,查詢資料庫取得對應資料,再以print(...)將結果傳回。

# by monkey

感謝大大的解析 :) 小弟已經有些明白了 謝謝您不辭辛勞的回答我

# by 小蔡

請問如何完成多個欄位 function (res) { $("#txtEmpName").val(res); }); 要怎麼改

# by Jeffrey

to 小蔡,不太明膫完成多欄位的定義,需要再多一點解釋。

Post a comment