Related
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.
I've got a table in my page wrapped up in Datatables. The data is being grabbed from a php file perfectly and there is no problem with the code in that part. However, I've got a problem with buttons inside the table.
The followings are the columns inside my Datatables table:
columns: [
{"data": "id"},
{"data": "name"},
{data: null, render: function(data, type, row) {
return '<span class="fa fa-pen"></span>'
}},
],
As you can see, I have defined three columns, the first of which is 'id', the second 'name' and the third a column including a button. My problem is related to this button. In fact, I want to call a function, for instance, edit(), whenever this button is clicked. The edit() function gets the value of 'name' (second column) as its parameter. Now the question is this: how can I pass the value of the second column to the function edit() when the button is clicked;
as a result, the onClick call of the third column shall be something like this: onclick="edit(name.val)"... I have left this onClick="" empty, because I don't know how to do this.
Millions of thanks in advance.
After dealing with the problem for a couple of hours, I came to know that with a simple javascript concatenation, the problem could be solved.
Previously, the block was like the following:
<span class="fa fa-pen"></span>
and I had problems with onclick call where I could not pass a parameter for the functions.
To solve the problem, I changed the block a little bit and used concatentation, and voila, the problem was gone.
This should turn into
return '<span class="fa fa-times-circle"></span>'
Here is a working example. It uses the data function described on this page. The relevant part is this:
{ "data": function ( row, type, val, meta ) {
return "the data in column 1 is '" + row[0] + "'";
}
}
In the three-column table (below), the third column is populated based on data from the first column.
Here is the full sample - you should be able to adapt this technique to your situation:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Animals</title>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.20/css/jquery.dataTables.min.css">
<link rel="stylesheet" type="text/css" href="https://datatables.net/media/css/site-examples.css">
</head>
<body>
<div style="margin: 20px;">
<table id="animals" class="display dataTable cell-border" style="width:100%">
<thead>
<tr><th>Animal</th><th>Collective Noun</th><th>Customized Data</th></tr>
</thead>
<tbody>
<tr><td>antelopes</td><td>herd</td><td></td></tr>
<tr><td>elephants</td><td>herd</td><td></td></tr>
<tr><td>hounds</td><td>pack</td><td></td></tr>
<tr><td>kittens</td><td>kindle</td><td></td></tr>
<tr><td>lions</td><td>pride</td><td></td></tr>
<tr><td>ravens</td><td>unkindness</td><td></td></tr>
<tr><td>whales</td><td>pod</td><td></td></tr>
<tr><td>zebras</td><td>herd</td><td></td></tr>
</tbody>
</table>
</div>
<script type="text/javascript">
$(document).ready(function() {
var table = $('#animals').DataTable({
"columns": [
null,
null,
{ "data": function ( row, type, val, meta ) {
//console.log(row);
//console.log(type);
//console.log(val);
//console.log(meta);
return "the data in column 1 is '" + row[0] + "'";
}
}
]
});
});
</script>
</body>
The (commented-out) console logging statements are there so you can take a closer look at how the function works, if you wish.
make use the edit method is placed outside jquery initiation in golobal level
function edit(){
//content
}
jQuery(document).ready(function() {
//data-tables script here
}
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.
For an implementation of Magnific Popup, I need to pass a post id to the ajax settings. The post id is stored in a data attribute of the element to which Magnific Popup is bound. I would like this to work:
html element:
<a data-id="412">Clicke me</a>
Javascript:
$('.element a').magnificPopup({
type: 'ajax',
ajax: {
settings: {
url: php_array.admin_ajax,
type: 'POST',
data: ({
action:'theme_post_example',
id: postId
})
}
}
});
Where postId is read from the data attribute.
Thanks in advance.
$('.element a').magnificPopup({
callbacks: {
elementParse: function(item){
postData = {
action :'theme_post_example',
id : $(item.el[0]).attr('data-id')
}
var mp = $.magnificPopup.instance;
mp.st.ajax.settings.data = postData;
}
},
type: 'ajax',
ajax: {
settings: {
url: php_array.admin_ajax,
type: 'POST'
}
}
});
Here is how to do it:
html:
<a class="modal" data-id="412" data-action="theme_post_example">Click me</a>
jquery:
$('a.modal').magnificPopup({
type: 'ajax',
ajax: {
settings: {
url : php_array.admin_ajax,
dataType : 'json'
}
},
callbacks: {
elementParse: function() {
this.st.ajax.settings.data = {
action : this.st.el.attr('data-action'),
id : this.st.el.attr('data-id')
}
}
},
parseAjax: function( response )
{
response.data = response.data.html;
}
});
php
function theme_post_example()
{
$id = isset( $_GET['id'] ) ? $_GET['id'] : false;
$html = '<div class="white-popup mfp-with-anim">';
/**
* generate your $html code here ...
*/
$html .= '</div>';
echo json_encode( array( "html" => $html ) );
die();
}
As this answer was the original question regarding inserting data into Magnific's ajax call, I'll post this here.
After many hours of trying to figure this out, you should know that if you're using a gallery with the ability to move between gallery items without closing the popup, using elementParse to set your AJAX data will fail when you visit an item after already viewing it (while the popup is still open).
This is because elementParse is wrapped up in a check that it makes detect if an item has already been 'parsed'. Here's a small explanation as to what happens:
Open gallery at item index 2.
Item has not been parsed yet, so it sets the parsed flag to true and runs the elementParse callback (in that order). Your callback sets the ajax options to fetch this item's data, all is well.
Move (right) to item index 3.
Same as above. The item has not been parsed, so it runs the callback. Your callback sets the data. It works.
Move (left) back to item index 2.
This time the item has been parsed. It skips re-parsing the item's element for assumed potential performance reasons.Your callback is not executed. Magnific's ajax data settings will remain the same as if it were item index 3.
The AJAX call is executed with the old settings, it returns with item index 3's data instead, which is rendered to the user. Magnific will believe it is on index 2, but it is rendering index 3's data.
To resolve this, you need to hook onto a callback which is always executed pre-ajax call, like beforeChange.
The main difference is that the current item isn't passed through into the callback. Fortunately, at this point, magnific has updated their pointers to the correct index. You need to fetch the current item's element by using:
var data = {}; // Your key-value data object for jQuery's $.ajax call.
// For non-closures, you can reference mfp's instance using
// $.magnificPopup.instance instead of 'this'.
// e.g.
// var mfp = $.magnificPopup.instance;
// var itemElement = mfp.items[mfp.index].el;
var itemElement = this.items[this.index].el;
// Set the ajax data settings directly.
if(typeof this.st.ajax.settings !== 'object') {
this.st.ajax.settings = {};
}
this.st.ajax.settings.data = data;
This answer can also be used as a suitable alternative to the currently highest voted, as it will work either way.
You may use open public method to open popup dynamically http://dimsemenov.com/plugins/magnific-popup/documentation.html#public_methods
postId = $(this).attr('data-id')
$(this) retrieve the current element (the link you clicked on), and attr the value of the specified attribute.
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.