入门

监控属性

使用内置绑定

控制文本和外观

绑定逻辑控制

处理表单属性

解析模板

高级应用

插件

更多信息

Mapping插件

Knockout设计成允许你使用任何JavaScript对象作为view model。必须view model的一些属性是 observables,你可以使用KO绑定他们到你的UI元素上,当这些observable值改变的时候,这些UI元素就会自动更新。

Knockout is designed to allow you to use arbitrary JavaScript objects as view models. As long as some of your view model’s properties are observables, you can use KO to bind to them to your UI, and the UI will be updated automatically whenever the observable properties change.

绝大多数程序都需要从服务器端获取数据,但是由于服务器不知道observable的概念是什么,它只支持简单的JavaScript对象(通常是序列化以后的JSON),mapping插件可以让你很方便地将简单JavaScript对象mapp到带有observable值的view model。你也可以自己写JavaScript代码将从服务器获取的数据来构建 view model,mapping插件只是一种很好的替代而已。

Most applications need to fetch data from a backend server. Since the server doesn’t have any concept of observables, it will just supply a plain JavaScript object (usually serialized as JSON). The mapping plugin gives you a straightforward way to map that plain JavaScript object into a view model with the appropriate observables. This is an alternative to manually writing your own JavaScript code that constructs a view model based on some data you’ve fetched from the server.

Download

例子:手工mapping

显示当前服务器时间和你网站上的当前用户数。你应该使用如下的view model来代表你的这些信息:

You want to display the current server-time and the number of users on your web page. You could represent this information using the following view model:

  1. var viewModel = {
  2. serverTime: ko.observable(),
  3. numUsers: ko.observable()
  4. }

然后绑定view model到HTML元素上,如下:

  1. The time on the server is: <span data-bind='text: serverTime'></span>
  2. and <span data-bind='text: numUsers'></span> user(s) are connected.

由于view model属性是observable的,在他们变化的时候,KO会自动更新绑定的HTML元素。

Since the view model properties are observable, KO will automatically update the HTML elements whenever those properties change.

接下来,从服务器获取最新的数据。或许每隔5秒你要调用一次Ajax请求(例如,使用jQuery的 $.getJSON$.ajax 函授):

Next, you want to fetch the latest data from the server. Every 5 seconds you might issue an Ajax request (e.g., using jQuery’s $.getJSON or $.ajax functions):

  1. var data = getDataUsingAjax(); // Gets the data from the server

然后,服务器返回和下面相似的JSON数据:

The server might return JSON data similar to the following:

  1. {
  2. serverTime: '2010-01-07',
  3. numUsers: 3
  4. }

最后,用这些数据更新你的view model(不使用mapping插件),代码如下:

Finally, to update your view model using this data (without using the mapping plugin), you would write:

  1. // Every time data is received from the server:
  2. viewModel.serverTime(data.serverTime);
  3. viewModel.numUsers(data.numUsers);

为了使数据显示在页面上,所有的属性都要像这样写代码。如果你的数据结构很复杂的话(例如,包含子对象或者数组),那就维护起来就相当痛苦。mapping插件就是来让你让你的JavaScript简单对象(或JSON结构)转换成observable的view model的。

You would have to do this for every variable you want to display on your page. If your data structures become more complex (e.g. they contain children or contain arrays) this becomes very cumbersome to handle manually. What the mapping plugin allows you to do is create a mapping from the regular JavaScript object (or JSON structure) to an observable view model.

例子:使用ko.mapping

通过mapping插件创建 viewModel ,直接使用 ko.mapping.fromJS 函数来创建:

To create a view model via the mapping plugin, replace the creation of viewModel in the code above with the ko.mapping.fromJS function:

  1. var viewModel = ko.mapping.fromJS(data);

它会自动将 data 里所有的属性创建成observable类型的属性。你可以通过ko.mapping.fromJS 函数定期从服务器获取数据,然后更新你的 viewModel

This automatically creates observable properties for each of the properties on data. Then, every time you receive new data from the server, you can update all the properties on viewModel in one step by calling the ko.mapping.fromJS function again:

  1. // Every time data is received from the server:
  2. ko.mapping.fromJS(data, viewModel);

如何mapping?

  • 对象的所有属性都被转换成observable类型值,如果获取的对象的值改变了,就会更新这个observable类型的值.
  • 数组也被转换成了 observable 数组,如果服务器更新改变了数组的个数,mapping插件也会添加或者删除相应的item项,也会尽量保持和原生JavaScript数组相同的order顺序。
  • All properties of an object are converted into an observable. If an update would change the value, it will update the observable.
  • Arrays are converted into observable arrays. If an update would change the number of items, it will perform the appropriate add/remove actions. It will also try to keep the order the same as the original JavaScript array.

Unmapping

如果你想让map过的对象转换成原来的JavaScript对象,使用如下方式:

If you want to convert your mapped object back to a regular JS object, use:

  1. var unmapped = ko.mapping.toJS(viewModel);

会创建一个unmapped对象,只包含你之前map过的对象属性,换句话说,你在view model上手工添加的属性或者函数都会被忽略的,唯一例外的是 _destroy 属性是可以unmapped回来的,因为你从 ko.observableArray里destroy一个item项的时候会生成这个属性。 请参考“高级用户”小节如何配置使用。

This will create an unmapped object containing only the properties of the mapped object that were part of your original JS object. So in other words, any properties or functions that you manually added to your view model are ignored. By default, the only exception to this rule is the _destroy property which will also be mapped back, because it is a property that Knockout may generate when you destroy an item from an ko.observableArray. See the “Advanced Usage” section for more details on how to configure this.

与JSON字符串一起使用

如果你的Ajax调用返回的是JSON字符串(而不是反序列化后的JavaScript对象),你可以使用 ko.mapping.fromJSON 函数来创建或者更新你的view model。用 ko.mapping.toJSON实现unmap。

If your Ajax call returns a JSON string (and does not deserialize it into a JavaScript object), then you can use the function ko.mapping.fromJSON to create and update your view model instead. To unmap, you can use ko.mapping.toJSON.

使用.from/toJSON函数处理JSON字符串和使用.from/toJS函数处理JS对象是等价的。

Apart from the fact that they work with JSON strings instead of JS objects these functions are completely identical to their *JS counterparts.

高级用法

有时候,在使用 ko.mapping.fromJS 的时候,可能有必要去使用mapping的高级用法来定义mapping的详细过程,以后定义了,以后再调用的时候就不必再定义了。

Sometimes it may be necessary to have more control over how the mapping is performed. This is accomplished using mapping options. They can be specified during the ko.mapping.fromJS call. In subsequent calls you don’t need to specify them again.

这里有一些情形,你可能需要使用这些mapping options。

Here a few situations in which you might want to use these mapping options.

用法1:使用keys来使对象unique化

你有一个JavaScript对象,如下:

  1. var data = {
  2. name: 'Scot',
  3. children: [
  4. { id : 1, name : 'Alicw' }
  5. ]
  6. }

使用map插件,你可以将它map到view model上(没任何问题):

You can map this to a view model without any problems:

  1. var viewModel = ko.mapping.fromJS(data);

现在,数据被更新成如下这样:

Now, let’s say the data is updated to be without any typos:

  1. var data = {
  2. name: 'Scott',
  3. children: [
  4. { id : 1, name : 'Alice' }
  5. ]
  6. }

这里发生了两件事: nameScot 变成了 Scottchildren[0].nameAlicw 变成了 Alice。你可以用如下代码更新 viewModel

Two things have happened here: name was changed from Scot to Scott and children[0].name was changed from Alicw to the typo-free Alice. You can update viewModel based on this new data:

  1. ko.mapping.fromJS(data, viewModel);

于是, name 像我们期望的一样更新了,但是在 children 数组里,子项Alicw被删除而新项Alice被添加到数组里。这不是我们所期望的,我们期望的是只是把 nameAlicw 更新成 Alice,不是替换整个item项。

And name would have changed as expected. However, in the children array, the child (Alicw) would have been completely removed and a new one (Alice) added. This is not completely what you would have expected. Instead, you would have expected that only the name property of the child was updated from Alicw to Alice, not that the entire child was replaced!

发生的原因是,默认情况下mapping plugin插件只是简单地比较数组里的两个对象是否相等。 因为JavaScript里 { id : 1, name : 'Alicw' }{ id : 1, name : 'Alice' } 是不相等的,所以它认为喜欢将新项替换掉老项。

This happens because, by default, the mapping plugin simply compares the two objects in the array. And since in JavaScript the object { id : 1, name : 'Alicw' } does not equal { id : 1, name : 'Alice' } it thinks that the entire child needs to be removed and replaced by a new one.

解决这个问题,你需要声明一个key让mapping插件使用,用来判断一个对象是新对象还是旧对象。代码如下:

To solve this, you can specify which key the mapping plugin should use to determine if an object is new or old. You would set it up like this:

  1. var mapping = {
  2. 'children': {
  3. key: function(data) {
  4. return ko.utils.unwrapObservable(data.id);
  5. }
  6. }
  7. }
  8. var viewModel = ko.mapping.fromJS(data, mapping);

这样,每次map的时候,mapping插件都会检查数组项的 id 属性来判断这个数组项是需要合并的还是全新replace的。

This way, every time the mapping plugin checks an item in the children array, it will only look at the id property to determine if an object was completely replaced or merely needs updating.

用法2:用create自定义对象的构造器

如果你想自己控制mapping,你也可以使用 create 回调。使用回调可以让你自己控制mapping。

If you want to handle a part of the mapping yourself, you can also provide a create callback. If this callback is present, the mapping plugin will allow you to do this part of the mapping yourself.

举例,你有一个像这样的JavaScript对象:

  1. var data = {
  2. name: 'Graham',
  3. children: [
  4. { id : 1, name : 'Lisa' }
  5. ]
  6. }

如果你想自己map children 数组,你可以这样声明:

If you want to map the children array yourself, you can specify that like this:

  1. var mapping = {
  2. 'children': {
  3. create: function(options) {
  4. return new myChildModel(options.data);
  5. }
  6. }
  7. }
  8. var viewModel = ko.mapping.fromJS(data, mapping);

支持 create 回调的 options 参数是一个JavaScript对象,包含如下:

The options argument supplied to your create callback is a JavaScript object containing:

  • data: JavaScript对象,包含child用到的数据
  • parent: child对象所属的父对象或者数组
  • data: The JavaScript object containing the data for this child
  • parent: The parent object or array to which this child belongs

当然,在内部的 create 回调里,你也可以再次调用 ko.mapping.fromJS 。一个例子就是:如果你想让初始的JavaScript对象带有额外的依赖属性 computed observables

Of course, inside the create callback you can do another call to ko.mapping.fromJS if you wish. A typical use-case might be if you want to augment the original JavaScript object with some additional computed observables:

  1. var myChildModel = function(data) {
  2. ko.mapping.fromJS(data, {}, this);
  3. this.nameLength = ko.computed(function() {
  4. return this.name().length;
  5. }, this);
  6. }
用法3:用update自定义对象的updating

你也可以使用 update 回调来自定义一个对象如何更新。它接受一个需要替代的对象以及和 create 回调一样的 options 参数,你应该 return 更新后的值。

You can also customize how an object is updated by specifying an update callback. It will receive the object it is trying to update and an options object which is identical to the one used by the create callback. You should return the updated value.

update 回调使用的 options 参数是一个JavaScript对象,包含如下内容:update 回调使用的options参数是一个JavaScript对象,包含如下内容:

The options argument supplied to your update callback is a JavaScript object containing:

  • data: JavaScript对象,包含child用到的数据
  • parent: child对象所属的父对象或者数组
  • observable: 如果属性是observable的,这将会写入到实际的observable里
  • data: The JavaScript object containing the data for this child
  • parent: The parent object or array to which this child belongs
  • observable: If the property is an observable, this will be set to the actual observable

例子,在数据显示之前,在新数据后面附加额外的字符串:

Here is an example of a configuration that will add some text to the incoming data before updating:

  1. var data = {
  2. name: 'Graham',
  3. }
  4. var mapping = {
  5. 'name': {
  6. update: function(options) {
  7. return options.data + 'foo!';
  8. }
  9. }
  10. }
  11. var viewModel = ko.mapping.fromJS(data, mapping);
  12. alert(viewModel.name());

alert的结果是: Grahamfoo!

用法4:使用ignore忽略不需要map的属性

如果在map的时候,你想忽略一些属性,你可以使用ignore累声明需要忽略的属性名称集合:

If you want the mapping plugin to ignore some properties of your JS object (i.e. to not map them), you can specify a array of propertynames to ignore:

  1. var mapping = {
  2. 'ignore': ["propertyToIgnore", "alsoIgnoreThis"]
  3. }
  4. var viewModel = ko.mapping.fromJS(data, mapping);

你声明的忽略数组被编译到默认的 ignore 数组里。你可以像下面代码一样来维护它:

The ignore array you specify in the mapping options is combined with the default ignore array. You can manipulate this default array like this:

  1. var oldOptions = ko.mapping.defaultOptions().ignore;
  2. ko.mapping.defaultOptions().ignore = ["alwaysIgnoreThis"];
用法5:使用include声明需要map的属性

默认情况下,当map你的view model回到JS对象是时候,只map原始view model里拥有的属性(除了例外的 _destroy 属性),不过,你可以使用include参数来定制:

When converting your view model back to a JS object, by default the mapping plugin will only include properties that were part of your original view model, except it will also include the Knockout-generated _destroy property even if it was not part of your original object. However, you can choose to customize this array:

  1. var mapping = {
  2. 'include': ["propertyToInclude", "alsoIncludeThis"]
  3. }
  4. var viewModel = ko.mapping.fromJS(data, mapping);

你声明的 include 数组被编译到默认的 include 数组里,默认只有 _destroy。 你可以像这样来维护:

The include array you specify in the mapping options is combined with the default include array, which by default only contains _destroy. You can manipulate this default array like this:

  1. var oldOptions = ko.mapping.defaultOptions().include;
  2. ko.mapping.defaultOptions().include = ["alwaysIncludeThis"];
用法6:使用copy来复制属性

默认情况下,map的时候是把所有的值都转换成observable的,默认情况下,map的时候是把所有的值都转换成observable的, 前叙,如果你只是想copy属性值而不是替换成observable的,你可以将属性名称添加到copy数组:

When converting your view model back to a JS object, by default the mapping plugin will create observables based on the rules explained above. If you want to force the mapping plugin to simply copy the property instead of making it observable, add its name to the “copy” array:

  1. var mapping = {
  2. 'copy': ["propertyToCopy"]
  3. }
  4. var viewModel = ko.mapping.fromJS(data, mapping);

你声明的copy 数组被编译到默认的 copy 数组里,默认值是空。你可以像这样来维护:

The copy array you specify in the mapping options is combined with the default copy array, which by default is empty. You can manipulate this default array like this:

  1. var oldOptions = ko.mapping.defaultOptions().copy;
  2. ko.mapping.defaultOptions().copy = ["alwaysCopyThis"];
用法7:Specifying the update target

在上面的例子,如果你想再一个class内map,你可以使用第三个参数作为操作的目标,例如:

If, like in the example above, you are performing the mapping inside of a class, you would like to have this as the target of your mapping operation. The third parameter to ko.mapping.fromJS indicates the target. For example,

  1. ko.mapping.fromJS(data, {}, someObject); // overwrites properties on someObject

所以,如果你想map一个JavaScript对象到 this 上,你可以这样声明:

So, if you would like to map a JavaScript object to this, you can pass this as the third argument:

  1. ko.mapping.fromJS(data, {}, this);
从多数据源map

你可以通过多次使用 ko.mapping.fromJS 来将多个JS对象的数据源map到一起,例如:

You can combine multiple JS objects in one viewmodel by applying multiple ko.mapping.fromJS calls, e.g.:

  1. var viewModel = ko.mapping.fromJS(alice, aliceMappingOptions);
  2. ko.mapping.fromJS(bob, bobMappingOptions, viewModel);

你声明的mapping选项option在每次调用的时候都会合并。

Mapping options that you specify in each call will be merged.

Map以后的observable数组

map插件map以后生产的observable数组,带有几个额外的函数来处理带有 keys 的 mapping:

Observable arrays that are generated by the mapping plugin are augmented with a few functions that can make use of the keys mapping:

  • mappedRemove
  • mappedRemoveAll
  • mappedDestroy
  • mappedDestroyAll
  • mappedIndexOf

它们是和 ko.observableArray 里的函数等价的,不同是他们通过key来处理对象。例如:

They are functionally equivalent to the regular ko.observableArray functions, but can do things based on the key of the object. For example, this would work:

  1. var obj = [
  2. { id : 1 },
  3. { id : 2 }
  4. ]
  5. var result = ko.mapping.fromJS(obj, {
  6. key: function(item) {
  7. return ko.utils.unwrapObservable(item.id);
  8. }
  9. });
  10. result.mappedRemove({ id : 2 });

map过的map过的 ko.observableArray ,除了上面的函数还支持一个 mappedCreate 函数:

The mapped observable array also exposes a mappedCreate function:

  1. var newItem = result.mappedCreate({ id : 3 });

首先会检查key(id=3)在数组里是否存在(如果存在则抛出异常),然后,如果有create和 update回调的话会调用他们,最后创建一个新对象,并将新对象添加到数组然后返回该新对象。

It will first check if the key is already present and will throw an exception if it is. Next, it will invoke the create and update callbacks, if any, to create the new object. Finally, it will add this object to the array and return it.

Download

(c) knockoutjs.com