KNockoutJS with jQuery datatables, bound rows are not updated properly - javascript

Here's JS code:
function ProductViewModel() {
// Init.
var self = this;
self.products = ko.observableArray();
self.singleProduct = ko.observable();
var mappedProducts;
// Initialize table here.
$.getJSON("/admin/test", function(allData) {
mappedProducts = $.map(allData, function(item) { return new Product(item);});
self.products(mappedProducts);
self.oTable = $('#products-table').dataTable( {
"aoColumns": [
{ "bSortable": false, "mDataProp": null, sDefaultContent: '' },
{"mData": "name"},
{"mData": "dealer"},
{"mData": "cost"},
{"mData": "price"},
{ "bSortable": false, sDefaultContent: '' }
],
});
});
// Here i'm using the basic switch pattern, as from KO tutorials.
// This is intended for showing a single product form.
self.edit = function(product) {
self.singleProduct(product);
}
// This is intended to hide form and show list back.
self.list = function() {
self.singleProduct(null);
}
// This is the form save handler, actually does nothing
// but switch the view back on list.
self.doEdit = function(product) {
self.list();
}
}
// My model.
function Product(item) {
this.name = ko.observable(item.name);
this.dealer = ko.observable(item.dealer);
this.cost = ko.observable(item.cost);
this.price = ko.observable(item.price);
this.picture = ko.observable();
}
Here's my markup:
<table id="products-table" class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th>Pic</th>
<th>Name</th>
<th>Dealer</th>
<th>Cost</th>
<th>Price</th>
<th>Actions</th>
</tr>
</thead>
<tbody data-bind="foreach: $parent.products">
<tr>
<td><span data-bind='ifnot: picture'>-</span></td>
<td><a data-bind="text: name"></a></td>
<td><span data-bind='text: dealer'></span></td>
<td><span data-bind='text: cost'></span></td>
<td><span data-bind='text: price'></span></td>
<td>
<button data-bind='click: $root.edit'><i class='icon-pencil'></i>
</button>
</td>
</tr>
</tbody>
</table>
When i do click on the edit button, triggering the $root.edit handler, a form is shown because of a
<div data-bind='with: singleProduct'>
binding i have made. Inside this binding i have a form, with a
<input data-bind="value: name" type="text" id="" placeholder="Name"
> class="col-xs-10 col-sm-5">
field.
Problem: When i edit the value in the input field, the relative row in the datatable is not updated. I have tried a basic table without the datatables plugin and it does work, meaning that if i change value, the row in the table is properly updated.
What's wrong here?
== EDIT ==
I found out that moving the bind point to the table TD fixed the problem, though still i can't figure out why.
<tr>
<td data-bind="text: name"></td>
<!-- More columns... -->
</tr>
The above code is working properly now. Why ?
== EDIT2 ==
Now that i fixed first issue, comes the second. I implemented my "save new" method like so
self.doAdd = function(product) {
$.ajax("/product/", {
data: ko.toJSON({ product: product }),
type: "post", contentType: "application/json",
success: function(result) { alert('ehh'); }
}).then(function(){
self.products.push(product); // <--- Look at this!
self.list();
});
}
The self.products.push(product); in the success handler is properly updating my products observable. Then, a new row is automatically added to my table, and this is the good news.
Bad news is that datatables controls, such search field or clickable sorting arrows, disappear as soon as i push the new product in the array. Why so!?

Did you ever resolve this?
I've been having similar issues for ages.
In the end my fix was to map all my entities using ko.mapping.fromJS(entity) - this then hooked up all the required dependencies and ensured that any changes flowed through my model.

http://jsfiddle.net/zachpainter77/4tLabu56/
ko.bindingHandlers.DataTablesForEach = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
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, 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 };
}
};
https://rawgit.com/zachpainter77/zach-knockout.js/master/zach-knockout.debug.js

Related

jQuery Datatable cell not updating

I have a table that I am using jQuery Datatables with.
Picture:
Scenario:
As you can see in the picture, there is a Delete link. When that link is clicked, a modal pop-up will show asking the user if they really want to delete that item. If yes, delete.. if no.. cancel out of the modal.
What I want:
When a user decides to delete an item and confirms it.. I would like to change the status of that item to "Deleted", via ajax. I am able to change the value, but that value does not show in the table. I have researched this for a couple of days now, but nothing seems to work.
My Code
<table id="Item-Table" class="table table-bordered">
<thead>
<tr>
<th class="text-center">
#Html.DisplayNameFor(model => model.AssetTag)
</th>
<th class="text-center">
#Html.DisplayNameFor(model => model.codeMakeModel.MakeModel)
</th>
<th class="text-center">
#Html.DisplayNameFor(model => model.codeStatu.Status)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr class="text-center">
<td>
#Html.ActionLink(item.AssetTag, "Edit", new { id = item.Id })
</td>
<td>
#Html.DisplayFor(modelItem => item.codeMakeModel.MakeModel)
</td>
<td class="changeStatus">
#Html.DisplayFor(modelItem => item.codeStatu.Status)
</td>
<td>
Delete
</td>
</tr>
}
</tbody>
</table>
#section scripts{
<script>
var settings = {};
settings.baseUri = '#Request.ApplicationPath';
var infoGetUrl = "";
if (settings.baseUri === "/projectonservername") {
infoGetUrl = settings.baseUri + "/api/itemsapi/";
} else {
infoGetUrl = settings.baseUri + "api/itemsapi/";
}
$(document).ready(function () {
var itemsTable = $("#Item-Table").DataTable({
"aoColumnDefs": [
{ "bSortable": false, "aTargets": [3] },
{ "bSearchable": false, "aTargets": [3] }
]
});
$("#Item-Table").on("click",
".js-item-delete",
function() {
var link = $(this);
bootbox.confirm({
title: "Delete Item?",
message: "Are you sure you want to delete this item?",
buttons: {
cancel: {
label: '<i class="fa fa-times"></i> Cancel'
},
confirm: {
label: '<i class="fa fa-check"></i> Confirm'
}
},
callback: function(result) {
if (result) {
toastr.options = {
timeOut: 5000
}
$.ajax({
url: infoGetUrl + link.data("item-id"),
method: "DELETE",
success: function (result) {
//itemsTable.cell(itemsTable.row(this), 2).data("Deleted");
//itemsTable.draw();
//itemsTable.reload();
console.log(itemsTable.cell(itemsTable.row(this), $('.changeStatus')).data());
itemsTable.cell(itemsTable.row(this), $('.changeStatus')).data("Deleted").draw();
console.log(itemsTable.cell(itemsTable.row(this), $('.changeStatus')).data());
toastr.success("Item successfully deleted");
},
error: function(jqXHR, textStatus, errorThrown) {
var status = capitalizeFirstLetter(textStatus);
console.log(jqXHR);
toastr.error(status + " - " + errorThrown, "Sorry, something went wrong.");
}
});
}
}
});
});
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
})
</script>
}
What I am Receiving
In the above code, specifically these lines:
console.log(itemsTable.cell(itemsTable.row(this), $('.changeStatus')).data());
itemsTable.cell(itemsTable.row(this), $('.changeStatus')).data("Deleted").draw();
console.log(itemsTable.cell(itemsTable.row(this), $('.changeStatus')).data());
I am logging the value of the cell before I update that cell value, then changing the cell value, then logging the new/updated cell value.
Here is what I am receiving in the console:
But the table is not updating, or rather.. redrawing itself to show deleted.. the only way for it show deleted is to refresh the page which defeats the purpose of ajax..
How do I get the table to update the cell value without a page refresh?
Any help is appreciated.
I was able to answer this myself with some help of DavidDomain in the comments.
He suggested that I could possibly be selecting an incorrect row. So that gave me the idea to get the row at the start of this by adding:
$("#Item-Table").on("click",
".js-item-delete",
function() {
var link = $(this);
var row = $(this).parents("tr"); // get row element
Then set the cell data using that variable like so:
itemsTable.cell(itemsTable.row(row), $('.changeStatus')).data("Deleted").draw();
This worked and successfully drew the table with the updated value.

Model not accesseble outside namespace

Im trying to bind my knockout array to a table, but cant reach the model outside the javascript function.
Here is my javascript code
(function (conf, $, undefined) {
var model = { menuRows : [], orderRows : [], menuDetails : null };
conf.getMenuRows = function () {
$.get("/orderpackage/row", function (data) {
model.orderRows = data;
});
};
conf.getMenuRows();
ko.applyBindings(model);
}(window.conf = window.conf || {}, jQuery));
And this is the HTML
<table class="table table-hover table-bordered">
<thead>
<tr>
<th>Beskrivning</th>
</tr>
</thead>
<tbody data-bind="foreach: model.orderRows">
<tr>
<td data-bind="text: description"></td>
</tr>
</tbody>
</table>
model.orderRows is not found.
Cant understand what im doing wrong here.
your models are not using the observable array function. You will need something like the following:
function Model() {
var self = this();
self.menuRows = ko.observableArray();
self.orderRows = ko.observableArray();
self.getMenuRows = function() {
$.get("/orderpackage/row", function (data) {
self.orderRows = ko.observableArray(data)
});
....
}
Then you can call
(function (conf, $, undefined) {
var model = Model();
model.getMenuRows();
ko.applyBindings(model);
}(window.conf = window.conf || {}, jQuery));
Then you should be able to bind like you are doing in your HTML.
More tutorials can be found here: http://learn.knockoutjs.com/
If you then want to bind to items in each of the array elements, the description for example, you will need to create an additional model definition for the row and parse the data returned from your api to the model type.
I found a good solution
(function (conf, $, undefined) {
var model = {
menuRows: ko.observableArray([]),
order: ko.observableArray([]),
menuDetails: ko.observable()
};
conf.getMenuRows = function () {
$.ajax({
url: "/orderpackage/row",
cache: false,
type: "GET",
datatype: "json",
contenttype: "application/json;utf8"
}).done(function (data) {
model.order(data.model);
});
};
conf.getMenuRows();
ko.applyBindings(model);
}(window.conf = window.conf || {}, jQuery));

Angular - Datatable click row event

I am working with AngularJS and angular-datatable and I want to work with the event in a row, I have setup the controller to listen the event but it is not work. My code is :
html
<div class="panel panel-flat">
<div class="panel-heading">
<h6 class="panel-title">Planilla</h6>
</div>
<div class="panel-heading">
<table class="table datatable-basic table-hover" datatable="ng" dt-options="empleadoList.dtOptions" dt-column-defs="empleadoList.dtColumnDefs" >
<thead>
<tr>
<th style="width: 30px;">Nro.</th>
<th>Nombre Completo</th>
<th class="col-md-2">DNI</th>
<th class="col-md-2">Celular</th>
<th class="col-md-2">Teléfono</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="empleado in empleadoList.empleados">
<td style="width: 30px;">{{$index + 1}}</td>
<td> <span class="text-muted"><i class="icon-user"></i>{{empleado.apellidoPaterno}} {{empleado.apellidoMaterno}} {{empleado.nombre}}</span></td>
<td><span class="text-success-600"><span class="status-mark border-blue position-left"></span>{{empleado.dni}}</span></td>
<td><span class="text-success-600"><i class="icon-mobile position-left"></i> {{empleado.celular}}</span></td>
<td><h6 class="text-semibold"><i class="icon-phone position-left"></i> {{empleado.telefono}}</h6></td>
</tr>
</tbody>
</table>
</div>
</div>
controller.js
App.controller('EmpleadoListController', function($scope,$resource,EmpleadoService,DTOptionsBuilder,DTColumnDefBuilder) {
$scope.dtOptions = DTOptionsBuilder.newOptions()
.withDisplayLength(10)
.withOption('bLengthChange', false)
.withPaginationType('full_numbers')
.withOption('rowCallback', rowCallback);
$scope.dtColumnDefs = [
DTColumnDefBuilder.newColumnDef(0),
DTColumnDefBuilder.newColumnDef(1),
DTColumnDefBuilder.newColumnDef(2),
DTColumnDefBuilder.newColumnDef(3),
DTColumnDefBuilder.newColumnDef(4)
];
function rowCallback(nRow, aData, iDisplayIndex, iDisplayIndexFull) {
$('td', nRow).unbind('click');
$('td', nRow).bind('click', function() {
$scope.$apply(function() {
console.log('click row');
});
});
return nRow;
}
EmpleadoService.fetch().then(
function(response){
return $scope.empleadoList = { empleados: response.data};
},
function(errResponse){
console.error('Error while fetching users');
return $q.reject(errResponse);
}
);
});
app.js
'use strict';
var App = angular.module('myApp', ['ngRoute','ngResource','datatables']);
App.config(function($routeProvider) {
var resolveEmpleados = {
empleados: function (EmpleadoService) {
return EmpleadoService.fetch();
}
};
$routeProvider
.when('/planilla', {
controller:'EmpleadoListController as empleadoList',
templateUrl:'static/js/planilla.html',
});
});
Thanks for all.
Since you are using the angular way for rendering, why not use ng-click as well :
<tr ng-repeat="empleado in empleadoList.empleados" ng-click="click(empleado)">
$scope.click = function(empleado) {
console.log(empleado.apellidoPaterno+' clicked')
}
I see you miss function in your code:
function someClickHandler(info) {
vm.message = info.id + ' - ' + info.firstName;
}
function rowCallback(nRow, aData, iDisplayIndex, iDisplayIndexFull) {
// Unbind first in order to avoid any duplicate handler (see https://github.com/l-lin/angular-datatables/issues/87)
$('td', nRow).unbind('click');
$('td', nRow).bind('click', function() {
$scope.$apply(function() {
vm.someClickHandler(aData);
});
});
return nRow;
}
and don't forget this:
vm.someClickHandler = someClickHandler;
you can read document in here
Hope help you.
You were almost there. The row element is accessible from within the row callback function as nRow.
So for instance you can for instance change the colour of the row by toggling the selected class as follows
$scope.$apply(function() {
$(nRow).toggleClass('selected');
// do your stuff with the row here
});
nRow gives you access to the row element.
Then there is aData which gives you an array containing the values of the td or column elements in that row.
$scope.$apply(function() {
console.log(aData);
// do your stuff with the row here
});
Maybe this code can help us:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-row-click-event',
templateUrl: 'row-click-event.component.html'
})
export class RowClickEventComponent implements OnInit {
message = '';
dtOptions: DataTables.Settings = {};
constructor() { }
someClickHandler(info: any): void {
this.message = info.id + ' - ' + info.firstName;
}
ngOnInit(): void {
this.dtOptions = {
ajax: 'data/data.json',
columns: [{
title: 'ID',
data: 'id'
}, {
title: 'First name',
data: 'firstName'
}, {
title: 'Last name',
data: 'lastName'
}],
rowCallback: (row: Node, data: any[] | Object, index: number) => {
const self = this;
// Unbind first in order to avoid any duplicate handler
// (see https://github.com/l-lin/angular-datatables/issues/87)
$('td', row).unbind('click');
$('td', row).bind('click', () => {
self.someClickHandler(data);
});
return row;
}
};
}
}
```
Link where i found this example:
[Link github datatables examples][1]
[1]: https://github.com/l-lin/angular-datatables/blob/master/demo/src/app/advanced/row-click-event.component.ts

DataTables bindings with KnockoutJS and Knockout-mapper.js not working

I a following tutorial from here on how to bind DataTables with KnockoutJS and Knockout mapper. From what i can see i only need to give my object and apply binding to table but when i do it i don't get anything (no errors and no display data). What am i missing?
Controller JSON data:
public virtual JsonResult GetRecordsJsonResult()
{
var userBusinessLogic = InterfaceResolver.ResolveWithTransaction<IUserBusinessLogic>();
var records = userBusinessLogic.GetAll().Select(x => new
{
x.Id,
x.FirstName,
x.LastName,
x.Email
}).OrderBy(i => i.Id);
var data = Json(new
{
max = records.Count(),
items = records
}, JsonRequestBehavior.AllowGet);
return data;
}
JSON data example that i get
HTML:
<script type="text/javascript">
$(function () {
function viewModel(data) {
console.log("viewModel");
var self = this;
ko.mapping.fromJS(data, {}, self);
console.log(self);
console.log("data");
console.log(data);
}
$.ajax({
url: "#Url.Action("GetRecordsJsonResult")", success: function (data) {
ko.applyBindings(new viewModel(data));
$("#items").DataTable({ responsive: true });
}
});
});
</script>
<table id="items" class="display table table-striped table-responsive table-bordered table-hover">
<thead>
<tr>
<th>ID</th>
<th>First name</th>
<th>Last name</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<tr>
<td><span data-bind="text: $data.Id"></span></td>
<td><span data-bind="text: $data.FirstName"></span></td>
<td><span data-bind="text: $data.LastName"></span></td>
<td><span data-bind="text: $data.Emaiol"></span></td>
</tr>
</tbody>
</table>
Console log from browser:
I think there might be an error in how i get my items from controller since everything is working fine when i follow tutorial. Any ideas? Thank you for your time.
yes there are many things wrong with the above example... This is the easiest solution for getting knockout to play nicely with datatables...
This solution uses a custom binding that I created that depends on a knockout fork. If you like this solution please leave a comment on the pull request to get it merged intp knockout 3.4, Thanks.
example on jsfiddle
knockout pull request
ko.bindingHandlers.DataTablesForEach = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
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, 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().clear();
$(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 };
}
};

How to define knockout data model for given situation?

My data model is as given below.
Module
Fields (ObservableArray)
Actions(ObservableArray)
Fields (?)
name (dyanmic from field list)
type (dynamic from field list)
selected (entered by user in UI)
Module is main object. Fields and Actions are observable arrays. Field lists under each action needs to be have updated field list and also will have an additional property which is captured from UI.
How the Fields under action model should be populated? Fields list under each action will have unique value for selected field.
Do I need to subscribe to fields ObservableArray and manipulate the Fields list under each action manually or is there any other better way doing this?
This is how I handle this situation
http://plnkr.co/edit/sWVqrFHdzWUXob42xS7Z?p=preview
Javascript
var childObject = function(data){
var self = this;
//No special mapping needed here, the mapping plugin does it for us
ko.mapping.fromJS(data, {}, self);
this.Select = function(){
self.selected(!self.selected());
};
};
var parentObject = function(data){
var self = this;
//Map the object to myself, using the mapping object we made earlier
ko.mapping.fromJS(data, {}, self);
//Remap the actions column to observable's
this.Actions = ko.observableArray(_.map(self.Actions(), function(item){
return new childObject(item);
}));
};
var myViewModel = function(){
var self = this;
this.RootObject = ko.observable();
var objectData = {
"Fields": [1, 2, 3, 4],
"Actions": [
{
"name": "David",
"type": "string",
"selected": false
},
{
"name": "Nish",
"type": "string",
"selected": true
}]
};
this.Init = function(){
//Pass the object data to the parent object.
self.RootObject(new parentObject(objectData))
};
};
$(function(){
myApp = new myViewModel();
myApp.Init();
ko.applyBindings(myApp);
})
Html
<div data-bind="with: RootObject">
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Selected</th>
</tr>
</thead>
<tbody data-bind="foreach: Actions">
<tr data-bind="click: Select">
<td data-bind="text: name"></td>
<td data-bind="text: type"></td>
<td data-bind="text: selected"></td>
</tr>
</tbody>
</table>
</div>

Categories

Resources