0
votes

I am currently using Josh Buckley's Datatables binding for Knockout. I'm trying to create a "delete row" button in the datatable. I currently have a click data-bind on each button. The datatable source is hooked up to an observable array. When a user clicks on the button, the object is removed from the observable array, which then triggers the update binding, and the table view subsequently gets updated. That all works fine, at least the first time I click on a button.

However, once the the table is updated, all of the click bindings on the "delete row" buttons stop working. I presume that this is because the entire table is cleared and rebuilt each time the update binding is called, and so the bindings aren't applied on the new elements.

How can I rebind the new elements that are generated with each datatable update? Alternatively, is there a better way to handle this?

Custom binding:

(function($){
    ko.bindingHandlers.dataTable = {
        init: function(element, valueAccessor){
            var binding = ko.utils.unwrapObservable(valueAccessor());

            // If the binding is an object with an options field,
            // initialise the dataTable with those options. 
            if(binding.options){
                $(element).dataTable(binding.options);
            }
        },
        update: function(element, valueAccessor){
            var binding = ko.utils.unwrapObservable(valueAccessor());

            // If the binding isn't an object, turn it into one. 
            if(!binding.data){
                binding = { data: valueAccessor() }
            }

            // Clear table
            $(element).dataTable().fnClearTable();

            // Rebuild table from data source specified in binding
            $(element).dataTable().fnAddData(binding.data());
        }
    };
})(jQuery);

HTML code:

<table id="membersTable" class="table table-striped table-bordered bootstrap-datatable datatable" data-bind="dataTable: membersTable">
    <thead>
        <tr>
            <th>Name</th>
            <th>Actions</th>
        </tr>
    </thead>   
</table>

View Model:

var groupViewModel = {  
    groupMembers: ko.observableArray([
        new GroupMember("1", "Abe", true),
        new GroupMember("2", "Bob", false),
        new GroupMember("3", "Bill", false)
    ])
};

groupViewModel.membersTable = ko.computed(function() {
    var self = this;

    var final_array = new Array();
    for(var i = 0; i < self.groupMembers().length; i++)
    {
        var row_array = new Array();
        row_array[0] = self.groupMembers()[i].nameWithLink();
        row_array[1] = self.groupMembers()[i].actions();
        final_array.push(row_array);
    }

    return final_array;
}, groupViewModel);

groupViewModel.removeMember = function(id) {
    var self = this;

    self.groupMembers.remove(function(groupMember) {
        return groupMember.id == id;
    });
};

Group Member object:

function GroupMember(id, name, isGroupLeader)
{
    var self = this;
    self.id = id;
    self.name = name;
    self.isGroupLeader = ko.observable(isGroupLeader);

    self.link = ko.computed(function() {
        return "/#user/" + self.id;
    }); 

    self.nameWithLink = ko.computed(function() {
        return '<a href="' + self.link() + '">' + self.name + '</a>';
    });

    self.actions = ko.computed(function() {
        return '<a class="btn btn-danger" data-bind="click: function() {removeMember(' + self.id + ')}">' + '<i class="icon-minus-sign"></i>' + '</a>';
    });
}

Edit:

jsfiddle link: http://jsfiddle.net/zongweil/PLUKv/1/

1
Can you put this into a fiddle? Makes it much easier to help.PW Kad

1 Answers

0
votes

http://jsfiddle.net/PLUKv/4/

I am not sure about the Buckley guys Data table binding but there are a lot of problems in the original fiddle that you posted so I will touch on those so you can see why the solution I posted works and is a better method -

  1. Don't create an array of arrays just to give your data some functionality (such as remove or linking). That is bad practice and you are setting yourself up for problems. You can either use a generic method like I put into your viewmodel to add functionality to the data record or you can use a plethora of other methods - but don't add HTML in your view model.

  2. You don't need to foreach your data in the view model to add functionality. Knockout is helping you by giving you the tools you need in the view so that your view model has to know nothing about the view. Also, don't put view model functionality into your view - it's bad practice (you had a function injected into your HTML, thats dirty!)

  3. Finally, don't put view model functionality into your model - that is what your view model is for!

Your view is faster and more efficient at rendering - let it do it's job -

<tbody data-bind="foreach: groupMembers">
    <tr>
    <td><a data-bind="click: $parent.gotoPersonLink, text: name" /></td>
    <td><button data-bind="click: $parent.removeMember"><i class="icon-remove" /></button></td>
    </tr>
</tbody>

Give your view model the opportunity to provide functionality without having to inject HTML -

self.removeMember = function (member) {
    self.groupMembers.remove(member);
};

self.gotoPersonLink = function (member) {
    alert('add your change window logic here');    
};

You could always add functionality into the model, such as removing a member, but that means if you have 1000 records that code runs 999 more times than it needs to. That being said, if you absolutely have to go back to your old way of doing it, then good luck! If that is the case get rid of your dependency to Knockout because you just don't need it.