如何向 Knockout 映射视图模型添加内容?

How to add stuff to Knockout mapping view model?

我是 Knockout 的新手,在使用 Knockout 映射插件时,我无法理解如何 "edit" 视图模型。希望有人能帮助我。我有一个列表列表。下面是一个类似的例子。基本上有多个文件的多个组。

[
    {
        "group": "Alice",
        "files": [
            {"filename": "red.mp3", "length": 5},
            {"filename": "blue.mp3","length": 6},
            {"filename": "yellow.mp3","length": 5}
        ]
    },
    {
        "group": "Bob",
        "files": [
            {"filename": "green.mp3","length": 2},
            {"filename": "purple.mp3","length": 10}
        ]
    }
]

我可以从这里得到基本模型:

$.getJSON('api/get-list', function(data)
    {
        view = ko.mapping.fromJS(data);
        ko.applyBindings(view);
    });

它有效,我已经设法将它绑定在 HTML 中,所以它是可见的,并且在该区域一切正常。但是,我需要添加一些东西,但我不确定该怎么做。更重要的是,如何做到干净利落。

我正在输出带有复选框的文件,我想要一个 'select' 属性 绑定到它。我已经能够通过在后端添加该字段来做到这一点,但不希望这样做,因为它真的不应该在那里。还需要显示当前选择的数量、每组的数量和总数。

所以,基本上我想要这样的东西:

{
    "formSubmit": ?,
    "totalNumberOfFiles": ?,
    "totalNumberOfSelectedFiles": ?,
    "groups": 
    [
        {
            "group": "Alice",
            "numberOfFiles": ?,
            "selectedFiles": ?,
            "files": [
                {
                    "filename": "red.mp3",
                    "length": 5,
                    "selected": boolean
                },
                ...
            ]
        },
        ...
    ]
}

基本上,我知道(可以弄清楚)如何在模型正常工作时进行绑定,但不了解如何在使用映射插件时以良好的方式构建它(我 真的不想手动做)。

希望有人能帮助我,因为我就是想不通

当您使用 ko.mapping.fromJS 时,每个 属性 都被转换为一个 observable,每个数组都被转换为一个 observableArray。

主视图模型 MyViewModel 有一个文件组列表,该列表使用自定义映射对象的映射进行初始化。此对象有一个 'create' 回调(如 http://knockoutjs.com/documentation/plugins-mapping.html 中所述),用于实例化一个新的文件组。

FileGroup 构造函数中,就在创建新的子视图模型之前,添加了 属性 'selected' false 是它的默认值。

此外,主视图模型有两个计算的可观测值:

  1. numberOfFiles: returns 每个文件组中的文件总数
  2. selectedFiles: returns 包含每个文件组中所有选定文件的数组

submit 方法中有一个简单的警告来演示如何访问所选文件的数组。

// data obtained from the server
var data = [
  {
    "group": "Alice",
    "files": [
      { "filename": "red.mp3", "length": 5 },
      { "filename": "blue.mp3", "length": 6 },
      { "filename": "yellow.mp3", "length": 5 }
    ]
  },
  {
    "group": "Bob",
    "files": [
      { "filename": "green.mp3", "length": 2 },
      { "filename": "purple.mp3", "length": 10 }
    ]
  }
];

// sub view model representing a single file grouping
var FileGroup = function (data) {
  data.files.map(f => f.selected = false);
  ko.mapping.fromJS(data, {}, this);
}

// main view model
var MyViewModel = function (data) {
  this.fileGroups = ko.mapping.fromJS(data, { create: options => new FileGroup(options.data) });

  this.numberOfFiles = ko.computed(() => {
    return this.fileGroups().reduce((total, fg) => {
      total += fg.files().length;
      return total;
    }, 0);
  }, this);

  this.selectedFiles = ko.computed(function() {
    return this.fileGroups().reduce((selectedFiles, fg) => {
      selectedFiles.push.apply(selectedFiles, fg.files().filter(f => f.selected()));
      return selectedFiles;
    }, [])
  }, this);

  this.submit = function() {
    alert("FILES POSTED TO SERVER: " + this.selectedFiles().length);
  }
}

var viewModel = new MyViewModel(data);
ko.applyBindings(viewModel);
.fileGroup {
  border: 1px solid lightgray;
  margin-bottom: 15px;
  padding: 10px;
}

.selected {
  border: 1px solid lightgreen;
  margin-bottom: 15px;
  padding: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.js" type="text/javascript"></script>

<div data-bind="foreach: fileGroups">
  <h3 data-bind="text: group"></h3>

  <div data-bind="foreach: files" class="fileGroup">
    <input type="checkbox" data-bind="checked: selected">
    <span data-bind="text: filename" />
  </div>
</div>

<h4>Number of Files: <span data-bind="text: numberOfFiles"></span></h4> 

<div data-bind="foreach: selectedFiles, visible: selectedFiles().length > 0" class=selected>
  <span data-bind="text: filename" />
</div>

<button data-bind="click: submit">Submit</button>