Skip to content

Commit

Permalink
#6674 add keyboard support and aesthetic improvements (#6896)
Browse files Browse the repository at this point in the history
* #6674 fix hiding and moving columns in parallel

* #6674 add keyboard event handling

* #6674 fix the first column hover handler
  • Loading branch information
Mariusz Jurowicz authored and scottdraves committed Feb 26, 2018
1 parent 1e529ed commit f99ec04
Show file tree
Hide file tree
Showing 20 changed files with 576 additions and 162 deletions.
125 changes: 60 additions & 65 deletions js/notebook/src/tableDisplay/dataGrid/BeakerxDataGrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ import RowManager from "./row/RowManager";
import CellSelectionManager from "./cell/CellSelectionManager";
import {SectionList} from "@phosphor/datagrid/lib/sectionlist";
import CellManager from "./cell/CellManager";
import {DataGridHelpers} from "./dataGridHelpers";
import EventManager from "./EventManager";

import getStringWidth = DataGridHelpers.getStringWidth;
import CellFocusManager from "./cell/CellFocusManager";

export class BeakerxDataGrid extends DataGrid {
columnSections: any;
Expand All @@ -43,6 +48,8 @@ export class BeakerxDataGrid extends DataGrid {
rowManager: RowManager;
cellSelectionManager: CellSelectionManager;
cellManager: CellManager;
eventManager: EventManager;
cellFocusManager: CellFocusManager;
focused: boolean;

headerCellHovered = new Signal<this, ICellData|null>(this);
Expand All @@ -57,28 +64,18 @@ export class BeakerxDataGrid extends DataGrid {
this.rowSections = this['_rowSections'];
this.columnSections = this['_columnSections'];

this.resizeColumnSection = this.resizeColumnSection.bind(this);
this.init(modelState);
}

handleEvent(event: Event): void {
switch (event.type) {
case 'mousemove':
this.handleHeaderCellHover(event as MouseEvent);
break;
case 'mousedown':
this.handleMouseDown(event as MouseEvent);
break;
case 'wheel':
this.handleMouseWheel(event as MouseEvent);
return;
}

super.handleEvent(event);
this.eventManager.handleEvent(event, super.handleEvent);
}

destroy() {
this.eventManager.destroy();
this.columnManager.destroy();
this.dispose();
this.isAttached && this.dispose();
}

getColumn(config: CellRenderer.ICellConfig): DataGridColumn {
Expand Down Expand Up @@ -133,7 +130,7 @@ export class BeakerxDataGrid extends DataGrid {
let section = this.columnSections;
let columnType = COLUMN_TYPES.body;
let pos = x + this.scrollX - this.headerWidth;
if (x <= this.rowHeaderSections.sectionSize()) {
if (x <= this.rowHeaderSections.totalSize) {
section = this.rowHeaderSections;
columnType = COLUMN_TYPES.index;
pos += this.headerWidth;
Expand Down Expand Up @@ -163,24 +160,37 @@ export class BeakerxDataGrid extends DataGrid {
return x < (this.bodyWidth + this.rowHeaderSections.totalSize) && y < this.headerHeight;
}

resizeMovedColumns(oldMap: number[], newMap: number[]) {
newMap.forEach((index, position) => {
const column = this.columnManager.getColumnByIndex(COLUMN_TYPES.body, index);

this.columnSections.resizeSection(
position,
this.getSectionWidth(column)
);
});
}

private init(modelState: IDataModelState) {
this.columnManager = new ColumnManager(modelState, this);
this.rowManager = new RowManager(modelState.values, modelState.hasIndex, this.columnManager);
this.cellSelectionManager = new CellSelectionManager(this);
this.cellManager = new CellManager(this);
this.eventManager = new EventManager(this);
this.cellFocusManager = new CellFocusManager(this);
this.model = new BeakerxDataGridModel(modelState, this.columnManager, this.rowManager);
this.focused = false;

this.node.removeEventListener('mouseout', this.handleMouseOut.bind(this));
this.node.addEventListener('mouseout', this.handleMouseOut.bind(this));

this.columnManager.addColumns();
this.rowManager.createFilterExpressionVars();
this.model.reset();

this.addHighlighterManager(modelState);
this.addCellRenderers();
this.setWidgetHeight();
this.resizeSections();

this.model.reset();
this.repaint();
}

private addHighlighterManager(modelState: IDataModelState) {
Expand Down Expand Up @@ -208,52 +218,6 @@ export class BeakerxDataGrid extends DataGrid {
this.node.style.minHeight = `${ (rowCount + 2) * this.baseRowSize + this.baseColumnHeaderSize }px`;
}

//@todo debounce it
private handleHeaderCellHover(event: MouseEvent): void {
if (!this.isOverHeader(event)) {
return;
}

const data = this.getCellData(event.clientX, event.clientY);

this.headerCellHovered.emit(data);
}

private handleMouseDown(event: MouseEvent) {
this.focused = true;
this.node.classList.add('bko-focused');
this.handleHeaderClick(event);
}

private handleMouseOut(event: MouseEvent) {
this.headerCellHovered.emit(null);
this.node.classList.remove('bko-focused');
this.focused = false;
}

private handleMouseWheel(event: MouseEvent) {
if(!this.focused) {
return;
}

super.handleEvent(event);
}

private handleHeaderClick(event: MouseEvent): void {
if (!this.isOverHeader(event)) {
return;
}

const data = this.getCellData(event.clientX, event.clientY);

if (!data) {
return;
}

const column = this.columnManager.columns[data.type][data.column];
column.toggleSort();
}

private findHoveredRowIndex(y: number) {
// Convert the position into unscrolled coordinates.
let pos = y + this.scrollY - this.headerHeight;
Expand Down Expand Up @@ -288,4 +252,35 @@ export class BeakerxDataGrid extends DataGrid {

return null;
}

private resizeColumnSection(column) {
this.columnSections.resizeSection(
column.getResolvedIndex(),
this.getSectionWidth(column)
);
}

private getSectionWidth(column) {
let value = String(column.formatFn(this.cellManager.createCellConfig({
region: 'body',
value: column.maxValue,
column: column.index,
row: 0,
})));

return getStringWidth(value.length > column.name.length ? value : column.name);
}

private resizeSections() {
this.columnManager.columns[COLUMN_TYPES.body].forEach(this.resizeColumnSection);
this.resizeIndexColumn();
}

private resizeIndexColumn() {
let valueCharLength = this.model.rowCount('body');
let name = this.columnManager.getColumnByIndex(COLUMN_TYPES.index, 0).name;
let value = name.length > valueCharLength ? name : String(valueCharLength);

this.rowHeaderSections.resizeSection(0, getStringWidth(value) + 10);
}
}
4 changes: 2 additions & 2 deletions js/notebook/src/tableDisplay/dataGrid/DataGridScope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import IDataGridScopeOptions from "./interface/IDataGridScopeOptions";
import consts from "../consts";

export class DataGridScope {
private dataGrid: BeakerxDataGrid;
private element: HTMLElement;
readonly dataGrid: BeakerxDataGrid;
readonly element: HTMLElement;
private tableDisplayModel: any;
private tableDisplayView: any;

Expand Down
163 changes: 163 additions & 0 deletions js/notebook/src/tableDisplay/dataGrid/EventManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Copyright 2018 TWO SIGMA OPEN SOURCE, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {BeakerxDataGrid} from "./BeakerxDataGrid";
import DataGridColumn from "./column/DataGridColumn";
import {HIGHLIGHTER_TYPE} from "./interface/IHighlighterState";
import {DataGridHelpers} from "./dataGridHelpers";
import disableKeyboardManager = DataGridHelpers.disableKeyboardManager;
import enableKeyboardManager = DataGridHelpers.enableKeyboardManager;

export default class EventManager {
dataGrid: BeakerxDataGrid;

constructor(dataGrid: BeakerxDataGrid) {
this.dataGrid = dataGrid;
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleMouseOut = this.handleMouseOut.bind(this);
this.handleMouseOut = this.handleMouseOut.bind(this);

this.dataGrid.node.removeEventListener('mouseout', this.handleMouseOut);
this.dataGrid.node.addEventListener('mouseout', this.handleMouseOut);
document.removeEventListener('keydown', this.handleKeyDown);
document.addEventListener('keydown', this.handleKeyDown);
}

handleEvent(event: Event, parentHandler: Function): void {
switch (event.type) {
case 'mousemove':
this.handleHeaderCellHover(event as MouseEvent);
break;
case 'mousedown':
this.handleMouseDown(event as MouseEvent);
break;
case 'wheel':
this.handleMouseWheel(event as MouseEvent, parentHandler);
return;
}

parentHandler.call(this.dataGrid, event);
}

destroy() {
document.removeEventListener('keydown', this.handleKeyDown);
}

//@todo debounce it
private handleHeaderCellHover(event: MouseEvent): void {
if (!this.dataGrid.isOverHeader(event)) {
this.dataGrid.headerCellHovered.emit(null);

return;
}

const data = this.dataGrid.getCellData(event.clientX, event.clientY);

this.dataGrid.headerCellHovered.emit(data);
}

private handleMouseDown(event: MouseEvent): void {
this.dataGrid.focused = true;
this.dataGrid.node.classList.add('bko-focused');
this.handleHeaderClick(event);
disableKeyboardManager();
}

private handleMouseOut(event: MouseEvent): void {
this.dataGrid.headerCellHovered.emit(null);
this.dataGrid.node.classList.remove('bko-focused');
this.dataGrid.focused = false;
enableKeyboardManager();
}

private handleMouseWheel(event: MouseEvent, parentHandler: Function): void {
if(!this.dataGrid.focused) {
return;
}

parentHandler.call(this.dataGrid, event);
}

private handleHeaderClick(event: MouseEvent): void {
if (!this.dataGrid.isOverHeader(event)) {
return;
}

const data = this.dataGrid.getCellData(event.clientX, event.clientY);

if (!data) {
return;
}

const column = this.dataGrid.columnManager.columns[data.type][data.column];
const destColumn = this.dataGrid.columnManager.getColumnByIndex(data.type, column.getResolvedIndex());

destColumn.toggleSort();
}

private handleKeyDown(event: KeyboardEvent): void {
if (!this.dataGrid.focused) {
return;
}

event.preventDefault();
event.stopPropagation();

const focusedCell = this.dataGrid.cellFocusManager.focusedCellData;
const column: DataGridColumn|null = focusedCell && this.dataGrid.columnManager.takeColumnByCell(focusedCell);
const key = event.keyCode;
const charCode = String.fromCharCode(key);

if (!charCode) {
return;
}

this.handleHighlighterKeyDown(charCode, column);
this.handleNumKeyDown(event, charCode, column);
this.handleArrowKeyDown(event);
}

private handleHighlighterKeyDown(charCode: string, column: DataGridColumn|null) {
switch(charCode.toUpperCase()){
case 'H':
column && column.toggleHighlighter(HIGHLIGHTER_TYPE.heatmap);
break;
case 'U':
column && column.toggleHighlighter(HIGHLIGHTER_TYPE.uniqueEntries);
break;
}
}

private handleArrowKeyDown(event: KeyboardEvent) {
if (event.keyCode < 37 || event.keyCode > 40) {
return;
}

this.dataGrid.cellFocusManager.setFocusedCellByArrowKey(event.keyCode);
}

private handleNumKeyDown(event: KeyboardEvent, charCode: string, column: DataGridColumn|null) {
if (event.keyCode < 48 || event.keyCode > 57) { //numbers 1..9
return;
}

if (event.shiftKey && column) {
column.setDataTypePrecission(parseInt(charCode));
} else {
this.dataGrid.columnManager.setColumnsDataTypePrecission(parseInt(charCode));
}
}
}
Loading

0 comments on commit f99ec04

Please sign in to comment.