Knockout.js 3.4 Custom Binding for jQuery Datatables.net

Knockout.js 3.4 Custom Binding for jQuery Datatables.net

zachpainter77zachpainter77 Posts: 22Questions: 1Answers: 1

Hey guys I created a custom binding that works with knockout 3.4 out of the box. I know that alot people have been trying to get this to work and the solution was not intuitive. I first had to edit the knockout source code and create a pull request in the knockout repository to have two events added to the source. These events were, beforeRenderAll and afterRenderAll. The best way to make datatables play nicely with knockout seemed to be, destroy the datatable, let knockout render its bindings, then reinitialize the datatable. I have created a pull request with knockout to add two events to the foreach binding that will make things easier for knockout to play nice with many jQuery plugins that break the knockout bindings. Those changes aren't too far off. Maybe 5 or 6 months or so. Should be in 3.5 or 4.0.

But with a hint from one of the top knockout devs, I was able to create a binding that works with knockout right out of the box!!

This binding is located here --> https://github.com/zachpainter77/DatatablesForEach-Custom-Knockout-Binding

Pull Request for knockout --> https://github.com/knockout/knockout/pull/1856

Example --> JSFIDDLE

If you like this binding, please give me a star on git hub!

Star ME!

Here is the code:


ko.bindingHandlers.dataTablesForEach = { page: 0, init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { valueAccessor().data.subscribe(function (changes) { var table = $(element).closest('table').DataTable(); ko.bindingHandlers.dataTablesForEach.page = table.page(); table.destroy(); }, null, 'arrayChange'); 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, allBindings, viewModel, bindingContext) { var options = ko.unwrap(valueAccessor()), key = 'DataTablesForEach_Initialized'; ko.unwrap(options.data); ko.bindingHandlers.foreach.update(element, valueAccessor, allBindings, viewModel, bindingContext); (function() { var table = $(element).closest('table').DataTable(options.dataTableOptions); if (options.dataTableOptions.paging) { if (table.page.info().pages - ko.bindingHandlers.dataTablesForEach.page == 0) table.page(--ko.bindingHandlers.dataTablesForEach.page).draw(false); else table.page(ko.bindingHandlers.dataTablesForEach.page).draw(false); } })(); if (!ko.utils.domData.get(element, key) && (options.data || options.length)) ko.utils.domData.set(element, key, true); return { controlsDescendantBindings: true }; } };

Replies

  • zachpainter77zachpainter77 Posts: 22Questions: 1Answers: 1

    Refactored Binding:

    ko.bindingHandlers.dataTablesForEach = {
        page: 0,
        init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
          var options = ko.unwrap(valueAccessor());
          ko.unwrap(options.data);
          if(options.dataTableOptions.paging){
            valueAccessor().data.subscribe(function (changes) {
                var table = $(element).closest('table').DataTable();
                ko.bindingHandlers.dataTablesForEach.page = table.page();
                table.destroy();
            }, null, 'arrayChange');          
          }
            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, allBindings, viewModel, bindingContext) {        
            var options = ko.unwrap(valueAccessor()),
                key = 'DataTablesForEach_Initialized';
            ko.unwrap(options.data);
            var table;
            if(!options.dataTableOptions.paging){
              table = $(element).closest('table').DataTable();
                table.destroy();
            }
            ko.bindingHandlers.foreach.update(element, valueAccessor, allBindings, viewModel, bindingContext);
            table = $(element).closest('table').DataTable(options.dataTableOptions);
            if (options.dataTableOptions.paging) {
               if (table.page.info().pages - ko.bindingHandlers.dataTablesForEach.page == 0) 
                   table.page(--ko.bindingHandlers.dataTablesForEach.page).draw(false);                
               else 
                   table.page(ko.bindingHandlers.dataTablesForEach.page).draw(false);                
            }        
            if (!ko.utils.domData.get(element, key) && (options.data || options.length))
                ko.utils.domData.set(element, key, true);
            return { controlsDescendantBindings: true };
        }
    }; 
    
  • choltcholt Posts: 2Questions: 1Answers: 0

    Having a problem with the above binding that I hope you might be able to solve. Using datatables 1.10 and knockout 3.4. The problem is, if you manipulate a view model item when that view model item is not on the current page the DataTable is displaying, the value will not be reflected in the DataTable when going to the page the item is on. So say you have a simple object reflected in the Datatable with a string and a bool and that data is tied to a text column and a checkbox. If you set the object bool to true and that item is on say page 2 of the datatable. When you go to page 2 the checkbox will be not checked. I believe it has something to do with the fact that the html elements are only physically on the html page when viewing that page of data in the DataTable.

    Can you confirm that you see the same thing and if you do, have any idea how it might be solved?

  • zachpainter77zachpainter77 Posts: 22Questions: 1Answers: 1

    if you want to manipulate data on seperate pages I would just turn off paging. And use scrolling instead. datatables uses dynamic html and dynamic html just doesn't work well with knockout bindings.

  • mkirkpatrickmkirkpatrick Posts: 2Questions: 1Answers: 0

    This is really nice. But I have a few issues:

    1) Destroying and re-initializing the table is pretty slow when you have thousands of results.
    2) When the table is re-initialized, buttons are removed. It seems like the button array isn't being passed in to the update function in the binding. Maybe this is an issue with DataTables itself? Not too sure myself.
    3) It seems like the responsive plug-in does not work when the table is first initialized. Strangely, after being destroyed and re-created it functions as expected.

    I would really like a solution that doesn't involve destroying the table, but I'm not sure how to get dataTables to cooperate with knockout.

  • sohel3csedusohel3csedu Posts: 3Questions: 1Answers: 0

    Does anybody has idea how can i enable buttons library for datatables in knockoutjs custom binding

    thanks

  • HarmonickeyHarmonickey Posts: 1Questions: 0Answers: 0

    This has to be the best solution I've seen so far.

    http://codepen.io/ricardobrandao/pen/MKaYgY?editors=1111

  • saunasauna Posts: 1Questions: 0Answers: 0
    edited December 2017

    How would you change this to also support the select extension (and typescript)?

This discussion has been closed.