Update Datatables column searching dropdownbox after ajax load - datatables

I added a filtering function with Individual column searching (https://datatables.net/examples/api/multi_filter_select.html) to my datatable which is working fine.
This table also has a button to reload table data. The button triggers code like:
table.ajax.url("foo").load();
It updates table data correctly. Now, I want to update searching dropdown box with new column data. I want to empty dropdown box something like select.empty() then fill the box, but not sure how. I think this update process should be written in "rowCallback".

Summary
To rebuild the drop-downs after each ajax call, here is one approach:
Instead of using the DataTables ajax option, you can fetch the data using a jQuery ajax call, outside of DataTables.
Use the jQuery done function populate the table, and re-build the drop-downs after each ajax call.
This approach ensures that the ajax data has been fetched before any additional processing takes place.
Walkthrough
Assume we have a button like this:
<button type="button" onclick="fetchData();">Reload Data</button>
And a HTML table like this:
<table id="example" class="display" style="width:100%">
<tfoot>
<tr>
<th></th>
<th></th>
<th></th> <!-- you may need more footer cells in your table -->
</tr>
</tfoot>
</table>
Here is the related fetchData function, which clears all existing data, then re-populates the table with the newly fetched data:
function fetchData() {
$.ajax({
url: [your url goes here], // your URL here
context: document.body
}).done(function( data ) {
var table = $('#example').DataTable();
table.clear();
table.rows.add(data);
buildSelectLists();
table.draw();
});
}
The function to rebuild the select lists is identical to the logic from the DataTables example solution:
function buildSelectLists() {
$('#example').DataTable().columns().every(function() {
var column = this;
var select = $('<select><option value=""></option></select>')
.appendTo($(column.footer()).empty())
.on('change', function() {
var val = $.fn.dataTable.util.escapeRegex(
$(this).val()
);
column
.search(val ? '^' + val + '$' : '', true, false)
.draw();
});
column.data().unique().sort().each(function(d, j) {
select.append('<option value="' + d + '">' + d + '</option>')
});
});
}
Finally, the DataTable is defined in a "document ready" function:
$(document).ready(function() {
$('#example').DataTable({
// your options here - but no need for the ajax or data options
"initComplete": function() {
fetchData(); // make sure the table contains data, when it is created
}
});
});
Alternatively:
You can achieve a similar result by using the DataTables ajax option which makes use of a function:
Example (taken from the documentation here):
$('#example').dataTable( {
"ajax": function (data, callback, settings) {
callback(
JSON.parse( localStorage.getItem('dataTablesData') )
);
}
} );
I think in this case, it is a bit cleaner to keep the ajax call in its own separate function.

Related

Send Dynamic Table as Model back to the controller

I would like to send a dynamic table back to the controller by using its model.
Following situation. On the view I am creating a table with javascript which allows the users to insert different values. These values are picked from pre-defined comboboxes:
$(document).ready(function () {
$('#btn_insert').on('click', function ()
var table=document.getElementById("table_computerimport");
var table_len=(table.rows.length)-1;
var row = table.insertRow(table_len).outerHTML="<tr id='row"+table_len+"'><td id='computername_row"+table_len+"'>"+computername+"</td><td id='uuid_row"+table_len+"'>"+"HERE UUID"+"</td><td id='collection_row"+table_len+"'>"+" HERE Collection Name"+"</td><td><input type='button' value='Delete' class='delete' onclick='delete_row("+table_len+")'></td></tr>";
});
This is working fine, so that at least the table is created with n entries.
<div>
<button type="button" id="btn_insert" class="btn btn-primary">Insert</button>
<div >
<table class="table" id="table_computerimport">
<thead>
<tr>
<th>Hostname</th>
<th>MacAddress</th>
<th>GUID</th>
</tr>
</thead>
<tr>
</tr>
</table>
</div>
</div>
These entries I would like to pass back to the controller by using its model:
public IEnumerable<ComputerImportModel> TableComputerImport {get; set;}
#model ViewModels.ComputerImportViewModel
So I don’t want to loop though the list of objects and publish them in the view, I would like to create n records in one view at once and pass these back to the controller by using the model TableComputerImport.
I guess in Javascript somehow I have to say something like this: input asp-for="ComputerImport.hostname"
Update,
ok, I think I "solved" it. What I did is that I created an array with javascript out of the table and passed this by ajax to the controller. It was important that the array has the name attributes like the class does so that I can use this object in the constructor as a well defined object.
As I mentioed, I had to use an array which I send by AJAX to the controller. Because of the identical names in the table and in the model, everything goes its correct way:
var singlecomputerimport = [];
var headers = [];
$('#table_computerimport th').each(function(index, item) {
headers[index] = $(item).html();
});
$('#table_computerimport tr').has('td').each(function() {
var arrayItem = {};
$('td', $(this)).each(function(index, item) {
if(headers[index] !== undefined){
//console.log($(item));
//arrayItem[headers[index].toLowerCase()] = $(item).html();
arrayItem[headers[index].toLowerCase()] = $(item).attr('value');
}
});
singlecomputerimport.push(arrayItem);
});

Datatables doesn't populate when I do row.add().draw()

I have an app that I'm trying to migrate to using datatables. I'm stripping out as much of the underlying irrelevant code as I can here. Basically the app is one where a user makes a series of selections from dropdowns and based on those dropdowns it picks a json file, clears the table, runs through the elements in the json object, and adds rows to my table. At the end of all that it redraws.
To start off though I populate it with default data using th.e same operation. Either scenario produces the same result, but it somewhat explains why the initialization is in a separate IIFE
When I do that, I wind up with nothing. No js errors, no nothing. Just an empty datatable. Here's my relevant html code (I am simplifying it and obfuscating things, but nothing that should be relevant:
<table id="conf">
<thead>
<tr>
<th class="header">a</th>
<th class="header">b</th>
<th class="header">c</th>
<th class="header">d</th>
<th class="header">e</th>
<th class="header">f</th>
<th class="header">g</th>
</tr>
</thead>
</table>
And the javascript
(function() {
'use strict';
var date="20190101"
$(document).ready(function(){
var table = $('#conf').DataTable({
ordering:true,
paging:false
});
ConfTable.PopulateTable(date);
});
}());
var ConfTable = (function() {
'use strict';
var table = $('#conf').DataTable();
return{
PopulateTable:function(){
$.getJSON("data/conf/"+date+".json?_=" + new Date().getTime(),
function(data, tableParam){
table.clear().draw();
GenerateTable(data)
table.draw();
});
}
}
function GenerateTable(data) {
for(var i=0; i<data.dockets.length; i++){
addRow(data.dockets[i]);
}
}
function addRow(thisCase) {
table.row.add(
[
1,2,3,4,5,6,7
]
);
}
}());
I do intend to put actual data in the array eventually, but just to simplify for troubleshooting that is what I'm trying. I have also tried adding it as an array of arrays using table.rows.add, and that array would look like this:
[
[1,2,3,4,5,6,7],
[1,2,3,4,5,6,7],
[1,2,3,4,5,6,7],
...
]
I have also attempted to put an object around the wrapper:
{"data": [1,2,3,4,5,6,7]}
In all of these cases, the end result is an initialized datatable with the options I selected with no data in it, as if I had not performed the table.row.add operation. ("No data available in table") I've stepped through and confirmed that the code is being executed as intended, but I am stuck as to why I'm not getting any results back. If you need any additional information I'm happy to provide it. Thanks.
PROBLEM
The problem with your code is in the order of code execution.
Your table gets initialized in the ConfTable before it gets initialized in the ready event handler.
You're using function expressions ((function(){}())) which are executed in the order they are defined ( and before the document is ready). Therefore table fails to initialize in ConfTable and your row.add() method fails.
SOLUTION
Either retrieve table instance inside each function or pass table variable as an argument.
(function() {
'use strict';
$(document).ready(function(){
var table = $('#example').DataTable({
ordering:true,
paging:false
});
ConfTable.PopulateTable();
});
}());
var ConfTable = (function() {
'use strict';
return{
PopulateTable:function(){
var table = $('#example').DataTable();
table.clear().draw();
GenerateTable({
dockets: [[1,2,3,4,5,6]]
});
table.draw();
}
}
function GenerateTable(data) {
for(var i=0; i<data.dockets.length; i++){
addRow(data.dockets[i]);
}
}
function addRow(thisCase) {
var table = $('#example').DataTable();
table.row.add(thisCase);
}
}());
EXAMPLE
See this example for code and demonstration.

Datatables data refresh based on user input parameters

Goal
I need generate/refresh table from the input parameters from an user.
Iam using datatables (1.10.7) jquery plugin.
User input
Table
<table class="display" id="table1" cellspacing="0">
</table>
DataTable script
<script type="text/javascript">
$(document).ready(function() {
$('#table1').DataTable({
/* SERVER SIDE PROCESSING */
"serverSide": true,
"ajax":
{
"url": "Home/Search",
"type": "POST",
"data": {
'NumberType': '<NumberType>', /* How pass this from function ? */
'Number': '<Number>' /* How pass this from function ? */
}
}
});
} );
</script>
Question
1) How can I input returned value from function in ajax "data" parameter?
The Idea is that function will return NumberType and Number value from the textboxes in the POST request to server side process.
2) What event I can call to refresh the ajax server side source ?
Something like
$('#table1').DataTable.Reload()
Thank you

Meteor with DataTables: Meteor._atFlush TypeError

I'm trying to use DataTables (via mrt add datatables) with Meteor. I've variously tried adding the $('#mytableid').dataTable() to the Meteor.subscribe callback, Meteor.autorun, Meteor.startup, and Template.mytemplate.rendered -- all resulting in the following exception and a No data available in table message.
Any pointers?
Exception from Meteor._atFlush: TypeError: Cannot call method 'insertBefore' of null
at http://localhost:3000/packages/liverange/liverange.js?bc1d62454d1fefbec95201344b27a7a5a7356293:405:27
at LiveRange.operate (http://localhost:3000/packages/liverange/liverange.js?bc1d62454d1fefbec95201344b27a7a5a7356293:459:11)
at LiveRange.replaceContents (http://localhost:3000/packages/liverange/liverange.js?bc1d62454d1fefbec95201344b27a7a5a7356293:403:17)
at http://localhost:3000/packages/spark/spark.js?c202b31550c71828e583606c7a5e233ae9ca50e9:996:37
at withEventGuard (http://localhost:3000/packages/spark/spark.js?c202b31550c71828e583606c7a5e233ae9ca50e9:105:16)
at http://localhost:3000/packages/spark/spark.js?c202b31550c71828e583606c7a5e233ae9ca50e9:981:9
at http://localhost:3000/packages/deps/deps-utils.js?f3fceedcb1921afe2b17e4dbd9d4c007f409eebb:106:13
at http://localhost:3000/packages/deps/deps.js?1df0a05d3ec8fd21f591cfc485e7b03d2e2b6a01:71:15
at Array.forEach (native)
at Function._.each._.forEach (http://localhost:3000/packages/underscore/underscore.js?47479149fe12fc56685a9de90c5a9903390cb451:79:11)
Update: Potentially related to this issue, for which the best solution found was to call dataTable() for each row -- not ideal in that case, and potentially catastrophic in mine given the very large number of rows.
Dror's answer is the right start for sure. Here's the best practice the way I see it currently:
HTML
<template name="data_table">
{{#constant}}
<table class="table table-striped" id="tblData">
</table>
{{/constant}}
</template>
JS:
Template.data_table.created = function() {
var _watch = Collection.find({});
var handle = _watch.observe({
addedAt: function (doc, atIndex, beforeId) {
$('#tblData').is(":visible") && $('#tblData').dataTable().fnAddData(doc);
}
, changedAt: function(newDoc, oldDoc, atIndex) {
$('#tblData').is(":visible") && $('#tblData').dataTable().fnUpdate(newDoc, atIndex);
}
, removedAt: function(oldDoc, atIndex) {
$('#tblData').is(":visible") && $('#tblData').dataTable().fnRemove(atIndex);
}
});
};
Template.data_table.rendered = function () {
if (!($("#tblData").hasClass("dataTable"))) {
$('#tblStudents').dataTable({
"aaSorting": []
, "sDom": "<'row-fluid'<'span6'l><'span6'f>r>t<'row-fluid'<'span6'i><'span6'p>>"
, "sPaginationType": "bootstrap"
, "oLanguage": {
"sLengthMenu": "_MENU_ records per page"
}
, "aoColumns": [
{ "sTitle": "First Data", "mData": function (data) { return data.firstData },
]
});
$('#tblData').dataTable().fnClearTable();
$('#tblData').dataTable().fnAddData(Collection.find().fetch());
}
};
Since Meteor is reactive, you need to create an empty table first and then add the rows.
Create the table:
$('#myTable').dataTable( {
"aoColumns": cols, //the column titles
"sDom": gridDom
} );
Add rows should look something like:
var myCursor = myCollection.find({});
var handle = myCursor.observe({
added: function (row) {
//Add in here the data to the table
},
});
The answered approach still stands but requires some changes after two years.
There is no such thing as constant anymore.
Instead of an empty table, put the thead but NOT the tbody. This way, columns are defined properly.
Put the observer in the onRendered (not rendered anymore) and NOT on onCreated (not created anymore). Otherwise, table is only filled at first creation and not when you come back.
clear and fill methods on the onRendered are not required.
In the observe method, make sure that you are adding an array and not an object. Otherwise fnAdd cannot render content
fnRemove is no more. Use fnDeleteRow instead
I'm not sure if the "is visible check" is required or not. There does not seem to be a problem when you remove it too, so I removed it.
With the above remarks, here is the code:
Template.creamDealsList.onRendered(function () {
if (!($("#tblData").hasClass("dataTable"))) {
$('#tblStudents').dataTable({
// Those configuration are not really important, use it as they are convenient to you
"aaSorting": []
, "sDom": "<'row-fluid'<'span6'l><'span6'f>r>t<'row-fluid'<'span6'i><'span6'p>>"
, "sPaginationType": "bootstrap"
, "oLanguage": {
"sLengthMenu": "_MENU_ records per page"
}
, "aoColumns": [
{ "sTitle": "First Data", "mData": function (data) { return data.firstData },
]
});
}
var _watch = Collection.find({});
var convertDocToArray = function (doc) {return [doc._id, doc.name]}
var handle = _watch.observe({
addedAt: function (doc, atIndex, beforeId) {
$('#tblData').dataTable().fnAddData(convertDocToArray(doc));
}
, changedAt: function(newDoc, oldDoc, atIndex) {
$('#tblData').dataTable().fnUpdate(convertDocToArray(newDoc), atIndex);
}
, removedAt: function(oldDoc, atIndex) {
$('#tblData').dataTable().fnDeleteRow(atIndex);
}
});
})
And simple HTML would be like:
<template name="data_table">
<table class="table table-striped" id="tblData">
<thead>
<th>Id</th>
<th>Name</th>
</thead>
</table>
</template>
This worked at least for me. I'm not seeing flush errors anymore. And also, when you render later, sometimes, there was a no data available message in the datatable and that disappeared as well.
Here's my implementation of dataTables in Meteor, using CoffeeScript and HTML. It works, and it's simpler, but may be less efficient than the existing answers for large tables (it's quick in my case though).
Template Function
Template.dashboard.rendered = ->
# Tear down and rebuild datatable
$('table:first').dataTable
"sPaginationType": "full_numbers"
"bDestroy": true
HTML
<template name="dashboard">
<table class="table table-bordered" id="datatable_3" style="min-width:1020px">
<thead>
<!-- code removed to save space -->
</thead>
<tbody id="previous-jobs-list">
{{#each jobs}}
{{> jobRow}}
{{/each}}
</tbody>
</table>
</template>
<template name="jobRow">
<tr>
<td>{{number}}</td>
<td>{{normalTime date}}</td>
<!-- more code removed to save space -->
</tr>
</template>
Where dashboard.jobs is a simple .find(...) on the collection.

Modify dynamically added content

I have used the search.. and tried many things.. but I just dont get it to work. Here is what I do:
On change of a select, I request some Data, in JSON-Format.
After that, I call a function:
if (result.events) {
$('#filter_result').trigger('put_result', result);
}
In put_results() I build the HTML:
$('#filter_result').bind('put_result', function (e, data) {
html = '<h1 class="header_dates">Termine</h1>';
// and some more
$(this).html(html).stop(true, true).stop(true, true).slideDown();
}
What I want to do now, is modify part of the #filter_result HTML. I want to look in one that was build, and get the value.
The HTML markup that is inserted looks like this:
<table>
<thead>
<tr>
<th>Großraum</th>
<th>Thema</th>
<th>Termin</th>
<th>Anmeldung</th>
</tr>
</thead>
<tbody>
<tr class="last">
<td class="key_0">Berlin</td>
<td class="key_1">273</td>
<td class="key_2">27.04.2012</td>
<td class="key_4">Jetzt anmelden</td>
</tr>
</tbody>
</table>
To do that, I have written a function that gets called just after the "trigger" line. I thought about something like:
function unsetlink(){
if(Date.parse($('.key_2').live().HTML()).equals(Date.today().add(2).days())) {
alert('false');
}
else {
alert('true');
}
}
I use date.js for date comparison. The functions logic is working, thats not the problem. Its just that I think i'm using live at the wrong place or something like that. I just cant find an answer.. Anything that could help is welcome.
Thank you.
Given your updates, and making the assumption that the content exists when you call it, the function should be:
function unsetlink() {
if (Date.parse($('.key_2').text()).equals(Date.today().add(2).days())) {
alert('false');
} else {
alert('true');
}
}
Here is a hacked together test for the function: http://jsfiddle.net/MarkSchultheiss/86mDa/
EDIT2: Here is an example of processing multiple injected content groups: http://jsfiddle.net/MarkSchultheiss/86mDa/3/
Here is the code in that example:
function unsetlink(daysadvance) {
$('.key_2').each(function() {
if (Date.parse($(this).text()).equals(Date.today().add(daysadvance).days())) {
$(this).addClass('showFalse');
$('#me').text($(this).text());//this is just to show what we are processing currnetly
alert('false');
} else {
$(this).addClass('showTrue');
alert('true');
}
})
}
$('#me').after('<table><thead><tr><th>Großraum</th><th>Thema</th><th>Termin</th><th>Anmeldung</th></tr></thead><tbody><tr class="last"><td class="key_0">Berlin</td><td class="key_1">273</td><td class="key_2">27.04.2012</td><td class="key_4">register</td></tr><tr class="last"><td class="key_0">Berlin</td><td class="key_1">273</td><td class="key_2">28.04.2012</td><td class="key_4">register</td></tr></tbody></table>');
var daysadvance = 3;
unsetlink(daysadvance);
I think you want to access some elements that will be added to the DOM inside the custom event put_result.
If that is the case, then I suggest you write a callback function to the put_result event which will be called after you append to DOM. See below,
$('#filter_result').bind('put_result', function (e, data, callback) {
html = '<h1 class="header_dates">Termine</h1>';
// and some more
$(this).html(html).stop(true, true).slideDown(); //removed one .stop(true, true)
callback(); //invoke the callback at the end or when needed accordingly
});
And in the trigger,
//unsetlink is your callback function - Change it to an inner function
//if you want to do more and then call unsetlink at the end.
$('#filter_result').trigger('put_result', [result, unsetlink]);
And change your unsetlink function as
function unsetlink(){
if(Date.parse($('.key_2').text()).equals(Date.today().add(2).days())) {
alert('false');
} else {
alert('true');
}
}