I wrote a dataTables directive for AngularJS. Its working fine except that i trying to add an button to the row that removes an row with an ng-click.
In my opinion is that the problem occurs because the table row doesn't now the scope.
Can somebody help me out solving this problem.
jsFiddle Example: http://jsfiddle.net/A5Zvh/7/
My directive looks like this.
angular.module('DataTables', [])
.directive('datatable', function() {
return {
restrict: 'E',
transclude: true,
replace: true,
require: 'ngModel',
template: '<table></table>',
link: function(scope, element, attrs, model) {
var dataTable = null,
options;
var buttons = jQuery.parseJSON(attrs['buttons']) || null;
options = {
"bJQueryUI": false,
"sDom": "<'row-fluid'<'span4'l><'span8 filter' <'pull-right'T> <'pull-right'f>>r>t<'row-fluid'<'span6'i><'span6'p>>",
"sPaginationType": "bootstrap",
"oTableTools": {
}
};
if(_.has(attrs, 'datatableOptions')) {
jQuery.extend(true, options, scope.$eval(attrs['datatableOptions']));
}
scope.$watch(attrs.ngModel, function(data) {
if(data && _.size(data.aaData) > 0 && _.size(data.aoColumns) > 0) {
_.extend(options, scope.$eval(attrs.ngModel))
dataTable = $(element).dataTable(options);
dataTable.fnClearTable();
dataTable.fnAddData(data.aaData);
}
});
}
}
})
I'm using Angular-datatbles, and I was trying to dynamically add, Edit & Remove links to the datatble rows and display modal on ng-click;
This was the solution for my case;
$scope.dtOptions.withOption('fnRowCallback',
function (nRow, aData, iDisplayIndex, iDisplayIndexFull) {
$compile(nRow)($scope);
});
All the datatable binding code;
$scope.reloadData = function () {
$scope.dtOptions.reloadData();
};
$scope.dtColumnDefs = [
DTColumnDefBuilder.newColumnDef(2).renderWith(function (data, type, row) {
var html = '<i class="fa fa-pencil hidden-xs"></i> Edit' +
'<i class="fa fa-times hidden-xs"></i> Remove';
return html;
})
];
$scope.dtColumns = [
DTColumnBuilder.newColumn('name').withTitle('Name'),
DTColumnBuilder.newColumn('type').withTitle('Type'),
DTColumnBuilder.newColumn('id').withTitle(''),
];
$scope.dtOptions.withOption('fnRowCallback',
function (nRow, aData, iDisplayIndex, iDisplayIndexFull) {
$compile(nRow)($scope);
});
I solved this by going through each td and calling $compile. Using the datatable row callback function. Hope this helps.
options.fnCreatedCell = function (nTd, sData, oData, iRow, iCol) {
$compile(nTd)($scope);
}
//or row
options.fnCreatedRow = function( nRow, aData, iDataIndex ) {
$compile(nRow)($scope);
}
The delete function in your controller isn't called because AngularJS doesn't know anything about DataTables's insertion of those elements to the DOM, thus ngClick directive within those elements isn't compiled and linked. So change:
dataTable.fnAddData(data.aaData);
To
dataTable.fnAddData(data.aaData);
$compile(element)(scope);
And to inject $compile service:
.directive('datatable', function () {
To
.directive('datatable', function ($compile) {
And your delete function is broken in the jsFiddle, hope that's not the case in your actual project!
You might want to give a look at the first couple of Zdam's post on this Google Groups thread, especially to his/her two linked jsFiddles. I basically copied them and they work at a basic level. I have not tried yet to get some action starting from a click on a row.
I see that you implemented a slightly different approach, recreating the <table> HTML node altogether. Not sure if this is causing issues.
By the way, on the scope.$watch call I had to make sure there was a third parameter set to true, in order to make value comparison (instead of reference comparison) on the returned resource$ object. Not sure why you don't need that.
fnCreatedCell be supplied in aoColumns or fnCreatedRow supplied to mRender
1 )fnCreatedCell is column based
ex :
tableElement.dataTable({
"bDestroy": true,
oLanguage : {
sLengthMenu : '_MENU_ records per page'
},
aoColumnDefs: [
{
bSortable: false,
aTargets: [ -1,-2,-3 ],
"fnCreatedCell": function (nTd, sData, oData, iRow, iCol)
{
$compile(nTd)($scope)
}
}
],
2 ) fnCreatedRow is a 'top level' callback
tableElement.dataTable({
"bDestroy": true,
oLanguage : {
sLengthMenu : '_MENU_ records per page'
},
aoColumnDefs: [
{
bSortable: false,
aTargets: [ -1,-2,-3 ]
}
],
"fnCreatedRow": function( nRow, aData, iDataIndex ){
compile(nRow)(scope);
},
Related
so I'm trying to change the td class of my datatables elements dynamically. From my research i found fnRowCallback, it seems to be the solution but I can't get it to work. From 1 of the questions here at Stack I found:
"fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
for (var i in aData) {
// Check if a cell contains data in the following format:
// '[state] content'
if (aData[i].substring(0,1)=='[') {
// remove the state part from the content and use the given state as CSS class
var stateName= aData[i].substring(1,aData[i].indexOf(']'));
var content= aData[i].substring(aData[i].indexOf(']')+1);
$('td:eq('+i+')', nRow).html(content).addClass(stateName);
}
}
}
But this does not work for me, I get the error: Uncaught SyntaxError: Unexpected token, and my elements keep the "[class name] content" format. Here is my javascript function:
$('#tableId').DataTable({
"ajax" : "function.php",
"columns" : [
{
"data" : "id"
}, {
"data" : "firstElement"
}, {
"data" : "secondElement"
}, {
"data" : "thirdElement"
}],
"fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
for (var i in aData) {
// Check if a cell contains data in the following format:
// '[state] content'
if (aData[i].substring(0,1)=='[') {
// remove the state part from the content and use the given state as CSS class
var stateName= aData[i].substring(1,aData[i].indexOf(']'));
var content= aData[i].substring(aData[i].indexOf(']')+1);
$('td:eq('+i+')', nRow).html(content).addClass(stateName);
}
}
},
"iDisplayLength": 5,
"scrollX": true,
"orderFixed": [[ 0, "asc"]]
});
function.php gives me a JSON with all the fields, and in the ones I want the class altered I use "[class name] content" in the field. Any ideas on how to get it to work?
Thanks
Problem with your code is that aData would be an associative array, for example:
{ "id": 1, "firstElement": "[class] content" }
When you enumerate properties with for...in construct, i would hold property name (id, firstElement) and not numerical index.
Therefore you cannot use i to access cell within given row.
Another problem with your code is that it sets HTML content after it has been created. jQuery DataTables will be unaware that you've updated cell content.
I have a function that creates an instance of DataTables and for some reason it initializes n-times after destruction. I only noticed this because I add custom fields on init and they were multiplying. I could prevent that but that only deals with the symptom.
To clarify, after I "destroy: the instance and reinitialize it, to change the data source, if it's the second time it initializes twice. Three times if it's the 3rd time, etc.
I speculate that the table variable is part of the closure formed by the function because even if I set table = null the same thing happens.
How can I prevent this?
DataTables Function
/*Create a DataTable on tableElementID using pageUrl as the source*/
function ajaxLoadTable ( pageUrl, tableElementID ) {
window.table = $(tableElementID)
.on( 'init.dt', function () {
//The success function is used internally so it should NOT be overwritten, have to listen for this event instead
//Add our custom fields _length refers to an element generated datatables
if ( additionalElements.saveButton ) {
$(tableElementID + '_length').after('<div class="dataTables_filter"><button>Save All Edits</button></div>');
}
if ( additionalElements.selectState ) {
$(tableElementID + '_length').after('<div class="dataTables_filter"><label>Project State: <select name="projectState" style="width:auto;"><option>Select ...</option><option value="Active">Active</option><option value="Historical">Historical</option></select></label></div>');
}
if ( additionalElements.searchBox ) {
$(tableElementID + '_length').after('<div class="dataTables_filter"><label>Search:<input type="search" id="customSearch" style="width:auto;"></label></div>');
}
})
.DataTable({
"processing": true,
"serverSide": true,
"ajax":{
type: "POST",
url: pageUrl,
data: function ( additionalData ) {
$('.serverData').each( function( index, element ){
if( element.nodeName === "SELECT"){
additionalData[element.name.toUpperCase()] = element.options[element.selectedIndex].value;
return true; //return true is equivalent to continue for $.each
}
additionalData[element.name.toUpperCase()] = element.value;
});
},
dataType: "json"
},
"pageLength": 4,
"lengthMenu": [ 4, 8, 12, 16, 24 ],
"searchDelay": 1500,
"columnDefs":
{ "targets": 0,
"orderable": false,
"data": {
"_": "display"
}
}
});
}
Destruction Function
/*Load the selected project state*/
$('html').on( 'change' , '[name=projectState]' ,function(){
var currentState = $('option:selected', this).val();
$('#projectState').val(currentState);
//Remove the old table records and the datatables. Order matters, otherwise there is unsual behavior.
if( $.fn.DataTable.isDataTable( '#searchTable' ) ) {
window.table.destroy();
window.table = null;
}
$('.projectStateText').html( currentState );
//Get the new table records
ajaxLoadTable( *some undisclosed URL*, '#searchTable');
});
I'm following this link to implement server side pagination in jquery datatable.
I have index view which load partial view with jquery data table. This partial view calls asp.net-mvc controller and return json data which should be injected into jquery datatable. But on initial render of my index view I'm getting following error
DataTables warning: table id=dataTables-table - Cannot reinitialise DataTable
so I tried to add into partial view where jquery datatable is initialized
"bRetrieve": true,
but that not helped.
partial view jq. data table initialization looks like this
<script type="text/javascript">
$(document).ready(function () {
$('#dataTables-table').dataTable({
"bRetrieve": true,
"bProcessing": true,
"bServerSide": true,
"sAjaxSource": "/MyController/MyAction",
"sServerMethod": "POST",
"aoColumns": [
{ "mDataProp": "String A" },
{ "mDataProp": "String B" },
{ "mDataProp": "String C" }],
"fnRowCallback": function (nRow, aData, iDisplayIndex, iDisplayIndexFull) {
var birthday = new Date(parseInt(aData.Birthday.replace("/Date(", "").replace(")/", ""), 10));
$('td:eq(3)', nRow).html(birthday.getMonth() + 1 + "/" + birthday.getDate() + "/" + birthday.getFullYear());
}
});
});
</script>
What I'm missing here?
Update:
[HttpPost]
public JsonResult MyAction(JQueryDataTablesModel model)
{
int totalRecordCount;
int searchRecordCount;
var data = GetMyData(model.iDisplayStart, model.iDisplayLength, model.GetSortedColumns(),
out totalRecordCount, out searchRecordCount, model.sSearch);
return Json(new JQueryDataTablesResponse<MyObject>(data, totalRecordCount, searchRecordCount, model.sEcho));
}
Try adding option:
"destroy": true.
I am doing some post processing in datatable in fnRowCallback. But they are not being called when the table is redrawn. (i.e, when some event like changing the number of displayed rows are called from UI, the table is redrawn)
$(document).ready(function () {
var oTable = $('#data').dataTable({
"bJQueryUI": true,
"bProcessing": true,
"bServerSide": true,
"bSort": false,
"sAjaxSource": "query.php",
"sPaginationType": "full_numbers",
"aoColumns": [
null,
null,
null,
],
"fnRowCallback": function (nRow, aData, iDisplayIndex) {
$(nRow).attr("id", aData[4]);
return nRow;
},
"fnDrawCallback": function( oSettings ) {
// How do I call fnRowCallback here?
// losing post processing because it is not being called after a redraw
}
});
I think your attempt to look up the actual row via jquery in $(nRow) does not work. nRow contains the whole row. You should just is as namespace for the jquery selector (second parameter) to restrict it to this particular row.
Like so:
$("selector",nRow).jqueryaction()
This works for me:
Html:
<tr>
<td>a</td>
<td class="boldmetight">b</td>
</tr>
<tr>
<td class="boldmetight">c</td>
<td>d</td>
</tr>.. etc
And the table definition with a rowcallback that bolds every cell with a specific class (just for example):
var otable = $("#datatable").dataTable({
"fnRowCallback": function(nRow, aData, iDisplayIndex) {
$('.boldmetight', nRow).html('<b>' + $('.boldmetight', nRow).text() + '</b>');
}
});
Look at this working Plunker
I have a data table like this and would like to make my data table rows have knockout observable properties. What is the best approach to be able to data-bind:"click" on a row in a datatable. I have seen a datatables knockout binding but it doesnt seem to support ajax sources. Any ideas I tried using the foreach and template bindings to create the table and having datatables initialize it from the DOM but it removes the binding I had so when I click now it does nothing. Also seems slow. I would like to use AJAX or JS Array.
{
"bDeferRender" : true,
"bProcessing" : true,
"sDom": '<"top"r>t<"bottom"lp><"clear">',
"oLanguage" : {
"sLoadingRecords" : " ",
"sProcessing" : processDialog
},
"sAjaxSource":'/get_statistics',
"sAjaxDataProp": 'landing_page_statistics',
"fnServerParams": function (aoData) {
aoData.push({"name": "start_date", "value": startDateEl.val()});
aoData.push({"name": "end_date", "value": endDateEl.val()});
},
"aoColumns" : [
{"mData" : "status", "sWidth": "6%"},
{"mData" : "name"},
{"mData" : "url"},
{"mData" : "pageViews", "sWidth": "15%"},
{"mData" : "leads", "sWidth": "5%"},
{"mData" : "convRate", "sWidth": "12%"}
],
"fnRowCallback": function (nRow, aData, iDisplayIndex) {
renderDataTableRow(nRow, aData, iDisplayIndex);
},
"fnFooterCallback" : function (nFoot, aData, iStart, iEnd, aiDisplay) {
renderDataTableTotalsRow(nFoot, aData, iStart, iEnd, aiDisplay);
},
"fnDrawCallback": function( oSettings ) {
// status tooltips
$('.lp-status').tooltip();
}
}
I'm not sure if I get the point in your question, and if I do my answer feels like cheating, merely pointing to the relevant sources. In any case, here goes.
Your first option is to use load and save AJAX data to and from your view models manually. The related tutorial does a pretty decent job of explaining things. Loading comes down to:
// Load initial state from server, convert it to Task instances, then populate self.tasks
$.getJSON("/tasks", function(allData) {
var mappedTasks = $.map(allData, function(item) { return new Task(item) });
self.tasks(mappedTasks);
});
Saving it to your service looks like this:
self.save = function() {
$.ajax("/tasks", {
data: ko.toJSON({ tasks: self.tasks }),
type: "post", contentType: "application/json",
success: function(result) { alert(result) }
});
};
A related second option is using the mapping plugin to save/load your ViewModels in a conventions-based way. You still need some wiring to communicate with the server though.
For the View part, in both cases I think you already had the correct approach: use a foreach binding on a tbody and construct one row per ViewModel.
Again, this is a pretty vague/broad answer, in part because your question is rather broad. Hope it helps.
This is the way to do it... I have made a jsfiddle showing this:
To get this to work I had to add two callback methods to the original knockout foreach binding definition. I am currently trying to get these events into the newest version of knockout. I needed to add a beforeRenderAll and afterRenderAll callback to destroy the datatable and reinitialize the datatable after the knockouts foreach binding adds the html. This works like a charm The JSFiddle showing this has a fully editable jquery datatable bound to the ViewModel through knockout.
With Knockout, you wont be using the ajax implementation of datatables. You will be using the standard implementation where the html is already defined. Let Knockout handle the html by loading your data through ajax and assigning that to an observable array. The custom foreach binding below will then destroy the datatable, let knockout handle the html in the default knockout foreach fashion, and then the binding will reinitialize the datatable.
var viewmodel = new function(){
var self = this;
this.Objects = ko.mapping.fromJS([]);
this.GetObjects = function(){
$.get('your api url here', function(data){
ko.mapping.fromJS(
data,
{
key: function(o){ return ko.utils.unwrapObservable(o.Id); },
create: function(options){ return new YourObjectViewModel(options.data); }
},
that.Objects
);
});
};
};
//Or if you do not need to map to a viewmodel simply just...
$.get('your api url here', function(data){
viewmodel.Options(data);
});
And below is the custom datatables binding...
ko.bindingHandlers.DataTablesForEach = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
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 };
}
};