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 };
}
};
Related
I have a web application with multiple Selectize objects initialized on the page. I'm trying to have each instance load a default value based on the query string when the page loads, where ?<obj.name>=<KeywordID>. All URL parameters have already been serialized are are a dictionary call that.urlParams.
I know there are other ways to initializing Selectize with a default value I could try; but, I'm curious why calling setValue inside onInitialize isn't working for me because I'm getting any error messages when I run this code.
I'm bundling all this JavaScript with Browserify, but I don't think that's contributing to this problem.
In terms of debugging, I've tried logging this to the console inside onInititalize and found that setValue is up one level in the Function.prototype property, the options property is full of data from load, the key for those objects inside options corresponds to the KeywordID. But when I log getValue(val) to the console, I get an empty string. Is there a way to make this work or am I ignoring something about Selectize or JavaScript?
module.exports = function() {
var that = this;
...
this.selectize = $(this).container.selectize({
valueField: 'KeywordID', // an integer value
create: false,
labelField: 'Name',
searchField: 'Name',
preload: true,
allowEmptyOptions: true,
closeAfterSelect: true,
maxItems: 1,
render: {
option: function(item) {
return that.template(item);
},
},
onInitialize: function() {
var val = parseInt(that.urlParams[that.name], 10); // e.g. 5
this.setValue(val);
},
load: function(query, callback) {
$.ajax({
url: that.url,
type: 'GET',
error: callback,
success: callback
})
}
});
};
...
After sprinkling in some console.logs into Selectize.js, I found that the ajax data hadn't been imported, when the initialize event was triggered. I ended up finding a solution using jQuery.when() to make setValue fire after the data had been loaded, but I still wish I could find a one-function-does-one-thing solution.
module.exports = function() {
var that = this;
...
this.selectize = $(this).container.selectize({
valueField: 'KeywordID', // an integer value
create: false,
labelField: 'Name',
searchField: 'Name',
preload: true,
allowEmptyOptions: true,
closeAfterSelect: true,
maxItems: 1,
render: {
option: function(item) {
return that.template(item);
},
},
load: function(query, callback) {
var self = this;
$.when( $.ajax({
url: that.url,
type: 'GET',
error: callback,
success: callback
}) ).then(function() {
var val = parseInt(that.urlParams[that.name], 10); // e.g. 5
self.setValue(val);
});
}
});
};
...
You just need to add the option before setting it as the value, as this line in addItem will be checking for it:
if (!self.options.hasOwnProperty(value)) return;
inside onInitialize you would do:
var val = that.urlParams[that.name]; //It might work with parseInt, I haven't used integers in selectize options though, only strings.
var opt = {id:val, text:val};
self.addOption(opt);
self.setValue(opt.id);
Instead of using onInitialize you could add a load trigger to the selectize. This will fire after the load has finished and will execute setValue() as expected.
var $select = $(this).container.selectize({
// ...
load: function(query, callback) {
// ...
}
});
var selectize = $select[0].selectize;
selectize.on('load', function(options) {
// ...
selectize.setValue(val);
});
Note that for this you first have to get the selectize instanze ($select[0].selectize).
in my case it need refresh i just added another command beside it
$select[0].selectize.setValue(opt);
i added this
$select[0].selectize.options[opt].selected = true;
and changes applied
but i dont know why?
You can initialize each selectize' selected value by setting the items property. Fetch the value from your querystring then add it as an item of the items property value:
const selectedValue = getQueryStringValue('name') //set your query string value here
$('#sel').selectize({
valueField: 'id',
labelField: 'title',
preload: true,
options: [
{ id: 0, title: 'Item 1' },
{ id: 1, title: 'Item 2' },
],
items: [ selectedValue ],
});
Since it accepts array, you can set multiple selected items
Problem
I have a text input that I have selectized as tags which works fine for querying remote data, I can search and even create new items using it and that all works OK.
Using selectize:
var $select = $('.authorsearch').selectize({
valueField: 'AuthorId',
labelField: 'AuthorName',
searchField: ['AuthorName'],
maxOptions: 10,
create: function (input, callback) {
$.ajax({
url: '/Author/AjaxCreate',
data: { 'AuthorName': input },
type: 'POST',
dataType: 'json',
success: function (response) {
return callback(response);
}
});
},
render: {
option: function (item, escape) {
return '<div>' + escape(item.AuthorName) + '</div>';
}
},
load: function (query, callback) {
if (!query.length) return callback();
$.ajax({
url: '/Author/SearchAuthorsByName/' + query,
type: 'POST',
dataType: 'json',
data: {
maxresults: 10
},
error: function () {
callback();
},
success: function (res) {
callback(res);
}
});
}
});
The text box:
<input class="authorsearch" id="Authors" name="Authors" type="text" value="" />
Examples:
Then when I select one (in this case 'apple') it comes up in a badge as you'd expect, and the underlying value of the textbox is a comma separated list of the values of these items.
Current Output
The problem is when I load a page and want values retrieved from the database to be displayed in the selectized text input as tags, it only loads the values and I can see no way of displaying the displayname instead.
<input class="authorsearch" id="Authors" name="Authors" type="text" value="1,3,4" />
Desired Ouput
I have tried all sorts of values for the inputs value field to have it load the items as showing their displayname and not their values. Below is an example of a single object being returned as JSON, being able to load a JSON array of these as selectized tags would be ideal.
[{"AuthorId":1,"AuthorName":"Test Author"},
{"AuthorId":3,"AuthorName":"Apple"},
{"AuthorId":4,"AuthorName":"Test Author 2"}]
How can I go about this? Do I need to form the value of the text box a particular way, or do I need to load my existing values using some javascript?
Thanks to your answer and based on your onInitialize() approach I ended up with a similar solution. In my case I just needed to translate one value, thus I was able to store the id and label as data attributes in the input field.
<input type="text" data-actual-value="1213" data-init-label="Label for 1213 item">
Then on initialization:
onInitialize: function() {
var actualValue = this.$input.data('actual-value');
if (actualValue){
this.addOption({id: actualValue, value: this.$input.data('init-label')});
this.setValue(actualValue);
this.blur();
}
}
According to these options:
$('input').selectize({
valueField: 'id',
labelField: 'value',
searchField: 'value',
create: false,
maxItems: 1,
preload: true,
// I had to initialize options in order to addOption to work properly
// although I'm loading the data remotely
options: [],
load: ... ,
render: ...,
onInitialize: ....
});
I know this does not answer your question but wanted to share just in case this could help someone.
I ended up using the onInitialize callback to load the JSON values stored in a data-* field. You can see it in action here in this jsfiddle.
<input class="authorsearch" id="Authors" name="Authors" type="text" value=""
data-selectize-value='[{"AuthorId":1,"AuthorName":"Test"},{"AuthorId":2,"AuthorName":"Test2"}]'/>
Basically it parses the data-selectize-value value and then adds the option(s) to the selectize then adds the items themselves.
onInitialize: function() {
var existingOptions = JSON.parse(this.$input.attr('data-selectize-value'));
var self = this;
if(Object.prototype.toString.call( existingOptions ) === "[object Array]") {
existingOptions.forEach( function (existingOption) {
self.addOption(existingOption);
self.addItem(existingOption[self.settings.valueField]);
});
}
else if (typeof existingOptions === 'object') {
self.addOption(existingOptions);
self.addItem(existingOptions[self.settings.valueField]);
}
}
My solution does presume my JSON object is formed correctly, and that it's either a single object or an object Array, so it may or may not be appropriate for someone elses needs.
So it parses:
[{"AuthorId":1,"AuthorName":"Test"},
{"AuthorId":2,"AuthorName":"Test2"}]
To:
Based of course on my selectize settings in my original post above.
Even simpler on new version of selectize using items attribute. Basically to set a selected item you need to have it first in the options. But if you use remote data like me, the options are empty so you need to add it to both places.
$('select').selectize({
valueField: 'id',
labelField: 'name',
options:[{id:'123',name:'hello'}],
items: ['123'],
...
This is working for me and took me a while to figure it out... so just sharing
I'm developing an extJS 4.2 MVC app.
I've this context menu view object defined:
Ext.define('XApp.view.message.inbox.CtxMenu', {
extend : 'Ext.menu.Menu',
alias : 'widget.inboxctxmenu',
items : [ {
itemId : 'buttonSetFlags',
text : 'ToRead'
}]
});
this context menu is builded when i'm creating this grid (and other my extended grids):
Ext.define('XApp.view.message.inbox.Grid', {
extend: 'Ext.grid.Panel',
alias: 'widget.inboxgrid',
store: 'message.Inbox',
initComponent : function(){
this.menu = this.buildMenu();
this.callParent(arguments);
this.on({
scope : this,
itemcontextmenu : this.onItemContextMenu
});
},
onItemContextMenu : function(grid, record, item, index, e, eOpts) {
console.log('onItemContextMenu');
e.stopEvent();
this.menu.showAt(e.getXY());
},
onDestroy : function(){
console.log('destroy grid and menu');
this.menu.destroy();
this.callParent(arguments);
},
buildMenu : function(){
return Ext.widget('inboxctxmenu');
}
});
this code is extracted from Sencha blog on point 2 to avoid memory leak on nested object.
Now in my controller i want to listen
Ext.define('XApp.controller.Inbox', {
extend : 'Ext.app.Controller',
init : function(application) {
this.control({
"inboxctxmenu #buttonSetFlags" : {
click : this.onFlagsSetter
}
});
},
onFlagsSetter : function(button, e, eOpts) {
this.getController('Message').SetMessageStatus(1,"ToRead",this.getStore('message.Inbox').load);
}
});
in this controller, i call another controller function and i want to reload 'message.Inbox' store:
Ext.define('XApp.controller.Message', {
extend : 'Ext.app.Controller',
SetMessageStatus: function(id,statusToSet,callback) {
Ext.Ajax.request({
url : XApp.util.Util.serverUrl + 'api/message/SetMessageStatus/' + id + "/" + statusToSet,
method : "GET",
failure : function(response, options) {
console.log('Failure' + response);
},
success : function(conn, response, options, eOpts) {
console.log('Success');
if (callback && typeof(callback) === "function") {
console.log('Calling callback');
callback();
}
}
});
}
});
in this function, i've an async call with AJAX, and i want to reload store of InboxController after ajax response, but with this notation, console throw an error.
There are best practices to call async function and launch a callback after success or failure?
Another question is:
what is the best pratices with ExtJs MVC to listen on nested view event (in example my ctxmenu is nested in a grid)? i read for fireevent and bubbleevent but i'm confused...Please bring me back to the right way...
JFYI the context menu in your example is not nested in the grid. Menus are floating objects, and as such they are outside of the usual component hierarchy.
The error you're having is because you're not passing a callback to SetMessageStatus, you're passing the result of expression this.getStore('message.Inbox').load - which evaluates to a function, but without a scope bound to it it's useless. Read this question's answers for more explanations on what the function scope is.
With a naïve head-on approach, the fix would look thusly:
onFlagsSetter: function(button, e) {
var me = this; // Important for the closure below
this.getController('Message').SetMessageStatus(1, 'ToRead', function() {
// Note that within the callback function, `this` is an object
// entirely different from `this` in the above line, so we call
// `getStore` on the captured scope instead.
me.getStore('message.Inbox').load();
});
}
However, a much better approach is to use Controller events:
Ext.define('XApp.controller.Inbox', {
extend: 'Ext.app.Controller',
init: function() {
this.listen({
component: {
'inboxctxmenu #buttonSetFlags': {
click: this.onFlagsSetter
}
},
controller: {
'*': {
statusmessage: this.onStatusMessage
}
}
});
},
onFlagsSetter: function(button) {
this.fireEvent('setstatus', 1, 'ToRead');
},
onStatusMessage: function(success, response) {
if (success) {
this.getStore('message.Inbox').load();
}
}
});
Ext.define('Xapp.controller.Message', {
extend: 'Ext.app.Controller',
init: function() {
this.listen({
controller: {
'*': {
setstatus: this.setMessageStatus
}
}
});
},
setMessageStatus: function(id, statusToSet) {
Ext.Ajax.request({
url: ...,
method: 'GET',
failure: function(response) {
this.fireEvent('statusmessage', false, response);
},
success: function(connection, response) {
this.fireEvent('statusmessage', true, response);
},
// We are setting the above callbacks' scope to `this` here,
// so they would be bound to the Controller instance
scope: this
});
}
});
As you can see, by using Controller events we have decoupled Inbox controller from the Message controller; they are no longer calling each other's methods directly but are passing information instead. The code is much cleaner, and concerns are properly separated.
I'm coding a jQuery plugin to build KendoUI grids. Everything was fine until I had to use 2 instances of the plugin in the same page. In this case, I need each instance of the plugin to have its own data.
I've read the jQuery guidelines for storing data with .data(), but when trying to access from outside the code of the plugin to the 'getSelectedItem()' this way:
selectedItemGrid1= $("#Grid1").kendoGrid_extended.getSelectedItem();
selectedItemGrid2= $("#Grid2").kendoGrid_extended.getSelectedItem();
I get the selectedItem of Grid2 in selectedItemGrid1. Here is a simplified version of my plugin code. Basically what I wanted to do is, anytime a row of the grid is selected ("change" event on the kendoGrid definition), store the new selected row inside the plugin, so when the user clicks on a "delete" button, read from the plugin the selected row and call the delete action with the info recovered from the plugin.
$.fn.kendoGrid_extended = function (options) {
var opts = $.extend({}, $.fn.kendoGrid_extended.defaults, options);
opts.controlId = '#' + this.attr('id');
var gridExtended;
var selectedItem;
var instance = $(this);
//Public accessor for the selectedItem object.
$.fn.kendoGrid_extended.getSelectedItem = function () {
return instance.data('selectedItem');
}
opts.gridDataSource = new kendo.data.DataSource({
type: "json",
serverPaging: true,
serverSorting: true,
sort: sortObject,
serverFiltering: true,
allowUnsort: true,
pageSize: opts.pageSize,
group: opts.group,
transport: {
read: {
url: opts.dataSourceURL,
type: 'POST',
dataType: 'json',
contentType: 'application/json; charset=utf-8',
data: function () { return opts.dataSourceParamsFunction(); }
},
parameterMap: function (options) {
return JSON.stringify(options);
}
},
schema: opts.dataSourceSchema,
});
gridExtended = $(opts.controlId).kendoGrid({
columns: opts.gridColumns,
dataSource: opts.gridDataSource,
pageable: {
refresh: true,
pageSizes: true
},
groupable: opts.groupable,
sortable: { mode: "multiple" },
filterable: false,
selectable: true,
scrollable: true,
resizable: true,
reorderable: true,
columnReorder: SetColumnsReorder,
columnResize: SetColumnsResize,
change: function () {
var gridChange = this;
gridChange.select().each(function () {
selectedItem = gridChange.dataItem($(this));
instance.data('selectedItem', selectedItem);
});
}
});
}
The code itself it's a simplified version of my plugin. I know that this may not be the best way to write a plugin as I've read the jQuery guidelines for plugin development. It would be awesome if you could point me in the right direction or tell me why my code is not working. Thanks a lot in advance!
I think you do not need that "public accessor" that you wrote, I think you actually can get it like this:
selectedItemGrid1= $("#Grid1").data('selectedItem);
selectedItemGrid2= $("#Grid2").data('selectedItem);
On a side you do not have to wrap that instance element into a jQuery object. You still be able to use rest of the jQuery methods, check the example.
I finally solved it appending a hidden input with a unique Id to the grid of the helper. On that hidden input I'm storing all the data that I want to be persistent with the Jquery .data().
So if I generate 2 grids using the plugin, each grid will have appended something like this:
<input type="hidden" id="uniqueIdHere" />
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);
},