UNPKG

devexpress-gantt

Version:

DevExpress Gantt Control

322 lines (268 loc) 12.8 kB
import { DomUtils } from "@devexpress/utils/lib/utils/dom"; import { Dependency } from "../../Model/Entities/Dependency"; import { Task } from "../../Model/Entities/Task"; import { ViewVisualModelItem } from "../../Model/VisualModel/ViewVisualModelItem"; import { TaskTitlePosition } from "../Helpers/Enums"; import { GridLayoutCalculator } from "../Helpers/GridLayoutCalculator"; import { CustomTaskRender } from "./CustomTaskRender"; import { RenderElementUtils } from "./RenderElementUtils"; import { RenderHelper } from "./RenderHelper"; export class TaskRender { static minTitleOutRightPadding = 5; private _renderHelper: RenderHelper; private _selectionElements: Array<HTMLElement> = []; private _taskElements: Array<HTMLElement> = []; private _fakeTaskWrapper: HTMLElement; customTaskRender: CustomTaskRender; constructor(renderHelper: RenderHelper) { this._renderHelper = renderHelper; this.customTaskRender = new CustomTaskRender(renderHelper, this); } protected get gridLayoutCalculator(): GridLayoutCalculator { return this._renderHelper.gridLayoutCalculator; } public get taskElements(): HTMLElement[] { return this._taskElements; } public get selectionElements(): HTMLElement[] { return this._selectionElements; } protected get taskArea(): HTMLElement { return this._renderHelper.taskArea; } protected get isExternalTaskAreaContainer(): boolean { return this._renderHelper.isExternalTaskAreaContainer; } protected get resourcesElements(): HTMLElement[] { return this._renderHelper.resourcesElements; } protected get hlRowElements():HTMLElement[] { return this._renderHelper.hlRowElements; } protected get renderedRowIndices(): number[] { return this._renderHelper.renderedRowIndices; } protected get taskTitlePosition(): TaskTitlePosition { return this._renderHelper.taskTitlePosition; } protected get showResources(): boolean { return this._renderHelper.showResources; } protected get areHorizontalBordersEnabled(): boolean { return this._renderHelper.areHorizontalBordersEnabled; } protected get taskTextHeightKey(): string { return this._renderHelper.taskTextHeightKey; } get viewModelItems(): ViewVisualModelItem[] { return this._renderHelper.viewModelItems; } isHighlightRowElementAllowed(index: number): boolean { return this._renderHelper.isHighlightRowElementAllowed(index); } getTaskVisibility(index: number): boolean { return this._renderHelper.getTaskVisibility(index); } getTaskText(index: number): string { return this._renderHelper.getTaskText(index); } protected get invalidTaskDependencies(): Dependency[] { return this._renderHelper.invalidTaskDependencies; } public get fakeTaskWrapper(): HTMLElement { this._fakeTaskWrapper ??= this.createFakeTaskWrapper(); return this._fakeTaskWrapper; } getViewItem(index: number): ViewVisualModelItem { return this._renderHelper.getViewItem(index); } getTask(index: number): Task { return this._renderHelper.getTask(index); } createHighlightRowElement(index: number): void { this._renderHelper.createHighlightRowElement(index); } getTaskDependencies(taskInternalId: any): Dependency[] { return this._renderHelper.getTaskDependencies(taskInternalId); } addInvalidTaskDependencies(taskDependencies: Array<Dependency>): void { this._renderHelper.invalidTaskDependencies = this._renderHelper.invalidTaskDependencies.concat(taskDependencies); } removeInvalidTaskDependencies(taskId: any): void { this._renderHelper.invalidTaskDependencies = this._renderHelper.invalidTaskDependencies.filter(d => d.predecessorId != taskId || d.successorId != taskId); } createResources(index: number): void { if(this.showResources) this._renderHelper.createResources(index); } attachEventsOnTask(taskIndex: number): void { this._renderHelper.attachEventsOnTask(taskIndex); } detachEventsOnTask(taskIndex: number): void { this._renderHelper.detachEventsOnTask(taskIndex); } recreateConnectorLineElement(dependencyId: string, forceRender: boolean = false): void { this._renderHelper.recreateConnectorLineElement(dependencyId, forceRender); } renderTaskElement(index: number): void { this._renderHelper.createTaskElement(index); } reset(): void { this._taskElements.forEach((el, i) => this.removeTaskElement(i)); this._selectionElements = []; this._taskElements = []; } createTaskWrapperElement(index: number): void { const taskWrapperInfo = this.gridLayoutCalculator.getTaskWrapperElementInfo(index); RenderElementUtils.create(taskWrapperInfo, index, this.taskArea, this.taskElements); this.taskElements[index].style.display = this.getTaskVisibility(index) ? "" : "none"; } createTaskElement(index: number, taskTemplateFunction: (container: HTMLElement, model: any) => boolean): void { const viewItem = this.getViewItem(index); if(taskTemplateFunction) this.customTaskRender.createCustomTaskElement(index, taskTemplateFunction); else { if(!viewItem.task.isValid() || !viewItem.visible) { const taskDependencies = this.getTaskDependencies(viewItem.task.internalId); this.addInvalidTaskDependencies(taskDependencies); if(viewItem.selected) this.createTaskSelectionElement(index); return; } this.createDefaultTaskElement(index); } } createTaskVisualElement(index: number): HTMLElement { const taskElementInfo = this.gridLayoutCalculator.getTaskElementInfo(index, this.taskTitlePosition !== TaskTitlePosition.Inside); const taskElement = RenderElementUtils.create(taskElementInfo, index, this.taskElements[index]); this.attachEventsOnTask(index); return taskElement; } createDefaultTaskElement(index: number):void { const viewItem = this.getViewItem(index); if(this.isHighlightRowElementAllowed(index)) this.createHighlightRowElement(index); if(viewItem.selected) this.createTaskSelectionElement(index); this.createTaskWrapperElement(index); if(this.taskTitlePosition === TaskTitlePosition.Outside) this.createTaskTextElement(index, this.taskElements[index]); const taskVisualElement = this.createTaskVisualElement(index); if(!viewItem.task.isMilestone()) { if(this.taskTitlePosition === TaskTitlePosition.Inside) this.createTaskTextElement(index, taskVisualElement); this.createTaskProgressElement(index, taskVisualElement); } this.createResources(index); } removeTaskElement(index: number): void { const task = this.getTask(index); if(task) this.removeInvalidTaskDependencies(task.id); this.detachEventsOnTask(index); if(this._renderHelper.hasTaskTemplate()) { const taskWrapper = this.taskElements[index]; const taskTemplateContainer = taskWrapper?.firstElementChild; if(taskTemplateContainer) { this._renderHelper.destroyTemplate(taskTemplateContainer as HTMLElement); taskWrapper.removeChild(taskTemplateContainer); } } RenderElementUtils.remove(null, index, this.taskArea, this.taskElements); RenderElementUtils.remove(null, index, this.taskArea, this.resourcesElements); RenderElementUtils.remove(null, index, this.taskArea, this.selectionElements); if(this.isHighlightRowElementAllowed(index)) RenderElementUtils.remove(null, index, this.taskArea, this.hlRowElements); this.gridLayoutCalculator.resetTaskInfo(index); } recreateTaskElement(index: number): void { const isVisible = this.renderedRowIndices.filter(r => r === index).length > 0; const task = this.getTask(index); if(!task) return; if(isVisible) { this.removeTaskElement(index); this.renderTaskElement(index); } const dependencies = this.getTaskDependencies(task.internalId); if(dependencies.length) dependencies.forEach(d => this.recreateConnectorLineElement(d.internalId, true)); } createFakeTaskWrapper(): HTMLElement { const index = this.viewModelItems.filter(v => v.task && !v.task.isMilestone)[0]?.visibleIndex ?? 0; const calc = this.gridLayoutCalculator; const fakeWrapper = RenderElementUtils.create(calc.getTaskWrapperElementInfo(index), null, this.taskArea); const taskVisualElement = RenderElementUtils.create(calc.getTaskElementInfo(index), null, fakeWrapper); this.createTaskTextElement(index, taskVisualElement); this.createTaskProgressElement(index, taskVisualElement); fakeWrapper.style.display = "none"; return fakeWrapper; } createTaskProgressElement(index: number, parent: HTMLElement): void { const taskProgressInfo = this.gridLayoutCalculator.getTaskProgressElementInfo(index); RenderElementUtils.create(taskProgressInfo, index, parent); } protected getTextWidth(text: string): number { return this._renderHelper.getTextWidth(text); } private _minTextWidth: number; public get minTextWidth() : number { this._minTextWidth ??= 5 * this.getTextWidth("a"); return this._minTextWidth; } createTaskTextElement(index: number, parent: HTMLElement): void { const taskTextInfo = this.gridLayoutCalculator.getTaskTextElementInfo(index, this.taskTitlePosition === TaskTitlePosition.Inside); if(taskTextInfo.additionalInfo["hidden"]) return; const taskTextElement = RenderElementUtils.create(taskTextInfo, index, parent); const text = this.getTaskText(index); if(this.taskTitlePosition === TaskTitlePosition.Outside && taskTextInfo.size.width > 0) { const style = getComputedStyle(taskTextElement); const avaliableTextWidth = taskTextInfo.size.width - DomUtils.pxToInt(style.paddingLeft); if(avaliableTextWidth >= this.minTextWidth) { let rightPadding = DomUtils.pxToInt(style.paddingRight); const textWidth = text ? this.getTextWidth(text) : 0; if(rightPadding && textWidth > avaliableTextWidth - rightPadding) { rightPadding = Math.min(TaskRender.minTitleOutRightPadding, avaliableTextWidth - this.minTextWidth); taskTextElement.style.paddingRight = rightPadding + "px"; } if(textWidth > avaliableTextWidth - rightPadding) { taskTextElement.style.overflowX = "hidden"; taskTextElement.style.textOverflow = "ellipsis"; } } else taskTextElement.style.display = "none"; } if(!text) { this[this.taskTextHeightKey] ??= this.getTaskTextHeight(taskTextElement); taskTextElement.style.height = this[this.taskTextHeightKey]; } taskTextElement.innerText = text; } createTaskSelectionElement(index: number): void { const selectionInfo = this.gridLayoutCalculator.getSelectionElementInfo(index); if(this.isExternalTaskAreaContainer && !this.areHorizontalBordersEnabled) selectionInfo.size.height++; RenderElementUtils.create(selectionInfo, index, this.taskArea, this.selectionElements); } getTaskTextHeight(textElement: HTMLElement): string { textElement.innerText = "WWW"; const height = getComputedStyle(textElement).height; textElement.innerText = ""; return height; } getSmallTaskWidth(etalonPaddingLeft: string): number { let result = 0; if(etalonPaddingLeft != null && etalonPaddingLeft !== "") { const indexOfRem = etalonPaddingLeft.indexOf("rem"); if(indexOfRem > -1) try { const remSize = parseFloat(etalonPaddingLeft.substr(0, indexOfRem)); result = remSize * parseFloat(getComputedStyle(document.documentElement).fontSize); } catch(_) { } else result = DomUtils.pxToInt(etalonPaddingLeft); } return result * 2; } }