Assign different attribute to each column in datatables - javascript

I have my dataTable done successfully
All I have to do is assign the attributes to the columns of this table.
I have the attributes in the array as shown:
$attributes = array('7' => array(
'17' => array(
'class' => 'editable'
)
'18' => array(
'class' => 'custom_7_18 editable'
)
)
where 17 and 18 are the ids of fieldone and fieldtwo.
code for dataTable
$('table.crm-multifield-selector').dataTable({
"bProcessing": true,
"asStripClasses" : [ "odd-row", "even-row" ],
"sPaginationType": "full_numbers",
"sDom" : '<"crm-datatable-pager-top"lfp>rt<"crm-datatable-pager-bottom"ip>',
"bServerSide": true,
"bSort" : false,
"sAjaxSource": sourceUrl,
});
I can't use the sClass in datatable as it will assign same attribute to all the <td> element. Is there any param to assign different attribute?
Can I assign the attributes from the $attribute array to the dataTable?
Or is there any way to assign attribute from the callback function? so that I can use this $attribute array in the php file and the dataTable assigns this attribute every time it draws a row?
I've tried this -
"fnCreatedRow": function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
$.each(attributes, function(index, item) {
//as the number of column can be changed, I can't give
//hardcoded values as I found during searching like
$('td:eq(2)', nRow).addClass('editable');
});
}
Has anyone come across a situation like this ?

I've just fixed this, would like to share here :)-
Assign the attribute for each cell in the call back function itself, i.e, change the cell value to be the array which contains the data as well as the attributes (for eg: class)
//$value is the array sent to dataTable
foreach ($value as $fieldId => &$fieldName) {
if (!empty($attributes[$fieldId][$id]['class'])) {
//change the fieldName to array containing the attributes and the data part
$fieldName = array(
'data' => $fieldName,
// as I said in the question, I had $attributes array for the fields ids
'fieldClass' => $attributes[$fieldId][$id]['class']
);
}
}
Adjust this to replicate on the dataTable using standard fnRowCallback:
//Add class attributes to cells
"fnRowCallback": function(nRow, aData) {
// iterate through each row
$('td', nRow).each(function(index, element) {
if (typeof aData[index]=='object') {
if (typeof aData[index].fieldClass != 'undefined') {
$(element).addClass(aData[index].fieldClass);
}
if (typeof aData[index].data != 'undefined') {
$(element).html(aData[index].data);
}
}
});
return nRow;
},

Related

How to differentiate between buttons in R/Shiny reactable javascript callback?

Take this example from the reactable documentation (interactive Shiny example provided at link):
data <- cbind(
MASS::Cars93[1:5, c("Manufacturer", "Model", "Type", "Price")],
details = NA
)
reactable(
data,
columns = list(
# Render a "show details" button in the last column of the table.
# This button won't do anything by itself, but will trigger the custom
# click action on the column.
details = colDef(
name = "",
sortable = FALSE,
cell = function() htmltools::tags$button("Show details")
)
),
onClick = JS("function(rowInfo, colInfo) {
// Only handle click events on the 'details' column
if (colInfo.id !== 'details') {
return
}
// Display an alert dialog with details for the row
window.alert('Details for row ' + rowInfo.index + ':\\n' + JSON.stringify(rowInfo.row, null, 2))
// Send the click event to Shiny, which will be available in input$show_details
// Note that the row index starts at 0 in JavaScript, so we add 1
if (window.Shiny) {
Shiny.setInputValue('show_details', { index: rowInfo.index + 1 }, { priority: 'event' })
}
}")
)
I want to include 2 buttons per details column cell, which I can do by changing the cell definition to:
cell = function() {
a <- htmltools::tags$button("Approve")
b <- htmltools::tags$button("Decline")
return(list(a,b))
}
But then how to differentiate between the Approve/Decline button within the JS() onClick() function? Is there another parameter I can pass that will give me this ability? I console.log'd both rowInfo and colInfo and could not find anything that seemed helpful to identify the two buttons. I'd like to have it so that I can return both:
Shiny.setInputValue('approve_button_click', ...)
and
Shiny.setInputValue('decline_button_click',...)
from the JS side so I can handle them separately in R. Any help is appreciated!
If you want to get just the row index you can do:
library(htmltools)
details = colDef(
name = "",
sortable = FALSE,
cell = function(value, rowIndex, colName){
as.character(tags$div(
tags$button("Approve", onclick=sprintf('alert("approve - %d")', rowIndex)),
tags$button("Decline", onclick=sprintf('alert("decline - %d")', rowIndex))
))
},
html = TRUE
)
In Shiny:
reactable(
data,
columns = list(
details = colDef(
name = "",
sortable = FALSE,
cell = function(value, rowIndex, colName){
as.character(tags$div(
tags$button(
"Approve",
onclick =
sprintf(
'Shiny.setInputValue("approve", %d, {priority: "event"})',
rowIndex
)
),
tags$button(
"Decline",
onclick =
sprintf(
'Shiny.setInputValue("decline", %d, {priority: "event"})',
rowIndex
)
)
))
},
html = TRUE
)
)
)

Buttons in the first column disappear when sorting the table

I have a datatable that shows mock json data retrieved from a text file. I have removed the first column as it contains the id and it isn't ideal to display the id on the table. I appended a buy button on each row on the first column only. However, when I sort the table the button disappears and the original first column data also appears.
JavScript:
$(document).ready(function() {
$.getJSON('/apps/mchp/clientlibs/clientlib-site/components/parametrictable/data.txt', function(json) {
var data = json.data;
var $thead = $('#parametrictable').find('thead');
var tr = $("<tr>");
$thead.append(tr);
var columns = [];
var obj = Object.keys(data[0]);
console.log(data[0])
var button = '<div class="left-btn"><i class="download fas fa-file-download"></i></div><div class="right-btn"><div class="input-group"><div class="input-group-area"><input type="text" value="100"></div><div class="input-group-icon">BUY</div></div></div>';
$.each(data[0], function(name, value) {
var column = {
"data": name,
"title":name
};
$('tr').find('th:first-child',).remove();
columns.push(column);
});
for (i=1; i<obj.length; i++) {
$(".dropdown-content").append('<li><input type="checkbox" class="dropcheck" data-column="'+i+'"/>'+obj[i]+'</li>');
}
var table= $('#tableId').DataTable({
data: data,
columns: columns,
columnDefs: [ {
orderable: true,
className: 'select-checkbox',
targets: 1
} ],
select: {
style: 'multi',
selector: 'td:first-child'
},
order: [[ 1, 'asc' ]]
});
$('tr').find('th:first-child').remove();
$('tr').find('td:first-child').remove();
$('tr').find('td:first-child').append(button);
$('tr').on('change', function(e){
e.preventDefault();
console.log('change!');
});
$('input[type=checkbox]').on( 'change', function (e) {
e.preventDefault();
var column = table.column( $(this).attr('data-column') );
column.visible( ! column.visible() );
} );
$('.show-all').on( 'click', function (e) {
e.preventDefault();
var obj = Object.keys(data[0])
for (i=1; i<obj.length; i++) {
var col = table.columns([i]);
if (col.visible().join(', ') == 'false') {
col.visible(true);
$(".dropcheck").prop("checked", false);
}
}
} );
});
})
First, it is not a good practice to hide datatable columns by .remove(), datatables have a built-in code for that by columnDefs refer:
https://datatables.net/examples/basic_init/hidden_columns.html
Second, that is not the right way to append data/html in datatable columns, use rowCallback function for datatable, see:
columnDefs: [
{
"targets": [ 0 ],
"visible": false,
"searchable": false
},
],
rowCallback: function(row, data, index) {
if($('td:eq(1)', row).find('.download').length == 0)
{
$('td:eq(1)', row).append(button);
}
},
When using datatable sort or search or any datatable actions it always refreshes the table therefore, any initialization before the action will be disregarded unless executed again.

Datatables JS get row index by single column value PHP

I'm working on a system of medical transportation. Every day I have around 700 trips, so the table is pretty large.
I want the app, after the user adds a trip, the screen is positioned in the newly added trip. (scroll) ,for the user to corroborate the trip successfully added (not my idea, my client wants it that way) . For this, I am sending to the page by parameter the ID of the new trip added.
I'm using a function "fnFindCellRowIndexes" that i found on datatables page, but i can't make it work. The column i wait to search in is index 1.
Here's my code:
<script type="text/javascript">
$(document).ready(function() {
var table = $('#tabla').DataTable({
"deferRender": true,
"language": {
"url": "include/DataTables/Spanish.json"
},
"paging": false,
"ordering": false,
"info": false,
"searching": true,
"columnDefs": [
{
"targets": [ 0 ],
"visible": false,
"searchable": true
},
{
"targets": [ 1 ],
"visible": false,
"searchable": false
}
],
"stateSave": true
});
var index = table.fnFindCellRowIndexes( '<?=$idNuevo?>', 1 );
alert(index);
jQuery.fn.dataTableExt.oApi.fnFindCellRowIndexes = function ( oSettings, sSearch, iColumn )
{
var
i,iLen, j, jLen, val,
aOut = [], aData,
columns = oSettings.aoColumns;
for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
{
aData = oSettings.aoData[i]._aData;
if ( iColumn === undefined )
{
for ( j=0, jLen=columns.length ; j<jLen ; j++ )
{
val = this.fnGetData(i, j);
if ( val == sSearch )
{
aOut.push( i );
}
}
}
else if (this.fnGetData(i, iColumn) == sSearch )
{
aOut.push( i );
}
}
return aOut;
};
</script>
I'm getting error "fnFindCellRowIndexes is not a function"
Can anyone help me? if anyone has any idea to do it in a better way, suggestions are accepted.
Declare fnFindCellRowIndexes before you instantiate your dataTable!
You must instantiate with dataTable(). fnFindCellRowIndexes is an oldschool none-API plugin. As far as I can see in your code, you should not have problems with that. If you still want to use the new API, you can always use table.api().<api-functions>
The PHP variable does not magically echo itself out (no offense :-) - you forget to actually print it :
var index = table.fnFindCellRowIndexes( '<? echo $idNuevo ?>', 1 );
Remember that fnFindCellRowIndexes is an equal / == search. If you search for 1 in column #1 it only return columns that hold the value 1 and nothing more, not rows holding 10, 101, 1.1 etc.

Inline editing - save new values on button click

I have following code for jQuery DataTables:
Contact.DataTable = $('#otable').DataTable( {
"ajax": {
"url" : '/Contact/' + Contact.id,
"dataSrc": function(check) {
return check.data;
},
},
"responsive": true,
"columns": [
{ "data": "id"},
{ "data": "category", "sClass": "category" },
{ "data": "name", "sClass": "name" },
{ "data": "lname" },
{
"render": function ( data, type, method, meta ) {
return Contact.saveContact(method);
}
},
]
} );
Datatable - dropdown - inline edit:
$('#otable tbody').on('click', '.category', function () { //second column
var row = this.parentElement;
if(!$('#otable').hasClass("editing")){
$('#otable').addClass("editing");
var data = Contact.DataTable.row(row).data();
var $row = $(row);
var thiscategory = $row.find("td:nth-child(2)");
var thiscategoryText = thiscategory.text();
thiscategory.empty().append($("<select></select>",{
"id":"category" + data[0],
"class":"in_edit"
}).append(function(){
var options = [];
$.each(Categories, function(key, value){
options.push($("<option></option>",{
"text":value,
"value":value
}))
})
return options;
}));
$("#category" + data[0]).val(thiscategoryText)
}
})
;
For changing values in dropdown
$('#otable tbody').on("change", ".in_edit", function(){ //Inline editing
var val = $(this).val();
$(this).parent("td").empty().text(val);
$('#otable').removeClass("editing");
});
Below code for saving new values(after inline edit) while clicking save:
$('#divsave').on("click", ".saveContact", function() {
var data = Contact.DataTable.row($(this).closest('tr')).data();
// Here I have to get new values after inline editing - but getting old values
});
My problem is : while clicking edit, in 'data', I am getting old values in the row of datatable, not the modified value after inline edit
datatable view - 1:
datatable - dropdown in column:
datatable after inline editing:
What I need: Save modified row while clicking 'save' image - currently it saves older value before inline editing(datatable view - 1)
When using dataTables it is generally a very bad idea to manipulate the DOM <table> or any content by "hand" - you should always go through the dataTables API.
Thats why you are getting "old values" - you have manipulated the content of the <table>, or so it seems - dataTables are not aware of those changes.
In a perfect world you should refactor the setup completely (i.e to use the API) but I guess you can solve the issue by using invalidate() on the row being changed in order to refresh the dataTables internals :
$('#otable tbody').on("change", ".in_edit", function(){ //Inline editing
var val = $(this).val();
$(this).parent("td").empty().text(val);
//add this line to refresh the dataTables internals
Contact.DataTable.row($(this).parent("tr")).invalidate();
//
$('#otable').removeClass("editing");
});

Bootstrap Multiselect update option list on flow

I use bootstrap multi-select and I want to update options on flow with ajax
To populate on init my multiselect I do
<select name="model" class="multiselect" multiple="multiple">
<? foreach ($sel_models as $mod) { ?>
<option value="<?= $mod ?>" <?= ($mod == $params['model']) ? 'selected' : '' ?>><?= $mod ?></option>
<? } ?>
</select>
then on event I would like to update my option list with the following ajax
I was trying to use the rebuild method but won't fire the drop-down after creation
$.ajax({
type: 'post',
url: "helper/ajax_search.php",
data: {models: decodeURIComponent(brands)},
dataType: 'json',
success: function(data) {
$('select.multiselect').empty();
$('select.multiselect').append(
$('<option></option>')
.text('alle')
.val('alle')
);
$.each(data, function(index, html) {
$('select.multiselect').append(
$('<option></option>')
.text(html.name)
.val(html.name)
);
});
$('.multiselect').multiselect('rebuild')
},
error: function(error) {
console.log("Error:");
console.log(error);
}
});
With firebug I can see that the list is generated but on select won't show up
In the doc I can read :
.multiselect('setOptions', options)
Used to change configuration after initializing the multiselect. This may be useful in combination with .multiselect('rebuild').
Maybe you can't change your widget data by your initial way. In a correct way you should use setOptions method.
Else : With your way, maybe should you think about destroy your widget .multiselect('destroy') and create it again after.
Update after comment :
In the doc : ( you've linked )
Provides data for building the select's options the following way:
var data = [
{label: "ACNP", value: "ACNP"},
{label: "test", value: "test"}
];
$("#multiselect").multiselect('dataprovider', data);
So :
When you get data from your ajax call, you have to create an array of objects ( it's the options in the select you want to have ) with the format like
var data =
[
{label: 'option1Label', value: 'option1Value'},
{label: 'option2Label', value: 'option2Value'},
...
]
When your objects array is created, then you just have to call the method
$("#multiselect").multiselect('dataprovider', data);
Where data is your array of objects.
I hope I'm clear :/
As an alternative to multiselect('dataprovider', data) you can build the list with jquery append exactly the way you did in your question. The only change you need to make is to delay the rebuild until after the ajax request is complete.
var buildDrivers = $.getJSON('resources/orders/drivers.json', function(data) {
$.each(data, function(i, driver) {
$('#toolbar select[name="drivers"]').append('<option>'+driver+'</option>');
});
});
buildDrivers.complete(function() {
$('.multiselect').multiselect('rebuild');
});
see http://api.jquery.com/jquery.getjson/ for documentation
I've been added the functionality of updating options after filtering and getting them from the server side. This solution relays on the concept of injecting new options, destroying the select and initializing it again.
I took into account:
Considering the existing selected options, which must stay.
Removing duplicate options (might be as a conflict from which that already selected and the new that came from the server).
Keeping the options tray open after the update.
Reassign the previous text in the search text box & focusing it.
Just add the 'updateOptions' as a function after the 'refresh' function along with the two helper functions as follows:
updateOptions: function (options) {
var select = this.$select;
options += this.getSelectedOptionsString();
var selectedIds = select.val(),
btnGroup = select.next('.btn-group'),
searchInput = btnGroup.find('.multiselect-search'),
inputVal = searchInput.val();
options = this.removeOptionsDuplications(options);
if (!options) {
options = '<option disabled></option>';
}
// 1) Replacing the options with new & already selected options
select.html(options);
// 2) Destroyng the select
select.multiselect('destroy');
// 3) Reselecting the previously selected values
if (selectedIds) {
select.val(selectedIds);
}
// 4) Initialize the select again after destroying it
select.multiselect(this.options);
btnGroup = select.next('.btn-group');
searchInput = btnGroup.find('.multiselect-search');
// 5) Keep the tray options open
btnGroup.addClass('open');
// 6) Setting the search input again & focusing it
searchInput.val(inputVal);
searchInput.focus();
},
getSelectedOptionsString: function () { // Helper
var result = '',
select = this.$select,
options = select.find('option:selected');
if (options && options.length) {
$.each(options, function (index, value) {
if (value) {
result += value.outerHTML;
}
});
}
return result;
},
removeOptionsDuplications: function (options) { // Helper
var result = '',
ids = new Object();
if (options && options.length) {
options = $(options);
$.each(options, function (index, value) {
var option = $(value),
optionId = option.attr('value');
if (optionId) {
if (!ids[optionId]) {
result += option[0].outerHTML;
ids[optionId] = true;
}
}
});
}
return result;
},
Demo:
State:
"Option 1"
$('#select').multiselect('updateOptions', '<option value="2">Option 2</option>');
State:
"Option 2"
"Option 1"
I think this is an easier way to add options on the fly (using ajax or any other listener) to an existing Bootstrap MultiSelect.
Following is a simplified example to add options:
function addOptionToMultiSelect(multiselectSelector, value, selected) {
var data = [];
$(multiselectSelector + ' option').each(function(){
var value = $(this)[0].value;
var selected = $(this)[0].selected;
data.push({label: value, value: value, selected: selected});
});
// Add the new item
data.push({label: value, value: value, selected: selected});
$(multiselectSelector).multiselect('dataprovider', data);
}
For simplicity, I have assumed both the label and value are the same in an option. Note that the already selected options are taken care of by reading the selected attribute from the existing options. You can make it more sophisticated by tracking the disabled and other attributes.
Sample:
addOptionToMultiSelect('#multiselect-example', 'new-option', true);

Categories