# 七、访问外部数据

对于大多数 web 应用来说，如果不能将数据传递给服务器，收集用户输入相对来说是没有用的。在本章中，我们将学习如何使用 AJAX 请求从服务器发送和接收信息。这将把*模型*放回模型-视图-视图模型设计模式中

![](img/image027.png)

图 27:将模型添加回我们的 MVVM 模式

请记住，knockout. js 旨在与任何其他客户端或服务器端技术兼容。这本书使用了 jQuery 的`$.getJSON()`和`$.post()`函数，但是你可以自由使用任何可以发送和接收 JSON 数据的 JavaScript 框架。同样，服务器端脚本语言完全由您决定。本章没有展示后端代码示例，而是简单地包含了敲除器所期望的 JSON 数据。

## 一种新的网页表单

我们将使用一个新的 HTML 页面来试验 Knockout.js/AJAX 集成。因为这个页面必须与一些服务器端代码交互，所以请确保可以从本地服务器的文档根目录访问它。我们将从类似于前一章的内容开始:

```js
    <html lang='en'> <head>   <title>External Data</title>   <meta charset='utf-8' />   <link rel='stylesheet' href='style.css' /> </head> <body>
      <h1>External Data</h1>

      <form action="#" method="post">
        <p>First name: <input data-bind='value: firstName' /></p>
        <p>Last name: <input data-bind='value: lastName' /></p>
        <div>
          Your favorite food:
          <select data-bind='options: activities,           value: favoriteHobby'></select>
        </div>
        <p><button data-bind='click: loadUserData'>Load Data</button></p>
      </form>

      <script type='text/javascript' src='knockout-2.1.0.js'></script>
      <script type='text/javascript' src='jquery-1.7.2.js'></script>
      <script type='text/javascript'>     function PersonViewModel() {       var self = this;       self.firstName = ko.observable("");       self.lastName = ko.observable("");       self.activities = ko.observableArray([]);       self.favoriteHobby = ko.observable("");     }
         ko.applyBindings(new PersonViewModel());   </script> </body> </html>

```

这是一个带有几个`<input>`字段的基本表单，所以我们可以看到如何从服务器发送和接收信息。请注意，在我们的自定义`<script>`元素之前，我们还包括了 [jQuery](http://jquery.com/download/) 库。

## 正在加载数据

你可能注意到了，与前几章不同，我们所有的观察点都是空的。我们将使用 jQuery 的`$.getJSON()`方法从服务器加载数据，而不是将数据硬编码到我们的视图模型中。首先，让我们制作一个用于加载数据的按钮(通常，当应用启动时，您会自动加载数据，但这样我们就可以一步一步地看到一切是如何工作的):

```js
        <p><button data-bind='click: loadUserData'>Load Data</button></p>

```

该按钮的处理程序使用`$.getJSON()`调用服务器端脚本:

```js
          self.loadUserData = function() {         $.getJSON("/get-user-data", function(data) {           alert(data.firstName);         });       }

```

`/get-user-data`字符串应该是脚本的路径。同样，只要它能够对 JSON 进行编码和解码，任何服务器端语言都可以与 Lenking . js 一起使用。

```js
    {"firstName":"John",
     "lastName":"Smith",
     "activities":[
        "Golf",
        "Kayaking",
        "Web Development"],
      "favoriteHobby":"Golf" }

```

`$.getJson()`方法自动将该字符串翻译回一个 JavaScript 对象，并通过`data`参数将其传递给处理程序方法。用新信息更新我们的视图模型很简单:

```js
          self.loadUserData = function() {         $.getJSON("/get-user-data", function(data) {           self.firstName(data.firstName);           self.lastName(data.lastName);           self.activities(data.activities);           self.favoriteHobby(data.favoriteHobby);         });       }

```

单击**加载数据**按钮后，`$.getJSON()`从服务器加载数据，并使用它来更新我们的视图模型的所有观察点。和往常一样，Quicken . js 会自动更新表单字段以匹配。

## 保存数据

对于普通的 web 应用，保存数据是一件简单的事情，将对象转换成 JSON，然后用类似 jQuery 的`$.post()`方法将其发送到服务器。对于 knockout. js 应用来说，事情有些复杂。不可能使用标准的 JSON 序列化程序将对象转换为字符串，因为视图模型使用可观测的对象，而不是普通的 JavaScript 属性。请记住，可观测对象实际上是函数，因此尝试序列化它们并将结果发送到服务器会有意想不到的结果。

幸运的是，knockout. js 为这个问题提供了一个简单的解决方案:`ko.toJSON()`实用函数。将一个对象传递给`ko.toJSON()`会用其当前值替换该对象的所有可观测属性，并将结果作为 JSON 字符串返回。

创建另一个名为“保存数据”的按钮，并将其指向视图模型上的`saveUserData()`方法。然后，可以看到`ko.toJSON()`生成的 JSON，如下所示:

```js
          self.saveUserData = function() {         alert(ko.toJSON(self));       }

```

单击此按钮将显示表单字段中转换为 JSON 字符串的当前数据。现在，我们已经消除了所有可见性，我们可以将它发送到服务器进行处理:

```js
          self.saveUserData = function() {         var data_to_send = {userData: ko.toJSON(self)};         $.post("/save-user-data", data_to_send, function(data) {           alert("Your data has been posted to the server!");         });       } 

```

这将使用 POST 方法将表示您的视图模型的 JSON 字符串发送到名为`/save-user-data`的脚本。因此，您的脚本应该在其 POST 字典中的`userData`条目下找到该字符串。然后，您可以将 JSON 字符串反序列化为一个对象，将其保存到数据库中，或者执行您需要执行的任何类型的服务器端处理。

## 将数据映射到视图模型

前两节中介绍的加载和保存机制提供了创建丰富用户界面所需的一切，这些用户界面由任意服务器端脚本语言支持。然而，如果您使用的不仅仅是几个属性，手动将加载的数据映射到可观测的对象可能会变得非常乏味。

knockout. js 的`mapping`插件通过让您自动将从服务器加载的 JSON 对象映射到 ViewModel observables 来解决这个问题。本质上，`mapping`是我们`saveUserData()`和`loadUserData()`方法的通用版本。

`mapping`插件是作为一个单独的项目发布的，所以我们需要[下载](https://github.com/SteveSanderson/knockout.mapping/tree/master/build/output)并在使用前将其包含在我们的 HTML 页面中:

```js
      <script type='text/javascript' src='knockout.mapping-latest.js'></script>

```

接下来，我们将完全取代我们的`PersonViewModel`。取而代之的是，我们将使用 jQuery 的`$.getJSON()`方法从服务器中加载一些初始数据，并让`mapping`插件动态生成可观测值。用以下内容替换整个自定义`<script>`元素:

```js
      <script type='text/javascript'>     $.getJSON("/get-user-data", function(data) {       var viewModel = ko.mapping.fromJS(data);       ko.applyBindings(viewModel);     });   </script>

```

当我们的应用加载时，它会立即对初始用户数据发出 AJAX 请求。您的`/get-intial-data`服务器端脚本应该返回与本章[加载数据](#heading_id_68)部分的示例 JSON 输出相同的内容。一旦数据被加载，我们通过`ko.mapping.fromJS()`创建一个视图模型。这将采用脚本生成的本机 JavaScript 对象，并将每个属性转换为可观测的。除了`saveUserData()`和`loadUserData()`方法，这个动态生成的视图模型具有与`PersonViewModel`完全相同的功能。

此时，我们只使用来自服务器的数据初始化了我们的视图模型*。`mapping`插件也让我们*以同样的方式更新*一个现有的视图模型。让我们继续向视图模型添加一个显式的`loadUserData()`方法:*

```js
          viewModel.loadUserData = function() {         $.getJSON("/get-user-data", function(data) {           ko.mapping.fromJS(data, viewModel);         });       }

```

在旧版本的`loadUserData()`中，我们必须手动将每个数据属性分配给其各自的可观测值。但是现在，`mapping`插件为我们做了这一切。请注意，将`data`对象作为第一个参数传递给`ko.mapping.fromJS()`会导致它*更新*视图模型，而不是*初始化*视图模型。

`mapping`插件仅与加载数据相关，因此`saveUserData()`不受影响，除了它需要被分配给`viewModel`对象:

```js
          viewModel.saveUserData = function() {          var data_to_send = {userData: ko.toJSON(viewModel)};         $.post("/save-user-data", data_to_send, function(data) {           alert("Your data has been posted to the server!");         });       }

```

现在我们应该回到本节开始时的位置——加载数据按钮和**保存数据按钮**都应该可以工作，并且 knockout. js 应该保持视图和视图模型同步。

虽然不是所有敲除. js 项目的必要插件，但`mapping`插件确实可以扩展到复杂的对象，而无需为您添加到视图模型中的每个新属性添加额外的代码行。

## 摘要

在这一章中，我们学习了 knockout. js 如何与服务器端脚本进行通信。大多数与 AJAX 相关的功能都来自于 jQuery web 框架，尽管 Quicken . js 确实提供了一个简洁的实用函数，可以将其可观测的内容转换为本机 JavaScript 属性。我们还讨论了`mapping`插件，它提供了一种通用的方法来将一个原生的 JavaScript 对象转换成具有可观测属性的视图模型。

请记住，knockout. js 是一个纯客户端库。它只用于连接 JavaScript 对象(视图模型)和 HTML 元素。一旦建立了这种关系，您就可以使用您喜欢的任何其他技术与服务器进行通信。在客户端，您可以用 Dojo、Prototype、MooTools 或任何其他支持 AJAX 请求的框架来代替 jQuery。在服务器端，你可以选择 ASP.NET、PHP、Django、Ruby on Rails、Perl、JavaServer Pages……你明白了。这种关注点的分离使得 Quicken . js 成为一个非常灵活的用户界面开发工具。*