I'd like to use the resizable function in jQuery to resize column widths in a shiny application. Unfortunately, I'm not sure how to pass along options to the jqui_resizable function, so that I can pass along the also_resize function, or to set maxHeight/minHeight.
Ideally, I'd like for the columns to have a set height (i.e. full-screen), but synchronous re-sizable width's (which also take up the entire width of the screen).
Below is my very simple attempt at demonstrating what I'd like. Unfortunately, it doesn't work as, given the set column widths, resizing one column to make it bigger moves the other column down a row.
Any ideas on how to do this would be greatly appreciated! Thanks!
library(shiny)
library(shinyjqui)
ui <- fluidPage(
fluidRow(
jqui_resizable(column(6,"text")),
jqui_resizable(column(6,"more text"))
)
)
server <- function(input, output, session) {
}
shinyApp(ui, server)
Here is a solution based on this codepen.
File resizableColumns.js:
$(document).ready(function() {
(function($, window, document, undefined) {
$.widget('ce.resizableGrid', {
_create: function() {
this.resizing = false;
this._on({
'mousedown .resizable-column-handle': '_resizeStartHandler',
'mousemove': '_resizeHandler',
'mouseup': '_resizeStopHandler',
'mouseleave': '_resizeStopHandler'
});
},
_init: function() {
this._createHelpers();
},
_createHelpers: function() {
this.element.addClass('resizable-grid');
this.element.find('> .row:not(.resizable-row)').each(function(rowIndex, rowElement) {
var row = $(rowElement);
row.addClass('resizable-row');
row.find('> [class^="col-"]:not(.resizable-column)').each(function(columnIndex, columnElement) {
var column = $(columnElement);
column.addClass('resizable-column');
column.append(
$('<div>', {
class: 'resizable-column-handle resizable-column-handle-w',
'data-is-west': 'true'
}),
$('<div>', {
class: 'resizable-column-handle resizable-column-handle-e',
'data-is-west': 'false'
})
);
});
});
},
_resizeStartHandler: function(event) {
this.resizing = {};
this.resizing.handle = $(event.currentTarget).addClass('resizable-column-handle-resizing');
this.resizing.column = this.resizing.handle.closest('.resizable-column').addClass('resizable-column-resizing');
this.resizing.row = this.resizing.column.closest('.resizable-row').addClass('resizable-row-resizing');
this.resizing.handleIsWest = this.resizing.handle.data('isWest');
this.resizing.directionIsWest = this._getResizingDirectionIsWest(event.pageX);
this.resizing.columnSize = this._getColumnSize(this.resizing.column);
this.resizing.siblings = this._getResizingSiblings(this.resizing.column);
this.resizing.offsets = this._getResizingOffsets();
this.element.addClass('resizable-grid-resizing');
},
_resizeHandler: function(event) {
if(!this.resizing) {
return;
}
this.resizing.directionIsWest = this._getResizingDirectionIsWest(event.pageX);
var resizingOffsetSize = this._getResizingOffsetSize(event.pageX);
if(resizingOffsetSize && (this.resizing.columnSize !== resizingOffsetSize)) {
if(resizingOffsetSize > this.resizing.columnSize) {
var widestColumn = this._getWidestColumn(this.resizing.siblings),
widestColumnSize = this._getColumnSize(widestColumn);
this._setColumnSize(widestColumn, (widestColumnSize - 1));
this._setColumnSize(this.resizing.column, resizingOffsetSize);
} else {
var narrowestColumn = this._getNarrowestColumn(this.resizing.siblings),
narrowestColumnSize = this._getColumnSize(narrowestColumn);
this._setColumnSize(narrowestColumn, (narrowestColumnSize + 1));
this._setColumnSize(this.resizing.column, resizingOffsetSize);
}
this.resizing.columnSize = resizingOffsetSize;
}
},
_resizeStopHandler: function(event) {
if(!this.resizing) {
return;
}
this.resizing.handle.removeClass('resizable-column-handle-resizing');
this.resizing.column.removeClass('resizable-column-resizing');
this.resizing.row.removeClass('resizable-row-resizing');
this.element.removeClass('resizable-grid-resizing');
this.resizing = false;
},
_getResizingDirectionIsWest: function(x) {
var resizingDirectionIsWest;
if(!this.resizing.directionLastX) {
this.resizing.directionLastX = x;
resizingDirectionIsWest = null;
} else {
if(x < this.resizing.directionLastX) {
resizingDirectionIsWest = true;
} else {
resizingDirectionIsWest = false;
}
this.resizing.directionLastX = x;
}
return resizingDirectionIsWest;
},
_getResizingSiblings: function(column) {
return ((this.resizing.handleIsWest) ? column.prevAll() : column.nextAll());
},
_getResizingOffsetSize: function(x) {
var that = this,
resizingOffsetSize;
$.each(this.resizing.offsets, function(index, offset) {
if((that.resizing.directionIsWest && ((x <= offset.end) && (x >= offset.start))) || (!that.resizing.directionIsWest && ((x >= offset.start) && (x <= offset.end)))) {
resizingOffsetSize = offset.size;
}
});
return resizingOffsetSize;
},
_getResizingOffsets: function() {
var that = this,
row = this.resizing.row.clone(),
css = {
'height': '1px',
'min-height': '1px',
'max-height': '1px'
};
row.removeClass('resizable-row resizable-row-resizing').css(css);
row.children().empty().removeClass('resizable-column resizable-column-resizing').css(css);
this.resizing.row.parent().append(row);
var column = row.children().eq(this.resizing.row.children().index(this.resizing.column)),
totalSize = this._getColumnSize(column);
this._getResizingSiblings(column).each(function() {
totalSize += (that._getColumnSize($(this)) - 1);
that._setColumnSize($(this), 1);
});
var size = ((this.resizing.handleIsWest) ? totalSize : 1),
sizeEnd = ((this.resizing.handleIsWest) ? 1 : totalSize),
sizeOperator = ((this.resizing.handleIsWest) ? -1 : 1),
offset = 0,
offsetOperator = ((this.resizing.handleIsWest) ? 1 : 0);
var columnGutter = ((column.outerWidth(true) - column.width()) / 2),
columnWidth = ((this.resizing.handleIsWest) ? false : true);
var resizingOffsets = [];
while(true) {
this._setColumnSize(column, size);
this._setColumnOffset(column, offset);
var left = (Math.floor((column.offset()).left) + columnGutter + ((columnWidth) ? column.width() : 0));
resizingOffsets.push({
start: (left + ((columnGutter * 3) * -1)),
end: (left + (columnGutter * 3)),
size: size
});
if(size === sizeEnd) {
break;
}
size += sizeOperator;
offset += offsetOperator;
}
row.remove();
return resizingOffsets;
},
_getWidestColumn: function(columns) {
var that = this,
widestColumn;
columns.each(function() {
if(!widestColumn || (that._getColumnSize($(this)) > that._getColumnSize(widestColumn))) {
widestColumn = $(this);
}
});
return widestColumn;
},
_getNarrowestColumn: function(columns) {
var that = this,
narrowestColumn;
columns.each(function() {
if(!narrowestColumn || (that._getColumnSize($(this)) < that._getColumnSize(narrowestColumn))) {
narrowestColumn = $(this);
}
});
return narrowestColumn;
},
_getColumnSize: function(column) {
var columnSize;
$.each($.trim(column.attr('class')).split(' '), function(index, value) {
if(value.match(/^col-/) && !value.match(/-offset-/)) {
columnSize = parseInt($.trim(value).replace(/\D/g, ''), 10);
}
});
return columnSize;
},
_setColumnSize: function(column, size) {
column.toggleClass([
['col', 'sm', this._getColumnSize(column)].join('-'), ['col', 'sm', size].join('-')
].join(' '));
},
_getColumnOffset: function(column) {
var columnOffset;
$.each($.trim(column.attr('class')).split(' '), function(index, value) {
if(value.match(/^col-/) && value.match(/-offset-/)) {
columnOffset = parseInt($.trim(value).replace(/\D/g, ''), 10);
}
});
return columnOffset;
},
_setColumnOffset: function(column, offset) {
var currentColumnOffset,
toggleClasses = [];
if((currentColumnOffset = this._getColumnOffset(column)) !== undefined) {
toggleClasses.push(['col', 'sm', 'offset', currentColumnOffset].join('-'));
}
toggleClasses.push(['col', 'sm', 'offset', offset].join('-'));
column.toggleClass(toggleClasses.join(' '));
},
_destroy: function() {
this._destroyHelpers();
},
_destroyHelpers: function() {
this.element.find('.resizable-column-handle').remove();
this.element.find('.resizable-column').removeClass('resizable-column resizable-column-resizing');
this.element.find('.resizable-row').removeClass('resizable-row resizable-row-resizing');
this.element.removeClass('resizable-grid resizable-grid-resizing');
}
});
})(jQuery, window, document);
$('#layout').resizableGrid();
});
File resizableColumns.css:
.container {
margin-top: 50px;
}
.row {
background-color: rgba(189, 195, 199, 0.25);
}
.row:hover {
background-color: rgba(189, 195, 199, 0.5);
}
.row > .col-sm-1,
.row > .col-sm-2,
.row > .col-sm-3,
.row > .col-sm-4,
.row > .col-sm-5,
.row > .col-sm-6,
.row > .col-sm-7,
.row > .col-sm-8,
.row > .col-sm-9,
.row > .col-sm-10,
.row > .col-sm-11,
.row > .col-sm-12 {
min-height: 100px;
}
.row > .col-sm-1:before,
.row > .col-sm-2:before,
.row > .col-sm-3:before,
.row > .col-sm-4:before,
.row > .col-sm-5:before,
.row > .col-sm-6:before,
.row > .col-sm-7:before,
.row > .col-sm-8:before,
.row > .col-sm-9:before,
.row > .col-sm-10:before,
.row > .col-sm-11:before,
.row > .col-sm-12:before {
content: '';
display: block;
position: absolute;
top: 0;
right: 15px;
bottom: 0;
left: 15px;
background-color: rgba(52, 152, 219, 0.25);
}
.row > .col-sm-1:hover:before,
.row > .col-sm-2:hover:before,
.row > .col-sm-3:hover:before,
.row > .col-sm-4:hover:before,
.row > .col-sm-5:hover:before,
.row > .col-sm-6:hover:before,
.row > .col-sm-7:hover:before,
.row > .col-sm-8:hover:before,
.row > .col-sm-9:hover:before,
.row > .col-sm-10:hover:before,
.row > .col-sm-11:hover:before,
.row > .col-sm-12:hover:before {
background-color: rgba(52, 152, 219, 0.5);
}
/* Resizable Grid */
.resizable-grid > .resizable-row > .resizable-column > .resizable-column-handle {
z-index: 100;
display: none;
position: absolute;
top: 0;
height: 100%;
width: 6px;
cursor: col-resize;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-ms-touch-action: none;
touch-action: none;
}
.resizable-grid > .resizable-row > .resizable-column > .resizable-column-handle-w {
left: 12px;
}
.resizable-grid > .resizable-row > .resizable-column > .resizable-column-handle-e {
right: 12px;
}
.resizable-grid > .resizable-row > .resizable-column:first-child:not(:last-child) > .resizable-column-handle-e,
.resizable-grid > .resizable-row > .resizable-column:not(:first-child):not(:last-child) > .resizable-column-handle-w,
.resizable-grid > .resizable-row > .resizable-column:not(:first-child):not(:last-child) > .resizable-column-handle-e,
.resizable-grid > .resizable-row > .resizable-column:last-child:not(:first-child) > .resizable-column-handle-w {
display: block;
}
.resizable-grid-resizing {
cursor: col-resize;
}
.resizable-grid > .resizable-row-resizing > .resizable-column:not(.resizable-column-resizing) {
opacity: 0.5;
}
Put the files resizableColumns.js and resizableColumns.css in the www subfolder of the app folder. The app:
library(shiny)
ui <- fluidPage(
tags$head(
tags$script(src = "https://code.jquery.com/ui/1.12.1/jquery-ui.js"),
tags$link(rel = "stylesheet", href = "resizableColumns.css"),
tags$script(src = "resizableColumns.js")
),
tags$div(
id = "layout",
fluidRow(
column(
width = 3,
h3("column1")
),
column(
width = 3,
h3("column2")
),
column(
width = 3,
h3("column3")
),
column(
width = 3,
h3("column4")
)
)
)
)
server <- function(input, output){}
shinyApp(ui, server)