這個範例回到我當年寫公司內部系統第一天就碰上的需求--輸入員工編號後,後方欄位需自動帶出員工姓名。

在開始之前,要先準備一個簡單的伺服器端查詢程式(EmpData.ashx)當內應,送入員編後要傳回包含EmpNo, EmpName資料的JSON格式(查不到時EmpNo與EmpName為null)。老樣子,我們使用奧林匹克指定假資料--Colors的141個具名顏色充當員工姓名,依序編為0001到0141號:

<%@ WebHandler Language="C#" Class="EmpData" %>
 
using System;
using System.Web;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using System.Web.Script.Serialization;
 
public class EmpData : IHttpHandler {
 
    public class User
    {
        public string EmpNo { get; set; }
        public string EmpName { get; set; }
    }
    private static Dictionary<string, User> users = null;
    private static Dictionary<string, User> Users
    {
        get
        {
            int i = 0;
            if (users == null)
            {
                users = typeof(System.Drawing.Color)
                .GetProperties(BindingFlags.Static | BindingFlags.Public)
                .ToDictionary(
                    o => (++i).ToString(),
                    o => new User() { 
                        EmpNo = i.ToString("0000"), EmpName = o.Name 
                    }
                );
            }
            return users;
        }
    }    
    
    
    public void ProcessRequest (HttpContext context) {
        context.Response.ContentType = "text/plain";
        string empNo = (context.Request["u"] ?? string.Empty).TrimStart('0');
        User u = Users.ContainsKey(empNo) ? Users[empNo] : new User();
        JavaScriptSerializer jss = new JavaScriptSerializer();
        context.Response.Write(jss.Serialize(u));
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }
 
}

網頁部分有兩個<input>欄位,分別繫結到ViewModel的userId及userName兩個observable,另外比照範例18 - 整合Ajax的做法,宣告一個computed,以userId()當參數,呼叫jQuery.getJSON()從EmpData.ashx取值,就大功告成了! 線上展示

<!DOCTYPE html>
 
<html>
<head>
    <title>Lab 20 - 輸入員編自動帶入員工姓名</title>
    <script src="../Scripts/jquery-1.7.2.js"></script>
    <script src="../Scripts/knockout-2.1.0.debug.js"></script>
    <script>
        $.ajaxSettings.cache = false;
        function MyViewModel() {
            var self = this;
            self.userId = ko.observable();
            self.userName = ko.observable();
            ko.computed(function () {
                $.getJSON("EmpData.ashx", { u: self.userId() },
                function (res) {
                    self.userName(res.EmpName);
                });
            });
 
        }
        $(function () {
            ko.applyBindings(new MyViewModel());
        });
    </script>
    <style>
        body, input
        {
            font-size: 9pt;
        }
        .user-name
        {
            background-color: #C0C0C0;
        }
    </style>
</head>
<body>
    員編: <input type="text" data-bind="value: userId" style="width: 50px;"/>
    <input type="text" data-bind="value: userName" class="user-name" readonly />
    <br />
    <span>有效員編: "0001"-"0141"</span>
</body>
</html>

EmpData.ashx很貼心,員編前方的0可以省略不敲,但規格要求查到姓名的同時要一併校正員編將0補齊。例如: 員編輸入123,查詢後姓名填入"Sienna",員編要調為"0123"。

這太簡單了! 線上展示

            ko.computed(function () {
                $.getJSON("EmpData.ashx", { u: self.userId() },
                function (res) {
                    if (res.EmpNo !== null)
                        self.userId(res.EmpNo);
                    self.userName(res.EmpName);
                });
            });

但有個小問題:

由於computed相依於userId,當userId被修改為0123,也會觸發重算。所以操作過程會發生兩次Ajax查詢,一次傳入123,一次傳入0123。第二次查詢傳回0123時,雖然會再一次修改userId,但因為0123改成0123不會觸發onchange,才免於陷入無窮迴圈的危機。

要處理這個問題,我們要讓computed聰明一點點,知道在特定情況下不必重算,故程式可修改如下:

            //允許暫時停用姓名查詢
              var disableNameLookup = false;
            ko.computed(function () {
                //不論是否觸發Ajax查詢,都讀取userId()以保持相依性
                var empNo = self.userId();
                if (!disableNameLookup) {
                    $.getJSON("EmpData.ashx", { u: empNo },
                    function (res) {
                        if (res.EmpNo !== null) {
                            //先暫停Ajax查詢
                                disableNameLookup = true;
                            self.userId(res.EmpNo);
                            //恢復Ajax查詢
                                disableNameLookup = false;
                        }
                        self.userName(res.EmpName);
                    });
                }
            });

透過disabledNaqmeLookup旗標,我們可以避免因修改userId引發Ajax查詢。其中有個眉角是讀取userId的邏輯必須提取在if條件式之外,確保每次computed重算時都會執行才能維持相依性。(看似信手捻來,其實花了我近一個小時才參透這點,可謂"台上一行Code,台下想破頭"... )

修改後的線上展示就能避開多餘的Ajax查詢囉~

【補充】Ryan Niemey寫了一個可暫停重算的pauseableComputed可實現類似效果,而他的Blog(http://www.knockmeout.net)有許多深入的KO文章,值得一看。

[KO系列]

http://www.darkthread.net/kolab/labs/default.aspx?m=post

Comments

# by Vincent

想問問, ajax pass JSON to server $.ajax({ url:'action.php', data:JSON.stringify(ko.toJS(self.contacts), null, 2), type:'POST', contentType:"application/json;charset=utf-8" }); 是不是一定要 php://input 的方法, 不能用 $_POST 接 value 呢. 謝謝.

# by Jeffrey

to Vincent, 是的,由於整個Post內容就是JSON字串,故在PHP端要以Stream方式讀入解析。

# by Vincent

謝謝回答, 若不想用JSON型式, 用回傳統的Key=value 方式, 把 JSON 轉換成 string 是否可行呢. 怎麼方便的把 view model (complex data structure) 變成純 string. 然後PHP 用 $_POST 方式接收. 是否可行?

Post a comment