原文地址:http://ddmvc4.codeplex.com/

原文名称:Design and Develop a website using ASP.NET MVC 4, EF, Knockoutjs and Bootstrap

Part 1: 创建 Web Application (Knockout.js, Asp.Net MVC and Bootstrap): 前端设计

在开始 UI 部分之前,我们先看一下在 ASP.NET MVC4 中使用 Knockoutjs 和 Bootstrap 有什么好处?

Why Knockoutjs: Knockout 使用 JavaScript ViewModel 实现了 MVVM 模式. 在 MVC 中还有一个很棒的因素是从 Javascript 模型序列化为 Json 和从 Json 反序列为模型都很简单,在 MVC4 中已经包含了这个脚本库, 这使得在我们开发复杂的 UI 的时候,不论怎样修改,都只需要很少的编码,马上我们用它来实现页面。

Why Bootstrap: Twitter 的 Bootstrap 是包括简单并且灵活的 HTML, CSS, 以及广受欢迎的 Javascript 用户界面组件和交互。包括一组 CSS 样式,组件和 JavaScript 插件。提供了跨平台的支持, 消除了不同平台的不一致问题。处理的非常好,良好的文档和 Twitter Bootstrap's 站点本事就是现实中很棒的参考。最后,它节省了我大量的时间,只需要很少的测试,几乎没有浏览器的问题,节约了一半的开发时间,在我们的框架中其它优点还包括。

  • 12-列表个, 固定布局, 流式布局以及响应式布局.
  • 提供基本的 CSS, 包括:版式, code (使用  Google prettify 的语法高亮), 表格, 表单, 按钮,以及 Glpyhicons 图标 .
  • Web UI 组件,例如 按钮, 导航菜单, 标签, 缩略图, 提示, 进度条和其他杂项.
  • Javascript 插件,包括模式对话框, 下拉列表, 滚动条, 窗格, 工具提示, 弹出窗口, 提示, 按钮, 收缩, 转轮和提示.

在下面的步骤中,我们将演练使用测试数据来创建布局,设计 UI ,完成上述的目标。Step 1:

创建空白的应用; 命名为 “Application”

Step 2:

在解决方案上右击鼠标,添加一个新的 ASP.NET MVC4 项目,选择 Internet Application 模版,使用 Razor 引擎。


完成第 2 步之后 - 项目的结构如下所示

Step 3:

在 MVC 项目上鼠标右击,选择管理 NuGet 包,在搜索框中输入 Bootstrap ,找到后,点击 Install 按钮。

有的时候,我会联不到 NuGet 网站,你可以直接到 BootStrap 网站下载文件,添加到项目中。

主要是使用了样式文件 bootstrap.css ,下载后,保存到 Content 文件夹中。

Step 4:

将下面的两行代码添加到 App_Start 文件夹中德 BundleConfig.cs 文件中,为所有页面使用 Knockoutjs 和 Bootstrap 提供支持。

  1. bundles.Add(new ScriptBundle("~/bundles/knockout").Include(
  2. "~/Scripts/knockout-{version}.js"));
  3. bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/bootstrap.css"));

在 Views/Shared 文件夹中 _Layout.cshtml 文件中,添加下面的行,注册使用 knockout。

Also in _Layout,cshtml file under Views/Shared folder add below line to register knockout files as :

  1. @Scripts.Render("~/bundles/knockout")

Step 5:

在视图文件夹 Views 中添加一个名为 Contack 的文件夹,在其中添加一个名为 Index.cshtml 的视图文件 ( 这一步可以通过在控制器中的 Index 方法上点击右键完成 )。然后添加一个名为 ContactController 的控制器,在 Scripts 文件夹中添加一个名为 Contact.js 的脚本文件,项目文件夹如下所示:

Step 6:

修改 Route.config 文件,将默认的路由设置到 Contact 控制器。

  1. routes.MapRoute(
  2. name: "Default",
  3. url: "{controller}/{action}/{id}",
  4. defaults: new { controller = "Contact", action = "Index", id = UrlParameter.Optional }
  5. );

修改 View/Shared 文件夹中的 _Layout.cshtml 文件,使用 BootStrap 语法,修改后的代码如下所示。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8" />
  5. <title>@ViewBag.Title - Contact manager</title>
  6. <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
  7. <meta name="viewport" content="width=device-width" />
  8. @Scripts.Render("~/bundles/jquery")
  9. @Scripts.Render("~/bundles/knockout")
  10. @Styles.Render("~/Content/css")
  11. @Scripts.Render("~/bundles/modernizr")
  12. @RenderSection("scripts", required: false)
  13. </head>
  14. <body>
  15.  
  16. <div class="container-narrow">
  17. <div class="masthead">
  18. <ul class="nav nav-pills pull-right">
  19.  
  20. </ul>
  21. <h3 class="muted">Contact Manager</h3>
  22. </div>
  23. <div id="body" class="container">
  24. @RenderSection("featured", required: false)
  25. <section>
  26. @RenderBody()
  27. </section>
  28. </div>
  29. <hr />
  30. <div id="footer">
  31. <div class="container">
  32. <p class="muted credit">&copy; @DateTime.Now.Year - Design and devloped by <a href="http://www.anandpandey.com">Anand Pandey</a>.</p>
  33. </div>
  34. </div>
  35. </div>
  36.  
  37. </body>
  38. </html>

Step 7:

现在可以运行程序,效果如下:

我们使用这个页面显示 Screen 1 ,显示联系人列表。

Step 8:

首先,我们在 Contact.js 中创建一个模拟的联系人数据数组,  (最后我们从数据库中获取), 随后,我们使用这些数据填充表格。

  1. var DummyProfile = [
  2. {
  3. "ProfileId": 1,
  4. "FirstName": "Anand",
  5. "LastName": "Pandey",
  6. "Email": "anand@anandpandey.com"
  7. },
  8. {
  9. "ProfileId": 2,
  10. "FirstName": "John",
  11. "LastName": "Cena",
  12. "Email": "john@cena.com"
  13. }
  14. ]

然后,我们创建 ProfilesViewModel, ViewModel 用来保存联系人信息,数组用来保存联系人信息的集合。注意这里使用 ko.observableArray,         相当于常规数组,是观察者模式中的主题,这意味着它可以在其中的项目发生变化的时候,自动更新界面。

最后,我们需要使用 ko.applyBindings() 来激活 knockout.

  1. var ProfilesViewModel = function () {
  2. var self = this;
  3. var refresh = function () {
  4. self.Profiles(DummyProfile);
  5. };
  6.  
  7. // Public data properties
  8. self.Profiles = ko.observableArray([]);
  9. refresh();
  10. };
  11. ko.applyBindings(new ProfilesViewModel());

Step 9:

下一步,我们需要在 Index.cshtml 页面写一些代码。以显示联系人列表。我们在 tbody 元素上使用 foreach 绑定,使用 knockout 根据联系人数组中的每一个数据生成对应的子元素,然后告诉 knockout 我们希望使用对每个数据生成 tr 来填充 tbody.

  1. <table class="table table-striped table-bordered table-condensed">
  2. <tr>
  3. <th>First Name</th>
  4. <th>Last Name</th>
  5. <th>Email</th>
  6. </tr>
  7. <tbody data-bind="foreach: Profiles">
  8. <tr>
  9. <td data-bind="text: FirstName"></td>
  10. <td data-bind="text: LastName"></td>
  11. <td data-bind="text: Email"></td>
  12. </tr>
  13. </tbody>
  14. </table>
  15.  
  16. <script src="~/Scripts/Contact.js"></script>
  1.  

如果现在运行程序,就会看到联系人的简单列表

不要忘了,我们使用 Bootstrap 的样式类来应用到 table 上,在上边的例子中,如下所示。

  1. <table class="table table-striped table-bordered table-condensed">

Step 10:

现在,我们需要为每一行增加编辑和删除功能,表格的上边有一个创建新联系人的按钮,做以下工作:

  • 在模版中添加 th 和 td ,在脚本中绑定 removeProfile 函数处理按钮的点击事件处理
  • 修改名字单元格,增加编辑联系人的链接,使用 editProfile 函数绑定点击事件
  • 在表格的前面添加创建联系人的按钮,使用 createProfile 函数绑定到点击事件处理

页面内容如下所示 :

  1. <input type="button" class="btn btn-small btn-primary" value="New Contact" data-bind="click:$root.createProfile" />
  2. <hr />
  3. <table class="table table-striped table-bordered table-condensed">
  4. <tr>
  5. <th>First Name</th>
  6. <th>Last Name</th>
  7. <th>Email</th>
  8. <th></th>
  9. </tr>
  10. <tbody data-bind="foreach: Profiles">
  11. <tr>
  12. <td class="name"><a data-bind="text: FirstName, click: $parent.editProfile"></a></td>
  13. <td data-bind="text: LastName"></td>
  14. <td data-bind="text: Email"></td>
  15. <td><button class="btn btn-mini btn-danger" data-bind="click: $parent.removeProfile">remove</button></td>
  16. </tr>
  17. </tbody>
  18. </table>
  19.  
  20. <script src="~/Scripts/Contact.js"></script>

执行的效果如下所示:

但是现在按钮都不能工作,因为我们还没有编写响应的代码,下一步我们来处理这个问题。

Step 11:

在 Contact.js 中,编写  createProfile 函数, editProfile 函数,以及 removeProfile 函数。

  1. self.createProfile = function () {
  2. alert("Create a new profile");
  3. };
  4.  
  5. self.editProfile = function (profile) {
  6. alert("Edit tis profile with profile id as :" + profile.ProfileId);
  7. };
  8.  
  9. self.removeProfile = function (profile) {
  10. if (confirm("Are you sure you want to delete this profile?")) {
  11. self.Profiles.remove(profile);
  12. }
  13. };
  1.  

现在运行程序,点击删除联系人按钮,将会从数组中删除当前的联系人。由于我们定义了一个观察者数组,所以,UI 界面会与数组保持同步。点击删除按钮可以测试一下。编辑链接和创建联系人链接只是简单地弹出一个提示框,下一步我们实现它们。

Step 12:

下面我们会增加:

  • 在 ContactController.cs 类中增加一个 CreateEdit 的 Action 方法,并添加对应的视图。
  1. public ActionResult CreateEdit()
  2. {
  3. return View();
  4. }
  • 在 Scripts 文件夹中增加一个名为 CreateEdit.js 的新脚本文件
  • 修改Contact.js 中的 createProfile 和 editProfile 方法, 以便指向 CreateEdit 页面。
  1. self.createProfile = function () {
  2. window.location.href = '/Contact/CreateEdit/0';
  3. };
  4.  
  5. self.editProfile = function (profile) {
  6. window.location.href = '/Contact/CreateEdit/' + profile.ProfileId;
  7. };
  1.  

重新运行程序,所有的事件处理都可以工作了。创建和编辑联系人会定有参数的重定向到 CreateEdit 页面。

Step 13:

首先,我们从 CreateEdit 页面的添加联系人信息开始,我们需要如下的工作:

我们需要从 Url 中获取联系人的标识,下面的前两行处理这个问题。

  1. var url = window.location.pathname;
  2. var profileId = url.substring(url.lastIndexOf('/') + 1);
  3. var DummyProfile = [
  4. {
  5. "ProfileId": 1,
  6. "FirstName": "Anand",
  7. "LastName": "Pandey",
  8. "Email": "anand@anandpandey.com"
  9. },
  10. {
  11. "ProfileId": 2,
  12. "FirstName": "John",
  13. "LastName": "Cena",
  14. "Email": "john@cena.com"
  15. }
  16. ]

Profile 是一个简单的 Javascript 类,用来存储联系人的姓名和电子邮件。

  1. var Profile = function (profile) {
  2. var self = this;
  3.  
  4. self.ProfileId = ko.observable(profile ? profile.ProfileId : 0);
  5. self.FirstName = ko.observable(profile ? profile.FirstName : '');
  6. self.LastName = ko.observable(profile ? profile.LastName : '');
  7. self.Email = ko.observable(profile ? profile.Email : '');
  8. };

ProfileCollection,是一个 ViewModel 类,用来保存联系人信息,处理 saveProfile 和 backToProfileList 事件。

  1. var ProfileCollection = function () {
  2. var self = this;
  3.  
  4. //if ProfileId is 0, It means Create new Profile
  5. if (profileId == 0) {
  6. self.profile = ko.observable(new Profile());
  7. }
  8. else {
  9. var currentProfile = $.grep(DummyProfile, function (e) { return e.ProfileId == profileId; });
  10. self.profile = ko.observable(new Profile(currentProfile[0]));
  11. }
  12.  
  13. self.backToProfileList = function () { window.location.href = '/contact'; };
  14.  
  15. self.saveProfile = function () {
  16. alert("Date to save is : " + JSON.stringify(ko.toJS(self.profile())));
  17. };
  18. };

最后,使用 ko.applyBindings() 激活 Knockout.

  1. ko.applyBindings(new ProfileCollection());

Step 14:

下一步,我们需要在 CreateEdit.cshtml 页面写代码以支持显示联系人信息。我们需要使用 with 绑定联系人数据,以便它生成特定联系人的响应子元素,并赋予适当的值。代码如下:

  1. <table class="table">
  2. <tr>
  3. <th colspan="3">Profile Information</th>
  4. </tr>
  5. <tr></tr>
  6. <tbody data-bind='with: profile'>
  7. <tr>
  8. <td>
  9. <input class="input-large" data-bind='value: FirstName' placeholder="First Name"/>
  10. </td>
  11. <td>
  12. <input class="input-large" data-bind='value: LastName' placeholder="Last Name"/>
  13. </td>
  14. <td>
  15. <input class="input-large" data-bind='value: Email' placeholder="Email" />
  16. </td>
  17. </tr>
  18. </tbody>
  19. </table>
  20.  
  21. <button class="btn btn-small btn-success" data-bind='click: saveProfile'>Save Profile</button>
  22. <input class="btn btn-small btn-primary" type="button" value="Back To Profile List" data-bind="click:$root.backToProfileList" />
  23.  
  24. <script src="~/Scripts/CreateEdit.js"></script>

当创建新联系人的时候。

编辑现有的编号为 1 的联系人的时候:

更新现有的数据,点击保存的时候:

对于这个界面的每个需求,我们已经完成:

2.1 用户可以编辑姓名,电子邮件地址。
2.6 点击保存按钮保存到数据库,返回联系人列表。
2.7 点击返回,回到联系人列表。

下面我们处理下面的两个需求:
2.2 通过点击添加按钮,用户可以为联系人增加多个电话
2.3 用户可以删除电话

Step 15:

为了达到 2.2 和 2.3 的需求,我们需要:
在 CreateEdit.js 中定义一个假的 PhoneType 和 PhoneDTO 数据数组。phoneTypeData 将用来绑定到下列列表.

  1. var phoneTypeData = [
  2. {
  3. "PhoneTypeId": 1,
  4. "Name": "Work Phone"
  5. },
  6. {
  7. "PhoneTypeId": 2,
  8. "Name": "Personal Phone"
  9. }
  10. ];
  11.  
  12. var PhoneDTO = [
  13. {
  14. "PhoneId":1,
  15. "PhoneTypeId": 1,
  16. "ProfileId":1,
  17. "Number": "111-222-3333"
  18. },
  19. {
  20. "PhoneId": 2,
  21. "PhoneTypeId": 2,
  22. "ProfileId": 1,
  23. "Number": "444-555-6666"
  24. }
  25. ];

PhoneLine 是一个 JavaScript 类,用来保存一行电话信息,

  1. var PhoneLine = function (phone) {
  2. var self = this;
  3. self.PhoneId = ko.observable(phone ? phone.PhoneId : 0);
  4. self.PhoneTypeId = ko.observable(phone ? phone.PhoneTypeId : 0);
  5. self.Number = ko.observable(phone ? phone.Number : '');
  6. };

修改 ProfileCollection ViewModel 类,对于 addPhone 和 removePhone 事件保存电话号码。

  1. var ProfileCollection = function () {
  2. var self = this;
  3.  
  4. //if ProfileId is 0, It means Create new Profile
  5. if (profileId == 0) {
  6. self.profile = ko.observable(new Profile());
  7. self.phoneNumbers = ko.observableArray([new PhoneLine()]);
  8. }
  9. else {
  10. var currentProfile = $.grep(DummyProfile, function (e) { return e.ProfileId == profileId; });
  11. self.profile = ko.observable(new Profile(currentProfile[0]));
  12. var currentProfilePhone = $.grep(PhoneDTO, function (e) { return e.ProfileId == profileId; });
  13. self.phoneNumbers = ko.observableArray(ko.utils.arrayMap(currentProfilePhone, function (phone) {
  14. return phone;
  15. }));
  16. }
  17.  
  18. self.addPhone = function () {
  19. self.phoneNumbers.push(new PhoneLine())
  20. };
  21.  
  22. self.removePhone = function (phone) { self.phoneNumbers.remove(phone) };
  23.  
  24. self.backToProfileList = function () { window.location.href = '/contact'; };
  25.  
  26. self.saveProfile = function () {
  27. alert("Date to save is : " + JSON.stringify(ko.toJS(self.profile())));
  28. };
  29. };
  1.  

Step 16:

下一步我们在 CreateEdit.cshtml 页面中添加过个节来添加电话信息。一个联系人可以拥有多个不同类型的电话,我们使用 foreach 绑定来处理电话数据,在 CreateEdit.cshtml 中添加下面的节,位置在联系人信息之后,保存按钮之前。

  1. <table class="table">
  2. <tr>
  3. <th colspan="3">Phone Information</th>
  4. </tr>
  5. <tr></tr>
  6. <tbody data-bind='foreach: phoneNumbers'>
  7. <tr>
  8. <td>
  9. <select data-bind="options: phoneTypeData, value: PhoneTypeId, optionsValue: 'PhoneTypeId', optionsText: 'Name', optionsCaption: 'Select Phone Type...'"></select>
  10. </td>
  11. <td>
  12. <input class="input-large" data-bind='value: Number' placeholder="Number" />
  13. </td>
  14. <td>
  15. <a class="btn btn-small btn-danger" href='#' data-bind=' click: $parent.removePhone'>X</a>
  16. </td>
  17. </tr>
  18. </tbody>
  19. </table>
  20. <p>
  21. <button class="btn btn-small btn-primary" data-bind='click: addPhone'>Add New Phone</button>
  22. </p>

现在运行界面如下:

编辑现有联系人的界面:

现在只剩下下面的两个需求:

2.4 用户可以添加多个地址
2.5 用户可以删除地址

Step 17:

需求的 2.4 和 2.5 类似电话处理, 下面是最终的代码:

CreateEdit.js

  1. var url = window.location.pathname;
  2. var profileId = url.substring(url.lastIndexOf('/') + 1);
  3.  
  4. var DummyProfile = [
  5. {
  6. "ProfileId": 1,
  7. "FirstName": "Anand",
  8. "LastName": "Pandey",
  9. "Email": "anand@anandpandey.com"
  10. },
  11. {
  12. "ProfileId": 2,
  13. "FirstName": "John",
  14. "LastName": "Cena",
  15. "Email": "john@cena.com"
  16. }
  17. ];
  18.  
  19. var PhoneTypeData = [
  20. {
  21. "PhoneTypeId": 1,
  22. "Name": "Work Phone"
  23. },
  24. {
  25. "PhoneTypeId": 2,
  26. "Name": "Personal Phone"
  27. }
  28. ];
  29.  
  30. var PhoneDTO = [
  31. {
  32. "PhoneId":1,
  33. "PhoneTypeId": 1,
  34. "ProfileId":1,
  35. "Number": "111-222-3333"
  36. },
  37. {
  38. "PhoneId": 2,
  39. "PhoneTypeId": 2,
  40. "ProfileId": 1,
  41. "Number": "444-555-6666"
  42. }
  43. ];
  44.  
  45. var AddressTypeData = [
  46. {
  47. "AddressTypeId": 1,
  48. "Name": "Shipping Address"
  49. },
  50. {
  51. "AddressTypeId": 2,
  52. "Name": "Billing Address"
  53. }
  54. ];
  55.  
  56. var AddressDTO = [
  57. {
  58. "AddressId": 1,
  59. "AddressTypeId": 1,
  60. "ProfileId": 1,
  61. "AddressLine1": "10000 Richmond Avenue",
  62. "AddressLine2": "Apt # 1000",
  63. "Country": "USA",
  64. "State": "Texas",
  65. "City": "Houston",
  66. "ZipCode": "70000"
  67. },
  68. {
  69. "AddressId": 2,
  70. "AddressTypeId": 2,
  71. "ProfileId": 1,
  72. "AddressLine1": "20000 Highway 6",
  73. "AddressLine2": "Suite # 2000",
  74. "Country": "USA",
  75. "State": "Texas",
  76. "City": "Houston",
  77. "ZipCode": "80000"
  78. }
  79. ];
  80.  
  81. var Profile = function (profile) {
  82. var self = this;
  83.  
  84. self.ProfileId = ko.observable(profile ? profile.ProfileId : 0);
  85. self.FirstName = ko.observable(profile ? profile.FirstName : '');
  86. self.LastName = ko.observable(profile ? profile.LastName : '');
  87. self.Email = ko.observable(profile ? profile.Email : '');
  88. self.PhoneDTO = ko.observableArray(profile ? profile.PhoneDTO : []);
  89. self.AddressDTO = ko.observableArray(profile ? profile.AddressDTO : []);
  90. };
  91.  
  92. var PhoneLine = function (phone) {
  93. var self = this;
  94. self.PhoneId = ko.observable(phone ? phone.PhoneId : 0);
  95. self.PhoneTypeId = ko.observable(phone ? phone.PhoneTypeId : 0);
  96. self.Number = ko.observable(phone ? phone.Number : '');
  97. };
  98.  
  99. var AddressLine = function (address) {
  100. var self = this;
  101. self.AddressId = ko.observable(address ? address.AddressId : 0);
  102. self.AddressTypeId = ko.observable(address ? address.AddressTypeId : 0);
  103. self.AddressLine1 = ko.observable(address ? address.AddressLine1 : '');
  104. self.AddressLine2 = ko.observable(address ? address.AddressLine2 : '');
  105. self.Country = ko.observable(address ? address.Country : '');
  106. self.State = ko.observable(address ? address.State : '');
  107. self.City = ko.observable(address ? address.City : '');
  108. self.ZipCode = ko.observable(address ? address.ZipCode : '');
  109. };
  110.  
  111. var ProfileCollection = function () {
  112. var self = this;
  113.  
  114. //if ProfileId is 0, It means Create new Profile
  115. if (profileId == 0) {
  116. self.profile = ko.observable(new Profile());
  117. self.phoneNumbers = ko.observableArray([new PhoneLine()]);
  118. self.addresses = ko.observableArray([new AddressLine()]);
  119. }
  120. else {
  121. //For Profile information
  122. var currentProfile = $.grep(DummyProfile, function (e) { return e.ProfileId == profileId; });
  123. self.profile = ko.observable(new Profile(currentProfile[0]));
  124. //For Phone number
  125. var currentProfilePhone = $.grep(PhoneDTO, function (e) { return e.ProfileId == profileId; });
  126. self.phoneNumbers = ko.observableArray(ko.utils.arrayMap(currentProfilePhone, function (phone) {
  127. return phone;
  128. }));
  129. //For Address
  130. var currentProfileAddress = $.grep(AddressDTO, function (e) { return e.ProfileId == profileId; });
  131. self.addresses = ko.observableArray(ko.utils.arrayMap(currentProfileAddress, function (address) {
  132. return address;
  133. }));
  134. }
  135.  
  136. self.addPhone = function () { self.phoneNumbers.push(new PhoneLine()) };
  137.  
  138. self.removePhone = function (phone) { self.phoneNumbers.remove(phone) };
  139.  
  140. self.addAddress = function () { self.addresses.push(new AddressLine()) };
  141.  
  142. self.removeAddress = function (address) { self.addresses.remove(address) };
  143.  
  144. self.backToProfileList = function () { window.location.href = '/contact'; };
  145.  
  146. self.saveProfile = function () {
  147. self.profile().AddressDTO = self.addresses;
  148. self.profile().PhoneDTO = self.phoneNumbers;
  149. alert("Date to save is : " + JSON.stringify(ko.toJS(self.profile())));
  150. };
  151. };
  152.  
  153. ko.applyBindings(new ProfileCollection());

CreateEdit.cshtml

  1. <table class="table">
  2. <tr>
  3. <th colspan="3">Profile Information</th>
  4. </tr>
  5. <tr></tr>
  6. <tbody data-bind='with: profile'>
  7. <tr>
  8. <td>
  9. <input class="input-large" data-bind='value: FirstName' placeholder="First Name"/>
  10. </td>
  11. <td>
  12. <input class="input-large" data-bind='value: LastName' placeholder="Last Name"/>
  13. </td>
  14. <td>
  15. <input class="input-large" data-bind='value: Email' placeholder="Email" />
  16. </td>
  17. </tr>
  18. </tbody>
  19. </table>
  20.  
  21. <table class="table">
  22. <tr>
  23. <th colspan="3">Phone Information</th>
  24. </tr>
  25. <tr></tr>
  26. <tbody data-bind='foreach: phoneNumbers'>
  27. <tr>
  28. <td>
  29. <select data-bind="options: PhoneTypeData, value: PhoneTypeId, optionsValue: 'PhoneTypeId', optionsText: 'Name', optionsCaption: 'Select Phone Type...'"></select>
  30. </td>
  31. <td>
  32. <input class="input-large" data-bind='value: Number' placeholder="Number" />
  33. </td>
  34. <td>
  35. <a class="btn btn-small btn-danger" href='#' data-bind=' click: $parent.removePhone'>X</a>
  36. </td>
  37. </tr>
  38. </tbody>
  39. </table>
  40. <p>
  41. <button class="btn btn-small btn-primary" data-bind='click: addPhone'>Add New Phone</button>
  42. </p>
  43. <hr />
  44. <table class="table">
  45. <tr><th colspan="5">Address Information</th></tr>
  46. <tbody data-bind="foreach: addresses">
  47. <tr>
  48. <td colspan="5">
  49. <select data-bind="options: AddressTypeData, value: AddressTypeId, optionsValue: 'AddressTypeId', optionsText: 'Name', optionsCaption: 'Select Address Type...'"></select>
  50. </td>
  51. </tr>
  52. <tr>
  53. <td>
  54. <input class="input-large" data-bind='value: AddressLine1' placeholder="Address Line1" />
  55. <p style="padding-top: 5px;"><input class="input-large" data-bind='value: State' placeholder="State" /></p>
  56. </td>
  57. <td>
  58. <input class="input-large" data-bind=' value: AddressLine2' placeholder="Address Line2" />
  59. <p style="padding-top: 5px;"><input class="input-large" data-bind='value: Country' placeholder="Country" /></p>
  60. </td>
  61. <td>
  62. <input class="input-large" data-bind='value: City' placeholder="City" />
  63. <p style="padding-top: 5px;"><input class="input-large" data-bind='value: ZipCode' placeholder="Zip Code" />
  64. <a class="btn btn-small btn-danger" href='#' data-bind='click: $root.removeAddress'>X</a></p>
  65. </td>
  66. </tr>
  67. </tbody>
  68. </table>
  69. <p>
  70. <button class="btn btn-small btn-primary" data-bind='click: addAddress'>Add New Address</button>
  71. </p>
  72. <hr />
  73. <button class="btn btn-small btn-success" data-bind='click: saveProfile'>Save Profile</button>
  74. <input class="btn btn-small btn-primary" type="button" value="Back To Profile List" data-bind="click:$root.backToProfileList" />
  75.  
  76. <script src="~/Scripts/CreateEdit.js"></script>
  1.  

最后,我们完成了每一个需求。

Screen 1: Contact List -显示所有联系人

Screen 2: 创建联系人 

Screen 3: 更新联系人

文章转载于:http://www.cnblogs.com/haogj/

翻译:使用 ASP.NET MVC 4, EF, Knockoutjs and Bootstrap 设计和开发站点 - 3的更多相关文章

  1. 使用 ASP.NET MVC 4, EF, Knockoutjs and Bootstrap 设计和开发站点 - 6 - 业务逻辑

    翻译:使用 ASP.NET MVC 4, EF, Knockoutjs and Bootstrap 设计和开发站点 - 6 - 业务逻辑 Part 3: 设计逻辑层:核心开发 如前所述,我们的解决方案 ...

  2. 翻译:使用 ASP.NET MVC 4, EF, Knockoutjs and Bootstrap 设计和开发站点 - 1

    原文地址:http://ddmvc4.codeplex.com/ 原文名称:Design and Develop a website using ASP.NET MVC 4, EF, Knockout ...

  3. 翻译:使用 ASP.NET MVC 4, EF, Knockoutjs and Bootstrap 设计和开发站点 - 6 - 业务逻辑

    Part 3: 设计逻辑层:核心开发 如前所述,我们的解决方案如下所示: 下面我们讨论整个应用的结构,根据应用中不同组件的逻辑相关性,分离到不同的层中,层与层之间的通讯通过或者不通过限制.分层属于架构 ...

  4. 翻译:使用 ASP.NET MVC 4, EF, Knockoutjs and Bootstrap 设计和开发站点 - 4 - 验证

    验证: 快要完成我们程序的界面部分了.剩下的事情就是在用户点击 "保存" 的时候管理验证问题了.验证是主要需求,今天就是最无知的应用也不会忽视它.通过正确的验证,用户可以知道应该输 ...

  5. 翻译:使用 ASP.NET MVC 4, EF, Knockoutjs and Bootstrap 设计和开发站点 - 5 - 数据库设计

    数据库方面我们需要的主要功能如下: 联系人有姓名和电子邮件地址. 联系人可以拥有多个地址. 联系人可以拥有多个电话. 为了实现目标,我们需要在数据库中创建下列表.表与表的关系如下图所示: 数据库的脚本 ...

  6. 翻译:使用 ASP.NET MVC 4, EF, Knockoutjs and Bootstrap 设计和开发站点 - 2

    我们的目标: 需求 Screen 1: 联系人列表 - 查看所有联系人 1.1 这个 screen 将显示数据库中的所有联系人. 1.2 用户可以删除任何联系人.1.3 用户可以编辑任何联系人的详细信 ...

  7. ASP.NET MVC和EF集成AngularJS开发

    参考资料: 如何在ASP.NET MVC和EF中使用AngularJS AngularJS+ASP.NET MVC+SignalR实现消息推送 [AngularJs + ASP.NET MVC]使用A ...

  8. [翻译] 使用ASP.NET MVC操作过滤器记录日志

    [翻译] 使用ASP.NET MVC操作过滤器记录日志 原文地址:http://www.singingeels.com/Articles/Logging_with_ASPNET_MVC_Action_ ...

  9. ASP.NET MVC 中应用Windows服务以及Webservice服务开发分布式定时器

    ASP.NET MVC 中应用Windows服务以及Webservice服务开发分布式定时器一:闲谈一下:1.现在任务跟踪管理系统已经开发快要结束了,抽一点时间来写一下,想一想自己就有成就感啊!!  ...

随机推荐

  1. Java 字符串格式化详解

    Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...

  2. <译>通过PowerShell工具跨多台服务器执行SQL脚本

    有时候,当我们并没有合适的第三方工具(大部分需要付费)去管理多台数据库服务器,那么如何做最省力.省心呢?!Powershell一个强大的工具,可以很方便帮到我们处理日常的数据库维护工作 .简单的几步搞 ...

  3. 2013 Asia Changsha Regional Contest---Josephina and RPG(DP)

    题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=4800 Problem Description A role-playing game (RPG and ...

  4. SharePoint 2013管理中心里【管理服务器上的服务】不见了

    打开管理中心,准备配置Managed Metadata Service,发现"管理服务器上的服务"不见了 那我自己拼url直接访问:http://xxxx/_admin/Serve ...

  5. Android 扫描条形码(Zxing插件)

    使用Android Studio 一.在build.gradle(Module:app)添加代码  下载,调用插件 1 apply plugin: 'com.android.application' ...

  6. IOS开发基础知识--碎片51

    1:https关闭证书跟域名的验证 AFSecurityPolicy *securityPolicy = [AFSecurityPolicy defaultPolicy]; securityPolic ...

  7. Open-Test 测试驱动模式与版本号管理机制

    以测试用例驱动项目开发,coding/case俩条线并走模式.   1.开发人员只负责功能实现:   2.测试人员提供自测用例,研发人员jenkins持续集成项目后自动化执行自测用例,通过后方可转测试 ...

  8. AutoMapper(七)

    返回总目录 Null值替换 如果源类型的成员链上的属性值为Null,Null值替换允许提供一个可替换的值.下面有两个类Person和PersonInfo类,都有一个属性Title(头衔),从Perso ...

  9. ztreeDeptSelect 基于jquery和ztree的部门选择插件

    插件介绍 首先我们来看插件的功能演示(效果): 插件准备好后.前台只需编写html: <input type="text" class="deptName" ...

  10. 利用AOP写2PC框架(二)

    AOP的底层已经封装好了以后,我们就要开始针对应用层写具体的业务逻辑了. 也就是说我们需要有个类继承于AopProxyBase,并且重写其After,Bofore以达到我们的拦截记录的功能.代码如下: ...