deleting items from an array with knockoutjs es5

101 Views Asked by At

Fiddle

I'm trying to use knockout.js with the ES5 plugin, but I'm not able to get deleting from an array to work.

It seems to work somewhat, you can delete, but there is always one item left in the array, but not really somehow. I'm totally confused why this doesn't work like you'd think it would.

What am I doing wrong?

(I have a more complex scenario that is using a durandal widget, but I was able to boil it down to just this, so I think the es5 plugin is the culprit)

Here is my markup:

<div data-bind="foreach: staffList" style="border:1px solid black;">
    <div style="border: 1px solid red;">
        <p data-bind="text: Name"></p>
        <p>
            <button data-bind="click: deleteClickHandler">Delete</button>
        </p>
    </div>
</div>

and script:

function ctor(){
  var self=this;
  self.staffList = [{Name:'one'},{Name:'two'},{Name:'three'},{Name:'four'}];
  ko.track(self.staffList, { deep: true });
  self.deleteClickHandler = function (obj) {
    //TODO show confirm dialog first
    var index = self.staffList.indexOf(obj);
    if (index >= 0) {
      self.staffList.splice(index, 1);
    }
  };
}
ko.applyBindings(ctor);

only other difference in the real world is that I'm getting the data from an API call, but the behavior is the same.

2

There are 2 best solutions below

4
Jeroen On BEST ANSWER

Interesting question. I'm not sure yet as to the root cause, but I did manage to find a way to make it work. Some changes I needed:

  • Make ctor a real Constructor function / use it as such;
  • Because of the previous point, you'll need to reference deleteClickHandler with specific scope;
  • Track self, not just the one member;

Here's a sample that works for me:

<div data-bind="foreach: staffList" style="border:1px solid black;">
    <div style="border: 1px solid red;">
        <span data-bind="text: Name"></span>
        <button data-bind="click: $root.deleteClickHandler">Delete</button>
    </div>
</div>
<hr>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
function ViewModel(){
  var self = this;
  self.staffList = [{Name:'one'},{Name:'two'},{Name:'three'},{Name:'four'}];
  ko.track(self);
  self.deleteClickHandler = function (obj) {
    var index = self.staffList.indexOf(obj);
    if (index >= 0) {
      self.staffList.splice(index, 1);
    }
  };
}
ko.applyBindings(new ViewModel());

In addition, I want ot mention that this is also possible:

  self.deleteClickHandler = function (obj) {
    self.staffList.remove(obj);
  };

Again, I'm not sure why ko.track(self.staffList) didn't work, but the above at least gets you going.

2
Incorporeal Logic On

The button that is nested within the foreach loop neads to reference the parent, so that would be

<button data-bind="click: $root.deleteClickHandler">Delete</button>

instead of

<button data-bind="click: deleteClickHandler">Delete</button>

I just tested this, and found no remaining items listed on the view. That should solve the issue you are seeing, as I am seeing an empty array after the last item is deleted.

Also, I had to change the ko.applyBindings call to ko.applyBindings(new ctor()); and removed the ko.track call in favor of a normal observable array for the staffList object. Unless there was a specific reason you cannot use the observableArray?

JSFiddle Example