diff --git a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py b/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py index fa784ee6..2f5b3786 100644 --- a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py +++ b/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py @@ -58,6 +58,7 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): self._copies_rectangular_selection() self._shift_resizes_rectangular_selection() self._shift_resizes_column_selection() + self._mouseup_outside_grid_still_makes_a_selection() def _copies_rows(self): pyperclip.copy("old clipboard contents") @@ -103,11 +104,12 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): def _copies_rectangular_selection(self): pyperclip.copy("old clipboard contents") - topLeftCell = self.page.find_by_xpath("//div[contains(@class, 'slick-cell') and contains(., 'Some-Other-Name')]") - bottomRightCell = self.page.find_by_xpath("//div[contains(@class, 'slick-cell') and contains(., '14')]") - - ActionChains(self.page.driver).click_and_hold(topLeftCell).move_to_element(bottomRightCell).release(bottomRightCell).perform() + top_left_cell = self.page.find_by_xpath( + "//div[contains(@class, 'slick-cell') and contains(., 'Some-Other-Name')]") + bottom_right_cell = self.page.find_by_xpath("//div[contains(@class, 'slick-cell') and contains(., '14')]") + ActionChains(self.page.driver).click_and_hold(top_left_cell).move_to_element(bottom_right_cell) \ + .release(bottom_right_cell).perform() ActionChains(self.page.driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() self.assertEqual("""'Some-Other-Name','22' @@ -116,9 +118,11 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): def _shift_resizes_rectangular_selection(self): pyperclip.copy("old clipboard contents") - top_left_cell = self.page.find_by_xpath("//div[contains(@class, 'slick-cell') and contains(., 'Some-Other-Name')]") - initial_bottom_right_cell = self.page.find_by_xpath("//div[contains(@class, 'slick-cell') and contains(., '14')]") - ActionChains(self.page.driver).click_and_hold(top_left_cell).move_to_element(initial_bottom_right_cell)\ + top_left_cell = self.page.find_by_xpath( + "//div[contains(@class, 'slick-cell') and contains(., 'Some-Other-Name')]") + initial_bottom_right_cell = self.page.find_by_xpath( + "//div[contains(@class, 'slick-cell') and contains(., '14')]") + ActionChains(self.page.driver).click_and_hold(top_left_cell).move_to_element(initial_bottom_right_cell) \ .release(initial_bottom_right_cell).perform() ActionChains(self.page.driver).key_down(Keys.SHIFT).send_keys(Keys.ARROW_RIGHT).key_up(Keys.SHIFT).perform() @@ -131,7 +135,7 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): pyperclip.copy("old clipboard contents") self.page.find_by_xpath("//*[@data-test='output-column-header' and contains(., 'value')]").click() - ActionChains(self.page.driver).key_down(Keys.SHIFT).send_keys(Keys.ARROW_LEFT)\ + ActionChains(self.page.driver).key_down(Keys.SHIFT).send_keys(Keys.ARROW_LEFT) \ .key_up(Keys.SHIFT).perform() ActionChains(self.page.driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() @@ -142,6 +146,22 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): 'Yet-Another-Name','14'""", pyperclip.paste()) + def _mouseup_outside_grid_still_makes_a_selection(self): + pyperclip.copy("old clipboard contents") + + bottom_right_cell = self.page.find_by_xpath( + "//div[contains(@class, 'slick-cell') and contains(., 'cool info')]") + + load_button = self.page.find_by_xpath("//button[@id='btn-load-file']") + ActionChains(self.page.driver).click_and_hold(bottom_right_cell) \ + .move_to_element(load_button) \ + .release(load_button) \ + .perform() + + ActionChains(self.page.driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() + + self.assertEqual("'cool info'", pyperclip.paste()) + def after(self): self.page.close_query_tool() self.page.remove_server(self.server) diff --git a/web/pgadmin/static/js/selection/column_selector.js b/web/pgadmin/static/js/selection/column_selector.js index 8806b68c..53cdad82 100644 --- a/web/pgadmin/static/js/selection/column_selector.js +++ b/web/pgadmin/static/js/selection/column_selector.js @@ -8,36 +8,38 @@ define([ var gridEventBus = new Slick.EventHandler(); var init = function (grid) { - gridEventBus.subscribe(grid.onHeaderClick, function (event, eventArgument) { - var columnDefinition = eventArgument.column; + gridEventBus.subscribe(grid.onHeaderClick, handleHeaderClick.bind(null, grid)); + grid.getSelectionModel().onSelectedRangesChanged + .subscribe(handleSelectedRangesChanged.bind(null, grid)); + }; - grid.focus(); + var handleHeaderClick = function (grid, event, args) { + var columnDefinition = args.column; - if (isColumnSelectable(columnDefinition)) { - var $columnHeader = $(event.target); - if(hasClickedChildOfColumnHeader(event)) { - $columnHeader = $(event.target).parents(".slick-header-column"); - } - $columnHeader.toggleClass('selected'); + grid.focus(); - updateRanges(grid, columnDefinition.id); + if (isColumnSelectable(columnDefinition)) { + var $columnHeader = $(event.target); + if (hasClickedChildOfColumnHeader(event)) { + $columnHeader = $(event.target).parents(".slick-header-column"); } - }); - grid.getSelectionModel().onSelectedRangesChanged - .subscribe(handleSelectedRangesChanged.bind(null, grid)); + $columnHeader.toggleClass('selected'); + + updateRanges(grid, columnDefinition.id); + } }; - var handleSelectedRangesChanged = function (grid, event, ranges) { - $('.slick-header-column.selected') - .each(function (index, columnHeader) { - var $spanHeaderColumn = $(columnHeader).find('[data-cell-type="column-header-row"]'); - var columnIndex = grid.getColumnIndex($spanHeaderColumn.data('column-id')); - var isStillSelected = RangeSelectionHelper.isRangeSelected(ranges, - RangeSelectionHelper.rangeForColumn(grid, columnIndex)); - if (!isStillSelected) { - $(columnHeader).toggleClass('selected') - } - }); + var handleSelectedRangesChanged = function (grid, event, selectedRanges) { + $('.slick-header-column').each(function (index, columnHeader) { + var $spanHeaderColumn = $(columnHeader).find('[data-cell-type="column-header-row"]'); + var columnIndex = grid.getColumnIndex($spanHeaderColumn.data('column-id')); + + if (isColumnSelected(grid, selectedRanges, columnIndex)) { + $(columnHeader).addClass('selected'); + } else { + $(columnHeader).removeClass('selected'); + } + }); }; var updateRanges = function (grid, columnId) { @@ -68,6 +70,19 @@ define([ return columnDefinition.selectable !== false; }; + var isColumnSelected = function (grid, selectedRanges, columnIndex) { + var allRangesAreRows = RangeSelectionHelper.areAllRangesCompleteRows(grid, selectedRanges); + return isAnyCellSelectedInColumn(grid, selectedRanges, columnIndex) && !allRangesAreRows; + }; + + var isAnyCellSelectedInColumn = function (grid, selectedRanges, columnIndex) { + var isStillSelected = RangeSelectionHelper.isRangeEntirelyWithinSelectedRanges(selectedRanges, + RangeSelectionHelper.rangeForColumn(grid, columnIndex)); + var cellSelectedInColumn = RangeSelectionHelper.isAnyCellOfColumnSelected(selectedRanges, columnIndex); + + return isStillSelected || cellSelectedInColumn; + }; + var getColumnDefinitions = function (columnDefinitions) { return _.map(columnDefinitions, function (columnDefinition) { if (isColumnSelectable(columnDefinition)) { @@ -75,7 +90,10 @@ define([ "" + - " " + columnDefinition.name + "" + + " " + + " " + columnDefinition.display_name + "" + + " " + columnDefinition.column_type + "" + + " " + ""; return _.extend(columnDefinition, { name: name diff --git a/web/pgadmin/static/js/selection/copy_data.js b/web/pgadmin/static/js/selection/copy_data.js index 166219ba..ad79d3eb 100644 --- a/web/pgadmin/static/js/selection/copy_data.js +++ b/web/pgadmin/static/js/selection/copy_data.js @@ -15,7 +15,7 @@ function ($, _, clipboard, RangeSelectionHelper, rangeBoundaryNavigator) { var data = grid.getData(); var rows = grid.getSelectedRows(); - if (allTheRangesAreFullRows(selectedRanges, columnDefinitions)) { + if (RangeSelectionHelper.areAllRangesCompleteRows(grid, selectedRanges)) { self.copied_rows = rows.map(function (rowIndex) { return data[rowIndex]; }); diff --git a/web/pgadmin/static/js/selection/range_selection_helper.js b/web/pgadmin/static/js/selection/range_selection_helper.js index 3d121f4f..936530ff 100644 --- a/web/pgadmin/static/js/selection/range_selection_helper.js +++ b/web/pgadmin/static/js/selection/range_selection_helper.js @@ -9,7 +9,26 @@ define(['slickgrid'], function () { var isRangeSelected = function (selectedRanges, range) { return _.any(selectedRanges, function (selectedRange) { return isSameRange(selectedRange, range) - }) + }); + }; + + var isAnyCellOfColumnSelected = function (selectedRanges, column) { + return _.any(selectedRanges, function (selectedRange) { + return selectedRange.fromCell <= column && selectedRange.toCell >= column + }); + }; + + var isAnyCellOfRowSelected = function (selectedRanges, row) { + return _.any(selectedRanges, function (selectedRange) { + return selectedRange.fromRow <= row && selectedRange.toRow >= row + }); + }; + + var isRangeEntirelyWithinSelectedRanges = function (selectedRanges, range) { + return _.any(selectedRanges, function (selectedRange) { + return selectedRange.fromCell <= range.fromCell && selectedRange.toCell >= range.toCell && + selectedRange.fromRow <= range.fromRow && selectedRange.toRow >= range.toRow; + }); }; var removeRange = function (selectedRanges, range) { @@ -30,10 +49,7 @@ define(['slickgrid'], function () { }; var areAllRangesSingleColumns = function (ranges, grid) { - return _.every(ranges, function (range) { - return range.fromCell == range.toCell && - range.fromRow == 0 && range.toRow == grid.getDataLength() - 1 - }) + return _.every(ranges, isRangeAColumn.bind(this, grid)) }; var rangeForRow = function (grid, rowId) { @@ -62,6 +78,12 @@ define(['slickgrid'], function () { return !_.isUndefined(columnDefinitions[0].pos); }; + var areAllRangesCompleteColumns = function (grid, ranges) { + return _.every(ranges, function (range) { + return rangeHasCompleteColumns(grid, range); + }) + }; + var areAllRangesCompleteRows = function (grid, ranges) { return _.every(ranges, function (range) { return rangeHasCompleteRows(grid, range); @@ -78,10 +100,34 @@ define(['slickgrid'], function () { return indexArray; }; - function rangeHasCompleteRows(grid, range) { - var firstDataColumn = isFirstColumnData(grid.getColumns()) ? 0 : 1; - return range.fromCell == firstDataColumn && - range.toCell == grid.getColumns().length - 1; + var isRangeAColumn = function (grid, range) { + return range.fromCell == range.toCell && + range.fromRow == 0 && range.toRow == grid.getDataLength() - 1; + }; + + var rangeHasCompleteColumns = function (grid, range) { + return range.fromRow === 0 && range.toRow === grid.getDataLength() - 1; + }; + + var rangeHasCompleteRows = function (grid, range) { + return range.fromCell === getFirstDataColumnIndex(grid) && + range.toCell === getLastDataColumnIndex(grid); + }; + + function getFirstDataColumnIndex(grid) { + return _.findIndex(grid.getColumns(), function (columnDefinition) { + var pos = columnDefinition.pos; + + return !_.isUndefined(pos) && isSelectable(columnDefinition); + }); + } + + function getLastDataColumnIndex(grid) { + return _.findLastIndex(grid.getColumns(), isSelectable); + } + + function isSelectable(columnDefinition) { + return (_.isUndefined(columnDefinition.selectable) || columnDefinition.selectable === true); } function selectAll(grid) { @@ -98,12 +144,19 @@ define(['slickgrid'], function () { areAllRangesSingleRows: areAllRangesSingleRows, areAllRangesSingleColumns: areAllRangesSingleColumns, areAllRangesCompleteRows: areAllRangesCompleteRows, + areAllRangesCompleteColumns: areAllRangesCompleteColumns, rangeForRow: rangeForRow, rangeForColumn: rangeForColumn, isEntireGridSelected: isEntireGridSelected, getRangeOfWholeGrid: getRangeOfWholeGrid, isFirstColumnData: isFirstColumnData, - selectAll: selectAll, getIndexesOfCompleteRows: getIndexesOfCompleteRows, + selectAll: selectAll, + isRangeAColumn: isRangeAColumn, + rangeHasCompleteColumns: rangeHasCompleteColumns, + rangeHasCompleteRows: rangeHasCompleteRows, + isAnyCellOfColumnSelected: isAnyCellOfColumnSelected, + isRangeEntirelyWithinSelectedRanges: isRangeEntirelyWithinSelectedRanges, + isAnyCellOfRowSelected: isAnyCellOfRowSelected, } }); \ 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 1088f879..33d5a72e 100644 --- a/web/pgadmin/static/js/selection/row_selector.js +++ b/web/pgadmin/static/js/selection/row_selector.js @@ -1,39 +1,44 @@ -define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], function ($, RangeSelectionHelper) { +define([ + 'jquery', + 'sources/selection/range_selection_helper', + 'slickgrid' +], function ($, RangeSelectionHelper) { var RowSelector = function () { var Slick = window.Slick; var gridEventBus = new Slick.EventHandler(); var init = function (grid) { - grid.getSelectionModel() - .onSelectedRangesChanged.subscribe(handleSelectedRangesChanged.bind(null, grid)); + grid.getSelectionModel().onSelectedRangesChanged + .subscribe(handleSelectedRangesChanged.bind(null, grid)); gridEventBus - .subscribe(grid.onClick, handleClick.bind(null, grid)) + .subscribe(grid.onClick, handleClick.bind(null, grid)); }; var handleClick = function (grid, event, args) { if (grid.getColumns()[args.cell].id === 'row-header-column') { var $rowHeaderSpan = $(event.target); - if($rowHeaderSpan.data('cell-type') != "row-header-selector") { + if ($rowHeaderSpan.data('cell-type') != "row-header-selector") { $rowHeaderSpan = $(event.target).find('[data-cell-type="row-header-selector"]'); } - $rowHeaderSpan.toggleClass('selected'); + + $rowHeaderSpan.parent().toggleClass('selected'); updateRanges(grid, args.row); } }; - var handleSelectedRangesChanged = function (grid, event, ranges) { - $('[data-cell-type="row-header-selector"].selected') - .each(function (index, rowHeaderSpan) { - var $rowHeaderSpan = $(rowHeaderSpan); - var row = parseInt($rowHeaderSpan.data('row')); - var isStillSelected = RangeSelectionHelper.isRangeSelected(ranges, - RangeSelectionHelper.rangeForRow(grid, row)); - if (!isStillSelected) { - $rowHeaderSpan.toggleClass('selected'); - } - }); + var handleSelectedRangesChanged = function (grid, event, selectedRanges) { + $('[data-cell-type="row-header-selector"]').each(function (index, rowHeaderSpan) { + var $rowHeaderSpan = $(rowHeaderSpan); + var row = parseInt($rowHeaderSpan.data('row')); + + if (isRowSelected(grid, selectedRanges, row)) { + $rowHeaderSpan.parent().addClass('selected'); + } else { + $rowHeaderSpan.parent().removeClass('selected'); + } + }); }; var updateRanges = function (grid, rowId) { @@ -55,6 +60,19 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func selectionModel.setSelectedRanges(newRanges); }; + var isAnyCellSelectedInRow = function (grid, selectedRanges, row) { + var isStillSelected = RangeSelectionHelper.isRangeEntirelyWithinSelectedRanges(selectedRanges, + RangeSelectionHelper.rangeForRow(grid, row)); + var cellSelectedInRow = RangeSelectionHelper.isAnyCellOfRowSelected(selectedRanges, row); + + return isStillSelected || cellSelectedInRow; + }; + + var isRowSelected = function (grid, selectedRanges, row) { + var allRangesAreColumns = RangeSelectionHelper.areAllRangesCompleteColumns(grid, selectedRanges); + return isAnyCellSelectedInRow(grid, selectedRanges, row) && !allRangesAreColumns; + }; + var getColumnDefinitions = function (columnDefinitions) { columnDefinitions.unshift({ id: 'row-header-column', diff --git a/web/pgadmin/static/js/selection/set_staged_rows.js b/web/pgadmin/static/js/selection/set_staged_rows.js index 46aa8c8c..249a6414 100644 --- a/web/pgadmin/static/js/selection/set_staged_rows.js +++ b/web/pgadmin/static/js/selection/set_staged_rows.js @@ -10,9 +10,10 @@ define( [ 'jquery', - 'underscore' + 'underscore', + 'sources/selection/range_selection_helper' ], - function ($, _) { + function ($, _, RangeSelectionHelper) { function disableButton(selector) { $(selector).prop('disabled', true); } @@ -85,19 +86,19 @@ define( disableButton('#btn-delete-row'); disableButton('#btn-copy-row'); - if (!_.has(this.selection, 'getSelectedRows')) { - setStagedRows({}); - return; + function areAllSelectionsEntireRows() { + return RangeSelectionHelper.areAllRangesCompleteRows(self.grid, + self.selection.getSelectedRanges()) } - var selectedRows = this.selection.getSelectedRows(); var selectedRanges = this.selection.getSelectedRanges(); if (selectedRanges.length > 0) { enableButton('#btn-copy-row'); } - if (selectedRows.length > 0) { + if (areAllSelectionsEntireRows()) { + var selectedRows = RangeSelectionHelper.getIndexesOfCompleteRows(this.grid, this.selection.getSelectedRanges()) var stagedRows = getPrimaryKeysForSelectedRows(self, selectedRows); setStagedRows(stagedRows); if (_.isEmpty(stagedRows)) { @@ -114,5 +115,3 @@ define( return setStagedRows; } ); - - diff --git a/web/pgadmin/static/js/selection/xcell_selection_model.js b/web/pgadmin/static/js/selection/xcell_selection_model.js new file mode 100644 index 00000000..81f46cce --- /dev/null +++ b/web/pgadmin/static/js/selection/xcell_selection_model.js @@ -0,0 +1,228 @@ +define([ + 'jquery', + 'underscore', + 'sources/selection/range_selection_helper', + + 'slickgrid', + 'slickgrid/plugins/slick.cellrangeselector' +], function ($, _, RangeSelectionHelper) { + var XCellSelectionModel = function (options) { + + var KEY_ARROW_RIGHT = 39; + var KEY_ARROW_LEFT = 37; + var KEY_ARROW_UP = 38; + var KEY_ARROW_DOWN = 40; + + var _grid; + var _canvas; + var _ranges = []; + var _self = this; + var _selector = new Slick.CellRangeSelector({ + "selectionCss": { + "border": "2px solid black" + } + }); + var _options; + var _defaults = { + selectActiveCell: true + }; + + + function init(grid) { + _options = $.extend(true, {}, _defaults, options); + _grid = grid; + _canvas = _grid.getCanvasNode(); + _grid.onActiveCellChanged.subscribe(handleActiveCellChange); + _grid.onKeyDown.subscribe(handleKeyDown); + grid.registerPlugin(_selector); + _selector.onCellRangeSelected.subscribe(handleCellRangeSelected); + _selector.onBeforeCellRangeSelected.subscribe(handleBeforeCellRangeSelected); + $(window.parent).mouseup(handleWindowMouseUp); + } + + function destroy() { + _grid.onActiveCellChanged.unsubscribe(handleActiveCellChange); + _grid.onKeyDown.unsubscribe(handleKeyDown); + _selector.onCellRangeSelected.unsubscribe(handleCellRangeSelected); + _selector.onBeforeCellRangeSelected.unsubscribe(handleBeforeCellRangeSelected); + _grid.unregisterPlugin(_selector); + $(window.parent).off('mouseup', handleWindowMouseUp); + } + + function removeInvalidRanges(ranges) { + var result = []; + + for (var i = 0; i < ranges.length; i++) { + var r = ranges[i]; + if (_grid.canCellBeSelected(r.fromRow, r.fromCell) && _grid.canCellBeSelected(r.toRow, r.toCell)) { + result.push(r); + } + } + + return result; + } + + function setSelectedRanges(ranges) { + // simple check for: empty selection didn't change, prevent firing onSelectedRangesChanged + if ((!_ranges || _ranges.length === 0) && (!ranges || ranges.length === 0)) { return; } + + _ranges = removeInvalidRanges(ranges); + _self.onSelectedRangesChanged.notify(_ranges); + } + + function getSelectedRanges() { + return _ranges; + } + + function setSelectedRows(rows) { + _ranges = []; + + for(var i = 0 ; i < rows.length ; i++) { + _ranges.push(RangeSelectionHelper.rangeForRow(_grid, rows[i])); + } + } + + function handleBeforeCellRangeSelected(e, args) { + if (_grid.getEditorLock().isActive()) { + e.stopPropagation(); + return false; + } + } + + function handleCellRangeSelected(e, args) { + setSelectedRanges([args.range]); + } + + function handleActiveCellChange(e, args) { + if (_options.selectActiveCell && args.row != null && args.cell != null) { + setSelectedRanges([new Slick.Range(args.row, args.cell)]); + } + } + + function arrowKeyPressed(event) { + return event.which == KEY_ARROW_RIGHT + || event.which == KEY_ARROW_LEFT + || event.which == KEY_ARROW_UP + || event.which == KEY_ARROW_DOWN; + } + + function shiftArrowKeyPressed(event) { + return event.shiftKey && !event.ctrlKey && !event.altKey && + (arrowKeyPressed(event)); + } + + function needUpdateRange(newRange) { + return removeInvalidRanges([newRange]).length; + } + + function handleKeyDown(e) { + var ranges; + var lastSelectedRange; + var anchorActiveCell = _grid.getActiveCell(); + + function isKey(key) { return e.which === key; } + + function getKeycode() { return e.which; } + + function shouldScrollToBottommostRow() { return anchorActiveCell.row === newSelectedRange.fromRow; } + + function shouldScrollToRightmostColumn() { return anchorActiveCell.cell === newSelectedRange.fromCell; } + + function getMobileCellFromRange(range, activeCell) { + var mobileCell = {}; + + mobileCell.row = range.fromRow === activeCell.row ? range.toRow : range.fromRow; + mobileCell.cell = range.fromCell === activeCell.cell ? range.toCell : range.fromCell; + + return mobileCell; + } + + function getNewRange(rangeCorner, oppositeCorner) { + var newFromCell = rangeCorner.cell <= oppositeCorner.cell ? rangeCorner.cell : oppositeCorner.cell; + var newToCell = rangeCorner.cell <= oppositeCorner.cell ? oppositeCorner.cell : rangeCorner.cell; + + var newFromRow = rangeCorner.row <= oppositeCorner.row ? rangeCorner.row : oppositeCorner.row; + var newToRow = rangeCorner.row <= oppositeCorner.row ? oppositeCorner.row : rangeCorner.row; + + return new Slick.Range( + newFromRow, + newFromCell, + newToRow, + newToCell + ); + } + + if (anchorActiveCell && shiftArrowKeyPressed(e)) { + ranges = getSelectedRanges(); + if (!ranges.length) { + ranges.push(new Slick.Range(anchorActiveCell.row, anchorActiveCell.cell)); + } + + // keyboard can work with last range only + lastSelectedRange = ranges.pop(); + + // can't handle selection out of active cell + if (!lastSelectedRange.contains(anchorActiveCell.row, anchorActiveCell.cell)) { + lastSelectedRange = new Slick.Range(anchorActiveCell.row, anchorActiveCell.cell); + } + + var mobileCell = getMobileCellFromRange(lastSelectedRange, anchorActiveCell); + + switch (getKeycode()) { + case KEY_ARROW_LEFT: + mobileCell.cell -= 1; + break; + case KEY_ARROW_RIGHT: + mobileCell.cell += 1; + break; + case KEY_ARROW_UP: + mobileCell.row -= 1; + break; + case KEY_ARROW_DOWN: + mobileCell.row += 1; + break; + } + + var newSelectedRange = getNewRange(anchorActiveCell, mobileCell); + + if (needUpdateRange(newSelectedRange)) { + var rowToView = shouldScrollToBottommostRow() ? newSelectedRange.toRow : newSelectedRange.fromRow; + var columnToView = shouldScrollToRightmostColumn() ? newSelectedRange.toCell : newSelectedRange.fromCell; + + if (isKey(KEY_ARROW_RIGHT) || isKey(KEY_ARROW_LEFT)) { + _grid.scrollColumnIntoView(columnToView); + } else if (isKey(KEY_ARROW_UP) || isKey(KEY_ARROW_DOWN)) { + _grid.scrollRowIntoView(rowToView); + } + ranges.push(newSelectedRange); + } else { + ranges.push(lastSelectedRange); + } + + setSelectedRanges(ranges); + + e.preventDefault(); + e.stopPropagation(); + } + } + + function handleWindowMouseUp(event) { + var selectedRange = _selector.getCurrentRange(); + if (!_.isUndefined(selectedRange)) { + _grid.onDragEnd.notify({range: selectedRange}); + } + } + + $.extend(this, { + "getSelectedRanges": getSelectedRanges, + "setSelectedRanges": setSelectedRanges, + "setSelectedRows": setSelectedRows, + + "init": init, + "destroy": destroy, + + "onSelectedRangesChanged": new Slick.Event() + }); + }; + return XCellSelectionModel; +}); diff --git a/web/pgadmin/static/vendor/slickgrid/plugins/slick.rowselectionmodel.js b/web/pgadmin/static/vendor/slickgrid/plugins/slick.rowselectionmodel.js index 8553d050..95c12ab5 100644 --- a/web/pgadmin/static/vendor/slickgrid/plugins/slick.rowselectionmodel.js +++ b/web/pgadmin/static/vendor/slickgrid/plugins/slick.rowselectionmodel.js @@ -81,7 +81,7 @@ } function setSelectedRanges(ranges) { - // simle check for: empty selection didn't change, prevent firing onSelectedRangesChanged + // simple check for: empty selection didn't change, prevent firing onSelectedRangesChanged if ((!_ranges || _ranges.length === 0) && (!ranges || ranges.length === 0)) { return; } _ranges = ranges; _self.onSelectedRangesChanged.notify(_ranges); diff --git a/web/pgadmin/static/vendor/slickgrid/slick.grid.js b/web/pgadmin/static/vendor/slickgrid/slick.grid.js index 8ebfd68b..27e8009b 100644 --- a/web/pgadmin/static/vendor/slickgrid/slick.grid.js +++ b/web/pgadmin/static/vendor/slickgrid/slick.grid.js @@ -2794,6 +2794,25 @@ if (typeof Slick === "undefined") { } } + function scrollColumnIntoView(columnIndex) { + var colspan = getColspan(row, columnIndex); + + var left = columnPosLeft[columnIndex], + right = columnPosRight[columnIndex + (colspan > 1 ? colspan - 1 : 0)], + scrollRight = scrollLeft + viewportW; + + if (left < scrollLeft) { + $viewport.scrollLeft(left); + handleScroll(); + render(); + } else if (right > scrollRight) { + $viewport.scrollLeft(Math.min(left, right - $viewport[0].clientWidth)); + handleScroll(); + render(); + } + + } + function scrollCellIntoView(row, cell, doPaging) { scrollRowIntoView(row, doPaging); @@ -3740,6 +3759,7 @@ if (typeof Slick === "undefined") { "scrollRowIntoView": scrollRowIntoView, "scrollRowToTop": scrollRowToTop, "scrollCellIntoView": scrollCellIntoView, + "scrollColumnIntoView": scrollColumnIntoView, "getCanvasNode": getCanvasNode, "focus": setFocus, diff --git a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css index 8695b11e..e4be9ebe 100644 --- a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css +++ b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css @@ -260,7 +260,7 @@ li { } .slick-header-column.ui-state-default { - height: 40px !important; + height: 32px !important; } #datagrid .grid-header label { @@ -350,9 +350,19 @@ li { #datagrid .slick-header .slick-header-column.ui-state-default { color: #222222; - padding: 4px 0 4px 6px; - background-color: #e8e8e8; - border-bottom: 1px solid black; + padding: 4px 0 3px 6px; + background-color: #E8E8E8; + border-bottom: 1px solid silver !important; +} + +#datagrid .slick-header .slick-header-column.selected { + background-color: #2C76B4; + color: #FFFFFF; +} + +#datagrid .slick-header .slick-header-column .column-name { + font-weight: bold; + display: block; } .column-description { @@ -404,11 +414,6 @@ input.editor-checkbox:focus { outline: none; } -/* Override selected row color */ -.slick-cell.selected { - background-color: #DEE8F1 !important; -} - /* Remove active cell border */ .slick-cell.active { border: 1px solid transparent; @@ -441,11 +446,20 @@ input.editor-checkbox:focus { color: #999999; } +/* Override selected row color */ +#datagrid .slick-row .slick-cell.selected { + background-color: #DEE8F1; +} + /* color the first column */ -.slick-row .slick-cell.l0.r0 { +#datagrid .slick-row .slick-cell.l0.r0 { background-color: #e8e8e8; } +#datagrid .slick-row .slick-cell.l0.r0.selected { + background-color: #2C76B4; +} + #datagrid div.slick-header.ui-state-default { background: #ffffff; border-bottom: none; @@ -464,11 +478,7 @@ input.editor-checkbox:focus { -ms-box-sizing: content-box; } -.slick-row.ui-widget-content { - border-top: 1px solid silver; -} - .select-all-icon { margin-left: 9px; vertical-align: bottom; -} \ No newline at end of file +} diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js index 310c6f5b..b6bbfc43 100644 --- a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js +++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js @@ -8,6 +8,7 @@ define( 'sources/selection/copy_data', 'sources/selection/range_selection_helper', 'sources/slickgrid/event_handlers/handle_query_output_keyboard_event', + 'sources/selection/xcell_selection_model', 'sources/selection/set_staged_rows', 'sources/gettext', @@ -35,7 +36,7 @@ define( function( $, _, S, alertify, pgAdmin, Backbone, Backgrid, CodeMirror, pgExplain, GridSelector, ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent, - setStagedRows, gettext + XCellSelectionModel, setStagedRows, gettext ) { /* Return back, this has been called more than once */ if (pgAdmin.SqlEditor) @@ -601,6 +602,8 @@ define( pos: c.pos, field: c.name, name: c.label, + display_name: c.display_name, + column_type: c.column_type, not_null: c.not_null, has_default_val: c.has_default_val }; @@ -686,7 +689,7 @@ define( var grid = new Slick.Grid($data_grid, collection, grid_columns, grid_options); grid.registerPlugin( new Slick.AutoTooltips({ enableForHeaderCells: false }) ); grid.registerPlugin(new ActiveCellCapture()); - grid.setSelectionModel(new Slick.CellSelectionModel()); + grid.setSelectionModel(new XCellSelectionModel()); grid.registerPlugin(gridSelector); var editor_data = { @@ -2108,6 +2111,8 @@ define( 'cell': col_cell, 'can_edit': self.can_edit, 'type': type, + 'display_name': c.display_name, + 'column_type': col_type, 'not_null': c.not_null, 'has_default_val': c.has_default_val }; diff --git a/web/regression/javascript/selection/column_selector_spec.js b/web/regression/javascript/selection/column_selector_spec.js index 7e6f2a5f..679af52f 100644 --- a/web/regression/javascript/selection/column_selector_spec.js +++ b/web/regression/javascript/selection/column_selector_spec.js @@ -4,14 +4,14 @@ define( "sources/selection/column_selector", "sources/selection/active_cell_capture", "sources/selection/grid_selector", + 'sources/selection/xcell_selection_model', "slickgrid", - 'slickgrid/slick.cellrangedecorator', - 'slickgrid/slick.cellrangeselector', - "slickgrid/slick.cellselectionmodel", + 'slickgrid/plugins/slick.cellrangedecorator', + 'slickgrid/plugins/slick.cellrangeselector', "slickgrid/slick.grid", ], - function ($, _, ColumnSelector, ActiveCellCapture, GridSelector) { + function ($, _, ColumnSelector, ActiveCellCapture, GridSelector, XCellSelectionModel) { var KEY_RIGHT = 39; var KEY_LEFT = 37; var KEY_UP = 38; @@ -31,6 +31,10 @@ define( 'some-column-name': 'first value', 'second column': 'second value', 'third column': 'nonselectable value' + }, { + 'some-column-name': 'row 1 - first value', + 'second column': 'row 1 - second value', + 'third column': 'row 1 - nonselectable value' }]; columns = [ @@ -38,26 +42,36 @@ define( id: 'row-header-column', name: 'row header column name', selectable: false, + display_name: 'row header column name', + column_type: 'text' }, { id: '1', name: 'some-column-name', - pos: 0 + pos: 0, + display_name: 'some-column-name', + column_type: 'text' }, { id: '2', name: 'second column', - pos: 1 + pos: 1, + display_name: 'second column', + column_type: 'json' }, { id: 'third-column-id', name: 'third column', - pos: 2 + pos: 2, + display_name: 'third column', + column_type: 'text' }, { name: 'some-non-selectable-column', selectable: false, - pos: 3 + pos: 3, + display_name: 'some-non-selectable-column', + column_type: 'numeric' } ]; }); @@ -67,8 +81,12 @@ define( expect($(container.find('.slick-header-columns .slick-column-name')[1]).text()) .toContain('some-column-name'); + expect($(container.find('.slick-header-columns .slick-column-name')[1]).text()) + .toContain('text'); expect($(container.find('.slick-header-columns .slick-column-name')[2]).text()) .toContain('second column'); + expect($(container.find('.slick-header-columns .slick-column-name')[2]).text()) + .toContain('json'); }); it("preserves the other attributes of column definitions", function () { @@ -95,7 +113,7 @@ define( grid = new SlickGrid(container, data, columns); grid.registerPlugin(new ActiveCellCapture()); - cellSelectionModel = new Slick.CellSelectionModel(); + cellSelectionModel = new XCellSelectionModel(); grid.setSelectionModel(cellSelectionModel); grid.registerPlugin(columnSelector); @@ -128,10 +146,10 @@ define( describe("when the user clicks an additional column header", function () { beforeEach(function () { container.find('.slick-header-column:contains(some-column-name)').click(); + container.find('.slick-header-column:contains(second column)').click(); }); it("selects additional columns", function () { - container.find('.slick-header-column:contains(second column)').click(); var selectedRanges = cellSelectionModel.getSelectedRanges(); @@ -147,30 +165,34 @@ define( describe("and presses shift + right-arrow", function () { beforeEach(function () { - container.find('.slick-header-column:contains(second column)').click(); + pressShiftArrow(KEY_RIGHT); }); it("keeps the last column selected", function () { - pressShiftArrow(KEY_RIGHT); - expect(cellSelectionModel.getSelectedRanges().length).toBe(1); }); it("grows the selection to the right", function () { - pressShiftArrow(KEY_RIGHT); - var selectedRange = cellSelectionModel.getSelectedRanges()[0]; expect(selectedRange.fromCell).toBe(2); expect(selectedRange.toCell).toBe(3); expect(selectedRange.fromRow).toBe(0); expect(selectedRange.toRow).toBe(9); }); + + it("keeps selected class on columns 2 and 3", function () { + expect($(container.find('.slick-header-column:contains(second column)')).hasClass('selected')) + .toBe(true); + expect($(container.find('.slick-header-column:contains(third column)')).hasClass('selected')) + .toBe(true); + expect($(container.find('.slick-header-column:contains(some-column-name)')).hasClass('selected')) + .toBe(false); + }); }); describe("when the user deselects the last selected column header", function () { beforeEach(function () { container.find('.slick-header-column:contains(second column)').click(); - container.find('.slick-header-column:contains(second column)').click(); }); describe("and presses shift + right-arrow", function () { @@ -226,7 +248,7 @@ define( expect(column.toCell).toBe(1); expect(column.fromRow).toBe(0); expect(column.toRow).toBe(9); - }) + }); }); describe("clicking a second time", function () { @@ -266,7 +288,7 @@ define( describe("when a non-column range was already selected", function () { beforeEach(function () { - var selectedRanges = [new Slick.Range(0, 0, 1, 0)]; + var selectedRanges = [new Slick.Range(0, 0, 2, 0)]; cellSelectionModel.setSelectedRanges(selectedRanges); }); @@ -275,7 +297,90 @@ define( var selectedRanges = cellSelectionModel.getSelectedRanges(); expectOnlyTheFirstColumnToBeSelected(selectedRanges); - }) + }); + }); + + describe('when a column is selected', function () { + beforeEach(function () { + container.find('.slick-header-column:contains(some-column-name)').click(); + }); + + describe('when the user click a cell on the current range', function () { + beforeEach(function () { + container.find('.slick-cell.l1.r1')[1].click(); + }); + + it('column is deselected', function () { + + var selectedRanges = cellSelectionModel.getSelectedRanges(); + + expect(selectedRanges.length).toBe(1); + + var column = selectedRanges[0]; + + expect(column.fromCell).toBe(1); + expect(column.toCell).toBe(1); + expect(column.fromRow).toBe(1); + expect(column.toRow).toBe(1); + }); + + it('keep select class on column header', function () { + expect($(container.find('.slick-header-column:contains(some-column-name)')).hasClass('selected')) + .toBeTruthy(); + }); + }); + + describe('when the user click a cell outside the current range', function () { + beforeEach(function () { + container.find('.slick-cell.l2.r2')[2].click(); + }); + + it('column is deselected', function () { + + var selectedRanges = cellSelectionModel.getSelectedRanges(); + + expect(selectedRanges.length).toBe(1); + + var column = selectedRanges[0]; + + expect(column.fromCell).toBe(2); + expect(column.toCell).toBe(2); + expect(column.fromRow).toBe(2); + expect(column.toRow).toBe(2); + }); + + it('remove select class on "some-column-name" column header', function () { + expect($(container.find('.slick-header-column:contains(some-column-name)')).hasClass('selected')) + .toBeFalsy(); + expect($(container.find('.slick-header-column:contains(second column)')).hasClass('selected')) + .toBeTruthy(); + }); + }); + + describe('when the user click in a row header', function () { + beforeEach(function () { + var selectedRanges = [new Slick.Range(1, 1, 1, 3)]; + cellSelectionModel.setSelectedRanges(selectedRanges); + }); + + it('column is deselected', function () { + var selectedRanges = cellSelectionModel.getSelectedRanges(); + + expect(selectedRanges.length).toBe(1); + + var column = selectedRanges[0]; + + expect(column.fromCell).toBe(1); + expect(column.toCell).toBe(3); + expect(column.fromRow).toBe(1); + expect(column.toRow).toBe(1); + }); + + it('no column should have the class "selected"', function () { + expect($(container.find('.slick-header-column:contains(some-column-name)')).hasClass('selected')) + .toBeFalsy(); + }); + }); }); }); @@ -284,7 +389,7 @@ define( columns = columnSelector.getColumnDefinitions(columns); var grid = new SlickGrid(container, data, columns, options); - var cellSelectionModel = new Slick.CellSelectionModel(); + var cellSelectionModel = new XCellSelectionModel(); grid.setSelectionModel(cellSelectionModel); grid.registerPlugin(columnSelector); diff --git a/web/regression/javascript/selection/copy_data_spec.js b/web/regression/javascript/selection/copy_data_spec.js index 718a830b..561576ce 100644 --- a/web/regression/javascript/selection/copy_data_spec.js +++ b/web/regression/javascript/selection/copy_data_spec.js @@ -10,21 +10,29 @@ define( ["jquery", "slickgrid/slick.grid", - "slickgrid/slick.rowselectionmodel", + "sources/selection/xcell_selection_model", "sources/selection/copy_data", "sources/selection/clipboard", "sources/selection/range_selection_helper" ], - function ($, SlickGrid, RowSelectionModel, copyData, clipboard, RangeSelectionHelper) { + function ($, SlickGrid, XCellSelectionModel, copyData, clipboard, RangeSelectionHelper) { describe('copyData', function () { - var grid, sqlEditor; + var grid, sqlEditor, gridContainer, buttonPasteRow; beforeEach(function () { var data = [[1, "leopord", "12"], [2, "lion", "13"], [3, "puma", "9"]]; - var columns = [{ + var columns = [ + { + id: 'row-header-column', + name: 'row header column name', + selectable: false, + display_name: 'row header column name', + column_type: 'text' + }, + { name: "id", pos: 0, label: "id
numeric", @@ -48,17 +56,18 @@ define( } ] ; - var gridContainer = $("
"); + gridContainer = $("
"); $("body").append(gridContainer); - $("body").append(""); + buttonPasteRow = $(""); + $("body").append(buttonPasteRow); grid = new Slick.Grid("#grid", data, columns, {}); - grid.setSelectionModel(new Slick.RowSelectionModel({selectActiveRow: false})); + grid.setSelectionModel(new XCellSelectionModel()); sqlEditor = {slickgrid: grid}; }); afterEach(function() { - $("body").remove('#grid'); - $("body").remove('#btn-paste-row'); + gridContainer.remove(); + buttonPasteRow.remove(); }); describe("when rows are selected", function () { @@ -92,8 +101,8 @@ define( describe("when a column is selected", function () { beforeEach(function () { - var firstColumn = new Slick.Range(0, 0, 2, 0); - grid.getSelectionModel().setSelectedRanges([firstColumn]) + var firstDataColumn = RangeSelectionHelper.rangeForColumn(grid, 1); + grid.getSelectionModel().setSelectedRanges([firstDataColumn]) }); it("copies text to the clipboard", function () { @@ -117,9 +126,11 @@ define( }); describe("when the user can edit the grid", function () { - it("disables the paste row button", function () { + beforeEach(function () { copyData.apply(_.extend({can_edit: true}, sqlEditor)); + }); + it("disables the paste row button", function () { expect($("#btn-paste-row").prop('disabled')).toBe(true); }); }); diff --git a/web/regression/javascript/selection/grid_selector_spec.js b/web/regression/javascript/selection/grid_selector_spec.js index 5ea63692..d79d417f 100644 --- a/web/regression/javascript/selection/grid_selector_spec.js +++ b/web/regression/javascript/selection/grid_selector_spec.js @@ -1,12 +1,12 @@ define(["jquery", "underscore", "slickgrid/slick.grid", - "slickgrid/slick.rowselectionmodel", + "sources/selection/xcell_selection_model", "sources/selection/grid_selector" ], - function ($, _, SlickGrid, RowSelectionModel, GridSelector) { + function ($, _, SlickGrid, XCellSelectionModel, GridSelector) { describe("GridSelector", function () { - var container, data, columns, gridSelector, rowSelectionModel; + var container, data, columns, gridSelector, xCellSelectionModel; beforeEach(function () { container = $("
"); @@ -30,8 +30,8 @@ define(["jquery", } var grid = new SlickGrid(container, data, columns); - rowSelectionModel = new RowSelectionModel(); - grid.setSelectionModel(rowSelectionModel); + xCellSelectionModel = new XCellSelectionModel(); + grid.setSelectionModel(xCellSelectionModel); grid.registerPlugin(gridSelector); grid.invalidate(); @@ -58,7 +58,7 @@ define(["jquery", it("selects the whole grid", function () { container.find("[title='Select/Deselect All']").parent().click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); + var selectedRanges = xCellSelectionModel.getSelectedRanges(); expect(selectedRanges.length).toBe(1); var selectedRange = selectedRanges[0]; expect(selectedRange.fromCell).toBe(1); @@ -79,7 +79,7 @@ define(["jquery", it("selects all the cells", function () { container.find("[title='Select/Deselect All']").click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); + var selectedRanges = xCellSelectionModel.getSelectedRanges(); expect(selectedRanges.length).toBe(1); var selectedRange = selectedRanges[0]; expect(selectedRange.fromCell).toBe(1); @@ -96,7 +96,7 @@ define(["jquery", it("deselects all the cells", function () { container.find("[title='Select/Deselect All']").click(); - var selectedRanges = rowSelectionModel.getSelectedRanges(); + var selectedRanges = xCellSelectionModel.getSelectedRanges(); expect(selectedRanges.length).toBe(0); }); }); diff --git a/web/regression/javascript/selection/row_selector_spec.js b/web/regression/javascript/selection/row_selector_spec.js index 78348bca..e0bc63be 100644 --- a/web/regression/javascript/selection/row_selector_spec.js +++ b/web/regression/javascript/selection/row_selector_spec.js @@ -4,13 +4,17 @@ define( "slickgrid/slick.grid", "sources/selection/active_cell_capture", "sources/selection/row_selector", + 'sources/selection/xcell_selection_model', "slickgrid", - 'slickgrid/slick.cellrangedecorator', - 'slickgrid/slick.cellrangeselector', - "slickgrid/slick.cellselectionmodel", + 'slickgrid/plugins/slick.cellrangedecorator', + 'slickgrid/plugins/slick.cellrangeselector', ], - function ($, _, SlickGrid, ActiveCellCapture, RowSelector, Slick) { + function ($, _, SlickGrid, ActiveCellCapture, RowSelector, XCellSelectionModel, Slick) { + var KEY_RIGHT = 39; + var KEY_LEFT = 37; + var KEY_UP = 38; + var KEY_DOWN = 40; describe("RowSelector", function () { var container, data, columnDefinitions, grid, cellSelectionModel; @@ -40,7 +44,7 @@ define( grid = new SlickGrid(container, data, columnDefinitions); grid.registerPlugin(new ActiveCellCapture()); - cellSelectionModel = new Slick.CellSelectionModel(); + cellSelectionModel = new XCellSelectionModel(); grid.setSelectionModel(cellSelectionModel); grid.registerPlugin(rowSelector); @@ -81,28 +85,152 @@ define( expectOnlyTheFirstRowToBeSelected(selectedRanges); }); - it("add selected class to span", function () { + it("add selected class to parent of the span", function () { container.find('.slick-row .slick-cell:first-child span[data-cell-type="row-header-selector"]')[5].click(); - expect($(container.find('.slick-row .slick-cell:first-child span[data-cell-type="row-header-selector"]')[5]) + expect($(container.find('.slick-row .slick-cell:first-child ')[5]) .hasClass('selected')).toBeTruthy(); }); }); describe("when the user clicks a row header", function () { + beforeEach(function () { + container.find('.slick-row .slick-cell:first-child')[1].click(); + + }); it("selects the row", function () { - container.find('.slick-row .slick-cell:first-child')[0].click(); var selectedRanges = cellSelectionModel.getSelectedRanges(); - expectOnlyTheFirstRowToBeSelected(selectedRanges); + var row = selectedRanges[0]; + + expect(selectedRanges.length).toEqual(1); + expect(row.fromCell).toBe(1); + expect(row.toCell).toBe(2); + expect(row.fromRow).toBe(1); + expect(row.toRow).toBe(1); }); - it("add selected class to span", function () { - container.find('.slick-row .slick-cell:first-child')[7].click(); + it("add selected class to parent of the span", function () { - expect($(container.find('.slick-row .slick-cell:first-child span[data-cell-type="row-header-selector"]')[7]) + expect($(container.find('.slick-row .slick-cell:first-child ')[1]) .hasClass('selected')).toBeTruthy(); }); + + describe("when the user clicks again the same row header", function () { + it("add selected class to parent of the span", function () { + container.find('.slick-row .slick-cell:first-child span[data-cell-type="row-header-selector"]')[1].click(); + + expect($(container.find('.slick-row .slick-cell:first-child ')[1]) + .hasClass('selected')).toBeFalsy(); + }); + }); + + describe("and presses shift + down-arrow", function () { + beforeEach(function () { + pressShiftArrow(KEY_DOWN); + }); + + it("keeps the last row selected", function () { + expect(cellSelectionModel.getSelectedRanges().length).toBe(1); + }); + + it("grows the selection down", function () { + var selectedRanges = cellSelectionModel.getSelectedRanges(); + + var row = selectedRanges[0]; + + expect(selectedRanges.length).toEqual(1); + expect(row.fromCell).toBe(1); + expect(row.toCell).toBe(2); + expect(row.fromRow).toBe(1); + expect(row.toRow).toBe(2); + }); + + it("keeps selected class on rows 1 and 2", function () { + expect($(container.find('.slick-row .slick-cell:first-child ')[0]) + .hasClass('selected')).toBeFalsy(); + expect($(container.find('.slick-row .slick-cell:first-child ')[1]) + .hasClass('selected')).toBeTruthy(); + expect($(container.find('.slick-row .slick-cell:first-child ')[2]) + .hasClass('selected')).toBeTruthy(); + expect($(container.find('.slick-row .slick-cell:first-child ')[3]) + .hasClass('selected')).toBeFalsy(); + }); + }); + + describe('when the user clicks a cell on the current range', function () { + beforeEach(function () { + container.find('.slick-cell.l1.r1')[5].click(); + }); + + it('row gets deselected', function () { + + var selectedRanges = cellSelectionModel.getSelectedRanges(); + + expect(selectedRanges.length).toBe(1); + + var newSelection = selectedRanges[0]; + + expect(newSelection.fromCell).toBe(1); + expect(newSelection.fromRow).toBe(5); + expect(newSelection.toCell).toBe(1); + expect(newSelection.toRow).toBe(5); + }); + + it('keep select class on row header', function () { + expect($(container.find('.slick-cell.l0.r0')[5]).hasClass('selected')) + .toBeTruthy(); + }); + }); + + describe('when the user clicks a cell outside the current range', function () { + beforeEach(function () { + container.find('.slick-cell.l2.r2')[2].click(); + }); + + it('row gets deselected', function () { + + var selectedRanges = cellSelectionModel.getSelectedRanges(); + + expect(selectedRanges.length).toBe(1); + + var newSelection = selectedRanges[0]; + + expect(newSelection.fromCell).toBe(2); + expect(newSelection.fromRow).toBe(2); + expect(newSelection.toCell).toBe(2); + expect(newSelection.toRow).toBe(2); + }); + + it('remove select class on "some-column-name" column header', function () { + expect($(container.find('.slick-cell.l0.r0')[5]).hasClass('selected')) + .toBeFalsy(); + expect($(container.find('.slick-cell.l0.r0')[2]).hasClass('selected')) + .toBeTruthy(); + }); + }); + + describe('when the user has a column selected', function () { + beforeEach(function () { + var selectedRanges = [new Slick.Range(0, 1, 9, 1)]; + cellSelectionModel.setSelectedRanges(selectedRanges); + }); + + it('no row should have the class "selected"', function () { + expect($(container.find('.slick-cell.l0.r0')[0]).hasClass('selected')) + .toBeFalsy(); + expect($(container.find('.slick-cell.l0.r0')[1]).hasClass('selected')) + .toBeFalsy(); + expect($(container.find('.slick-cell.l0.r0')[2]).hasClass('selected')) + .toBeFalsy(); + expect($(container.find('.slick-cell.l0.r0')[3]).hasClass('selected')) + .toBeFalsy(); + expect($(container.find('.slick-cell.l0.r0')[4]).hasClass('selected')) + .toBeFalsy(); + expect($(container.find('.slick-cell.l0.r0')[5]).hasClass('selected')) + .toBeFalsy(); + }); + }); }); describe("when the user clicks multiple row headers", function () { @@ -134,7 +262,7 @@ define( var selectedRanges = cellSelectionModel.getSelectedRanges(); expectOnlyTheFirstRowToBeSelected(selectedRanges); - }) + }); }); describe("when the row is deselected through setSelectedRanges", function () { @@ -169,6 +297,16 @@ define( }) }); }); + + function pressShiftArrow(keyCode) { + var pressEvent = new $.Event("keydown"); + pressEvent.shiftKey = true; + pressEvent.ctrlKey = false; + pressEvent.altKey = false; + pressEvent.which = keyCode; + + $(container.find('.grid-canvas')).trigger(pressEvent); + } }); function expectOnlyTheFirstRowToBeSelected(selectedRanges) { diff --git a/web/regression/javascript/selection/set_staged_rows_spec.js b/web/regression/javascript/selection/set_staged_rows_spec.js index 8949a0f4..99c6d3a6 100644 --- a/web/regression/javascript/selection/set_staged_rows_spec.js +++ b/web/regression/javascript/selection/set_staged_rows_spec.js @@ -12,77 +12,75 @@ define([ "underscore", "sources/selection/set_staged_rows", ], function ($, _, SetStagedRows) { - describe('when no full rows are selected', function () { - var sqlEditorObj, deleteButton, copyButton; + describe('set_staged_rows', function () { + var sqlEditorObj, gridSpy, deleteButton, copyButton, selectionSpy; beforeEach(function () { - var gridSpy = jasmine.createSpyObj('gridSpy', ['getData', 'getCellNode']); - + gridSpy = jasmine.createSpyObj('gridSpy', ['getData', 'getCellNode', 'getColumns']); gridSpy.getData.and.returnValue([ {0: 'one', 1: 'two', __temp_PK: '123'}, {0: 'three', 1: 'four', __temp_PK: '456'}, {0: 'five', 1: 'six', __temp_PK: '789'}, {0: 'seven', 1: 'eight', __temp_PK: '432'} ]); + gridSpy.getColumns.and.returnValue([ + { + pos: 0, + selectable: true, + }, { + pos: 1, + selectable: true, + } + ]); + + selectionSpy = jasmine.createSpyObj('selectionSpy', ['setSelectedRows', 'getSelectedRanges']); + deleteButton = $(''); copyButton = $(''); - var selectionSpy = jasmine.createSpyObj('selectionSpy', [ - 'getSelectedRanges', - ]); - sqlEditorObj = { grid: gridSpy, editor: { handler: { data_store: { - staged_rows: {1: [1, 2]} - } + staged_rows: {'456': {}} + }, + can_edit: false } }, + keys: null, selection: selectionSpy, + columns: [ + { + name: 'a pk column', + pos: 0 + }, + { + name: 'some column', + pos: 1 + } + ] }; + $('body').append(deleteButton); $('body').append(copyButton); - deleteButton.prop('disabled', false); - copyButton.prop('disabled', false); + + deleteButton.prop('disabled', true); + copyButton.prop('disabled', true); + + selectionSpy = jasmine.createSpyObj('selectionSpy', [ + 'setSelectedRows', + 'getSelectedRanges', + ]); }); afterEach(function () { copyButton.remove(); deleteButton.remove(); }); - - describe('when getSelectedRows is not present in the selection model', function () { - beforeEach(function () { - SetStagedRows.call(sqlEditorObj, {}, {}); - }); - it('should disable the delete row button', function () { - expect($('#btn-delete-row').prop('disabled')).toBeTruthy(); - }); - - it('should disable the copy row button', function () { - expect($('#btn-copy-row').prop('disabled')).toBeTruthy(); - }); - - it('should clear staged rows', function () { - expect(sqlEditorObj.editor.handler.data_store.staged_rows).toEqual({}); - }); - }); - - describe('when getSelectedRows is present in the selection model', function () { - var selectionSpy; - beforeEach(function () { - selectionSpy = jasmine.createSpyObj('selectionSpy', [ - 'getSelectedRows', - 'setSelectedRows', - 'getSelectedRanges', - ]); - selectionSpy.getSelectedRows.and.returnValue([]); - }); - + describe('when no full rows are selected', function () { describe('when nothing is selected', function () { beforeEach(function () { - selectionSpy.getSelectedRanges.and.returnValue({length: 0}); + selectionSpy.getSelectedRanges.and.returnValue([]); sqlEditorObj.selection = selectionSpy; SetStagedRows.call(sqlEditorObj, {}, {}); }); @@ -102,7 +100,14 @@ define([ describe('when there is a selection', function () { beforeEach(function () { - selectionSpy.getSelectedRanges.and.returnValue({length: 1}); + var range = { + fromCell: 0, + toCell: 0, + fromRow: 1, + toRow: 1, + }; + + selectionSpy.getSelectedRanges.and.returnValue([range]); sqlEditorObj.selection = selectionSpy; SetStagedRows.call(sqlEditorObj, {}, {}); }); @@ -120,62 +125,24 @@ define([ }); }); }); - }); - describe('when 2 full rows are selected', function () { - describe('when getSelectedRows is present in the selection model', function () { - var sqlEditorObj, gridSpy, deleteButton, copyButton; + describe('when 2 full rows are selected', function () { beforeEach(function () { - gridSpy = jasmine.createSpyObj('gridSpy', ['getData', 'getCellNode']); - gridSpy.getData.and.returnValue([ - {0: 'one', 1: 'two', __temp_PK: '123'}, - {0: 'three', 1: 'four', __temp_PK: '456'}, - {0: 'five', 1: 'six', __temp_PK: '789'}, - {0: 'seven', 1: 'eight', __temp_PK: '432'} - ]); - - var selectionSpy = jasmine.createSpyObj('selectionSpy', ['getSelectedRows', 'setSelectedRows', 'getSelectedRanges']); - selectionSpy.getSelectedRows.and.returnValue([1, 2]); - selectionSpy.getSelectedRanges.and.returnValue({length: 1}); - - deleteButton = $(''); - copyButton = $(''); - - sqlEditorObj = { - grid: gridSpy, - editor: { - handler: { - data_store: { - staged_rows: {'456': {}} - }, - can_edit: false - } - }, - keys: null, - selection: selectionSpy, - columns: [ - { - name: 'a pk column', - pos: 0 - }, - { - name: 'some column', - pos: 1 - } - ] + var range1 = { + fromCell: 0, + toCell: 1, + fromRow: 1, + toRow: 1, + }; + var range2 = { + fromCell: 0, + toCell: 1, + fromRow: 2, + toRow: 2, }; - $('body').append(deleteButton); - $('body').append(copyButton); - - deleteButton.prop('disabled', true); - copyButton.prop('disabled', true); - - }); - - afterEach(function () { - copyButton.remove(); - deleteButton.remove(); + selectionSpy.getSelectedRanges.and.returnValue([range1, range2]); + sqlEditorObj.selection = selectionSpy; }); describe('when table does not have primary keys', function () { @@ -220,7 +187,6 @@ define([ SetStagedRows.call(sqlEditorObj, {}, {}); expect(sqlEditorObj.selection.setSelectedRows).not.toHaveBeenCalledWith(); }); - }); describe('selected rows missing primary key', function () { @@ -242,7 +208,6 @@ define([ SetStagedRows.call(sqlEditorObj, {}, {}); expect(sqlEditorObj.selection.setSelectedRows).toHaveBeenCalledWith([]); }); - }); describe('when the selected row is a new row', function () { @@ -274,4 +239,4 @@ define([ }); }); }); -}); \ No newline at end of file +}); diff --git a/web/regression/javascript/selection/xcell_selection_model_spec.js b/web/regression/javascript/selection/xcell_selection_model_spec.js new file mode 100644 index 00000000..1682c244 --- /dev/null +++ b/web/regression/javascript/selection/xcell_selection_model_spec.js @@ -0,0 +1,513 @@ +////////////////////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2017, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////////////////// + +define([ + 'sources/selection/xcell_selection_model', + "slickgrid/slick.grid", + 'slickgrid', +], function (XCellSelectionModel, SlickGrid, Slick) { + describe('XCellSelectionModel', function () { + var KEY_RIGHT = 39; + var KEY_LEFT = 37; + var KEY_UP = 38; + var KEY_DOWN = 40; + + var container, grid; + var oldWindowParent = window.parent; + + beforeEach(function () { + window.parent = window; + + var columns = [{ + id: 'row-header-column', + name: 'row header column name', + selectable: false, + }, { + id: '1', + name: 'some-column-name', + pos: 0 + }, { + id: 'second-column-id', + name: 'second column', + pos: 1 + }, { + id: 'third-column-id', + name: 'third column', + pos: 2 + }, + ]; + + var data = []; + for (var i = 0; i < 10; i++) { + data.push({ + 'some-column-name': 'some-value-' + i, + 'second column': 'second value ' + i, + 'third column': 'third value ' + i, + 'fourth column': 'fourth value ' + i, + }); + } + container = $("
"); + container.height(9999); + container.width(9999); + + grid = new SlickGrid(container, data, columns); + grid.setSelectionModel(new XCellSelectionModel()); + $("body").append(container); + }); + + afterEach(function () { + grid.destroy(); + container.remove(); + window.parent = oldWindowParent; + }); + + describe('handleKeyDown', function () { + describe('when we press a random key', function () { + it('should not change the range', function () { + var range = new Slick.Range(1, 2); + grid.setActiveCell(1, 2); + grid.getSelectionModel().setSelectedRanges([range]); + pressKey(72); + + expect(grid.getSelectionModel().getSelectedRanges()[0]).toEqual(range); + }); + }); + + describe('when we press an arrow key ', function () { + it('should select the cell to the right', function () { + var range = new Slick.Range(1, 2); + grid.setActiveCell(1, 2); + grid.getSelectionModel().setSelectedRanges([range]); + pressKey(KEY_RIGHT); + + expectOneSelectedRange(1, 3, 1, 3); + }); + }); + + describe('when we press shift', function () { + describe('and we press an arrow key', function () { + var scrollColumnIntoViewSpy, scrollRowIntoViewSpy; + + beforeEach(function () { + scrollColumnIntoViewSpy = spyOn(grid, 'scrollColumnIntoView'); + scrollRowIntoViewSpy = spyOn(grid, 'scrollRowIntoView'); + }); + + describe('the right arrow', function () { + describe('when a cell is selected', function () { + beforeEach(function () { + var range = new Slick.Range(1, 1); + grid.setActiveCell(1, 1); + grid.getSelectionModel().setSelectedRanges([range]); + pressShiftPlusKey(KEY_RIGHT); + }); + + it('increases the range by one to the right', function () { + expectOneSelectedRange(1, 1, 1, 2); + }); + + it('should scroll the next column into view', function () { + expect(scrollColumnIntoViewSpy).toHaveBeenCalledWith(2); + expect(scrollRowIntoViewSpy).not.toHaveBeenCalled(); + }); + + it('pressing right again grows the range right', function () { + pressShiftPlusKey(KEY_RIGHT); + + expectOneSelectedRange(1, 1, 1, 3); + }); + + it('then pressing left keeps the original selection', function () { + pressShiftPlusKey(KEY_LEFT); + + expectOneSelectedRange(1, 1, 1, 1); + }); + }); + + describe('when a column is selected', function () { + beforeEach(function () { + var range = new Slick.Range(0, 1, 9, 1); + grid.setActiveCell(0, 1); + grid.getSelectionModel().setSelectedRanges([range]); + pressShiftPlusKey(KEY_RIGHT); + }); + + it('increases the range by one column to the right', function () { + expectOneSelectedRange(0, 1, 9, 2); + }); + + it('should scroll the next column into view', function () { + expect(scrollColumnIntoViewSpy).toHaveBeenCalledWith(2); + expect(scrollRowIntoViewSpy).not.toHaveBeenCalled(); + }); + }); + }); + + describe('the left arrow', function () { + describe('when a cell is selected', function () { + beforeEach(function () { + var range = new Slick.Range(1, 3); + grid.setActiveCell(1, 3); + grid.getSelectionModel().setSelectedRanges([range]); + pressShiftPlusKey(KEY_LEFT); + }); + + it('increases the range by one to the left', function () { + expectOneSelectedRange(1, 2, 1, 3); + }); + + it('should scroll previous column into view', function () { + expect(scrollColumnIntoViewSpy).toHaveBeenCalledWith(2); + expect(scrollRowIntoViewSpy).not.toHaveBeenCalled(); + }); + + it('pressing left again grows the range the left', function () { + pressShiftPlusKey(KEY_LEFT); + + expectOneSelectedRange(1, 1, 1, 3); + }); + + it('then pressing right keeps the original selection', function () { + pressShiftPlusKey(KEY_RIGHT); + + expectOneSelectedRange(1, 3, 1, 3); + }); + }); + + describe('when a column is selected', function () { + beforeEach(function () { + var range = new Slick.Range(0, 2, 9, 2); + grid.setActiveCell(0, 2); + grid.getSelectionModel().setSelectedRanges([range]); + pressShiftPlusKey(KEY_LEFT); + }); + + it('increases the range by one column to the left', function () { + expectOneSelectedRange(0, 1, 9, 2); + }); + + it('should scroll previous column into view', function () { + expect(scrollColumnIntoViewSpy).toHaveBeenCalledWith(1); + expect(scrollRowIntoViewSpy).not.toHaveBeenCalled(); + }); + }); + }); + + describe('the up arrow', function () { + describe('when a cell is selected', function () { + beforeEach(function () { + var range = new Slick.Range(2, 2); + grid.setActiveCell(2, 2); + grid.getSelectionModel().setSelectedRanges([range]); + pressShiftPlusKey(KEY_UP); + }); + + it('increases the range by one up', function () { + expectOneSelectedRange(1, 2, 2, 2); + }); + + it('should scroll the row above into view', function () { + expect(scrollRowIntoViewSpy).toHaveBeenCalledWith(1); + expect(scrollColumnIntoViewSpy).not.toHaveBeenCalled(); + }); + + it('pressing up again grows the range up', function () { + pressShiftPlusKey(KEY_UP); + + expectOneSelectedRange(0, 2, 2, 2); + }); + + it('then pressing down keeps the original selection', function () { + pressShiftPlusKey(KEY_DOWN); + + expectOneSelectedRange(2, 2, 2, 2); + }); + }); + + describe('when a row is selected', function () { + beforeEach(function () { + var range = new Slick.Range(2, 1, 2, 3); + grid.setActiveCell(2, 1); + grid.getSelectionModel().setSelectedRanges([range]); + pressShiftPlusKey(KEY_UP); + }); + + it('increases the range by one row up', function () { + expectOneSelectedRange(1, 1, 2, 3); + }); + + it('should scroll the row above into view', function () { + expect(scrollRowIntoViewSpy).toHaveBeenCalledWith(1); + expect(scrollColumnIntoViewSpy).not.toHaveBeenCalled(); + }); + }); + }); + + describe('the down arrow', function () { + describe('when a cell is selected', function () { + beforeEach(function () { + var range = new Slick.Range(2, 2); + grid.setActiveCell(2, 2); + grid.getSelectionModel().setSelectedRanges([range]); + pressShiftPlusKey(KEY_DOWN); + }); + + it('increases the range by one down', function () { + expectOneSelectedRange(2, 2, 3, 2); + }); + + it('should scroll the row below into view', function () { + expect(scrollRowIntoViewSpy).toHaveBeenCalledWith(3); + expect(scrollColumnIntoViewSpy).not.toHaveBeenCalled(); + }); + + it('pressing down again grows the range down', function () { + pressShiftPlusKey(KEY_DOWN); + + expectOneSelectedRange(2, 2, 4, 2); + }); + + it('then pressing up keeps the original selection', function () { + pressShiftPlusKey(KEY_UP); + + expectOneSelectedRange(2, 2, 2, 2); + }); + }); + + describe('when a row is selected', function () { + beforeEach(function () { + var range = new Slick.Range(2, 1, 2, 3); + grid.setActiveCell(2, 1); + grid.getSelectionModel().setSelectedRanges([range]); + pressShiftPlusKey(KEY_DOWN); + }); + + it('increases the range by one row down', function () { + expectOneSelectedRange(2, 1, 3, 3); + }); + + it('should scroll the row below into view', function () { + expect(scrollRowIntoViewSpy).toHaveBeenCalledWith(3); + expect(scrollColumnIntoViewSpy).not.toHaveBeenCalled(); + }); + }); + }); + + describe('rectangular selection works', function () { + + it('in the down-and-rightward direction', function () { + var range = new Slick.Range(1, 1); + grid.setActiveCell(1, 1); + grid.getSelectionModel().setSelectedRanges([range]); + + pressShiftPlusKey(KEY_DOWN); + pressShiftPlusKey(KEY_DOWN); + pressShiftPlusKey(KEY_DOWN); + pressShiftPlusKey(KEY_RIGHT); + pressShiftPlusKey(KEY_RIGHT); + + expectOneSelectedRange(1, 1, 4, 3); + }); + + it('in the up-and-leftward direction', function () { + var range = new Slick.Range(4, 3); + grid.setActiveCell(4, 3); + grid.getSelectionModel().setSelectedRanges([range]); + + pressShiftPlusKey(KEY_UP); + pressShiftPlusKey(KEY_UP); + pressShiftPlusKey(KEY_UP); + pressShiftPlusKey(KEY_LEFT); + pressShiftPlusKey(KEY_LEFT); + + expectOneSelectedRange(1, 1, 4, 3); + }); + + it('in the up-and-rightward direction', function () { + var range = new Slick.Range(4, 1); + grid.setActiveCell(4, 1); + grid.getSelectionModel().setSelectedRanges([range]); + + pressShiftPlusKey(KEY_UP); + pressShiftPlusKey(KEY_UP); + pressShiftPlusKey(KEY_UP); + pressShiftPlusKey(KEY_RIGHT); + pressShiftPlusKey(KEY_RIGHT); + + expectOneSelectedRange(1, 1, 4, 3); + }); + + it('in the down-and-leftward direction', function () { + var range = new Slick.Range(1, 3); + grid.setActiveCell(1, 3); + grid.getSelectionModel().setSelectedRanges([range]); + + pressShiftPlusKey(KEY_DOWN); + pressShiftPlusKey(KEY_DOWN); + pressShiftPlusKey(KEY_DOWN); + pressShiftPlusKey(KEY_LEFT); + pressShiftPlusKey(KEY_LEFT); + + expectOneSelectedRange(1, 1, 4, 3); + }); + }); + + describe('and we are on an edge', function () { + var range; + + beforeEach(function () { + range = new Slick.Range(2, 1); + grid.setActiveCell(2, 1); + grid.getSelectionModel().setSelectedRanges([range]); + }); + + it('we still have the selected range before we arrowed', function () { + pressShiftPlusKey(KEY_LEFT); + expectOneSelectedRange(2, 1, 2, 1); + }); + }); + }); + }); + }); + + describe('when we drag and drop', function () { + var dd; + // We could not find an elegant way to calculate this value + // after changing window size we saw this was a constant value + var offsetLeftColumns = 100; + + function cellTopPosition($cell, rowNumber) { + return $(grid.getCanvasNode()).offset().top + $cell[0].scrollHeight * rowNumber; + } + + function cellLeftPosition(columnNumber) { + return $(grid.getCanvasNode()).offset().left + offsetLeftColumns * columnNumber; + } + + beforeEach(function () { + var initialPosition = {cell: 3, row: 4}; + var $cell = $($('.slick-cell.l3')[initialPosition.row]); + var event = { + target: $cell, + isPropagationStopped: jasmine.createSpy('isPropagationStopped').and.returnValue(false), + isImmediatePropagationStopped: jasmine.createSpy('isImmediatePropagationStopped').and.returnValue(false), + stopImmediatePropagation: jasmine.createSpy('stopImmediatePropagation') + }; + + dd = { + grid: grid, + startX: cellLeftPosition(initialPosition.cell), + startY: cellTopPosition($cell, initialPosition.row) + }; + + grid.onDragStart.notify(dd, event, grid); + }); + + describe('when the drop happens outside of the grid', function () { + beforeEach(function () { + var $cell = $($('.slick-cell.l1')[1]); + var finalPosition = {cell: 1, row: 1}; + + var event = { + target: $cell, + isPropagationStopped: jasmine.createSpy('isPropagationStopped').and.returnValue(false), + isImmediatePropagationStopped: jasmine.createSpy('isImmediatePropagationStopped').and.returnValue(false), + stopImmediatePropagation: jasmine.createSpy('stopImmediatePropagation'), + + pageX: cellLeftPosition(finalPosition.cell), + pageY: cellTopPosition($cell, finalPosition.row) + }; + + grid.onDrag.notify(dd, event, grid); + $(window).mouseup(); + }); + it('should call handleDragEnd from CellRangeSelector', function () { + var newRange = grid.getSelectionModel().getSelectedRanges(); + + expect(newRange.length).toBe(1); + + expect(newRange[0].fromCell).toBe(1); + expect(newRange[0].toCell).toBe(3); + expect(newRange[0].fromRow).toBe(1); + expect(newRange[0].toRow).toBe(4); + }); + }); + }); + + describe('when we mouse up and no drag and drop occured', function () { + beforeEach(function () { + grid.onDragEnd.notify = jasmine.createSpy('notify'); + grid.onDragEnd.notify.calls.reset(); + $(window).mouseup(); + }); + + it('do not notify onDragEnd', function () { + expect(grid.onDragEnd.notify).not.toHaveBeenCalled() + }); + }); + + describe('setSelectedRows', function () { + + beforeEach(function () { + grid.getSelectionModel().setSelectedRanges( + [new Slick.Range(1, 1, 1, 1)] + ); + }); + + describe('when passed an empty array', function () { + beforeEach(function () { + grid.getSelectionModel().setSelectedRows([]); + }); + it('clears ranges', function () { + var newRanges = grid.getSelectionModel().getSelectedRanges(); + expect(newRanges.length).toEqual(0); + }); + }); + + it('sets ranges corresponding to rows', function () { + grid.getSelectionModel().setSelectedRows([0, 2]); + + var selectedRanges = grid.getSelectionModel().getSelectedRanges(); + + expect(selectedRanges.length).toBe(2); + expectRangeToMatch(selectedRanges[0], 0, 1, 0, 3); + expectRangeToMatch(selectedRanges[1], 2, 1, 2, 3); + }); + }); + + function pressKey(keyCode) { + var pressEvent = new $.Event("keydown"); + pressEvent.which = keyCode; + + $(container.find('.grid-canvas')).trigger(pressEvent); + } + + function pressShiftPlusKey(keyCode) { + var pressEvent = new $.Event("keydown"); + pressEvent.shiftKey = true; + pressEvent.which = keyCode; + + $(container.find('.grid-canvas')).trigger(pressEvent); + } + + function expectOneSelectedRange(fromRow, fromCell, toRow, toCell) { + var selectedRanges = grid.getSelectionModel().getSelectedRanges(); + expect(selectedRanges.length).toBe(1); + expectRangeToMatch(selectedRanges[0], fromRow, fromCell, toRow, toCell); + } + + function expectRangeToMatch(range, fromRow, fromCell, toRow, toCell) { + expect(range.fromRow).toBe(fromRow); + expect(range.toRow).toBe(toRow); + expect(range.fromCell).toBe(fromCell); + expect(range.toCell).toBe(toCell); + } + }); +}) +; \ No newline at end of file diff --git a/web/regression/javascript/slickgrid/cell_selector_spec.js b/web/regression/javascript/slickgrid/cell_selector_spec.js index 841ea72b..ed7c0aee 100644 --- a/web/regression/javascript/slickgrid/cell_selector_spec.js +++ b/web/regression/javascript/slickgrid/cell_selector_spec.js @@ -9,11 +9,11 @@ define(["jquery", "slickgrid/slick.grid", - "slickgrid/slick.cellselectionmodel", + "sources/selection/xcell_selection_model", "sources/slickgrid/cell_selector", "sources/selection/range_selection_helper" ], - function ($, SlickGrid, CellSelectionModel, CellSelector, RangeSelectionHelper) { + function ($, SlickGrid, XCellSelectionModel, CellSelector, RangeSelectionHelper) { describe("CellSelector", function () { var container, columns, cellSelector, data, cellSelectionModel, grid; beforeEach(function () { @@ -34,7 +34,7 @@ define(["jquery", } grid = new SlickGrid(container, data, columns); - cellSelectionModel = new CellSelectionModel(); + cellSelectionModel = new XCellSelectionModel(); grid.setSelectionModel(cellSelectionModel); grid.registerPlugin(cellSelector); diff --git a/web/regression/javascript/slickgrid/event_handlers/handle_query_output_keyboard_event_spec.js b/web/regression/javascript/slickgrid/event_handlers/handle_query_output_keyboard_event_spec.js index b61e5aff..14768227 100644 --- a/web/regression/javascript/slickgrid/event_handlers/handle_query_output_keyboard_event_spec.js +++ b/web/regression/javascript/slickgrid/event_handlers/handle_query_output_keyboard_event_spec.js @@ -11,12 +11,10 @@ define([ 'sources/slickgrid/event_handlers/handle_query_output_keyboard_event', 'sources/selection/clipboard', 'sources/selection/range_selection_helper', - 'slickgrid', - 'slickgrid/slick.cellrangedecorator', - 'slickgrid/slick.cellrangeselector', - 'slickgrid/slick.cellselectionmodel', + 'sources/selection/xcell_selection_model', + 'slickgrid' ], -function (handleQueryOutputKeyboardEvent, clipboard, RangeSelectionHelper) { +function (handleQueryOutputKeyboardEvent, clipboard, RangeSelectionHelper, XCellSelectionModel) { var Slick = window.Slick; describe('#handleQueryOutputKeyboardEvent', function () { @@ -39,7 +37,7 @@ function (handleQueryOutputKeyboardEvent, clipboard, RangeSelectionHelper) { name: 'secondColumn' }]; grid = new Slick.Grid($('
'), data, columnDefinitions); - grid.setSelectionModel(new Slick.CellSelectionModel()); + grid.setSelectionModel(new XCellSelectionModel()); slickEvent = { grid: grid diff --git a/web/regression/javascript/test-main.js b/web/regression/javascript/test-main.js index ba200a35..065c950f 100644 --- a/web/regression/javascript/test-main.js +++ b/web/regression/javascript/test-main.js @@ -36,10 +36,8 @@ require.config({ 'underscore.string': sourcesDir + 'vendor/underscore/underscore.string', 'slickgrid': sourcesDir + 'vendor/slickgrid/slick.core', 'slickgrid/slick.grid': sourcesDir + 'vendor/slickgrid/slick.grid', - 'slickgrid/slick.rowselectionmodel': sourcesDir + 'vendor/slickgrid/plugins/slick.rowselectionmodel', - 'slickgrid/slick.cellselectionmodel': sourcesDir + 'vendor/slickgrid/plugins/slick.cellselectionmodel', - 'slickgrid/slick.cellrangedecorator': sourcesDir + 'vendor/slickgrid/plugins/slick.cellrangedecorator', - 'slickgrid/slick.cellrangeselector': sourcesDir + 'vendor/slickgrid/plugins/slick.cellrangeselector', + 'slickgrid/plugins/slick.cellrangedecorator': sourcesDir + 'vendor/slickgrid/plugins/slick.cellrangedecorator', + 'slickgrid/plugins/slick.cellrangeselector': sourcesDir + 'vendor/slickgrid/plugins/slick.cellrangeselector', 'translations': '/base/regression/javascript/fake_translations', 'sources': sourcesDir + 'js', 'browser': '/base/pgadmin/browser/static/js' @@ -61,29 +59,23 @@ require.config({ ], "exports": 'window.Slick.Grid' }, - "slickgrid/slick.rowselectionmodel": { - "deps": [ - "jquery" - ], - "exports": 'window.Slick.RowSelectionModel' - }, - "slickgrid/slick.cellrangedecorator": { + "slickgrid/plugins/slick.cellrangedecorator": { "deps": [ "jquery" ], "exports": 'window.Slick.RowRangeDecorator' }, - "slickgrid/slick.cellrangeselector": { + "slickgrid/plugins/slick.cellrangeselector": { "deps": [ - "jquery", "slickgrid/slick.cellrangedecorator" + "jquery", "slickgrid/plugins/slick.cellrangedecorator" ], "exports": 'window.Slick.CellRangeSelector' }, - "slickgrid/slick.cellselectionmodel": { + "sources/selection/xcell_selection_model": { "deps": [ - "jquery", "slickgrid/slick.cellrangeselector" + "jquery", "slickgrid/plugins/slick.cellrangeselector" ], - "exports": 'window.Slick.CellSelectionModel' + "exports": 'XCellSelectionModel' }, "backbone": { "deps": ['underscore', 'jquery'],