Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(igx-grid): Copy into clipboard behavior #5282

Merged
merged 16 commits into from
Jul 11, 2019
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion projects/igniteui-angular/src/lib/core/grid-selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,11 +371,14 @@ export class IgxGridSelectionService {
}
}

keyboardStateOnFocus(node: ISelectionNode, emitter: EventEmitter<GridSelectionRange>): void {
keyboardStateOnFocus(node: ISelectionNode, emitter: EventEmitter<GridSelectionRange>, dom): void {
const kbState = this.keyboardState;

// Focus triggered by keyboard navigation
if (kbState.active) {
if (isChromium()) {
this._moveSelectionChrome(dom);
}
// Start generating a range if shift is hold
if (kbState.shift) {
this.dragSelect(node, kbState);
Expand Down Expand Up @@ -525,6 +528,11 @@ export class IgxGridSelectionService {
}
}

/**
* (╯°□°)╯︵ ┻━┻
* Chrome and Chromium don't care about the active
* range after keyboard navigation, thus this.
*/
_moveSelectionChrome(node: Node) {
const selection = window.getSelection();
selection.removeAllRanges();
Expand All @@ -534,3 +542,7 @@ export class IgxGridSelectionService {
selection.addRange(range);
}
}

export function isChromium(): boolean {
return (/Chrom|e?ium/g.test(navigator.userAgent) || /Google Inc/g.test(navigator.vendor)) && !/Edge/g.test(navigator.userAgent);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<ng-template #defaultCell>
<div igxTextHighlight [cssClass]="highlightClass" [activeCssClass]="activeHighlightClass" [groupName]="gridID"
<div igxTextHighlight style="pointer-events: none" [cssClass]="highlightClass" [activeCssClass]="activeHighlightClass" [groupName]="gridID"
[value]="formatter ? formatter(value) : column.dataType === 'number' ? (value | igxdecimal: grid.locale) : column.dataType === 'date' ? (value | igxdate: grid.locale) : value"
[row]="rowData" [column]="this.column.field" [containerClass]="'igx-grid__td-text'"
class="igx-grid__td-text">{{ formatter ? formatter(value) : column.dataType === 'number' ? (value | igxdecimal:
Expand Down
12 changes: 1 addition & 11 deletions projects/igniteui-angular/src/lib/grids/cell.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,8 +520,6 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy {
protected isInCompositionMode = false;
protected compositionStartHandler;
protected compositionEndHandler;
protected focusHandlerIE;
protected focusOut;
private _highlight: IgxTextHighlightDirective;


Expand Down Expand Up @@ -552,12 +550,6 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy {
// Hitting Enter with IME submits and exits from edit mode instead of first closing the IME dialog
this.nativeElement.addEventListener('compositionstart', this.compositionStartHandler);
this.nativeElement.addEventListener('compositionend', this.compositionEndHandler);

// https://stackoverflow.com/q/51404782
this.focusHandlerIE = (e: FocusEvent) => this.onFocus(e);
this.focusOut = () => this.onBlur();
this.nativeElement.addEventListener('focusin', this.focusHandlerIE);
this.nativeElement.addEventListener('focusout', this.focusOut);
}
});
}
Expand All @@ -575,8 +567,6 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy {
if (isIE()) {
this.nativeElement.removeEventListener('compositionstart', this.compositionStartHandler);
this.nativeElement.removeEventListener('compositionend', this.compositionEndHandler);
this.nativeElement.removeEventListener('focusin', this.focusHandlerIE);
this.nativeElement.removeEventListener('focusout', this.focusOut);
}
});
}
Expand Down Expand Up @@ -792,7 +782,7 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy {
}

this.selectionService.primaryButton = true;
this.selectionService.keyboardStateOnFocus(node, this.grid.onRangeSelection);
this.selectionService.keyboardStateOnFocus(node, this.grid.onRangeSelection, this.nativeElement);
}

/**
Expand Down
110 changes: 102 additions & 8 deletions projects/igniteui-angular/src/lib/grids/grid-base.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
import { Subject } from 'rxjs';
import { takeUntil, first, filter } from 'rxjs/operators';
import { IgxSelectionAPIService } from '../core/selection';
import { cloneArray, isEdge, isNavigationKey, CancelableEventArgs, flatten, mergeObjects } from '../core/utils';
import { cloneArray, isEdge, isNavigationKey, CancelableEventArgs, flatten, mergeObjects, isIE } from '../core/utils';
import { DataType } from '../data-operations/data-util';
import { FilteringLogic, IFilteringExpression } from '../data-operations/filtering-expression.interface';
import { IGroupByRecord } from '../data-operations/groupby-record.interface';
Expand Down Expand Up @@ -89,6 +89,7 @@ import { IgxGridFilteringRowComponent } from './filtering/grid-filtering-row.com
import { IgxDragIndicatorIconDirective } from './row-drag.directive';
import { IgxDragDirective } from '../directives/dragdrop/dragdrop.directive';
import { DeprecateProperty } from '../core/deprecateDecorators';
import { CharSeparatedValueData } from '../services/csv/char-separated-value-data';

const MINIMUM_COLUMN_WIDTH = 136;
const FILTER_ROW_HEIGHT = 50;
Expand All @@ -102,6 +103,11 @@ const MIN_ROW_EDITING_COUNT_THRESHOLD = 2;

export const IgxGridTransaction = new InjectionToken<string>('IgxGridTransaction');

export interface IGridClipboardEvent {
data: any[];
cancel: boolean;
}

export interface IGridCellEventArgs {
cell: IgxGridCellComponent;
event: Event;
Expand Down Expand Up @@ -1535,6 +1541,13 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
@Output()
public onRowDragEnd = new EventEmitter<IRowDragEndEventArgs>();

/**
* Emitted when a copy operation is executed.
* Fired only if copy behavior is enabled through the [`clipboardOptions`]{@link IgxGridBaseComponent#clipboardOptions}.
*/
@Output()
onGridCopy = new EventEmitter<IGridClipboardEvent>();

/**
* @hidden
*/
Expand Down Expand Up @@ -2313,6 +2326,29 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
}
}

/**
* Controls the copy behavior of the grid.
*/
@Input()
clipboardOptions = {
/**
* Enables/disables the copy behavior
*/
enabled: true,
/**
* Include the columns headers in the clipboard output.
*/
copyHeaders: true,
/**
* Apply the columns formatters (if any) on the data in the clipboard output.
*/
copyFormatters: true,
/**
* The separator used for formatting the copy output. Defaults to `\t`.
*/
separator: '\t'
};

/**
* @hidden
*/
Expand All @@ -2332,7 +2368,10 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements

/* End of toolbar related definitions */

// TODO: Document
/**
* Emitted when making a range selection either through
* drag selection or through keyboard selection.
*/
@Output()
onRangeSelection = new EventEmitter<GridSelectionRange>();

Expand Down Expand Up @@ -2577,6 +2616,10 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements

private keydownHandler(event) {
const key = event.key.toLowerCase();
// TODO: Move in a separate handler on the `grid body`.
// if (event.ctrlKey && key === 'c' && isIE()) {
// this.copyHandler(null, true);
// }
if ((isNavigationKey(key) && event.keyCode !== 32) || key === 'tab' || key === 'pagedown' || key === 'pageup') {
event.preventDefault();
if (key === 'pagedown') {
Expand Down Expand Up @@ -4787,7 +4830,8 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
return this.selectionService.ranges;
}

extractDataFromSelection(source: any[]): any[] {

protected extractDataFromSelection(source: any[], formatters = false, headers = false): any[] {
let columnsArray: IgxColumnComponent[];
let record = {};
const selectedData = [];
Expand All @@ -4805,7 +4849,9 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
columnsArray = this.getSelectableColumnsAt(each);
columnsArray.forEach((col) => {
if (col) {
record[col.field] = source[row][col.field];
const key = headers ? col.header || col.field : col.field;
record[key] = formatters && col.formatter ? col.formatter(source[row][col.field])
: source[row][col.field];
}
});
}
Expand All @@ -4832,10 +4878,15 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
}
}

getSelectedData() {
/**
*
* Returns an array of the current cell selection in the form of `[{ column.field: cell.value }, ...]`.
* If `formatters` is enabled, the cell value will be formatted by its respective column formatter (if any).
* If `headers` is enabled, it will use the column header (if any) instead of the column field.
*/
getSelectedData(formatters = false, headers = false) {
const source = this.verticalScrollContainer.igxForOf;

return this.extractDataFromSelection(source);
return this.extractDataFromSelection(source, formatters, headers);
}

/**
Expand All @@ -4859,14 +4910,57 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements
/**
* @hidden
*/
// @HostListener('scroll', ['$event'])
public scrollHandler(event) {
this.parentVirtDir.getHorizontalScroll().scrollLeft += event.target.scrollLeft;
this.verticalScrollContainer.getVerticalScroll().scrollTop += event.target.scrollTop;
event.target.scrollLeft = 0;
event.target.scrollTop = 0;
}

copyHandlerIE() {
if (isIE()) {
this.copyHandler(null, true);
}
}

/**
* @hidden
* @internal
*/
public copyHandler(event, ie11 = false) {
if (!this.clipboardOptions.enabled || this.crudService.inEditMode) {
return;
}

const data = this.getSelectedData(this.clipboardOptions.copyFormatters, this.clipboardOptions.copyHeaders);
const ev = { data, cancel: false } as IGridClipboardEvent;
this.onGridCopy.emit(ev);

if (ev.cancel) {
return;
}

const transformer = new CharSeparatedValueData(ev.data, this.clipboardOptions.separator);
let result = transformer.prepareData();

if (!this.clipboardOptions.copyHeaders) {
result = result.substring(result.indexOf('\n') + 1);
}

if (ie11) {
(window as any).clipboardData.setData('Text', result);
return;
}

event.preventDefault();

/* Necessary for the hiearachical case but will probably have to
change how getSelectedData is propagated in the hiearachical grid
*/
event.stopPropagation();
event.clipboardData.setData('text/plain', result);
}

/**
* This method allows you to navigate to a position
* in the grid based on provided `rowindex` and `visibleColumnIndex`,
Expand Down
Loading