简体   繁体   中英

Using Knockout with Datatables Ajax source

I have a data table like this and would like to make my data table rows have knockout observable properties. What is the best approach to be able to data-bind:"click" on a row in a datatable. I have seen a datatables knockout binding but it doesnt seem to support ajax sources. Any ideas I tried using the foreach and template bindings to create the table and having datatables initialize it from the DOM but it removes the binding I had so when I click now it does nothing. Also seems slow. I would like to use AJAX or JS Array.

         {            
            "bDeferRender" : true,
            "bProcessing" : true,
            "sDom": '<"top"r>t<"bottom"lp><"clear">',
            "oLanguage" : {
            "sLoadingRecords" : "&nbsp;",
                "sProcessing" : processDialog
            },
            "sAjaxSource":'/get_statistics',
            "sAjaxDataProp": 'landing_page_statistics',
            "fnServerParams": function (aoData) {
                aoData.push({"name": "start_date", "value": startDateEl.val()});
                aoData.push({"name": "end_date", "value": endDateEl.val()});
            },
            "aoColumns" : [
                {"mData" : "status", "sWidth": "6%"},
                {"mData" : "name"},
                {"mData" : "url"},
                {"mData" : "pageViews", "sWidth": "15%"},
                {"mData" : "leads", "sWidth": "5%"},
                {"mData" : "convRate", "sWidth": "12%"}
            ],
            "fnRowCallback": function (nRow, aData, iDisplayIndex) {
                renderDataTableRow(nRow, aData, iDisplayIndex);
            },
            "fnFooterCallback" : function (nFoot, aData, iStart, iEnd, aiDisplay) {
               renderDataTableTotalsRow(nFoot, aData, iStart, iEnd, aiDisplay);
            },
            "fnDrawCallback": function( oSettings ) {
                // status tooltips
                $('.lp-status').tooltip();
            }
        }

I'm not sure if I get the point in your question, and if I do my answer feels like cheating, merely pointing to the relevant sources. In any case, here goes.

Your first option is to use load and save AJAX data to and from your view models manually. The related tutorial does a pretty decent job of explaining things. Loading comes down to:

// Load initial state from server, convert it to Task instances, then populate self.tasks
$.getJSON("/tasks", function(allData) {
    var mappedTasks = $.map(allData, function(item) { return new Task(item) });
    self.tasks(mappedTasks);
}); 

Saving it to your service looks like this:

self.save = function() {
    $.ajax("/tasks", {
        data: ko.toJSON({ tasks: self.tasks }),
        type: "post", contentType: "application/json",
        success: function(result) { alert(result) }
    });
};

A related second option is using the mapping plugin to save/load your ViewModels in a conventions-based way. You still need some wiring to communicate with the server though.

For the View part, in both cases I think you already had the correct approach: use a foreach binding on a tbody and construct one row per ViewModel.

Again, this is a pretty vague/broad answer, in part because your question is rather broad. Hope it helps.

This is the way to do it... I have made a jsfiddle showing this:

To get this to work I had to add two callback methods to the original knockout foreach binding definition. I am currently trying to get these events into the newest version of knockout. I needed to add a beforeRenderAll and afterRenderAll callback to destroy the datatable and reinitialize the datatable after the knockouts foreach binding adds the html. This works like a charm The JSFiddle showing this has a fully editable jquery datatable bound to the ViewModel through knockout.

With Knockout, you wont be using the ajax implementation of datatables. You will be using the standard implementation where the html is already defined. Let Knockout handle the html by loading your data through ajax and assigning that to an observable array. The custom foreach binding below will then destroy the datatable, let knockout handle the html in the default knockout foreach fashion, and then the binding will reinitialize the datatable.

var viewmodel = new function(){
   var self = this;
   this.Objects = ko.mapping.fromJS([]);
   this.GetObjects = function(){
       $.get('your api url here', function(data){
           ko.mapping.fromJS(
               data,
               {
                   key: function(o){ return ko.utils.unwrapObservable(o.Id); },
                   create: function(options){ return new YourObjectViewModel(options.data); }
               },
               that.Objects
           ); 
       });
   };
};

//Or if you do not need to map to a viewmodel simply just...
$.get('your api url here', function(data){
    viewmodel.Options(data);
});

And below is the custom datatables binding...

ko.bindingHandlers.DataTablesForEach = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
             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 };
    }
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM