Skip to content

Instantly share code, notes, and snippets.

@MayaGans
Created June 23, 2021 13:40
Show Gist options
  • Select an option

  • Save MayaGans/d41b9ab56f87326013526989570bcab7 to your computer and use it in GitHub Desktop.

Select an option

Save MayaGans/d41b9ab56f87326013526989570bcab7 to your computer and use it in GitHub Desktop.
sticky
/*
Copyright (c) 2013-2020 NDP Software, Andrew J. Peterson
* https://github.com/ndp/align-column
*/
$.fn.alignColumn = function (columns, options) {
var config = $.extend({}, {
addedCellClass: 'added',
skipTHs: true
}, options);
if (!jQuery.isArray(columns)) {
columns = [columns];
}
columns = columns.sort().reverse();
if (config.debug) {
console.log('Adjusting columns ', columns);
}
var splitter;
if (typeof config.center == 'string') {
if (config.debug) {
console.log('Splitting by ', config.center);
}
splitter = function (text) {
if(text === undefined) return '';
var s = text.split(config.center);
return (s.length == 2) ? [s[0], config.center + s[1]] : [text];
}
} else if (jQuery.isFunction(config.center)) {
if (config.debug) {
console.log('Splitting using fn');
}
splitter = config.center;
} else {
if (config.debug) {
console.log('Splitting using decimal align');
}
splitter = function (text) {
if (!text) return [''];
var match;
if (text.indexOf('.') == -1) {
match = text.match(/^(\S+[\d]+)(.*)$/);
} else {
match = text.match(/^([^\.]+)(\..*)$/);
}
return match ? [match[1], match[2] || ''] : [text, ''];
};
}
return $(this).each(function () {
var $table = $(this).css('border-collapse', 'collapse'); // no way around this!
var $rows = $table.find('tr');
$rows.each(function () {
var cells = $(this).find('>td,>th').not('.' + config.addedCellClass).get();
for (var index = 0; index < columns.length; index++) {
var colIndex = columns[index],
$cell = $(cells[colIndex]),
text = $cell.html(),
pieces = splitter(text),
existingClasses = $cell.attr('class');
if (config.debug) {
console.log("Split '" + text + "' into " + pieces.length + " pieces: ", "" + pieces[0], "" + pieces[1]);
}
if (pieces.length === 2 &&
(!config.skipTHs || $cell.get(0).nodeName == 'TD')) {
if (' ' == pieces[0][pieces[0].length - 1]) {
pieces[0] = pieces[0].slice(0, pieces[0].length - 1) + '&nbsp;';
}
$cell.html(pieces[0]).
css({textAlign: 'right', paddingRight: 0, borderRight: 'none'});
$('<td></td>').
html(pieces[1]).
css({textAlign: 'left', paddingLeft: 0, borderLeft: 'none'}).
addClass(config.addedCellClass).
addClass(existingClasses).
insertAfter($cell);
} else {
$cell.attr('colspan', 2);
}
}
});
});
};
library(shiny)
library(magrittr)
library(dplyr)
test <- data.frame(
a = c(rep("a", 3), rep("b", 3)),
b = c(10000, 10, 1.1, 2000.3000, 2003.01, 200),
c = c(10000, 10, 1.1, 2000.03, 2003.001, 200),
d = c(10000, 10, 1.1, 2000.3000, 2003.01, 200),
e = c(10000, 10, 1.1, 2000.03, 2003.001, 200)
)
ui <- fluidPage(
includeCSS("www/styles.css"),
# tags$head(tags$script(src = "rowsGroup.js")),
tags$head(tags$script(src = "alignColumn.js")),
tags$head(tags$script(src = "decimalAlign.js")),
actionButton("change", "change"),
DT::dataTableOutput("example")
)
server <- function(input, output, session) {
output$example <- DT::renderDT({
dtable <- DT::datatable(test,
filter = "top",
rownames = FALSE,
options = list(
fixedHeader = TRUE,
rowsGroup = list(0),
scrollX = TRUE,
scrollY = TRUE,
fixedColumns = list(leftColumns = 1)
),
# callback = DT::JS("decimalAlign({id: 'example', columns: [1,2]})"),
extensions = c("FixedHeader", "FixedColumns")
)
dep1 <- htmltools::htmlDependency("RowsGroup",
"2.0.0",
src = "www",
script = "rowsGroup.js")
dtable$dependencies <- c(dtable$dependencies, list(dep1))
dtable
})
}
shinyApp(ui = ui, server = server)
decimalAlign = function(message) {
console.log(message)
console.log("its working")
setTimeout(function(){
$('#' + message.id).alignColumn(message.columns)
}, 200);
}
Shiny.addCustomMessageHandler("decimal_align", decimalAlign)
/*! RowsGroup for DataTables v2.0.0
* 2015-2016 Alexey Shildyakov ashl1future@gmail.com
* 2016 Tibor Wekerle
*/
/**
* @summary RowsGroup
* @description Group rows by specified columns
* @version 2.0.0
* @file dataTables.rowsGroup.js
* @author Alexey Shildyakov (ashl1future@gmail.com)
* @contact ashl1future@gmail.com
* @copyright Alexey Shildyakov
*
* License MIT - http://datatables.net/license/mit
*
* This feature plug-in for DataTables automatically merges columns cells
* based on it's values equality. It supports multi-column row grouping
* in according to the requested order with dependency from each previous
* requested columns. Now it supports ordering and searching.
* Please see the example.html for details.
*
* Rows grouping in DataTables can be enabled by using any one of the following
* options:
*
* * Setting the `rowsGroup` parameter in the DataTables initialisation
* to array which containes columns selectors
* (https://datatables.net/reference/type/column-selector) used for grouping. i.e.
* rowsGroup = [1, 'columnName:name', ]
* * Setting the `rowsGroup` parameter in the DataTables defaults
* (thus causing all tables to have this feature) - i.e.
* `$.fn.dataTable.defaults.RowsGroup = [0]`.
* * Creating a new instance: `new $.fn.dataTable.RowsGroup( table, columnsForGrouping );`
* where `table` is a DataTable's API instance and `columnsForGrouping` is the array
* described above.
*
* For more detailed information please see:
*
*/
(function($){
ShowedDataSelectorModifier = {
order: 'current',
page: 'current',
search: 'applied',
}
GroupedColumnsOrderDir = 'asc';
/*
* columnsForGrouping: array of DTAPI:cell-selector for columns for which rows grouping is applied
*/
var RowsGroup = function ( dt, columnsForGrouping )
{
this.table = dt.table();
this.columnsForGrouping = columnsForGrouping;
// set to True when new reorder is applied by RowsGroup to prevent order() looping
this.orderOverrideNow = false;
this.mergeCellsNeeded = false; // merge after init
this.order = []
var self = this;
dt.on('order.dt', function ( e, settings) {
if (!self.orderOverrideNow) {
self.orderOverrideNow = true;
self._updateOrderAndDraw()
} else {
self.orderOverrideNow = false;
}
})
dt.on('preDraw.dt', function ( e, settings) {
if (self.mergeCellsNeeded) {
self.mergeCellsNeeded = false;
self._mergeCells()
}
})
dt.on('column-visibility.dt', function ( e, settings) {
self.mergeCellsNeeded = true;
})
dt.on('search.dt', function ( e, settings) {
// This might to increase the time to redraw while searching on tables
// with huge shown columns
self.mergeCellsNeeded = true;
})
dt.on('page.dt', function ( e, settings) {
self.mergeCellsNeeded = true;
})
dt.on('length.dt', function ( e, settings) {
self.mergeCellsNeeded = true;
})
dt.on('xhr.dt', function ( e, settings) {
self.mergeCellsNeeded = true;
})
this._updateOrderAndDraw();
/* Events sequence while Add row (also through Editor)
* addRow() function
* draw() function
* preDraw() event
* mergeCells() - point 1
* Appended new row breaks visible elements because the mergeCells() on previous step doesn't apllied to already processing data
* order() event
* _updateOrderAndDraw()
* preDraw() event
* mergeCells()
* Appended new row now has properly visibility as on current level it has already applied changes from first mergeCells() call (point 1)
* draw() event
*/
};
RowsGroup.prototype = {
setMergeCells: function(){
this.mergeCellsNeeded = true;
},
mergeCells: function()
{
this.setMergeCells();
this.table.draw();
},
_getOrderWithGroupColumns: function (order, groupedColumnsOrderDir)
{
if (groupedColumnsOrderDir === undefined)
groupedColumnsOrderDir = GroupedColumnsOrderDir
var self = this;
var groupedColumnsIndexes = this.columnsForGrouping.map(function(columnSelector){
return self.table.column(columnSelector).index()
})
var groupedColumnsKnownOrder = order.filter(function(columnOrder){
return groupedColumnsIndexes.indexOf(columnOrder[0]) >= 0
})
var nongroupedColumnsOrder = order.filter(function(columnOrder){
return groupedColumnsIndexes.indexOf(columnOrder[0]) < 0
})
var groupedColumnsKnownOrderIndexes = groupedColumnsKnownOrder.map(function(columnOrder){
return columnOrder[0]
})
var groupedColumnsOrder = groupedColumnsIndexes.map(function(iColumn){
var iInOrderIndexes = groupedColumnsKnownOrderIndexes.indexOf(iColumn)
if (iInOrderIndexes >= 0)
return [iColumn, groupedColumnsKnownOrder[iInOrderIndexes][1]]
else
return [iColumn, groupedColumnsOrderDir]
})
groupedColumnsOrder.push.apply(groupedColumnsOrder, nongroupedColumnsOrder)
return groupedColumnsOrder;
},
// Workaround: the DT reset ordering to 'asc' from multi-ordering if user order on one column (without shift)
// but because we always has multi-ordering due to grouped rows this happens every time
_getInjectedMonoSelectWorkaround: function(order)
{
if (order.length === 1) {
// got mono order - workaround here
var orderingColumn = order[0][0]
var previousOrder = this.order.map(function(val){
return val[0]
})
var iColumn = previousOrder.indexOf(orderingColumn);
if (iColumn >= 0) {
// assume change the direction, because we already has that in previos order
return [[orderingColumn, this._toogleDirection(this.order[iColumn][1])]]
} // else This is the new ordering column. Proceed as is.
} // else got milti order - work normal
return order;
},
_mergeCells: function()
{
var columnsIndexes = this.table.columns(this.columnsForGrouping, ShowedDataSelectorModifier).indexes().toArray()
var showedRowsCount = this.table.rows(ShowedDataSelectorModifier)[0].length
this._mergeColumn(0, showedRowsCount - 1, columnsIndexes)
},
// the index is relative to the showed data
// (selector-modifier = {order: 'current', page: 'current', search: 'applied'}) index
_mergeColumn: function(iStartRow, iFinishRow, columnsIndexes)
{
var columnsIndexesCopy = columnsIndexes.slice()
currentColumn = columnsIndexesCopy.shift()
currentColumn = this.table.column(currentColumn, ShowedDataSelectorModifier)
var columnNodes = currentColumn.nodes()
var columnValues = currentColumn.data()
var newSequenceRow = iStartRow,
iRow;
for (iRow = iStartRow + 1; iRow <= iFinishRow; ++iRow) {
if (columnValues[iRow] === columnValues[newSequenceRow]) {
$(columnNodes[iRow]).hide()
} else {
$(columnNodes[newSequenceRow]).show()
$(columnNodes[newSequenceRow]).attr('rowspan', (iRow-1) - newSequenceRow + 1)
if (columnsIndexesCopy.length > 0)
this._mergeColumn(newSequenceRow, (iRow-1), columnsIndexesCopy)
newSequenceRow = iRow;
}
}
$(columnNodes[newSequenceRow]).show()
$(columnNodes[newSequenceRow]).attr('rowspan', (iRow-1)- newSequenceRow + 1)
if (columnsIndexesCopy.length > 0)
this._mergeColumn(newSequenceRow, (iRow-1), columnsIndexesCopy)
},
_toogleDirection: function(dir)
{
return dir == 'asc'? 'desc': 'asc';
},
_updateOrderAndDraw: function()
{
this.mergeCellsNeeded = true;
var currentOrder = this.table.order();
currentOrder = this._getInjectedMonoSelectWorkaround(currentOrder);
this.order = this._getOrderWithGroupColumns(currentOrder)
this.table.order($.extend(true, Array(), this.order))
this.table.draw()
},
};
$.fn.dataTable.RowsGroup = RowsGroup;
$.fn.DataTable.RowsGroup = RowsGroup;
// Automatic initialisation listener
$(document).on( 'init.dt', function ( e, settings ) {
if ( e.namespace !== 'dt' ) {
return;
}
var api = new $.fn.dataTable.Api( settings );
if ( settings.oInit.rowsGroup ||
$.fn.dataTable.defaults.rowsGroup )
{
options = settings.oInit.rowsGroup?
settings.oInit.rowsGroup:
$.fn.dataTable.defaults.rowsGroup;
var rowsGroup = new RowsGroup( api, options );
$.fn.dataTable.Api.register( 'rowsgroup.update()', function () {
rowsGroup.mergeCells();
return this;
} );
$.fn.dataTable.Api.register( 'rowsgroup.updateNextDraw()', function () {
rowsGroup.setMergeCells();
return this;
} );
}
} );
}(jQuery));
/*
TODO: Provide function which determines the all <tr>s and <td>s with "rowspan" html-attribute is parent (groupped) for the specified <tr> or <td>. To use in selections, editing or hover styles.
TODO: Feature
Use saved order direction for grouped columns
Split the columns into grouped and ungrouped.
user = grouped+ungrouped
grouped = grouped
saved = grouped+ungrouped
For grouped uses following order: user -> saved (because 'saved' include 'grouped' after first initialisation). This should be done with saving order like for 'groupedColumns'
For ungrouped: uses only 'user' input ordering
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment