JS Fiddle showing issue: http://jsfiddle.net/davetropeano/58vm9r6g/7/
I have a custom component that renders an observable array. List elements are readonly and I am trying to support letting a user delete an element.
Here's the template:
<template id="kv-list">
<input type="text" placeholder="key" data-bind="textInput: k">
<input type="text" placeholder="value" data-bind="textInput: v">
<button data-bind="click: add">Add</button><br>
<table>
<thead>
<tr>
<th data-bind="text: keyHeading"></th>
<th data-bind="text: valueHeading"></th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: items">
<tr>
<td data-bind="text: k"></td>
<td data-bind="text: v"></td>
<td><a href="#" data-bind="click: $component.delete">delete</a></td>
</tr>
</tbody>
</table>
and the ViewModel and registration code:
function KV(k, v) {
self = this;
self.k = k;
self.v = v;
}
function KVPairList(params) {
this.items = params.items;
this.keyHeading = params.keyHeading || 'Key';
this.valueHeading = params.valueHeading || 'Value';
this.k = ko.observable();
this.v = ko.observable();
}
KVPairList.prototype.add = function () {
this.items.push(new KV(this.k(), this.v()));
};
KVPairList.prototype.delete = function (item) {
this.items.remove(item);
};
function VM(params) {
this.title = params && params.heading ? params.heading : 'KO Component Example';
this.variants = ko.observableArray();
}
ko.components.register('kvlist', {
viewModel: KVPairList,
template: {
element: 'kv-list'
}
});
ko.components.register('page-main', {
viewModel: VM,
template: { element: 'wrapper' }
});
ko.applyBindings();
Adding to the observable array works fine. But if you click delete on one of the rows KO throughs an error:
Uncaught TypeError: Cannot read property 'remove' of undefined
It looks like what is happening is that $component is not the context of the component's viewmodel -- it is just the item inside the foreach binding. I tried $parent with the same effect.
Is there a way to access the component's viewmodel inside the foreach loop?
(JS Fiddle showing issue: http://jsfiddle.net/davetropeano/58vm9r6g/7/)
data-bind="click: $component.delete.bind($component)"should work. Right now you're just passing a function to the click binding. It as no idea where that function came from which means it cannot call it with the correct context.data-bind="click: function () { $component.delete($data) }"would also work because then you're calling the delete function on the view model which provides it with the correct context. - CrimsonChrisselfto completely avoid the need to deal with the calling context is also an option as answered by Luis. - CrimsonChris