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

时间:2017-02-07 19:21:12

标签: javascript knockout.js viewmodel

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

[
    {
        "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中,因此它可见并且在该区域一切都很好。但是,我需要添加一些东西,而且我不确定如何做到这一点。更重要的是,如何干净利落地完成它。

我输出带有复选框的文件,我想要一个'选择'属于它的财产。我已经能够通过在后端添加字段来实现这一目标,但并不想要它,因为它真的不应该存在。还需要显示当前选择的数量,每组数量和总数。

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

{
    "formSubmit": ?,
    "totalNumberOfFiles": ?,
    "totalNumberOfSelectedFiles": ?,
    "groups": 
    [
        {
            "group": "Alice",
            "numberOfFiles": ?,
            "selectedFiles": ?,
            "files": [
                {
                    "filename": "red.mp3",
                    "length": 5,
                    "selected": boolean
                },
                ...
            ]
        },
        ...
    ]
}
  • 虽然,例如,甚至可能不需要numberOfFiles?可以从files.length或其他东西获得吗?
  • 并且selectedFiles应该是一个函数/可观察函数来计算所选文件的数量(看起来如何?)或者它应该是一个以某种方式添加/删除的列表(以及如何做吗?)
  • 我怎样才能在提交功能中获取当前所选文件的列表,以便将其发回服务器?
  • 无论哪种方式,我如何"增强/充实/包装"我用这些东西从服务器得到的基本数组,不是太乱了?

基本上,我知道(可以弄清楚)当模型正在运行时如何进行绑定,但是在使用映射插件时并不理解如何以良好的方式构建它(并且我真的不想手动完成。)

希望有人可以帮助我,因为我无法解决这个问题

1 个答案:

答案 0 :(得分:3)

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

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

FileGroup 构造函数中,在创建新的子视图模型之前,添加了一个属性“selected”,其中 false 是其默认值。

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

  1. numberOfFiles:返回每个FileGroup中的文件总数
  2. selectedFiles:返回包含每个FileGroup中所有选定文件的数组
  3. 提交方法中,有一个简单的提醒,用于演示如何访问所选文件的数组。

    // 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>