mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Fixes #1243 - Allow to resize the columns of the result table in the
Query editor.
This commit is contained in:
committed by
Ashesh Vashi
parent
486da5e12c
commit
278f18bab8
501
web/pgadmin/static/js/backgrid/backgrid-sizeable-columns.js
Normal file
501
web/pgadmin/static/js/backgrid/backgrid-sizeable-columns.js
Normal file
@@ -0,0 +1,501 @@
|
||||
/*
|
||||
backgrid-sizeable-columns
|
||||
https://github.com/FortesSolutions/backgrid-sizeable-columns
|
||||
|
||||
Copyright (c) 2016 Fortes Solutions
|
||||
Licensed under the MIT @license.
|
||||
*/
|
||||
(function (root, factory) {
|
||||
// CommonJS
|
||||
if (typeof exports == "object") {
|
||||
module.exports = factory(require("underscore"), require("backgrid"));
|
||||
}
|
||||
// AMD. Register as an anonymous module.
|
||||
else if (typeof define === 'function' && define.amd) {
|
||||
define(['underscore', 'backgrid'], factory);
|
||||
}
|
||||
// Browser
|
||||
else {
|
||||
factory(root._, root.Backgrid);
|
||||
}
|
||||
|
||||
}(this, function (_, Backgrid) {
|
||||
"use strict";
|
||||
|
||||
// Adds width support to columns
|
||||
Backgrid.Extension.SizeAbleColumns = Backbone.View.extend({
|
||||
/** @property */
|
||||
tagName: "colgroup",
|
||||
|
||||
/**
|
||||
* Initializer
|
||||
* @param options
|
||||
*/
|
||||
initialize: function (options) {
|
||||
this.grid = options.grid;
|
||||
|
||||
// Attach event listeners once on render
|
||||
this.listenTo(this.grid.header, "backgrid:header:rendered", this.render);
|
||||
this.listenTo(this.grid.columns, "width:auto", this.setWidthAuto);
|
||||
this.listenTo(this.grid.columns, "width:fixed", this.setWidthFixed);
|
||||
this.listenTo(this.grid, "backgrid:refresh", this.setColToActualWidth);
|
||||
this.listenTo(this.grid.collection, "add remove reset", this.setColToActualWidth);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds sizeable columns using <col> elements in a <colgroup>
|
||||
* @returns {Backgrid.Extension.SizeAbleColumns}
|
||||
*/
|
||||
render: function () {
|
||||
var view = this;
|
||||
view.$el.empty();
|
||||
|
||||
view.grid.columns.each(function (col) {
|
||||
if (typeof col.get("renderable") == "undefined" || col.get("renderable")) {
|
||||
var $colEl = $("<col>").appendTo(view.$el).attr("data-column-cid", col.cid);
|
||||
var colWidth = col.get("width");
|
||||
var colMinWidth = col.get("minWidth");
|
||||
var colMaxWidth = col.get("maxWidth");
|
||||
if (colWidth && colWidth != "*") {
|
||||
if (colMinWidth && colWidth < colMinWidth) {
|
||||
colWidth = colMinWidth;
|
||||
}
|
||||
if (colMaxWidth && colWidth > colMaxWidth) {
|
||||
colWidth = colMaxWidth;
|
||||
}
|
||||
$colEl.width(colWidth);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add data attribute to column cells
|
||||
if (view.grid.header.headerRows) {
|
||||
_.each(view.grid.header.headerRows, function(row) {
|
||||
_.each(row.cells, function(cell) {
|
||||
cell.$el.attr("data-column-cid", cell.column.cid);
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
_.each(view.grid.header.row.cells, function(cell) {
|
||||
cell.$el.attr("data-column-cid", cell.column.cid);
|
||||
});
|
||||
}
|
||||
|
||||
// Trigger event
|
||||
view.grid.collection.trigger("backgrid:colgroup:changed");
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a <col> element belonging to given model
|
||||
* @param colModel Backgrid.Column
|
||||
* @returns {*|JQuery|any|jQuery}
|
||||
* @private
|
||||
*/
|
||||
getColumnElement: function (colModel) {
|
||||
return this.$el.find('col[data-column-cid="' + colModel.cid + '"]');
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the column width of given model
|
||||
* @param colModel Backgrid.Column
|
||||
* @returns {Integer}
|
||||
* @private
|
||||
*/
|
||||
getHeaderElementWidth: function(colModel) {
|
||||
return this.grid.header.$el.find("th[data-column-cid='" + colModel.cid + "']").outerWidth();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets a width of the given column to "*" (auto)
|
||||
* @param colModel Backgrid.Column
|
||||
* @private
|
||||
*/
|
||||
setWidthAuto: function (colModel) {
|
||||
// Get column element
|
||||
var $colElement = this.getColumnElement(colModel);
|
||||
|
||||
// Save width
|
||||
colModel.set("width", "*");
|
||||
|
||||
// Set column width to auto
|
||||
$colElement.css("width", "");
|
||||
|
||||
this.grid.collection.trigger("backgrid:colgroup:updated");
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets a width of the given column to a fixed width defined in the model.
|
||||
* @param colModel Backgrid.Column
|
||||
* @private
|
||||
*/
|
||||
setWidthFixed: function (colModel) {
|
||||
// Get column element
|
||||
var $colElement = this.getColumnElement(colModel);
|
||||
|
||||
// Get width of header element
|
||||
var width = this.getHeaderElementWidth(colModel);
|
||||
|
||||
// Set column width to the original width
|
||||
$colElement.css("width", width);
|
||||
|
||||
// Save width
|
||||
colModel.set("width", width);
|
||||
|
||||
this.grid.collection.trigger("backgrid:colgroup:updated");
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the view's <col> elements to current width
|
||||
* @private
|
||||
*/
|
||||
setColToActualWidth: function() {
|
||||
var view = this;
|
||||
var changed = false;
|
||||
_.each(view.grid.header.row.cells, function(cell) {
|
||||
var $colEl = view.getColumnElement(cell.column);
|
||||
if (cell.column.get("width") !== "*") {
|
||||
changed = changed || $colEl.width() == cell.$el.outerWidth();
|
||||
$colEl.width(cell.$el.outerWidth());
|
||||
}
|
||||
});
|
||||
|
||||
if (changed) {
|
||||
view.grid.collection.trigger("backgrid:colgroup:updated");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Makes column resizable; requires Backgrid.Extension.sizeAbleColumns
|
||||
Backgrid.Extension.SizeAbleColumnsHandlers = Backbone.View.extend({
|
||||
minWidthDynamicColumns: 80,
|
||||
|
||||
/**
|
||||
* Initializer
|
||||
* @param options
|
||||
*/
|
||||
initialize: function (options) {
|
||||
this.sizeAbleColumns = options.sizeAbleColumns;
|
||||
this.grid = this.sizeAbleColumns.grid;
|
||||
this.columns = this.grid.columns;
|
||||
this.header = this.grid.header;
|
||||
|
||||
this.saveColumnWidth = options.saveColumnWidth;
|
||||
if (options.minWidthDynamicColumns != null) {
|
||||
this.minWidthDynamicColumns = options.minWidthDynamicColumns;
|
||||
}
|
||||
|
||||
this.setHeaderElements();
|
||||
this.attachEvents();
|
||||
|
||||
this.checkSpacerColumn();
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds handlers to resize the columns
|
||||
* @returns {Backgrid.Extension.SizeAbleColumnsHandlers}
|
||||
*/
|
||||
render: function () {
|
||||
var view = this;
|
||||
view.$el.empty();
|
||||
|
||||
// For now, loop tds in first row
|
||||
_.each(view.headerElements, function (columnEl, index) {
|
||||
// Get matching col element
|
||||
var $column = $(columnEl);
|
||||
var columnModelCid = $column.data("column-cid");
|
||||
var $col = view.sizeAbleColumns.$el.find("col[data-column-cid=" + columnModelCid + "]");
|
||||
var columnModel = view.columns.get({ cid: columnModelCid});
|
||||
|
||||
if (columnModel && columnModel.get("resizeable")) {
|
||||
// Create helper elements
|
||||
var $resizeHandler = $("<div></div>")
|
||||
.addClass("resizeHandler")
|
||||
.attr("data-column-index", index)
|
||||
.appendTo(view.$el);
|
||||
var $resizeHandlerHelper = $("<div></div>")
|
||||
.hide()
|
||||
.addClass("grid-draggable-cursor")
|
||||
.appendTo($resizeHandler);
|
||||
|
||||
// Make draggable
|
||||
$resizeHandler.on("mousedown", function (e) {
|
||||
view._stopEvent(e);
|
||||
var startX = Math.round($resizeHandler.offset().left);
|
||||
var $doc = $(document);
|
||||
var handlerNonDragSize = $resizeHandler.outerWidth();
|
||||
|
||||
// Set class
|
||||
$resizeHandler.addClass("grid-draggable");
|
||||
$resizeHandlerHelper.show();
|
||||
|
||||
// Follow the mouse
|
||||
var mouseMoveHandler = function (evt) {
|
||||
view._stopEvent(evt);
|
||||
|
||||
// Check for constraints
|
||||
var minWidth = columnModel.get("minWidth");
|
||||
if (!minWidth || minWidth < 20) {
|
||||
minWidth = 20;
|
||||
}
|
||||
var maxWidth = columnModel.get("maxWidth");
|
||||
var newLeftPos = evt.pageX;
|
||||
var currentWidth = columnModel.get("width");
|
||||
var newWidth = currentWidth + (newLeftPos - startX) - handlerNonDragSize / 2;
|
||||
|
||||
if (minWidth && newWidth <= minWidth) {
|
||||
newLeftPos = startX - (currentWidth - minWidth) + handlerNonDragSize / 2;
|
||||
}
|
||||
if (maxWidth && newWidth >= maxWidth) {
|
||||
newLeftPos = startX + maxWidth - currentWidth + handlerNonDragSize / 2;
|
||||
}
|
||||
|
||||
// Apply mouse change to handler
|
||||
$resizeHandler.offset({
|
||||
left: newLeftPos
|
||||
});
|
||||
};
|
||||
$doc.on("mousemove", mouseMoveHandler);
|
||||
|
||||
// Add handler to listen for mouseup
|
||||
var mouseUpHandler = function (evt) {
|
||||
// Cleanup
|
||||
view._stopEvent(evt);
|
||||
$resizeHandler.removeClass("grid-draggable");
|
||||
$resizeHandlerHelper.hide();
|
||||
$doc.off("mouseup", mouseUpHandler);
|
||||
$doc.off("mousemove", mouseMoveHandler);
|
||||
|
||||
// Adjust column size
|
||||
var stopX = Math.round($resizeHandler.offset().left);
|
||||
var offset = (startX - stopX);
|
||||
var oldWidth = $column.outerWidth();
|
||||
var newWidth = oldWidth - offset;
|
||||
|
||||
// Save width and trigger events
|
||||
if (newWidth != oldWidth) {
|
||||
view._disableAutoWithColumns();
|
||||
$col.width(newWidth);
|
||||
|
||||
if (view.saveColumnWidth) {
|
||||
// Save updated width
|
||||
columnModel.set("width", newWidth, {silent: true});
|
||||
}
|
||||
|
||||
// Trigger event
|
||||
columnModel.trigger("resize", columnModel, newWidth, oldWidth, offset);
|
||||
}
|
||||
view.updateHandlerPosition();
|
||||
};
|
||||
$doc.on("mouseup", mouseUpHandler);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Position drag handlers
|
||||
view.updateHandlerPosition();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Disable automatic width for all columns and set the current width as the new width
|
||||
* @returns {undefined}
|
||||
*/
|
||||
_disableAutoWithColumns: function () {
|
||||
// Check if we have an autosize column, if so, trigger resize on it as well
|
||||
var autoWidthColumns = this.columns.filter(function (column) {
|
||||
return column.get('width') == '*' && column.get('name') != '__spacerColumn';
|
||||
});
|
||||
|
||||
for (var i = 0; i < autoWidthColumns.length; i++) {
|
||||
var autoWidthColumn = autoWidthColumns[i];
|
||||
|
||||
// find the corresponding header element to determine width and cid from
|
||||
var $headerElement;
|
||||
for (var j = 0; j < this.headerElements.length; j++) {
|
||||
$headerElement = $(this.headerElements[j]);
|
||||
if ($headerElement.hasClass(autoWidthColumn.get('name'))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var outerWidth = $headerElement.outerWidth();
|
||||
var columnModelCid = $headerElement.data("column-cid");
|
||||
this.sizeAbleColumns.$el.find("col[data-column-cid=" + columnModelCid + "]").width(outerWidth);
|
||||
|
||||
var column = this.columns.get({ cid: columnModelCid});
|
||||
if (this.saveColumnWidth) {
|
||||
column.set('width', outerWidth, {silent: true});
|
||||
}
|
||||
column.trigger('resize', column, outerWidth);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function to prevent event propagation
|
||||
* @param e {Event}
|
||||
* @private
|
||||
*/
|
||||
_stopEvent: function (e) {
|
||||
if (e.stopPropagation) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
e.cancelBubble = true;
|
||||
e.returnValue = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add listeners
|
||||
* @private
|
||||
*/
|
||||
attachEvents: function () {
|
||||
var view = this;
|
||||
view.listenTo(view.columns, "change:resizeable", view.render);
|
||||
view.listenTo(view.columns, "resize width:auto width:fixed add remove", view.checkSpacerColumn);
|
||||
view.listenTo(view.grid.collection, "backgrid:colgroup:updated", view.updateHandlerPosition);
|
||||
view.listenTo(view.grid.collection, "backgrid:colgroup:changed", function () {
|
||||
// Wait for callstack to be cleared
|
||||
_.defer(function () {
|
||||
view.setHeaderElements();
|
||||
view.render();
|
||||
});
|
||||
});
|
||||
|
||||
this.resizeEvtHandler = _.debounce(_.bind(view.updateHandlerPosition, view), 250);
|
||||
$(window).on("resize", this.resizeEvtHandler);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether a spacer column is nessecary. This is the case when widths are set on all columns and it's smaller
|
||||
* that the grid element width.
|
||||
* @private
|
||||
*/
|
||||
checkSpacerColumn: function () {
|
||||
var view = this;
|
||||
var spacerColumn = _.first(view.columns.where({name: "__spacerColumn"}));
|
||||
var autoColumns = view.columns.filter(function (col) {
|
||||
return col.get("width") == "*" && col.get("name") != "__spacerColumn";
|
||||
});
|
||||
|
||||
// Check if there is a column with auto width, if so, no need to do anything
|
||||
if (_.isEmpty(autoColumns)) {
|
||||
var totalWidth = view.columns.reduce(function (memo, num) {
|
||||
// count 0 pixels for the spacer column
|
||||
var colWidth = (num.get("width") == "*") ? 0 : num.get("width");
|
||||
return memo + colWidth;
|
||||
}, 0);
|
||||
var gridWidth = view.grid.$el.width();
|
||||
|
||||
if (gridWidth > totalWidth) {
|
||||
// The grid is larger than the cumulative column width, we need a spacer column
|
||||
if (!spacerColumn) {
|
||||
// Create new column model
|
||||
view.columns.add(view.getSpacerColumn(), {silent: true});
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (spacerColumn) {
|
||||
view.columns.remove(spacerColumn, {silent: true});
|
||||
}
|
||||
|
||||
var columnsWidth = this.columns.reduce(function (memo, column) {
|
||||
return memo + (column.get('width') == '*' ? this.minWidthDynamicColumns : column.get('width'));
|
||||
}, 0, this);
|
||||
// min width on columns does not work, therefore place the min width on the table itself
|
||||
this.grid.$el.css('min-width', columnsWidth + 'px');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a spacer column definition
|
||||
* @returns Object
|
||||
* @private
|
||||
*/
|
||||
getSpacerColumn: function() {
|
||||
return Backgrid.Extension.SizeAbleColumns.spacerColumnDefinition;
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the position of the handlers
|
||||
* @private
|
||||
*/
|
||||
updateHandlerPosition: function () {
|
||||
var view = this;
|
||||
_.each(view.headerElements, function (columnEl, index) {
|
||||
var $column = $(columnEl);
|
||||
|
||||
// Get handler for current column and update position
|
||||
view.$el.children().filter("[data-column-index='" + index + "']")
|
||||
.css("left", $column.position().left + $column.outerWidth());
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Find the current header elements and stores them
|
||||
*/
|
||||
setHeaderElements: function () {
|
||||
var self = this;
|
||||
var rows = self.grid.header.headerRows || [self.grid.header.row];
|
||||
self.headerCells = [];
|
||||
|
||||
// Loop all rows
|
||||
_.each(rows, function (row) {
|
||||
// Loop cells of row
|
||||
_.each(row.cells, function (cell) {
|
||||
var columnModel = self.columns.get({cid: cell.column.cid});
|
||||
if (!_.isEmpty(columnModel)) {
|
||||
self.headerCells.push({
|
||||
$el: cell.$el,
|
||||
el: cell.el,
|
||||
column: columnModel
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Sort cells
|
||||
var headerCells = _.sortBy(self.headerCells, function (cell) {
|
||||
return self.columns.indexOf(cell.column);
|
||||
});
|
||||
|
||||
// Filter cells
|
||||
self.headerCells = _.filter(headerCells, function(cell) {
|
||||
return cell.column.get("renderable") === true ||
|
||||
typeof cell.column.get("renderable") === "undefined";
|
||||
});
|
||||
|
||||
self.headerElements = _.map(self.headerCells, function (cell) {
|
||||
return cell.el;
|
||||
});
|
||||
},
|
||||
|
||||
remove: function() {
|
||||
$(window).off("resize", this.resizeEvtHandler);
|
||||
Backbone.View.prototype.remove.call(this);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Sample definition for the spacer column
|
||||
*/
|
||||
Backgrid.Extension.SizeAbleColumns.spacerColumnDefinition = {
|
||||
name: "__spacerColumn",
|
||||
label: "",
|
||||
editable: false,
|
||||
cell: Backgrid.StringCell,
|
||||
width: "*",
|
||||
nesting: [],
|
||||
resizeable: false,
|
||||
sortable: false,
|
||||
orderable: false,
|
||||
displayOrder: 9999
|
||||
};
|
||||
return Backgrid;
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user