DataTables footerCallback and contenteditable - javascript

I have a DataTable that sums over each column using footerCallback. This works perfectly given the data in each column, but I also want to add the ability to change each cell's value that is being summed. I've tried adding "contenteditable" to those cells, but making a change does not affect the sum in the footer.
Here is a simple jsfiddle showing the behavior I'm experiencing: https://jsfiddle.net/rantoun/552y9j90/6/
HTML:
<table id="table1">
<thead>
<tr>
<th>Fruit</th>
<th># Eaten</th>
<th># Remaining</th>
</tr>
</thead>
<tfoot>
<tr>
<th align="right">Count</th>
<th align="left"></th>
<th align="left"></th>
</tr>
</tfoot>
<tbody>
<tr>
<td>Apples</td>
<td contenteditable>3</td>
<td contenteditable>8</td>
</tr>
<tr>
<td>Oranges</td>
<td contenteditable>6</td>
<td contenteditable>5</td>
</tr>
<tr>
<td>Bananas</td>
<td contenteditable>2</td>
<td contenteditable>9</td>
</tr>
</tbody>
</table>
jQuery:
$("#table1").DataTable({
"paging": false,
"searching": false,
"info": false,
"footerCallback": function ( row, data, start, end, display ) {
var columns = [1, 2];
var api = this.api();
_.each(columns, function(idx) {
var total = api
.column(idx)
.data()
.reduce(function (a, b) {
return parseInt(a) + parseInt(b);
}, 0)
$('tr:eq(0) th:eq('+idx+')', api.table().footer()).html(total);
})
}
});
I have also found the DataTables editor (https://editor.datatables.net/examples/inline-editing/simple), which would be perfect for this situation - but it is not open source. Any ideas on how to mimic this inline editing functionality is welcome. I would like to avoid doing this with modals. Any help is appreciated!

Here is an answer that allows you to edit in place with contenteditable.
Note that this requires the dataTables KeyTable plugin
Working Fiddle here
/* Note - requires datatable.keys plugin */
var table = $("#table1").DataTable({
"keys": true,
"paging": false,
"searching": false,
"info": false,
"footerCallback": function ( row, data, start, end, display ) {
var columns = [1, 2];
var api = this.api();
_.each(columns, function(idx) {
var total = api
.column(idx)
.data()
.reduce(function (a, b) {
return parseInt(a) + parseInt(b);
}, 0)
$('tr:eq(0) th:eq('+idx+')', api.table().footer()).html(total);
})
}
});
// keys introduces the key-blur event where we can detect moving off a cell
table
.on( 'key-blur', function ( e, datatable, cell ) {
// The magic part - using the cell object, get the HTML node value,
// put it into the dt cell cache and redraw the table to invoke the
// footer function.
cell.data( $(cell.node()).html() ).draw()
} );
Note - I can foresee that you may get non-numeric data entered. You will have to police that in the key-blur event where you can test the dom node value and act accordingly.

Related

Adding select menu to multiple tables with jquery data tables pluglin

I've got two tables (table1,table2) that are structurally the same, but fed from different sources. Both tables share the class 'display' which I use to initialise the plugin for both tables. This works great for the most part, however the column filters select menu for table2 is duplicated on table1.
I've tried to fix this by using 'this' keyword to indicate a particular instance of the toolbar each of the filters need applying to, but not had much success.
The code that works is:
HTML:
<table id="table1" class="display table-striped table-borded table-hover" cellspacing="0" width="100%">
<thead>
<tr>
<th>Report Name</th>
<th>Year</th>
<th>Montly Snapshot</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Date of Last Refresh --var from warehouse-- </th>
</tr>
</tfoot>
</table>
<table id="table2" class="display table-striped table-borded table-hover" cellspacing="0" width="100%">
<thead>
<tr>
<th>Report Name</th>
<th>Year</th>
<th>Montly Snapshot</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Date of Last Refresh --var from warehouse-- </th>
</tr>
</tfoot>
</table>
JS:
//initialise data tables plugin and apply custom settings.
var reportstable = $('table.display').DataTable({
"sDom": '<"toolbar">Bfrtlp',
"searchCols": [
null, {
'sSearch': 'Current'
}
],
language: {
search: "_INPUT_",
searchPlaceholder: "Search for a Report..."
},
// create select form controls
initComplete: function() {
this.api().columns([1, 2]).every(function() {
var column = this;
var select = $('<select><option value=""></option></select>')
.appendTo($('.toolbar'))
.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>')
if ($('#info').css('display') == 'block') {
$("#reports_wrapper").css('display', 'none');
}
// add bootstrap classes and placeholder property to form controls to add style
$('.toolbar select').addClass('selectpicker');
$('.dt-button').addClass('btn btn-danger').removeClass('dt-button');
$('.toolbar select').attr('id', 'year');
$('#year').prop('title', 'Financial Year');
$("#year option:contains('Current')").remove();
$('.toolbar select:nth-of-type(2)').attr('id', 'month');
$('#month').prop('title', 'Monthy Snapshot');
$("#month option:contains('undefined')").remove();
});
});
},
// create clear filter button
buttons: [
{
text: 'Clear Filters',
action: function(e, dt, node, config) {
$('select').val('').selectpicker('refresh');
// set Current Year as default filter
reportstable
.search('')
.columns([1]).search('Current')
.columns([2]).search('')
.draw();
}
}
],
//hide specified columns
"columnDefs": [
{
"targets": [1],
"visible": false,
"searchable": true
}, {
"targets": [2],
"visible": false,
"searchable": true
}
]
});
I've updates your jsfiddle and created a new jsfiddle. It's now appending 2 select menus in both tables's wrapper div. I've found why it's appending 4 select menus instead of 2 on the table1's wrapper div. It's because of this code: .appendTo($('.toolbar')). When initComplete callback function is called for table1 there is only one div with class=toolbar, and when the initComplete callback function is called for table2 there is two div with class=toolbar, one in table1's wrapper div and one in table2's wrapper div. that's why on table2's initComplete callback function it append select menus to all divs with class=toolbar. We should rather append to the current table wrapper's div with class=toolbar.

jQuery Real Time Search - Remove Table Header Too

I am using the regular expression search code at this link, to allow real time search through a rather large table being populated server-side via php.
With a slight twist to the scenario describe in the above link, I am using table header tags to group (label) chunks of table row's together. I am preventing these table header row's from disappearing with the rest of the table row's so that when searching, the results are still nested in their group.
I would like the table header row's to disappear too, but only when there are no table row's between it and the next table header row. I'm not sure if counting row's will work, since the row's aren't gone, they're just hidden.
As an example, this is how my table is laid out:
HTML:
<table>
<tr>
<th colspan="2">Group 1</th>
</tr>
<tr class="searchable">
<td>Record 1</td>
<td>Record 2</td>
</tr>
<tr class="searchable">
<th>Group 2</th>
</tr>
<tr>
<td>Record 3</td>
<td>Record 4</td>
</tr>
</table>
jQuery:
$( document ).ready(function() {
var $rows = $('tr.searchable');
$('#search').keyup(function(e) {
if (e.keyCode == 27) { $(this).val("") }
var val = '^(?=.*\\b' + $.trim($(this).val()).split(/\s+/).join('\\b)(?=.*\\b') + ').*$',
reg = RegExp(val, 'i'),
text;
$rows.show().filter(function() {
text = $(this).text().replace(/\s+/g, ' ');
return !reg.test(text);
}).hide();
});
});
All help is appreciated!! Thanks!
I solved my dilemma by adding another class (unique to each group/label) and within the '.keyup()' section counting the ':visible' rows of each class. Once the number of rows in each class dropped below a specified number, I used '.hide()' in an if statement to make the table headers disappear. The else side of the if statement used '.show()' to bring the table headers back if their classes count rose above the specified number.
I also encountered this issue, it was solved by just targetting the body part for the .filter instead of the table as a whole.
$("#SearchPermissions").on("keyup", function() {
var value = $(this).val().toLowerCase();
$("#contentPart tr").filter(function() {
$(this).toggle($(this).text().toLowerCase().indexOf(value) > -1)
});
});
Mind the $("#contentPart tr") part of the code.. this refers to the
This way the header is excluded from the search, thus will still be displayed while filtering table data

Why won't DataTable redraw after manual search

I have a table which I would like to search manually. After looking at the API, I see that I can just do:
table.search(somestring).draw();
and this should search the table and redraw it, or so I assume.
$(document).ready(function(){
var table = $('table').DataTable({
searching: false,
paging: false,
info: false
});
// add a bunch of rows
for(var i=0; i<20; i++){
table.row.add([
"test "+i, "best "+i, "rest "+i
]);
}
// draw the table
table.draw();
// why won't table redraw with only rows containing 15?
table.search('15').draw();
// this doesn't work either
/*
$('input').on( 'keyup click', function () {
filterColumn( 1 ); // filter only first column
} );
*/
});
function filterColumn ( i ) {
console.log( $('input').val() );
$('table').DataTable().column( i ).search(
$('input').val()
).draw();
}
HTML:
<input>
<table>
<thead>
<tr>
<th>column 1</th>
<th>column 2</th>
<th>column 3</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
Plunker: http://plnkr.co/edit/BlwWWw7mlpde4vKqoX2q?p=preview
You have searching functionality disabled with searching: false, see searching option for more details.
You need to enable searching with searching: true or by removing this option in order for search() method to work.
If you just want to hide filtering control, use dom: 'lrtip', see dom option for more details.

jQuery Table sorter Sorting of Float distance

I am using tablesorter to sort the table content. My table is as below.
<table class="results" border="1">
<thead>
<tr>
<th class="header">No</th>
<th class="header">Distance</th>
<th class="header">Diagnostic Fee</th>
<th class="header">Regular Price </th>
<th class="header">Company Price</th>
<th class=""> </th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td class="distance"><a>16.50 kms.</a></td>
<td class="brand"><a>Credited</a></td>
<td><a>$5</a></td>
<td><a>$5<small>after 5% cash back</small></a></td>
</tr>
<tr>
<td>2</td>
<td class="distance"><a>6.30 kms.</a></td>
<td class="brand"><a>Credited</a></td>
<td><a>$8</a></td>
<td><a>$8<small>after 5% cash back</small></a></td>
</tr>
<tr>
<td>3</td>
<td class="distance"><a>10.25 kms.</a></td>
<td class="brand"><a>Credited</a></td>
<td><a>$2</a></td>
<td><a>$2<small>after 5% cash back</small></a></td>
</tr>
</tbody>
</table>​
I want to sort the table using distance and price.
I am facing difficulty is solving table with distance, because distance is displayed in alphanumeric like "12 kms". So, The table is not getting sorted.
Can anyone advise how to parse the content by taking only digits?
here is the jsfiddle demo
Tablesorter provides a way of defining additional parsers for cells in which it can't obtain data correctly. You need to define 2 parsers for the 2 columns you're interested in.
So you might have:
$.tablesorter.addParser({
id: 'distance',
is: function(s) {
return false;
},
format: function(text, table, cell) {
return parseFloat(text.replace('kms.', ''));
},
type: 'numeric'
});
for distance, and:
$.tablesorter.addParser({
id: 'price',
is: function(s) {
return false;
},
format: function(text, table, cell) {
return parseFloat(text.replace('$', ''));
},
type: 'numeric'
});
for price. You then tell tablesorter which columns to use the parses on, so:
$("table").tablesorter({
debug:false,
sortList: [[0, 0], [2, 0]],
headers: {
1: {
sorter: 'distance'
},
3: {
sorter: 'price'
}
}
});​
Tablesorte has an option 'textExtraction', so you can define a function to go through the text before sorting. Example:
$("table").tablesorter({
debug:false,
sortList: [[0, 0], [2, 0]],
textExtraction: function(node) {
var $node = $(node)
var text = $node.text();
if ($node.hasClass('distance')) {
text = text.replace('kms', '');
};
return text;
}
});
Updated fiddle

Sorting tbody elements by column value with jQuery

I am trying to sort a table - so when a user clicks on the table heading, it will sort in ascending/descending order. I've got it to the point where I can sort the table based on the column value. However, I have groupings of table rows (two rows per table body), and I want to sort the columns based on the values in the columns of the first row of each table body, but when it reorders the table, it want it to reorder the table bodies, not the table rows.
<table width="100%" id="my-tasks" class="gen-table">
<thead>
<tr>
<th class="sortable"><p>Name</p></th>
<th class="sortable"><p>Project</p></th>
<th class="sortable"><p>Priority</p></th>
<th class="sortable"><p>%</p></th>
</tr>
</thead>
<tbody>
<tr class="sortable-row" id="44">
<td><p>dfgdf</p></td><td><p>Test</p></td>
<td><p>1</p></td><td><p>0</p></td>
</tr>
<tr>
<td></td>
<td colspan="3"><p>asdfds</p></td>
</tr>
</tbody>
<tbody>
<tr class="sortable-row" id="43">
<td><p>a</p></td>
<td><p>Test</p></td>
<td><p>1</p></td>
<td><p>11</p></td>
</tr>
<tr>
<td></td>
<td colspan="3"><p>asdf</p></td>
</tr>
</tbody>
<tbody>
<tr class="sortable-row" id="40">
<td><p>Filter Tasks</p></td>
<td><p>Propel</p></td>
<td><p>10</p></td>
<td><p>10</p></td>
</tr>
<tr>
<td></td>
<td colspan="3"><p>Add a button to filter tasks.</p></td>
</tr>
</tbody>
</table>
With the following javascript:
jQuery(document).ready(function () {
jQuery('thead th').each(function(column) {
jQuery(this).addClass('sortable').click(function() {
var findSortKey = function($cell) {
return $cell.find('.sort-key').text().toUpperCase() + ' ' + $cell.text().toUpperCase();
};
var sortDirection = jQuery(this).is('.sorted-asc') ? -1 : 1;
var $rows = jQuery(this).parent().parent().parent().find('.sortable-row').get();
jQuery.each($rows, function(index, row) {
row.sortKey = findSortKey(jQuery(row).children('td').eq(column));
});
$rows.sort(function(a, b) {
if (a.sortKey < b.sortKey) return -sortDirection;
if (a.sortKey > b.sortKey) return sortDirection;
return 0;
});
jQuery.each($rows, function(index, row) {
jQuery('#propel-my-tasks').append(row);
row.sortKey = null;
});
jQuery('th').removeClass('sorted-asc sorted-desc');
var $sortHead = jQuery('th').filter(':nth-child(' + (column + 1) + ')');
sortDirection == 1 ? $sortHead.addClass('sorted-asc') : $sortHead.addClass('sorted-desc');
jQuery('td').removeClass('sorted').filter(':nth-child(' + (column + 1) + ')').addClass('sorted');
});
});
});
You need to sort the tbody elements, not the row elements. You said that yourself in your description of the problem, but your code actually sorts rows, not tbodies.
A secondary problem is that your sort treats everything as a string, which breaks when sorting 1-digit numeric strings ("2") against two-digit strings ("10").
To fix, replace this:
var $rows = jQuery(this).parent().parent().parent()
.find('.sortable-row').get();
jQuery.each($rows, function(index, row) {
row.sortKey = findSortKey(jQuery(row).children('td').eq(column));
});
with this:
var $tbodies = jQuery(this).parent().parent().parent()
.find('.sortable-row').parent().get();
jQuery.each($tbodies, function(index, tbody) {
var x = findSortKey(jQuery(tbody).find('tr > td').eq(column));
var z = ~~(x); // if integer, z == x
tbody.sortKey = (z == x) ? z : x;
});
And then replace $rows with $tbodies throughout your script, and row with tbody.
Example:
http://jsbin.com/oxuva5
I highly recommend the jQuery plugin http://tablesorter.com/ instead of rolling your own.
It's fully featured and well supported.

Categories