Skip to content

Commit

Permalink
Support for resizing columns (#5840)
Browse files Browse the repository at this point in the history
Closes #4806.

* Improve default layout by initially capping column widths to 300px

* Make resizing area even around column boundaries

* Only keep the mouse event listeners when needed

* Remember widths of all columns for grid re-renderings

* Introduce minimum width on columns for their default sizing

* Set column widths in em so that they are properly resized when zooming
  • Loading branch information
wetneb committed Sep 24, 2023
1 parent 3f2df80 commit 1ae36a2
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

function DataTableColumnHeaderUI(dataTableView, column, columnIndex, td) {
function DataTableColumnHeaderUI(dataTableView, column, columnIndex, td, col) {
this._dataTableView = dataTableView;
this._column = column;
this._columnIndex = columnIndex;
this._td = td;
this._col = col;

this._render();
}
Expand All @@ -58,6 +59,17 @@ DataTableColumnHeaderUI.prototype.getColumn = function() {
return this._column;
};

// global state for the resizing of column headers
DataTableColumnHeaderUI.resizingState = {
dragging: false, // whether we are currently resizing any column
col: null, // the column being resized
columnName: null, // the name of the column being resized
originalWidth: 0, // the original width of the header when the dragging started
originalPosition: 0, // the original position of the cursor when the dragging started
moveListener: null, // the event listener for mouse move events
releaseListener: null, // the event listener for mouse release events
};

DataTableColumnHeaderUI.prototype._render = function() {
var self = this;
var td = $(this._td);
Expand All @@ -73,6 +85,56 @@ DataTableColumnHeaderUI.prototype._render = function() {
self.updateColumnStats();
};

DataTableColumnHeaderUI.prototype._startResizing = function(clickEvent) {
var self = this;
clickEvent.preventDefault();
var state = DataTableColumnHeaderUI.resizingState;
state.dragging = true;
state.col = self._col;
state.columnName = self._column.name;
state.originalWidth = self._col.width();
state.originalPosition = clickEvent.pageX;
// for conversion from px to em
state.emFactor = parseFloat(getComputedStyle($(".data-table-container colgroup")[0]).fontSize);

$('body')
.on('mousemove', DataTableColumnHeaderUI.mouseMoveListener)
.on('mouseup', DataTableColumnHeaderUI.mouseReleaseListener);
};

// event handlers to react to mouse moves during resizing
DataTableColumnHeaderUI.mouseMoveListener = function(e) {
var state = DataTableColumnHeaderUI.resizingState;
if (state.dragging) {
var totalMovement = e.pageX - state.originalPosition;
var newWidth = state.originalWidth + totalMovement;
if (state.col.css('min-width')) {
state.col.css('min-width', '');
}
state.col.width(newWidth);

e.preventDefault();
}
};

DataTableColumnHeaderUI.mouseReleaseListener = function(e) {
// only capture left clicks
if (e.button !== 0) {
return;
}
var state = DataTableColumnHeaderUI.resizingState;
if (state.dragging) {
var totalMovement = e.pageX - state.originalPosition;
var newWidth = state.originalWidth + totalMovement;
state.col.width((newWidth / state.emFactor) + 'em');
state.dragging = false;
$('body')
.off('mousemove', DataTableColumnHeaderUI.mouseMoveListener)
.off('mouseup', DataTableColumnHeaderUI.mouseReleaseListener);
}
e.preventDefault();
};

DataTableColumnHeaderUI.prototype.updateColumnStats = function() {
var self = this;
var container = $(this._td).find('.recon-stats-container');
Expand Down
105 changes: 70 additions & 35 deletions main/webapp/modules/core/scripts/views/data-table/data-table-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,25 @@ DataTableView.prototype.render = function() {
var oldTableDiv = this._div.find(".data-table-container");
var scrollLeft = (oldTableDiv.length > 0) ? oldTableDiv[0].scrollLeft : 0;

// utility to convert px widths to em.
// The factor is computed only on demand (and once) because it might trigger some
// DOM rendering given that it uses computed styles
var emFactor = null;
var getEmFactor = function() {
if (emFactor === null) {
emFactor = parseFloat(getComputedStyle($(".data-table-container colgroup")[0]).fontSize);
}
return emFactor;
};
// store the current width of each column to be able to restore it later
this._div.find(".data-table-container col").each(function(index) {
var column = $(this);
if (column.data('name')) {
var width = column.width() / getEmFactor();
DataTableView.columnWidthCache.set(column.data('name'), width);
}
});

var html = $(
'<div class="viewpanel-header">' +
'<div class="viewpanel-rowrecord" bind="rowRecordControls">'+$.i18n('core-views/show-as')+': ' +
Expand All @@ -112,6 +131,7 @@ DataTableView.prototype.render = function() {
'</div>' +
'<div bind="dataTableContainer" class="data-table-container">' +
'<table class="data-table">'+
'<colgroup bind="colGroup"></colgroup>'+
'<thead bind="tableHeader" class="data-table-header">'+
'</thead>'+
'<tbody bind="table" class="data-table">'+
Expand Down Expand Up @@ -144,7 +164,7 @@ DataTableView.prototype.render = function() {
this._renderSortingControls(elmts.sortingControls);
}

this._renderDataTables(elmts.table[0], elmts.tableHeader[0]);
this._renderDataTables(elmts.table[0], elmts.tableHeader[0], elmts.colGroup);
this._div.empty().append(html);

// show/hide null values in cells
Expand Down Expand Up @@ -273,46 +293,18 @@ DataTableView.prototype._checkPaginationSize = function(gridPageSize, defaultGri
return newGridPageSize;
};

DataTableView.prototype._renderDataTables = function(table, tableHeader) {
DataTableView.prototype._renderDataTables = function(table, tableHeader, colGroup) {
var self = this;

var columns = theProject.columnModel.columns;

/*------------------------------------------------------------
* Column Group Headers
*------------------------------------------------------------
*/

var renderColumnKeys = function(keys) {
if (keys.length > 0) {
var tr = tableHeader.insertRow(tableHeader.rows.length);
$(tr.appendChild(document.createElement("th"))).attr('colspan', '3'); // star, flag, row index

for (var c = 0; c < columns.length; c++) {
var column = columns[c];
var th = tr.appendChild(document.createElement("th"));
if (self._collapsedColumnNames.hasOwnProperty(column.name)) {
$(th).html('&nbsp;');
} else {
for (var k = 0; k < keys.length; k++) {
// if a node is a key in the tree-based data (JSON/XML/etc), then also display a dropdown arrow (non-functional currently)
// See https://github.com/OpenRefine/OpenRefine/blob/master/main/src/com/google/refine/model/ColumnGroup.java
// and https://github.com/OpenRefine/OpenRefine/tree/master/main/src/com/google/refine/importers/tree
if (c == keys[k]) {
$('<img />').attr("src", "images/down-arrow.png").appendTo(th);
break;
}
}
}
}
}
};

/*------------------------------------------------------------
* Column Headers with Menus
*------------------------------------------------------------
*/

colGroup.empty();
self._renderTableHeader(tableHeader, colGroup);

/*------------------------------------------------------------
* Data Cells
Expand Down Expand Up @@ -416,11 +408,16 @@ DataTableView.prototype._renderDataTables = function(table, tableHeader) {
}
};

DataTableView.prototype._renderTableHeader = function(tableHeader) {
// cache which remembers the set width of each column (used when the grid is re-rendered)
DataTableView.columnWidthCache = new Map();

DataTableView.prototype._renderTableHeader = function(tableHeader, colGroup) {
var self = this;
var columns = theProject.columnModel.columns;
var trHead = document.createElement('tr');
tableHeader.append(trHead);

// header for the first three columns (star, flag, row number)
DOM.bind(
$(trHead.appendChild(document.createElement("th")))
.attr("colspan", "3")
Expand All @@ -433,18 +430,56 @@ DataTableView.prototype._renderTableHeader = function(tableHeader) {
).dropdownMenu.on('click',function() {
self._createMenuForAllColumns(this);
});
$('<col>').attr('span', 3).appendTo(colGroup);

// headers for the normal columns
this._columnHeaderUIs = [];
var createColumnHeader = function(column, index) {
var th = trHead.appendChild(document.createElement("th"));
$(th).addClass("column-header").attr('title', column.name);
var col = $('<col>')
.attr('span', 1)
.data('name', column.name)
.appendTo(colGroup);
var cachedWidth = DataTableView.columnWidthCache.get(column.name);
if (cachedWidth !== undefined && !self._collapsedColumnNames.hasOwnProperty(column.name)) {
col.width(cachedWidth + 'em');
} else {
// Not set in CSS directly because the user needs to be able to override that by dragging
col.css('min-width', '5em');
}
if (self._collapsedColumnNames.hasOwnProperty(column.name)) {
$(th).html("&nbsp;").on('click',function(evt) {
$(th).html("&nbsp;").on('mousedown',function(evt) {
delete self._collapsedColumnNames[column.name];
self.render();
});
} else {
var columnHeaderUI = new DataTableColumnHeaderUI(self, column, index, th);
var columnHeaderUI = new DataTableColumnHeaderUI(self, column, index, th, col);
self._columnHeaderUIs.push(columnHeaderUI);

// add resizing controls
var resizerLeft = $('<div></div>').addClass('column-header-resizer-left')
.appendTo(th);
resizerLeft.on('mousedown', function(e) {
// only capture left clicks
if (e.button !== 0) {
return;
}
columnHeaderUI._startResizing(e);
});

// add resizing control for the previous column (if uncollapsed)
if (index > 0 && !self._collapsedColumnNames.hasOwnProperty(columns[index-1].name)) {
var resizerRight = $('<div></div>').addClass('column-header-resizer-right')
.appendTo(th);
resizerRight.on('mousedown', function(e) {
// only capture left clicks
if (e.button !== 0) {
return;
}
self._columnHeaderUIs[index - 1]._startResizing(e);
});
}
}
};

Expand Down
34 changes: 30 additions & 4 deletions main/webapp/modules/core/styles/views/data-table-view.css
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
padding: 0;
font-size: 1.05em;
border-collapse: separate;
width: max-content;
}

.data-table td, .data-table-header td, .data-table-header th {
Expand All @@ -79,6 +80,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
border-bottom: 0.02rem dotted #ddd;
border-right: 0.02rem solid #ddd;
position: relative;
max-width: 300px;
}

.data-table-header td, .data-table-header th {
Expand Down Expand Up @@ -113,6 +115,30 @@ th {
text-align: left;
}

.column-header-title {
overflow: hidden;
text-overflow: clip;
}

th .column-header-resizer-left, th .column-header-resizer-right {
display: block;
position: absolute;
top: 0;
height: 100%;
margin: 0;
padding: 0;
cursor: col-resize;
width: 6px;
}

th .column-header-resizer-left {
right: 0;
}

th .column-header-resizer-right {
left: 0;
}

table.data-header-table>tbody>tr>td {
overflow: hidden !important;
}
Expand Down Expand Up @@ -145,14 +171,13 @@ table.data-table td.column-header, table.data-table th.column-header {
}

.column-header-name {
margin: 0 0 0 21px;
padding: 4px 0 0 0;
display: block;
display: inline-block;
position: absolute;
}

.column-header-menu {
float: left;
display: block;
display: inline-block;
margin: 0 4px 0 0;
width: 17px;
height: 19px;
Expand Down Expand Up @@ -192,6 +217,7 @@ div.data-table-cell-content {
color: #222;
position: relative;
white-space: pre-wrap;
overflow-wrap: anywhere;
}

div.data-table-cell-content-numeric {
Expand Down

0 comments on commit 1ae36a2

Please sign in to comment.