.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號:N130713802
出刊日期:2013/7/17
Knockout是一個開放源碼,套用Model-View-ViewModel (MVVM) 模式(pattern)的JavaScript函式庫,能夠利用宣告式的語法設定UI繫結(declarative bindings)。透過Knockout,我們可以使用View與declarative bindings定義使用者介面;使用ViewModel定義資料(Data)與行為(Behavior)。Knockout支援,dependency tracking自動在資料異動時,同步物件與設定繫結的標籤。官方網站在:http://knockoutjs.com/
Model-View-ViewModel模式的定義如下:
- Model:代表應用程式儲存的資料,和UI獨立
- ViewModel:代表UI上的資料與操作(operation),不用來儲存資料,而是用來暫存使用者正在操作的資料。通常是一個JavaScript物件,不需知道任何View的細節。
- View:可視的使用者介面,用來顯示ViewModel中的資訊,可以因應使用者的互動,如點選按鈕或連結,傳送命令到ViewModel,並在ViewModel變動時,自動更新內容。
本篇文章將以step-by-step的方式,利用Knockout與ASP.NET Web API來設計網頁應用程式。範例看起來如下,上方的區塊用來新增資料;下方的清單顯示資料表目前的所有資料。清單中的項目都包含一個「Delete」按鈕用來刪除資料;「Edit」按鈕則用來切換到編輯模式,以修改資料,請參考下圖所示:

圖 1:整合Knockout與ASP.NET Web API設計CRUD網頁範例。
建立專案
從Visual Studio 2012開發工具 -「File」-「New」-「Project」,在「New Project」對話盒中選取程式語言,例如本範例選擇「Visual C#」,從「Web」分類中,選取「ASP.NET MVC 4 Web Application」,名稱設定為「KODemo」稱;按下「OK」鍵,請參考下圖所示:

圖 2:建立「ASP.NET MVC 4 Web Application」專案。
在「New ASP.NET MVC 4 Project」對話盒選取「Web API」類型專案,檢視引擎(View Engine)使用預設的「Razor」,按下「OK」鍵,請參考下圖所示:

圖 3:選取「Web API」類型專案。
完成這個步驟之後,就建立好一個Web API範本專案,在Visual Studio 2012開發工具按F5執行這個應用程式,Visual Studio 2012預設會使用IIS Express來執行你的網站應用程式。自動賦予此網站一個埠號來執行,此筆者目前的環境而言,網站路徑為「http://localhost: 51207」,代表使用51207埠。
http://localhost:51207/
網站應用程式執行後會啟動瀏覽器,首先會顯示ASP.NET Web API範例頁面,請參考下圖所示:

圖 4:ASP.NET Web API範例頁面。
從「方案總管(Solution Explorer)」視窗,選取專案下「Models」目錄,按滑鼠右鍵,選取「Add」-「New Item」加入一個新項目,選取「ADO.NET Entity Data Model」,使用預設的「Model1.edmx」當作名稱,按「Add」按鈕,請參考下圖所示:

圖 5:新增「ADO.NET Entity Data Model」。
在「實體資料模型精靈(Entity Data Model Wizard)」視窗中,目前有兩種選項:「Generate from database」與「Empty model」。選擇模型內容為「Generate from database」,然後選「Next」按鈕,請參考下圖所示:

圖 6:「Generate from database」從資料庫建立模型。
接著設定資料連接,點選「New Connection」開啟「Connection Properties」視窗,請參考下圖所示:

圖 7:新增資料庫連線。
在「Connection Properties」視窗,設定「Data Source」為「Microsoft SQL Server」;「Server Name」則輸入您的資料庫伺服器名稱,本文使用「.\Sqlexpress」;「Select or enter a database name」則選取「Northwind」資料庫,然後按下「OK」按鈕,請參考下圖所示:

圖 8:連接到SQL Server。
選「Next」按鈕,勾選「Region」資料表,以及「Pluralize or singularize generated object names」然後按下「Finish」按鈕,請參考下圖所示:

圖 9:選取資料表。
選取Visual Studio 2012開發工具上方的「Build」-「Build Solution」編譯目前的方案。
新增Controller
從Visual Studio 2012開發工具-「Solution Explorer」- 你的專案-「Controllers」目錄上方按滑鼠右鍵,從快捷選單選擇「Add」- 「Controller」。設定控制器名稱為「RegionsController」;在這個階段Template設定為「API controller with read/write actions, using Entity Framework」,Model Class選擇「Region」,Data context class選取「NorthwindEntities」,然後按下「Add 」按鈕,請參考下圖所示:

圖 10:新增Controller。
Visual Studio 2012會自動在Controllers目錄中,產生RegionsController.cs檔案,包含利用Entity Framework來新增、刪除、修改、查詢資料的程式碼:
namespace KODemo.Controllers {
public class RegionsController : ApiController {
private NorthwindEntities db = new NorthwindEntities( );
// GET api/Regions
public IEnumerable<Region> GetRegions( ) {
return db.Regions.AsEnumerable( );
}
// GET api/Regions/5
public Region GetRegion( int id ) {
Region region = db.Regions.Find( id );
if ( region == null ) {
throw new HttpResponseException( Request.CreateResponse( HttpStatusCode.NotFound ) );
}
return region;
}
// PUT api/Regions/5
public HttpResponseMessage PutRegion( int id , Region region ) {
if ( !ModelState.IsValid ) {
return Request.CreateErrorResponse( HttpStatusCode.BadRequest , ModelState );
}
if ( id != region.RegionID ) {
return Request.CreateResponse( HttpStatusCode.BadRequest );
}
db.Entry( region ).State = EntityState.Modified;
try {
db.SaveChanges( );
} catch ( DbUpdateConcurrencyException ex ) {
return Request.CreateErrorResponse( HttpStatusCode.NotFound , ex );
}
return Request.CreateResponse( HttpStatusCode.OK );
}
// POST api/Regions
public HttpResponseMessage PostRegion( Region region ) {
if ( ModelState.IsValid ) {
db.Regions.Add( region );
db.SaveChanges( );
HttpResponseMessage response = Request.CreateResponse( HttpStatusCode.Created , region );
response.Headers.Location = new Uri( Url.Link( "DefaultApi" , new { id = region.RegionID } ) );
return response;
}
else {
return Request.CreateErrorResponse( HttpStatusCode.BadRequest , ModelState );
}
}
// DELETE api/Regions/5
public HttpResponseMessage DeleteRegion( int id ) {
Region region = db.Regions.Find( id );
if ( region == null ) {
return Request.CreateResponse( HttpStatusCode.NotFound );
}
db.Regions.Remove( region );
try {
db.SaveChanges( );
} catch ( DbUpdateConcurrencyException ex ) {
return Request.CreateErrorResponse( HttpStatusCode.NotFound , ex );
}
return Request.CreateResponse( HttpStatusCode.OK , region );
}
protected override void Dispose( bool disposing ) {
db.Dispose( );
base.Dispose( disposing );
}
}
}
讀取資料
預設建立Web API專案時,Scripts目錄下,已經包含了jQuery與Knockout函式庫,您可以在專案中直接使用它們,請參考下圖所示:

圖 11:ASP.NET Web API專案預設引用jQuery、knockout函式庫。
預設建立Web API專案時,Controllers目錄中包含了HomeController,其中包含Index Action:
namespace KODemo.Controllers {
public class HomeController : Controller {
public ActionResult Index( ) {
return View( );
}
}
}
專案中「Views」-「Home」目錄下,包含Index.cshtml,此為網站的首頁。刪掉Index.cshtml所有內容,只留下id為「body」的<div>標籤
我們要利用jQuery的$.ajax方法來讀取Web API的資料,在<div>標籤之後加入以下程式碼(url中的51207埠號,請記得修改成正確的):
@section scripts{
<script>
var url = "
http://localhost:51207/api/Regions/"; $.ajax({
url: url,
type: 'GET',
dataType: 'json',
success: function (data) {
alert( data.length);
},
error: function (e) {
alert(e);
}
});
</script>
}
$.ajax方法傳入一個settings物件,設定:
- url:為ASP.NET Web API所在網址。
- type:使用HTTP GET。
- dataType:設定為「json」,表示預期從伺服端取回JSON物件。
- success:請求成功執行時,要繼續執行的回呼方法(callback function),從data參數的length屬性,顯示回傳的資料筆數。
- error:請求執行失敗時,要繼續執行的回呼方法。
在Visual Studio 2012開發工具按F5執行這個應用程式,若讀取資料成功,便會顯示一個對話方塊,顯示目前取回4筆資料,請參考下圖所示:

圖 12:取回Region資料表資料筆數。
使用Knockout函式庫
修改Home目錄下的Index.cshtml檔案,在「@section scripts」區段的第一行引用Knockout函式庫:
<script src = "~/Scripts/knockout-2.2.0.js"> </script>
定義Region
在<script>標籤中,宣告url變數的程式碼下方加上Region 類別代表一筆Region資料,從建構函式傳入regionID、與regionDescription參數來初始化Region中的RegionID與RegionDescription屬性:
在Region中,宣告一個self變數來代表物件本身(this),這是防止this被其他程式重新定義,或參考到別的物件。
定義ViewModel
下一步在Region宣告的下方,定義regionsViewModel,其中包含一個regions屬性,屬性初始化為observableArray,以便後續在異動其中的項目時,能夠自動進行追蹤。此外,將呼叫jQuery .ajax()方法取回資料的程式放到ViewModel:
function regionsViewModel() {
var self = this;
self.regions = ko.observableArray([]);
$.ajax({
url: url,
type: 'GET',
dataType: 'json',
success: function (data) {
var mappedRegions = $.map(data, function (item) { return new Region(item.RegionID, item.RegionDescription); });
self.regions( mappedRegions );
},
error: function (e) {
alert(e);
}
});
}
修改success中的方法,叫用jQuery的$.map方法,將data陣列中的項目,轉換成Region物件,然後放到regionsViewModel的regions屬性,因為regions屬性是observableArray,所以要使用方法的語法來設定regions屬性值。
建立繫結
最後在</script>上方,加上以下程式碼,利用knockout applyBindings方法設定繫結,傳入regionsViewModel物件:
ko.applyBindings( new regionsViewModel() );
定義View
我們需要定義View來呈現ViewModel中的資料,在id為「body」的<div>標籤之中,加入以下HTML標籤:
<div id = "body">
<h2> KnockoutDemo </h2>
<table>
<thead>
<tr>
<th> RegionID </th>
<th> RegionDescription </th>
<th> </th>
</tr>
</thead>
<tbody data-bind = "foreach: regions">
<tr>
<td data-bind = "text: RegionID"> </td>
<td data-bind = "text: RegionDescription"> </td>
</tr>
</tbody>
</table>
</div>
<tbody>開頭標籤中設定「data-bind="foreach: regions"」,利用knockout foreach binding取出regionsViewModel中regions陣列內的Region物件做繫結。因為目前只需呈現唯讀的資料,我們利用knockout text binding在<td>標籤中呈現RegionID���RegionDescription的值。
完整Index.cshtml
截至目前為止,Index.cshtml完整程式看起來如下:
<div id = "body">
<h2> KnockoutDemo </h2>
<table>
<thead>
<tr>
<th> RegionID </th>
<th> RegionDescription </th>
<th> </th>
</tr>
</thead>
<tbody data-bind = "foreach: regions">
<tr>
<td data-bind = "text: RegionID"> </td>
<td data-bind = "text: RegionDescription"> </td>
</tr>
</tbody>
</table>
</div>
@section scripts{
<script src="~/Scripts/knockout-2.2.0.js"></script>
<script>
var url = "http://localhost:51207/api/Regions/";
function Region(regionID, regionDescription) {
var self = this;
self.RegionID = regionID;
self.RegionDescription = regionDescription;
}
function regionsViewModel() {
var self = this;
self.regions = ko.observableArray([]);
$.ajax({
url: url,
type: 'GET',
dataType: 'json',
success: function (data) {
var mappedRegions = $.map(data,
function (item) {
return new Region(item.RegionID, item.RegionDescription);
});
self.regions(mappedRegions);
},
error: function (e) {
alert(e);
}
});
}
ko.applyBindings(new regionsViewModel());
</script>
}
測試
在Visual Studio 2012開發工具按F5執行這個應用程式,若讀取資料成功,便會以清單的方式,顯示從ASP.NET Web API取回的資料庫資料,請參考下圖所示:

圖 13:顯示從ASP.NET Web API取回的資料庫資料。
新增資料
修改regionsViewModel程式碼,在宣告regions屬性程式下,新增一個createRegionForm方法,這個方法將傳入Form項目,我們利用jQuery .serialize()方法,將表單中的項目序列化成文字格式,以便傳送到伺服端。接著叫用jQuery .post()方法以HTTP POST送出請求,以新增一筆資料:
self.createRegionForm = function (formElement) {
$.post(url, $(formElement).serialize(), "json")
.done(function (newRegion) {
var r = new Region(newRegion.RegionID,newRegion.RegionDescription);
self.regions.push(r);
});
}
.post()方法第一個參數ASP.NET Web API所在的URL位址;第二個參數為表單序列化完的字串資料;第三個參數設為「json」,表示預期從伺服端取得JSON格式的資料。.post()方法呼叫完成後,再叫用.done()方法,將伺服端傳回的新Region資料取出,以這些資料建立Region物件,然後叫用push方法,加到regions屬性中。
建立AddRegion View
在這個範例中,我們將要建立一個部分檢視(Partial View)來當新增資料的介面。從Visual Studio 2012開發工具-「Solution Explorer」- 你的專案-「Views\Home」目錄上方按滑鼠右鍵,從快捷選單選擇「Add」- 「View」。設定View名稱為「AddRegion」;勾選「Create a strongly-typed view」;Model Class選取「Region」;Scaffold template選取「Create」,勾選「Create as a partial view」然後按下「Add 」按鈕,請參考下圖所示:

圖 14:建立AddRegion View。
刪除AddRegion.cshtml檔案最下方的標籤:
<div>
@Html.ActionLink("Back to List", "Index")
</div>
Visual Studuio 2012不會顯示RegionID欄位的標籤,因為Northwind資料庫Region資料表中,RegionID欄位並不是自動編號欄位,我們需要手動修改AddRegion.cshtml,顯示RegionID欄位以供編輯。選取工具為我們產生的兩個顯示RegionDescription介面的<div>標籤,按下滑鼠右鍵,從快捷選單中選取「Copy」,請參考下圖所示:

圖 15:複製標籤。
在<legend>下方按CTRL + V貼上標籤,修改標籤如下,呈現RegionID編輯畫面:
<div class = "editor-label">
@Html.LabelFor(model => model.RegionID)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.RegionID)
@Html.ValidationMessageFor(model => model.RegionID)
</div>
此外我們希望產生出來的<form>標籤可以加上knockout submit binding:「data-bind="submit: createRegionForm"」。
<form data-bind = "submit: createRegionForm">
submit binding指定一個JavaScript事件處理常式(createRegionForm),當表單提交後,knockout會擋掉預設表單的提交行為,不將表單送到伺服器,而叫用createRegionForm方法。
修改AddRegion.cshtml檔案中,「@using Html.BeginForm」這行程式如下,就可以產生我們需要的data-bind attribute:
@using ( Html.BeginForm( "" , "" ,
FormMethod.Post ,
new Dictionary<string , object> { { "data-bind" , "submit:createRegionForm" } } ) ) {
修改View
在Index.cshtml上方,<h2>標籤下加入以下程式碼,利用Html.Partial方法,插入AddRegion View:
<p>
@Html.Partial("AddRegion")
</p>
測試
在Visual Studio 2012開發工具按F5執行這個應用程式,試著新增一筆資料,按下「Create」按鈕,新增的資料便會顯示在畫面清單中,請參考下圖所示:

圖 16:新增一筆資料。
因為資料繫結的關係,新增完文字方塊的資料不會清除,若要清除文字方塊內容,可以
修改ViewModel createRegionForm方法,利用val()方法清空文字方塊的內容:
self.createRegionForm = function (formElement) {
$.post(url, $(formElement).serialize(), "json")
.done(function (newRegion) {
var r = new Region(newRegion.RegionID,newRegion.RegionDescription);
self.regions.push(r);
$(".editor-field #RegionID").val("");
$(".editor-field #RegionDescription").val("");
});
}
這樣資料新增完成之後,文字方塊的內容便會清空,請參考下圖所示:

圖 17:新增一筆資料完成清空畫面。
Index.cshtml完整程式
截至目前為止,Index.cshtml完整程式看起來如下:
<div id = "body">
<h2> KnockoutDemo </h2>
<p>
@Html.Partial("AddRegion")
</p>
<table>
<thead>
<tr>
<th> RegionID </th>
<th> RegionDescription </th>
<th> </th>
</tr>
</thead>
<tbody data-bind="foreach: regions">
<tr>
<td data-bind="text: RegionID"></td>
<td data-bind="text: RegionDescription"></td>
</tr>
</tbody>
</table>
</div>
@section scripts{
<script src="~/Scripts/knockout-2.2.0.js"></script>
<script>
var url = "http://localhost:51207/api/Regions/";
function Region(regionID, regionDescription) {
var self = this;
self.RegionID = regionID;
self.RegionDescription = regionDescription;
}
function regionsViewModel() {
var self = this;
self.regions = ko.observableArray([]);
self.createRegionForm = function (formElement) {
$.post(url, $(formElement).serialize(), "json")
.done(function (newRegion) {
var r = new Region(newRegion.RegionID, newRegion.RegionDescription);
self.regions.push(r);
$(".editor-field #RegionID").val("");
$(".editor-field #RegionDescription").val("");
});
}
$.ajax({
url: url,
type: 'GET',
dataType: 'json',
success: function (data) {
var mappedRegions = $.map(data,
function (item) {
return new Region(item.RegionID, item.RegionDescription);
});
self.regions(mappedRegions);
},
error: function (e) {
alert(e);
}
});
}
ko.applyBindings(new regionsViewModel());
</script>
}
刪除資料
下一步進行資料刪除的設計。首先修改ViewModel,在createRegionForm方法定義下方,加入deleteRegion方法,利用ajax方法送出HTTP DELETE,id則為欲刪除的RegionID值,若叫用完成,則從regions陣列中移除此Region物件:
self.deleteRegion = function (r) {
var id = r.RegionID;
$.ajax({ type: "DELETE", url: url + id })
.done(function () {
self.regions.remove(r);
});
}
修改View,在<tbody>-<tr>內加入第三個<td>,其中包含一個連結,利用knockout click binding繫結到regionViewModel的deleteRegion方法,我們利用$root來找到foreach外層的regionViewModel:
<td>
<a href="#" data-bind="click: $root.deleteRegion">Delete</a>
</td>
在Visual Studio 2012開發工具按F5執行這個應用程式執行測試,可以正確地刪除資料。
刪除確認
若要防止使用者誤點選「Delete」連結誤刪資料,我們可以加上刪除確認的動作,修改deleteRegion方法,利用confirm顯示確認刪除的方塊:
self.deleteRegion = function (r) {
var id = r.RegionID;
var sure = confirm("Are you sure?");
if (!sure)
return;
$.ajax({ type: "DELETE", url: url + id })
.done(function () {
self.regions.remove(r);
});
}
在Visual Studio 2012開發工具按F5執行這個應用程式執行測試,可以看到確認刪除的對話盒,請參考下圖所示:

圖 18:確認刪除的對話盒。
Index.cshtml完整程式
截至目前為止,Index.cshtml完整程式看起來如下:
<div id="body">
<h2> KnockoutDemo </h2>
<p>
@Html.Partial("AddRegion")
</p>
<table>
<thead>
<tr>
<th>RegionID </th>
<th> RegionDescription </th>
<th> </th>
</tr>
</thead>
<tbody data-bind="foreach: regions">
<tr>
<td data-bind="text: RegionID"></td>
<td data-bind="text: RegionDescription"></td>
<td>
<a href="#" data-bind="click: $root.deleteRegion">Delete</a>
</td>
</tr>
</tbody>
</table>
</div>
@section scripts{
<script src="~/Scripts/knockout-2.2.0.js"></script>
<script>
var url = "http://localhost:51207/api/Regions/";
function Region(regionID, regionDescription) {
var self = this;
self.RegionID = regionID;
self.RegionDescription = regionDescription;
}
function regionsViewModel() {
var self = this;
self.regions = ko.observableArray([]);
self.createRegionForm = function (formElement) {
$.post(url, $(formElement).serialize(), "json")
.done(function (newRegion) {
var r = new Region(newRegion.RegionID, newRegion.RegionDescription);
self.regions.push(r);
$(".editor-field #RegionID").val("");
$(".editor-field #RegionDescription").val("");
});
}
self.deleteRegion = function (r) {
var id = r.RegionID;
var sure = confirm("Are you sure?");
if (!sure)
return;
$.ajax({ type: "DELETE", url: url + id })
.done(function () {
self.regions.remove(r);
});
}
$.ajax({
url: url,
type: 'GET',
dataType: 'json',
success: function (data) {
var mappedRegions = $.map(data,
function (item) {
return new Region(item.RegionID, item.RegionDescription);
});
self.regions(mappedRegions);
},
error: function (e) {
alert(e);
}
});
}
ko.applyBindings(new regionsViewModel());
</script>
}
編輯資料
最後我們來談談資料的編輯動作,首先修改Region Model,RegionDescription屬性改用observable,新增一個oriRegionDescription屬性,儲存資料修改前的原始值,若使用者取消編輯,就可以還原資料。editMode屬性則用來記錄目前是否處於編輯模式;showEditPanel方法則用來顯示編輯介面:
function Region(regionID, regionDescription) {
var self = this;
self.RegionID = regionID;
self.RegionDescription = ko.observable(regionDescription);
self.oriRegionDescription = regionDescription;
self.editMode = ko.observable(false);
self.showEditPanel = function (event) {
self.oriRegionDescription = self.RegionDescription();
self.editMode(true);
}
self.cancelEdit = function () {
self.RegionDescription(self.oriRegionDescription);
self.editMode(false);
}
self.editRegion = function (aRegion) {
var self = this;
var r = ko.toJS(aRegion);
var json = JSON.stringify(r);
var id = r.RegionID;
$.ajax({
url: url + id,
cache: false,
type: 'PUT',
contentType: 'application/json; charset=utf-8',
data: json,
success: function () {
self.editMode(false);
}
});
}
}
修改View
在「Delete」連結下,加入一個「Edit」連結,利用knockout click binding繫結到showEditPanel方法,當使用者點選「Edit」連結,便顯示編輯介面。在<tbody>中加入第二個<tr>,利用knockout visible binding繫節到regionViewModel的editMode屬性,在進入編輯模式時,才顯示此<tr>內容。<tr>內使用文字方塊搭配knockout value binding來顯示RegionDescription內容。<tr>中還包含兩個<a>分別繫結到editRegion、cancelEdit方法,用來儲存與取消編輯。
<div id="body">
<h2>KnockoutDemo</h2>
<p>
@Html.Partial( "AddRegion" )
</p>
<table>
<thead>
<tr>
<th>RegionID</th>
<th>RegionDescription</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: regions">
<tr>
<td data-bind="text: RegionID"></td>
<td data-bind="text: RegionDescription"></td>
<td>
<a href="#" data-bind="click: $root.deleteRegion">Delete</a>
<a href="#" data-bind="click: showEditPanel">Edit</a>
</td>
</tr>
<tr data-bind="visible: editMode" style="background-color: lightblue">
<td data-bind="text: RegionID"></td>
<td>RegionDescription:<input data-bind="value: RegionDescription" />
<td>
<a href="#" data-bind="click: editRegion">Save</a>
<a href="#" data-bind="click: cancelEdit">Cancel</a>
</td>
</tr>
</tbody>
</table>
</div>
測試
在Visual Studio 2012開發工具按F5執行這個應用程式,試著點選任一筆資料的「Edit」按鈕,修改一筆資料,按下「Save」連結,便將資料儲存到資料庫,請參考下圖所示:

圖 19:編輯資料。
Index.cshtml完整程式
最後Index.cshtml完整程式看起來如下:
<div id="body">
<h2>KnockoutDemo</h2>
<p>
@Html.Partial( "AddRegion" )
</p>
<table>
<thead>
<tr>
<th> RegionID </th>
<th> RegionDescription </th>
<th> </th>
</tr>
</thead>
<tbody data-bind="foreach: regions">
<tr>
<td data-bind="text: RegionID"></td>
<td data-bind="text: RegionDescription"></td>
<td>
<a href="#" data-bind="click: $root.deleteRegion">Delete</a>
<a href="#" data-bind="click: showEditPanel">Edit</a>
</td>
</tr>
<tr data-bind="visible: editMode" style="background-color: lightblue">
<td data-bind="text: RegionID"></td>
<td>RegionDescription:<input data-bind="value: RegionDescription" />
<td>
<a href="#" data-bind="click: editRegion">Save</a>
<a href="#" data-bind="click: cancelEdit">Cancel</a>
</td>
</tr>
</tbody>
</table>
</div>
@section scripts{
<script src="~/Scripts/knockout-2.2.0.js"></script>
<script>
var url = "http://localhost:51207/api/Regions/";
function Region(regionID, regionDescription) {
var self = this;
self.RegionID = regionID;
//self.RegionDescription = regionDescription;
self.RegionDescription = ko.observable(regionDescription);
self.oriRegionDescription = regionDescription;
self.editMode = ko.observable(false);
self.showEditPanel = function (event) {
self.oriRegionDescription = self.RegionDescription();
self.editMode(true);
}
self.cancelEdit = function () {
self.RegionDescription(self.oriRegionDescription);
self.editMode(false);
}
self.editRegion = function (aRegion) {
var self = this;
var r = ko.toJS(aRegion);
var json = JSON.stringify(r);
var id = r.RegionID;
$.ajax({
url: url + id,
cache: false,
type: 'PUT',
contentType: 'application/json; charset=utf-8',
data: json,
success: function () {
self.editMode(false);
}
});
}
}
function regionsViewModel() {
var self = this;
self.regions = ko.observableArray([]);
self.createRegionForm = function (formElement) {
$.post(url, $(formElement).serialize(), "json")
.done(function (newRegion) {
var r = new Region(newRegion.RegionID, newRegion.RegionDescription);
self.regions.push(r);
$(".editor-field #RegionID").val("");
$(".editor-field #RegionDescription").val("");
});
}
self.deleteRegion = function (r) {
var id = r.RegionID;
var sure = confirm("Are you sure?");
if (!sure)
return;
$.ajax({ type: "DELETE", url: url + id })
.done(function () {
self.regions.remove(r);
});
}
$.ajax({
url: url,
type: 'GET',
dataType: 'json',
success: function (data) {
var mappedRegions = $.map(data,
function (item) {
return new Region(item.RegionID, item.RegionDescription);
});
self.regions(mappedRegions);
},
error: function (e) {
alert(e);
}
});
}
ko.applyBindings(new regionsViewModel());
</script>
}