From f2b2da09df3a78f92bbceb8a4d96121e7c6f32da Mon Sep 17 00:00:00 2001 From: Sarah McAlear and Tira Odhner Date: Thu, 30 Mar 2017 17:06:07 -0400 Subject: [PATCH 06/11] Select rows and columns reliably - use new row selection plugin - adds grid selector plugin - select all is missing for now --- web/pgadmin/static/js/selection/column_selector.js | 212 ++++++++++++--------- web/pgadmin/static/js/selection/grid_selector.js | 41 ++++ web/pgadmin/static/js/selection/row_selector.js | 6 +- .../sqleditor/templates/sqleditor/js/sqleditor.js | 21 +- .../javascript/selection/column_selector_spec.js | 76 ++++++-- .../javascript/selection/grid_selector_spec.js | 80 ++++++++ .../javascript/selection/row_selector_spec.js | 4 +- 7 files changed, 311 insertions(+), 129 deletions(-) create mode 100644 web/pgadmin/static/js/selection/grid_selector.js create mode 100644 web/regression/javascript/selection/grid_selector_spec.js diff --git a/web/pgadmin/static/js/selection/column_selector.js b/web/pgadmin/static/js/selection/column_selector.js index 451625e3..15267d2c 100644 --- a/web/pgadmin/static/js/selection/column_selector.js +++ b/web/pgadmin/static/js/selection/column_selector.js @@ -1,95 +1,131 @@ define(['jquery', 'slickgrid'], function ($) { - var ColumnSelector = function (columnDefinitions) { - var Slick = window.Slick; - - var init = function (grid) { - grid.onHeaderClick.subscribe(function (e, eventArgument) { - var column = eventArgument.column; - - updateRanges(grid, column.id); - - if (!clickedCheckbox(e)) { - var $checkbox = $("[data-id='checkbox-" + column.id + "']"); - toggleCheckbox($checkbox); - } - }); - }; - - function updateRanges(grid, columnId) { - var selectionModel = grid.getSelectionModel(); - var ranges = selectionModel.getSelectedRanges(); - - var columnIndex = grid.getColumnIndex(columnId); - - var columnRange = new Slick.Range(0, columnIndex, grid.getDataLength() - 1, columnIndex); - var newRanges; - if (isColumnSelected(ranges, columnRange)) { - newRanges = removeColumn(ranges, columnRange); - } else { - if (!allRangesAreColumns(ranges, grid)) { - newRanges = [columnRange]; - } else { - newRanges = addColumn(ranges, columnRange); - } + var ColumnSelector = function () { + var Slick = window.Slick; + + var init = function (grid) { + grid.onHeaderClick.subscribe(function (event, eventArgument) { + var column = eventArgument.column; + + if (column.selectable !== false) { + + if (!clickedCheckbox(event)) { + var $checkbox = $("[data-id='checkbox-" + column.id + "']"); + toggleCheckbox($checkbox); } - selectionModel.setSelectedRanges(newRanges); + + updateRanges(grid, column.id); + } } + ); + grid.getSelectionModel().onSelectedRangesChanged + .subscribe(handleSelectedRangesChanged.bind(null, grid)); + }; - var isColumnSelected = function (ranges, columnRange) { - return _.any(ranges, function (range) { - return isSameRange(range, columnRange) - }) - }; - - var isSameRange = function (range, otherRange) { - return range.fromCell == otherRange.fromCell && range.toCell == otherRange.toCell && - range.fromRow == otherRange.fromRow && range.toRow == otherRange.toRow; - }; - - var removeColumn = function (ranges, columnRange) { - return _.filter(ranges, function (range) { - return !(isSameRange(range, columnRange)) - }) - }; - var addColumn = function (ranges, column) { - ranges.push(column); - return ranges; - }; - - var allRangesAreColumns = function (ranges, grid) { - return _.every(ranges, function (range) { - return range.fromCell == range.toCell && - range.fromRow == 0 && range.toRow == grid.getDataLength() - 1 - }) - }; - - var clickedCheckbox = function (e) { - return e.target.type == "checkbox" - }; - - var toggleCheckbox = function (checkbox) { - if (checkbox.prop("checked")) { - checkbox.prop("checked", false) - } else { - checkbox.prop("checked", true) - } - }; - - var getColumnsWithCheckboxes = function () { - return _.map(columnDefinitions, function (columnDefinition) { - var name = columnDefinition.name; - if (columnDefinition.id != "_checkbox_selector") - name = "
" + columnDefinition.name + "
"; - return _.extend(columnDefinition, { - name: name - }); - }); - }; - - $.extend(this, { - "init": init, - "getColumnsWithCheckboxes": getColumnsWithCheckboxes + function rangeForColumn(grid, columnIndex) { + return new Slick.Range(0, columnIndex, grid.getDataLength() - 1, columnIndex) + } + + function handleSelectedRangesChanged(grid, event, ranges) { + $('[data-cell-type="column-header-row"] input:checked') + .each(function (index, checkbox) { + var $checkbox = $(checkbox); + var columnIndex = grid.getColumnIndex($checkbox.data('column-id')); + var isStillSelected = isRangeSelected(ranges, rangeForColumn(grid, columnIndex)); + if (!isStillSelected) { + toggleCheckbox($checkbox); + } }); + } + + var isRangeSelected = function (selectedRanges, range) { + return _.any(selectedRanges, function (selectedRange) { + return isSameRange(selectedRange, range) + }) }; - return ColumnSelector; + + function updateRanges(grid, columnId) { + var selectionModel = grid.getSelectionModel(); + var ranges = selectionModel.getSelectedRanges(); + + var columnIndex = grid.getColumnIndex(columnId); + + var columnRange = rangeForColumn(grid, columnIndex); + var newRanges; + if (isColumnSelected(ranges, columnRange)) { + newRanges = removeColumn(ranges, columnRange); + } else { + if (allRangesAreColumns(ranges, grid)) { + newRanges = addColumn(ranges, columnRange); + } else { + newRanges = [columnRange]; + } + } + selectionModel.setSelectedRanges(newRanges); + } + + var isColumnSelected = function (ranges, columnRange) { + return _.any(ranges, function (range) { + return isSameRange(range, columnRange) + }) + }; + + var isSameRange = function (range, otherRange) { + return range.fromCell == otherRange.fromCell && range.toCell == otherRange.toCell && + range.fromRow == otherRange.fromRow && range.toRow == otherRange.toRow; + }; + + var removeColumn = function (ranges, columnRange) { + return _.filter(ranges, function (range) { + return !(isSameRange(range, columnRange)) + }) + }; + var addColumn = function (ranges, column) { + ranges.push(column); + return ranges; + }; + + var allRangesAreColumns = function (ranges, grid) { + return _.every(ranges, function (range) { + return range.fromCell == range.toCell && + range.fromRow == 0 && range.toRow == grid.getDataLength() - 1 + }) + }; + + var clickedCheckbox = function (e) { + return e.target.type == "checkbox" + }; + + var toggleCheckbox = function (checkbox) { + if (checkbox.prop("checked")) { + checkbox.prop("checked", false) + } else { + checkbox.prop("checked", true) + } + }; + + var getColumnDefinitionsWithCheckboxes = function (columnDefinitions) { + return _.map(columnDefinitions, function (columnDefinition) { + if (columnDefinition.selectable !== false) { + var name = "
" + + " " + + columnDefinition.name + + "
"; + return _.extend(columnDefinition, { + name: name + }); + } else { + return columnDefinition; + } + }); + }; + + $.extend(this, { + "init": init, + "getColumnDefinitionsWithCheckboxes": getColumnDefinitionsWithCheckboxes + }); + }; + return ColumnSelector; }); diff --git a/web/pgadmin/static/js/selection/grid_selector.js b/web/pgadmin/static/js/selection/grid_selector.js new file mode 100644 index 00000000..0d9e1e78 --- /dev/null +++ b/web/pgadmin/static/js/selection/grid_selector.js @@ -0,0 +1,41 @@ +define(['jquery', 'sources/selection/column_selector', 'sources/selection/row_selector'], + function ($, ColumnSelector, RowSelector) { + var Slick = window.Slick; + + var GridSelector = function (columnDefinitions) { + var rowSelector = new RowSelector(columnDefinitions); + var columnSelector = new ColumnSelector(columnDefinitions); + + var init = function (grid) { + this.grid = grid; + grid.onHeaderClick.subscribe(function (event, eventArgument) { + if (event.target == $("[data-id='checkbox-select-all']")[0]) { + toggleSelectAll(grid); + } + }); + grid.registerPlugin(rowSelector); + grid.registerPlugin(columnSelector); + }; + + var getColumnDefinitionsWithCheckboxes = function (columnDefinitions) { + columnDefinitions = columnSelector.getColumnDefinitionsWithCheckboxes(columnDefinitions); + columnDefinitions = rowSelector.getColumnDefinitionsWithCheckboxes(columnDefinitions); + + return columnDefinitions; + }; + + function toggleSelectAll(grid) { + var range = new Slick.Range(0, 1, grid.getDataLength() - 1, grid.getColumns().length - 1); + var selectionModel = grid.getSelectionModel(); + + selectionModel.setSelectedRanges([range]); + } + + $.extend(this, { + "init": init, + "getColumnDefinitionsWithCheckboxes": getColumnDefinitionsWithCheckboxes + }); + }; + + return GridSelector; + }); \ No newline at end of file diff --git a/web/pgadmin/static/js/selection/row_selector.js b/web/pgadmin/static/js/selection/row_selector.js index 29a37588..353efdc8 100644 --- a/web/pgadmin/static/js/selection/row_selector.js +++ b/web/pgadmin/static/js/selection/row_selector.js @@ -1,12 +1,12 @@ define(['jquery', 'slickgrid'], function ($) { - var RowSelector = function (columnDefinitions) { + var RowSelector = function () { var Slick = window.Slick; var gridEventBus = new Slick.EventHandler(); var init = function (grid) { grid.getSelectionModel() - .onSelectedRangesChanged.subscribe(handleSelectedRangesChanged.bind(null, grid)) + .onSelectedRangesChanged.subscribe(handleSelectedRangesChanged.bind(null, grid)); gridEventBus .subscribe(grid.onClick, handleClick.bind(null, grid)) }; @@ -92,7 +92,7 @@ define(['jquery', 'slickgrid'], function ($) { } }; - var getColumnDefinitionsWithCheckboxes = function () { + var getColumnDefinitionsWithCheckboxes = function (columnDefinitions) { columnDefinitions.unshift({ id: 'row-header-column', selectable: false, diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js index c8049b62..70412a5f 100644 --- a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js +++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js @@ -2,7 +2,7 @@ define( [ 'jquery', 'underscore', 'underscore.string', 'alertify', 'pgadmin', 'backbone', 'backgrid', 'codemirror', 'pgadmin.misc.explain', - 'sources/selection/column_selector', 'sources/selection/clipboard', + 'sources/selection/grid_selector', 'sources/selection/clipboard', 'sources/selection/copy_data', 'slickgrid', 'bootstrap', 'pgadmin.browser', 'wcdocker', @@ -22,13 +22,12 @@ define( 'slickgrid/plugins/slick.cellrangedecorator', 'slickgrid/plugins/slick.cellrangeselector', 'slickgrid/plugins/slick.cellselectionmodel', - 'slickgrid/plugins/slick.checkboxselectcolumn', 'slickgrid/plugins/slick.cellcopymanager', 'slickgrid/plugins/slick.rowselectionmodel', 'slickgrid/slick.grid' ], function( - $, _, S, alertify, pgAdmin, Backbone, Backgrid, CodeMirror, pgExplain, ColumnSelector, clipboard, copyData + $, _, S, alertify, pgAdmin, Backbone, Backgrid, CodeMirror, pgExplain, GridSelector, clipboard, copyData ) { /* Return back, this has been called more than once */ if (pgAdmin.SqlEditor) @@ -550,14 +549,7 @@ define( collection = []; } - var grid_columns = new Array(), - checkboxSelector; - - checkboxSelector = new Slick.CheckboxSelectColumn({ - cssClass: "sc-cb" - }); - - grid_columns.push(checkboxSelector.getColumnDefinition()); + var grid_columns = []; var grid_width = $($('#editor-panel').find('.wcFrame')[1]).width() _.each(columns, function(c) { @@ -593,8 +585,8 @@ define( grid_columns.push(options) }); - var columnSelector = new ColumnSelector(grid_columns); - grid_columns = columnSelector.getColumnsWithCheckboxes(); + var gridSelector = new GridSelector(); + grid_columns = gridSelector.getColumnDefinitionsWithCheckboxes(grid_columns); var grid_options = { editable: true, @@ -639,8 +631,7 @@ define( var grid = new Slick.Grid($data_grid, collection, grid_columns, grid_options); grid.registerPlugin( new Slick.AutoTooltips({ enableForHeaderCells: false }) ); grid.setSelectionModel(new Slick.RowSelectionModel({selectActiveRow: false})); - grid.registerPlugin(checkboxSelector); - grid.registerPlugin(columnSelector); + grid.registerPlugin(gridSelector); var editor_data = { keys: self.handler.primary_keys, diff --git a/web/regression/javascript/selection/column_selector_spec.js b/web/regression/javascript/selection/column_selector_spec.js index 5c0e1c1f..ff991cd1 100644 --- a/web/regression/javascript/selection/column_selector_spec.js +++ b/web/regression/javascript/selection/column_selector_spec.js @@ -3,41 +3,48 @@ define( "underscore", "slickgrid/slick.grid", "sources/selection/column_selector", - "slickgrid/slick.rowselectionmodel" + "slickgrid/slick.rowselectionmodel", + "slickgrid" ], - function ($, _, SlickGrid, ColumnSelector, RowSelectionModel) { + function ($, _, SlickGrid, ColumnSelector, RowSelectionModel, Slick) { describe("ColumnSelector", function () { var container, data, columns, options; beforeEach(function () { container = $("
"); + container.height(9999); + data = [{'some-column-name': 'first value', 'second column': 'second value'}]; columns = [ { id: '1', name: 'some-column-name', - selectable: true }, { id: '2', name: 'second column', - selectable: true - }] + }, + { + name: 'some-non-selectable-column', + selectable: false + } + ] }); - describe("when it is the checkbox column", function () { - it("does not create a checkbox", function () { + describe("when a column is not selectable", function () { + it("does not create a checkbox for selecting the column", function () { var checkboxColumn = { - id: '_checkbox_selector', - name: 'checkbox column', - selectable: true + name: 'some-column-name-4', + selectable: false }; columns.push(checkboxColumn); - var columnSelector = new ColumnSelector(columns); - columns = columnSelector.getColumnsWithCheckboxes(); + var columnSelector = new ColumnSelector(); + columns = columnSelector.getColumnDefinitionsWithCheckboxes(columns); var grid = new SlickGrid(container, data, columns, options); + var rowSelectionModel = new RowSelectionModel(); + grid.setSelectionModel(rowSelectionModel); grid.registerPlugin(columnSelector); grid.invalidate(); @@ -46,10 +53,13 @@ define( }); it("renders a checkbox in the column header", function () { - var columnSelector = new ColumnSelector(columns); - columns = columnSelector.getColumnsWithCheckboxes(); + var columnSelector = new ColumnSelector(); + columns = columnSelector.getColumnDefinitionsWithCheckboxes(columns); var grid = new SlickGrid(container, data, columns, options); + var rowSelectionModel = new RowSelectionModel(); + grid.setSelectionModel(rowSelectionModel); + grid.registerPlugin(columnSelector); grid.invalidate(); @@ -57,10 +67,12 @@ define( }); it("displays the name of the column", function () { - var columnSelector = new ColumnSelector(columns); - columns = columnSelector.getColumnsWithCheckboxes(); + var columnSelector = new ColumnSelector(); + columns = columnSelector.getColumnDefinitionsWithCheckboxes(columns); var grid = new SlickGrid(container, data, columns, options); + var rowSelectionModel = new RowSelectionModel(); + grid.setSelectionModel(rowSelectionModel); grid.registerPlugin(columnSelector); grid.invalidate(); @@ -69,18 +81,17 @@ define( }); it("preserves the other attributes of column definitions", function () { - var columnSelector = new ColumnSelector(columns); - var selectableColumns = columnSelector.getColumnsWithCheckboxes(); + var columnSelector = new ColumnSelector(); + var selectableColumns = columnSelector.getColumnDefinitionsWithCheckboxes(columns); expect(selectableColumns[0].id).toBe('1'); - expect(selectableColumns[0].selectable).toBe(true); }); describe("when the user clicks on a column header", function () { var grid, rowSelectionModel; beforeEach(function () { - var columnSelector = new ColumnSelector(columns); - columns = columnSelector.getColumnsWithCheckboxes(); + var columnSelector = new ColumnSelector(); + columns = columnSelector.getColumnDefinitionsWithCheckboxes(columns); data = []; for (var i = 0; i < 10; i++) { data.push({'some-column-name': 'some-value-' + i, 'second column': 'second value ' + i}); @@ -89,6 +100,7 @@ define( rowSelectionModel = new RowSelectionModel(); grid.setSelectionModel(rowSelectionModel); + grid.registerPlugin(columnSelector); grid.invalidate(); $("body").append(container); @@ -178,6 +190,28 @@ define( expect(selectedRanges.length).toEqual(0); }) }); + + describe("and the column is not selectable", function () { + it("does not select the column", function () { + $(container.find('.slick-header-column:contains(some-non-selectable-column)')).click(); + var selectedRanges = rowSelectionModel.getSelectedRanges(); + + expect(selectedRanges.length).toEqual(0); + }); + }); + + describe("when the column is deselected through setSelectedRanges", function () { + beforeEach(function () { + container.find('.slick-header-column')[1].click(); + }); + + it("should uncheck the checkbox", function () { + rowSelectionModel.setSelectedRanges([]); + + expect($(container.find('.slick-header-columns input')[1]) + .is(':checked')).toBeFalsy(); + }); + }); }); }); }); \ No newline at end of file diff --git a/web/regression/javascript/selection/grid_selector_spec.js b/web/regression/javascript/selection/grid_selector_spec.js new file mode 100644 index 00000000..df71049a --- /dev/null +++ b/web/regression/javascript/selection/grid_selector_spec.js @@ -0,0 +1,80 @@ +define(["jquery", + "underscore", + "slickgrid/slick.grid", + "slickgrid/slick.rowselectionmodel", + "sources/selection/grid_selector" + ], + function ($, _, SlickGrid, RowSelectionModel, GridSelector) { + describe("GridSelector", function () { + var container, data, columns, gridSelector, rowSelectionModel; + + beforeEach(function () { + container = $("
"); + container.height(9999); + columns = [{ + id: '1', + name: 'some-column-name', + }, { + id: '2', + name: 'second column', + }]; + + gridSelector = new GridSelector(); + columns = gridSelector.getColumnDefinitionsWithCheckboxes(columns); + + data = []; + for (var i = 0; i < 10; i++) { + data.push({'some-column-name': 'some-value-' + i, 'second column': 'second value ' + i}); + } + var grid = new SlickGrid(container, data, columns); + + rowSelectionModel = new RowSelectionModel(); + grid.setSelectionModel(rowSelectionModel); + + grid.registerPlugin(gridSelector); + grid.invalidate(); + + $("body").append(container); + }); + + afterEach(function () { + $("body").find(container).remove(); + }); + + it("renders an additional column on the left for selecting rows", function () { + expect(columns.length).toBe(3); + + var leftmostColumn = columns[0]; + expect(leftmostColumn.id).toBe('row-header-column'); + }); + + it("renders checkboxes for selecting columns", function () { + expect(container.find('[data-test="output-column-header"] input').length).toBe(2) + }); + + xit("renders a checkbox for selecting all the cells", function () { + expect(container.find("[title='Select/Deselect All']").length).toBe(1); + }); + + xdescribe("when the main checkbox in the corner gets selected", function () { + it("unchecks all the columns", function () { + container.find("[title='Select/Deselect All']").click(); + + expect($(container.find('.slick-header-columns input')[1]).is(':checked')).toBeFalsy(); + expect($(container.find('.slick-header-columns input')[2]).is(':checked')).toBeFalsy(); + }); + + it("selects all the cells", function () { + container.find("[title='Select/Deselect All']").click(); + + var selectedRanges = rowSelectionModel.getSelectedRanges(); + expect(selectedRanges.length).toBe(1); + var selectedRange = selectedRanges[0]; + expect(selectedRange.fromCell).toBe(1); + expect(selectedRange.toCell).toBe(2); + expect(selectedRange.fromRow).toBe(0); + expect(selectedRange.toRow).toBe(9); + }) + }); + }); + }); \ No newline at end of file diff --git a/web/regression/javascript/selection/row_selector_spec.js b/web/regression/javascript/selection/row_selector_spec.js index 30f155c9..10697e6a 100644 --- a/web/regression/javascript/selection/row_selector_spec.js +++ b/web/regression/javascript/selection/row_selector_spec.js @@ -24,12 +24,12 @@ define( selectable: true }]; - var rowSelector = new RowSelector(columnDefinitions); + var rowSelector = new RowSelector(); data = []; for (var i = 0; i < 10; i++) { data.push(['some-value-' + i, 'second value ' + i]); } - columnDefinitions = rowSelector.getColumnDefinitionsWithCheckboxes(); + columnDefinitions = rowSelector.getColumnDefinitionsWithCheckboxes(columnDefinitions); grid = new SlickGrid(container, data, columnDefinitions); rowSelectionModel = new RowSelectionModel(); -- 2.12.0