UNPKG

@sparser/au2-data-grid

Version:
246 lines (221 loc) 8.36 kB
import { onResolve, onResolveAll, resolve, } from '@aurelia/kernel'; import { BindingContext, Scope, } from '@aurelia/runtime'; import { bindable, ICustomAttributeController, ICustomAttributeViewModel, IHydratedController, IHydratedParentController, IRenderLocation, ISyntheticView, templateController, } from '@aurelia/runtime-html'; import { ChangeType, GridStateChangeSubscriber, GridStateModel, OrderChangeData, OrderChangeDropLocation, } from './grid-state.js'; import { SortOption, } from './sorting-options.js'; /** * Template controller to render the headers. * @internal */ @templateController('grid-headers') export class GridHeaders implements ICustomAttributeViewModel, GridStateChangeSubscriber { private readonly location: IRenderLocation = resolve(IRenderLocation); public readonly $controller!: ICustomAttributeController<this>; // This is set by the controller after this instance is constructed @bindable public state!: GridStateModel; private headers!: ISyntheticView[]; private promise: Promise<void> | void = void 0; /** * Key: original column-index; Value: view-index */ private _indexMap: Map<number, number> = new Map<number, number>(); public get indexMap(): Map<number, number> { return this._indexMap; } public attaching( initiator: IHydratedController, parent: IHydratedParentController, ): void | Promise<void> { const indexMap = this._indexMap; indexMap.clear(); const location = this.location; const state = this.state; const columns = state.columns; let len = 0; const headers = this.headers = columns.reduce((acc, column, i) => { if (column.hidden) return acc; acc.push(column.headerViewFactory!.create(initiator).setLocation(location)); indexMap.set(i, len++); return acc; }, [] as ISyntheticView[]); const activationPromises = new Array<void | Promise<void>>(len); for (let i = 0; i < len; i++) { const header = headers[i]; header.nodes.link(headers[i + 1]?.nodes ?? location); activationPromises[i] = header.activate(initiator, parent, Scope.create(new BindingContext('state', columns[i]))); } this.queue(() => onResolveAll(...activationPromises)); state.addSubscriber(this); return this.promise; } public detaching(initiator: IHydratedController, parent: IHydratedParentController): void | Promise<void> { this.state.removeSubscriber(this); this.queue(() => onResolveAll(...this.headers.map((header) => header.deactivate(initiator, parent)))); return this.promise; } public dispose(): void { const headers = this.headers; const len = headers.length; for (let i = 0; i < len; i++) { headers[i].dispose(); } this.headers.length = 0; } public handleGridStateChange(type: ChangeType.Width): void; public handleGridStateChange(type: ChangeType.Order, value: OrderChangeData): void; public handleGridStateChange(type: ChangeType.Sort, newValue: SortOption<Record<string, unknown>>, oldValue: SortOption<Record<string, unknown>> | null): void; public handleGridStateChange(type: ChangeType, value?: SortOption<Record<string, unknown>> | OrderChangeData, _oldValue?: SortOption<Record<string, unknown>> | null): void { if (type !== ChangeType.Order) return; handleReordering(this.headers, value as OrderChangeData, this.location, this._indexMap); } private queue(action: () => void | Promise<void>): void { const previousPromise = this.promise; let promise: void | Promise<void> = void 0; promise = this.promise = onResolve( onResolve(previousPromise, action), () => { if (this.promise === promise) { this.promise = void 0; } } ); } } /** * Template controller to render the content cells. * @internal */ @templateController('grid-content') export class GridContent implements ICustomAttributeViewModel { private readonly location: IRenderLocation = resolve(IRenderLocation); @bindable public item: unknown; @bindable public state!: GridStateModel; public readonly $controller!: ICustomAttributeController<this>; // This is set by the controller after this instance is constructed private cells!: ISyntheticView[]; private promise: Promise<void> | void = void 0; /** * Key: original column-index; Value: view-index */ private _indexMap: Map<number, number> = new Map<number, number>(); public attaching( initiator: IHydratedController, parent: IHydratedParentController, ): void | Promise<void> { const headersTc: GridHeaders = this.$controller .parent // synthetic ?.parent // repeater ?.parent // grid ?.children ?.find(c => c.viewModel instanceof GridHeaders) ?.viewModel as GridHeaders; if (headersTc == null) throw new Error('The grid-headers is not found.'); const indexMap = this._indexMap = headersTc.indexMap; const item = this.item; const location = this.location; const state = this.state; const columns = state.columns; const len = indexMap.size; const cells = this.cells = new Array<ISyntheticView>(len); const activationPromises = new Array<void | Promise<void>>(len); let i = 0; for (const [key,] of indexMap) { const cell = cells[i++] = columns[key].contentViewFactory!.create(initiator).setLocation(location); activationPromises[i] = cell.activate(initiator, parent, Scope.create(new BindingContext('item', item))); } this.queue(() => onResolveAll(...activationPromises)); state.addSubscriber(this); return this.promise; } public detaching(initiator: IHydratedController, parent: IHydratedParentController): void | Promise<void> { this.state.removeSubscriber(this); this.queue(() => onResolveAll(...this.cells.map((cell) => cell.deactivate(initiator, parent)))); return this.promise; } public dispose(): void { const cells = this.cells; const len = cells.length; for (let i = 0; i < len; i++) { cells[i].dispose(); } this.cells.length = 0; } private queue(action: () => void | Promise<void>): void { const previousPromise = this.promise; let promise: void | Promise<void> = void 0; promise = this.promise = onResolve( onResolve(previousPromise, action), () => { if (this.promise === promise) { this.promise = void 0; } } ); } public handleGridStateChange(type: ChangeType.Width): void; public handleGridStateChange(type: ChangeType.Order, value: OrderChangeData): void; public handleGridStateChange(type: ChangeType.Sort, newValue: SortOption<Record<string, unknown>>, oldValue: SortOption<Record<string, unknown>> | null): void; public handleGridStateChange(type: ChangeType, value?: SortOption<Record<string, unknown>> | OrderChangeData, _oldValue?: SortOption<Record<string, unknown>> | null): void { if (type !== ChangeType.Order) return; handleReordering(this.cells, value as OrderChangeData, this.location, this._indexMap); } } function handleReordering( views: ISyntheticView[], changeData: OrderChangeData, location: IRenderLocation, indexMap: Map<number, number>, ): void { const fromIdx = changeData.fromIndex; let toIdx = changeData.toIndex; const dropLocation = changeData.location; const fromNodes = views[indexMap.get(fromIdx)!].nodes; const toNodes = views[indexMap.get(toIdx)!].nodes; // link the next node with the previous node views[fromIdx - 1]?.nodes.link(views[fromIdx + 1]?.nodes ?? location); switch (dropLocation) { case OrderChangeDropLocation.Before: views[toIdx - 1]?.nodes.link(fromNodes); fromNodes.link(toNodes); if (fromIdx < toIdx) { toIdx--; } break; case OrderChangeDropLocation.After: fromNodes.link(views[toIdx + 1]?.nodes ?? location); toNodes.link(fromNodes); if (fromIdx > toIdx) { toIdx++; } break; } fromNodes.addToLinked(); views.splice( toIdx, 0, views.splice(fromIdx, 1)[0] ); }