UNPKG

devexpress-gantt

Version:

DevExpress Gantt Control

922 lines (855 loc) 40.8 kB
import { BarManager } from "../BarManager/BarManager"; import { Browser } from "@devexpress/utils/lib/browser"; import { CommandManager } from "../Commands/CommandManager"; import { CreateTaskHistoryItem } from "../Model/History/HistoryItems/Task/CreateTaskHistoryItem"; import { DateRange } from "../Model/WorkingTime/DateRange"; import { DateTimeUtils } from "../Model/WorkingTime/DateTimeUtils"; import { DateUtils } from "./Utils/DateUtils"; import { Dependency } from "../Model/Entities/Dependency"; import { DependencyType } from "../Model/Entities/Enums"; import { EditingSettings } from "./Settings/EditingSettings"; import { FullScreenHelperSettings } from "./Settings/InitSettings/FullScreenHelperSettings"; import { FullScreenModeHelper } from "./Helpers/FullScreenModeHelper"; import { GanttClientCommand } from "../Commands/ClientCommand"; import { GanttExportCalculator } from "../Export/Pdf/Calculator"; import { History } from "../Model/History/History"; import { ICommand } from "../Interfaces/ICommand"; import { IGanttOwner } from "../Interfaces/IGanttOwner"; import { IGanttView } from "../Interfaces/IGanttView"; import { IHistory } from "../Model/History/IHistory"; import { isDefined } from "@devexpress/utils/lib/utils/common"; import { ITaskAreaContainer } from "../Interfaces/ITaskAreaContainer"; import { ModelChangesDispatcher } from "../Model/Dispatchers/ModelChangesDispatcher"; import { ModelManipulator } from "../Model/Manipulators/ModelManipulator"; import { PdfGanttExporter } from "../Export/Pdf/Exporter"; import { RenderHelper } from "./Render/RenderHelper"; import { Resource } from "../Model/Entities/Resource"; import { Settings } from "./Settings/Settings"; import { Size } from "@devexpress/utils/lib/geometry/size"; import { StripLineSettings } from "./Settings/StripLineSettings"; import { Task } from "../Model/Entities/Task"; import { TaskEditController } from "./Edit/TaskEditController"; import { TaskEditSettings } from "./Settings/InitSettings/TaskEditSettings"; import { ValidationController } from "../Model/Validation/ValidationController"; import { ValidationControllerSettings } from "./Settings/InitSettings/ValidationControllerSettings"; import { ValidationSettings } from "./Settings/ValidationSettings"; import { ViewType, TaskTitlePosition } from "./Helpers/Enums"; import { ViewVisualModel } from "../Model/VisualModel/VisualModel"; import { ViewVisualModelItem } from "../Model/VisualModel/ViewVisualModelItem"; import { GanttViewApi } from "./Api/GanttViewApi"; import { EvtUtils } from "@devexpress/utils/lib/utils/evt"; import { IHistoryListener } from "../Interfaces/IHistiryListener"; import { DomUtils } from "@devexpress/utils/lib/utils/dom"; import { Point } from "@devexpress/utils/lib/geometry/point"; import { ITaskAreaEventsListener, TaskAreaEventsListener } from "./Events/TaskAreaEventsListener"; import { DialogBase } from "../Dialogs/DialogBase"; import { GanttDataObjectNames } from "../Model/Entities/DataObject"; export class GanttView implements IGanttView { static cachedPrefix = "cached_"; static taskAreaScrollLeftKey = GanttView.cachedPrefix + "taskAreaScrollLeft"; static taskAreaScrollTopKey = GanttView.cachedPrefix + "taskAreaScrollTop"; static taskTextHeightKey = GanttView.cachedPrefix + "taskTextHeight"; private _taskAreaEventsListener: ITaskAreaEventsListener; ganttOwner: IGanttOwner; barManager: BarManager; commandManager: CommandManager; dataRange: DateRange; dispatcher: ModelChangesDispatcher; fullScreenModeHelper: FullScreenModeHelper; history: IHistory; modelManipulator: ModelManipulator; range: DateRange; renderHelper: RenderHelper; settings: Settings; taskEditController: TaskEditController; validationController: ValidationController; viewModel: ViewVisualModel; ganttViewApi: GanttViewApi; currentSelectedTaskID = ""; isFocus: boolean = false; private _updateWithModelReloadLockedCounter: number = 0; private _pendingUpdateInfo: Record<string, any>; scaleCount: number = 2; tickSize: Size = new Size(0, 0); public currentZoom: number = 1; private stripLinesUpdaterId: number = null; _scrollTimeOut: number; constructor(element: HTMLElement, ganttOwner: IGanttOwner, settings: any) { this.ganttOwner = ganttOwner; this.settings = Settings.parse(settings); this.initValidationController(); this.renderHelper = new RenderHelper(this); this.renderHelper.initMarkup(element); this.loadOptionsFromGanttOwner(); this.renderHelper.init(this.tickSize, this.range, this.settings.viewType, this.viewModel, this.settings.firstDayOfWeek); this.commandManager = new CommandManager(this); this.barManager = new BarManager(this.commandManager, this.ganttOwner.bars); this.initTaskEditController(); this.history = new History(this._getHistoryListener()); this.initFullScreenModeHelper(); this.updateView(); this._scrollTimeOut = setTimeout(() => { this.scrollLeftByViewType(); }, 0); this.initializeStripLinesUpdater(); this.initGanttViewApi(); } initGanttViewApi(): void { this.ganttViewApi = new GanttViewApi(this); } private _getHistoryListener(): IHistoryListener { const listener = { onTransactionStart: this.onHistoryTransactionStart.bind(this), onTransactionEnd: this.onHistoryTransactionEnd.bind(this), }; return listener; } protected onHistoryTransactionStart(): void { this.lockUpdateWithReload(); } protected onHistoryTransactionEnd(): void { this.unlockUpdateWithReload(); } public lockUpdateWithReload(): void { this._updateWithModelReloadLockedCounter++; } public unlockUpdateWithReload(): void { this._updateWithModelReloadLockedCounter--; if(this._updateWithModelReloadLockedCounter === 0 && this._pendingUpdateInfo) { this.updateWithDataReload(this._pendingUpdateInfo.keepExpandState); this._pendingUpdateInfo = null; } } initValidationController(): void { const validationControllerSettings = ValidationControllerSettings.parse({ getViewModel: () => { return this.viewModel; }, getHistory: () => { return this.history; }, getModelManipulator: () => { return this.modelManipulator; }, getRange: () => { return this.range; }, getValidationSettings: () => { return this.settings.validation; }, updateOwnerInAutoParentMode: () => { this.updateOwnerInAutoParentMode(); }, getIsValidateDependenciesRequired: () => { return this.isValidateDependenciesRequired(); } }); this.validationController = new ValidationController(validationControllerSettings); } initTaskEditController(): void { const taskEditSettings = TaskEditSettings.parse({ destroyTemplate: (container: HTMLElement) => { this.destroyTemplate(container); }, formatDate: (date: Date) => { return this.getDateFormat(date); }, getRenderHelper: () => { return this.renderHelper; }, getGanttSettings: () => { return this.settings; }, getViewModel: () => { return this.viewModel; }, getCommandManager: () => { return this.commandManager; }, getModelManipulator: () => { return this.modelManipulator; }, getValidationController: () => { return this.validationController; } }); this.taskEditController = new TaskEditController(taskEditSettings); } public get taskAreaEventsListener(): ITaskAreaEventsListener { this._taskAreaEventsListener ??= new TaskAreaEventsListener(this); return this._taskAreaEventsListener; } initFullScreenModeHelper(): void { const fullScreenModeSeettings = FullScreenHelperSettings.parse({ getMainElement: () => { return this.getOwnerControlMainElement(); }, adjustControl: () => { this.adjustOwnerControl(); } } ); this.fullScreenModeHelper = new FullScreenModeHelper(fullScreenModeSeettings); } getDateRange(modelStartDate: Date, modelEndDate: Date): DateRange { const visibleAreaTime = this.getVisibleAreaTime(); let start = this.settings.startDateRange || DateUtils.adjustStartDateByViewType(new Date(modelStartDate.getTime() - visibleAreaTime), this.settings.viewType, this.settings.firstDayOfWeek); let end = this.settings.endDateRange || DateUtils.adjustEndDateByViewType(new Date(modelEndDate.getTime() + visibleAreaTime), this.settings.viewType, this.settings.firstDayOfWeek); if(this.settings.startDateRange && start > end) end = start; else if(this.settings.endDateRange && start > end) start = end; return new DateRange(start, end); } getVisibleAreaTime(): number { const visibleTickCount = Math.ceil(this.renderHelper.getTaskAreaContainerWidth() / this.tickSize.width); return visibleTickCount * DateUtils.getTickTimeSpan(this.settings.viewType); } zoomIn(leftPos: number = this.renderHelper.getTaskAreaContainerWidth() / 2): void { this.ganttViewApi.zoomIn(leftPos); } zoomOut(leftPos: number = this.renderHelper.getTaskAreaContainerWidth() / 2): void { this.ganttViewApi.zoomOut(leftPos); } scrollToDate(date: any): void { if(date) { const scrollDate = date instanceof Date ? DateUtils.getOrCreateUTCDate(date) : DateUtils.parse(date); this.scrollToDateCore(scrollDate, 0); } } public showDialog(name: string, parameters: any, callback: (params: any) => void, afterClosing: () => void) { this.ganttOwner.showDialog(name, parameters, callback, afterClosing); } public showPopupMenu(info: any) { this.ganttOwner.showPopupMenu(info); } public hidePopupMenu(): void { if(this.ganttOwner.hidePopupMenu) this.ganttOwner.hidePopupMenu(); } public collapseAll(): void { this.ganttOwner.collapseAll(); } public expandAll(): void { this.ganttOwner.expandAll(); } public onGanttViewContextMenu(evt: any, key: any, type: string): boolean { return this.ganttOwner.onGanttViewContextMenu(evt, key, type); } public changeGanttTaskSelection(id: string, selected: boolean): void { this.ganttOwner.changeGanttTaskSelection(id, selected); } public hideTaskEditControl() : void { this.taskEditController.hide(); } public scrollLeftByViewType(): void { const adjustedStartDate = DateUtils.roundStartDate(this.dataRange.start, this.settings.viewType); this.scrollToDateCore(adjustedStartDate, 1); } public scrollToDateCore(date: Date, addLeftPos: number): void { this.renderHelper.setTaskAreaContainerScrollLeftToDate(date, addLeftPos); } onVisualModelChanged(): void { this.resetAndUpdate(); } private initializeStripLinesUpdater() { if(this.settings.stripLines.showCurrentTime) this.stripLinesUpdaterId = setInterval(() => { this.renderHelper.recreateStripLines(); }, Math.max(this.settings.stripLines.currentTimeUpdateInterval, 100)); } private clearStripLinesUpdater() { if(this.stripLinesUpdaterId) clearInterval(this.stripLinesUpdaterId); this.stripLinesUpdaterId = null; } getGanttViewStartDate(tasks: Task[]): Date { if(!tasks) return new Date(); const dates = tasks.map(t => typeof t.start === "string" ? new Date(t.start) : t.start).filter(d => isDefined(d)); return dates.length > 0 ? dates.reduce((min, d) => d < min ? d : min, dates[0]) : new Date(); } getGanttViewEndDate(tasks: Task[]): Date { if(!tasks) return new Date(); const dates = tasks.map(t => typeof t.end === "string" ? new Date(t.end) : t.end).filter(d => isDefined(d)); return dates.length > 0 ? dates.reduce((max, d) => d > max ? d : max, dates[0]) : new Date(); } getTask(index: number): Task { const item = this.getViewItem(index); return item?.task; } getViewItem(index: number): ViewVisualModelItem { return this.viewModel?.items[index]; } isValidateDependenciesRequired(): boolean { return this.settings.validation.validateDependencies && this.settings.showDependencies; } updateTickSizeWidth(): void { this.tickSize.width = this.renderHelper.etalonScaleItemWidths * this.currentZoom; } updateView(): void { this.onBeginUpdateView(); this.renderHelper.setTimeScaleContainerScrollLeft(this.taskAreaContainerScrollLeft); this.processScroll(false); this.processScroll(true); this.ganttOwner.onGanttScroll(this.taskAreaContainerScrollTop); this.onEndUpdateView(); } protected onBeginUpdateView(): void { this[GanttView.taskAreaScrollTopKey] = this.renderHelper.taskAreaContainerScrollTop; this[GanttView.taskAreaScrollLeftKey] = this.renderHelper.taskAreaContainerScrollLeft; } protected onEndUpdateView(): void { delete this[GanttView.taskAreaScrollTopKey]; delete this[GanttView.taskAreaScrollLeftKey]; delete this[GanttView.taskTextHeightKey]; } get taskAreaContainerScrollTop(): number { return this[GanttView.taskAreaScrollTopKey] ?? this.renderHelper.taskAreaContainerScrollTop; } get taskAreaContainerScrollLeft(): number { return this[GanttView.taskAreaScrollLeftKey] ?? this.renderHelper.taskAreaContainerScrollLeft; } processScroll(isVertical: boolean): void { this.hideTaskEditControl(); this.renderHelper.processScroll(isVertical); } allowTaskAreaBorders(isVerticalScroll: boolean): boolean { return isVerticalScroll ? this.settings.areHorizontalBordersEnabled : this.settings.areVerticalBordersEnabled; } getScaleItemText(index: number, scale: ViewType): string { return this.renderHelper.getScaleItemText(index, scale); } getTaskText(index: number): string { return this.renderHelper.getTaskText(index); } public rowHasChildren(index: number): boolean { const item = this.getViewItem(index); return item?.children.length > 0; } public rowHasSelection(index: number): boolean { const item = this.getViewItem(index); return item?.selected; } getAllVisibleTaskIndices(start?: number, end?: number): number[] { return this.viewModel.getAllVisibleTaskIndices(start, end); } public getVisibleDependencyKeysByTaskRange(indices: number[]): string[] { if(!this.settings.showDependencies) return []; const model = this.viewModel; const taskKeys = indices.map(i => model.tasks.items[i].internalId); const dependencies = model.dependencies.items; return dependencies.filter(d => taskKeys.indexOf(d.successorId) > -1 || taskKeys.indexOf(d.predecessorId) > -1).map(d => d.internalId); } getTreeListTableStyle(): Record<string, any> { return this.ganttOwner.getTreeListTableStyle?.(); } getTreeListColCount(): number { return this.ganttOwner.getTreeListColCount?.(); } getTreeListHeaderInfo(colIndex: number): Record<string, any> { return this.ganttOwner.getTreeListHeaderInfo?.(colIndex); } getTreeListCellInfo(rowIndex: number, colIndex: number, key: any): Record<string, any> { return this.ganttOwner.getTreeListCellInfo?.(rowIndex, colIndex, key); } getTreeListEmptyDataCellInfo(): Record<string, any> { return this.ganttOwner.getTreeListEmptyDataCellInfo?.(); } public exportToPdf(options: Record<string, any>): any { options["docCreateMethod"] ??= this.getDefaultPdfDocCreateMethod(); const exporter = new PdfGanttExporter(new GanttExportCalculator(this, options)); return exporter.export(); } private getDefaultPdfDocCreateMethod(): (prop?: Record<string, any>) => any { return window["jspdf"]?.["jsPDF"]; } getTaskDependencies(taskInternalId: any): Array<Dependency> { return this.viewModel.dependencies.items.filter(d => d.predecessorId == taskInternalId || d.successorId == taskInternalId); } isHighlightRowElementAllowed(index: number): boolean { const viewItem = this.getViewItem(index); return index % 2 !== 0 && this.settings.areAlternateRowsEnabled || viewItem?.children.length > 0; } calculateAutoViewType(startDate, endDate): ViewType { const diffInHours = (endDate.getTime() - startDate.getTime()) / (1000 * 3600); if(diffInHours > 24 * 365) return ViewType.Years; if(diffInHours > 24 * 30) return ViewType.Months; if(diffInHours > 24 * 7) return ViewType.Weeks; if(diffInHours > 24) return ViewType.Days; if(diffInHours > 6) return ViewType.SixHours; if(diffInHours > 1) return ViewType.Hours; return ViewType.TenMinutes; } getExternalTaskAreaContainer(parent: HTMLElement): ITaskAreaContainer { return this.ganttOwner.getExternalTaskAreaContainer(parent); } prepareExternalTaskAreaContainer(element: HTMLElement, info: Record<string, any>): void { return this.ganttOwner.prepareExternalTaskAreaContainer(element, info); } getHeaderHeight(): number { return this.ganttOwner.getHeaderHeight(); } changeTaskExpanded(publicId: any, expanded: boolean): void { const task = this.getTaskByPublicId(publicId); if(task) this.viewModel.changeTaskExpanded(task.internalId, expanded); } expandTask(id: string): void { this.viewModel.changeTaskExpanded(id, true); } collapseTask(id: string): void { this.viewModel.changeTaskExpanded(id, false); } showTask(id: string): void { this.viewModel.changeTaskVisibility(id, true); } hideTask(id: string): void { this.viewModel.changeTaskVisibility(id, false); } getTaskVisibility(id: string): boolean { return this.viewModel.getTaskVisibility(id); } unselectCurrentSelectedTask(): void { this.unselectTask(this.currentSelectedTaskID); } getTaskSelected(id: string): boolean { return this.viewModel.getTaskSelected(id); } setViewType(viewType: ViewType, autoPositioning: boolean = true): void { this.ganttViewApi.setViewType(viewType, autoPositioning); } setViewTypeRange(min: ViewType, max: ViewType): void { this.ganttViewApi.setViewTypeRange(min, max); } setTaskTitlePosition(taskTitlePosition: TaskTitlePosition): void { if(this.settings.taskTitlePosition !== taskTitlePosition) { this.settings.taskTitlePosition = taskTitlePosition; this.resetAndUpdate(); } } setShowResources(showResources: boolean): void { if(this.settings.showResources !== showResources) { this.settings.showResources = showResources; this.resetAndUpdate(); } } toggleResources(): void { this.setShowResources(!this.settings.showResources); } setShowDependencies(showDependencies: boolean): void { if(this.settings.showDependencies !== showDependencies) { this.settings.showDependencies = showDependencies; this.resetAndUpdate(); } } toggleDependencies(): void { this.setShowDependencies(!this.settings.showDependencies); } setFirstDayOfWeek(firstDayOfWeek: number): void { if(this.settings.firstDayOfWeek !== firstDayOfWeek) { this.settings.firstDayOfWeek = firstDayOfWeek; this.resetAndUpdate(); } } setStartDateRange(start: Date): void { if(!DateTimeUtils.areDatesEqual(this.settings.startDateRange, start)) { this.settings.startDateRange = new Date(start); this.resetAndUpdate(); } } setEndDateRange(end: Date): void { if(!DateTimeUtils.areDatesEqual(this.settings.endDateRange, end)) { this.settings.endDateRange = new Date(end); this.resetAndUpdate(); } } loadOptionsFromGanttOwner(): void { this.tickSize.height = this.ganttOwner.getRowHeight(); const tasksData = this.ganttOwner.getGanttTasksData(); this.dataRange = new DateRange(this.getGanttViewStartDate(tasksData), this.getGanttViewEndDate(tasksData)); if(this.settings.viewType == undefined) this.settings.viewType = this.calculateAutoViewType(this.dataRange.start, this.dataRange.end); this.updateTickSizeWidth(); this.range = this.getDateRange(this.dataRange.start, this.dataRange.end); this.dispatcher = new ModelChangesDispatcher(); const modelChangesListener = this.ganttOwner.getModelChangesListener(); if(modelChangesListener) this.dispatcher.onModelChanged.add(modelChangesListener); this.viewModel = new ViewVisualModel(this, tasksData, this.ganttOwner.getGanttDependenciesData(), this.ganttOwner.getGanttResourcesData(), this.ganttOwner.getGanttResourceAssignmentsData(), this.range, this.ganttOwner.getGanttWorkTimeRules() ); this.modelManipulator = new ModelManipulator(this.viewModel, this.dispatcher); this.history?.historyItems.forEach(i => i.setModelManipulator(this.modelManipulator)); } resetAndUpdate(): void { this.range = this.getDateRange(this.dataRange.start, this.dataRange.end); this.viewModel.updateRange(this.range); this.renderHelper.resetAndUpdate(this.tickSize, this.range, this.settings.viewType, this.viewModel, this.settings.firstDayOfWeek); if(Browser.IE) this.taskEditController.createElements(); this.updateView(); } cleanMarkup(): void { this.setNormalScreenMode(); this.renderHelper.taskAreaManagerDetachEvents(); this.taskEditController.detachEvents(); this.clearStripLinesUpdater(); this.renderHelper.reset(); clearTimeout(this._scrollTimeOut); } checkAndProcessModelChanges(): boolean { const tasks = this.ganttOwner.getGanttTasksData(); const changed = this.viewModel.refreshTaskDataIfRequires(tasks); if(changed) this.resetAndUpdate(); return changed; } updateHistoryObsoleteInsertedKey(oldKey: string, newKey: string, type: string): void { this.history?.updateObsoleteInsertedKey(oldKey, newKey, type); if(type === GanttDataObjectNames.dependency) this.renderHelper.updateRenderedConnectorLinesId(oldKey, newKey); } updateRowHeights(height: number): void { if(this.tickSize.height !== height) { this.tickSize.height = height; const leftPosition = this.renderHelper.getTaskAreaContainerScrollLeft(); this.resetAndUpdate(); this.renderHelper.setTaskAreaContainerScrollLeft(leftPosition); } } selectTask(id: string): void { this.selectDependency(null); this.viewModel.changeTaskSelected(id, true); this.currentSelectedTaskID = id; this.updateBarManager(); } unselectTask(id: string): void { this.viewModel.changeTaskSelected(id, false); this.updateBarManager(); } selectTaskById(publicId: any): void { this.unselectCurrentSelectedTask(); const task = this.getTaskByPublicId(publicId); if(task) this.selectTask(task.internalId); } selectDependency(id: string): void { this.taskEditController.selectDependency(id); this.renderHelper.createConnectorLines(); } getTaskAreaContainer(): ITaskAreaContainer { return this.renderHelper.taskAreaContainer; } setWidth(value: number): void { this.renderHelper.setMainElementWidth(value); } setHeight(value: number): void { this.renderHelper.setMainElementHeight(value); } setAllowSelection(value: boolean): void { this.settings.allowSelectTask = value; } setEditingSettings(value: EditingSettings): void { this.settings.editing = value; this.updateBarManager(); } setValidationSettings(value: ValidationSettings): void { this.settings.validation = value; } setRowLinesVisible(value: boolean): void { this.settings.areHorizontalBordersEnabled = value; this.renderHelper.prepareTaskAreaContainer(); this.resetAndUpdate(); } setStripLines(stripLines: StripLineSettings): void { this.settings.stripLines = StripLineSettings.parse(stripLines); this.clearStripLinesUpdater(); this.initializeStripLinesUpdater(); this.renderHelper.recreateStripLines(); } deleteTask(key: any): void { const task = this.getTaskByPublicId(key.toString()); if(task) this.commandManager.removeTaskCommand.execute(task.internalId, false, true); } insertTask(data: any): string { if(data) { const parentId = data.parentId != null ? String(data.parentId) : null; const parent = this.getTaskByPublicId(parentId); const rootId = this.viewModel.getRootTaskId(); const start = typeof data.start === "string" ? new Date(data.start) : data.start as Date; const end = typeof data.end === "string" ? new Date(data.end) : data.end as Date; const taskData = { parentId: rootId && parentId === rootId ? parentId : parent?.internalId, title: data.title, start: start, end: end, progress: parseInt(data.progress) || 0, color: data.color }; if(this.commandManager.createTaskCommand.execute(taskData)) return this.getLastInsertedTaskId(); } return ""; } updateTask(key: any, data: Record<string, any>): void { const task = this.getTaskByPublicId(key.toString()); const dataToExecute = this._getTaskDataForUpdate(data, task); if(dataToExecute) this.commandManager.updateTaskCommand.execute(task.internalId, dataToExecute); } getTaskData(key: any): any { const task = this.getTaskByPublicId(key.toString()); if(task) return this.viewModel.getTaskObjectForDataSource(task); } insertDependency(data: any): void { if(!data) return; const predecessorId = String(data.predecessorId); const predecessor = this.getTaskByPublicId(predecessorId); const successorId = String(data.successorId); const successor = this.getTaskByPublicId(successorId); const type = data.type as DependencyType; if(predecessor && successor && this.validationController.canCreateDependency(predecessorId, successorId)) this.commandManager.createDependencyCommand.execute(predecessor.internalId, successor.internalId, type); } deleteDependency(key: any): void { const internalKey = this.viewModel.convertPublicToInternalKey("dependency", key); if(isDefined(internalKey)) this.commandManager.removeDependencyCommand.execute(internalKey); } getDependencyData(key: any): any { return this.viewModel.getDependencyObjectForDataSource(key); } insertResource(data: any, taskKeys: any[]): void { if(data) { const callback = (id: any) => { if(isDefined(taskKeys)) for(let i = 0; i < taskKeys.length; i++) this.assignResourceToTask(id, taskKeys[i]); }; this.commandManager.createResourceCommand.execute(String(data.text), data.color && String(data.color), callback); } } deleteResource(key: any): void { const internalKey = this.viewModel.convertPublicToInternalKey("resource", key); if(isDefined(internalKey)) this.commandManager.removeResourceCommand.execute(internalKey); } assignResourceToTask(resourceKey: any, taskKey: any): void { const resourceInternalKey = this.viewModel.convertPublicToInternalKey("resource", resourceKey); const taskInternalKey = this.viewModel.convertPublicToInternalKey("task", taskKey); if(isDefined(resourceInternalKey) && isDefined(taskInternalKey)) this.commandManager.assignResourceCommand.execute(resourceInternalKey, taskInternalKey); } unassignResourceFromTask(resourceKey: any, taskKey: any): void { const assignment = this.viewModel.findAssignment(resourceKey, taskKey); if(assignment) this.commandManager.deassignResourceCommand.execute(assignment.internalId); } unassignAllResourcesFromTask(taskPublicKey: any): void { const taskInternalKey = this.viewModel.convertPublicToInternalKey("task", taskPublicKey); const assignments = this.viewModel.findAllTaskAssignments(taskInternalKey); assignments.forEach(assignment => this.commandManager.deassignResourceCommand.execute(assignment.internalId)); } getResourceData(key: any): any { return this.viewModel.getResourceObjectForDataSource(key); } getResourceAssignmentData(key: any): any { return this.viewModel.getResourceAssignmentObjectForDataSource(key); } getTaskResources(key): Array<Resource> { const model = this.viewModel; const task = model.getItemByPublicId("task", key) as Task; return (task && model.getAssignedResources(task).items) || []; } getVisibleTaskKeys(): Array<any> { return this.viewModel.getVisibleTasks().map(t => t.id); } getVisibleDependencyKeys(): Array<any> { return this.viewModel.getVisibleDependencies().map(d => d.id); } getVisibleResourceKeys(): Array<any> { return this.viewModel.getVisibleResources().map(r => r.id); } getVisibleResourceAssignmentKeys(): Array<any> { return this.viewModel.getVisibleResourceAssignments().map(a => a.id); } getTasksExpandedState(): Record<any, boolean> { return this.viewModel.getTasksExpandedState(); } applyTasksExpandedState(state: Record<any, boolean>): void { this.viewModel.applyTasksExpandedState(state); } updateWithDataReload(keepExpandState: boolean): void { if(this._updateWithModelReloadLockedCounter > 0) { this._pendingUpdateInfo = { keepExpandState: keepExpandState }; return; } const state = keepExpandState && this.getTasksExpandedState(); this.loadOptionsFromGanttOwner(); if(keepExpandState) this.applyTasksExpandedState(state); else this.resetAndUpdate(); const activeDialog = DialogBase.activeInstance; if(activeDialog && activeDialog.canRefresh && activeDialog.getDialogName() === "TaskEdit") activeDialog.refresh(); this.dispatcher.notifyGanttViewUpdated(); } onBrowserWindowResize(): void { if(this.fullScreenModeHelper.isInFullScreenMode) this.fullScreenModeHelper.adjustControlInFullScreenMode(); else this.adjustOwnerControl(); } getTaskTreeLine(taskKey: string): Array<string> { return this.viewModel.getTaskTreeLine(taskKey).reverse(); } isInFullScreenMode(): boolean { return !!this.fullScreenModeHelper?.isInFullScreenMode; } setFullScreenMode(): void { if(!this.isInFullScreenMode()) this.fullScreenModeHelper.toggle(); } setNormalScreenMode(): void { if(this.isInFullScreenMode()) this.fullScreenModeHelper.toggle(); } setTaskValue(id: string, fieldName: string, newValue: any): boolean { const command = this.commandManager.updateTaskCommand; const task = this.getTaskByPublicId(id); const data = { }; if(task) { if(fieldName === "title") data[fieldName] = newValue ? newValue : ""; if(fieldName === "progress") data[fieldName] = newValue; if(fieldName === "start") data[fieldName] = DateTimeUtils.getMinDate(newValue, task.end); if(fieldName === "end") data[fieldName] = DateTimeUtils.getMaxDate(newValue, task.start); } return Object.keys(data).length > 0 ? command.execute(task.internalId, data) : false; } getLastInsertedTaskId(): string { const createTaskItems = this.history.historyItems.filter(i => i instanceof CreateTaskHistoryItem); const lastItem = createTaskItems[createTaskItems.length - 1] as CreateTaskHistoryItem; return lastItem && lastItem.insertedKey; } getTaskByPublicId(id: string): Task { return this.viewModel.tasks.getItemByPublicId(id); } getPrevTask(taskId: string): Task { const item = this.viewModel.findItem(taskId); if(!item) return null; const parent = item.parent || this.viewModel.root; const index = parent.children.indexOf(item) - 1; return index > -1 ? item.parent.children[index].task : item.parent.task; } getTaskIdByInternalId(internalId : string): string { const item = this.viewModel.findItem(internalId); const task = item && item.task; return task ? task.id : null; } isTaskHasChildren(taskId: string): boolean { const item = this.viewModel.findItem(taskId); return item && item.children.length > 0; } requireFirstLoadParentAutoCalc(): boolean { const owner = this.ganttOwner; return owner.getRequireFirstLoadParentAutoCalc && owner.getRequireFirstLoadParentAutoCalc(); } public updateOwnerInAutoParentMode(): void { if(this.viewModel.parentAutoCalc) this.dispatcher.notifyParentDataRecalculated(this.viewModel.getCurrentTaskData()); } getOwnerControlMainElement(): HTMLElement { const owner = this.ganttOwner; return owner.getMainElement && owner.getMainElement(); } adjustOwnerControl(): void { const owner = this.ganttOwner; if(owner.adjustControl) owner.adjustControl(); } applySettings(settings: any, preventViewUpdate: boolean = false): void { const ganttSettings = Settings.parse(settings); const preventUpdate = preventViewUpdate || this.settings.equal(ganttSettings); this.settings = ganttSettings; if(!preventUpdate) this.resetAndUpdate(); } getDataUpdateErrorCallback(): () => void { const history = this.history; const currentHistoryItemInfo = history.getCurrentProcessingItemInfo(); return () => { this.dispatcher.lock(); history.rollBackAndRemove(currentHistoryItemInfo); this.dispatcher.unlock(); this.updateBarManager(); }; } setTaskTooltipContentTemplate(taskTooltipContentTemplate: () => void): void { this.settings.taskTooltipContentTemplate = taskTooltipContentTemplate; } setTaskProgressTooltipContentTemplate(taskProgressTooltipContentTemplate: () => void): void { this.settings.taskProgressTooltipContentTemplate = taskProgressTooltipContentTemplate; } setTaskTimeTooltipContentTemplate(taskTimeTooltipContentTemplate: () => void): void { this.settings.taskTimeTooltipContentTemplate = taskTimeTooltipContentTemplate; } setTaskContentTemplate(taskContentTemplate: () => boolean): void { this.settings.taskContentTemplate = taskContentTemplate; } updateBarManager(): void { this.barManager.updateItemsState([]); } onTaskAreaClick(rowIndex: number, evt: Event): boolean { const clickedItem = this.viewModel.items[rowIndex]; return clickedItem && this.onTaskClick(clickedItem.task?.id, evt); } onTaskAreaDblClick(rowIndex: number, evt: Event): void { const clickedItem = this.viewModel.items[rowIndex]; if(clickedItem && this.onTaskDblClick(clickedItem.task.id, evt)) this.commandManager.showTaskEditDialog.execute(clickedItem.task); } onTaskAreaContextMenu(rowIndex: number, evt: Event, type: string): void { const isDependency = type === "dependency"; const event = evt as MouseEvent | TouchEvent; const model = this.viewModel; const key = isDependency ? model.convertInternalToPublicKey("dependency", EvtUtils.getEventSource(evt).getAttribute("dependency-id")) : model.items[rowIndex]?.task?.id; if(this.onGanttViewContextMenu(evt, key, type)) { const info = { event: evt, type: type, key: key, position: new Point(EvtUtils.getEventX(event), EvtUtils.getEventY(event)) }; this.showPopupMenu(info); } } onTaskSelectionChanged(rowIndex: number, evt: Event): void { const clickedTask = this.viewModel.items[rowIndex]; this.isFocus = DomUtils.isItParent(this.renderHelper.taskArea, EvtUtils.getEventSource(evt)); if(clickedTask && this.isFocus && this.settings.allowSelectTask) setTimeout(() => { this.changeGanttTaskSelection(clickedTask.task.id, true); }, 0); } onTaskClick(key: any, evt: any): boolean { if(!this.ganttOwner.onTaskClick) return true; return this.ganttOwner.onTaskClick(key, evt); } onTaskDblClick(key: any, evt: any): boolean { if(!this.ganttOwner.onTaskDblClick) return true; return this.ganttOwner.onTaskDblClick(key, evt); } getDateFormat(date: Date): string { return this.ganttOwner.getFormattedDateText ? this.ganttOwner.getFormattedDateText(date) : this.getDefaultDateFormat(date); } getDefaultDateFormat(date: Date): string { return ("0" + date.getDate()).slice(-2) + "/" + ("0" + (date.getMonth() + 1)).slice(-2) + "/" + date.getFullYear() + " " + ("0" + date.getHours()).slice(-2) + ":" + ("0" + date.getMinutes()).slice(-2); } destroyTemplate(container: HTMLElement): void { if(this.ganttOwner.destroyTemplate) this.ganttOwner.destroyTemplate(container); else container.innerHTML = ""; } onTaskAreaSizeChanged(info: Record<string, any>): void { if(this.ganttOwner.onTaskAreaSizeChanged) this.ganttOwner.onTaskAreaSizeChanged(info); } showTaskEditDialog(): void { this.commandManager.showTaskEditDialog.execute(); } showTaskDetailsDialog(taskPublicKey: any): void { const task = this.getTaskByPublicId(taskPublicKey); if(task) this.commandManager.showTaskEditDialog.execute(task, true); } showResourcesDialog(): void { this.commandManager.showResourcesDialog.execute(); } getCommandByKey(key: GanttClientCommand): ICommand { return this.commandManager.getCommand(key); } private _getTaskDataForUpdate(data: Record<string, any>, task: Task): Record<string, any> { const result = { }; if(task && data) { if(isDefined(data.title) && data.title !== task.title) result["title"] = data.title; if(isDefined(data.progress) && data.progress !== task.progress) result["progress"] = data.progress; if(isDefined(data.start) && data.start !== task.start) result["start"] = data.start; if(isDefined(data.end) && data.end !== task.end) result["end"] = data.end; if(isDefined(data.color) && data.color !== task.color) result["color"] = data.color; } return Object.keys(result).length > 0 ? result : null; } public updateViewDataRange(): void { const model = this.viewModel; const minStart = model.getTaskMinStart(); const maxEnd = model.getTaskMaxEnd(); const startChanged = minStart.getTime() < this.dataRange.start.getTime(); const endChanged = maxEnd.getTime() > this.dataRange.end.getTime(); if(startChanged) this.dataRange.start = minStart; if(endChanged) this.dataRange.end = maxEnd; if(startChanged || endChanged) this.resetAndUpdate(); } }