如何从 ko.js 数组中的父函数访问调用子元素?

How to access calling child element from parent function in ko.js array?

我正在使用 knockout.js foreach 循环创建一个复选框列表。我有一个保存复选框值的可观察数组,但是当我调用函数来检查它们的值时,this 元素指的是父元素,即 viewModel。如何访问与单击的元素相关的数组元素?

我已尝试从以下代码中删除 $parent 引用,但引发了此错误:Message: getSampleValue is not defined

这是创建复选框的 foreach 循环:

<!-- ko foreach: sampleList -->
            <div data-bind="css: { hasValue: $parent.getSampleValue() }">
                <span data-bind="click: $parent.sampleClick, text: name"></span>
                <div data-bind="click: $parent.sampleClick" class="spot fa fa-square"></div>
            </div>
<!-- /ko -->

这些是 ko 视图模型中定义的函数:

sampleViewModel = function (data) {
   var self = this;
   self.sampleList = ko.observableArray(data.sampleList);

    self.sampleClick = function () {
       this.isChecked = !this.isChecked;
    }

    self.getSampleValue = function () {
       return this.isChecked;
    }
}

样本列表的格式为:

{
  {
    "name": "name1",
    "isChecked": false
  },
  {
    "name": "name2",
    "isChecked": true
  }
}

预期结果:当数组中给定项目的 isChecked 值发生变化时,应该调用函数 getSampleValue,并且应该更新 UI 上的值。从该函数返回的 isChecked 值应该是数组中正确的元素而不是父元素。

实际结果:该函数被调用,但似乎只在创建复选框时被调用,而不是在 isChecked 值更新时被调用,函数中的 this 是父级调用者的元素,因此没有 isChecked 值。

非常感谢任何帮助。

编辑: 所以改变这一行:

<div data-bind="css: { hasValue: $parent.getSampleValue() }">

为此:

<div data-bind="css: { hasValue: isChecked }">

似乎在加载时显示正确的值,但它们仍然不会在 UI 上更新,即使可观察数组的值正在更改。

您的视图模型中的函数已 this 绑定到视图模型本身。您可以使用处理函数的第一个参数访问当前元素:

sampleViewModel = function (data) {
    var self = this;
    self.sampleList = ko.observableArray(data.sampleList);

    self.sampleClick = function (element) {
       element.isChecked = !element.isChecked;
    }

    self.getSampleValue = function (element) {
       return element.isChecked;
    }
}

参见The "click" binding

When calling your handler, Knockout will supply the current model value as the first parameter. This is particularly useful if you’re rendering some UI for each item in a collection, and you need to know which item’s UI was clicked.


此外,要正确绑定到 getSampleValue,您应该删除括号。

<div data-bind="css: { hasValue: $parent.getSampleValue }">

或者 - 正如您已经发现的那样 - 由于函数 returns 不修改它的值,您可以绑定到值本身:

<div data-bind="css: { hasValue: isChecked }">

您使用 Knockout 的方式不对。不要将属于项目的方法放入项目的 parent 中。让项目对自己的状态负责。

考虑以下示例:

  • SampleItem class 记录自己的名字和自己的 "checked" 状态。它还提供了一种切换 "checked" 状态的方法。
  • SampleApplication class 仅保留项目列表。它将用作页面的 $root 视图模型。
  • 视图使用 isChecked 来决定 Font Awesome 的 CSS class,并且 transparently 将点击事件链接到正确的接收器。
  • 整个视图模型图是通过单个函数调用构建的,在此过程中构建嵌套视图模型并将数据映射到可观察对象。
  • 不需要 cross-referencing 从 child 到 $parent

function SampleItem(data) {
  var self = this;
  
  self.name = ko.observable(data.name);
  self.isChecked = ko.observable(data.isChecked);
  
  self.toggleChecked = function () {
    self.isChecked(!self.isChecked());
  };
}

function SampleApplication(data) {
  var self = this;
  
  self.sampleList = ko.observableArray();
  
  // viewmodel init (i.e. data mapping)
  data.sampleList.forEach(function (itemData) {
    self.sampleList.push( new SampleItem(itemData) );
  });
}

// -------------------------------------------------------
var rawModelData = {
  sampleList: [{
    name: "name1",
    isChecked: false
  }, {
    name: "name2",
    isChecked: true
  }]
};

var vm = new SampleApplication(rawModelData);
ko.applyBindings(vm);
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<!-- ko foreach: sampleList -->
<div data-bind="click: toggleChecked">
  <span data-bind="text: name"></span>
  <div class="spot fa-square" data-bind="css: {far: !isChecked(), fas: isChecked()}"></div>
</div>
<!-- /ko -->

<hr>
<pre data-bind="text: ko.toJSON($root, null, 2)">


使用 mapping plugin 创建嵌套视图模型并使它们与服务器数据保持同步的过程变得更加容易,尤其是当有大量数据要映射到可观察对象并且视图模型嵌套结构变得更加复杂时。

只需调用 ko.mapping.fromJS(inputObj, options, target) 即可处理所有细节。

function SampleItem(data) {
  var self = this;

  self.name = ko.observable("");
  self.isChecked = ko.observable(false);

  // viewmodel init (i.e. data mapping)
  ko.mapping.fromJS(data, {}, self);
  
  self.toggleChecked = function () {
    self.isChecked(!self.isChecked());
  };
}

function SampleApplication(data) {
  var self = this;
  
  self.sampleList = ko.observableArray();
  
  // viewmodel init (i.e. data mapping)
  ko.mapping.fromJS(data, SampleApplication.mapping, self);
}
SampleApplication.mapping = {
  sampleList: {
    create: options => new SampleItem(options.data)
  }
};
// -------------------------------------------------------
var rawModelData = {
  sampleList: [{
    name: "name1",
    isChecked: false
  }, {
    name: "name2",
    isChecked: true
  }]
};

var vm = new SampleApplication(rawModelData);
ko.applyBindings(vm);
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>

<!-- ko foreach: sampleList -->
<div data-bind="click: toggleChecked">
  <span data-bind="text: name"></span>
  <div class="spot fa-square" data-bind="css: {far: !isChecked(), fas: isChecked()}"></div>
</div>
<!-- /ko -->

<hr>
<pre data-bind="text: ko.toJSON(ko.mapping.toJS($root), null, 2)">

请注意,在此变体中,需要 "unmapping"(通过 ko.mapping.toJS())删除映射插件添加的所有属性以跟踪事物。