mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-22 01:25:38 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
137
src/sql/base/browser/ui/table/plugins/autoSizeColumns.plugin.ts
Normal file
137
src/sql/base/browser/ui/table/plugins/autoSizeColumns.plugin.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
// Adapted from https://github.com/naresh-n/slickgrid-column-data-autosize/blob/master/src/slick.autocolumnsize.js
|
||||
|
||||
import { mixin, clone } from 'vs/base/common/objects';
|
||||
|
||||
export interface IAutoColumnSizeOptions extends Slick.PluginOptions {
|
||||
maxWidth?: number;
|
||||
}
|
||||
|
||||
const defaultOptions: IAutoColumnSizeOptions = {
|
||||
maxWidth: 200
|
||||
};
|
||||
|
||||
export class AutoColumnSize<T> implements Slick.Plugin<T> {
|
||||
private _grid: Slick.Grid<T>;
|
||||
private _$container: JQuery;
|
||||
private _context: CanvasRenderingContext2D;
|
||||
private _options: IAutoColumnSizeOptions;
|
||||
|
||||
constructor(options: IAutoColumnSizeOptions) {
|
||||
this._options = mixin(options, defaultOptions, false);
|
||||
}
|
||||
|
||||
public init(grid: Slick.Grid<T>) {
|
||||
this._grid = grid;
|
||||
|
||||
this._$container = $(this._grid.getContainerNode());
|
||||
this._$container.on('dblclick.autosize', '.slick-resizable-handle', e => this.reSizeColumn(e));
|
||||
this._context = document.createElement('canvas').getContext('2d');
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this._$container.off();
|
||||
}
|
||||
|
||||
private reSizeColumn(e: Event) {
|
||||
let headerEl = $(e.currentTarget).closest('.slick-header-column');
|
||||
let columnDef = headerEl.data('column');
|
||||
|
||||
if (!columnDef || !columnDef.resizable) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
let headerWidth = this.getElementWidth(headerEl[0]);
|
||||
let colIndex = this._grid.getColumnIndex(columnDef.id);
|
||||
let origCols = this._grid.getColumns();
|
||||
let allColumns = clone(origCols);
|
||||
allColumns.forEach((col, index) => {
|
||||
col.formatter = origCols[index].formatter;
|
||||
col.asyncPostRender = origCols[index].asyncPostRender;
|
||||
});
|
||||
let column = allColumns[colIndex];
|
||||
|
||||
let autoSizeWidth = Math.max(headerWidth, this.getMaxColumnTextWidth(columnDef, colIndex)) + 1;
|
||||
|
||||
if (autoSizeWidth !== column.width) {
|
||||
allColumns[colIndex].width = autoSizeWidth;
|
||||
this._grid.setColumns(allColumns);
|
||||
this._grid.onColumnsResized.notify();
|
||||
}
|
||||
}
|
||||
|
||||
private getMaxColumnTextWidth(columnDef, colIndex: number): number {
|
||||
let texts = [];
|
||||
let rowEl = this.createRow(columnDef);
|
||||
let data = this._grid.getData();
|
||||
let viewPort = this._grid.getViewport();
|
||||
let start = Math.max(0, viewPort.top + 1);
|
||||
let end = Math.min(data.getLength(), viewPort.bottom);
|
||||
for (let i = start; i < end; i++) {
|
||||
texts.push(data.getItem(i)[columnDef.field]);
|
||||
}
|
||||
let template = this.getMaxTextTemplate(texts, columnDef, colIndex, data, rowEl);
|
||||
let width = this.getTemplateWidth(rowEl, template);
|
||||
this.deleteRow(rowEl);
|
||||
return width;
|
||||
}
|
||||
|
||||
private getTemplateWidth(rowEl: JQuery, template: JQuery | HTMLElement): number {
|
||||
let cell = $(rowEl.find('.slick-cell'));
|
||||
cell.append(template);
|
||||
$(cell).find('*').css('position', 'relative');
|
||||
return cell.outerWidth() + 1;
|
||||
}
|
||||
|
||||
private getMaxTextTemplate(texts: string[], columnDef, colIndex: number, data, rowEl: JQuery): JQuery | HTMLElement {
|
||||
let max = 0,
|
||||
maxTemplate = null;
|
||||
let formatFun = columnDef.formatter;
|
||||
texts.forEach((text, index) => {
|
||||
let template;
|
||||
if (formatFun) {
|
||||
template = $('<span>' + formatFun(index, colIndex, text, columnDef, data[index]) + '</span>');
|
||||
text = template.text() || text;
|
||||
}
|
||||
let length = text ? this.getElementWidthUsingCanvas(rowEl, text) : 0;
|
||||
if (length > max) {
|
||||
max = length;
|
||||
maxTemplate = template || text;
|
||||
}
|
||||
});
|
||||
return maxTemplate;
|
||||
}
|
||||
|
||||
private createRow(columnDef): JQuery {
|
||||
let rowEl = $('<div class="slick-row"><div class="slick-cell"></div></div>');
|
||||
rowEl.find('.slick-cell').css({
|
||||
'visibility': 'hidden',
|
||||
'text-overflow': 'initial',
|
||||
'white-space': 'nowrap'
|
||||
});
|
||||
let gridCanvas = this._$container.find('.grid-canvas');
|
||||
$(gridCanvas).append(rowEl);
|
||||
return rowEl;
|
||||
}
|
||||
|
||||
private deleteRow(rowEl: JQuery) {
|
||||
$(rowEl).remove();
|
||||
}
|
||||
|
||||
private getElementWidth(element: HTMLElement): number {
|
||||
let width, clone = element.cloneNode(true) as HTMLElement;
|
||||
clone.style.cssText = 'position: absolute; visibility: hidden;right: auto;text-overflow: initial;white-space: nowrap;';
|
||||
element.parentNode.insertBefore(clone, element);
|
||||
width = clone.offsetWidth;
|
||||
clone.parentNode.removeChild(clone);
|
||||
return width;
|
||||
}
|
||||
|
||||
private getElementWidthUsingCanvas(element: JQuery, text: string): number {
|
||||
this._context.font = element.css('font-size') + ' ' + element.css('font-family');
|
||||
let metrics = this._context.measureText(text);
|
||||
return metrics.width;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
// Adopted and converted to typescript from https://github.com/6pac/SlickGrid/blob/master/plugins/slick.checkboxselectcolumn.js
|
||||
|
||||
import 'vs/css!vs/base/browser/ui/checkbox/checkbox';
|
||||
|
||||
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import * as nls from 'vs/nls';
|
||||
import { ICheckboxStyles } from 'vs/base/browser/ui/checkbox/checkbox';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
|
||||
export interface ICheckboxSelectColumnOptions extends Slick.PluginOptions, ICheckboxStyles {
|
||||
columnId?: string;
|
||||
cssClass?: string;
|
||||
toolTip?: string;
|
||||
width?: number;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
const defaultOptions: ICheckboxSelectColumnOptions = {
|
||||
columnId: '_checkbox_selector',
|
||||
cssClass: null,
|
||||
toolTip: nls.localize('selectDeselectAll', 'Select/Deselect All'),
|
||||
width: 30,
|
||||
inputActiveOptionBorder: Color.fromHex('#007ACC')
|
||||
};
|
||||
|
||||
const checkBoxTemplate = `<div style="display: flex; align-items: center; flex-direction: column">
|
||||
<div style="border-color: {0}" role="checkbox" aria-checked="{1}" aria-label="" class="custom-checkbox sql-checkbox {2}"></div>
|
||||
</div>`;
|
||||
|
||||
export class CheckboxSelectColumn<T> implements Slick.Plugin<T> {
|
||||
private _options: ICheckboxSelectColumnOptions;
|
||||
private _grid: Slick.Grid<T>;
|
||||
private _handler = new Slick.EventHandler();
|
||||
private _selectedRowsLookup = {};
|
||||
private _checkboxTemplate: string;
|
||||
|
||||
constructor(options?: Slick.PluginOptions) {
|
||||
this._options = mixin(options, defaultOptions, false);
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
public init(grid: Slick.Grid<T>): void {
|
||||
this._grid = grid;
|
||||
this._handler
|
||||
.subscribe(this._grid.onSelectedRowsChanged, (e, args) => this.handleSelectedRowsChanged(e, args))
|
||||
.subscribe(this._grid.onClick, (e, args) => this.handleClick(e, args))
|
||||
.subscribe(this._grid.onHeaderClick, (e, args) => this.handleHeaderClick(e, args))
|
||||
.subscribe(this._grid.onKeyDown, (e, args) => this.handleKeyDown(e, args));
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this._handler.unsubscribeAll();
|
||||
}
|
||||
|
||||
private handleSelectedRowsChanged(e: Event, args: Slick.OnSelectedRowsChangedEventArgs<T>): void {
|
||||
let selectedRows = this._grid.getSelectedRows();
|
||||
let lookup = {}, row, i;
|
||||
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];
|
||||
}
|
||||
}
|
||||
for (i in this._selectedRowsLookup) {
|
||||
this._grid.invalidateRow(i);
|
||||
}
|
||||
this._selectedRowsLookup = lookup;
|
||||
this._grid.render();
|
||||
|
||||
if (!this._options.title) {
|
||||
if (selectedRows.length && selectedRows.length === this._grid.getDataLength()) {
|
||||
this._grid.updateColumnHeader(this._options.columnId,
|
||||
strings.format(this._checkboxTemplate, 'true', 'checked'),
|
||||
this._options.toolTip);
|
||||
} else {
|
||||
this._grid.updateColumnHeader(this._options.columnId,
|
||||
strings.format(this._checkboxTemplate, 'true', 'unchecked'),
|
||||
this._options.toolTip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleKeyDown(e: KeyboardEvent, args: Slick.OnKeyDownEventArgs<T>): void {
|
||||
if (e.which === 32) {
|
||||
if (this._grid.getColumns()[args.cell].id === this._options.columnId) {
|
||||
// if editing, try to commit
|
||||
if (!this._grid.getEditorLock().isActive() || this._grid.getEditorLock().commitCurrentEdit()) {
|
||||
this.toggleRowSelection(args.row);
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleClick(e: Event, args: Slick.OnClickEventArgs<T>): void {
|
||||
// clicking on a row select checkbox
|
||||
if (this._grid.getColumns()[args.cell].id === this._options.columnId && $(e.target).is('.custom-checkbox')) {
|
||||
// if editing, try to commit
|
||||
if (this._grid.getEditorLock().isActive() && !this._grid.getEditorLock().commitCurrentEdit()) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
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 handleHeaderClick(e: Event, args: Slick.OnHeaderClickEventArgs<T>): void {
|
||||
if (!this._options.title && args.column.id === this._options.columnId && $(e.target).is('.custom-checkbox')) {
|
||||
// if editing, try to commit
|
||||
if (this._grid.getEditorLock().isActive() && !this._grid.getEditorLock().commitCurrentEdit()) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($(e.target).is('.unchecked')) {
|
||||
let rows = [];
|
||||
for (let i = 0; i < this._grid.getDataLength(); i++) {
|
||||
rows.push(i);
|
||||
}
|
||||
this._grid.setSelectedRows(rows);
|
||||
this._grid.updateColumnHeader(this._options.columnId,
|
||||
strings.format(this._checkboxTemplate, 'true', 'checked'),
|
||||
this._options.toolTip);
|
||||
} else {
|
||||
this._grid.setSelectedRows([]);
|
||||
this._grid.updateColumnHeader(this._options.columnId,
|
||||
strings.format(this._checkboxTemplate, 'true', 'unchecked'), this._options.toolTip);
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getColumnDefinition(): Slick.Column<T> {
|
||||
return {
|
||||
id: this._options.columnId,
|
||||
name: this._options.title || strings.format(this._checkboxTemplate, 'true', 'unchecked'),
|
||||
toolTip: this._options.toolTip,
|
||||
field: 'sel',
|
||||
width: this._options.width,
|
||||
resizable: false,
|
||||
sortable: false,
|
||||
cssClass: this._options.cssClass,
|
||||
formatter: (r, c, v, cd, dc) => this.checkboxSelectionFormatter(r, c, v, cd, dc)
|
||||
};
|
||||
}
|
||||
|
||||
private checkboxSelectionFormatter(row, cell, value, columnDef: Slick.Column<T>, dataContext): string {
|
||||
if (dataContext) {
|
||||
return this._selectedRowsLookup[row]
|
||||
? strings.format(this._checkboxTemplate, 'true', 'checked')
|
||||
: strings.format(this._checkboxTemplate, 'true', 'unchecked');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public style(styles: ICheckboxStyles): void {
|
||||
if (styles.inputActiveOptionBorder) {
|
||||
this._options.inputActiveOptionBorder = styles.inputActiveOptionBorder;
|
||||
}
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
protected applyStyles(): void {
|
||||
this._checkboxTemplate = strings.format(checkBoxTemplate, this._options.inputActiveOptionBorder.toString(), '{0}', '{1}');
|
||||
if (this._grid) {
|
||||
this._grid.invalidateAllRows();
|
||||
this._grid.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
// Drag select selection model gist taken from https://gist.github.com/skoon/5312536
|
||||
// heavily modified
|
||||
|
||||
import { clone } from 'vs/base/common/objects';
|
||||
|
||||
export class DragCellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slick.Range>> {
|
||||
private readonly keyColResizeIncr = 5;
|
||||
|
||||
private _grid: Slick.Grid<T>;
|
||||
private _ranges: Array<Slick.Range> = [];
|
||||
private _dragging = false;
|
||||
private _handler = new Slick.EventHandler();
|
||||
|
||||
public onSelectedRangesChanged = new Slick.Event<Slick.Range[]>();
|
||||
|
||||
public init(grid: Slick.Grid<T>): void {
|
||||
this._grid = grid;
|
||||
this._handler.subscribe(this._grid.onActiveCellChanged, (e: Event, data: Slick.OnActiveCellChangedEventArgs<T>) => this.handleActiveCellChange(e, data));
|
||||
this._handler.subscribe(this._grid.onKeyDown, (e: JQueryInputEventObject) => this.handleKeyDown(e));
|
||||
this._handler.subscribe(this._grid.onClick, (e: MouseEvent) => this.handleClick(e));
|
||||
this._handler.subscribe(this._grid.onDrag, (e: MouseEvent) => this.handleDrag(e));
|
||||
this._handler.subscribe(this._grid.onDragInit, (e: MouseEvent) => this.handleDragInit(e));
|
||||
this._handler.subscribe(this._grid.onDragStart, (e: MouseEvent) => this.handleDragStart(e));
|
||||
this._handler.subscribe(this._grid.onDragEnd, (e: MouseEvent) => this.handleDragEnd(e));
|
||||
this._handler.subscribe(this._grid.onHeaderClick, (e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e, args));
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this._handler.unsubscribeAll();
|
||||
}
|
||||
|
||||
private rangesToRows(ranges: Array<Slick.Range>): Array<number> {
|
||||
let rows = [];
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
for (let j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
|
||||
rows.push(j);
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
private rowsToRanges(rows: Array<number>): Array<Slick.Range> {
|
||||
let ranges = [];
|
||||
let lastCell = this._grid.getColumns().length - 1;
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell));
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
public getSelectedRows(): Array<number> {
|
||||
return this.rangesToRows(this._ranges);
|
||||
}
|
||||
|
||||
public setSelectedRows(rows: Array<number>) {
|
||||
this.setSelectedRanges(this.rowsToRanges(rows));
|
||||
}
|
||||
|
||||
public setSelectedRanges(ranges: Array<Slick.Range>) {
|
||||
this._ranges = ranges;
|
||||
this.onSelectedRangesChanged.notify(this._ranges);
|
||||
}
|
||||
|
||||
public getSelectedRanges(): Array<Slick.Range> {
|
||||
return this._ranges;
|
||||
}
|
||||
|
||||
private handleActiveCellChange(e: Event, data: Slick.OnActiveCellChangedEventArgs<T>) { }
|
||||
|
||||
private isNavigationKey(e: BaseJQueryEventObject) {
|
||||
// Nave keys (home, end, arrows) are all in sequential order so use a
|
||||
switch (e.which) {
|
||||
case $.ui.keyCode.HOME:
|
||||
case $.ui.keyCode.END:
|
||||
case $.ui.keyCode.LEFT:
|
||||
case $.ui.keyCode.UP:
|
||||
case $.ui.keyCode.RIGHT:
|
||||
case $.ui.keyCode.DOWN:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private navigateLeft(e: JQueryInputEventObject, activeCell: Slick.Cell) {
|
||||
if (activeCell.cell > 1) {
|
||||
let isHome = e.which === $.ui.keyCode.HOME;
|
||||
let newActiveCellColumn = isHome ? 1 : activeCell.cell - 1;
|
||||
// Unsure why but for range, must record 1 index less than expected
|
||||
let newRangeColumn = newActiveCellColumn - 1;
|
||||
|
||||
if (e.shiftKey) {
|
||||
let last = this._ranges.pop();
|
||||
|
||||
// If we are on the rightmost edge of the range and we navigate left,
|
||||
// we want to deselect the rightmost cell
|
||||
if (last.fromCell <= newRangeColumn) { last.toCell -= 1; }
|
||||
|
||||
let fromRow = Math.min(activeCell.row, last.fromRow);
|
||||
let fromCell = Math.min(newRangeColumn, last.fromCell);
|
||||
let toRow = Math.max(activeCell.row, last.toRow);
|
||||
let toCell = Math.max(newRangeColumn, last.toCell);
|
||||
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(activeCell.row, newRangeColumn, activeCell.row, newRangeColumn)];
|
||||
}
|
||||
|
||||
this._grid.setActiveCell(activeCell.row, newActiveCellColumn);
|
||||
this.setSelectedRanges(this._ranges);
|
||||
}
|
||||
}
|
||||
|
||||
private navigateRight(e: JQueryInputEventObject, activeCell: Slick.Cell) {
|
||||
let columnLength = this._grid.getColumns().length;
|
||||
if (activeCell.cell < columnLength) {
|
||||
let isEnd = e.which === $.ui.keyCode.END;
|
||||
let newActiveCellColumn = isEnd ? columnLength : activeCell.cell + 1;
|
||||
// Unsure why but for range, must record 1 index less than expected
|
||||
let newRangeColumn = newActiveCellColumn - 1;
|
||||
if (e.shiftKey) {
|
||||
let last = this._ranges.pop();
|
||||
|
||||
// If we are on the leftmost edge of the range and we navigate right,
|
||||
// we want to deselect the leftmost cell
|
||||
if (newRangeColumn <= last.toCell) { last.fromCell += 1; }
|
||||
|
||||
let fromRow = Math.min(activeCell.row, last.fromRow);
|
||||
let fromCell = Math.min(newRangeColumn, last.fromCell);
|
||||
let toRow = Math.max(activeCell.row, last.toRow);
|
||||
let toCell = Math.max(newRangeColumn, last.toCell);
|
||||
|
||||
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(activeCell.row, newRangeColumn, activeCell.row, newRangeColumn)];
|
||||
}
|
||||
this._grid.setActiveCell(activeCell.row, newActiveCellColumn);
|
||||
this.setSelectedRanges(this._ranges);
|
||||
}
|
||||
}
|
||||
|
||||
private handleKeyDown(e: JQueryInputEventObject) {
|
||||
let activeCell = this._grid.getActiveCell();
|
||||
|
||||
if (activeCell) {
|
||||
// navigation keys
|
||||
if (this.isNavigationKey(e)) {
|
||||
e.stopImmediatePropagation();
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
let event = new CustomEvent('gridnav', {
|
||||
detail: {
|
||||
which: e.which,
|
||||
ctrlKey: e.ctrlKey,
|
||||
metaKey: e.metaKey,
|
||||
shiftKey: e.shiftKey,
|
||||
altKey: e.altKey
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
return;
|
||||
}
|
||||
// end key
|
||||
if (e.which === $.ui.keyCode.END) {
|
||||
this.navigateRight(e, activeCell);
|
||||
}
|
||||
// home key
|
||||
if (e.which === $.ui.keyCode.HOME) {
|
||||
this.navigateLeft(e, activeCell);
|
||||
}
|
||||
// left arrow
|
||||
if (e.which === $.ui.keyCode.LEFT) {
|
||||
// column resize
|
||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
|
||||
let allColumns = clone(this._grid.getColumns());
|
||||
allColumns[activeCell.cell - 1].width = allColumns[activeCell.cell - 1].width - this.keyColResizeIncr;
|
||||
this._grid.setColumnWidths(allColumns);
|
||||
} else {
|
||||
this.navigateLeft(e, activeCell);
|
||||
}
|
||||
// up arrow
|
||||
} else if (e.which === $.ui.keyCode.UP && activeCell.row > 0) {
|
||||
if (e.shiftKey) {
|
||||
let last = this._ranges.pop();
|
||||
|
||||
// If we are on the bottommost edge of the range and we navigate up,
|
||||
// we want to deselect the bottommost row
|
||||
let newRangeRow = activeCell.row - 1;
|
||||
if (last.fromRow <= newRangeRow) { last.toRow -= 1; }
|
||||
|
||||
let fromRow = Math.min(activeCell.row - 1, last.fromRow);
|
||||
let fromCell = Math.min(activeCell.cell - 1, last.fromCell);
|
||||
let toRow = Math.max(newRangeRow, last.toRow);
|
||||
let toCell = Math.max(activeCell.cell - 1, last.toCell);
|
||||
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(activeCell.row - 1, activeCell.cell - 1, activeCell.row - 1, activeCell.cell - 1)];
|
||||
}
|
||||
this._grid.setActiveCell(activeCell.row - 1, activeCell.cell);
|
||||
this.setSelectedRanges(this._ranges);
|
||||
// right arrow
|
||||
} else if (e.which === $.ui.keyCode.RIGHT) {
|
||||
// column resize
|
||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
|
||||
let allColumns = clone(this._grid.getColumns());
|
||||
allColumns[activeCell.cell - 1].width = allColumns[activeCell.cell - 1].width + this.keyColResizeIncr;
|
||||
this._grid.setColumnWidths(allColumns);
|
||||
} else {
|
||||
this.navigateRight(e, activeCell);
|
||||
}
|
||||
// down arrow
|
||||
} else if (e.which === $.ui.keyCode.DOWN && activeCell.row < this._grid.getDataLength() - 1) {
|
||||
if (e.shiftKey) {
|
||||
let last = this._ranges.pop();
|
||||
|
||||
// If we are on the topmost edge of the range and we navigate down,
|
||||
// we want to deselect the topmost row
|
||||
let newRangeRow = activeCell.row + 1;
|
||||
if (newRangeRow <= last.toRow) { last.fromRow += 1; }
|
||||
|
||||
let fromRow = Math.min(activeCell.row + 1, last.fromRow);
|
||||
let fromCell = Math.min(activeCell.cell - 1, last.fromCell);
|
||||
let toRow = Math.max(activeCell.row + 1, last.toRow);
|
||||
let toCell = Math.max(activeCell.cell - 1, last.toCell);
|
||||
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(activeCell.row + 1, activeCell.cell - 1, activeCell.row + 1, activeCell.cell - 1)];
|
||||
}
|
||||
this._grid.setActiveCell(activeCell.row + 1, activeCell.cell);
|
||||
this.setSelectedRanges(this._ranges);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleHeaderClick(e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) {
|
||||
let columnIndex = this._grid.getColumnIndex(args.column.id);
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
this._ranges.push(new Slick.Range(0, columnIndex, this._grid.getDataLength() - 1, columnIndex));
|
||||
this._grid.setActiveCell(0, columnIndex + 1);
|
||||
} else if (e.shiftKey && this._ranges.length) {
|
||||
let last = this._ranges.pop().fromCell;
|
||||
let from = Math.min(columnIndex, last);
|
||||
let to = Math.max(columnIndex, last);
|
||||
this._ranges = [];
|
||||
for (let i = from; i <= to; i++) {
|
||||
if (i !== last) {
|
||||
this._ranges.push(new Slick.Range(0, i, this._grid.getDataLength() - 1, i));
|
||||
}
|
||||
}
|
||||
this._ranges.push(new Slick.Range(0, last, this._grid.getDataLength() - 1, last));
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(0, columnIndex, this._grid.getDataLength() - 1, columnIndex)];
|
||||
this._grid.resetActiveCell();
|
||||
}
|
||||
this.setSelectedRanges(this._ranges);
|
||||
e.stopImmediatePropagation();
|
||||
return true;
|
||||
}
|
||||
|
||||
private handleClick(e: MouseEvent) {
|
||||
let cell = this._grid.getCellFromEvent(e);
|
||||
if (!cell || !this._grid.canCellBeActive(cell.row, cell.cell)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!e.ctrlKey && !e.shiftKey && !e.metaKey) {
|
||||
if (cell.cell !== 0) {
|
||||
this._ranges = [new Slick.Range(cell.row, cell.cell - 1, cell.row, cell.cell - 1)];
|
||||
this.setSelectedRanges(this._ranges);
|
||||
this._grid.setActiveCell(cell.row, cell.cell);
|
||||
return true;
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(cell.row, 0, cell.row, this._grid.getColumns().length - 1)];
|
||||
this.setSelectedRanges(this._ranges);
|
||||
this._grid.setActiveCell(cell.row, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (this._grid.getOptions().multiSelect) {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
if (cell.cell === 0) {
|
||||
this._ranges.push(new Slick.Range(cell.row, 0, cell.row, this._grid.getColumns().length - 1));
|
||||
this._grid.setActiveCell(cell.row, 1);
|
||||
} else {
|
||||
this._ranges.push(new Slick.Range(cell.row, cell.cell - 1, cell.row, cell.cell - 1));
|
||||
this._grid.setActiveCell(cell.row, cell.cell);
|
||||
}
|
||||
} else if (this._ranges.length && e.shiftKey) {
|
||||
let last = this._ranges.pop();
|
||||
if (cell.cell === 0) {
|
||||
let fromRow = Math.min(cell.row, last.fromRow);
|
||||
let toRow = Math.max(cell.row, last.fromRow);
|
||||
this._ranges = [new Slick.Range(fromRow, 0, toRow, this._grid.getColumns().length - 1)];
|
||||
} else {
|
||||
let fromRow = Math.min(cell.row, last.fromRow);
|
||||
let fromCell = Math.min(cell.cell - 1, last.fromCell);
|
||||
let toRow = Math.max(cell.row, last.toRow);
|
||||
let toCell = Math.max(cell.cell - 1, last.toCell);
|
||||
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.setSelectedRanges(this._ranges);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private handleDragInit(e: MouseEvent) {
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
private handleDragStart(e: MouseEvent) {
|
||||
let cell = this._grid.getCellFromEvent(e);
|
||||
e.stopImmediatePropagation();
|
||||
this._dragging = true;
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
this._ranges.push(new Slick.Range(cell.row, cell.cell));
|
||||
this._grid.setActiveCell(cell.row, cell.cell);
|
||||
} else if (this._ranges.length && e.shiftKey) {
|
||||
let last = this._ranges.pop();
|
||||
let fromRow = Math.min(cell.row, last.fromRow);
|
||||
let fromCell = Math.min(cell.cell - 1, last.fromCell);
|
||||
let toRow = Math.max(cell.row, last.toRow);
|
||||
let toCell = Math.max(cell.cell - 1, last.toCell);
|
||||
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(cell.row, cell.cell)];
|
||||
this._grid.setActiveCell(cell.row, cell.cell);
|
||||
}
|
||||
this.setSelectedRanges(this._ranges);
|
||||
}
|
||||
|
||||
private handleDrag(e: MouseEvent) {
|
||||
if (this._dragging) {
|
||||
let cell = this._grid.getCellFromEvent(e);
|
||||
let activeCell = this._grid.getActiveCell();
|
||||
if (!cell || !this._grid.canCellBeActive(cell.row, cell.cell)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._ranges.pop();
|
||||
|
||||
if (activeCell.cell === 0) {
|
||||
let lastCell = this._grid.getColumns().length - 1;
|
||||
let firstRow = Math.min(cell.row, activeCell.row);
|
||||
let lastRow = Math.max(cell.row, activeCell.row);
|
||||
this._ranges.push(new Slick.Range(firstRow, 0, lastRow, lastCell));
|
||||
} else {
|
||||
let firstRow = Math.min(cell.row, activeCell.row);
|
||||
let lastRow = Math.max(cell.row, activeCell.row);
|
||||
let firstColumn = Math.min(cell.cell - 1, activeCell.cell - 1);
|
||||
let lastColumn = Math.max(cell.cell - 1, activeCell.cell - 1);
|
||||
this._ranges.push(new Slick.Range(firstRow, firstColumn, lastRow, lastColumn));
|
||||
}
|
||||
this.setSelectedRanges(this._ranges);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private handleDragEnd(e: MouseEvent) {
|
||||
this._dragging = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
// Adopted and converted to typescript from https://github.com/6pac/SlickGrid/blob/master/plugins/slick.rowselectionmodel.js
|
||||
// heavily modified
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
|
||||
const defaultOptions: IRowSelectionModelOptions = {
|
||||
selectActiveRow: true
|
||||
};
|
||||
|
||||
export interface IRowSelectionModelOptions extends Slick.PluginOptions {
|
||||
selectActiveRow?: boolean;
|
||||
}
|
||||
|
||||
export class RowSelectionModel<T extends Slick.SlickData> implements Slick.SelectionModel<T, Array<Slick.Range>> {
|
||||
private _options: IRowSelectionModelOptions;
|
||||
private _grid: Slick.Grid<T>;
|
||||
private _handler = new Slick.EventHandler();
|
||||
private _ranges: Array<Slick.Range> = [];
|
||||
|
||||
public onSelectedRangesChanged = new Slick.Event<Array<Slick.Range>>();
|
||||
|
||||
constructor(options?: Slick.PluginOptions) {
|
||||
this._options = mixin(options, defaultOptions, false);
|
||||
}
|
||||
|
||||
public init(grid: Slick.Grid<T>) {
|
||||
this._grid = grid;
|
||||
this._handler
|
||||
.subscribe(this._grid.onActiveCellChanged, (e, data) => this.handleActiveCellChange(e, data))
|
||||
.subscribe(this._grid.onKeyDown, e => this.handleKeyDown(e))
|
||||
.subscribe(this._grid.onClick, e => this.handleClick(e));
|
||||
}
|
||||
|
||||
private rangesToRows(ranges: Slick.Range[]): number[] {
|
||||
let rows = [];
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
for (let j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
|
||||
rows.push(j);
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
private rowsToRanges(rows: number[]): Slick.Range[] {
|
||||
let ranges = [];
|
||||
let lastCell = this._grid.getColumns().length - 1;
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell));
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
public getSelectedRows(): number[] {
|
||||
return this.rangesToRows(this._ranges);
|
||||
}
|
||||
|
||||
public setSelectedRows(rows: number[]): void {
|
||||
this.setSelectedRanges(this.rowsToRanges(rows));
|
||||
}
|
||||
|
||||
public setSelectedRanges(ranges: Slick.Range[]): void {
|
||||
// simle check for: empty selection didn't change, prevent firing onSelectedRangesChanged
|
||||
if ((!this._ranges || this._ranges.length === 0) && (!ranges || ranges.length === 0)) { return; }
|
||||
this._ranges = ranges;
|
||||
this.onSelectedRangesChanged.notify(this._ranges);
|
||||
}
|
||||
|
||||
public getSelectedRanges(): Slick.Range[] {
|
||||
return this._ranges;
|
||||
}
|
||||
|
||||
private getRowsRange(from: number, to: number): number[] {
|
||||
let i, rows = [];
|
||||
for (i = from; i <= to; i++) {
|
||||
rows.push(i);
|
||||
}
|
||||
for (i = to; i < from; i++) {
|
||||
rows.push(i);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
private handleActiveCellChange(e: Event, data: Slick.OnActiveCellChangedEventArgs<T>): void {
|
||||
if (this._options.selectActiveRow && data.row !== null) {
|
||||
this.setSelectedRanges([new Slick.Range(data.row, 0, data.row, this._grid.getColumns().length - 1)]);
|
||||
}
|
||||
}
|
||||
|
||||
private handleKeyDown(e: KeyboardEvent): void {
|
||||
let activeRow = this._grid.getActiveCell();
|
||||
if (activeRow && e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey && (e.which === 38 || e.which === 40)) {
|
||||
let selectedRows = this.getSelectedRows();
|
||||
selectedRows.sort((x, y) => x - y);
|
||||
|
||||
if (!selectedRows.length) {
|
||||
selectedRows = [activeRow.row];
|
||||
}
|
||||
|
||||
let top = selectedRows[0];
|
||||
let bottom = selectedRows[selectedRows.length - 1];
|
||||
let active;
|
||||
|
||||
if (e.which === 40) {
|
||||
active = activeRow.row < bottom || top === bottom ? ++bottom : ++top;
|
||||
} else {
|
||||
active = activeRow.row < bottom ? --bottom : --top;
|
||||
}
|
||||
|
||||
if (active >= 0 && active < this._grid.getDataLength()) {
|
||||
this._grid.scrollRowIntoView(active, undefined);
|
||||
let tempRanges = this.rowsToRanges(this.getRowsRange(top, bottom));
|
||||
this.setSelectedRanges(tempRanges);
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
private handleClick(e: KeyboardEvent): boolean {
|
||||
let cell = this._grid.getCellFromEvent(e);
|
||||
if (!cell || !this._grid.canCellBeActive(cell.row, cell.cell)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this._grid.getOptions().multiSelect || (
|
||||
!e.ctrlKey && !e.shiftKey && !e.metaKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let selection = this.rangesToRows(this._ranges);
|
||||
let idx = $.inArray(cell.row, selection);
|
||||
|
||||
if (idx === -1 && (e.ctrlKey || e.metaKey)) {
|
||||
selection.push(cell.row);
|
||||
this._grid.setActiveCell(cell.row, cell.cell);
|
||||
} else if (idx !== -1 && (e.ctrlKey || e.metaKey)) {
|
||||
selection = selection.filter(o => o !== cell.row);
|
||||
this._grid.setActiveCell(cell.row, cell.cell);
|
||||
} else if (selection.length && e.shiftKey) {
|
||||
let last = selection.pop();
|
||||
let from = Math.min(cell.row, last);
|
||||
let to = Math.max(cell.row, last);
|
||||
selection = [];
|
||||
for (let i = from; i <= to; i++) {
|
||||
if (i !== last) {
|
||||
selection.push(i);
|
||||
}
|
||||
}
|
||||
selection.push(last);
|
||||
this._grid.setActiveCell(cell.row, cell.cell);
|
||||
}
|
||||
|
||||
let tempRanges = this.rowsToRanges(selection);
|
||||
this.setSelectedRanges(tempRanges);
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this._handler.unsubscribeAll();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user