diff --git a/js/core/gridContextMenu.ts b/js/core/gridContextMenu.ts index 0f44268f..3156b1c6 100644 --- a/js/core/gridContextMenu.ts +++ b/js/core/gridContextMenu.ts @@ -97,6 +97,13 @@ export class FeatherGridContextMenu extends GridContextMenu { // Add menu items based on the region of the grid that was clicked on. switch (hit.region) { case 'column-header': + this._menu.addItem({ + command: FeatherGridContextMenu.CommandID.HideColumn, + args: args, + }); + this._menu.addItem({ + type: 'separator', + }); this._menu.addItem({ command: FeatherGridContextMenu.CommandID.SortAscending, args: args, @@ -141,6 +148,17 @@ export class FeatherGridContextMenu extends GridContextMenu { }); break; case 'corner-header': + this._menu.addItem({ + command: FeatherGridContextMenu.CommandID.ShowAllColumns, + args: args, + }); + this._menu.addItem({ + command: FeatherGridContextMenu.CommandID.HideAllColumns, + args: args, + }); + this._menu.addItem({ + type: 'separator', + }); this._menu.addItem({ command: FeatherGridContextMenu.CommandID.SortAscending, args: args, @@ -258,6 +276,9 @@ export namespace FeatherGridContextMenu { SaveSelectionAsCsv = 'saveSelectionAsCsv', SaveAllAsCsv = 'saveAllAsCsv', ClearSelection = 'clearSelection', + HideColumn = 'hideColumn', + HideAllColumns = 'hideAllColumns', + ShowAllColumns = 'showAllColumns', } /** diff --git a/js/core/transform.ts b/js/core/transform.ts index 23584691..26c16a5e 100644 --- a/js/core/transform.ts +++ b/js/core/transform.ts @@ -2,7 +2,10 @@ * A declarative spec for specifying transformations. */ export namespace Transform { - export type TransformSpec = Transform.Sort | Transform.Filter; + export type TransformSpec = + | Transform.Sort + | Transform.Filter + | Transform.Hide; /** * A declarative spec for the `Sort` transformation. @@ -30,6 +33,31 @@ export namespace Transform { desc: boolean; }; + /** + * A declarative spec for the `Hide` transformation. + */ + export type Hide = { + /** + * A type alias for this transformation. + */ + type: 'hide'; + + /** + * The column in the data schema to apply the transformation to. + */ + column: string; + + /** + * The column in the data schema to apply the transformation to. + */ + columnIndex?: number; + + /** + * Boolean indicating if all columns should be hidden + */ + hideAll: boolean; + }; + /** * A declarative spec for the `Filter` transformation. */ diff --git a/js/core/transformExecutors.ts b/js/core/transformExecutors.ts index bd67ce23..b2881329 100644 --- a/js/core/transformExecutors.ts +++ b/js/core/transformExecutors.ts @@ -1,7 +1,7 @@ import { Dict } from '@jupyter-widgets/base'; -import { Transform } from './transform'; import { DataSource } from '../datasource'; +import { Transform } from './transform'; import * as moment from 'moment'; @@ -25,6 +25,58 @@ export namespace TransformExecutor { export type IData = Readonly; } +export class HideExecutor extends TransformExecutor { + constructor(options: HideExecutor.IOptions) { + super(); + this._options = options; + } + + // input has data and schema + public apply(input: TransformExecutor.IData): TransformExecutor.IData { + const newSchema = { ...input.schema }; + + console.log('input.schema', input.schema); + if (this._options.hideAll) { + newSchema.fields = newSchema.fields.filter( + (field) => field.name === 'index_column', + ); + } else { + newSchema.fields = newSchema.fields.filter( + (field) => field.name !== this._options.field, + ); + } + + return new DataSource(input.data, input.fields, newSchema); + } + + protected _options: HideExecutor.IOptions; +} + +/** + * The namespace for the `HideExecutor` class statics. + */ +export namespace HideExecutor { + /** + * An options object for initializing a Hide. + */ + export interface IOptions { + /** + * The name of the field to hide in the data source. + */ + field: string; + + /** + * The data type of the column associated with this transform. + */ + dType: string; + + /** + * Boolean indicating if all columns should be hidden + */ + hideAll: boolean; + } +} + /** * A transformation that filters a single field by the provided operator and * value. diff --git a/js/core/transformStateManager.ts b/js/core/transformStateManager.ts index 6a6272f4..df05ab90 100644 --- a/js/core/transformStateManager.ts +++ b/js/core/transformStateManager.ts @@ -3,8 +3,9 @@ import { Transform } from './transform'; import { View } from './view'; import { - SortExecutor, FilterExecutor, + HideExecutor, + SortExecutor, TransformExecutor, } from './transformExecutors'; @@ -12,7 +13,7 @@ import { each } from '@lumino/algorithm'; import { JSONExt } from '@lumino/coreutils'; -import { Signal, ISignal } from '@lumino/signaling'; +import { ISignal, Signal } from '@lumino/signaling'; import { DataSource } from '../datasource'; /** @@ -25,6 +26,7 @@ export class TransformStateManager { this._state[transform.column] = { sort: undefined, filter: undefined, + hide: undefined, }; } @@ -41,6 +43,9 @@ export class TransformStateManager { case 'filter': this._state[transform.column]['filter'] = transform; break; + case 'hide': + this._state[transform.column]['hide'] = transform; + break; default: throw 'unreachable'; } @@ -110,6 +115,7 @@ export class TransformStateManager { private _createExecutors(data: Readonly): TransformExecutor[] { const sortExecutors: SortExecutor[] = []; const filterExecutors: FilterExecutor[] = []; + const hideExecutors: HideExecutor[] = []; Object.keys(this._state).forEach((column) => { const transform: TransformStateManager.IColumn = this._state[column]; @@ -146,10 +152,25 @@ export class TransformStateManager { }); filterExecutors.push(executor); } + if (transform.hide) { + let dType = ''; + for (const field of data.schema.fields) { + if (field.name === transform.hide.column) { + dType = field.type; + } + } + + const executor = new HideExecutor({ + field: transform.hide.column, + dType, + hideAll: transform.hide.hideAll, + }); + hideExecutors.push(executor); + } }); // Always put filters first - return [...filterExecutors, ...sortExecutors]; + return [...filterExecutors, ...sortExecutors, ...hideExecutors]; } /** @@ -171,10 +192,12 @@ export class TransformStateManager { columnState.sort = undefined; } else if (transformType === 'filter') { columnState.filter = undefined; + } else if (transformType === 'hide') { + columnState.hide = undefined; } else { throw 'unreachable'; } - if (!columnState.sort && !columnState.filter) { + if (!columnState.sort && !columnState.filter && !columnState.hide) { delete this._state[column]; } this._changed.emit({ @@ -229,6 +252,9 @@ export class TransformStateManager { if (this._state[column].filter) { transforms.push(this._state[column].filter!); } + if (this._state[column].hide) { + transforms.push(this._state[column].hide!); + } }); return transforms; } @@ -255,6 +281,7 @@ export namespace TransformStateManager { export interface IColumn { filter: Transform.Filter | undefined; sort: Transform.Sort | undefined; + hide: Transform.Hide | undefined; } export interface IState { [key: string]: IColumn; diff --git a/js/core/view.ts b/js/core/view.ts index 05b3daa3..5a16885b 100644 --- a/js/core/view.ts +++ b/js/core/view.ts @@ -42,6 +42,7 @@ export class View { if (region === 'body') { return this._data.length; } else { + // If there are no body rows, return one for the header row return this._bodyFields[0]?.rows.length ?? 1; } } diff --git a/js/core/viewbasedjsonmodel.ts b/js/core/viewbasedjsonmodel.ts index 37802479..5e335b36 100644 --- a/js/core/viewbasedjsonmodel.ts +++ b/js/core/viewbasedjsonmodel.ts @@ -10,8 +10,8 @@ import { View } from './view'; import { TransformStateManager } from './transformStateManager'; -import { ArrayUtils } from '../utils'; import { DataSource } from '../datasource'; +import { ArrayUtils } from '../utils'; /** * A view based data model implementation for in-memory JSON data. diff --git a/js/feathergrid.ts b/js/feathergrid.ts index 8222d68c..538cb12d 100644 --- a/js/feathergrid.ts +++ b/js/feathergrid.ts @@ -1070,6 +1070,64 @@ export class FeatherGrid extends Widget { this.grid.selectionModel?.clear(); }, }); + commands.addCommand(FeatherGridContextMenu.CommandID.HideColumn, { + label: 'Hide Column', + mnemonic: -1, + execute: (args) => { + const command: FeatherGridContextMenu.CommandArgs = + args as FeatherGridContextMenu.CommandArgs; + + const colIndex = this._dataModel.getSchemaIndex( + command.region, + command.columnIndex, + ); + + const column = + this.dataModel.currentView.dataset.schema.fields[colIndex]; + + this._dataModel.addTransform({ + type: 'hide', + column: column.name, + columnIndex: colIndex, + hideAll: false, + }); + }, + }); + commands.addCommand(FeatherGridContextMenu.CommandID.HideAllColumns, { + label: 'Hide All Columns', + mnemonic: -1, + execute: (args) => { + const cellClick: FeatherGridContextMenu.CommandArgs = + args as FeatherGridContextMenu.CommandArgs; + const colIndex = this._dataModel.getSchemaIndex( + cellClick.region, + cellClick.columnIndex, + ); + + const column = + this.dataModel.currentView.dataset.schema.fields[colIndex]; + + this._dataModel.addTransform({ + type: 'hide', + column: column.name, + columnIndex: colIndex, + hideAll: true, + }); + }, + }); + commands.addCommand(FeatherGridContextMenu.CommandID.ShowAllColumns, { + label: 'Show All Columns', + mnemonic: -1, + execute: () => { + const activeTransforms: Transform.TransformSpec[] = + this._dataModel.activeTransforms; + + const newTransforms = activeTransforms.filter( + (val) => val.type !== 'hide', + ); + this._dataModel.replaceTransforms(newTransforms); + }, + }); return commands; } diff --git a/tests/js/transformStateManager.test.ts b/tests/js/transformStateManager.test.ts index b2a5b244..04d67534 100644 --- a/tests/js/transformStateManager.test.ts +++ b/tests/js/transformStateManager.test.ts @@ -1,7 +1,7 @@ -import { TransformStateManager } from '../../js/core/transformStateManager'; import { Transform } from '../../js/core/transform'; -import { DataGenerator } from './testUtils'; +import { TransformStateManager } from '../../js/core/transformStateManager'; import { View } from '../../js/core/view'; +import { DataGenerator } from './testUtils'; describe('Test .add()', () => { const testCases: Transform.TransformSpec[] = [ @@ -229,10 +229,14 @@ namespace Private { return { type: 'filter', column: 'test', operator: '<', value: 0 }; } + export function simpleHide(): Transform.Hide { + return { type: 'hide', column: 'test', hideAll: false }; + } + /** * Returns a simple column of transform state. */ export function simpleState(): TransformStateManager.IColumn { - return { filter: simpleFilter(), sort: simpleSort() }; + return { filter: simpleFilter(), sort: simpleSort(), hide: simpleHide() }; } }