KO範例20 - 輸入員編帶出員工姓名
3 |
這個範例回到我當年寫公司內部系統第一天就碰上的需求--輸入員工編號後,後方欄位需自動帶出員工姓名。
在開始之前,要先準備一個簡單的伺服器端查詢程式(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系列]
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 方式接收. 是否可行?