diff --git a/.circleci/config.yml b/.circleci/config.yml
index 283da8b1d..08d22847d 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -5,7 +5,7 @@ jobs:
build:
working_directory: ~/angular-slickgrid
docker:
- - image: circleci/node:10-browsers
+ - image: circleci/node:12-browsers
steps:
- checkout
- restore_cache:
diff --git a/package.json b/package.json
index 83c8d1e63..2d47ea33a 100644
--- a/package.json
+++ b/package.json
@@ -109,7 +109,7 @@
"lodash.isequal": "^4.5.0",
"moment-mini": "^2.24.0",
"rxjs": "^6.3.3",
- "slickgrid": "^2.4.27",
+ "slickgrid": "^2.4.29",
"text-encoding-utf-8": "^1.0.2"
},
"peerDependencies": {
diff --git a/src/app/examples/grid-rowmove.component.html b/src/app/examples/grid-rowmove.component.html
index ed5df7d9e..99310fcd0 100644
--- a/src/app/examples/grid-rowmove.component.html
+++ b/src/app/examples/grid-rowmove.component.html
@@ -2,9 +2,34 @@
{{title}}
+
+
+
+
+
+
+
+
+
+
+ [columnDefinitions]="columnDefinitions" [gridOptions]="gridOptions" [dataset]="dataset">
diff --git a/src/app/examples/grid-rowmove.component.ts b/src/app/examples/grid-rowmove.component.ts
index 0cafac2ee..b079174af 100644
--- a/src/app/examples/grid-rowmove.component.ts
+++ b/src/app/examples/grid-rowmove.component.ts
@@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
-import { AngularGridInstance, Column, ExtensionName, Formatters, GridOption } from './../modules/angular-slickgrid';
+import { AngularGridInstance, Column, ExtensionName, Filters, Formatters, GridOption } from './../modules/angular-slickgrid';
import { TranslateService } from '@ngx-translate/core';
@Component({
@@ -43,12 +43,27 @@ export class GridRowMoveComponent implements OnInit {
ngOnInit(): void {
this.columnDefinitions = [
- { id: 'title', name: 'Title', field: 'title' },
- { id: 'duration', name: 'Duration', field: 'duration', sortable: true },
- { id: '%', name: '% Complete', field: 'percentComplete', sortable: true },
- { id: 'start', name: 'Start', field: 'start' },
- { id: 'finish', name: 'Finish', field: 'finish' },
- { id: 'effort-driven', name: 'Completed', field: 'effortDriven', formatter: Formatters.checkmark }
+ { id: 'title', name: 'Title', field: 'title', filterable: true, },
+ { id: 'duration', name: 'Duration', field: 'duration', filterable: true, sortable: true },
+ { id: '%', name: '% Complete', field: 'percentComplete', filterable: true, sortable: true },
+ {
+ id: 'start', name: 'Start', field: 'start', filterable: true, sortable: true,
+ filter: { model: Filters.compoundDate },
+ },
+ {
+ id: 'finish', name: 'Finish', field: 'finish',
+ filterable: true, sortable: true,
+ filter: { model: Filters.compoundDate },
+ },
+ {
+ id: 'effort-driven', name: 'Completed', field: 'effortDriven',
+ formatter: Formatters.checkmark,
+ filterable: true, sortable: true,
+ filter: {
+ collection: [{ value: '', label: '' }, { value: true, label: 'True' }, { value: false, label: 'False' }],
+ model: Filters.singleSelect
+ },
+ }
];
this.gridOptions = {
@@ -58,7 +73,13 @@ export class GridRowMoveComponent implements OnInit {
sidePadding: 10
},
enableCellNavigation: true,
+ enableFiltering: true,
enableCheckboxSelector: true,
+ checkboxSelector: {
+ // you can toggle these 2 properties to show the "select all" checkbox in different location
+ hideInFilterHeaderRow: false,
+ hideInColumnTitleRow: true
+ },
enableRowSelection: true,
rowSelectionOptions: {
// True (Single Selection), False (Multiple Selections)
@@ -73,6 +94,7 @@ export class GridRowMoveComponent implements OnInit {
singleRowMove: true,
disableRowSelection: true,
cancelEditOnDrag: true,
+ width: 30,
onBeforeMoveRows: (e, args) => this.onBeforeMoveRow(e, args),
onMoveRows: (e, args) => this.onMoveRows(e, args),
@@ -147,4 +169,33 @@ export class GridRowMoveComponent implements OnInit {
this.angularGrid.slickGrid.resetActiveCell();
this.dataset = tmpDataset;
}
+
+ hideDurationColumnDynamically() {
+ const columnIndex = this.angularGrid.slickGrid.getColumns().findIndex(col => col.id === 'duration');
+ if (columnIndex >= 0) {
+ this.angularGrid.gridService.hideColumnByIndex(columnIndex);
+ }
+ }
+
+ // Disable/Enable Filtering/Sorting functionalities
+ // --------------------------------------------------
+
+ disableFilters() {
+ this.angularGrid.filterService.disableFilterFunctionality(true);
+ }
+
+ disableSorting() {
+ this.angularGrid.sortService.disableSortFunctionality(true);
+ }
+
+ // or Toggle Filtering/Sorting functionalities
+ // ---------------------------------------------
+
+ toggleFilter() {
+ this.angularGrid.filterService.toggleFilterFunctionality();
+ }
+
+ toggleSorting() {
+ this.angularGrid.sortService.toggleSortFunctionality();
+ }
}
diff --git a/src/app/modules/angular-slickgrid/extensions/__tests__/extensionUtility.spec.ts b/src/app/modules/angular-slickgrid/extensions/__tests__/extensionUtility.spec.ts
index cd5661637..85af318f7 100644
--- a/src/app/modules/angular-slickgrid/extensions/__tests__/extensionUtility.spec.ts
+++ b/src/app/modules/angular-slickgrid/extensions/__tests__/extensionUtility.spec.ts
@@ -5,7 +5,7 @@ import { ExtensionUtility } from '../extensionUtility';
import { ExtensionName, GridOption } from '../../models';
import { SharedService } from '../../services/shared.service';
-declare const Slick: any;
+declare let Slick: any;
const mockAddon = jest.fn().mockImplementation(() => ({
init: jest.fn(),
@@ -88,16 +88,6 @@ describe('ExtensionUtility', () => {
translate.use('fr');
});
- describe('arrayRemoveItemByIndex method', () => {
- it('should remove an item from the array', () => {
- const input = [{ field: 'field1', name: 'Field 1' }, { field: 'field2', name: 'Field 2' }, { field: 'field3', name: 'Field 3' }];
- const expected = [{ field: 'field1', name: 'Field 1' }, { field: 'field3', name: 'Field 3' }];
-
- const output = utility.arrayRemoveItemByIndex(input, 1);
- expect(output).toEqual(expected);
- });
- });
-
describe('loadExtensionDynamically method', () => {
it('should check that autoTooltip gets loaded', () => {
utility.loadExtensionDynamically(ExtensionName.autoTooltip);
diff --git a/src/app/modules/angular-slickgrid/extensions/extensionUtility.ts b/src/app/modules/angular-slickgrid/extensions/extensionUtility.ts
index 962ac9b85..d2fda1fa0 100644
--- a/src/app/modules/angular-slickgrid/extensions/extensionUtility.ts
+++ b/src/app/modules/angular-slickgrid/extensions/extensionUtility.ts
@@ -12,15 +12,6 @@ declare function require(name: string);
export class ExtensionUtility {
constructor(private sharedService: SharedService, @Optional() private translate: TranslateService) { }
- /**
- * Remove a column from the grid by it's index in the grid
- * @param array input
- * @param index
- */
- arrayRemoveItemByIndex(array: T[], index: number): T[] {
- return array.filter((el: T, i: number) => index !== i);
- }
-
/**
* Load SlickGrid Extension (Control/Plugin) dynamically (on demand)
* This will basically only load the extension when user enables the feature
diff --git a/src/app/modules/angular-slickgrid/extensions/headerMenuExtension.ts b/src/app/modules/angular-slickgrid/extensions/headerMenuExtension.ts
index 73997592a..6167eeab4 100644
--- a/src/app/modules/angular-slickgrid/extensions/headerMenuExtension.ts
+++ b/src/app/modules/angular-slickgrid/extensions/headerMenuExtension.ts
@@ -18,7 +18,7 @@ import {
import { FilterService } from '../services/filter.service';
import { SortService } from '../services/sort.service';
import { SharedService } from '../services/shared.service';
-import { getTranslationPrefix } from '../services/utilities';
+import { arrayRemoveItemByIndex, getTranslationPrefix } from '../services/utilities';
import { ExtensionUtility } from './extensionUtility';
// using external non-typed js libraries
@@ -219,7 +219,7 @@ export class HeaderMenuExtension implements Extension {
if (this.sharedService.grid && this.sharedService.grid.getColumns && this.sharedService.grid.setColumns && this.sharedService.grid.getColumnIndex) {
const columnIndex = this.sharedService.grid.getColumnIndex(column.id);
const currentColumns = this.sharedService.grid.getColumns() as Column[];
- const visibleColumns = this.extensionUtility.arrayRemoveItemByIndex(currentColumns, columnIndex);
+ const visibleColumns = arrayRemoveItemByIndex(currentColumns, columnIndex);
this.sharedService.visibleColumns = visibleColumns;
this.sharedService.grid.setColumns(visibleColumns);
this.sharedService.onColumnsChanged.next(visibleColumns);
diff --git a/src/app/modules/angular-slickgrid/models/gridMenuItem.interface.ts b/src/app/modules/angular-slickgrid/models/gridMenuItem.interface.ts
index 2a656c1d6..1551b419c 100644
--- a/src/app/modules/angular-slickgrid/models/gridMenuItem.interface.ts
+++ b/src/app/modules/angular-slickgrid/models/gridMenuItem.interface.ts
@@ -7,12 +7,15 @@ export interface GridMenuItem {
/** A CSS class to be added to the menu item container. */
cssClass?: string;
- /** Defaults to false, whether the item is disabled. */
+ /** Defaults to false, whether the item/command is disabled. */
disabled?: boolean;
/** Defaults to false, whether the command is actually a divider (separator). */
divider?: boolean;
+ /** Defaults to false, whether the item/command is hidden. */
+ hidden?: boolean;
+
/** CSS class to be added to the menu item icon. */
iconCssClass?: string;
diff --git a/src/app/modules/angular-slickgrid/models/headerMenu.interface.ts b/src/app/modules/angular-slickgrid/models/headerMenu.interface.ts
index e8663b9b2..aa360e1d9 100644
--- a/src/app/modules/angular-slickgrid/models/headerMenu.interface.ts
+++ b/src/app/modules/angular-slickgrid/models/headerMenu.interface.ts
@@ -17,9 +17,6 @@ export interface HeaderMenu {
/** A command identifier to be passed to the onCommand event handlers. */
command?: string;
- /** Whether the item is disabled. */
- disabled?: boolean;
-
/** Defaults to false, which will hide the "Remove Filter" command in the Header Menu (Grid Option "enableHeaderMenu: true" has to be enabled) */
hideClearFilterCommand?: boolean;
diff --git a/src/app/modules/angular-slickgrid/models/menuItem.interface.ts b/src/app/modules/angular-slickgrid/models/menuItem.interface.ts
index 58ee84ba8..7f5660e2c 100644
--- a/src/app/modules/angular-slickgrid/models/menuItem.interface.ts
+++ b/src/app/modules/angular-slickgrid/models/menuItem.interface.ts
@@ -4,12 +4,15 @@ export interface MenuItem {
/** A CSS class to be added to the menu item container. */
cssClass?: string;
- /** Defaults to false, whether the item is disabled. */
+ /** Defaults to false, whether the item/command is disabled. */
disabled?: boolean;
/** Defaults to false, whether the command is actually a divider (separator). */
divider?: boolean | string;
+ /** Defaults to false, whether the item/command is hidden. */
+ hidden?: boolean;
+
/** CSS class to be added to the menu item icon. */
iconCssClass?: string;
diff --git a/src/app/modules/angular-slickgrid/models/slickEditorLock.interface.ts b/src/app/modules/angular-slickgrid/models/slickEditorLock.interface.ts
new file mode 100644
index 000000000..7824ebaa0
--- /dev/null
+++ b/src/app/modules/angular-slickgrid/models/slickEditorLock.interface.ts
@@ -0,0 +1,56 @@
+import { Editor } from './editor.interface';
+
+/**
+ * A locking helper to track the active edit controller and ensure that only a single controller
+ * can be active at a time. This prevents a whole class of state and validation synchronization
+ * issues. An edit controller (such as SlickGrid) can query if an active edit is in progress
+ * and attempt a commit or cancel before proceeding.
+ * @class EditorLock
+ * @constructor
+ */
+export interface SlickEditorLock {
+
+ /**
+ * Returns true if a specified edit controller is active (has the edit lock).
+ * If the parameter is not specified, returns true if any edit controller is active.
+ * @method isActive
+ * @param editController {EditController}
+ * @return {Boolean}
+ */
+ isActive(editController?: Editor): boolean;
+
+ /**
+ * Sets the specified edit controller as the active edit controller (acquire edit lock).
+ * If another edit controller is already active, and exception will be thrown.
+ * @method activate
+ * @param editController {EditController} edit controller acquiring the lock
+ */
+ activate(editController: Editor): void;
+
+ /**
+ * Unsets the specified edit controller as the active edit controller (release edit lock).
+ * If the specified edit controller is not the active one, an exception will be thrown.
+ * @method deactivate
+ * @param editController {EditController} edit controller releasing the lock
+ */
+ deactivate(editController: Editor): void;
+
+ /**
+ * Attempts to commit the current edit by calling "commitCurrentEdit" method on the active edit
+ * controller and returns whether the commit attempt was successful (commit may fail due to validation
+ * errors, etc.). Edit controller's "commitCurrentEdit" must return true if the commit has succeeded
+ * and false otherwise. If no edit controller is active, returns true.
+ * @method commitCurrentEdit
+ * @return {Boolean}
+ */
+ commitCurrentEdit(): boolean;
+
+ /**
+ * Attempts to cancel the current edit by calling "cancelCurrentEdit" method on the active edit
+ * controller and returns whether the edit was successfully cancelled. If no edit controller is
+ * active, returns true.
+ * @method cancelCurrentEdit
+ * @return {Boolean}
+ */
+ cancelCurrentEdit(): boolean;
+}
diff --git a/src/app/modules/angular-slickgrid/models/slickGrid.interface.ts b/src/app/modules/angular-slickgrid/models/slickGrid.interface.ts
index 1f6053053..2e384c679 100644
--- a/src/app/modules/angular-slickgrid/models/slickGrid.interface.ts
+++ b/src/app/modules/angular-slickgrid/models/slickGrid.interface.ts
@@ -5,6 +5,8 @@ import { Editor } from './editor.interface';
import { ElementPosition } from './elementPosition.interface';
import { FormatterResultObject } from './formatterResultObject.interface';
import { PagingInfo } from './pagingInfo.interface';
+import { SlickDataView } from './slickDataView.interface';
+import { SlickEditorLock } from './slickEditorLock.interface';
import { SlickEvent } from './slickEvent.interface';
export interface SlickGrid {
@@ -53,9 +55,11 @@ export interface SlickGrid {
/**
* Attempts to switch the active cell into edit mode. Will throw an error if the cell is set to be not editable. Uses the specified editor, otherwise defaults to any default editor for that given cell.
- * @param editor A SlickGrid editor (see examples in slick.editors.js).
+ * @param {object} editor A SlickGrid editor (see examples in slick.editors.js).
+ * @param {boolean} preClickModeOn Pre-Click Mode is Enabled?
+ * @param {object} event
*/
- editActiveCell(editor: Editor): void;
+ editActiveCell(editor: Editor, preClickModeOn?: boolean, event?: Event): void;
/**
* Flashes the cell twice by toggling the CSS class 4 times.
@@ -141,29 +145,35 @@ export interface SlickGrid {
*/
getColumnIndex(id: string | number): number;
- /** Returns an array of column definitions, containing the option settings for each individual column.*/
+ /** Returns an array of column definitions, containing the option settings for each individual column. */
getColumns(): Column[];
/** Get Grid Canvas Node DOM Element */
getContainerNode(): HTMLElement;
/** Returns an array of every data object, unless you're using DataView in which case it returns a DataView object. */
- getData(): any;
+ getData(): T;
/**
* Returns the databinding item at a given position.
- * @param index Item index.
+ * @param index Item row index.
*/
- getDataItem(index: number): T;
+ getDataItem(rowIndex: number): T;
/** Returns the size of the databinding source. */
getDataLength(): number;
/** Get Editor lock */
- getEditorLock(): any;
+ getEditorLock(): SlickEditorLock;
/** Get Editor Controller */
- getEditController(): { commitCurrentEdit(): boolean; cancelCurrentEdit(): boolean; };
+ getEditController(): {
+ /** Commit Current Editor command */
+ commitCurrentEdit(): boolean;
+
+ /** Cancel Current Editor command */
+ cancelCurrentEdit(): boolean;
+ };
/** Get the Footer DOM element */
getFooterRow(): HTMLElement;
@@ -335,10 +345,21 @@ export interface SlickGrid {
/**
* Sets an active cell.
- * @param row A row index.
- * @param cell A column index.
+ * @param {number} row - A row index.
+ * @param {number} cell - A column index.
+ * @param {boolean} optionEditMode Option Edit Mode is Auto-Edit?
+ * @param {boolean} preClickModeOn Pre-Click Mode is Enabled?
+ * @param {boolean} suppressActiveCellChangedEvent Are we suppressing Active Cell Changed Event (defaults to false)
+ */
+ setActiveCell(row: number, cell: number, optionEditMode?: boolean, preClickModeOn?: boolean, suppressActiveCellChangedEvent?: boolean): void;
+
+ /**
+ * Sets an active cell.
+ * @param {number} row - A row index.
+ * @param {number} cell - A column index.
+ * @param {boolean} suppressScrollIntoView - optionally suppress the ScrollIntoView that happens by default (defaults to false)
*/
- setActiveCell(row: number, cell: number): void;
+ setActiveRow(row: number, cell?: number, suppressScrollIntoView?: boolean): void;
/** Sets an active viewport node */
setActiveViewportNode(element: HTMLElement): void;
@@ -377,9 +398,11 @@ export interface SlickGrid {
/**
* Extends grid options with a given hash. If an there is an active edit, the grid will attempt to commit the changes and only continue if the attempt succeeds.
- * @options An object with configuration options.
+ * @params options An object with configuration options.
+ * @params do we want to supress the grid re-rendering? (defaults to false)
+ * @params do we want to supress the columns set, via "setColumns()" method? (defaults to false)
*/
- setOptions(options: GridOption): void;
+ setOptions(options: GridOption, suppressRender?: boolean, suppressColumnSet?: boolean): void;
/** Set the Pre-Header Visibility and optionally enable/disable animation (enabled by default) */
setPreHeaderPanelVisibility(visible: boolean, animate?: boolean): void;
@@ -394,7 +417,7 @@ export interface SlickGrid {
* Unregisters a current selection model and registers a new one. See the definition of SelectionModel for more information.
* @selectionModel A SelectionModel.
*/
- setSelectionModel(selectionModel: any): void; // todo: don't know the type of the event data type
+ setSelectionModel(selectionModel: any): void;
/**
* Accepts a columnId string and an ascending boolean. Applies a sort glyph in either ascending or descending form to the header of the column. Note that this does not actually sort the column. It only adds the sort glyph to the header.
diff --git a/src/app/modules/angular-slickgrid/services/__tests__/filter.service.spec.ts b/src/app/modules/angular-slickgrid/services/__tests__/filter.service.spec.ts
index 1aaa4d229..3f68d871b 100644
--- a/src/app/modules/angular-slickgrid/services/__tests__/filter.service.spec.ts
+++ b/src/app/modules/angular-slickgrid/services/__tests__/filter.service.spec.ts
@@ -11,7 +11,6 @@ import {
CurrentFilter,
GridOption,
FieldType,
- FilterChangedArgs,
SlickEventHandler,
} from '../../models';
import { Filters } from '../../filters';
@@ -72,6 +71,9 @@ const gridStub = {
onSort: new Slick.Event(),
onHeaderRowCellRendered: new Slick.Event(),
render: jest.fn(),
+ setColumns: jest.fn(),
+ setOptions: jest.fn(),
+ setHeaderRowVisibility: jest.fn(),
setSortColumns: jest.fn(),
};
@@ -1116,6 +1118,113 @@ describe('FilterService', () => {
expect(clearSpy).toHaveBeenCalledWith(false);
});
});
+ describe('disableFilterFunctionality method', () => {
+ beforeEach(() => {
+ gridOptionMock.enableFiltering = true;
+ gridOptionMock.showHeaderRow = true;
+ });
+
+ it('should disable the Filter Functionality from the Grid Options & toggle the Header Filter row', () => {
+ const mockColumns = [{ id: 'field1', field: 'field1', width: 100 }, { id: 'field2', field: 'field2', width: 100 }];
+ const setOptionSpy = jest.spyOn(gridStub, 'setOptions');
+ const setHeaderSpy = jest.spyOn(gridStub, 'setHeaderRowVisibility');
+ const setColsSpy = jest.spyOn(gridStub, 'setColumns');
+ jest.spyOn(SharedService.prototype, 'columnDefinitions', 'get').mockReturnValue(mockColumns)
+
+ service.init(gridStub);
+ service.disableFilterFunctionality();
+
+ expect(setOptionSpy).toHaveBeenCalledWith({ enableFiltering: false }, false, true);
+ expect(setHeaderSpy).toHaveBeenCalledWith(false);
+ expect(setColsSpy).toHaveBeenCalledWith(mockColumns);
+ });
+
+ it('should enable the Filter Functionality when passing 1st argument as False', () => {
+ gridOptionMock.enableFiltering = false;
+ gridOptionMock.showHeaderRow = false;
+
+ const mockColumns = [{ id: 'field1', field: 'field1', width: 100 }, { id: 'field2', field: 'field2', width: 100 }];
+ const setOptionSpy = jest.spyOn(gridStub, 'setOptions');
+ const setHeaderSpy = jest.spyOn(gridStub, 'setHeaderRowVisibility');
+ const setColsSpy = jest.spyOn(gridStub, 'setColumns');
+ jest.spyOn(SharedService.prototype, 'columnDefinitions', 'get').mockReturnValue(mockColumns)
+
+ service.init(gridStub);
+ service.disableFilterFunctionality(false);
+
+ expect(setOptionSpy).toHaveBeenCalledWith({ enableFiltering: true }, false, true);
+ expect(setHeaderSpy).toHaveBeenCalledWith(true);
+ expect(setColsSpy).toHaveBeenCalledWith(mockColumns);
+ });
+
+ it('should NOT change neither call anything if the end result of disabling is the same', () => {
+ gridOptionMock.enableFiltering = true;
+ gridOptionMock.showHeaderRow = true;
+
+ const mockColumns = [{ id: 'field1', field: 'field1', width: 100 }, { id: 'field2', field: 'field2', width: 100 }];
+ const setOptionSpy = jest.spyOn(gridStub, 'setOptions');
+ const setHeaderSpy = jest.spyOn(gridStub, 'setHeaderRowVisibility');
+ const setColsSpy = jest.spyOn(gridStub, 'setColumns');
+ jest.spyOn(SharedService.prototype, 'columnDefinitions', 'get').mockReturnValue(mockColumns)
+
+ service.init(gridStub);
+ service.disableFilterFunctionality(false);
+
+ expect(setOptionSpy).not.toHaveBeenCalled();
+ expect(setHeaderSpy).not.toHaveBeenCalled();
+ expect(setColsSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('toggleFilterFunctionality method', () => {
+ beforeEach(() => {
+ gridOptionMock.enableFiltering = true;
+ gridOptionMock.showHeaderRow = true;
+ });
+
+ it('should toggle the Filter Functionality from the Grid Options & toggle the Header Filter row', () => {
+ const mockColumns = [{ id: 'field1', field: 'field1', width: 100 }, { id: 'field2', field: 'field2', width: 100 }];
+ const setOptionSpy = jest.spyOn(gridStub, 'setOptions');
+ const setHeaderSpy = jest.spyOn(gridStub, 'setHeaderRowVisibility');
+ const setColsSpy = jest.spyOn(gridStub, 'setColumns');
+ jest.spyOn(SharedService.prototype, 'columnDefinitions', 'get').mockReturnValue(mockColumns)
+
+ service.init(gridStub);
+ service.toggleFilterFunctionality();
+
+ expect(setOptionSpy).toHaveBeenCalledWith({ enableFiltering: false }, false, true);
+ expect(setHeaderSpy).toHaveBeenCalledWith(false);
+ expect(setColsSpy).toHaveBeenCalledWith(mockColumns);
+ });
+ });
+
+ describe('toggleHeaderFilterRow method', () => {
+ beforeEach(() => {
+ gridOptionMock.enableFiltering = true;
+ gridOptionMock.showHeaderRow = true;
+ });
+
+ it('should toggle the Header Filter Row (to disabled when previously enabled)', () => {
+ const setHeaderSpy = jest.spyOn(gridStub, 'setHeaderRowVisibility');
+
+ service.init(gridStub);
+ service.toggleHeaderFilterRow();
+
+ expect(setHeaderSpy).toHaveBeenCalledWith(false);
+ });
+
+ it('should toggle the Header Filter Row and expect to call "setColumns" when changing to enabled when previously disabled', () => {
+ gridOptionMock.showHeaderRow = false;
+ const setHeaderSpy = jest.spyOn(gridStub, 'setHeaderRowVisibility');
+ const setColsSpy = jest.spyOn(gridStub, 'setColumns');
+
+ service.init(gridStub);
+ service.toggleHeaderFilterRow();
+
+ expect(setHeaderSpy).toHaveBeenCalledWith(true);
+ expect(setColsSpy).toHaveBeenCalled();
+ });
+ });
describe('setSortColumnIcons method', () => {
it('should set the sorting icon by calling "setSortColumns" on the grid object', () => {
diff --git a/src/app/modules/angular-slickgrid/services/__tests__/grid.service.spec.ts b/src/app/modules/angular-slickgrid/services/__tests__/grid.service.spec.ts
index 3a5e73bb1..318acebde 100644
--- a/src/app/modules/angular-slickgrid/services/__tests__/grid.service.spec.ts
+++ b/src/app/modules/angular-slickgrid/services/__tests__/grid.service.spec.ts
@@ -50,6 +50,7 @@ const gridStub = {
getDataItem: jest.fn(),
getOptions: jest.fn(),
getColumns: jest.fn(),
+ getColumnIndex: jest.fn(),
getSelectionModel: jest.fn(),
setSelectionModel: jest.fn(),
getSelectedRows: jest.fn(),
@@ -1198,6 +1199,92 @@ describe('Grid Service', () => {
});
});
+ describe('hideColumn method', () => {
+ it('should call hideColumnByIndex with the column index found', () => {
+ jest.spyOn(gridStub, 'getColumnIndex').mockReturnValue(2);
+ const hideColumnIdxSpy = jest.spyOn(service, 'hideColumnByIndex');
+
+ service.hideColumn({ id: 'field3', field: 'field3' });
+
+ expect(hideColumnIdxSpy).toHaveBeenCalledWith(2);
+ });
+ });
+
+ describe('hideColumnByIndex method', () => {
+ it('should set new columns minus the column to hide and it should keep new set as the new "visibleColumns"', () => {
+ const mockColumns = [{ id: 'field1', width: 100 }, { id: 'field2', width: 150 }, { id: 'field3', field: 'field3' }] as Column[];
+ const mockWithoutColumns = [{ id: 'field1', width: 100 }, { id: 'field3', field: 'field3' }] as Column[];
+ jest.spyOn(gridStub, 'getColumns').mockReturnValue(mockColumns);
+ const getVisibleSpy = jest.spyOn(SharedService.prototype, 'visibleColumns', 'set');
+ const setColsSpy = jest.spyOn(gridStub, 'setColumns');
+ const rxColChangedSpy = jest.spyOn(service.onColumnsChanged, 'next');
+
+ service.hideColumnByIndex(1);
+
+ expect(getVisibleSpy).toHaveBeenCalledWith(mockWithoutColumns);
+ expect(setColsSpy).toHaveBeenCalledWith(mockWithoutColumns);
+ expect(rxColChangedSpy).toHaveBeenCalledWith(mockWithoutColumns);
+ });
+
+ it('should set new columns minus the column to hide but without triggering an event when set to False', () => {
+ const mockColumns = [{ id: 'field1', width: 100 }, { id: 'field2', width: 150 }, { id: 'field3', field: 'field3' }] as Column[];
+ const mockWithoutColumns = [{ id: 'field1', width: 100 }, { id: 'field3', field: 'field3' }] as Column[];
+ jest.spyOn(gridStub, 'getColumns').mockReturnValue(mockColumns);
+ const getVisibleSpy = jest.spyOn(SharedService.prototype, 'visibleColumns', 'set');
+ const setColsSpy = jest.spyOn(gridStub, 'setColumns');
+ const rxColChangedSpy = jest.spyOn(service.onColumnsChanged, 'next');
+
+ service.hideColumnByIndex(1, false);
+
+ expect(getVisibleSpy).toHaveBeenCalledWith(mockWithoutColumns);
+ expect(setColsSpy).toHaveBeenCalledWith(mockWithoutColumns);
+ expect(rxColChangedSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('hideColumn method', () => {
+ it('should call hideColumnByIndex with the column index found', () => {
+ jest.spyOn(gridStub, 'getColumnIndex').mockReturnValue(2);
+ const hideColumnIdxSpy = jest.spyOn(service, 'hideColumnByIndex');
+
+ service.hideColumn({ id: 'field3', field: 'field3' });
+
+ expect(hideColumnIdxSpy).toHaveBeenCalledWith(2);
+ });
+ });
+
+ describe('hideColumnByIndex method', () => {
+ it('should set new columns minus the column to hide and it should keep new set as the new "visibleColumns"', () => {
+ const mockColumns = [{ id: 'field1', width: 100 }, { id: 'field2', width: 150 }, { id: 'field3', field: 'field3' }] as Column[];
+ const mockWithoutColumns = [{ id: 'field1', width: 100 }, { id: 'field3', field: 'field3' }] as Column[];
+ jest.spyOn(gridStub, 'getColumns').mockReturnValue(mockColumns);
+ const getVisibleSpy = jest.spyOn(SharedService.prototype, 'visibleColumns', 'set');
+ const setColsSpy = jest.spyOn(gridStub, 'setColumns');
+ const rxColChangedSpy = jest.spyOn(service.onColumnsChanged, 'next');
+
+ service.hideColumnByIndex(1);
+
+ expect(getVisibleSpy).toHaveBeenCalledWith(mockWithoutColumns);
+ expect(setColsSpy).toHaveBeenCalledWith(mockWithoutColumns);
+ expect(rxColChangedSpy).toHaveBeenCalledWith(mockWithoutColumns);
+ });
+
+ it('should set new columns minus the column to hide but without triggering an event when set to False', () => {
+ const mockColumns = [{ id: 'field1', width: 100 }, { id: 'field2', width: 150 }, { id: 'field3', field: 'field3' }] as Column[];
+ const mockWithoutColumns = [{ id: 'field1', width: 100 }, { id: 'field3', field: 'field3' }] as Column[];
+ jest.spyOn(gridStub, 'getColumns').mockReturnValue(mockColumns);
+ const getVisibleSpy = jest.spyOn(SharedService.prototype, 'visibleColumns', 'set');
+ const setColsSpy = jest.spyOn(gridStub, 'setColumns');
+ const rxColChangedSpy = jest.spyOn(service.onColumnsChanged, 'next');
+
+ service.hideColumnByIndex(1, false);
+
+ expect(getVisibleSpy).toHaveBeenCalledWith(mockWithoutColumns);
+ expect(setColsSpy).toHaveBeenCalledWith(mockWithoutColumns);
+ expect(rxColChangedSpy).not.toHaveBeenLastCalledWith(mockWithoutColumns);
+ });
+ });
+
describe('setSelectedRow method', () => {
it('should select the row with index provided', () => {
const spy = jest.spyOn(gridStub, 'setSelectedRows');
diff --git a/src/app/modules/angular-slickgrid/services/__tests__/sort.service.spec.ts b/src/app/modules/angular-slickgrid/services/__tests__/sort.service.spec.ts
index 2f36c67d1..b998d6dd7 100644
--- a/src/app/modules/angular-slickgrid/services/__tests__/sort.service.spec.ts
+++ b/src/app/modules/angular-slickgrid/services/__tests__/sort.service.spec.ts
@@ -8,8 +8,8 @@ import {
EmitterType,
FieldType,
GridOption,
+ MenuCommandItem,
SlickEventHandler,
- SortChangedArgs,
} from '../../models';
import { Sorters } from '../../sorters';
import { SortService } from '../sort.service';
@@ -46,7 +46,7 @@ const backendServiceStub = {
getCurrentPagination: jest.fn(),
getCurrentSorters: jest.fn(),
updateSorters: jest.fn(),
- processOnSortChanged: (event: Event, args: SortChangedArgs) => 'backend query',
+ processOnSortChanged: () => 'backend query',
} as unknown as BackendService;
const gridStub = {
@@ -60,6 +60,8 @@ const gridStub = {
onLocalSortChanged: jest.fn(),
onSort: new Slick.Event(),
render: jest.fn(),
+ setColumns: jest.fn(),
+ setOptions: jest.fn(),
setSortColumns: jest.fn(),
};
@@ -541,6 +543,105 @@ describe('SortService', () => {
});
});
+ describe('disableSortFunctionality method', () => {
+ let mockColumns: Column[];
+ beforeEach(() => {
+ mockColumns = [
+ { id: 'field1', field: 'field1', sortable: true, header: { menu: { items: [{ command: 'sort-asc' }, { command: 'sort-desc' }, { command: 'clear-sort' }] } } },
+ { id: 'field2', field: 'field2', sortable: true, header: { menu: { items: [{ command: 'sort-asc' }, { command: 'sort-desc' }, { command: 'clear-sort' }] } } },
+ ] as Column[];
+ });
+
+ it('should disable Sort functionality when passing True as 1st argument and trigger an event by default', () => {
+ const clearSpy = jest.spyOn(service, 'clearSorting');
+ const unsubscribeSpy = jest.spyOn(service.eventHandler, 'unsubscribeAll');
+ jest.spyOn(gridStub, 'getColumns').mockReturnValue(mockColumns);
+
+ service.bindLocalOnSort(gridStub);
+ service.disableSortFunctionality(true);
+
+ expect(clearSpy).toHaveBeenCalled();
+ expect(unsubscribeSpy).toHaveBeenCalled();
+ mockColumns.forEach(col => {
+ expect(col.sortable).toBeFalse();
+ });
+ mockColumns.forEach(col => col.header.menu.items.forEach(item => {
+ expect((item as MenuCommandItem).hidden).toBeTrue();
+ }));
+ });
+
+ it('should disable Sort functionality when passing True as 1st argument and False as 2nd argument SHOULD NOT trigger an event', () => {
+ const clearSpy = jest.spyOn(service, 'clearSorting');
+ const unsubscribeSpy = jest.spyOn(service.eventHandler, 'unsubscribeAll');
+ jest.spyOn(gridStub, 'getColumns').mockReturnValue(mockColumns);
+
+ service.bindLocalOnSort(gridStub);
+ service.disableSortFunctionality(true, false);
+
+ expect(clearSpy).not.toHaveBeenCalled();
+ expect(unsubscribeSpy).toHaveBeenCalled();
+ mockColumns.forEach(col => {
+ expect(col.sortable).toBeFalse();
+ });
+ mockColumns.forEach(col => col.header.menu.items.forEach(item => {
+ expect((item as MenuCommandItem).hidden).toBeTrue();
+ }));
+ });
+
+ it('should enable Sort functionality when passing False as 1st argument', (done) => {
+ jest.spyOn(gridStub, 'getColumns').mockReturnValue(mockColumns);
+ const handleSpy = jest.spyOn(service, 'handleLocalOnSort');
+
+ service.bindLocalOnSort(gridStub);
+ service.disableSortFunctionality(false);
+ gridStub.onSort.notify({ multiColumnSort: true, sortCols: [], grid: gridStub }, new Slick.EventData(), gridStub);
+
+ mockColumns.forEach(col => {
+ expect(col.sortable).toBeTrue();
+ });
+ mockColumns.forEach(col => col.header.menu.items.forEach(item => {
+ expect((item as MenuCommandItem).hidden).toBeFalse();
+ }));
+
+ setTimeout(() => {
+ expect(handleSpy).toHaveBeenCalled();
+ done();
+ });
+ });
+ });
+
+ describe('toggleSortFunctionality method', () => {
+ beforeEach(() => {
+ gridOptionMock.enableSorting = true;
+ });
+
+ it('should toggle the Sorting', () => {
+ const setOptionSpy = jest.spyOn(gridStub, 'setOptions');
+ const disableSpy = jest.spyOn(service, 'disableSortFunctionality');
+ const setColsSpy = jest.spyOn(gridStub, 'setColumns');
+
+ service.bindLocalOnSort(gridStub);
+ service.toggleSortFunctionality();
+
+ expect(setOptionSpy).toHaveBeenCalledWith({ enableSorting: false }, false, true);
+ expect(disableSpy).toHaveBeenCalledWith(true, true);
+ expect(setColsSpy).toHaveBeenCalled();
+ });
+
+ it('should toggle the Sorting BUT NOT trigger an event when defined as such', () => {
+ const setOptionSpy = jest.spyOn(gridStub, 'setOptions');
+ const disableSpy = jest.spyOn(service, 'disableSortFunctionality');
+ const setColsSpy = jest.spyOn(gridStub, 'setColumns');
+
+ service.bindLocalOnSort(gridStub);
+ service.toggleSortFunctionality(false);
+
+ expect(setOptionSpy).toHaveBeenCalledWith({ enableSorting: false }, false, true);
+ expect(disableSpy).toHaveBeenCalledWith(true, false);
+ expect(setColsSpy).toHaveBeenCalled();
+ });
+ });
+
describe('loadGridSorters method', () => {
const mockColumns = [{ id: 'firstName', field: 'firstName' }, { id: 'lastName', field: 'lastName' }] as Column[];
diff --git a/src/app/modules/angular-slickgrid/services/__tests__/utilities.spec.ts b/src/app/modules/angular-slickgrid/services/__tests__/utilities.spec.ts
index d5d420a5e..fea344596 100644
--- a/src/app/modules/angular-slickgrid/services/__tests__/utilities.spec.ts
+++ b/src/app/modules/angular-slickgrid/services/__tests__/utilities.spec.ts
@@ -4,6 +4,7 @@ import { FieldType, GridOption, OperatorType } from '../../models';
import {
addToArrayWhenNotExists,
addWhiteSpaces,
+ arrayRemoveItemByIndex,
castToPromise,
charArraysEqual,
convertHierarchicalViewToParentChildArray,
@@ -101,6 +102,16 @@ describe('Service/Utilies', () => {
});
});
+ describe('arrayRemoveItemByIndex method', () => {
+ it('should remove an item from the array', () => {
+ const input = [{ field: 'field1', name: 'Field 1' }, { field: 'field2', name: 'Field 2' }, { field: 'field3', name: 'Field 3' }];
+ const expected = [{ field: 'field1', name: 'Field 1' }, { field: 'field3', name: 'Field 3' }];
+
+ const output = arrayRemoveItemByIndex(input, 1);
+ expect(output).toEqual(expected);
+ });
+ });
+
describe('htmlEncode method', () => {
it('should return a encoded HTML string', () => {
const result = htmlEncode(`Something
`);
diff --git a/src/app/modules/angular-slickgrid/services/filter.service.ts b/src/app/modules/angular-slickgrid/services/filter.service.ts
index d6bce5426..3a2b5aa8c 100644
--- a/src/app/modules/angular-slickgrid/services/filter.service.ts
+++ b/src/app/modules/angular-slickgrid/services/filter.service.ts
@@ -153,7 +153,7 @@ export class FilterService {
this._filtersMetadata = [];
// subscribe to SlickGrid onHeaderRowCellRendered event to create filter template
- this._eventHandler.subscribe(grid.onHeaderRowCellRendered, (e: KeyboardEvent, args: any) => {
+ this._eventHandler.subscribe(grid.onHeaderRowCellRendered, (_e: KeyboardEvent, args: any) => {
// firstColumnIdRendered is null at first, so if it changes to being filled and equal, then we would know that it was already rendered
// this is to avoid rendering the filter twice (only the Select Filter for now), rendering it again also clears the filter which has unwanted side effect
if (args.column.id === this._firstColumnIdRendered) {
@@ -180,7 +180,7 @@ export class FilterService {
this._dataView.setFilterArgs({ columnFilters: this._columnFilters, grid: this._grid, dataView: this._dataView });
this._dataView.setFilter(this.customLocalFilter.bind(this));
- this._eventHandler.subscribe(this._onSearchChange, (e: KeyboardEvent, args: any) => {
+ this._eventHandler.subscribe(this._onSearchChange, (_e: KeyboardEvent, args: any) => {
const isGridWithTreeData = this._gridOptions && this._gridOptions.enableTreeData || false;
// When using Tree Data, we need to do it in 2 steps
@@ -201,7 +201,7 @@ export class FilterService {
});
// subscribe to SlickGrid onHeaderRowCellRendered event to create filter template
- this._eventHandler.subscribe(grid.onHeaderRowCellRendered, (e: KeyboardEvent, args: any) => {
+ this._eventHandler.subscribe(grid.onHeaderRowCellRendered, (_e: KeyboardEvent, args: any) => {
this.addFilterTemplateToHeaderRow(args);
});
}
@@ -638,6 +638,50 @@ export class FilterService {
}
}
+ /**
+ * Toggle the Filter Functionality
+ * @param {boolean} isFilterDisabled - optionally force a disable/enable of the Sort Functionality? Defaults to True
+ * @param {boolean} clearFiltersWhenDisabled - when disabling the Filter, do we also want to clear all the filters as well? Defaults to True
+ */
+ disableFilterFunctionality(isFilterDisabled = true, clearFiltersWhenDisabled = true) {
+ const prevShowFilterFlag = this._gridOptions.enableFiltering;
+ const newShowFilterFlag = !prevShowFilterFlag;
+
+ if (newShowFilterFlag !== isFilterDisabled) {
+ if (clearFiltersWhenDisabled && isFilterDisabled) {
+ this.clearFilters();
+ }
+ this._grid.setOptions({ enableFiltering: newShowFilterFlag }, false, true);
+ this._grid.setHeaderRowVisibility(newShowFilterFlag);
+
+ // when displaying header row, we'll call "setColumns" which in terms will recreate the header row filters
+ this._grid.setColumns(this.sharedService.columnDefinitions);
+ }
+ }
+
+ /**
+ * Toggle the Filter Functionality (show/hide the header row filter bar as well)
+ * @param {boolean} clearFiltersWhenDisabled - when disabling the filters, do we want to clear the filters before hiding the filters? Defaults to True
+ */
+ toggleFilterFunctionality(clearFiltersWhenDisabled = true) {
+ const prevShowFilterFlag = this._gridOptions.enableFiltering;
+ this.disableFilterFunctionality(prevShowFilterFlag, clearFiltersWhenDisabled);
+ }
+
+ /**
+ * Toggle the Header Row filter bar (this does not disable the Filtering itself, you can use "toggleFilterFunctionality()" instead, however this will reset any column positions)
+ */
+ toggleHeaderFilterRow() {
+ let showHeaderRow = this._gridOptions && this._gridOptions.showHeaderRow || false;
+ showHeaderRow = !showHeaderRow; // inverse show header flag
+ this._grid.setHeaderRowVisibility(showHeaderRow);
+
+ // when displaying header row, we'll call "setColumns" which in terms will recreate the header row filters
+ if (showHeaderRow === true) {
+ this._grid.setColumns(this.sharedService.columnDefinitions);
+ }
+ }
+
/**
* Set the sort icons in the UI (ONLY the icons, it does not do any sorting)
* The column sort icons are not necessarily inter-connected to the sorting functionality itself,
diff --git a/src/app/modules/angular-slickgrid/services/grid.service.ts b/src/app/modules/angular-slickgrid/services/grid.service.ts
index aaa21c81c..237a2df2d 100644
--- a/src/app/modules/angular-slickgrid/services/grid.service.ts
+++ b/src/app/modules/angular-slickgrid/services/grid.service.ts
@@ -15,6 +15,7 @@ import { FilterService } from './filter.service';
import { GridStateService } from './gridState.service';
import { SharedService } from './shared.service';
import { SortService } from './sort.service';
+import { arrayRemoveItemByIndex } from './utilities';
// using external non-typed js libraries
declare const Slick: any;
@@ -31,6 +32,7 @@ export class GridService {
onItemDeleted = new Subject();
onItemUpdated = new Subject();
onItemUpserted = new Subject();
+ onColumnsChanged = new Subject();
constructor(
private extensionService: ExtensionService,
@@ -131,6 +133,36 @@ export class GridService {
};
}
+ /**
+ * Hide a Column from the Grid (the column will just become hidden and will still show up in columnPicker/gridMenu)
+ * @param column
+ */
+ hideColumn(column: Column) {
+ if (this._grid && this._grid.getColumns && this._grid.setColumns && this._grid.getColumnIndex) {
+ const columnIndex = this._grid.getColumnIndex(column.id);
+ if (columnIndex >= 0) {
+ this.hideColumnByIndex(columnIndex);
+ }
+ }
+ }
+
+ /**
+ * Hide a Column from the Grid by its column definition index (the column will just become hidden and will still show up in columnPicker/gridMenu)
+ * @param columnIndex - column definition index
+ * @param triggerEvent - do we want to trigger an event (onHeaderMenuColumnsChanged) when column becomes hidden? Defaults to true.
+ */
+ hideColumnByIndex(columnIndex: number, triggerEvent = true) {
+ if (this._grid && this._grid.getColumns && this._grid.setColumns) {
+ const currentColumns = this._grid.getColumns();
+ const visibleColumns = arrayRemoveItemByIndex(currentColumns, columnIndex);
+ this.sharedService.visibleColumns = visibleColumns;
+ this._grid.setColumns(visibleColumns);
+ if (triggerEvent) {
+ this.onColumnsChanged.next(visibleColumns);
+ }
+ }
+ }
+
/**
* Highlight then fade a row for x seconds.
* The implementation follows this SO answer: https://stackoverflow.com/a/19985148/1212166
diff --git a/src/app/modules/angular-slickgrid/services/sort.service.ts b/src/app/modules/angular-slickgrid/services/sort.service.ts
index 3ecd72eef..433c1a912 100644
--- a/src/app/modules/angular-slickgrid/services/sort.service.ts
+++ b/src/app/modules/angular-slickgrid/services/sort.service.ts
@@ -80,30 +80,31 @@ export class SortService {
this._grid = grid;
this.processTreeDataInitialSort();
+ this._eventHandler.subscribe(grid.onSort, this.handleLocalOnSort.bind(this));
+ }
- this._eventHandler.subscribe(grid.onSort, (e: any, args: any) => {
- if (args && (args.sortCols || args.sortCol)) {
- // multiSort and singleSort are not exactly the same, but we want to structure it the same for the (for loop) after
- // also to avoid having to rewrite the for loop in the sort, we will make the singleSort an array of 1 object
- const sortColumns = (args.multiColumnSort) ? args.sortCols : new Array({ sortAsc: args.sortAsc, sortCol: args.sortCol });
-
- // keep current sorters
- this._currentLocalSorters = []; // reset current local sorters
- if (Array.isArray(sortColumns)) {
- sortColumns.forEach((sortColumn: { sortCol: Column; sortAsc: boolean; }) => {
- if (sortColumn.sortCol) {
- this._currentLocalSorters.push({
- columnId: sortColumn.sortCol.id,
- direction: sortColumn.sortAsc ? SortDirection.ASC : SortDirection.DESC
- });
- }
- });
- }
-
- this.onLocalSortChanged(grid, sortColumns);
- this.emitSortChanged(EmitterType.local);
+ handleLocalOnSort(_e: any, args: any) {
+ if (args && (args.sortCols || args.sortCol)) {
+ // multiSort and singleSort are not exactly the same, but we want to structure it the same for the (for loop) after
+ // also to avoid having to rewrite the for loop in the sort, we will make the singleSort an array of 1 object
+ const sortColumns = (args.multiColumnSort) ? args.sortCols : new Array({ sortAsc: args.sortAsc, sortCol: args.sortCol });
+
+ // keep current sorters
+ this._currentLocalSorters = []; // reset current local sorters
+ if (Array.isArray(sortColumns)) {
+ sortColumns.forEach((sortColumn: { sortCol: Column; sortAsc: boolean; }) => {
+ if (sortColumn.sortCol) {
+ this._currentLocalSorters.push({
+ columnId: sortColumn.sortCol.id,
+ direction: sortColumn.sortAsc ? SortDirection.ASC : SortDirection.DESC
+ });
+ }
+ });
}
- });
+
+ this.onLocalSortChanged(this._grid, sortColumns);
+ this.emitSortChanged(EmitterType.local);
+ }
}
clearSortByColumnId(event: Event | undefined, columnId: string | number) {
@@ -189,6 +190,44 @@ export class SortService {
}
}
+ /**
+ * Toggle the Sorting Functionality
+ * @param {boolean} isSortingDisabled - optionally force a disable/enable of the Sort Functionality? Defaults to True
+ * @param {boolean} clearSortingWhenDisabled - when disabling the sorting, do we also want to clear the sorting as well? Defaults to True
+ */
+ disableSortFunctionality(isSortingDisabled = true, clearSortingWhenDisabled = true) {
+ const prevSorting = this._gridOptions.enableSorting;
+ const newSorting = !prevSorting;
+
+ this._gridOptions.enableSorting = newSorting;
+ let updatedColumnDefinitions;
+ if (isSortingDisabled) {
+ if (clearSortingWhenDisabled) {
+ this.clearSorting();
+ }
+ this._eventHandler.unsubscribeAll();
+ updatedColumnDefinitions = this.disableSortingOnAllColumns(true);
+ } else {
+ updatedColumnDefinitions = this.disableSortingOnAllColumns(false);
+ const onSortHandler = this._grid.onSort;
+ this._eventHandler.subscribe(onSortHandler, (e: Event, args: any) => this.handleLocalOnSort(e, args));
+ }
+ this._grid.setOptions({ enableSorting: this._gridOptions.enableSorting }, false, true);
+
+ // reset columns so that it recreate the column headers and remove/add the sort icon hints
+ // basically without this, the sort icon hints were still showing up even after disabling the Sorting
+ this._grid.setColumns(updatedColumnDefinitions);
+ }
+
+ /**
+ * Toggle the Sorting functionality
+ * @param {boolean} clearSortingWhenDisabled - when disabling the sorting, do we also want to clear the sorting as well? Defaults to True
+ */
+ toggleSortFunctionality(clearSortingOnDisable = true) {
+ const previousSorting = this._gridOptions.enableSorting;
+ this.disableSortFunctionality(previousSorting, clearSortingOnDisable);
+ }
+
/**
* A simple function that is binded to the subscriber and emit a change when the sort is called.
* Other services, like Pagination, can then subscribe to it.
@@ -480,4 +519,38 @@ export class SortService {
}
}
}
+
+ // --
+ // private functions
+ // -------------------
+
+ /**
+ * Loop through all column definitions and do the following 2 things
+ * 1. disable/enable the "sortable" property of each column
+ * 2. loop through each Header Menu commands and change the command "hidden" property to enable/disable
+ * Also note that we aren't deleting any properties, we just toggle their flags so that we can reloop through at later point in time.
+ * (if we previously deleted these properties we wouldn't be able to change them back since these properties wouldn't exist anymore, hence why we just hide the commands)
+ * @param {boolean} isDisabling - are we disabling the sort functionality? Defaults to true
+ */
+ private disableSortingOnAllColumns(isDisabling = true): Column[] {
+ const columnDefinitions = this._grid.getColumns();
+
+ columnDefinitions.forEach((col) => {
+ if (typeof col.sortable !== undefined) {
+ col.sortable = !isDisabling;
+ }
+ if (col && col.header && col.header.menu) {
+ col.header.menu.items.forEach(menuItem => {
+ if (menuItem && typeof menuItem !== 'string') {
+ const menuCommand = menuItem.command;
+ if (menuCommand === 'sort-asc' || menuCommand === 'sort-desc' || menuCommand === 'clear-sort') {
+ menuItem.hidden = isDisabling;
+ }
+ }
+ });
+ }
+ });
+
+ return columnDefinitions;
+ }
}
diff --git a/src/app/modules/angular-slickgrid/services/utilities.ts b/src/app/modules/angular-slickgrid/services/utilities.ts
index d43480d27..c033551bb 100644
--- a/src/app/modules/angular-slickgrid/services/utilities.ts
+++ b/src/app/modules/angular-slickgrid/services/utilities.ts
@@ -41,6 +41,15 @@ export function addWhiteSpaces(nbSpaces: number): string {
return result;
}
+/**
+ * Remove a column from the grid by it's index in the grid
+ * @param array input
+ * @param index
+ */
+export function arrayRemoveItemByIndex(array: T[], index: number): T[] {
+ return array.filter((_el: T, i: number) => index !== i);
+}
+
/**
* Convert a flat array (with "parentId" references) into a hierarchical dataset structure (where children are array(s) inside their parent objects)
* @param flatArray input array (flat dataset)
@@ -252,7 +261,7 @@ export function htmlEncode(inputValue: string): string {
* @param string text: output text
*/
export function htmlEntityDecode(input: string): string {
- return input.replace(/(\d+);/g, function (match, dec) {
+ return input.replace(/(\d+);/g, function (_match, dec) {
return String.fromCharCode(dec);
});
}
diff --git a/src/app/modules/angular-slickgrid/styles/slick-controls.scss b/src/app/modules/angular-slickgrid/styles/slick-controls.scss
index a3400376d..549da9490 100644
--- a/src/app/modules/angular-slickgrid/styles/slick-controls.scss
+++ b/src/app/modules/angular-slickgrid/styles/slick-controls.scss
@@ -227,6 +227,7 @@
border: 0;
width: 22px;
font-size: $grid-menu-icon-font-size;
+ z-index: 2;
}
.slick-gridmenu-custom {
@@ -236,6 +237,7 @@
/* Menu items */
.slick-gridmenu-item {
cursor: pointer;
+ display: block;
border: $grid-menu-item-border;
border-radius: $grid-menu-item-border-radius;
padding: $grid-menu-item-padding;
@@ -277,6 +279,9 @@
color: $grid-menu-item-disabled-color;
}
}
+.slick-gridmenu-item-hidden {
+ display: none;
+}
.slick-gridmenu-icon {
display: inline-block;
diff --git a/src/app/modules/angular-slickgrid/styles/slick-plugins.scss b/src/app/modules/angular-slickgrid/styles/slick-plugins.scss
index 3e9e3da16..a22d2b55c 100644
--- a/src/app/modules/angular-slickgrid/styles/slick-plugins.scss
+++ b/src/app/modules/angular-slickgrid/styles/slick-plugins.scss
@@ -60,6 +60,7 @@
.slick-cell-menu-item {
cursor: pointer;
+ display: block;
border: $cell-menu-item-border;
border-radius: $cell-menu-item-border-radius;
font-size: $cell-menu-item-font-size;
@@ -119,6 +120,9 @@
color: $cell-menu-item-disabled-color;
}
}
+ &.slick-cell-menu-item-hidden {
+ display: none;
+ }
}
.slick-cell-menu-option-list {
@@ -201,6 +205,7 @@
&.slick-context-menu-item-divider {
cursor: default;
+ display: block;
border: none;
overflow: hidden;
padding: 0;
@@ -244,6 +249,9 @@
color: $context-menu-item-disabled-color;
}
}
+ &.slick-context-menu-item-hidden {
+ display: none;
+ }
}
.slick-context-menu-option-list {
@@ -381,6 +389,7 @@
.slick-header-menuitem {
cursor: pointer;
+ display: block;
border: $header-menu-item-border;
border-radius: $header-menu-item-border-radius;
padding: $header-menu-item-padding;
@@ -451,6 +460,9 @@
color: $header-menu-item-disabled-color;
}
}
+.slick-header-menuitem-hidden {
+ display: none;
+}
// ----------------------------------------------
// Row Move Manager Plugin
@@ -563,7 +575,7 @@
}
.placeholder {
font-family: $multiselect-placeholder-font-family;
- font-size: ($multiselect-input-filter-font-size + 2px);
+ font-size: $header-font-size;
}
}
.ms-filter.search-filter {
@@ -717,15 +729,19 @@
}
input.search-filter {
- font-family: $filter-placeholder-font-family;
+ font-family: $filter-placeholder-font-family;
}
.search-filter {
- input {
- font-family: $filter-placeholder-font-family;
- &.compound-input {
- border-radius: $compound-filter-border-radius !important;
- }
+ input {
+ font-family: $filter-placeholder-font-family;
+ &.compound-input {
+ border-radius: $compound-filter-border-radius !important;
+ border-left: none;
}
+ }
+ input.compound-slider {
+ border-left: none !important;
+ }
}
// ----------------------------------------------
@@ -735,6 +751,7 @@ input.search-filter {
.search-filter .flatpickr {
input.form-control {
cursor: pointer;
+ border-left: none;
}
.flatpickr {
@@ -846,6 +863,9 @@ input.flatpickr.form-control {
// ----------------------------------------------
// Input Slider Filter (with vanilla html)
// ----------------------------------------------
+.ui-widget.ui-widget-content {
+ border: 0;
+}
input.slider-editor-input[type=range],
input.slider-filter-input[type=range] {
/*removes default webkit styles*/
diff --git a/test/cypress/integration/example17.spec.js b/test/cypress/integration/example17.spec.js
index 70b73e082..6134e3a31 100644
--- a/test/cypress/integration/example17.spec.js
+++ b/test/cypress/integration/example17.spec.js
@@ -3,13 +3,6 @@
describe('Example 17 - Row Move & Checkbox Selector Selector Plugins', () => {
const fullTitles = ['', '', 'Title', 'Duration', '% Complete', 'Start', 'Finish', 'Completed'];
- beforeEach(() => {
- // create a console.log spy for later use
- cy.window().then((win) => {
- cy.spy(win.console, "log");
- });
- });
-
it('should display Example title', () => {
cy.visit(`${Cypress.config('baseExampleUrl')}/rowmove`);
cy.get('h2').should('contain', 'Example 17: Row Move & Checkbox Selector');
@@ -87,4 +80,196 @@ describe('Example 17 - Row Move & Checkbox Selector Selector Plugins', () => {
cy.get('[style="top:105px"] > .slick-cell:nth(1) input[type="checkbox"]:checked').should('have.length', 1);
cy.get('[style="top:175px"] > .slick-cell:nth(1) input[type="checkbox"]:checked').should('have.length', 1);
});
+
+ it('should move "Duration" column to a different position in the grid', () => {
+ const expectedTitles = ['', '', 'Title', '% Complete', 'Start', 'Finish', 'Duration', 'Completed', 'Title'];
+
+ cy.get('.slick-header-columns')
+ .children('.slick-header-column:nth(3)')
+ .should('contain', 'Duration')
+ .trigger('mousedown', 'center', { which: 1 });
+
+ cy.get('.slick-header-columns')
+ .children('.slick-header-column:nth(6)')
+ .should('contain', 'Finish')
+ .trigger('mousemove', 'bottomRight')
+ .trigger('mouseup', 'bottomRight', { force: true });
+
+ cy.get('#grid17')
+ .find('.slick-header-columns')
+ .children()
+ .each(($child, index) => expect($child.text()).to.eq(expectedTitles[index]));
+ });
+
+ it('should be able to hide "Duration" column', () => {
+ const expectedTitles = ['', '', 'Title', '% Complete', 'Start', 'Finish', 'Completed', 'Title'];
+
+ cy.get('[data-test="hide-duration-btn"]').click();
+
+ cy.get('#grid17')
+ .find('.slick-header-columns')
+ .children()
+ .each(($child, index) => expect($child.text()).to.eq(expectedTitles[index]));
+ });
+
+ it('should be able to click disable Filters functionality button and expect no Filters', () => {
+ const expectedTitles = ['', '', 'Title', '% Complete', 'Start', 'Finish', 'Completed', 'Title'];
+
+ cy.get('[data-test="disable-filters-btn"]').click().click(); // even clicking twice should have same result
+
+ cy.get('.slick-headerrow').should('not.be.visible');
+ cy.get('.slick-headerrow-columns .slick-headerrow-column').should('have.length', 0);
+
+ cy.get('#grid17')
+ .find('.slick-header-columns')
+ .children()
+ .each(($child, index) => expect($child.text()).to.eq(expectedTitles[index]));
+
+ cy.get('[data-test="toggle-filtering-btn"]').click(); // show it back
+ });
+
+ it('should be able to toggle Filters functionality', () => {
+ const expectedTitles = ['', '', 'Title', '% Complete', 'Start', 'Finish', 'Completed', 'Title'];
+
+ cy.get('[data-test="toggle-filtering-btn"]').click(); // hide it
+
+ cy.get('.slick-headerrow').should('not.be.visible');
+ cy.get('.slick-headerrow-columns .slick-headerrow-column').should('have.length', 0);
+
+ cy.get('#grid17')
+ .find('.slick-header-columns')
+ .children()
+ .each(($child, index) => expect($child.text()).to.eq(expectedTitles[index]));
+
+ cy.get('[data-test="toggle-filtering-btn"]').click(); // show it
+ cy.get('.slick-headerrow-columns .slick-headerrow-column').should('have.length', 7);
+
+ cy.get('#grid17')
+ .find('.slick-header-columns')
+ .children()
+ .each(($child, index) => expect($child.text()).to.eq(expectedTitles[index]));
+ });
+
+ it('should be able to toggle Sorting functionality (disable) and expect all Sorting commands to be hidden and also not show Sort hint while hovering a column', () => {
+ const expectedFullHeaderMenuCommands = ['Sort Ascending', 'Sort Descending', '', 'Remove Filter', 'Remove Sort', 'Hide Column'];
+
+ cy.get('.slick-sort-indicator').should('have.length.greaterThan', 0); // sort icon hints
+ cy.get('[data-test="toggle-sorting-btn"]').click(); // disable it
+ cy.get('.slick-sort-indicator').should('have.length', 0);
+
+ cy.get('#grid17')
+ .find('.slick-header-column:nth(5)')
+ .trigger('mouseover')
+ .children('.slick-header-menubutton')
+ .should('be.hidden')
+ .invoke('show')
+ .click();
+
+ cy.get('.slick-header-menu')
+ .children()
+ .each(($child, index) => {
+ const commandTitle = $child.text();
+ expect(commandTitle).to.eq(expectedFullHeaderMenuCommands[index]);
+
+ // expect all Sorting commands to be hidden
+ if (commandTitle === 'Sort Ascending' || commandTitle === 'Sort Descending' || commandTitle === 'Remove Sort') {
+ expect($child).not.to.be.visible;
+ }
+ });
+ });
+
+ it('should be able to toggle Sorting functionality (re-enable) and expect all Sorting commands to be hidden and also not show Sort hint while hovering a column', () => {
+ const expectedFullHeaderMenuCommands = ['Sort Ascending', 'Sort Descending', '', 'Remove Filter', 'Remove Sort', 'Hide Column'];
+
+ cy.get('.slick-sort-indicator').should('have.length', 0); // sort icon hints
+ cy.get('[data-test="toggle-sorting-btn"]').click(); // enable it back
+ cy.get('.slick-sort-indicator').should('have.length.greaterThan', 0);
+
+ cy.get('#grid17')
+ .find('.slick-header-column:nth(5)')
+ .trigger('mouseover')
+ .children('.slick-header-menubutton')
+ .should('be.hidden')
+ .invoke('show')
+ .click();
+
+ cy.get('.slick-header-menu')
+ .children()
+ .each(($child, index) => {
+ const commandTitle = $child.text();
+ expect(commandTitle).to.eq(expectedFullHeaderMenuCommands[index]);
+ expect($child).to.be.visible;
+ });
+ });
+
+ it('should be able to click disable Sorting functionality button and expect all Sorting commands to be hidden and also not show Sort hint while hovering a column', () => {
+ const expectedFullHeaderMenuCommands = ['Sort Ascending', 'Sort Descending', '', 'Remove Filter', 'Remove Sort', 'Hide Column'];
+
+ cy.get('.slick-sort-indicator').should('have.length.greaterThan', 0); // sort icon hints
+ cy.get('[data-test="disable-sorting-btn"]').click().click(); // even clicking twice should have same result
+ cy.get('.slick-sort-indicator').should('have.length', 0);
+
+ cy.get('#grid17')
+ .find('.slick-header-column:nth(5)')
+ .trigger('mouseover')
+ .children('.slick-header-menubutton')
+ .should('be.hidden')
+ .invoke('show')
+ .click();
+
+ cy.get('.slick-header-menu')
+ .children()
+ .each(($child, index) => {
+ const commandTitle = $child.text();
+ expect(commandTitle).to.eq(expectedFullHeaderMenuCommands[index]);
+
+ // expect all Sorting commands to be hidden
+ if (commandTitle === 'Sort Ascending' || commandTitle === 'Sort Descending' || commandTitle === 'Remove Sort') {
+ expect($child).not.to.be.visible;
+ }
+ });
+ });
+
+ it('should open Column Picker and show the "Duration" column back to visible and expect it to have kept its position after toggling filter/sorting', () => {
+ // first 2 cols are hidden but they do count as li item
+ const expectedFullPickerTitles = ['', '', 'Title', '% Complete', 'Start', 'Finish', 'Duration', 'Completed'];
+
+ cy.get('#grid17')
+ .find('.slick-header-column')
+ .first()
+ .trigger('mouseover')
+ .trigger('contextmenu')
+ .invoke('show');
+
+ cy.get('.slick-columnpicker')
+ .find('.slick-columnpicker-list')
+ .children()
+ .each(($child, index) => {
+ if (index < expectedFullPickerTitles.length) {
+ expect($child.text()).to.eq(expectedFullPickerTitles[index]);
+ }
+ });
+
+ cy.get('.slick-columnpicker')
+ .find('.slick-columnpicker-list')
+ .children('li:nth-child(7)')
+ .children('label')
+ .should('contain', 'Duration')
+ .click();
+
+ cy.get('#grid17')
+ .get('.slick-columnpicker:visible')
+ .find('span.close')
+ .trigger('click')
+ .click();
+
+ cy.get('#grid17')
+ .find('.slick-header-columns')
+ .children()
+ .each(($child, index) => {
+ if (index <= 5) {
+ expect($child.text()).to.eq(expectedFullPickerTitles[index]);
+ }
+ });
+ });
});