diff --git a/package.json b/package.json index e12316e4ef..2a48ef6271 100755 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "rxjs": "5.4.0", "sanitize-html": "1.19.1", "semver-umd": "^5.5.7", - "slickgrid": "github:Microsoft/SlickGrid.ADS#2.3.36", + "slickgrid": "github:Microsoft/SlickGrid.ADS#2.3.37", "spdlog": "^0.13.0", "sudo-prompt": "9.2.1", "tas-client-umd": "0.1.4", diff --git a/remote/package.json b/remote/package.json index befca5cf1d..7e84be5f55 100755 --- a/remote/package.json +++ b/remote/package.json @@ -40,7 +40,7 @@ "sanitize-html": "1.19.1", "semver-umd": "^5.5.7", "spdlog": "^0.13.0", - "slickgrid": "github:Microsoft/SlickGrid.ADS#2.3.36", + "slickgrid": "github:Microsoft/SlickGrid.ADS#2.3.37", "turndown": "^7.0.0", "turndown-plugin-gfm": "^1.0.2", "tas-client-umd": "0.1.4", diff --git a/remote/web/package.json b/remote/web/package.json index 8b17a27dbc..17846a39d7 100755 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -30,7 +30,7 @@ "rxjs": "5.4.0", "sanitize-html": "1.19.1", "semver-umd": "^5.5.7", - "slickgrid": "github:Microsoft/SlickGrid.ADS#2.3.36", + "slickgrid": "github:Microsoft/SlickGrid.ADS#2.3.37", "turndown": "^7.0.0", "turndown-plugin-gfm": "^1.0.2", "tas-client-umd": "0.1.4", diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index e20b9497be..55183e7089 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -416,9 +416,9 @@ semver-umd@^5.5.7: resolved "https://registry.yarnpkg.com/semver-umd/-/semver-umd-5.5.7.tgz#966beb5e96c7da6fbf09c3da14c2872d6836c528" integrity sha512-XgjPNlD0J6aIc8xoTN6GQGwWc2Xg0kq8NzrqMVuKG/4Arl6ab1F8+Am5Y/XKKCR+FceFr2yN/Uv5ZJBhRyRqKg== -"slickgrid@github:Microsoft/SlickGrid.ADS#2.3.36": - version "2.3.36" - resolved "https://codeload.github.com/Microsoft/SlickGrid.ADS/tar.gz/08f1ca3f6eb2cd1ea7f56e2af4b69600d48e1932" +"slickgrid@github:Microsoft/SlickGrid.ADS#2.3.37": + version "2.3.37" + resolved "https://codeload.github.com/Microsoft/SlickGrid.ADS/tar.gz/1de979b3cf66cee46846e5e0d2edbc938c8d6563" source-map@^0.6.1: version "0.6.1" diff --git a/remote/yarn.lock b/remote/yarn.lock index 156f6337bd..283cede2a7 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -739,9 +739,9 @@ semver@^5.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== -"slickgrid@github:Microsoft/SlickGrid.ADS#2.3.36": - version "2.3.36" - resolved "https://codeload.github.com/Microsoft/SlickGrid.ADS/tar.gz/08f1ca3f6eb2cd1ea7f56e2af4b69600d48e1932" +"slickgrid@github:Microsoft/SlickGrid.ADS#2.3.37": + version "2.3.37" + resolved "https://codeload.github.com/Microsoft/SlickGrid.ADS/tar.gz/1de979b3cf66cee46846e5e0d2edbc938c8d6563" smart-buffer@^4.1.0: version "4.1.0" diff --git a/src/sql/base/browser/ui/table/plugins/checkboxSelectColumn.plugin.ts b/src/sql/base/browser/ui/table/plugins/checkboxSelectColumn.plugin.ts index 45ae831446..4c332be64a 100644 --- a/src/sql/base/browser/ui/table/plugins/checkboxSelectColumn.plugin.ts +++ b/src/sql/base/browser/ui/table/plugins/checkboxSelectColumn.plugin.ts @@ -1,14 +1,15 @@ -// Adopted and converted to typescript from https://github.com/6pac/SlickGrid/blob/master/plugins/slick.checkboxselectcolumn.js +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ -import { mixin } from 'vs/base/common/objects'; -import * as nls from 'vs/nls'; -import { ICheckboxStyles } from 'vs/base/browser/ui/checkbox/checkbox'; -import { Emitter, Event as vsEvent } from 'vs/base/common/event'; -import * as strings from 'vs/base/common/strings'; +import { ICheckboxStyles } from 'sql/base/browser/ui/checkbox/checkbox'; +import { mixin } from 'sql/base/common/objects'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { Emitter, Event as vsEvent } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { range } from 'vs/base/common/arrays'; -import * as dict from 'vs/base/common/collections'; +import 'vs/css!./media/checkboxSelectColumn.plugin'; +import * as nls from 'vs/nls'; export interface ICheckboxSelectColumnOptions extends Slick.PluginOptions, ICheckboxStyles { columnId?: string; @@ -21,18 +22,18 @@ export interface ICheckboxSelectColumnOptions extends Slick.PluginOptions, IChec actionOnCheck?: ActionOnCheck; } -// Actions expected on checkbox click -export enum ActionOnCheck { - selectRow = 0, - customAction = 1 -} - export interface ICheckboxCellActionEventArgs { checked: boolean; row: number; column: number; } +// Actions expected on checkbox click +export enum ActionOnCheck { + selectRow = 0, + customAction = 1 +} + interface ICheckboxColumnValue { enabled: boolean; checked: boolean; @@ -42,205 +43,33 @@ const HeaderCheckboxTitle: string = nls.localize('selectDeselectAll', "Select/De const defaultOptions: ICheckboxSelectColumnOptions = { columnId: '_checkbox_selector', - cssClass: undefined, - headerCssClass: undefined, + cssClass: 'slick-plugin-checkbox-select-column', + headerCssClass: 'slick-plugin-checkbox-select-column', toolTip: undefined, width: 30 }; -const checkboxTemplate = `
- -
`; - export class CheckboxSelectColumn implements Slick.Plugin { - private _options: ICheckboxSelectColumnOptions; + private _grid!: Slick.Grid; private _handler = new Slick.EventHandler(); - private _selectedRowsLookup: dict.INumberDictionary = {}; - private _selectedCheckBoxLookup: { [key: string]: boolean } = {}; - private _useState = false; - + private _options: ICheckboxSelectColumnOptions; + public index: number; + private _headerCheckbox: HTMLInputElement; private _onChange = new Emitter(); public readonly onChange: vsEvent = this._onChange.event; - public index: number; constructor(options?: ICheckboxSelectColumnOptions, columnIndex?: number) { this._options = mixin(options, defaultOptions, false); + this._options.headerCssClass = (this._options.headerCssClass === undefined) ? this._options.headerCssClass + ' ' + defaultOptions.headerCssClass : defaultOptions.headerCssClass; + this._options.cssClass = (this._options.cssClass === undefined) ? this._options.cssClass + ' ' + defaultOptions.cssClass : defaultOptions.cssClass; this.index = columnIndex ? columnIndex : 0; } - public init(grid: Slick.Grid): void { - this._grid = grid; - this._handler - .subscribe(this._grid.onSelectedRowsChanged, (e: Event, args: Slick.OnSelectedRowsChangedEventArgs) => this.handleSelectedRowsChanged(e, args)) - .subscribe(this._grid.onClick, (e: DOMEvent, args: Slick.OnClickEventArgs) => this.handleClick(e as MouseEvent, args)) - .subscribe(this._grid.onHeaderClick, (e: DOMEvent, args: Slick.OnHeaderClickEventArgs) => this.handleHeaderClick(e as MouseEvent, args)) - .subscribe(this._grid.onKeyDown, (e: DOMEvent, args: Slick.OnKeyDownEventArgs) => this.handleKeyDown(e as KeyboardEvent, args)); - } - - public destroy(): void { - this._handler.unsubscribeAll(); - } - - private handleSelectedRowsChanged(e: Event, args: Slick.OnSelectedRowsChangedEventArgs): void { - if (this.isCustomActionRequested()) { - // do not assume anything for column based on row selection - // we can emit event here later if required. - return; - } - - const selectedRows = this._grid.getSelectedRows(); - let lookup: dict.INumberDictionary = {}, row: number, i: number; - for (i = 0; i < selectedRows.length; i++) { - row = selectedRows[i]; - lookup[row] = true; - if (lookup[row] !== this._selectedRowsLookup[row]) { - this._grid.invalidateRow(row); - delete this._selectedRowsLookup[row]; - } - } - dict.forEach(this._selectedRowsLookup, (e) => this._grid.invalidateRow(Number(e.key))); - this._selectedRowsLookup = lookup; - this._grid.render(); - - if (!this._options.title) { - // when no title is specified, show the select all/deselect all checkbox - const headerCheckboxChecked = selectedRows.length > 0 && selectedRows.length === this._grid.getDataLength(); - this._grid.updateColumnHeader(this._options.columnId!, this.getCheckboxHtml(headerCheckboxChecked, HeaderCheckboxTitle, true), this._options.toolTip); - } - } - - private handleKeyDown(e: KeyboardEvent, args: Slick.OnKeyDownEventArgs): void { - if (this._grid.getColumns()[args.cell] && this._grid.getColumns()[args.cell].id !== this._options.columnId - || !(this.getCheckboxPropertyValue(args.row).enabled) - ) { - return; - } - - const event = new StandardKeyboardEvent(e); - let handled = false; - if (event.equals(KeyCode.Space)) { - // if editing, try to commit - if (!this._grid.getEditorLock().isActive() || this._grid.getEditorLock().commitCurrentEdit()) { - if (this.isCustomActionRequested()) { - this.toggleCheckBox(args.row, args.cell, true); - } - else { - this.toggleRowSelection(args.row); - } - } - handled = true; - } else if (event.equals(KeyCode.Enter)) { - if (this.isCustomActionRequested()) { - this.toggleCheckBox(args.row, args.cell, true); - } - else { - this.toggleRowSelection(args.row); - } - handled = true; - } - if (handled) { - e.preventDefault(); - e.stopPropagation(); - } - } - - private handleClick(e: Event, args: Slick.OnClickEventArgs): void { - // clicking on a row select checkbox - if (this._grid.getColumns()[args.cell] && this._grid.getColumns()[args.cell].id === this._options.columnId && jQuery(e.target!).is('input[type="checkbox"]')) { - // if editing, try to commit - if (this._grid.getEditorLock().isActive() && !this._grid.getEditorLock().commitCurrentEdit()) { - e.preventDefault(); - e.stopImmediatePropagation(); - return; - } - - if (this.isCustomActionRequested()) { - this.toggleCheckBox(args.row, args.cell, false); - } - else { - this.toggleRowSelection(args.row); - } - e.stopPropagation(); - e.stopImmediatePropagation(); - } - } - - private toggleRowSelection(row: number): void { - if (this._selectedRowsLookup[row]) { - this._grid.setSelectedRows(this._grid.getSelectedRows().filter(n => n !== row)); - } else { - this._grid.setSelectedRows(this._grid.getSelectedRows().concat(row)); - } - } - - private toggleCheckBox(row: number, col: number, reRender: boolean): void { - this._useState = true; - - if (this._selectedCheckBoxLookup[row]) { - delete this._selectedCheckBoxLookup[row]; - this._onChange.fire({ checked: false, row: row, column: col }); - } else { - this._selectedCheckBoxLookup[row] = true; - this._onChange.fire({ checked: true, row: row, column: col }); - } - - if (reRender) { - // ensure that grid reflects the change - this._grid.invalidateRow(row); - this._grid.render(); - } - - //Ensure that the focus stays on current selected checkbox cell - this._grid.setActiveCell(row, col); - if (this._grid.getActiveCellNode()) { - this._grid.getActiveCellNode().focus(); - } - - // set selected row to the row of this checkbox - this._grid.setSelectedRows([row]); - } - - // This call is to handle reactive changes in check box UI - // This DOES NOT fire UI change Events - reactiveCheckboxCheck(row: number, value: boolean) { - value ? this._selectedCheckBoxLookup[row] = true : delete this._selectedCheckBoxLookup[row]; - - // update row to call formatter - this._grid.updateRow(row); - - // ensure that grid reflects the change - this._grid.scrollRowIntoView(row); - } - - private handleHeaderClick(e: Event, args: Slick.OnHeaderClickEventArgs): void { - if (this.isCustomActionRequested()) { - // do not assume action for column based on header click. - // we can emit event here later if required. - return; - } - if (!this._options.title && args.column.id === this._options.columnId && jQuery(e.target!).is('input[type="checkbox"]')) { - // if editing, try to commit - if (this._grid.getEditorLock().isActive() && !this._grid.getEditorLock().commitCurrentEdit()) { - e.preventDefault(); - e.stopImmediatePropagation(); - return; - } - - const headerCheckboxChecked = jQuery(e.target!).is(':checked'); - this._grid.setSelectedRows(headerCheckboxChecked ? range(this._grid.getDataLength()) : []); - this._grid.updateColumnHeader(this._options.columnId!, - this.getCheckboxHtml(headerCheckboxChecked, this._options.toolTip), - this._options.toolTip); - e.preventDefault(); - e.stopPropagation(); - } - } - public get definition(): Slick.Column { return { id: this._options.columnId, - name: this._options.title || strings.format(checkboxTemplate, '', ''), + name: this._options.title || ``, toolTip: this._options.toolTip, field: 'sel', width: this._options.width, @@ -253,43 +82,127 @@ export class CheckboxSelectColumn implements Slick.Pl } private checkboxSelectionFormatter(row: number, cell: number, value: any, columnDef: Slick.Column, dataContext: T): string { - if (this.isCustomActionRequested()) { - return this.checkboxTemplateCustom(row); - } - - // If checkbox is a row selector, we don't have requirement to enable/disable it, so always leave it enabled - return this.getCheckboxHtml(this._selectedRowsLookup[row], this._options.title, true); + const state = this.getCheckboxPropertyValue(row); + const checked = state.checked ? 'checked' : ''; + const enable = state.enabled ? '' : 'disabled'; + return ``; } - checkboxTemplateCustom(row: number): string { - const propertyValue = this.getCheckboxPropertyValue(row); - // use state after toggles - if (this._useState) { - return this.getCheckboxHtml(this._selectedCheckBoxLookup[row], this._options.title, propertyValue.enabled); - } - // use data for first time rendering - // note: make sure Init is called before using this._grid - if (propertyValue.checked) { - this._selectedCheckBoxLookup[row] = true; - } - else { - delete this._selectedCheckBoxLookup[row]; - } - return this.getCheckboxHtml(propertyValue.checked, this._options.title, propertyValue.enabled); + public init(grid: Slick.Grid): void { + this._grid = grid; + this._handler + .subscribe(this._grid.onClick, (e: Event, args: Slick.OnClickEventArgs) => this.handleClick(e, args)) + .subscribe(this._grid.onKeyDown, (e: DOMEvent, args: Slick.OnKeyDownEventArgs) => this.handleKeyDown(e as KeyboardEvent, args)) + .subscribe(this._grid.onHeaderClick, (e: Event, args: Slick.OnHeaderClickEventArgs) => this.handleHeaderClick(e, args)) + .subscribe(this._grid.onHeaderCellRendered, (e: Event, args: Slick.OnHeaderCellRenderedEventArgs) => this.handleHeaderCellRendered(e, args)); } - private isCustomActionRequested(): boolean { - return (this._options.actionOnCheck === ActionOnCheck.customAction); + private handleClick(e: DOMEvent, args: Slick.OnClickEventArgs): void { + if (args.cell !== this.index) { + return; + } + this.toggleCellCheckbox(args.row); + e.stopPropagation(); + e.stopImmediatePropagation(); + e.preventDefault(); } - private getCheckboxHtml(checked: boolean, title: string, enabled: boolean = true): string { - return strings.format(checkboxTemplate, checked ? 'checked' : '', title, enabled ? '' : 'disabled'); + private handleKeyDown(e: KeyboardEvent, args: Slick.OnKeyDownEventArgs): void { + const event = new StandardKeyboardEvent(e); + if (args.cell !== this.index) { + return; + } + if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { + this.toggleCellCheckbox(args.row); + e.stopPropagation(); + e.stopImmediatePropagation(); + e.preventDefault(); + } + } + + private toggleCellCheckbox(row: number): void { + const currentValue = this.getCheckboxPropertyValue(row); + this.setCheckboxPropertyValue(row, !currentValue.checked); + this._grid.invalidateRow(row); + this._grid.render(); + this._grid.setActiveCell(row, this.index); + this.checkSelectAll(); + if(this._options.actionOnCheck === ActionOnCheck.selectRow){ + this.updateSelectedRows(); + } else { + this._onChange.fire({ checked: false, row: row, column: this.index }); + } + } + + private updateSelectedRows(): void { + const checkedRows = []; + const rows = this._grid.getDataLength(); + for (let i = 0; i < rows; i++) { + if (this.getCheckboxPropertyValue(i).checked) { + checkedRows.push(i); + } + } + this._grid.setSelectedRows(checkedRows); + } + + private handleHeaderClick(e: Event, args?: Slick.OnHeaderClickEventArgs): void { + this.onHeaderCheckboxStateChange(); + e.preventDefault(); + e.stopPropagation(); + } + + private handleHeaderKeyDown(e: KeyboardEvent): void { + const event = new StandardKeyboardEvent(e); + if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { + this._headerCheckbox.checked = !this._headerCheckbox.checked; + this.onHeaderCheckboxStateChange(); + this._headerCheckbox.focus(); + event.preventDefault(); + event.stopPropagation(); + } + } + + public onHeaderCheckboxStateChange(): void { + const rows = this._grid.getDataLength(); + for (let i = 0; i < rows; i++) { + this.setCheckboxPropertyValue(i, this._headerCheckbox.checked); + } + + this._grid.updateColumnHeader(this._options.columnId!, ``, this._options.toolTip); + if(this._options.actionOnCheck === ActionOnCheck.selectRow){ + this.updateSelectedRows(); + } + this._grid.invalidateAllRows(); + this._grid.render(); + } + + private handleHeaderCellRendered(e: Event, args: Slick.OnHeaderCellRenderedEventArgs): void { + if (args.column.id === this._options.columnId) { + this._headerCheckbox = args.node.firstChild.firstChild; + this._headerCheckbox.onkeydown = (e) => this.handleHeaderKeyDown(e); + } + } + + private checkSelectAll(): void { + const rows = this._grid.getDataLength(); + let checked = true; + for (let i = 0; i < rows; i++) { + if (!this.getCheckboxPropertyValue(i).checked) { + checked = false; + break; + } + } + this._headerCheckbox.checked = checked; + } + + public destroy(): void { + this._handler.unsubscribeAll(); } private getCheckboxPropertyValue(row: number): ICheckboxColumnValue { const dataItem = this._grid?.getDataItem(row); - const propertyValue = (dataItem && this._options.title) ? dataItem[this._options.title] : undefined; + const propertyValue = dataItem[this._options.title]; let checkboxEnabled: boolean = true; let checkboxChecked: boolean = false; if (typeof propertyValue === 'boolean') { @@ -305,4 +218,17 @@ export class CheckboxSelectColumn implements Slick.Pl enabled: checkboxEnabled }; } + + private setCheckboxPropertyValue(row: number, value: boolean): void { + const dataItem = this._grid?.getDataItem(row); + const propertyValue = dataItem[this._options.title]; + if (typeof propertyValue === 'boolean') { + (dataItem)[this._options.title] = value; + } else { + (dataItem)[this._options.title] = { + checked: value, + enabled: propertyValue.enabled + }; + } + } } diff --git a/src/sql/base/browser/ui/table/plugins/media/checkboxSelectColumn.plugin.css b/src/sql/base/browser/ui/table/plugins/media/checkboxSelectColumn.plugin.css new file mode 100644 index 0000000000..ec50c0084a --- /dev/null +++ b/src/sql/base/browser/ui/table/plugins/media/checkboxSelectColumn.plugin.css @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.slick-header-columns .slick-header-column.slick-plugin-checkbox-select-column.ui-state-default, +.slick-plugin-checkbox-select-column { + text-align: center; + border-bottom: 0px; + border-right: 0px; +} + +.slick-plugin-checkbox-select-column > input, +.slick-column-name > input[type='checkbox']{ + margin: 0px; + vertical-align: middle; +} diff --git a/src/sql/base/browser/ui/table/table.ts b/src/sql/base/browser/ui/table/table.ts index 83b9660318..a3da53007f 100644 --- a/src/sql/base/browser/ui/table/table.ts +++ b/src/sql/base/browser/ui/table/table.ts @@ -174,6 +174,8 @@ export class Table extends Widget implements IDisposa if (this._autoscroll) { this._grid.scrollRowIntoView(this._data.getLength() - 1, false); } + this.ariaRowCount = this.grid.getDataLength(); + this.ariaColumnCount = this.grid.getColumns().length; } set columns(columns: Slick.Column[]) { @@ -194,6 +196,7 @@ export class Table extends Widget implements IDisposa this._data = new TableDataView(data); } this._grid.setData(this._data, true); + this.updateRowCount(); } getData(): IDisposableDataProvider { diff --git a/src/sql/workbench/browser/modelComponents/table.component.ts b/src/sql/workbench/browser/modelComponents/table.component.ts index a42795cbe1..2784b219fc 100644 --- a/src/sql/workbench/browser/modelComponents/table.component.ts +++ b/src/sql/workbench/browser/modelComponents/table.component.ts @@ -277,7 +277,7 @@ export default class TableComponent extends ComponentBase(this._inputContainer.nativeElement, { dataProvider: this._tableData, columns: this._tableColumns }, options); this._table.setData(this._tableData); - this._table.setSelectionModel(new RowSelectionModel({ selectActiveRow: true })); + this._table.setSelectionModel(new RowSelectionModel({ selectActiveRow: false })); this._register(this._table); this._register(attachTableStyler(this._table, this.themeService)); diff --git a/yarn.lock b/yarn.lock index 9c3420ef57..5f677a9673 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9483,9 +9483,9 @@ slice-ansi@^2.1.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" -"slickgrid@github:Microsoft/SlickGrid.ADS#2.3.36": - version "2.3.36" - resolved "https://codeload.github.com/Microsoft/SlickGrid.ADS/tar.gz/08f1ca3f6eb2cd1ea7f56e2af4b69600d48e1932" +"slickgrid@github:Microsoft/SlickGrid.ADS#2.3.37": + version "2.3.37" + resolved "https://codeload.github.com/Microsoft/SlickGrid.ADS/tar.gz/1de979b3cf66cee46846e5e0d2edbc938c8d6563" smart-buffer@^4.1.0: version "4.1.0"