1
votes

I am using knockout to bind my data to table and on page startup everything is working fine as expected. When i changed the underlying data by calling getDataByFilter() function, datatable data get refresh by knockout and everything is working fine as well.

Thing started to corrupt when i added paging and sorting by using $('#datatable_requirement').DataTable(). Everytime when i trigger getDataByFilter() function to retrieve new data, New data keep on stack on top of the existing data and sorting and searching is not functioning anymore. Google around still can't find any solution :(

Here is my View and Viewmodel

HTML:

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta charset="utf-8" />
<title></title>
</head>
<body>
<a href="javascript:void(0);" data-bind="click: getDataByFilter('pass some filter set')">Get new data</a>
<table id="datatable_requirement" class="table table-striped table-bordered" width="100%">
    <thead>
        <tr>
            <th class="hasinput" style="width:17%">
                <input type="text" class="form-control" placeholder="Filter Id" />
            </th>
            <th class="hasinput" style="width:17%">
                <input type="text" class="form-control" placeholder="Filter Name" />
            </th>
            <th class="hasinput" style="width:16%">
                <input type="text" class="form-control" placeholder="Filter Status" />
            </th>
        </tr>
        <tr>
            <th data-class="expand">Id</th>
            <th>Name</th>
            <th data-hide="phone">Status</th>
        </tr>
    </thead>
    <tbody>
        <!-- ko foreach: requirements -->
        <tr>
            <td><span data-bind="text: ReqEtpDispIdPrefix() + '-' + ReqDispId()"></span></td>
            <td><span data-bind="text: ReqName"></span></td>
            <td><span data-bind="text: ReqSttName"></span></td>
        </tr>
        <!-- /ko -->
    </tbody>
</table>
</body>
</html>

JavaScript:

<script type="text/javascript">

$(document).ready(function () {
 $.getJSON("/api/GetAllRequirementByFilter/", function (data){

 var VM = new ViewModel(data);
 ko.applyBindings(VM);

var responsiveHelper_datatable_requirement = undefined;

var otable = $('#datatable_requirement').DataTable({
    "sDom": "<'dt-toolbar'<'col-xs-12 col-sm-6 hidden-xs'f><'col-sm-6 col-xs-12 hidden-xs'l>r>" +
            "t" +
            "<'dt-toolbar-footer'<'col-sm-6 col-xs-12 hidden-xs'i><'col-xs-12 col-sm-6'p>>",
    "autoWidth": true,
    "preDrawCallback": function () {
        // Initialize the responsive datatables helper once.
        if (!responsiveHelper_datatable_requirement) {
            responsiveHelper_datatable_requirement = new ResponsiveDatatablesHelper($('#datatable_requirement'), breakpointDefinition);
        }
    },
    "rowCallback": function (nRow) {
        responsiveHelper_datatable_requirement.createExpandIcon(nRow);
    },
    "drawCallback": function (oSettings) {
        responsiveHelper_datatable_requirement.respond();
    }
});

// custom toolbar
$("div.toolbar").html('<div class="text-right"><img src="/Content/img/logo.png" alt="SmartAdmin" style="width: 111px; margin-top: 3px; margin-right: 10px;"></div>');

// Apply the filter
$("#datatable_requirement thead th input[type=text]").on('keyup change', function () {

    otable
        .column($(this).parent().index() + ':visible')
        .search(this.value)
        .draw();

   });
});
})

</script>

ViewModel:

var ViewModel = function (data) {
var self = this;
self.requirements = ko.observableArray([]);

self.requirements = ko.mapping.fromJS(data);

function ajaxHelper(uri, method, data) {
    self.error(''); // Clear error message
    return $.ajax({
        type: method,
        url: uri,
        dataType: 'json',
        contentType: 'application/json',
        data: data ? JSON.stringify(data) : null
    }).fail(function (jqXHR, textStatus, errorThrown) {
        self.error(errorThrown);
    });
}

self.getDataByFilter = function (item)
{
    ajaxHelper('/api/someapi/'+ item, 'GET').done(function (data) {
                    ko.mapping.fromJS(data, self.requirements);
                });
}
1

1 Answers

0
votes

Knockout needs complete control over the DOM, so when you want to throw a jQuery library into the mix that does the same, you need to do some extra work to make sure they both don't step on each others' toes. Often, you do that in form of a binding.

DataTables offers an example for a Knockout integration:

// Helper function so we know what has changed
// http://stackoverflow.com/questions/12166982
ko.observableArray.fn.subscribeArrayChanged = function(addCallback, deleteCallback) {
    var previousValue = undefined;
    this.subscribe(function(_previousValue) {
        previousValue = _previousValue.slice(0);
    }, undefined, 'beforeChange');
    this.subscribe(function(latestValue) {
        var editScript = ko.utils.compareArrays(previousValue, latestValue);
        for (var i = 0, j = editScript.length; i < j; i++) {
            switch (editScript[i].status) {
                case "retained":
                    break;
                case "deleted":
                    if (deleteCallback)
                        deleteCallback(editScript[i].value);
                    break;
                case "added":
                    if (addCallback)
                        addCallback(editScript[i].value);
                    break;
            }
        }
        previousValue = undefined;
    });
};

// Person object
var Person = function(data, dt) {
    this.id    = data.id;
    this.first = ko.observable(data.first);
    this.last  = ko.observable(data.last);
    this.age   = ko.observable(data.age);
    this.full  = ko.computed(function() {
        return this.first() + " " + this.last();
    }, this);     

    // Subscribe a listener to the observable properties for the table
    // and invalidate the DataTables row when they change so it will redraw
    var that = this;
    $.each( [ 'first', 'last', 'age' ], function (i, prop) {
        that[ prop ].subscribe( function (val) {
            // Find the row in the DataTable and invalidate it, which will
            // cause DataTables to re-read the data
            var rowIdx = dt.column( 0 ).data().indexOf( that.id );
            dt.row( rowIdx ).invalidate();
        } );
    } );
};


// Initial data set
var data = [
    { id: 0, first: "Allan", last: "Jardine", age: 86 },
    { id: 1, first: "Bob", last: "Smith", age: 54 },
    { id: 2, first: "Jimmy", last: "Jones", age: 32 }
];

$(document).ready(function() {
    var people = ko.mapping.fromJS( [] );
    var dt = $('#example').DataTable( {
        columns: [
            { data: 'id' },
            { data: 'first()' },
            { data: 'age()' }
        ]
    } );

    // Update the table when the `people` array has items added or removed
    people.subscribeArrayChanged(
        function ( addedItem ) {
            dt.row.add( addedItem ).draw();
        },
        function ( deletedItem ) {
            var rowIdx = dt.column( 0 ).data().indexOf( deletedItem.id );
            dt.row( rowIdx ).remove().draw();
        }
    );

    // Convert the data set into observable objects, and will also add the
    // initial data to the table
    ko.mapping.fromJS(
        data,
        {
            key: function(data) {
                return ko.utils.unwrapObservable(data.id);        
            },
            create: function(options) {
                return new Person(options.data, dt);
            }    
        },
        people
    );

    // Examples:

    // Update a field
    people()[0].first( 'Allan3' );

    // Add an item
    people.push( new Person( {
        id: 3,
        first: "John",
        last: "Smith",
        age: 34
    }, dt ) );

    // Remove an item
    people.shift();
} );