EditDataTest Merge (removal of angular in edit data) (#8951)

* another commmit

* Now shows blank grid, nullcheck in queryhistory

* renamed onAngularLoaded to onComponentLoaded

* removed whitespace

* removed unused dataservice import

* now displays data, need to fix contextmenu actions

* minor changes

* another small commit

* added timeout for context menu

* updated queryhistoryserviceimpl

* removed log

* added commented out contextmenuregistrations

* context menu now shows up need to test

* added plugin registration WIP

* another commit

* yet another commit

* added wip function

* Clean up commit

* more cleaning up

* removed accessor

* renamed instances of parts

* updated

* fixed merge conflicts

* refactored bootstrapparams

* fixed code

* small changes to format

* set editable to true for testing

* added more options

* moved options to separate variable

* added texteditorclass for later

* added rudimentary create editor support

* changed grid.resize.emit to fire

* added formatterfactory

* added tslint disable

* removed debug message

* added more functions from Slickgrid.ts

* added wip handlechanges function

* another change

* added columndefinitions

* Managed to display table using handlechange

* added ability to edit for now

* added changes to table creation

* added setupevents

* added onInit

* fixed sql.xlf

* minor changes

* tidying up

* more cleaning up

* changed console.log messages to debug ones.

* added this.enableEditing

* made changes to getoverridabletexteditor

* fixed opencontextmenu

* added timeout for detectChange

* need to find way to run oncontext asynchronously

* check stuff

* oncontextmenu now no longer constantly refreshes

* added oldDataRows for future use

* add check for datarows

* small changes made

* set enableediting to true

* more changes

* added additional information for handlechanges

* another change

* more changes

* set enableediting to true

* fixed rerender

* added small test mssage for jquery

* text editor is in getOverridableTextEditorClass()

* removed debug messages

* added transparency for input.editor for table.

* need to find out how to add editing for input

* Added grid div to make slickgrid style work

* reinstated selected.

* disabled selectedcellcssclass

* restored selected

* removed selectionmodel due to not being found in the original code

* Added externalSelectionModel for correct results

* removed selectionmodel as its not used.

* WIP work on refreshresultsets

* temporarily bringing back selection model for now

* added getSelectedRanges from slickgrid into Table

* added getselectedranges from slickgrid into table

* small cleanup changes

* removed detectchanges

* removed last of detectchanges

* return of toprownumber

* no need for toprownumber

* removed isColumnLoading

* some small formatting

* fixed null check

* added back todo comment

* Added fix for context menu

* small change

* added missing value to getFormatter in grid panel

* added fix for last row italics

* added fix for null inconsistencies

* Some consolidation

* added new check for null cells

* minor change

* add check for selections (usually undefined)

* removed null check in formatters

* Some changes made

* changed plugins array

* removed todo

* renamed some variables

* deleted html file

* Moved height and width to editData.css

* added box-sizing for slickgridcontainer

* fixed editdatagridpanel css

* added small changes

* More minor changes

* removed params

* renamed refreshResultsets to refreshDatasets

* removed the  stylesheet.remove lines

* added fix for null

* removed tables

* removed spaces in refreshGrid

* More minor changes

* optimization and formatting

* removal of unnecessary lines

* replaced firstRender in some parts with firstLoad

* Added timeout fix

* minor changes

* Still testing

* cleanup

* restored 200 timeout

* added styling changes for editdata

* removed angular2-slickgrid and added styling

* Small formatting changes to editDataGridPanel

* consolidation
This commit is contained in:
Alex Ma
2020-01-28 14:24:39 -08:00
committed by GitHub
parent 461dd79bc2
commit 32a89676f1
18 changed files with 501 additions and 262 deletions

View File

@@ -1,33 +0,0 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div class="fullsize vertBox editdata-component">
<div id="results" *ngIf="renderedDataSets.length > 0" class="results vertBox scrollable"
(onScroll)="onScroll($event)" [class.hidden]="!resultActive">
<div class="boxRow content horzBox slickgrid editable" *ngFor="let dataSet of renderedDataSets; let i = index"
[style.max-height]="dataSet.maxHeight" [style.min-height]="dataSet.minHeight">
<slick-grid #slickgrid id="slickgrid_{{i}}" [columnDefinitions]="dataSet.columnDefinitions"
[ngClass]="i === activeGrid ? 'active' : ''"
[dataRows]="dataSet.dataRows"
enableAsyncPostRender="true"
showDataTypeIcon="false"
showHeader="true"
[resized]="dataSet.resized"
[plugins]="plugins[i]"
(onActiveCellChanged)="onActiveCellChanged($event)"
(onCellChange)="onCellEditEnd($event)"
(onContextMenu)="openContextMenu($event, dataSet.batchId, dataSet.resultId, i)"
(onRendered)="onGridRendered($event)"
[isCellEditValid]="onIsCellEditValid"
[overrideCellFn]="overrideCellFn"
[onBeforeAppendCell]="onBeforeAppendCell"
[enableEditing]="true"
class="boxCol content vertBox slickgrid">
</slick-grid>
</div>
</div>
</div>

View File

@@ -1,57 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ApplicationRef, ComponentFactoryResolver, NgModule, Inject, forwardRef, Type } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { SlickGrid } from 'angular2-slickgrid';
import { EditDataComponent } from 'sql/workbench/contrib/editData/browser/editData.component';
import { providerIterator } from 'sql/workbench/services/bootstrap/browser/bootstrapService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IBootstrapParams, ISelector } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
export const EditDataModule = (params: IBootstrapParams, selector: string, instantiationService: IInstantiationService): Type<any> => {
@NgModule({
imports: [
CommonModule,
BrowserModule
],
declarations: [
EditDataComponent,
SlickGrid
],
entryComponents: [
EditDataComponent
],
providers: [
{ provide: IBootstrapParams, useValue: params },
{ provide: ISelector, useValue: selector },
...providerIterator(instantiationService)
]
})
class ModuleClass {
constructor(
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
@Inject(ISelector) private selector: string
) {
}
ngDoBootstrap(appRef: ApplicationRef) {
const factory = this._resolver.resolveComponentFactory(EditDataComponent);
(<any>factory).factory.selector = this.selector;
appRef.bootstrap(factory);
}
}
return ModuleClass;
};

View File

@@ -5,12 +5,11 @@
import 'vs/css!./media/editData';
import { ElementRef, ChangeDetectorRef, OnInit, OnDestroy, Component, Inject, forwardRef, EventEmitter } from '@angular/core';
import { VirtualizedCollection } from 'angular2-slickgrid';
import { VirtualizedCollection, AsyncDataProvider, ISlickColumn } from 'sql/base/browser/ui/table/asyncDataView';
import { Table } from 'sql/base/browser/ui/table/table';
import { IGridDataSet } from 'sql/workbench/contrib/grid/common/interfaces';
import * as Services from 'sql/base/browser/ui/table/formatters';
import { IEditDataComponentParams, IBootstrapParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
import { GridParentComponent } from 'sql/workbench/contrib/editData/browser/gridParentComponent';
import { EditDataGridActionProvider } from 'sql/workbench/contrib/editData/browser/editDataGridActions';
import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
@@ -18,7 +17,7 @@ import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColu
import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin';
import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin';
import { escape } from 'sql/base/common/strings';
import { DataService } from 'sql/workbench/contrib/grid/common/dataService';
import { INotificationService } from 'vs/platform/notification/common/notification';
import Severity from 'vs/base/common/severity';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -32,14 +31,10 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { EditUpdateCellResult } from 'azdata';
import { ILogService } from 'vs/platform/log/common/log';
import { deepClone, assign } from 'vs/base/common/objects';
export const EDITDATA_SELECTOR: string = 'editdata-component';
import { Emitter, Event } from 'vs/base/common/event';
import { equals } from 'vs/base/common/arrays';
@Component({
selector: EDITDATA_SELECTOR,
host: { '(window:keydown)': 'keyEvent($event)', '(window:gridnav)': 'keyEvent($event)' },
templateUrl: decodeURI(require.toUrl('./editData.component.html'))
})
export class EditDataComponent extends GridParentComponent implements OnInit, OnDestroy {
export class EditDataGridPanel extends GridParentComponent {
// The time(in milliseconds) we wait before refreshing the grid.
// We use clearTimeout and setTimeout pair to avoid unnecessary refreshes.
private refreshGridTimeoutInMs = 200;
@@ -53,8 +48,12 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
// FIELDS
// All datasets
private gridDataProvider: AsyncDataProvider<any>;
private dataSet: IGridDataSet;
private oldDataRows: VirtualizedCollection<any>;
private firstRender = true;
private firstLoad = true;
private enableEditing = true;
// Current selected cell state
private currentCell: { row: number, column: number, isEditable: boolean, isDirty: boolean };
private currentEditCellValue: string;
@@ -62,8 +61,9 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
private removingNewRow: boolean;
private rowIdMappings: { [gridRowId: number]: number } = {};
private dirtyCells: number[] = [];
protected plugins = new Array<Array<Slick.Plugin<any>>>();
protected plugins = new Array<Slick.Plugin<any>>();
// List of column names with their indexes stored.
private columnNameToIndex: { [columnNumber: number]: string } = {};
// Edit Data functions
public onActiveCellChanged: (event: Slick.OnActiveCellChangedEventArgs<any>) => void;
public onCellEditEnd: (event: Slick.OnCellChangeEventArgs<any>) => void;
@@ -81,31 +81,33 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
};
constructor(
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(forwardRef(() => ChangeDetectorRef)) cd: ChangeDetectorRef,
@Inject(IBootstrapParams) params: IEditDataComponentParams,
@Inject(IInstantiationService) private instantiationService: IInstantiationService,
@Inject(INotificationService) private notificationService: INotificationService,
@Inject(IContextMenuService) contextMenuService: IContextMenuService,
@Inject(IKeybindingService) keybindingService: IKeybindingService,
@Inject(IContextKeyService) contextKeyService: IContextKeyService,
@Inject(IConfigurationService) configurationService: IConfigurationService,
@Inject(IClipboardService) clipboardService: IClipboardService,
@Inject(IQueryEditorService) queryEditorService: IQueryEditorService,
@Inject(ILogService) logService: ILogService
dataService: DataService,
onSaveViewState: Event<void>,
onRestoreViewState: Event<void>,
@IInstantiationService protected instantiationService: IInstantiationService,
@INotificationService protected notificationService: INotificationService,
@IContextMenuService protected contextMenuService: IContextMenuService,
@IKeybindingService protected keybindingService: IKeybindingService,
@IContextKeyService protected contextKeyService: IContextKeyService,
@IConfigurationService protected configurationService: IConfigurationService,
@IClipboardService protected clipboardService: IClipboardService,
@IQueryEditorService protected queryEditorService: IQueryEditorService,
@ILogService protected logService: ILogService
) {
super(el, cd, contextMenuService, keybindingService, contextKeyService, configurationService, clipboardService, queryEditorService, logService);
this._el.nativeElement.className = 'slickgridContainer';
this.dataService = params.dataService;
super(contextMenuService, keybindingService, contextKeyService, configurationService, clipboardService, queryEditorService, logService);
this.nativeElement = document.createElement('editdatagridpanel');
this.nativeElement.className = 'slickgridContainer';
this.dataService = dataService;
this.actionProvider = this.instantiationService.createInstance(EditDataGridActionProvider, this.dataService, this.onGridSelectAll(), this.onDeleteRow(), this.onRevertRow());
params.onRestoreViewState(() => this.restoreViewState());
params.onSaveViewState(() => this.saveViewState());
onRestoreViewState(() => this.restoreViewState());
onSaveViewState(() => this.saveViewState());
this.onInit();
}
/**
* Called by Angular when the object is initialized
* Called when the object is initialized
*/
ngOnInit(): void {
onInit(): void {
const self = this;
this.baseInit();
@@ -132,27 +134,29 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
this.logService.error('Unexpected query event type "' + event.type + '" sent');
break;
}
self._cd.detectChanges();
});
this.dataService.onLoaded();
}
this.dataService.onAngularLoaded();
public render(container: HTMLElement): void {
container.appendChild(this.nativeElement);
}
protected initShortcuts(shortcuts: { [name: string]: Function }): void {
// TODO add any Edit Data-specific shortcuts here
}
public ngOnDestroy(): void {
public onDestroy(): void {
this.baseDestroy();
}
handleStart(self: EditDataComponent, event: any): void {
handleStart(self: EditDataGridPanel, event: any): void {
self.dataSet = undefined;
self.oldDataRows = undefined;
self.placeHolderDataSets = [];
self.renderedDataSets = self.placeHolderDataSets;
this._cd.detectChanges();
// Hooking up edit functions
// Hooking up edit functions handle
this.onIsCellEditValid = (row, column, value): boolean => {
// TODO can only run sync code
return true;
@@ -322,14 +326,14 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
cellSelectTasks.catch(() => { });
}
handleComplete(self: EditDataComponent, event: any): void {
handleComplete(self: EditDataGridPanel, event: any): void {
}
handleEditSessionReady(self, event): void {
// TODO: update when edit session is ready
}
handleMessage(self: EditDataComponent, event: any): void {
handleMessage(self: EditDataGridPanel, event: any): void {
if (event.data && event.data.isError) {
self.notificationService.notify({
severity: Severity.Error,
@@ -338,7 +342,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
}
}
handleResultSet(self: EditDataComponent, event: any): void {
handleResultSet(self: EditDataGridPanel, event: any): void {
// Clone the data before altering it to avoid impacting other subscribers
let resultSet = assign({}, event.data);
if (!resultSet.complete) {
@@ -363,9 +367,9 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
minHeight: minHeight,
dataRows: new VirtualizedCollection(
self.windowSize,
index => { return {}; },
resultSet.rowCount,
this.loadDataFunction,
index => { return {}; }
),
columnDefinitions: [rowNumberColumn.getColumnDefinition()].concat(resultSet.columnInfo.map((c, i) => {
let columnIndex = (i + 1).toString();
@@ -378,14 +382,15 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
};
}))
};
self.plugins.push([rowNumberColumn, new AutoColumnSize({ maxWidth: this.configurationService.getValue<number>('resultsGrid.maxColumnWidth') }), new AdditionalKeyBindings()]);
self.plugins.push(rowNumberColumn, new AutoColumnSize({ maxWidth: this.configurationService.getValue<number>('resultsGrid.maxColumnWidth') }), new AdditionalKeyBindings());
self.dataSet = dataSet;
self.gridDataProvider = new AsyncDataProvider(dataSet.dataRows);
// Create a dataSet to render without rows to reduce DOM size
let undefinedDataSet = deepClone(dataSet);
undefinedDataSet.columnDefinitions = dataSet.columnDefinitions;
undefinedDataSet.dataRows = undefined;
undefinedDataSet.resized = new EventEmitter();
undefinedDataSet.resized = new Emitter();
self.placeHolderDataSets.push(undefinedDataSet);
self.refreshGrid();
@@ -395,6 +400,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
this.removingNewRow = false;
this.newRowVisible = false;
this.dirtyCells = [];
}
/**
@@ -414,34 +420,54 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
}
private refreshGrid(): Thenable<void> {
return new Promise<void>((resolve, reject) => {
return new Promise<void>(async (resolve, reject) => {
const self = this;
clearTimeout(self.refreshGridTimeoutHandle);
this.refreshGridTimeoutHandle = setTimeout(() => {
for (let i = 0; i < self.placeHolderDataSets.length; i++) {
self.placeHolderDataSets[i].dataRows = self.dataSet.dataRows;
self.placeHolderDataSets[i].resized.emit();
if (self.dataSet && self.placeHolderDataSets[0].resized) {
self.placeHolderDataSets[0].dataRows = self.dataSet.dataRows;
self.placeHolderDataSets[0].resized.fire();
}
self._cd.detectChanges();
if (self.oldDataRows !== self.placeHolderDataSets[0].dataRows) {
self.detectChange();
self.oldDataRows = self.placeHolderDataSets[0].dataRows;
}
if (self.firstRender) {
let setActive = function () {
if (self.firstRender && self.slickgrids.toArray().length > 0) {
self.slickgrids.toArray()[0].setActive();
if (self.firstRender && self.table) {
self.table.setActive();
self.firstRender = false;
}
};
setTimeout(() => {
setActive();
});
setTimeout(() => setActive());
}
resolve();
}, self.refreshGridTimeoutInMs);
});
}
protected detectChange(): void {
if (this.firstLoad) {
this.handleChanges({
['dataRows']: { currentValue: this.dataSet.dataRows, firstChange: this.firstLoad, previousValue: undefined },
['columnDefinitions']: { currentValue: this.dataSet.columnDefinitions, firstChange: this.firstLoad, previousValue: undefined }
});
this.handleInitializeTable();
this.firstLoad = false;
}
else {
this.table.setData(this.gridDataProvider);
this.handleChanges({
['dataRows']: { currentValue: this.dataSet.dataRows, firstChange: this.firstLoad, previousValue: this.oldDataRows }
});
}
}
protected tryHandleKeyEvent(e: StandardKeyboardEvent): boolean {
let handled: boolean = false;
@@ -452,6 +478,24 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
return handled;
}
/**
* Force re-rendering of the results grids. Calling this upon unhide (upon focus) fixes UI
* glitches that occur when a QueryResultsEditor is hidden then unhidden while it is running a query.
*/
refreshDatasets(): void {
let tempRenderedDataSets = this.renderedDataSets;
this.renderedDataSets = [];
this.handleChanges({
['dataRows']: { currentValue: undefined, firstChange: this.firstLoad, previousValue: this.dataSet.dataRows },
['columnDefinitions']: { currentValue: undefined, firstChange: this.firstLoad, previousValue: this.dataSet.columnDefinitions }
});
this.renderedDataSets = tempRenderedDataSets;
this.handleChanges({
['dataRows']: { currentValue: this.renderedDataSets[0].dataRows, firstChange: this.firstLoad, previousValue: undefined },
['columnDefinitions']: { currentValue: this.renderedDataSets[0].columnDefinitions, firstChange: this.firstLoad, previousValue: undefined }
});
}
// Private Helper Functions ////////////////////////////////////////////////////////////////////////////
private async revertCurrentRow(): Promise<void> {
@@ -537,7 +581,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
// Adds CSS classes to slickgrid cells to indicate a dirty state
private setCellDirtyState(row: number, column: number, dirtyState: boolean): void {
let slick: any = this.slickgrids.toArray()[0];
let slick: any = this.table;
let grid = slick._grid;
if (dirtyState) {
// Change cell color
@@ -555,7 +599,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
// Adds CSS classes to slickgrid rows to indicate a dirty state
private setRowDirtyState(row: number, dirtyState: boolean): void {
let slick: any = this.slickgrids.toArray()[0];
let slick: any = this.table;
let grid = slick._grid;
if (dirtyState) {
// Change row header color
@@ -593,10 +637,11 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
self.dataSet.minHeight = self.getMinHeight(self.dataSet.totalRows);
self.dataSet.dataRows = new VirtualizedCollection(
self.windowSize,
index => { return {}; },
self.dataSet.totalRows,
self.loadDataFunction,
index => { return {}; }
);
self.gridDataProvider = new AsyncDataProvider(self.dataSet.dataRows);
});
}
@@ -608,11 +653,11 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
this.dataSet.totalRows--;
this.dataSet.dataRows = new VirtualizedCollection(
this.windowSize,
index => { return {}; },
this.dataSet.totalRows,
this.loadDataFunction,
index => { return {}; }
);
this.gridDataProvider = new AsyncDataProvider(this.dataSet.dataRows);
// refresh results view
return this.refreshGrid().then(() => {
// Set focus to the row index column of the removed row if the current selection is in the removed row
@@ -624,25 +669,25 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
}
private focusCell(row: number, column: number, forceEdit: boolean = true): void {
let slick: any = this.slickgrids.toArray()[0];
let slick: any = this.table;
let grid = slick._grid;
grid.gotoCell(row, column, forceEdit);
}
private getMaxHeight(rowCount: number): any {
return rowCount < this._defaultNumShowingRows
? ((rowCount + 1) * this._rowHeight) + 10
return rowCount < this.defaultNumShowingRows
? ((rowCount + 1) * this.rowHeight) + 10
: 'inherit';
}
private getMinHeight(rowCount: number): any {
return rowCount > this._defaultNumShowingRows
? (this._defaultNumShowingRows + 1) * this._rowHeight + 10
return rowCount > this.defaultNumShowingRows
? (this.defaultNumShowingRows + 1) * this.rowHeight + 10
: this.getMaxHeight(rowCount);
}
private saveViewState(): void {
let grid = this.slickgrids.toArray()[0];
let grid = this.table;
let self = this;
if (grid) {
let gridSelections = grid.getSelectedRanges();
@@ -671,8 +716,8 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
private restoreViewState(): void {
if (this.savedViewState) {
this.slickgrids.toArray()[0].selection = this.savedViewState.gridSelections;
let viewport = ((this.slickgrids.toArray()[0] as any)._grid.getCanvasNode() as HTMLElement).parentElement;
// Row selections are undefined in original slickgrid, removed for no purpose
let viewport = ((this.table as any)._grid.getCanvasNode() as HTMLElement).parentElement;
viewport.scrollLeft = this.savedViewState.scrollLeft;
viewport.scrollTop = this.savedViewState.scrollTop;
this.savedViewState = undefined;
@@ -700,7 +745,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
}
private isCellOnScreen(row: number, column: number): boolean {
let slick: any = this.slickgrids.toArray()[0];
let slick: any = this.table;
let grid = slick._grid;
let viewport = grid.getViewport();
let cellBox = grid.getCellNodeBox(row, column);
@@ -733,6 +778,283 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
}
private createNewTable(): void {
let newGridContainer = document.createElement('div');
newGridContainer.className = 'grid';
if (this.placeHolderDataSets) {
let dataSet = this.placeHolderDataSets[0];
let options = {
enableCellNavigation: true,
enableColumnReorder: false,
renderRowWithRange: true,
showHeader: true,
rowHeight: this.rowHeight,
defaultColumnWidth: 120,
defaultFormatter: undefined,
editable: this.enableEditing,
autoEdit: this.enableEditing,
enableAddRow: false,
enableAsyncPostRender: false,
editorFactory: {
getEditor: (column: ISlickColumn<any>) => this.getColumnEditor(column)
}
};
if (dataSet.columnDefinitions) {
this.table = new Table(this.nativeElement.appendChild(newGridContainer), { dataProvider: this.gridDataProvider, columns: dataSet.columnDefinitions }, options);
for (let plugin of this.plugins) {
this.table.registerPlugin(plugin);
}
for (let i = 0; i < dataSet.columnDefinitions.length; i++) {
this.columnNameToIndex[this.dataSet.columnDefinitions[i].name] = i;
}
}
}
else {
this.table = new Table(this.nativeElement.appendChild(newGridContainer));
}
}
getOverridableTextEditorClass(): any {
let self = this;
class OverridableTextEditor {
private _textEditor: any;
public keyCaptureList: number[];
constructor(private _args: any) {
this._textEditor = new Slick.Editors.Text(_args);
const END = 35;
const HOME = 36;
// These are the special keys the text editor should capture instead of letting
// the grid handle them
this.keyCaptureList = [END, HOME];
}
destroy(): void {
this._textEditor.destroy();
}
focus(): void {
this._textEditor.focus();
}
getValue(): string {
return this._textEditor.getValue();
}
setValue(val): void {
this._textEditor.setValue(val);
}
loadValue(item, rowNumber): void {
if (self.overrideCellFn) {
let overrideValue = self.overrideCellFn(rowNumber, this._args.column.id, item[this._args.column.id]);
if (overrideValue !== undefined) {
item[this._args.column.id] = overrideValue;
}
}
this._textEditor.loadValue(item);
}
serializeValue(): string {
return this._textEditor.serializeValue();
}
applyValue(item, state): void {
let activeRow = self.currentCell.row;
let currentRow = self.dataSet.dataRows.at(activeRow);
let colIndex = self.getColumnIndex(this._args.column.name);
let dataLength: number = self.dataSet.dataRows.getLength();
// If this is not the "new row" at the very bottom
if (activeRow !== dataLength) {
currentRow[colIndex] = state;
this._textEditor.applyValue(item, state);
}
}
isValueChanged(): boolean {
return this._textEditor.isValueChanged();
}
validate(): any {
let activeRow = self.currentCell.row;
let result: any = { valid: true, msg: undefined };
let colIndex: number = self.getColumnIndex(this._args.column.name);
let newValue: any = this._textEditor.getValue();
if (self.onIsCellEditValid && !self.onIsCellEditValid(activeRow, colIndex, newValue)) {
result.valid = false;
}
return result;
}
}
return OverridableTextEditor;
}
private getColumnEditor(column: ISlickColumn<any>): any {
if (column.isEditable === false || typeof column.isEditable === 'undefined') {
return undefined;
}
let columnId = column.id;
let canEditColumn = columnId !== undefined;
if (canEditColumn) {
return this.getOverridableTextEditorClass();
}
return undefined;
}
public getColumnIndex(name: string): number {
return this.columnNameToIndex[name];
}
handleChanges(changes: { [propName: string]: any }): void {
let columnDefinitionChanges = changes['columnDefinitions'];
let activeCell = this.table ? this.table.grid.getActiveCell() : undefined;
let hasGridStructureChanges = false;
let wasEditing = this.table ? !!this.table.grid.getCellEditor() : false;
if (columnDefinitionChanges && !equals(columnDefinitionChanges.previousValue, columnDefinitionChanges.currentValue)) {
if (!this.table) {
this.createNewTable();
} else {
this.table.grid.resetActiveCell();
this.table.grid.setColumns(this.dataSet.columnDefinitions);
}
hasGridStructureChanges = true;
if (!columnDefinitionChanges.currentValue || columnDefinitionChanges.currentValue.length === 0) {
activeCell = undefined;
}
if (activeCell) {
let columnThatContainedActiveCell = columnDefinitionChanges.previousValue[Math.max(activeCell.cell - 1, 0)];
let newActiveColumnIndex = columnThatContainedActiveCell
? columnDefinitionChanges.currentValue.findIndex(c => c.id === columnThatContainedActiveCell.id)
: -1;
activeCell.cell = newActiveColumnIndex !== -1 ? newActiveColumnIndex + 1 : 0;
}
}
if (changes['dataRows']
|| (changes['highlightedCells'] && !equals(changes['highlightedCells'].currentValue, changes['highlightedCells'].previousValue))
|| (changes['blurredColumns'] && !equals(changes['blurredColumns'].currentValue, changes['blurredColumns'].previousValue))
|| (changes['columnsLoading'] && !equals(changes['columnsLoading'].currentValue, changes['columnsLoading'].previousValue))) {
this.setCallbackOnDataRowsChanged();
this.table.rerenderGrid(0, this.dataSet.dataRows.getLength());
hasGridStructureChanges = true;
}
if (hasGridStructureChanges) {
if (activeCell) {
this.table.grid.setActiveCell(activeCell.row, activeCell.cell);
} else {
this.table.grid.resetActiveCell();
}
}
if (wasEditing && hasGridStructureChanges) {
this.table.grid.editActiveCell(this.table.grid.getCellEditor());
}
}
private setCallbackOnDataRowsChanged(): void {
//check if dataRows exist before we enable editing or slickgrid will complain
if (this.dataSet.dataRows) {
this.changeEditSession(true);
this.dataSet.dataRows.setCollectionChangedCallback((startIndex: number, count: number) => {
this.renderGridDataRowsRange(startIndex, count);
});
}
}
private changeEditSession(enabled: boolean): void {
this.enableEditing = enabled;
let options: any = this.table.grid.getOptions();
options.editable = enabled;
options.enableAddRow = false;
this.table.grid.setOptions(options);
}
private renderGridDataRowsRange(startIndex: number, count: number): void {
let editor = <Slick.Editors.Text<any>>this.table.grid.getCellEditor();
let oldValue = editor ? editor.getValue() : undefined;
let wasValueChanged = editor ? editor.isValueChanged() : false;
this.invalidateRange(startIndex, startIndex + count);
let activeCell = this.currentCell;
if (editor && activeCell.row >= startIndex && activeCell.row < startIndex + count) {
if (oldValue && wasValueChanged) {
editor.setValue(oldValue);
}
}
}
private invalidateRange(start: number, end: number): void {
let refreshedRows = Array.from({ length: (end - start) }, (v, k) => k + start);
this.table.grid.invalidateRows(refreshedRows, true);
this.table.grid.render();
}
private setupEvents(): void {
this.table.grid.onScroll.subscribe((e, args) => {
this.onScroll(args);
});
this.table.grid.onCellChange.subscribe((e, args) => {
this.onCellEditEnd(args);
});
this.table.grid.onBeforeEditCell.subscribe((e, args) => {
this.onBeforeEditCell(args);
});
// Subscribe to all active cell changes to be able to catch when we tab to the header on the next row
this.table.grid.onActiveCellChanged.subscribe((e, args) => {
// Emit that we've changed active cells
this.onActiveCellChanged(args);
});
this.table.grid.onContextMenu.subscribe((e, args) => {
this.openContextMenu(e, this.dataSet.batchId, this.dataSet.resultId, 0);
});
this.table.grid.onBeforeAppendCell.subscribe((e, args) => {
// Since we need to return a string here, we are using calling a function instead of event emitter like other events handlers
return this.onBeforeAppendCell ? this.onBeforeAppendCell(args.row, args.cell) : undefined;
});
this.table.grid.onRendered.subscribe((e, args) => {
this.onGridRendered(args);
});
}
onBeforeEditCell(event: Slick.OnBeforeEditCellEventArgs<any>): void {
this.logService.debug('onBeforeEditCell called with grid: ' + event.grid + ' row: ' + event.row
+ ' cell: ' + event.cell + ' item: ' + event.item + ' column: ' + event.column);
}
handleInitializeTable(): void {
// handleInitializeTable() will be called *after* the first time handleChanges() is called
// so, grid must be there already
if (this.dataSet.dataRows && this.dataSet.dataRows.getLength() > 0) {
this.table.grid.scrollRowToTop(0);
}
if (this.dataSet.resized) {
// Re-rendering the grid is expensive. Throttle so we only do so every 100ms.
this.dataSet.resized.throttleTime(100)
.subscribe(() => this.onResize());
}
// subscribe to slick events
// https://github.com/mleibman/SlickGrid/wiki/Grid-Events
this.setupEvents();
}
private onResize(): void {
if (this.table.grid !== undefined) {
// this will make sure the grid header and body to be re-rendered
this.table.grid.resizeCanvas();
}
}
/*Formatter for Column*/
private getColumnFormatter(row: number | undefined, cell: any | undefined, value: any, columnDef: any | undefined, dataContext: any | undefined): string {
let valueToDisplay = '';

View File

@@ -15,11 +15,8 @@ import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import * as types from 'vs/base/common/types';
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import { bootstrapAngular } from 'sql/workbench/services/bootstrap/browser/bootstrapService';
import { BareResultsGridInfo } from 'sql/workbench/contrib/query/browser/queryResultsEditor';
import { IEditDataComponentParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
import { EditDataModule } from 'sql/workbench/contrib/editData/browser/editData.module';
import { EDITDATA_SELECTOR } from 'sql/workbench/contrib/editData/browser/editData.component';
import { BareResultsGridInfo, getBareResultsGridInfoStyles } from 'sql/workbench/contrib/query/browser/queryResultsEditor';
import { EditDataGridPanel } from 'sql/workbench/contrib/editData/browser/editDataGridPanel';
import { EditDataResultsInput } from 'sql/workbench/contrib/editData/browser/editDataResultsInput';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IStorageService } from 'vs/platform/storage/common/storage';
@@ -31,6 +28,8 @@ export class EditDataResultsEditor extends BaseEditor {
protected _input: EditDataResultsInput;
protected _rawOptions: BareResultsGridInfo;
private styleSheet = DOM.createStyleSheet();
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@@ -54,9 +53,11 @@ export class EditDataResultsEditor extends BaseEditor {
}
public createEditor(parent: HTMLElement): void {
parent.appendChild(this.styleSheet);
}
public dispose(): void {
this.styleSheet = undefined;
super.dispose();
}
@@ -67,7 +68,7 @@ export class EditDataResultsEditor extends BaseEditor {
super.setInput(input, options, CancellationToken.None);
this._applySettings();
if (!input.hasBootstrapped) {
this._bootstrapAngular();
this.createGridPanel();
}
return Promise.resolve<void>(null);
}
@@ -85,41 +86,26 @@ export class EditDataResultsEditor extends BaseEditor {
cssRuleText = this._rawOptions.cellPadding.join('px ') + 'px;';
}
let content = `.grid .slick-cell { padding: ${cssRuleText}; }`;
content += `.grid-panel .monaco-table, .message-tree { ${getBareResultsGridInfoStyles(this._rawOptions)} }`;
this.input.css.innerHTML = content;
}
}
/**
* Load the angular components and record for this input that we have done so
*/
private _bootstrapAngular(): void {
private createGridPanel(): void {
let input = <EditDataResultsInput>this.input;
let uri = input.uri;
// Pass the correct DataService to the new angular component
let dataService = this._queryModelService.getDataService(uri);
if (!dataService) {
throw new Error('DataService not found for URI: ' + uri);
}
// Mark that we have bootstrapped
input.setBootstrappedTrue();
// Get the bootstrap params and perform the bootstrap
// Note: pass in input so on disposal this is cleaned up.
// Otherwise many components will be left around and be subscribed
// to events from the backing data service
const parent = input.container;
let params: IEditDataComponentParams = {
dataService: dataService,
onSaveViewState: input.onSaveViewStateEmitter.event,
onRestoreViewState: input.onRestoreViewStateEmitter.event
};
bootstrapAngular(this._instantiationService,
EditDataModule,
parent,
EDITDATA_SELECTOR,
params,
input);
this._applySettings();
let editGridPanel = this._register(this._instantiationService.createInstance(EditDataGridPanel, dataService, input.onSaveViewStateEmitter.event, input.onRestoreViewStateEmitter.event));
editGridPanel.render(this.getContainer());
}
}

View File

@@ -6,9 +6,8 @@
import 'vs/css!./media/flexbox';
import 'vs/css!./media/styles';
import { Table } from 'sql/base/browser/ui/table/table';
import { Subscription, Subject } from 'rxjs/Rx';
import { ElementRef, QueryList, ChangeDetectorRef, ViewChildren } from '@angular/core';
import { SlickGrid } from 'angular2-slickgrid';
import * as Constants from 'sql/workbench/contrib/query/common/constants';
import * as LocalizedConstants from 'sql/workbench/contrib/query/common/localizedConstants';
import { IGridInfo, IGridDataSet, SaveFormat } from 'sql/workbench/contrib/grid/common/interfaces';
@@ -25,20 +24,21 @@ import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { DisposableStore, Disposable } from 'vs/base/common/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ILogService } from 'vs/platform/log/common/log';
import { subscriptionToDisposable } from 'sql/base/browser/lifecycle';
export abstract class GridParentComponent {
export abstract class GridParentComponent extends Disposable {
// CONSTANTS
// tslint:disable:no-unused-variable
protected get selectionModel() { return new CellSelectionModel(); }
protected _rowHeight = 29;
protected _defaultNumShowingRows = 8;
protected rowHeight = 29;
protected defaultNumShowingRows = 8;
protected Constants = Constants;
protected LocalizedConstants = LocalizedConstants;
protected Utils = Utils;
@@ -56,7 +56,6 @@ export abstract class GridParentComponent {
protected toDispose = new DisposableStore();
// Context keys to set when keybindings are available
private resultsVisibleContextKey: IContextKey<boolean>;
private gridFocussedContextKey: IContextKey<boolean>;
@@ -69,34 +68,32 @@ export abstract class GridParentComponent {
// Datasets currently being rendered on the DOM
protected renderedDataSets: IGridDataSet[] = this.placeHolderDataSets;
protected resultActive = true;
protected _messageActive = true;
protected messageActiveBool = true;
protected activeGrid = 0;
@ViewChildren('slickgrid') slickgrids: QueryList<SlickGrid>;
protected nativeElement: HTMLElement;
protected table: Table<any>;
set messageActive(input: boolean) {
this._messageActive = input;
this.messageActiveBool = input;
if (this.resultActive) {
this.resizeGrids();
}
this._cd.detectChanges();
}
get messageActive(): boolean {
return this._messageActive;
return this.messageActiveBool;
}
constructor(
protected _el: ElementRef,
protected _cd: ChangeDetectorRef,
protected contextMenuService: IContextMenuService,
protected keybindingService: IKeybindingService,
protected contextKeyService: IContextKeyService,
protected configurationService: IConfigurationService,
protected clipboardService: IClipboardService,
protected queryEditorService: IQueryEditorService,
protected logService: ILogService
@IContextMenuService protected contextMenuService: IContextMenuService,
@IKeybindingService protected keybindingService: IKeybindingService,
@IContextKeyService protected contextKeyService: IContextKeyService,
@IConfigurationService protected configurationService: IConfigurationService,
@IClipboardService protected clipboardService: IClipboardService,
@IQueryEditorService protected queryEditorService: IQueryEditorService,
@ILogService protected logService: ILogService
) {
super();
}
protected baseInit(): void {
@@ -105,13 +102,13 @@ export abstract class GridParentComponent {
if (this.configurationService) {
let sqlConfig = this.configurationService.getValue('sql');
if (sqlConfig) {
this._messageActive = sqlConfig['messagesDefaultOpen'];
this.messageActiveBool = sqlConfig['messagesDefaultOpen'];
}
}
this.subscribeWithDispose(this.dataService.gridContentObserver, (type) => {
switch (type) {
case GridContentEvents.RefreshContents:
self.refreshResultsets();
self.refreshDatasets();
break;
case GridContentEvents.ResizeContents:
self.resizeGrids();
@@ -184,7 +181,7 @@ export abstract class GridParentComponent {
this.queryEditorVisible = QueryEditorVisibleContext.bindTo(contextKeyService);
this.queryEditorVisible.set(true);
let gridContextKeyService = this.contextKeyService.createScoped(this._el.nativeElement);
let gridContextKeyService = this.contextKeyService.createScoped(this.nativeElement);
this.toDispose.add(gridContextKeyService);
this.resultsVisibleContextKey = ResultsVisibleContext.bindTo(gridContextKeyService);
this.resultsVisibleContextKey.set(true);
@@ -203,7 +200,6 @@ export abstract class GridParentComponent {
if (this.resultActive) {
this.resizeGrids();
}
this._cd.detectChanges();
}
protected toggleMessagePane(): void {
@@ -227,7 +223,7 @@ export abstract class GridParentComponent {
}
protected getSelection(index?: number): Slick.Range[] {
let selection = this.slickgrids.toArray()[index || this.activeGrid].getSelectedRanges();
let selection = this.table.getSelectedRanges();
if (selection) {
selection = selection.map(c => { return <Slick.Range>{ fromCell: c.fromCell - 1, toCell: c.toCell - 1, toRow: c.toRow, fromRow: c.fromRow }; });
return selection;
@@ -380,13 +376,11 @@ export abstract class GridParentComponent {
}
openContextMenu(event, batchId, resultId, index): void {
let slick: any = this.slickgrids.toArray()[index];
let grid = slick._grid;
let grid = this.table.grid;
let selection = this.getSelection(index);
if (selection && selection.length === 0) {
let cell = (grid as Slick.Grid<any>).getCellFromEvent(event);
let cell = grid.getCellFromEvent(event);
selection = [new Slick.Range(cell.row, cell.cell - 1)];
}
@@ -421,16 +415,14 @@ export abstract class GridParentComponent {
let self = this;
return (gridIndex: number) => {
self.activeGrid = gridIndex;
let grid = self.slickgrids.toArray()[self.activeGrid];
let grid = self.table;
grid.setActive();
grid.selection = true;
grid.setSelectedRows(true);
};
}
private onSelectAllForActiveGrid(): void {
if (this.activeGrid >= 0 && this.slickgrids.length > this.activeGrid) {
this.slickgrids.toArray()[this.activeGrid].selection = true;
}
this.table.setSelectedRows(true);
}
/**
@@ -447,29 +439,27 @@ export abstract class GridParentComponent {
}
setTimeout(() => {
self.resizeGrids();
self.slickgrids.toArray()[0].setActive();
self._cd.detectChanges();
self.table.setActive();
});
}
abstract onScroll(scrollTop): void;
protected getResultsElement(): any {
return this._el.nativeElement.querySelector('#results');
return this.nativeElement.querySelector('#results');
}
protected getMessagesElement(): any {
return this._el.nativeElement.querySelector('#messages');
return this.nativeElement.querySelector('#messages');
}
/**
* Force angular to re-render the results grids. Calling this upon unhide (upon focus) fixes UI
* Force re-rendering of the results grids. Calling this upon unhide (upon focus) fixes UI
* glitches that occur when a QueryRestulsEditor is hidden then unhidden while it is running a query.
* Detect Changes has been moved to editDataGridPanel version of this.
*/
refreshResultsets(): void {
refreshDatasets(): void {
let tempRenderedDataSets = this.renderedDataSets;
this.renderedDataSets = [];
this._cd.detectChanges();
this.renderedDataSets = tempRenderedDataSets;
this._cd.detectChanges();
}
getSelectedRangeUnderMessages(): Selection {
@@ -481,8 +471,8 @@ export abstract class GridParentComponent {
}
selectAllMessages(): void {
let msgEl = this._el.nativeElement.querySelector('#messages');
this.selectElementContents(msgEl);
let msgEl = this.nativeElement.querySelector('#messages');
this.selectElementContents(<HTMLElement>msgEl);
}
selectElementContents(el: HTMLElement): void {
@@ -513,10 +503,15 @@ export abstract class GridParentComponent {
const self = this;
setTimeout(() => {
for (let grid of self.renderedDataSets) {
grid.resized.emit();
grid.resized.fire();
}
});
}
// Private Helper Functions ////////////////////////////////////////////////////////////////////////////
/**
* used to render the native element into the container.
* */
public render(container: HTMLElement): void {
container.appendChild(this.nativeElement);
}
}

View File

@@ -3,11 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.editdata-component * {
.editdatagridpanel * {
box-sizing: border-box;
}
#workbench\.editor\.editDataEditor .monaco-toolbar .monaco-select-box {
margin-top: 4px;
margin-bottom: 4px;
}
}