File

core/src/table/table.directive.ts

Description

Place this directive on the top-level table element:

<table tableEditor  (rowChange)='listener()'>
            ....
</table>

This directive only works in combination with [teCell] directives placed on HTMLTableCellElements and [teRow] directives placed on HTMLTableRowElements. See the examples.

Implements

AfterContentInit OnInit OnDestroy

Metadata

Selector table[tableEditor]

Index

Methods
Outputs

Outputs

rowChange

Bind to the rowChange attribrute to listen to RowChangeEvents, see the details.

$event Type: EventEmitter

Methods

Public trigger
trigger(target: TeExecutor)

Use this method to programatically control the movements in the table. See the guide in the README for an example.

Parameters :
Name Type Optional
target TeExecutor No
Returns : void
import {
	Directive,
	ContentChildren,
	QueryList,
	AfterContentInit,
	IterableDiffer,
	IterableDiffers,
	ElementRef,
	Output,
	EventEmitter,
	OnInit,
	Renderer2,
	OnDestroy,
	ViewContainerRef,
	Inject,
	Type
} from '@angular/core';
import { TableEditorRowDirective } from './row.directive';
import { AbstractTableCell, CellDisabledState } from '../control-value-accessors/abstract-table-cell';
import { AbstractNavigator, NavigationAction, TE_NAVIGATORS } from '../navigators/navigator';
import { Subscription } from 'rxjs';
import { RowChangeEvent } from '../utils/row-change-event';

/**
 * An interface implemented by an instruction set to trigger a movement in a {@link TableEditorDirective}. Both `row` and `cell` are optional. If both are left blank, the instruction set is interpreted as an exit command. If `cell` is left blank, the first enabled cell in the row will be selected. Only leaving `row` blank will throw an error.
 */
export interface TeExecutor {
	row: TableEditorRowDirective | null;
	cell: AbstractTableCell | null;
}

/**
 * Place this directive on the top-level `table` element:
 * ```html
<table tableEditor  (rowChange)='listener()'>
			....
</table>
```
 * This directive only works in combination with `[teCell]` directives placed on `HTMLTableCellElement`s and `[teRow]` directives placed on `HTMLTableRowElement`s. See the [examples]{@link ../../demo/#/examples/basic}.
 */
@Directive({
	selector: 'table[tableEditor]'
})
export class TableEditorDirective implements AfterContentInit, OnInit, OnDestroy {
	/** Bind to the `rowChange` attribrute to listen to `RowChangeEvent`s, see [the details]{@link RowChangeEvent}. */
	@Output('rowChange')
	public rowChange = new EventEmitter<RowChangeEvent<any>>();
	/** @internal  */
	private navigators: Array<AbstractNavigator>;
	/** @internal  */
	private currentRow: TableEditorRowDirective | null = null;
	/** @internal  */
	private currentCell: AbstractTableCell | null = null;
	/** @internal  */
	private differ: IterableDiffer<TableEditorRowDirective>;
	/** @internal  */
	private subscriptions: Array<Subscription> = [];
	/** @internal  */
	private unlistenNavigators: Array<() => void> = [];
	/** @internal  */
	private _blockNavigation = false;
	/** @internal  */
	private get blockNavigation() {
		const rv = this._blockNavigation;
		this._blockNavigation = false;
		return rv;
	}
	/** @internal  */
	private set blockNavigation(value: boolean) {
		this._blockNavigation = value;
	}
	/** @internal  */
	@ContentChildren(TableEditorRowDirective)
	private rows: QueryList<TableEditorRowDirective>;
	/** @internal  */
	constructor(
		private element: ElementRef<HTMLTableElement>,
		private renderer: Renderer2,
		private differs: IterableDiffers,
		private vc: ViewContainerRef,
		@Inject(TE_NAVIGATORS) navigators: Array<Type<AbstractNavigator>>
	) {
		this.navigators = navigators.map(ctor => new ctor(renderer, element, this.actionParser.bind(this)));
	}
	/**
	 * Use this method to programatically control the movements in the table. See the guide in the README for an example.
	 * @param  TeExecutor target An instruction set that implements the [TeExecutor interface]{@link TeExecutor}.
	 */
	public trigger(target: TeExecutor) {
		if (target.row != null && !target.cell) {
			if (target.row!.cells.length) {
				target.cell = target.row.cells[0];
			}
		}
		this.executeFor(target);
	}
	/**
	 * @internal @ignore
	 * A method used internally to cellify and inputify specific rows based on an [instruction set]{@link TeExecutor}.
	 * @param  TeExecutor target
	 * @param  NavigationAction action
	 */
	private executeFor(target: TeExecutor, action: NavigationAction | null = null) {
		if (target.row) {
			let currentRow: TableEditorRowDirective | null = null;
			if (target.row !== this.currentRow) {
				if (this.currentRow) {
					currentRow = this.currentRow;
					this.currentRow.cellify();
				}
				target.row.inputify();
				this.rowChange.emit(new RowChangeEvent(currentRow, target.row, action));
				this.currentRow = target.row;
			}
		} else {
			this.currentRow!.cellify();
			this.rowChange.emit(new RowChangeEvent(this.currentRow, null, action));
			this.currentRow = null;
		}
		if (target.cell && target.cell !== this.currentCell) {
			target.cell.focus();
			this.currentCell = target.cell;
		}
	}

	/**
	 * @internal @ignore
	 * A method used internally to convert a {@link NavigationAction} into an [instruction set]{@link TeExecutor}
	 * @param  NavigationAction action
	 * @returns void
	 */
	private actionParser(action: NavigationAction | undefined): void {
		const _action = action;
		if (this.blockNavigation || (this.currentRow === null && action === NavigationAction.Exit)) return;
		const executor: TeExecutor = { row: null, cell: null };

		const currentRow = this.currentRow!;
		const currentCells = currentRow.cells.filter(c => c.disabled === CellDisabledState.Enabled);
		const cellIdx = currentCells.indexOf(this.currentCell!);
		let absoluteIdx = currentRow.cells.indexOf(this.currentCell!);
		const currentRows = this.rows.toArray();
		const rowIdx = currentRows.indexOf(currentRow);
		let nextCellIdx;
		let nextCell;
		let nextRowIdx;
		let nextRow;
		if (action === NavigationAction.Right || action === NavigationAction.Left) {
			const indexOperator = action === NavigationAction.Left ? '--' : '++';
			nextCellIdx = cellIdx;
			eval('nextCellIdx' + indexOperator);
			nextCell = currentCells[nextCellIdx];
			if (nextCell) {
				executor.cell = nextCell;
				executor.row = this.currentRow;
				return this.executeFor(executor, _action);
			} else {
				action = action === NavigationAction.Left ? NavigationAction.Up : NavigationAction.Down;
				cellIdx > 0 ? (absoluteIdx = 0) : (absoluteIdx = currentRow.cells.length - 1);
			}
		}
		if (action === NavigationAction.Up || action === NavigationAction.Down) {
			const indexOperator = action === NavigationAction.Up ? '--' : '++';
			nextRowIdx = rowIdx;
			while (nextCell === undefined) {
				/* assures that it will jump over a disabled row */
				eval('nextRowIdx' + indexOperator);
				nextRow = currentRows[nextRowIdx];
				if (nextRow) {
					const nextRowAllCells = nextRow.cells;
					const nextRowEnabledCells = nextRowAllCells.filter(c => c.disabled === CellDisabledState.Enabled);
					executor.row = nextRow;

					nextCell = nextRowAllCells[absoluteIdx];
					if (nextCell.disabled === CellDisabledState.Disabled) {
						if (absoluteIdx > nextRowEnabledCells.length - 1) {
							nextCell = nextRowEnabledCells[nextRowEnabledCells.length - 1];
						} else {
							nextCell = nextRowEnabledCells[absoluteIdx];
						}
					}
					executor.cell = nextCell;
				} else {
					executor.row = null;
					executor.cell = null;
					break;
				}
			}
		}
		return this.executeFor(executor, _action);
	}

	/** @internal  */
	ngOnDestroy() {
		if (this.currentRow) {
			this.currentRow.cellify();
		}
		this.subscriptions.forEach(s => s.unsubscribe());
		this.unlistenNavigators.forEach(un => un());
		this.vc.clear();
	}
	/** @internal  Instantiates the navigators. */
	ngOnInit() {
		this.navigators.forEach(nav => this.unlistenNavigators.push(nav.listener()));
	}
	/** @internal  Subcribing to the rows and managing rows that are added dynamically. */
	ngAfterContentInit() {
		const rows = this.rows.toArray();
		rows.forEach(row => {
			this.subscriptions.push(row.teCellClick.subscribe((executor: TeExecutor) => this.executeFor(executor)));
			this.subscriptions.push(
				row.teBlockNavigationEventEmitter.subscribe(() => {
					this.blockNavigation = true;
				})
			);
		});
		this.differ = this.differs.find(rows).create();
		this.differ.diff(rows);
		const changesSubscription = this.rows.changes.subscribe((changes: QueryList<TableEditorRowDirective>) => {
			const newRows = this.rows.toArray();
			const diff = this.differ.diff(newRows)!;
			diff.forEachAddedItem(r => {
				r.item.teCellClick.subscribe((executor: TeExecutor) => this.executeFor(executor));
				r.item.teBlockNavigationEventEmitter.subscribe(() => {
					this.blockNavigation = true;
				});
			});
		});
		// keeping the subsriptions in once place to make it easier to unregister everything upon destroy
		this.subscriptions.push(changesSubscription);
	}
}

result-matching ""

    No results matching ""