Search code examples
jquerytypescriptknockout.jsdatatablesknockout-3.0

Using Knockout BindingHandler to expose JQuery.Datatables Select?


I have setup a custom KO bindingHandler to help with updating the DataTable.

The documentation for JQuery.DataTable.Select on how to obtain the data requires a handle.

var table = $('#myTable').DataTable();

table.rows( { selected: true } ).data();

However, my ko.bindingHandler moves the setup of the DataTable to my cshtml file so I don't have a handle for $('#myTable').DataTable().

How can I make the DataTable functions available to my viewModel? I had thought I might be able to use JQuery to cast $('#myTable') as a dataTable but am having no such luck.

.cshtml:

<table id="myTable">
   <thead>
     <tr>
       <th>Title</th>
   </thead>
   <tbody data-bind="dataTablesForEach: {data: trainingSearchResults, dataTableOptions: {
                select: {items: 'row', style: 'os'},
                searching: false,
                info: false,
                paging: false
              }">
       <tr>
         <td data-bind="text: title"></td>
       </tr>
  </tbody>
</table>

Custom Binding:

import * as ko from "knockout"
import * as $ from "jquery";

export class KnockoutExtensions {
    // Constructor
    constructor() {
        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: 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 };
            }
        }; 
    }
}

Solution

  • While trying to just call the initialization again from my viewModel:

    var table = $("#trainingSearchResultsTable").DataTable({
        select: { items: 'row', style: 'os' },
        searching: false,
        info: false,
        paging: false
    });
    

    got me an error Warning: Cannot reinitialise

    It lead me to the answer:

    This error is triggered by passing in options to a DataTables constructor object when the DataTable instance for the selected node has already been initialised.

    Object instance retrieval

    This error can often occur when trying to obtain a reference to the DataTable for working with the API. For example, you might have a function which will always try to create a DataTable instance by passing in options when created. Then you make a modification which calls this function on a table which has already been initialised and you get this error.

    In such a case, you will want to use the $.fn.dataTable.isDataTable() static method. This can be used to check if a table is a DataTable or not already:

    if ( $.fn.dataTable.isDataTable( '#example' ) ) {
        table = $('#example').DataTable();
    }
    else {
        table = $('#example').DataTable( {
            paging: false
        } );
    }