diff --git a/src/sql/azdata.proposed.d.ts b/src/sql/azdata.proposed.d.ts index c5cf5b7c16..881a735417 100644 --- a/src/sql/azdata.proposed.d.ts +++ b/src/sql/azdata.proposed.d.ts @@ -756,4 +756,11 @@ declare module 'azdata' { */ title: string; } + + export interface TableComponentProperties { + /** + * Specifies whether to use headerFilter plugin + */ + headerFilter?: boolean, + } } diff --git a/src/sql/base/browser/ui/table/formatters.ts b/src/sql/base/browser/ui/table/formatters.ts index 7015d29bf6..1c7f813430 100644 --- a/src/sql/base/browser/ui/table/formatters.ts +++ b/src/sql/base/browser/ui/table/formatters.ts @@ -94,6 +94,9 @@ export function slickGridDataItemColumnValueWithNoData(value: any, columnDef: an if (typeof displayValue === 'number') { displayValue = displayValue.toString(); } + if (displayValue instanceof Array) { + displayValue = displayValue.toString(); + } return { text: displayValue, ariaLabel: displayValue ? escape(displayValue) : ((displayValue !== undefined) ? localize("tableCell.NoDataAvailable", "no data available") : displayValue) diff --git a/src/sql/base/browser/ui/table/plugins/headerFilter.plugin.ts b/src/sql/base/browser/ui/table/plugins/headerFilter.plugin.ts index ff0f58fda0..ba8e79da8e 100644 --- a/src/sql/base/browser/ui/table/plugins/headerFilter.plugin.ts +++ b/src/sql/base/browser/ui/table/plugins/headerFilter.plugin.ts @@ -15,9 +15,9 @@ export interface IExtendedColumn extends Slick.Column { } export interface CommandEventArgs { - grid: Slick.Grid, - column: Slick.Column, - command: string + grid: Slick.Grid, + column: Slick.Column, + command: string } export class HeaderFilter { @@ -337,55 +337,50 @@ export class HeaderFilter { } private getFilterValues(dataView: Slick.DataProvider, column: Slick.Column): Array { - const seen: Array = []; - for (let i = 0; i < dataView.getLength(); i++) { - const value = dataView.getItem(i)[column.field!]; + const seen: Set = new Set(); + dataView.getItems().forEach(items => { + const value = items[column.field!]; + const valueArr = value instanceof Array ? value : [value]; + valueArr.forEach(v => seen.add(v)); + }); - if (!seen.some(x => x === value)) { - seen.push(value); - } - } - return seen; + return Array.from(seen); } private getFilterValuesByInput($input: JQuery): Array { const column = $input.data('column'), filter = $input.val() as string, dataView = this.grid.getData() as Slick.DataProvider, - seen: Array = []; - - for (let i = 0; i < dataView.getLength(); i++) { - const value = dataView.getItem(i)[column.field]; + seen: Set = new Set(); + dataView.getItems().forEach(item => { + const value = item[column.field]; + const valueArr = value instanceof Array ? value : [(!value ? '' : value)]; if (filter.length > 0) { - const itemValue = !value ? '' : value; const lowercaseFilter = filter.toString().toLowerCase(); - const lowercaseVal = itemValue.toString().toLowerCase(); - if (!seen.some(x => x === value) && lowercaseVal.indexOf(lowercaseFilter) > -1) { - seen.push(value); - } + valueArr.map(v => v.toLowerCase()).forEach((lowerVal, index) => { + if (lowerVal.indexOf(lowercaseFilter) > -1) { + seen.add(valueArr[index]); + } + }); + } else { + valueArr.forEach(v => seen.add(v)); } - else { - if (!seen.some(x => x === value)) { - seen.push(value); - } - } - } + }); - return seen.sort((v) => { return v; }); + return Array.from(seen).sort((v) => { return v; }); } private getAllFilterValues(data: Array, column: Slick.Column) { - const seen: Array = []; - for (let i = 0; i < data.length; i++) { - const value = data[i][column.field!]; + const seen: Set = new Set(); - if (!seen.some(x => x === value)) { - seen.push(value); - } - } + data.forEach(items => { + const value = items[column.field!]; + const valueArr = value instanceof Array ? value : [value]; + valueArr.forEach(v => seen.add(v)); + }); - return seen.sort((v) => { return v; }); + return Array.from(seen).sort((v) => { return v; }); } private handleMenuItemClick(e: JQuery.Event, command: string, columnDef: Slick.Column) { diff --git a/src/sql/workbench/browser/modelComponents/table.component.ts b/src/sql/workbench/browser/modelComponents/table.component.ts index 5f5383fc44..9c26578426 100644 --- a/src/sql/workbench/browser/modelComponents/table.component.ts +++ b/src/sql/workbench/browser/modelComponents/table.component.ts @@ -15,7 +15,7 @@ import { ComponentBase } from 'sql/workbench/browser/modelComponents/componentBa import { Table } from 'sql/base/browser/ui/table/table'; import { TableDataView } from 'sql/base/browser/ui/table/tableDataView'; -import { attachTableStyler } from 'sql/platform/theme/common/styler'; +import { attachTableStyler, attachButtonStyler } from 'sql/platform/theme/common/styler'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { getContentHeight, getContentWidth, Dimension } from 'vs/base/browser/dom'; import { RowSelectionModel } from 'sql/base/browser/ui/table/plugins/rowSelectionModel.plugin'; @@ -29,6 +29,7 @@ import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } fro import { convertSizeToNumber } from 'sql/base/browser/dom'; import { ButtonColumn, ButtonClickEventArgs } from 'sql/base/browser/ui/table/plugins/buttonColumn.plugin'; import { createIconCssClass } from 'sql/workbench/browser/modelComponents/iconUtils'; +import { HeaderFilter } from 'sql/base/browser/ui/table/plugins/headerFilter.plugin'; export enum ColumnSizingMode { ForceFit = 0, // all columns will be sized to fit in viewable space, no horiz scroll bar @@ -51,6 +52,7 @@ export default class TableComponent extends ComponentBase[] = []; private _buttonsColumns: ButtonColumn<{}>[] = []; private _pluginsRegisterStatus: boolean[] = []; + private _filterPlugin: HeaderFilter; private _onCheckBoxChanged = new Emitter(); private _onButtonClicked = new Emitter>(); public readonly onCheckBoxChanged: vsEvent = this._onCheckBoxChanged.event; @@ -133,7 +135,30 @@ export default class TableComponent extends ComponentBase(); + this._tableData = new TableDataView( + null, + null, + null, + (data: Slick.SlickData[]) => { + let columns = this._table.grid.getColumns(); + + for (let i = 0; i < columns.length; i++) { + let col: any = columns[i]; + let filterValues: Array = col.filterValues; + if (filterValues && filterValues.length > 0) { + return data.filter(item => { + let colValue = item[col.field]; + if (colValue instanceof Array) { + return filterValues.find(x => colValue.indexOf(x) >= 0); + } + return filterValues.find(x => x === colValue); + }); + } + } + + return data; + } + ); let options = >{ syncColumnCellResize: true, @@ -249,7 +274,9 @@ export default class TableComponent extends ComponentBase this.registerPlugins(col, this._checkboxColumns[col])); Object.keys(this._buttonsColumns).forEach(col => this.registerPlugins(col, this._buttonsColumns[col])); - + if (this.headerFilter === true) { + this.registerFilterPlugin(); + } if (this.ariaRowCount === -1) { this._table.removeAriaRowCount(); } @@ -356,6 +383,35 @@ export default class TableComponent extends ComponentBase(); + this._register(attachButtonStyler(filterPlugin, this.themeService)); + this._filterPlugin = filterPlugin; + this._filterPlugin.onFilterApplied.subscribe((e, args) => { + let filterValues = (args).column.filterValues; + if (filterValues) { + this._tableData.filter(); + this._table.grid.resetActiveCell(); + this.layoutTable(); + } else { + this._tableData.clearFilter(); + } + }); + + this._filterPlugin.onCommand.subscribe((e, args: any) => { + this._tableData.sort({ + sortAsc: args.command === 'sort-asc', + sortCol: args.column, + multiColumnSort: false, + grid: this._table.grid + }); + this.layoutTable(); + }); + + this._table.registerPlugin(filterPlugin); + } + public focus(): void { if (this._table.grid.getDataLength() > 0) { if (!this._table.grid.getActiveCell()) { @@ -426,4 +482,8 @@ export default class TableComponent extends ComponentBase((properties, value) => { properties.updateCells = value; }, newValue); } + + public get headerFilter(): boolean { + return this.getPropertyOrDefault((props) => props.headerFilter, false); + } }