问题
I want to render data into table using datatable knockoutjs binding. I am using the below link and code for rendering data into table. http://datatables.net/dev/knockout/
The only change I did in above example is while rendering age data I have added input box in age col for ever record and Updatebutton at the bottom of table, so that user can change his age and click of update button data should be updated automatically and in next page it should reflect in table.
The issue am facing is that i am unable to update local js "people" model and hence unable to bind updated data using knockoutjs.
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;
});
};`
`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 }
]; `
`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);
// 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();
} );
} );
};
$(document).ready(function() {
var people = ko.mapping.fromJS( [] );
//loadData();
var dt = $('#example').DataTable( {
"bPaginate": false,
"bInfo" : false,
"bAutoWidth" : false,
"sDom" : 't',
"columns": [
{ "data": 'id' },
{ "data": 'first' },
{ "data": 'age',
"mRender": function (data, type, row ) {
var html = '<div style="display:inline-flex">' +
'<input type="text" class="headerStyle h5Style" id="ageId" value="'+data()+'"/>' +
'</div>';
return html;
}
}
]
} );
// 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) {
var d = data;
return ko.utils.unwrapObservable(d.id);
},
create: function(options) {
return new Person(options.data, dt);
}
},
people
);
} );
回答1:
I made a fiddle with a solution
http://jsfiddle.net/Jarga/hg45z9rL/
Clicking "Update" will display the current knockout model as text below the button.
What was missing was the linking the change of the textbox to the observable by adding a listener in the render function. Also each row's textbox was being given the same id, which is not a good idea either. (Note: the event aliases are just to prevent collision with other handlers)
Changing the render function to build useful ids and adding the following should work:
$('#' + id).off('change.grid')
$('#' + id).on('change.grid', function() {
row.age($(this).val());
});
Ideally Knockout would handle this for you but since you are not calling applyBindings nor creating the data-bind attributes for the html elements all that knockout really gives you here is the observable pattern.
Edit: Additional Solution
Looking into it a little bit more you can let Knockout handle the rendering by adding the data-bind
attribute into the template and binding your knockout model to the table element.
var html = '<div style="display:inline-flex">' +
'<input type="text" class="headerStyle h5Style" id="' + id + '" data-bind="value: $data[' + cell.row + '].age"/>'
And
ko.applyBindings(people, document.getElementById("example"));
This removes the whole custom subscription call when constructing the Person
object as well.
Here is another fiddle with the 2nd solution:
http://jsfiddle.net/Jarga/a1gedjaa/
I feel like this simplifies the solution. However, i do not know how efficient it performs nor have i tested it with paging so additional work may need to be done. With this method the mRender function is never re-executed and the DOM manipulation for the input is done entirely with knockout.
回答2:
This is the way to do it... I have made a jsfiddle showing this:
Edit: Recently worked out a way to get this binding using vanilla knockout. I've tested this out on the latest version of knockout (3.4) Just use this binding and knockout datatables works!
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); // !!!!! Need to set dependency
ko.bindingHandlers.foreach.update(element, valueAccessor, allBindings, viewModel, bindingContext);
(function() {
console.log(options);
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 };
}
};
JSFiddle
回答3:
Here is a simple workaround that re-binds the data in knockout and then destroys/recreates the datatable:
// Here's my data model
var ViewModel = function() {
this.rows = ko.observable(null);
this.datatableinstance = null;
this.initArray = function() {
var rowsource1 = [
{ "firstName" : "John",
"lastName" : "Doe",
"age" : 23 },
{ "firstName" : "Mary",
"lastName" : "Smith",
"age" : 32 }
];
this.redraw(rowsource1);
}
this.swapArray = function() {
var rowsource2 = [
{ "firstName" : "James",
"lastName" : "Doe",
"age" : 23 },
{ "firstName" : "Alice",
"lastName" : "Smith",
"age" : 32 },
{ "firstName" : "Doug",
"lastName" : "Murphy",
"age" : 40 }
];
this.redraw(rowsource2);
}
this.redraw = function(rowsource) {
this.rows(rowsource);
var options = { paging: false, "order": [[0, "desc"]], "searching":true };
var datatablescontainer = $('#datatablescontainer');
var html = $('#datatableshidden').html();
//Destroy datatable
if (this.datatableinstance) {
this.datatableinstance.destroy();
datatablescontainer.empty();
}
//Recreate datatable
datatablescontainer.html(html);
this.datatableinstance = datatablescontainer.find('table.datatable').DataTable(options);
}
};
ko.applyBindings(new ViewModel("Planet", "Earth")); // This makes Knockout get to work
https://jsfiddle.net/benjblack/xty5y9ng/
来源:https://stackoverflow.com/questions/27707378/datatable-data-binding-using-knockoutjs