The following code samples demonstrates how I initialize a jQuery Datatable with HTML, knockout and typescript
HTML:
<table id="coursemoment-info-table" class="table table-hover">
<thead>
<tr >
// headers
</tr>
</thead>
<tbody>
<!-- ko foreach: selectedCourseMoment().courseApplications -->
<tr>
<td>
<a data-bind="{text: ssn, attr:{ href: '/Medlem/' + ssn} }"></a>
</td>
<td data-bind="text:name + ' ' + surname"></td>
.
.
.
</tr>
<!-- /ko-->
</tbody>
</table>
Typescript:
private initCourseMomentInformationDataTable(): void {
$("#coursemoment-info-table").DataTable({
pageLength: 5,
order: [1, 'desc'],
language: {
url: "/Assets/js/jquery-datatable-swedish.json"
}
});
}
I have had some problems with reinitializing the table, but I managed to handle it with first clearing the datatable, and then adding rows to the datatable and redraw it.
if (this.tableInitiliazed) {
$("#coursemoment-info-table").DataTable().clear().draw();
for (var i = 0; i < data.courseApplications.length; i++) {
var application = data.courseApplications[i];
$("#coursemoment-info-table").DataTable().row.add(
[
application.ssn,
// blah blah yada yada
]).draw();
}
This does indeed reinitiliaze the datatable, but it does not put a hyperlink to the first column as it does when initializing the table. All the other table settings are correct, such as language and pagelength.
Since I add one row at a time, with the multiple columns I do not know how to set column settings for "applications.ssn" directly. I have tried to initialize the datatable in the viewmodel with typescript but I get the same problem.
Any ideas how to reinitialize and put a hyperlink setting for a specific column?
I think you want to use Datatables render to create your link. so here is a custom data binder for data table that uses render to create the link.
here is the fiddle to see it in action. http://jsfiddle.net/LkqTU/35823/
ko.bindingHandlers.dataTable = {
init: function(element, valueAccessor, allBindingsAccessor) {
var value = valueAccessor(),
rows = ko.toJS(value);
allBindings = ko.utils.unwrapObservable(allBindingsAccessor()),
$element = $(element);
var table = $element.DataTable( {
data: rows,
columns: [
{ data: "id" },
{ data: "firstName" },
{ data: "lastName" },
{ data: "phone" },
{
data: "ssn",
"render": function ( data, type, full, meta ) {
return '<a href="/Medlem/'+data+'">' + data + '<a>';
}
}
]
} );
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$element.dataTable().fnDestroy();
});
value.subscribe(function(newValue) {
rows = ko.toJS(newValue)
console.log(rows);
$element.find("tbody tr").remove();
table.rows().remove().draw();
setTimeout(function(){ $.each(rows, function( index, row ) {
table.row.add(row).draw()
});
}, 0);
}, null);
}
}
Related
I want to use the jquery pluging datatables and complement local data through an external REST ressource.
My table has to columns, Id and RestCalledValue:
<table id="table">
<thead>
<tr>
<th>Id</th>
<th>RestCalledValue</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
My js has an object data, which holds my local data:
let data = [
{
"id": 1
},
{
"id": 2
}
]
My datatables initialisation looks like below: i want to give the second column the returned value from the function getRestDataForId (id).
$(function () {
var table = $('#table').DataTable({
data: data,
columns: [
{
data: "id"
},
{
data: function (row, type, set, meta) {
getRestDataForId(row.id);
},
defaultContent: "-"
}
]
});
});
function getRestDataForId(id) {
fetch('https://jsonplaceholder.typicode.com/todos/' + id)
.then(response => response.json())
.then(data => data.title)
}
I constructed a fiddle here:
https://jsfiddle.net/mxhdwfz6/2/
I must be treating the promise wrong. Looking forward for your input!
I have a web app with cloud firestore as my backend. I used DataTable to export data from cloud firestore and display on the webpage, and the table looks like this:
Table
The code to load "orders" collection from cloud firestore and append to DataTables is:
var dataTable;
db.collection("orders").orderBy('timestamp', 'desc')
.get()
.then(function (querySnapshot) {
if (querySnapshot.size) {
var firestore_source = [];
querySnapshot.forEach(function (data) {
var obj = data.data();
obj.id = data.id;
firestore_source.push(obj);
});
//console.log('data:', firestore_source);
dataTable.clear();
dataTable.rows.add(firestore_source);
dataTable.order([0, 'desc']).draw();
}
})
.catch(function (error) {
console.log("Error getting documents: ", error);
});
$(document).ready(function () {
dataTable = $('#example').DataTable({
columns: [
{ data: 'Name' },
{ data: "Date" },
{ data: "Ins" },
{ data: "Phone" },
{ data: "Item" },
{ data: "Price"},
{ data: "Commision"},
{ data: "Revenue"},
{
data: null,
className: "center",
defaultContent: 'Edit / Delete'
}
],
"columnDefs": [
{"className": "dt-center", "targets": "_all"}
],
});
$('#example').on('click', 'a.editor_remove', function (e) {
e.preventDefault();
console.log("delete clicked");
console.log($(this).closest('tr'));
// what I should do here?
} );
});
And datatables in HTML:
<table id="example" class="table table-striped table-bordered" style="width:100%">
<thead>
<tr>
<th>Customer</th>
<th>Order Date</th>
<th>Instagram</th>
<th>Phone</th>
<th>Item</th>
<th>Price $</th>
<th>Commission</th>
<th>Earnings $</th>
<th>Edit / Delete</th>
</tr>
</thead>
</table>
Currently, the entire data within "orders" collection is loaded and obviously there are no features like editing and deleting data in each row.
So, I am stuck here that I have no idea how to identify each row in my table when clicking the edit/delete buttons on that row, so that I can use it as parameters to query cloud firestore?
I saw that there is built in tool Editor, but I am looking for native methods.
Regarding datatable API, You can get the clicked/selected row's data by this code which means you can get identity to edit or remove the selected row.
$('#example tbody').on('click', 'a.editor_remove', function () {
let row = dataTable.api().row($(this).closest("tr")).data();
console.log(row);
});
I am using gijgo grid to bind data. I did it two ways using gijgo grid.
1)First Binding data with html helpers to html table and doing CRUD with Gijgo,it binds data,do CRUD but does not reload grid on add,edit and delete.
<table id="grid">
<thead>
<tr>
<th width="56">ID</th>
<th data-sortable="true">Brand</th>
<th data-sortable="true">Model</th>
<th width="64" data-tmpl="<span class='material-icons gj-cursor-pointer'>edit</span>" align="center" data-events="click: Edit"></th>
<th width="64" data-tmpl="<span class='material-icons gj-cursor-pointer'>delete</span>" align="center" data-events="click: Delete"></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Id)
</td>
<td>
#Html.DisplayFor(modelItem => item.Brand)
</td>
<td>
#Html.DisplayFor(modelItem => item.Model)
</td>
</tr>
}
</tbody>
</table>
in delete function,grid doesn't reload after deleting
function Delete(e) {
if (confirm('Are you sure?')) {
$.ajax({ url: '/Home/Delete', data: { id: e.data.id }, method: 'POST' })
.done(function () {
//grid.reload({ page: 1});
grid.reload();
})
.fail(function () {
alert('Failed to delete.');
});
}
}
2) Then I did a different implementation of gijgo grid binding data via ajax call of gijgo using this example
Gijgo Grid example
The Get function returns JSON
public JsonResult Get(int? page, int? limit, string sortBy, string direction, string brand, string model)
{
List<CarsViewModel> records;
int total;
var query = _context.Cars.Select(p => new CarsViewModel
{
Id = p.Id,
Brand = p.Brand,
Model = p.Model
});
if (!string.IsNullOrWhiteSpace(model))
{
query = query.Where(q => q.Model != null && q.Model.Contains(model));
}
if (!string.IsNullOrWhiteSpace(brand))
{
query = query.Where(q => q.Brand != null && q.Brand.Contains(brand));
}
if (!string.IsNullOrEmpty(sortBy) && !string.IsNullOrEmpty(direction))
{
if (direction.Trim().ToLower() == "asc")
{
switch (sortBy.Trim().ToLower())
{
case "brand":
query = query.OrderBy(q => q.Brand);
break;
case "model":
query = query.OrderBy(q => q.Model);
break;
}
}
else
{
switch (sortBy.Trim().ToLower())
{
case "brand":
query = query.OrderByDescending(q => q.Brand);
break;
case "model":
query = query.OrderByDescending(q => q.Model);
break;
}
}
}
//else
//{
// query = query.OrderBy(q => q.o);
//}
total = query.Count();
if (page.HasValue && limit.HasValue)
{
int start = (page.Value - 1) * limit.Value;
records = query.Skip(start).Take(limit.Value).ToList();
}
else
{
records = query.ToList();
}
return this.Json(new { records, total }, JsonRequestBehavior.AllowGet);
}
jQuery(document).ready(function ($) {
grid = $('#grid').grid({
primaryKey: 'Id',
dataSource: '/Home/Get',
columns: [
{ field: 'Id', width: 56 },
{ field: 'Brand', sortable: true },
{ field: 'Model', sortable: true },
{ width: 64, tmpl: '<span class="material-icons gj-cursor-pointer">edit</span>', align: 'center', events: { 'click': Edit } },
{ width: 64, tmpl: '<span class="material-icons gj-cursor-pointer">delete</span>', align: 'center', events: { 'click': Delete } }
],
pager: { limit: 5 }
});
dialog = $('#dialog').dialog({
autoOpen: false,
resizable: false,
modal: true,
width: 360
});
$('#btnAdd').on('click', function () {
$('#Id').val('');
$('#Brand').val('');
$('#Model').val('');
dialog.open('Add Car');
});
$('#btnSave').on('click', Save);
$('#btnCancel').on('click', function () {
dialog.close();
});
$('#btnSearch').on('click', function () {
grid.reload({ page: 1, name: $('#txtBrand').val(), nationality: $('#txtModel').val() });
});
$('#btnClear').on('click', function () {
$('#txtBrand').val('');
$('#txtModel').val('');
grid.reload({ brand: '', model: '' });
});});
which results in JSON returned in this format
{"records":[{"Id":7,"Brand":"toyota","Model":"matrix"},{"Id":8,"Brand":"Mazda","Model":"M3"}],"total":2} and gives error unable to bind data like
SyntaxError: Unexpected token o in JSON at position 1
If I remove the record and total section and put raw json as datasource like this
[{"Id":7,"Brand":"toyota","Model":"matrix"},{"Id":8,"Brand":"Mazda","Model":"M3"}]
then data is bound but again grid.reload() doesnt work. I am really frustrated why this issues are there. First the gijgo grid server side controller code returns JSON dataas record with total and then I am not able to bind it with the code that gijgo has provided in jquery. Then grid.reload() isn't working
Could you review our ASP.NET examples where everything is setup correctly. You can find them at https://github.com/atatanasov/gijgo-asp-net-examples
I'm trying to implement Server Side DataTable.
Everything goes perfectly fine up to the last rowCallback where I'm appending button to additional column for Actions (i.e. Edit/Delete).
Issue:
Here's my code.
<link href="~/Content/datatables.min.css" rel="stylesheet" /> //<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/dt/dt-1.10.16/datatables.min.css"/>
<link href="~/Content/font-awesome.min.css" rel="stylesheet" />
<div class="container">
<div class="row" style="margin-top:25px">
<table class="table table-bordered table-responsive dataTables-list">
<thead>
<tr>
<th>
Id
</th>
<th>
Name
</th>
<th>
Actions
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Id)
</td>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
<i class="fa fa-pencil"></i>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
JavaScript:
<script src="~/Scripts/datatables.min.js"></script> //<script type="text/javascript" src="https://cdn.datatables.net/v/dt/dt-1.10.16/datatables.min.js"></script>
<script>
$(document).ready(function () {
$('.dataTables-list').DataTable({
/*Sorting*/
"bSort": true,
"aoColumnDefs": [{
'bSortable': true
}],
"processing": true, // for show progress bar
"serverSide": true, // for process server side
"ajax": {
"url": "/Home/LoadData",
"type": "POST",
"datatype": "json"
},
"aoColumns": [{
"mDataProp": "Id"
}, {
"mDataProp": "Name"
}, {
"mDataProp": "Actions"
}],
"rowCallback": function (row, data, index) {
var newBtns = '<i class="fa fa-pencil"></i> ';
// $(row).append(newBtns);
$('td:eq(2)', row).html(newBtns);
},
language: {
paginate: {
next: '»',
previous: '«'
},
emptyTable: "Der er ingen poster.",
sInfo: "Viser _START_ til _END_ af poster."
}
});
});
</script>
Controller:
[HttpPost]
public ActionResult LoadData()
{
try
{
var draw = Request.Form.GetValues("draw").FirstOrDefault();
var start = Request.Form.GetValues("start").FirstOrDefault();
var length = Request.Form.GetValues("length").FirstOrDefault();
//Find Order Column
var sortColumn = Request.Form.GetValues("columns[" + Request.Form.GetValues("order[0][column]").FirstOrDefault() + "][data]").FirstOrDefault();
var sortColumnDir = Request.Form.GetValues("order[0][dir]").FirstOrDefault();
int pageSize = length != null ? Convert.ToInt32(length) : 0;
int skip = start != null ? Convert.ToInt32(start) : 0;
int recordsTotal = 0;
var v = (from a in _db.AllRoles select a); //Table contains only two Columns - Id and Name
//SORT
if (!(string.IsNullOrEmpty(sortColumn) && string.IsNullOrEmpty(sortColumnDir)))
{
v = v.OrderBy(sortColumn + " " + sortColumnDir);
}
recordsTotal = v.Count();
var data = v.Skip(skip).Take(pageSize).ToList();
return Json(new { draw = draw, recordsFiltered = recordsTotal, recordsTotal = recordsTotal, data = data }, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
throw;
}
}
The issue is due to column difference may be but I don't know how to solve it as implementing ServerSide Datatable for first time.
Thanks in advance.
I modified the below section in my Code to solve the Error.
"aoColumns": [{
"mDataProp": "Id"
}, {
"mDataProp": "Name"
}, {
"mDataProp": "Actions",
"defaultContent": '<i class="fa fa-pencil"></i> '
}],
I added defaultContent for that column as it is not getting values from Database/Sp.
P.S. Answer provided by #dee is also correct and will solve the error. Both are the Solutions to this Question.
The link in the error message provides very good information about what is the problem. You have specified three columns for the DataTable function but as you write in the comment Table contains only two Columns - Id and Name.
"aoColumns": [{
"mDataProp": "Id"
}, {
"mDataProp": "Name"
}, {
"mDataProp": "Actions"
}],
The Resolution section of the document tells what is needed to do:
If using columns ensure that you have specified exactly the number of columns that are present in the HTML for the table.
So you will need the transform the result of the query into another class which will have additional property for Actions. HTH
When a user clicks on a row from a list in the UI (html table tblAllCert), I have a click event that fires to populate another observable array that should populate a second html table (tblCertDetails). My click event fires, I can see data come back, and i can see data in the observable array, but my view does not update.
Here is an overview of the sequence in the code-
1. user clicks on row from html table tblAllCert, which fires selectThing method.
2. In viewmodel code, selectThing passes the row data from the row selected to the GetCertificateDetails(row) method.
3. GetCertificateDetails(row) method calls getCertDetails(CertificateDetails, source) function on my data service (the data service gets data from a web api). getCertDetails(CertificateDetails, source) function returns an observable array.
4. once the data is returned to selectThing, CertificateDetails observable array is populated. It does get data to this point.
Step 5 should be updating the UI (specifically tblCertDetails html table), however the UI does not get updated.
Here is my view-
<table id="tblAllCert" border="0" class="table table-hover" width="100%">
<tbody data-bind="foreach: allCertificates">
<tr id="AllCertRow" style="cursor: pointer" data-bind="click: $parent.selectThing, css: { highlight: $parent.isSelected() == $data.lwCertID }">
<td>
<ul style="width: 100%">
<b><span data-bind=" text: clientName"></span> (<span data-bind=" text: clientNumber"></span>) <span data-bind=" text: borrowBaseCount"></span> Loan(s) </b>
<br />
Collateral Analyst: <span data-bind=" text: userName"></span>
<br />
Certificate: <span data-bind="text: lwCertID"></span> Request Date: <span data-bind=" text: moment(requestDate).format('DD/MMM/YYYY')"></span>
</td>
</tr>
</tbody>
</table>
<table id="tblCertDetails" border="0" class="table table-hover" width="100%">
<tbody data-bind="foreach: CertificateDetails">
<tr id="Tr1" style="cursor: pointer">
<td>
<ul style="width: 100%">
<b>Loan: <span data-bind=" text: loanNum"></span>
</td>
</tr>
</tbody>
</table>
My viewmodel-
define(['services/logger', 'durandal/system', 'durandal/plugins/router', 'services/CertificateDataService'],
function (logger, system, router, CertificateDataService) {
var allCertificates = ko.observableArray([]);
var myCertificates = ko.observableArray([]);
var isSelected = ko.observable();
var serverSelectedOptionID = ko.observable();
var CertificateDetails = ko.observableArray([]);
var serverOptions = [
{ id: 1, name: 'Certificate', OptionText: 'lwCertID' },
{ id: 2, name: 'Client Name', OptionText: 'clientName' },
{ id: 3, name: 'Client Number', OptionText: 'clientNumber' },
{ id: 4, name: 'Request Date', OptionText: 'requestDate' },
{ id: 5, name: 'Collateral Analyst', OptionText: 'userName' }
];
var activate = function () {
// go get local data, if we have it
return SelectAllCerts(), SelectMyCerts();
};
var vm = {
activate: activate,
allCertificates: allCertificates,
myCertificates: myCertificates,
CertificateDetails: CertificateDetails,
title: 'Certificate Approvals',
SelectMyCerts: SelectMyCerts,
SelectAllCerts: SelectAllCerts,
theOptionId: ko.observable(1),
serverOptions: serverOptions,
serverSelectedOptionID: serverSelectedOptionID,
SortUpDownAllCerts: SortUpDownAllCerts,
isSelected: isSelected,
selectThing: function (row, event) {
CertificateDetails = GetCertificateDetails(row);
isSelected(row.lwCertID);
}
};
serverSelectedOptionID.subscribe(function () {
var sortCriteriaID = serverSelectedOptionID();
allCertificates.sort(function (a, b) {
var fieldname = serverOptions[sortCriteriaID - 1].OptionText;
if (a[fieldname] == b[fieldname]) {
return a[fieldname] > b[fieldname] ? 1 : a[fieldname] < b[fieldname] ? -1 : 0;
}
return a[fieldname] > b[fieldname] ? 1 : -1;
});
});
return vm;
function GetCertificateDetails(row) {
var source = {
'lwCertID': row.lwCertID,
'certType': row.certType,
}
return CertificateDataService.getCertDetails(CertificateDetails, source);
}
function SortUpDownAllCerts() {
allCertificates.sort();
}
function SelectAllCerts() {
return CertificateDataService.getallCertificates(allCertificates);
}
function SelectMyCerts() {
return CertificateDataService.getMyCertificates(myCertificates);
}
});
And releveant excerpt from my data service-
var getCertDetails = function (certificateDetailsObservable, source) {
var dataObservableArray = ko.observableArray([]);
$.ajax({
type: "POST",
dataType: "json",
url: "/api/caapproval/CertDtlsByID/",
data: source,
async: false,
success: function (dataIn) {
ko.mapping.fromJSON(dataIn, {}, dataObservableArray);
},
error: function (error) {
jsonValue = jQuery.parseJSON(error.responseText);
//jError('An error has occurred while saving the new part source: ' + jsonValue, { TimeShown: 3000 });
}
});
return dataObservableArray;
}
Ideas of why the UI is not updating?
When you're using Knockout arrays, you should be changing the contents of the array rather than the reference to the array itself. In selectThing, when you set
CertificateDetails = GetCertificateDetails(row);
the new observableArray that's been assigned to CertificateDetails isn't the same observableArray that KO bound to your table.
You're very close to a solution, though. In your AJAX success callback, instead of creating a new dataObservableArray and mapping into that, you can perform the mapping directly into parameter certificateDetailsObservable. ko.mapping.fromJSON should replace the contents of the table-bound array (you would also get rid of the assignment in selectThing).