brazorf
brazorf

Reputation: 1961

KNockoutJS with jQuery datatables, bound rows are not updated properly

Here's JS code:

function ProductViewModel() {
  // Init.
  var self = this;
  self.products = ko.observableArray();
  self.singleProduct = ko.observable();
  var mappedProducts;

  // Initialize table here.
  $.getJSON("/admin/test", function(allData) {
    mappedProducts = $.map(allData, function(item) { return new Product(item);});
    self.products(mappedProducts);

    self.oTable = $('#products-table').dataTable( {
      "aoColumns": [
        { "bSortable": false, "mDataProp": null, sDefaultContent: '' },
        {"mData": "name"}, 
        {"mData": "dealer"},
        {"mData": "cost"},
        {"mData": "price"}, 
        { "bSortable": false, sDefaultContent: '' }
      ],
    });
  });

  // Here i'm using the basic switch pattern, as from KO tutorials.
  // This is intended for showing a single product form.
  self.edit = function(product) {
    self.singleProduct(product);
  }

  // This is intended to hide form and show list back.
  self.list = function() {
    self.singleProduct(null);
  }

  // This is the form save handler, actually does nothing 
  // but switch the view back on list.
  self.doEdit = function(product) {
    self.list();
  } 
}



// My model.
function Product(item) {
  this.name = ko.observable(item.name);
  this.dealer = ko.observable(item.dealer);
  this.cost = ko.observable(item.cost);
  this.price = ko.observable(item.price);
  this.picture = ko.observable();
}

Here's my markup:

<table id="products-table" class="table table-striped table-bordered table-hover">
        <thead>
          <tr>
            <th>Pic</th>
            <th>Name</th>
            <th>Dealer</th>
            <th>Cost</th>
            <th>Price</th>
            <th>Actions</th>
          </tr>
        </thead>

        <tbody data-bind="foreach: $parent.products">
          <tr>
            <td><span data-bind='ifnot: picture'>-</span></td>
            <td><a data-bind="text: name"></a></td>
            <td><span data-bind='text: dealer'></span></td>
            <td><span data-bind='text: cost'></span></td>
            <td><span data-bind='text: price'></span></td>
            <td>
              <button data-bind='click: $root.edit'><i class='icon-pencil'></i>
              </button>
            </td>
          </tr>
        </tbody>
      </table>

When i do click on the edit button, triggering the $root.edit handler, a form is shown because of a

<div data-bind='with: singleProduct'>

binding i have made. Inside this binding i have a form, with a

<input data-bind="value: name" type="text" id="" placeholder="Name"
> class="col-xs-10 col-sm-5">

field.

Problem: When i edit the value in the input field, the relative row in the datatable is not updated. I have tried a basic table without the datatables plugin and it does work, meaning that if i change value, the row in the table is properly updated.

What's wrong here?

== EDIT ==

I found out that moving the bind point to the table TD fixed the problem, though still i can't figure out why.

          <tr>
            <td data-bind="text: name"></td>
            <!-- More columns... -->
          </tr>

The above code is working properly now. Why ?

== EDIT2 ==

Now that i fixed first issue, comes the second. I implemented my "save new" method like so

self.doAdd = function(product) {
    $.ajax("/product/", {
      data: ko.toJSON({ product: product }),
      type: "post", contentType: "application/json",
      success: function(result) { alert('ehh'); }
    }).then(function(){
      self.products.push(product); // <--- Look at this!
      self.list();
    });
  }

The self.products.push(product); in the success handler is properly updating my products observable. Then, a new row is automatically added to my table, and this is the good news.

Bad news is that datatables controls, such search field or clickable sorting arrows, disappear as soon as i push the new product in the array. Why so!?

Upvotes: 1

Views: 2251

Answers (2)

Zach Painter
Zach Painter

Reputation: 140

http://jsfiddle.net/zachpainter77/4tLabu56/

ko.bindingHandlers.DataTablesForEach = {

        init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
             var nodes = Array.prototype.slice.call(element.childNodes, 0);
            ko.utils.arrayForEach(nodes, function (node) {
                if (node && node.nodeType !== 1) {
                    node.parentNode.removeChild(node);
                }
            });
            return ko.bindingHandlers.foreach.init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
        },
        update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {

            var value = ko.unwrap(valueAccessor()),
            key = "DataTablesForEach_Initialized";

            var newValue = function () {
                return {
                    data: value.data || value,
                    beforeRenderAll: function (el, index, data) {

                        if (ko.utils.domData.get(element, key)) {

                            $(element).closest('table').DataTable().destroy();
                        }
                    },
                    afterRenderAll: function (el, index, data) {
                        $(element).closest('table').DataTable(value.options);
                    }

                };
            };

            ko.bindingHandlers.foreach.update(element, newValue, allBindingsAccessor, viewModel, bindingContext);

            //if we have not previously marked this as initialized and there is currently items in the array, then cache on the element that it has been initialized
            if (!ko.utils.domData.get(element, key) && (value.data || value.length)) {
                ko.utils.domData.set(element, key, true);
            }

            return { controlsDescendantBindings: true };
        }
    };

https://rawgit.com/zachpainter77/zach-knockout.js/master/zach-knockout.debug.js

Upvotes: 0

BrettH
BrettH

Reputation: 189

Did you ever resolve this?

I've been having similar issues for ages.

In the end my fix was to map all my entities using ko.mapping.fromJS(entity) - this then hooked up all the required dependencies and ensured that any changes flowed through my model.

Upvotes: 1

Related Questions