Skip to content

Commit

Permalink
feat(plugins): Apply auto scroll when dragging on RowMoveManager plugin
Browse files Browse the repository at this point in the history
- add auto-scroll on RowMoveManager, this is a continuation of previous PR #662
  • Loading branch information
ghiscoding committed Dec 22, 2021
1 parent 941e99c commit 1c14a4f
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ describe('SlickRowMoveManager Plugin', () => {
plugin.init(gridStub);

expect(plugin.addonOptions).toEqual({
autoScroll: true,
cancelEditOnDrag: false,
columnId: '_move',
cssClass: 'slick-row-move-column',
Expand Down Expand Up @@ -154,6 +155,7 @@ describe('SlickRowMoveManager Plugin', () => {
plugin.setOptions({ cssClass: 'some-class', hideRowMoveShadow: false, rowMoveShadowMarginLeft: 2, rowMoveShadowMarginTop: 5, rowMoveShadowOpacity: 1, rowMoveShadowScale: 0.9, singleRowMove: true, width: 20 });

expect(plugin.addonOptions).toEqual({
autoScroll: true,
cancelEditOnDrag: false,
columnId: '_move',
cssClass: 'some-class',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,21 +116,36 @@ describe('SlickRowSelectionModel Plugin', () => {
it('should create the plugin and initialize it', () => {
plugin.init(gridStub);

expect(plugin.addonOptions).toEqual({ selectActiveRow: true });
expect(plugin.addonOptions).toEqual({
autoScrollWhenDrag: true,
cellRangeSelector: undefined,
dragToSelect: false,
selectActiveRow: true
});
});

it('should create the plugin and initialize it with just "selectActiveRow" option and still expect the same result', () => {
plugin = new SlickRowSelectionModel({ selectActiveRow: false, });
plugin.init(gridStub);

expect(plugin.addonOptions).toEqual({ selectActiveRow: false });
expect(plugin.addonOptions).toEqual({
autoScrollWhenDrag: true,
cellRangeSelector: undefined,
dragToSelect: false,
selectActiveRow: false
});
});

it('should create the plugin and initialize it with just "selectActiveRow" option and still expect the same result', () => {
plugin = new SlickRowSelectionModel({ selectActiveRow: true });
plugin.init(gridStub);

expect(plugin.addonOptions).toEqual({ selectActiveRow: true, });
expect(plugin.addonOptions).toEqual({
autoScrollWhenDrag: true,
cellRangeSelector: undefined,
dragToSelect: false,
selectActiveRow: true,
});
});

it('should call "setSelectedRanges" when "setSelectedRows" is called', () => {
Expand Down Expand Up @@ -451,6 +466,29 @@ describe('SlickRowSelectionModel Plugin', () => {

describe('with Selector', () => {
beforeEach(() => {
plugin.addonOptions.dragToSelect = true;
});

afterEach(() => {
jest.clearAllMocks();
});

it('should call "setSelectedRanges" when "onCellRangeSelected" event is triggered', () => {
const setSelectedRangeSpy = jest.spyOn(plugin, 'setSelectedRanges');

plugin.init(gridStub);
const scrollEvent = addJQueryEventPropagation(new Event('scroll'));
plugin.getCellRangeSelector().onCellRangeSelected.notify({ range: { fromCell: 2, fromRow: 3, toCell: 4, toRow: 5 } }, scrollEvent, gridStub);

expect(setSelectedRangeSpy).toHaveBeenCalledWith([{
fromCell: 0, fromRow: 3, toCell: 2, toRow: 5,
contains: expect.toBeFunction(), isSingleCell: expect.toBeFunction(), isSingleRow: expect.toBeFunction(), toString: expect.toBeFunction()
}]);
});

it('should be able to manually create Row Selection and then call "setSelectedRanges" when "onCellRangeSelected" event is triggered', () => {
const setSelectedRangeSpy = jest.spyOn(plugin, 'setSelectedRanges');

plugin.addonOptions.cellRangeSelector = new SlickCellRangeSelector({
selectionCss: {
border: 'none'
Expand All @@ -459,12 +497,7 @@ describe('SlickRowSelectionModel Plugin', () => {
minIntervalToShowNextCell: 30,
maxIntervalToShowNextCell: 500,
accelerateInterval: 5
})
});

it('should call "setSelectedRanges" when "onCellRangeSelected" event is triggered', () => {
const setSelectedRangeSpy = jest.spyOn(plugin, 'setSelectedRanges');

});
plugin.init(gridStub);
const scrollEvent = addJQueryEventPropagation(new Event('scroll'));
plugin.getCellRangeSelector().onCellRangeSelected.notify({ range: { fromCell: 2, fromRow: 3, toCell: 4, toRow: 5 } }, scrollEvent, gridStub);
Expand Down Expand Up @@ -499,7 +532,7 @@ describe('SlickRowSelectionModel Plugin', () => {

it('should NOT call "setActiveCell" when EditorLock isActive is returning True', () => {
const setActiveCellSpy = jest.spyOn(gridStub, 'setActiveCell');
jest.spyOn(getEditorLockMock, 'isActive').mockReturnValue(true)
jest.spyOn(getEditorLockMock, 'isActive').mockReturnValue(true);
mockGridOptions.multiSelect = false;

plugin.init(gridStub);
Expand All @@ -508,5 +541,32 @@ describe('SlickRowSelectionModel Plugin', () => {

expect(setActiveCellSpy).not.toHaveBeenCalled();
});

it('should call "setActiveCell" when RowMoveManager is enabled and the column cell does NOT have any "behavior" defined', () => {
const setActiveCellSpy = jest.spyOn(gridStub, 'setActiveCell');
jest.spyOn(getEditorLockMock, 'isActive').mockReturnValue(false);
mockGridOptions.enableRowMoveManager = true;
mockGridOptions.multiSelect = false;

plugin.init(gridStub);
const scrollEvent = addJQueryEventPropagation(new Event('scroll'));
plugin.getCellRangeSelector().onBeforeCellRangeSelected.notify({ row: 2, cell: 1 }, scrollEvent, gridStub);

expect(setActiveCellSpy).toHaveBeenCalledWith(2, 1);
});

it('should NOT call "setActiveCell" when RowMoveManager is enabled and the column cell has a "behavior" defined as "selectAndMove"', () => {
const setActiveCellSpy = jest.spyOn(gridStub, 'setActiveCell');
jest.spyOn(getEditorLockMock, 'isActive').mockReturnValue(false);
mockGridOptions.enableRowMoveManager = true;
mockGridOptions.multiSelect = false;
mockColumns.unshift({ id: '_move', field: '_move', behavior: 'selectAndMove' });

plugin.init(gridStub);
const scrollEvent = addJQueryEventPropagation(new Event('scroll'));
plugin.getCellRangeSelector().onBeforeCellRangeSelected.notify({ row: 2, cell: 0 }, scrollEvent, gridStub);

expect(setActiveCellSpy).not.toHaveBeenCalled();
});
});
});
16 changes: 10 additions & 6 deletions packages/common/src/extensions/slickCellRangeSelector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,12 @@ export class SlickCellRangeSelector {
// ---------------------

protected handleDrag(e: SlickEventData, dd: DragPosition) {
if (!this._dragging) {
if (!this._dragging && !this._gridOptions.enableRowMoveManager) {
return;
}
e.stopImmediatePropagation();
if (!this._gridOptions.enableRowMoveManager) {
e.stopImmediatePropagation();
}

if (this.addonOptions.autoScroll) {
this._draggingMouseOffset = this.getMouseOffsetViewport(e, dd);
Expand Down Expand Up @@ -285,10 +287,12 @@ export class SlickCellRangeSelector {
return;
}

dd.range.end = end;
const range = new Slick.Range(dd.range.start.row, dd.range.start.cell, end.row, end.cell);
this._decorator.show(range);
this.onCellRangeSelecting.notify({ range });
if (dd?.range) {
dd.range.end = end;
const range = new Slick.Range(dd.range.start.row, dd.range.start.cell, end.row, end.cell);
this._decorator.show(range);
this.onCellRangeSelecting.notify({ range });
}
}
}

Expand Down
5 changes: 3 additions & 2 deletions packages/common/src/extensions/slickRowMoveManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export class SlickRowMoveManager {
protected _handler = new Slick.EventHandler();
protected _usabilityOverride?: UsabilityOverrideFn;
protected _defaults = {
autoScroll: true,
columnId: '_move',
cssClass: 'slick-row-move-column',
cancelEditOnDrag: false,
Expand Down Expand Up @@ -77,7 +78,7 @@ export class SlickRowMoveManager {
this._canvas = this._grid.getCanvasNode();

// user could override the expandable icon logic from within the options or after instantiating the plugin
if (this._addonOptions && typeof this._addonOptions.usabilityOverride === 'function') {
if (typeof this._addonOptions?.usabilityOverride === 'function') {
this.usabilityOverride(this._addonOptions.usabilityOverride);
}

Expand All @@ -100,7 +101,7 @@ export class SlickRowMoveManager {
this._addonOptions = { ...this._defaults, ...gridOptions.rowMoveManager } as RowMoveManagerOption;
if (Array.isArray(columnDefinitions) && gridOptions) {
const newRowMoveColumn: Column = this.getColumnDefinition();
const rowMoveColDef = Array.isArray(columnDefinitions) && columnDefinitions.find((col: Column) => col && col.behavior === 'selectAndMove');
const rowMoveColDef = Array.isArray(columnDefinitions) && columnDefinitions.find((col: Column) => col?.behavior === 'selectAndMove');
const finalRowMoveColumn = rowMoveColDef ? rowMoveColDef : newRowMoveColumn;

// column index position in the grid
Expand Down
30 changes: 25 additions & 5 deletions packages/common/src/extensions/slickRowSelectionModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ export class SlickRowSelectionModel {
protected _ranges: CellRange[] = [];
protected _selector?: SlickCellRangeSelector;
protected _defaults = {
autoScrollWhenDrag: true,
cellRangeSelector: undefined,
dragToSelect: false,
selectActiveRow: true
} as RowSelectionModelOption;
pluginName = 'RowSelectionModel';
Expand Down Expand Up @@ -51,17 +53,24 @@ export class SlickRowSelectionModel {
this._addonOptions = { ...this._defaults, ...this._addonOptions };
this._selector = this.addonOptions.cellRangeSelector;

if (!this._selector && this._addonOptions.dragToSelect) {
this._selector = new SlickCellRangeSelector({
selectionCss: { border: 'none' } as CSSStyleDeclaration,
autoScroll: this._addonOptions.autoScrollWhenDrag
});
this.addonOptions.cellRangeSelector = this._selector;
}

this._eventHandler
.subscribe(this._grid.onActiveCellChanged, this.handleActiveCellChange.bind(this))
.subscribe(this._grid.onClick, this.handleClick.bind(this))
.subscribe(this._grid.onKeyDown, this.handleKeyDown.bind(this));

if (this._selector) {
this._grid.registerPlugin(this._selector);
this._eventHandler
.subscribe(this._selector.onCellRangeSelecting, this.handleCellRangeSelected.bind(this) as EventListener)
.subscribe(this._selector.onCellRangeSelected, this.handleCellRangeSelected.bind(this) as EventListener)
.subscribe(this._selector.onBeforeCellRangeSelected, this.handleBeforeCellRangeSelected.bind(this) as EventListener);
this._selector.onCellRangeSelecting.subscribe(this.handleCellRangeSelected.bind(this) as EventListener);
this._selector.onCellRangeSelected.subscribe(this.handleCellRangeSelected.bind(this) as EventListener);
this._selector.onBeforeCellRangeSelected.subscribe(this.handleBeforeCellRangeSelected.bind(this) as EventListener);
}
}

Expand Down Expand Up @@ -129,7 +138,11 @@ export class SlickRowSelectionModel {
}

protected handleBeforeCellRangeSelected(e: SlickEventData, cell: { row: number; cell: number; }): boolean | void {
if (this._grid.getEditorLock().isActive()) {
let isRowMoveColumn = false;
if (this.gridOptions.enableRowMoveManager) {
isRowMoveColumn = this.isHandlerColumn(cell.cell) ?? false;
}
if (this._grid.getEditorLock().isActive() || isRowMoveColumn) {
e.stopPropagation();
return false;
}
Expand Down Expand Up @@ -224,6 +237,13 @@ export class SlickRowSelectionModel {
}
}

/** is the column a column Row Move OR Select Row Move */
isHandlerColumn(columnIndex: number): boolean {
const columns = this._grid.getColumns();
const col = columns[columnIndex].behavior || '';
return /move|selectAndMove/.test(col);
}

protected rangesToRows(ranges: CellRange[]) {
const rows = [];
for (let i = 0; i < ranges.length; i++) {
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/interfaces/column.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface Column<T = any> {
/** async background post-rendering formatter */
asyncPostRender?: (domCellNode: any, row: number, dataContext: T, columnDef: Column) => void;

/** Row Move Behavior, used by the Row Move Manager Plugin */
/** optional Behavior of a column with action, for example it's used by the Row Move Manager Plugin */
behavior?: string;

/** Block event triggering of an insert? */
Expand Down
18 changes: 17 additions & 1 deletion packages/common/src/interfaces/rowMoveManagerOption.interface.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import { UsabilityOverrideFn } from '../enums/usabilityOverrideFn.type';
import { SlickCellRangeSelector } from '../extensions/slickCellRangeSelector';

export interface RowMoveManagerOption {
/**
* Defaults to True, do we want to disable auto-scroll feature (which comes from CellRangeSelector).
* NOTE: this flag has no effect when a `cellRangeSelector` is provided, you could however turn `autoScroll: false` inside the `cellRangeSelector`
*/
autoScrollWhenDrag?: boolean;

/** Defaults to false, option to cancel editing while dragging a row */
cancelEditOnDrag?: boolean;

/**
* Optional Cell Range Selector.
* NOTE: for an even simpler approach, we could use `enableCellRangeSelector` which the lib will take care of creating the instance by itself.
*/
cellRangeSelector?: SlickCellRangeSelector;

/** A CSS class to be added to the menu item container. */
cssClass?: string;

Expand All @@ -17,9 +30,12 @@ export interface RowMoveManagerOption {
*/
columnIndexPosition?: number;

/** Defaults to False, do we want to disable the row selection? */
/** Defaults to False, do we want to disable the row selection? */
disableRowSelection?: boolean;

/** Defaults to False, should we select when dragging? */
dragToSelect?: boolean;

/** Defaults to True, do we want to hide the row move shadow of what we're dragging? */
hideRowMoveShadow?: boolean;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { SlickCellRangeSelector } from '../extensions/slickCellRangeSelector';

export type RowSelectionModelOption = {
/** Defaults to True, should we auto-scroll when dragging a row */
autoScrollWhenDrag?: boolean;

/** Defaults to False, should we select when dragging? */
dragToSelect?: boolean;

/** cell range selector */
cellRangeSelector?: SlickCellRangeSelector;

Expand Down
16 changes: 8 additions & 8 deletions packages/common/src/services/extension.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,17 +170,21 @@ export class ExtensionService {

// Row Selection Plugin
// this extension should be registered BEFORE the CheckboxSelector, RowDetail or RowMoveManager since it can be use by these 2 plugins
if (!this._rowSelectionModel && (this.gridOptions.enableRowSelection || this.gridOptions.enableCheckboxSelector || this.sharedService.gridOptions.enableRowDetailView || this.sharedService.gridOptions.enableRowMoveManager)) {
if (!this._rowSelectionModel && (this.gridOptions.enableRowSelection || this.gridOptions.enableCheckboxSelector || this.gridOptions.enableRowDetailView || this.gridOptions.enableRowMoveManager)) {
if (!this._rowSelectionModel || !this.sharedService.slickGrid.getSelectionModel()) {
this._rowSelectionModel = new SlickRowSelectionModel(this.sharedService.gridOptions.rowSelectionOptions);
const rowSelectionOptions = this.gridOptions.rowSelectionOptions ?? {};
if (this.gridOptions.enableRowMoveManager && this.gridOptions.rowMoveManager?.dragToSelect !== false) {
rowSelectionOptions.dragToSelect = true;
}
this._rowSelectionModel = new SlickRowSelectionModel(rowSelectionOptions);
this.sharedService.slickGrid.setSelectionModel(this._rowSelectionModel);
}
this._extensionList[ExtensionName.rowSelection] = { name: ExtensionName.rowSelection, instance: this._rowSelectionModel };
}

// Checkbox Selector Plugin
if (this.gridOptions.enableCheckboxSelector) {
this._checkboxSelectColumn = this._checkboxSelectColumn || new SlickCheckboxSelectColumn(this.sharedService.gridOptions.checkboxSelector);
this._checkboxSelectColumn = this._checkboxSelectColumn || new SlickCheckboxSelectColumn(this.gridOptions.checkboxSelector);
this._checkboxSelectColumn.init(this.sharedService.slickGrid);
const createdExtension = this.getCreatedExtensionByName(ExtensionName.checkboxSelector); // get the instance from when it was really created earlier
const instance = createdExtension && createdExtension.instance;
Expand Down Expand Up @@ -257,11 +261,7 @@ export class ExtensionService {
// Row Move Manager Plugin
if (this.gridOptions.enableRowMoveManager) {
this._rowMoveManagerPlugin = this._rowMoveManagerPlugin || new SlickRowMoveManager();
this._rowMoveManagerPlugin.init(this.sharedService.slickGrid, this.sharedService.gridOptions.rowMoveManager);
if (!this._rowSelectionModel || !this.sharedService.slickGrid.getSelectionModel()) {
this._rowSelectionModel = new SlickRowSelectionModel(this.sharedService.gridOptions.rowSelectionOptions);
this.sharedService.slickGrid.setSelectionModel(this._rowSelectionModel);
}
this._rowMoveManagerPlugin.init(this.sharedService.slickGrid, this.gridOptions.rowMoveManager);
const createdExtension = this.getCreatedExtensionByName(ExtensionName.rowMoveManager); // get the instance from when it was really created earlier
const instance = createdExtension?.instance;
if (instance) {
Expand Down
6 changes: 3 additions & 3 deletions test/cypress/integration/example17.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// <reference types='cypress' />
import { getScrollDistanceWhenDragOutsideGrid } from '../support/drag';

describe('Example 17 - Auto-Scroll with Range Selector', { retries: 0 }, () => {
describe('Example 17 - Auto-Scroll with Range Selector', { retries: 1 }, () => {
// NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after Unfreeze Columns/Rows
const CELL_WIDTH = 80;
const CELL_HEIGHT = 35;
Expand Down Expand Up @@ -181,8 +181,8 @@ describe('Example 17 - Auto-Scroll with Range Selector', { retries: 0 }, () => {
testInterval(SCROLLBAR_DIMENSION).then(newInterval => {

// scrolling speed is quicker than before
expect(3.2 * newInterval.cell).to.be.lessThan(defaultInterval.cell);
expect(3.2 * newInterval.row).to.be.lessThan(defaultInterval.row);
expect(3.0 * newInterval.cell).to.be.lessThan(defaultInterval.cell);
expect(3.0 * newInterval.row).to.be.lessThan(defaultInterval.row);

cy.get('[data-test="default-options-btn"]').click();
cy.get('[data-test="delay-cursor-input"]').should('have.value', '5');
Expand Down

0 comments on commit 1c14a4f

Please sign in to comment.