UNPKG

@progress/kendo-ui

Version:

This package is part of the [Kendo UI for jQuery](http://www.telerik.com/kendo-ui) suite.

1,380 lines 89.4 kB
//#region ../src/kendo.gantt.timeline.js const __meta__ = { id: "gantt.timeline", name: "Gantt Timeline", category: "web", description: "The Gantt Timeline", depends: [ "dom", "touch", "draganddrop", "icons" ], hidden: true }; (function($) { var Widget = kendo.ui.Widget; var kendoDomElement = kendo.dom.element; var kendoTextElement = kendo.dom.text; var kendoHtmlElement = kendo.dom.html; var isPlainObject = $.isPlainObject; var outerWidth = kendo._outerWidth; var outerHeight = kendo._outerHeight; var extend = $.extend; var isRtl = false; var keys = kendo.keys; var Query = kendo.data.Query; var STRING = "string"; var NS = ".kendoGanttTimeline"; var CLICK = "click"; var DBLCLICK = "dblclick"; var MOUSEMOVE = "mousemove"; var MOUSEENTER = "mouseenter"; var MOUSELEAVE = "mouseleave"; var KEYDOWN = "keydown"; var DOT = "."; var TIME_HEADER_TEMPLATE = ({ start }) => kendo.toString(start, "t"); var DAY_HEADER_TEMPLATE = ({ start }) => kendo.toString(start, "ddd M/dd"); var WEEK_HEADER_TEMPLATE = ({ start, end }) => `${kendo.toString(start, "ddd M/dd")} - ${kendo.toString(kendo.date.addDays(end, -1), "ddd M/dd")}`; var MONTH_HEADER_TEMPLATE = ({ start }) => kendo.toString(start, "MMM"); var YEAR_HEADER_TEMPLATE = ({ start }) => kendo.toString(start, "yyyy"); var RESIZE_HINT = ({ styles }) => `<div class="${styles.marquee}">` + `<div class="${styles.marqueeColor}"></div>` + "</div>"; var RESIZE_TOOLTIP_TEMPLATE = ({ styles, messages, start, end, format }) => `<div class="${styles.tooltipWrapper} k-gantt-resize-hint">` + `<div class="${styles.tooltipContent}">` + `<div>${kendo.htmlEncode(messages.start)}: ${kendo.toString(start, format)}</div>` + `<div>${kendo.htmlEncode(messages.end)}: ${kendo.toString(end, format)}</div>` + "</div>" + "</div>"; var PERCENT_RESIZE_TOOLTIP_TEMPLATE = ({ styles, text }) => `<div ${kendo.attr("style-z-index")}="100002" class="${styles.tooltipWrapper}" >` + `<div class="${styles.tooltipContent}">${text}%</div>` + `<div class="${styles.tooltipCallout}" ${kendo.attr("style-left")}="13px"></div>` + "</div>"; var TASK_TOOLTIP_TEMPLATE = ({ styles, task, messages }) => `<div class="${kendo.htmlEncode(styles.taskDetails)}">` + `<strong>${kendo.htmlEncode(task.title)}</strong>` + `<div class="${styles.taskDetailsPercent}">${kendo.toString(task.percentComplete, "p0")}</div>` + `<ul class="${styles.reset}">` + `<li>${kendo.htmlEncode(messages.start)}: ${kendo.toString(task.start, "h:mm tt ddd, MMM d")}</li>` + `<li>${kendo.htmlEncode(messages.end)}: ${kendo.toString(task.end, "h:mm tt ddd, MMM d")}</li>` + "</ul>" + "</div>"; var OFFSET_TOOLTIP_TEMPLATE = ({ offsetPrefix, offsetText }) => `<span>${offsetPrefix}: ${offsetText}</span>`; var PLANNED_TOOLTIP_TEMPLATE = ({ plannedStart, plannedEnd, startDate, endDate }) => "<div class=\"k-task-content\">" + `<div>${plannedStart}: ${startDate}</div>` + `<div>${plannedEnd}: ${endDate}</div>` + "</div>"; var SIZE_CALCULATION_TEMPLATE = `<table ${kendo.attr("style-visibility")}="hidden">` + "<tbody>" + `<tr ${kendo.attr("style-height")}="{0}">` + "<td>&nbsp;</td>" + "</tr>" + "</tbody>" + "</table>"; var defaultViews = { day: { type: "kendo.ui.GanttDayView" }, week: { type: "kendo.ui.GanttWeekView" }, month: { type: "kendo.ui.GanttMonthView" }, year: { type: "kendo.ui.GanttYearView" } }; function trimOptions(options) { delete options.name; delete options.prefix; delete options.views; return options; } function getWorkDays(options) { var workDays = []; var dayIndex = options.workWeekStart; workDays.push(dayIndex); while (options.workWeekEnd != dayIndex) { if (dayIndex > 6) { dayIndex -= 7; } else { dayIndex++; } workDays.push(dayIndex); } return workDays; } function blurActiveElement() { var activeElement = kendo._activeElement(); if (activeElement && activeElement.nodeName.toLowerCase() !== "body") { $(activeElement).trigger("blur"); } } var viewStyles = { alt: "k-table-row k-table-alt-row", reset: "k-reset", nonWorking: "k-nonwork-hour", header: "k-header k-table-td", gridHeader: "k-grid-header", gridHeaderWrap: "k-grid-header-wrap", gridContent: "k-grid-content", tasksWrapper: "k-gantt-tables", rowsTable: "k-gantt-rows", columnsTable: "k-gantt-columns", tasksTable: "k-gantt-tasks", dependenciesWrapper: "k-gantt-dependencies", resource: "k-resource", resourceAlt: "k-resource k-alt", headerTable: "k-grid-header-table k-table", table: "k-table", tbody: "k-table-tbody", tableRow: "k-table-row", tableCell: "k-table-td", task: "k-task", taskSingle: "k-task-single", taskMilestone: "k-task-milestone", taskSummary: "k-task-summary", taskWrap: "k-task-wrap", taskMilestoneWrap: "k-milestone-wrap", taskSummaryWrap: "k-summary-wrap", taskPlanned: "k-task-planned", taskPlannedMoment: "k-task-moment", taskPlannedDuration: "k-task-duration", taskPlannedMomentLeft: "k-moment-left", taskAdvanced: "k-task-advanced", taskDelayed: "k-task-delayed", taskOffset: "k-task-offset", taskOffsetWrap: "k-task-offset-wrap", taskInnerWrap: "k-task-inner-wrap", resourcesWrap: "k-resources-wrap", taskDot: "k-task-dot", taskDotStart: "k-task-start", taskDotEnd: "k-task-end", taskDragHandle: "k-task-draghandle", taskContent: "k-task-content", taskTemplate: "k-task-template", taskActions: "k-task-actions", taskDelete: "k-task-delete", taskComplete: "k-task-complete", taskDetails: "k-task-details", taskDetailsPercent: "k-task-pct", link: "k-link", iconDelete: "x", taskResizeHandle: "k-resize-handle", taskResizeHandleWest: "k-resize-w", taskResizeHandleEast: "k-resize-e", taskSummaryProgress: "k-task-summary-progress", taskSummaryComplete: "k-task-summary-complete", line: "k-gantt-line", lineHorizontal: "k-gantt-line-h", lineVertical: "k-gantt-line-v", arrowWest: "k-arrow-w", arrowEast: "k-arrow-e", dragHint: "k-drag-hint", dependencyHint: "k-gantt-dependency-hint", tooltipWrapper: "k-tooltip", tooltipContent: "k-tooltip-content", tooltipCallout: "k-callout k-callout-s", callout: "k-callout", marquee: "k-marquee k-gantt-marquee", marqueeColor: "k-marquee-color", offsetTooltipAdvanced: "k-offset-tooltip-advanced", offsetTooltipDelay: "k-offset-tooltip-delayed", plannedTooltip: "k-planned-tooltip" }; var GanttView = kendo.ui.GanttView = Widget.extend({ init: function(element, options) { Widget.fn.init.call(this, element, options); this.title = this.options.title || this.options.name; this.header = this.element.find(DOT + GanttView.styles.gridHeader); this.content = this.element.find(DOT + GanttView.styles.gridContent); this.contentWidth = this.content.width(); this._workDays = getWorkDays(this.options); this._headerTree = options.headerTree; this._taskTree = options.taskTree; this._taskTemplate = options.taskTemplate ? kendo.template(options.taskTemplate, extend({}, kendo.Template, options.templateSettings)) : null; this._dependencyTree = options.dependencyTree; this._taskCoordinates = {}; this._currentTime(); }, destroy: function() { Widget.fn.destroy.call(this); clearTimeout(this._tooltipTimeout); this.headerRow = null; this.header = null; this.content = null; this._dragHint = null; this._resizeHint = null; this._resizeTooltip = null; this._taskTooltip = null; this._percentCompleteResizeTooltip = null; this._headerTree = null; this._taskTree = null; this._dependencyTree = null; }, options: { showWorkHours: false, showWorkDays: false, workDayStart: new Date(1980, 1, 1, 8, 0, 0), workDayEnd: new Date(1980, 1, 1, 17, 0, 0), workWeekStart: 1, workWeekEnd: 5, hourSpan: 1, slotSize: 100, currentTimeMarker: { updateInterval: 1e4 } }, renderLayout: function() { this._slots = this._createSlots(); this._tableWidth = this._calculateTableWidth(); this.createLayout(this._layout()); this._slotDimensions(); this._adjustHeight(); this.content.find(DOT + GanttView.styles.dependenciesWrapper).width(this._tableWidth); }, _adjustHeight: function() { if (this.content) { this.content.height(this.element.height() - outerHeight(this.header)); } }, createLayout: function(rows) { var headers = this._headers(rows); var colgroup = this._colgroup(); var tree = this._headerTree; var header = kendoDomElement("tbody", { className: GanttView.styles.tbody }, headers); var table = kendoDomElement("table", { className: GanttView.styles.headerTable, style: { width: this._tableWidth + "px" }, role: "presentation" }, [colgroup, header]); tree.render([table]); this.headerRow = this.header.find("table").first().find("tr").last(); }, _slotDimensions: function() { var headers = this.headerRow[0].children; var slots = this._timeSlots(); var slot; var header; for (var i = 0, length = headers.length; i < length; i++) { header = headers[i]; slot = slots[i]; slot.offsetLeft = header.offsetLeft; slot.offsetWidth = header.offsetWidth; } }, render: function(tasks) { var taskCount = tasks.length; var styles = GanttView.styles; var contentTable; var rowsTable = this._rowsTable(taskCount); var columnsTable = this._columnsTable(taskCount); var tasksTable = this._tasksTable(tasks); var currentTimeMarker = this.options.currentTimeMarker; var calculatedSize = this.options.calculatedSize; var totalHeight; this._taskTree.render([ rowsTable, columnsTable, tasksTable ]); contentTable = this.content.find(DOT + styles.rowsTable); if (calculatedSize) { totalHeight = calculatedSize.row * tasks.length; this.content.find(DOT + styles.tasksTable).height(totalHeight); contentTable.height(totalHeight); } this._contentHeight = contentTable.height(); this._rowHeight = calculatedSize ? calculatedSize.row : this._contentHeight / contentTable.find("tr").length; this.content.find(DOT + styles.columnsTable).height(this._contentHeight); if (currentTimeMarker !== false && currentTimeMarker.updateInterval !== undefined) { this._renderCurrentTime(); } }, _rowsTable: function(rowCount) { var rows = []; var row; var styles = GanttView.styles; var attributes = [{ className: styles.tableRow }, { className: styles.alt }]; for (var i = 0; i < rowCount; i++) { row = kendoDomElement("tr", attributes[i % 2], [kendoDomElement("td", { className: styles.tableCell }, [kendoTextElement("\xA0")])]); rows.push(row); } return this._createTable(1, rows, { className: styles.rowsTable + " k-grid-table " + styles.table }); }, _columnsTable: function() { var cells = []; var row; var styles = GanttView.styles; var slots = this._timeSlots(); var slotsCount = slots.length; var slot; var slotSpan; var totalSpan = 0; var attributes; for (var i = 0; i < slotsCount; i++) { slot = slots[i]; attributes = { className: styles.tableCell }; slotSpan = slot.span; totalSpan += slotSpan; if (slotSpan !== 1) { attributes.colspan = slotSpan; } if (slot.isNonWorking) { attributes.className += " " + styles.nonWorking; } cells.push(kendoDomElement("td", attributes, [kendoTextElement("\xA0")])); } row = kendoDomElement("tr", { className: styles.tableRow }, cells); return this._createTable(totalSpan, [row], { className: styles.columnsTable + " " + styles.table }); }, _tasksTable: function(tasks) { var rows = []; var row; var cell; var position; var plannedPosition; var task; var styles = GanttView.styles; var coordinates = this._taskCoordinates = {}; var size = this._calculateMilestoneWidth(); var milestoneWidth = Math.round(size.width); var resourcesField = this.options.resourcesField; var className = [styles.resource, styles.resourceAlt]; var calculatedSize = this.options.calculatedSize; var resourcesPosition; var resourcesMargin = this._calculateResourcesMargin(); var taskBorderWidth = this._calculateTaskBorderWidth(); var resourceStyle; var showPlannedTasks = this.options.showPlannedTasks; var attributes = [{ className: styles.tableRow }, { className: styles.alt }]; var taskElement; var addCoordinates = function(rowIndex) { var taskLeft; var taskRight; taskLeft = position.left; taskRight = taskLeft + position.width; if (task.isMilestone()) { taskLeft -= milestoneWidth / 2; taskRight = taskLeft + milestoneWidth; } coordinates[task.id] = { start: taskLeft, end: taskRight, rowIndex }; }; for (var i = 0, l = tasks.length; i < l; i++) { task = tasks[i]; position = this._taskPosition(task); if (showPlannedTasks) { plannedPosition = this._taskPositionPlanned(task); plannedPosition.borderWidth = taskBorderWidth; } position.borderWidth = taskBorderWidth; row = kendoDomElement("tr", attributes[i % 2]); cell = kendoDomElement("td", { className: styles.tableCell }); if (task.start <= this.end && task.end >= this.start) { taskElement = this._renderTask(tasks[i], position, plannedPosition); if (this.options.navigatable) { taskElement.children[0].attr["tabIndex"] = i ? -1 : 0; } cell.children.push(taskElement); if (task[resourcesField] && task[resourcesField].length) { if (isRtl) { resourcesPosition = this._tableWidth - position.left; } else { resourcesPosition = Math.max(position.width || size.clientWidth, 0) + position.left; } resourceStyle = { width: this._tableWidth - (resourcesPosition + resourcesMargin) + "px" }; resourceStyle[isRtl ? "right" : "left"] = resourcesPosition + "px"; if (calculatedSize) { resourceStyle.height = calculatedSize.cell + "px"; } cell.children.push(kendoDomElement("div", { className: styles.resourcesWrap, style: resourceStyle }, this._renderResources(task[resourcesField], className[i % 2]))); } addCoordinates(i); } row.children.push(cell); rows.push(row); } return this._createTable(1, rows, { className: GanttView.styles.tasksTable + " " + styles.table }); }, _createTable: function(colspan, rows, styles) { var cols = []; var colgroup; var tbody; for (var i = 0; i < colspan; i++) { cols.push(kendoDomElement("col")); } colgroup = kendoDomElement("colgroup", null, cols); tbody = kendoDomElement("tbody", { className: GanttView.styles.tbody }, rows); if (!styles.style) { styles.style = {}; } styles.style.width = this._tableWidth + "px"; styles.role = "presentation"; return kendoDomElement("table", styles, [colgroup, tbody]); }, _calculateTableWidth: function() { var slots = this._timeSlots(); var maxSpan = 0; var totalSpan = 0; var currentSpan; var tableWidth; for (var i = 0, length = slots.length; i < length; i++) { currentSpan = slots[i].span; totalSpan += currentSpan; if (currentSpan > maxSpan) { maxSpan = currentSpan; } } tableWidth = Math.round(totalSpan * this.options.slotSize / maxSpan); return tableWidth; }, _calculateMilestoneWidth: function() { var size; var className = GanttView.styles.task + " " + GanttView.styles.taskMilestone; var boundingClientRect; var milestone = $(`<div class="${className}">`).css({ visibility: "hidden", position: "absolute" }); this.content.append(milestone); boundingClientRect = milestone[0].getBoundingClientRect(); size = { "width": boundingClientRect.right - boundingClientRect.left, "clientWidth": milestone[0].clientWidth }; milestone.remove(); return size; }, _calculateResourcesMargin: function() { var margin; var wrapper = $(`<div class="${GanttView.styles.resourcesWrap}">`).css({ visibility: "hidden", position: "absolute" }); this.content.append(wrapper); margin = parseInt(wrapper.css(isRtl ? "margin-right" : "margin-left"), 10); wrapper.remove(); return margin; }, _calculateTaskBorderWidth: function() { var width; var className = GanttView.styles.task + " " + GanttView.styles.taskSingle; var computedStyle; var task = $(`<div class="${className}">`).css({ visibility: "hidden", position: "absolute" }); this.content.append(task); computedStyle = kendo.getComputedStyles(task[0], ["border-left-width"]); width = parseFloat(computedStyle["border-left-width"], 10); task.remove(); return width; }, _renderTask: function(task, position, plannedPosition) { var editable = this.options.editable; var taskLeft = position.left; var styles = GanttView.styles; var wrapClassName = styles.taskWrap; var calculatedSize = this.options.calculatedSize; var dragHandleStyle = {}; var taskWrapAttr = { className: wrapClassName, style: { left: taskLeft + "px" } }; var children = []; var endTaskDotRight = 0; var taskFullWidth = position.width; var taskWrapper, taskElement, progressHandleOffset, plannedElement; var endTaskDotLeft, taskOffsetWrap, offsetElement, offsetWidth; if (calculatedSize) { taskWrapAttr.style.height = calculatedSize.cell + "px"; taskWrapAttr.style["vertical-align"] = "top"; } if (plannedPosition) { if (task.isMilestone()) { plannedElement = this._renderPlannedMilestone(position, plannedPosition); } else { plannedElement = this._renderPlannedSingleTask(position, plannedPosition, task); } children.push(plannedElement); if (isRtl && plannedPosition.left <= position.left) { taskWrapAttr.style.left = plannedPosition.left + "px"; } } if (task.summary) { taskElement = this._renderSummary(task, position, plannedPosition); taskWrapAttr.className += " " + styles.taskSummaryWrap; } else if (task.isMilestone()) { taskElement = this._renderMilestone(task, position); taskWrapAttr.className += " " + styles.taskMilestoneWrap; } else { taskElement = this._renderSingleTask(task, position, plannedPosition); } if (plannedPosition && !task.isMilestone() && task.plannedStart < task.end && task.plannedEnd > task.start && task.plannedEnd < task.end) { if (isRtl) { taskFullWidth = position.left + position.width - plannedPosition.left; } else { taskFullWidth = plannedPosition.left + plannedPosition.width - position.left; } if (isRtl) { offsetWidth = plannedPosition.left - position.left; } else { offsetWidth = position.left + position.width - (plannedPosition.left + plannedPosition.width); } offsetElement = kendoDomElement("div", { className: styles.taskOffset, style: { width: offsetWidth - 2 * plannedPosition.borderWidth + "px" } }); if (editable && editable.resize !== false && editable.update !== false && !task.summary) { if (editable.destroy !== false) { offsetElement.children.push(kendoDomElement("span", { className: styles.taskActions, "aria-hidden": "true" }, [kendoDomElement("a", { className: styles.link + " " + styles.taskDelete, href: "#", "aria-label": "Delete" }, [kendoDomElement($(kendo.ui.icon(styles.iconDelete))[0])])])); } if (isRtl) { offsetElement.children.push(kendoDomElement("span", { className: styles.taskResizeHandle + " " + styles.taskResizeHandleWest, style: { right: position.width - 5 + "px" } })); } else { offsetElement.children.push(kendoDomElement("span", { className: styles.taskResizeHandle + " " + styles.taskResizeHandleEast })); } } taskOffsetWrap = kendoDomElement("div", { className: styles.taskOffsetWrap + " " + styles.taskInnerWrap }, [taskElement, offsetElement]); children.push(taskOffsetWrap); } else if (plannedPosition) { children.push(kendoDomElement("div", { className: styles.taskInnerWrap }, [taskElement])); } else { children.push(taskElement); } taskWrapper = kendoDomElement("div", taskWrapAttr, children); if (editable && editable.dependencyCreate !== false) { if (plannedPosition && task.plannedEnd > task.end) { endTaskDotRight = plannedPosition.left + plannedPosition.width - position.left - position.width - 3 + "px"; } taskWrapper.children.push(kendoDomElement("div", { className: styles.taskDot + " " + styles.taskDotStart })); if (isRtl) { endTaskDotRight = "auto"; if (plannedPosition && task.plannedEnd > task.end) { endTaskDotLeft = position.left - plannedPosition.left + "px"; } } taskWrapper.children.push(kendoDomElement("div", { className: styles.taskDot + " " + styles.taskDotEnd, style: { right: endTaskDotRight, left: endTaskDotLeft } })); } if (!task.summary && !task.isMilestone() && editable && editable.dragPercentComplete !== false && editable.update !== false && this._taskTemplate === null) { progressHandleOffset = Math.round(taskFullWidth * task.percentComplete); dragHandleStyle[isRtl ? "right" : "left"] = progressHandleOffset + "px"; taskWrapper.children.push(kendoDomElement("div", { className: styles.taskDragHandle, style: dragHandleStyle })); } return taskWrapper; }, _renderSingleTask: function(task, position, plannedPosition) { var styles = GanttView.styles; var progressWidth; var taskChildren = []; var taskContent; var editable = this.options.editable; var classes = styles.task + " " + styles.taskSingle; var widthExceptDelay = position.width; if (plannedPosition) { if (task.plannedEnd && task.plannedEnd <= task.start) { classes += " " + styles.taskDelayed; } else if (task.plannedEnd && task.plannedEnd > task.end) { classes += " " + styles.taskAdvanced; } else if (task.plannedEnd && task.plannedEnd < task.end) { if (!isRtl) { widthExceptDelay = widthExceptDelay - (position.left + position.width - plannedPosition.left - plannedPosition.width); } else { widthExceptDelay = widthExceptDelay + position.left - plannedPosition.left; } } } progressWidth = Math.round(widthExceptDelay * task.percentComplete); if (this._taskTemplate !== null) { taskContent = kendoHtmlElement(this._taskTemplate(task)); } else { taskContent = kendoTextElement(task.title); taskChildren.push(kendoDomElement("div", { className: styles.taskComplete, style: { width: progressWidth + "px" }, "aria-hidden": "true" })); } var content = kendoDomElement("div", { className: styles.taskContent }, [kendoDomElement("div", { className: styles.taskTemplate }, [taskContent])]); taskChildren.push(content); if (editable) { if (editable.destroy !== false && (!plannedPosition || !task.plannedEnd || task.end <= task.plannedEnd || task.start >= task.plannedEnd)) { content.children.push(kendoDomElement("span", { className: styles.taskActions, "aria-hidden": "true" }, [kendoDomElement("a", { className: styles.link + " " + styles.taskDelete, href: "#", "aria-label": "Delete" }, [kendoDomElement($(kendo.ui.icon(styles.iconDelete))[0])])])); } if (editable.resize !== false && editable.update !== false) { content.children.push(kendoDomElement("span", { className: styles.taskResizeHandle + " " + styles.taskResizeHandleWest })); content.children.push(kendoDomElement("span", { className: styles.taskResizeHandle + " " + styles.taskResizeHandleEast })); } } var element = kendoDomElement("div", { className: classes, "data-uid": task.uid, role: "treeitem", style: { width: Math.max(widthExceptDelay - position.borderWidth * 2, 0) + "px" } }, taskChildren); return element; }, _renderMilestone: function(task) { var styles = GanttView.styles; var classes = styles.task + " " + styles.taskMilestone; var showPlanned = this.options.showPlannedTasks; if (showPlanned && task.plannedEnd && task.plannedEnd < task.start) { classes += " " + styles.taskDelayed; } else if (task.plannedStart && task.plannedStart > task.end) { classes += " " + styles.taskAdvanced; } return kendoDomElement("div", { className: classes, "data-uid": task.uid, role: "treeitem", "aria-label": task.title }); }, _renderSummary: function(task, position, plannedPosition) { var styles = GanttView.styles; var widthExceptDelay = position.width; var progressWidth; var classes = styles.task + " " + styles.taskSummary; if (plannedPosition) { if (task.plannedEnd && task.plannedEnd <= task.start) { classes += " " + styles.taskDelayed; } else if (task.plannedEnd && task.plannedEnd > task.end) { classes += " " + styles.taskAdvanced; } else if (task.plannedEnd && task.plannedEnd < task.end) { if (!isRtl) { widthExceptDelay = widthExceptDelay - (position.left + position.width - plannedPosition.left - plannedPosition.width); } else { widthExceptDelay = widthExceptDelay + position.left - plannedPosition.left; } } } progressWidth = Math.round(widthExceptDelay * task.percentComplete); var element = kendoDomElement("div", { className: classes, "data-uid": task.uid, role: "treeitem", "aria-label": task.title, style: { width: widthExceptDelay + "px" } }, [kendoDomElement("div", { className: styles.taskSummaryProgress, style: { width: progressWidth + "px" } }, [kendoDomElement("div", { className: styles.taskSummaryComplete, style: { width: position.width + "px" } })])]); return element; }, _renderPlannedSingleTask: function(position, plannedPosition, task) { var styles = GanttView.styles; var children = []; var style = {}; if (task.plannedStart && task.plannedEnd) { children.push(kendoDomElement("div", { className: styles.taskPlannedMoment + " " + styles.taskPlannedMomentLeft })); children.push(kendoDomElement("div", { className: styles.taskPlannedDuration, style: { width: Math.max(plannedPosition.width - plannedPosition.borderWidth * 2 - 16, 0) + "px" } })); children.push(kendoDomElement("div", { className: styles.taskPlannedMoment })); } else if (task.plannedStart) { children.push(kendoDomElement("div", { className: styles.taskPlannedMoment + " " + styles.taskPlannedMomentLeft })); } else if (task.plannedEnd) { children.push(kendoDomElement("div", { className: styles.taskPlannedMoment, style: { "margin-left": Math.max(plannedPosition.width - 5, 0) + "px" } })); } if (isRtl) { style = { "margin-right": position.left - plannedPosition.left + position.width - plannedPosition.width + "px" }; } else { style = { "margin-left": plannedPosition.left - position.left + "px" }; } var element = kendoDomElement("div", { className: styles.taskPlanned, style }, children); return element; }, _renderPlannedMilestone: function(position, plannedPosition) { var styles = GanttView.styles; var style = {}; var element; if (isRtl) { style = { "margin-right": position.left - plannedPosition.left + "px" }; } else { style = { "margin-left": plannedPosition.left - position.left + "px" }; } element = kendoDomElement("div", { className: styles.taskPlanned, style }, [kendoDomElement("div", { className: styles.taskPlannedMoment })]); return element; }, _renderResources: function(resources, className) { var children = []; var resource; for (var i = 0, length = resources.length; i < length; i++) { resource = resources[i]; children.push(kendoDomElement("span", { className, style: { "color": resource.get("color") } }, [kendoTextElement(resource.get("name"))])); } if (isRtl) { children.reverse(); } return children; }, _taskPosition: function(task) { var round = Math.round; var startLeft = round(this._offset(isRtl ? task.end : task.start)); var endLeft = round(this._offset(isRtl ? task.start : task.end)); return { left: startLeft, width: endLeft - startLeft }; }, _taskPositionPlanned: function(task) { var round = Math.round; var startLeft = round(this._offset(isRtl ? task.plannedEnd : task.plannedStart)); var endLeft = round(this._offset(isRtl ? task.plannedStart : task.plannedEnd)); return { left: startLeft, width: endLeft - startLeft }; }, _offset: function(date) { var slots = this._timeSlots(); var slot; var startOffset; var slotDuration; var slotOffset = 0; var startIndex; if (!slots.length) { return 0; } startIndex = this._slotIndex("start", date); slot = slots[startIndex]; if (slot.end < date) { slotOffset = slot.offsetWidth; } else if (slot.start <= date) { startOffset = date - slot.start; slotDuration = slot.end - slot.start; slotOffset = startOffset / slotDuration * slot.offsetWidth; } if (isRtl) { slotOffset = slot.offsetWidth + 1 - slotOffset; } return slot.offsetLeft + slotOffset; }, _slotIndex: function(field, value, reverse) { var slots = this._timeSlots(); var startIdx = 0; var endIdx = slots.length - 1; var middle; if (reverse) { slots = [].slice.call(slots).reverse(); } do { middle = Math.ceil((endIdx + startIdx) / 2); if (slots[middle][field] < value) { startIdx = middle; } else { if (middle === endIdx) { middle--; } endIdx = middle; } } while (startIdx !== endIdx); if (reverse) { startIdx = slots.length - 1 - startIdx; } return startIdx; }, _timeByPosition: function(x, snap, snapToEnd) { var slot = this._slotByPosition(x); if (snap) { return snapToEnd ? slot.end : slot.start; } var offsetLeft = x - this.element.find(DOT + GanttView.styles.tasksTable).offset().left; var duration = slot.end - slot.start; var slotOffset = offsetLeft - slot.offsetLeft; if (isRtl) { slotOffset = slot.offsetWidth - slotOffset; } return new Date(slot.start.getTime() + duration * (slotOffset / slot.offsetWidth)); }, _slotByPosition: function(x) { var offsetLeft = x - this.element.find(DOT + GanttView.styles.tasksTable).offset().left; var slotIndex = this._slotIndex("offsetLeft", offsetLeft, isRtl); return this._timeSlots()[slotIndex]; }, _renderDependencies: function(dependencies) { var elements = []; var tree = this._dependencyTree; for (var i = 0, l = dependencies.length; i < l; i++) { elements.push.apply(elements, this._renderDependency(dependencies[i])); } tree.render(elements); }, _renderDependency: function(dependency) { var predecessor = this._taskCoordinates[dependency.predecessorId]; var successor = this._taskCoordinates[dependency.successorId]; var elements; var method; if (!predecessor || !successor) { return []; } method = "_render" + [ "FF", "FS", "SF", "SS" ][isRtl ? 3 - dependency.type : dependency.type]; elements = this[method](predecessor, successor); for (var i = 0, length = elements.length; i < length; i++) { elements[i].attr["data-uid"] = dependency.uid; } return elements; }, _renderFF: function(from, to) { var lines = this._dependencyFF(from, to, false); lines[lines.length - 1].children[0] = this._arrow(true); return lines; }, _renderSS: function(from, to) { var lines = this._dependencyFF(to, from, true); lines[0].children[0] = this._arrow(false); return lines.reverse(); }, _renderFS: function(from, to) { var lines = this._dependencyFS(from, to, false); lines[lines.length - 1].children[0] = this._arrow(false); return lines; }, _renderSF: function(from, to) { var lines = this._dependencyFS(to, from, true); lines[0].children[0] = this._arrow(true); return lines.reverse(); }, _dependencyFF: function(from, to, reverse) { var that = this; var lines = []; var left = 0; var top = 0; var width = 0; var height = 0; var dir = reverse ? "start" : "end"; var delta; var overlap = 2; var arrowOverlap = 1; var rowHeight = this._rowHeight; var minLineWidth = 10; var fromTop = from.rowIndex * rowHeight + Math.floor(rowHeight / 2) - 1; var toTop = to.rowIndex * rowHeight + Math.floor(rowHeight / 2) - 1; var styles = GanttView.styles; var addHorizontal = function() { lines.push(that._line(styles.line + " " + styles.lineHorizontal, { left: left + "px", top: top + "px", width: width + "px" })); }; var addVertical = function() { lines.push(that._line(styles.line + " " + styles.lineVertical, { left: left + "px", top: top + "px", height: height + "px" })); }; left = from[dir]; top = fromTop; width = minLineWidth; delta = to[dir] - from[dir]; if (delta > 0 !== reverse) { width = Math.abs(delta) + minLineWidth; } if (reverse) { left -= width; width -= arrowOverlap; addHorizontal(); } else { addHorizontal(); left += width - overlap; } if (toTop < top) { height = top - toTop; height += overlap; top = toTop; addVertical(); } else { height = toTop - top; height += overlap; addVertical(); top += height - overlap; } width = Math.abs(left - to[dir]); if (!reverse) { width -= arrowOverlap; left -= width; } addHorizontal(); return lines; }, _dependencyFS: function(from, to, reverse) { var that = this; var lines = []; var left = 0; var top = 0; var width = 0; var height = 0; var rowHeight = this._rowHeight; var minLineHeight = Math.floor(rowHeight / 2); var minLineWidth = 10; var minDistance = 2 * minLineWidth; var delta = to.start - from.end; var overlap = 2; var arrowOverlap = 1; var fromTop = from.rowIndex * rowHeight + Math.floor(rowHeight / 2) - 1; var toTop = to.rowIndex * rowHeight + Math.floor(rowHeight / 2) - 1; var styles = GanttView.styles; var addHorizontal = function() { lines.push(that._line(styles.line + " " + styles.lineHorizontal, { left: left + "px", top: top + "px", width: width + "px" })); }; var addVertical = function() { lines.push(that._line(styles.line + " " + styles.lineVertical, { left: left + "px", top: top + "px", height: height + "px" })); }; left = from.end; top = fromTop; width = minLineWidth; if (reverse) { left += arrowOverlap; if (delta > minDistance) { width = delta - (minLineWidth - overlap); } width -= arrowOverlap; } addHorizontal(); left += width - overlap; if (delta <= minDistance) { height = reverse ? Math.abs(toTop - fromTop) - minLineHeight : minLineHeight; if (toTop < fromTop) { top -= height; height += overlap; addVertical(); } else { addVertical(); top += height; } width = from.end - to.start + minDistance; if (width < minLineWidth) { width = minLineWidth; } left -= width - overlap; addHorizontal(); } if (toTop < fromTop) { height = top - toTop; top = toTop; height += overlap; addVertical(); } else { height = toTop - top; addVertical(); top += height; } width = to.start - left; if (!reverse) { width -= arrowOverlap; } addHorizontal(); return lines; }, _line: function(className, styles) { return kendoDomElement("div", { className, style: styles }); }, _arrow: function(direction) { return kendoDomElement("span", { className: direction ? GanttView.styles.arrowWest : GanttView.styles.arrowEast }); }, _colgroup: function() { var slots = this._timeSlots(); var count = slots.length; var cols = []; for (var i = 0; i < count; i++) { for (var j = 0, length = slots[i].span; j < length; j++) { cols.push(kendoDomElement("col")); } } return kendoDomElement("colgroup", null, cols); }, _createDragHint: function(element) { var styles = GanttView.styles; var plannedElement; this._dragHint = element.clone().addClass(styles.dragHint).css({ "cursor": "move" }); plannedElement = this._dragHint.find(DOT + styles.taskPlanned); plannedElement.css({ "visibility": "hidden" }); if (isRtl && element.find(DOT + styles.taskAdvanced).length > 0) { plannedElement.css({ "margin-right": "auto", "width": 0 }); this._dragHint.find(DOT + styles.taskDotEnd).css({ "left": 0 }); } element.closest("td").append(this._dragHint); }, _updateDragHint: function(start) { var left = this._offset(start); this._dragHint.css({ "left": left }); }, _removeDragHint: function() { if (this._dragHint) { this._dragHint.remove(); this._dragHint = null; } }, _createResizeHint: function(task) { var styles = GanttView.styles; var taskTop = this._taskCoordinates[task.id].rowIndex * this._rowHeight; var tooltipHeight; var tooltipTop; var options = this.options; var messages = options.messages; this._resizeHint = $(RESIZE_HINT({ styles })).css({ "top": 0, "height": this._contentHeight }); this.content.append(this._resizeHint); this._resizeTooltip = $(RESIZE_TOOLTIP_TEMPLATE({ styles, start: task.start, end: task.end, messages: messages.views, format: options.resizeTooltipFormat })).css({ "z-index": "100002", "top": 0, "left": 0 }); this.content.append(this._resizeTooltip); this._resizeTooltipWidth = outerWidth(this._resizeTooltip); tooltipHeight = outerHeight(this._resizeTooltip); tooltipTop = taskTop - tooltipHeight; if (tooltipTop < 0) { tooltipTop = taskTop + this._rowHeight; } this._resizeTooltipTop = tooltipTop; }, _updateResizeHint: function(start, end, resizeStart) { var left = this._offset(isRtl ? end : start); var right = this._offset(isRtl ? start : end); var width = right - left; var tooltipLeft = resizeStart !== isRtl ? left : right; var tablesWidth = this._tableWidth - kendo.support.scrollbar(); var tooltipWidth = this._resizeTooltipWidth; var options = this.options; var messages = options.messages; var tableOffset = this.element.find(DOT + GanttView.styles.tasksTable).offset().left - this.element.find(DOT + GanttView.styles.tasksWrapper).offset().left; if (isRtl) { left += tableOffset; } this._resizeHint.css({ "left": left, "width": width }); if (this._resizeTooltip) { this._resizeTooltip.remove(); } tooltipLeft -= Math.round(tooltipWidth / 2); if (tooltipLeft < 0) { tooltipLeft = 0; } else if (tooltipLeft + tooltipWidth > tablesWidth) { tooltipLeft = tablesWidth - tooltipWidth; } if (isRtl) { tooltipLeft += tableOffset; } this._resizeTooltip = $(RESIZE_TOOLTIP_TEMPLATE({ styles: GanttView.styles, start, end, messages: messages.views, format: options.resizeTooltipFormat })).css({ "z-index": "100002", "top": this._resizeTooltipTop, "left": tooltipLeft, "min-width": tooltipWidth }).appendTo(this.content); }, _removeResizeHint: function() { this._resizeHint.remove(); this._resizeHint = null; this._resizeTooltip.remove(); this._resizeTooltip = null; }, _updatePercentCompleteTooltip: function(top, left, text) { this._removePercentCompleteTooltip(); var tooltip = this._percentCompleteResizeTooltip = $(PERCENT_RESIZE_TOOLTIP_TEMPLATE({ styles: GanttView.styles, text })); kendo.applyStylesFromKendoAttributes(tooltip, ["z-index", "left"]); tooltip.appendTo(this.element); var tooltipMiddle = Math.round(outerWidth(tooltip) / 2); var arrow = tooltip.find(DOT + GanttView.styles.callout); var arrowHeight = Math.round(outerWidth(arrow) / 2); tooltip.css({ "top": top - (outerHeight(tooltip) + arrowHeight), "left": left - tooltipMiddle }); arrow.css("left", tooltipMiddle - arrowHeight); }, _removePercentCompleteTooltip: function() { if (this._percentCompleteResizeTooltip) { this._percentCompleteResizeTooltip.remove(); } this._percentCompleteResizeTooltip = null; }, _updateDependencyDragHint: function(from, to) { this._removeDependencyDragHint(); this._creteDependencyDragHint(from, to); }, _creteDependencyDragHint: function(from, to) { var styles = GanttView.styles; var deltaX = to.x - from.x; var deltaY = to.y - from.y; var width = Math.sqrt(deltaX * deltaX + deltaY * deltaY); var angle = Math.atan(deltaY / deltaX); if (deltaX < 0) { angle += Math.PI; } $("<div class='" + styles.line + " " + styles.lineHorizontal + " " + styles.dependencyHint + "'></div>").css({ "top": from.y, "left": from.x, "width": width, "transform-origin": "0% 0", "-ms-transform-origin": "0% 0", "-webkit-transform-origin": "0% 0", "transform": "rotate(" + angle + "rad)", "-ms-transform": "rotate(" + angle + "rad)", "-webkit-transform": "rotate(" + angle + "rad)" }).appendTo(this.content); }, _removeDependencyDragHint: function() { this.content.find(DOT + GanttView.styles.dependencyHint).remove(); }, _createTaskTooltip: function(task, element, mouseLeft) { var styles = GanttView.styles; var options = this.options; var content = this.content; var contentOffset = content.offset(); var contentScrollLeft = kendo.scrollLeft(content); var row = $(element).parents("tr").first(); var rowOffset = row.offset(); var template = options.tooltip && options.tooltip.template ? kendo.template(options.tooltip.template) : TASK_TOOLTIP_TEMPLATE; var left = isRtl ? mouseLeft - (contentOffset.left + contentScrollLeft + kendo.support.scrollbar()) : mouseLeft - (contentOffset.left - contentScrollLeft); var top = rowOffset.top + outerHeight(row) - contentOffset.top + content.scrollTop(); var tooltip = this._taskTooltip = $("<div class=\"" + styles.tooltipWrapper + "\" >" + "<div class=\"" + styles.taskContent + "\"></div></div>"); tooltip.css({ "z-index": "100002", "left": left, "top": top }).appendTo(content).find(DOT + styles.taskContent).append(template({ styles, task, messages: options.messages.views })); this._adjustTooltipDimensions(tooltip, rowOffset, contentOffset, left, contentScrollLeft); }, _removeTaskTooltip: function() { if (this._taskTooltip) { this._taskTooltip.remove(); } this._taskTooltip = null; }, _createOffsetTooltip: function(task, element, mouseLeft) { var styles = GanttView.styles; var content = this.content; var contentOffset = content.offset(); var contentScrollLeft = kendo.scrollLeft(content); var row = element.parents("tr").first(); var rowOffset = row.offset(); var left = isRtl ? mouseLeft - (contentOffset.left + contentScrollLeft + kendo.support.scrollbar()) : mouseLeft - (contentOffset.left - contentScrollLeft); var top = rowOffset.top + outerHeight(row) - contentOffset.top + content.scrollTop(); var tooltip = this._offsetTooltip = $("<div class=\"" + styles.tooltipWrapper + "\" ></div>"); var offsetValue = Math.round((task.end.getTime() - task.plannedEnd.getTime()) / 6e4); var plannedTasksMessages = this.options.messages.plannedTasks; var minutes = offsetValue % 60; var offsetText = minutes + " " + plannedTasksMessages.minutes; var hours, days; if (offsetValue >= 60) { hours = offsetValue = Math.floor(offsetValue / 60); offsetText = hours + " " + plannedTasksMessages.hours; if (minutes !== 0) { offsetText += " " + minutes + " " + plannedTasksMessages.minutes; } if (offsetValue >= 24) { hours = offsetValue % 24; days = offsetValue = Math.floor(offsetValue / 24); offsetText = days + " " + plannedTasksMessages.days; if (hours !== 0) { offsetText += " " + hours + " " + plannedTasksMessages.hours; } } } tooltip.css({ "z-index": "100002", "left": left, "top": top }).addClass(styles.offsetTooltipDelay).appendTo(content).append(OFFSET_TOOLTIP_TEMPLATE({ offsetPrefix: plannedTasksMessages.offsetTooltipDelay, offsetText })); this._adjustTooltipDimensions(tooltip, rowOffset, contentOffset, left, contentScrollLeft); }, _removeOffsetTooltip: function() { if (this._offsetTooltip) { this._offsetTooltip.remove(); } this._offsetTooltip = null; }, _createPlannedTooltip: function(task, element, mouseLeft) { var styles = GanttView.styles; var content = this.content; var contentOffset = content.offset(); var contentScrollLeft = kendo.scrollLeft(content); var row = element.parents("tr").first(); var rowOffset = row.offset(); var left = isRtl ? mouseLeft - (contentOffset.left + contentScrollLeft + kendo.support.scrollbar()) : mouseLeft - (contentOffset.left - contentScrollLeft); var top = rowOffset.top + outerHeight(row) - contentOffset.top + content.scrollTop(); var tooltip = this._plannedTooltip = $("<div class=\"" + styles.tooltipWrapper + " " + styles.plannedTooltip + "\" ></div>"); var editorMessages = this.options.messages.editor; tooltip.css({ "z-index": "100002", "left": left, "top": top }).appendTo(content).append(PLANNED_TOOLTIP_TEMPLATE({ plannedStart: editorMessages.plannedStart, startDate: kendo.toString(task.plannedStart, "H:mm tt ddd, MMM dd"), plannedEnd: editorMessages.plannedEnd, endDate: kendo.toString(task.plannedEnd, "H:mm tt ddd, MMM dd") })); this._adjustTooltipDimensions(tooltip, rowOffset, contentOffset, left, contentScrollLeft); }, _removePlannedTooltip: function() { if (this._plannedTooltip) { this._plannedTooltip.remove(); } this._plannedTooltip = null; }, _adjustTooltipDimensions: function(tooltip, rowOffset, contentOffset, left, contentScrollLeft) { var content = this.content; var contentWidth = content.width(); var tooltipWidth; if (outerHeight(tooltip) < rowOffset.top - contentOffset.top) { tooltip.css("top", rowOffset.top - contentOffset.top - outerHeight(tooltip) + content.scrollTop()); } tooltipWidth = outerWidth(tooltip); if (tooltipWidth + left - contentScrollLeft > contentWidth) { left -= tooltipWidth; if (left < contentScrollLeft) { left = contentScrollLeft + contentWidth - (tooltipWidth + 17); } tooltip.css("left", left); } }, _scrollTo: function(element) { var elementLeft = element.offset().left; var elementWidth = element.width(); var elementRight = elementLeft + elementWidth; var row = element.closest("tr"); var rowTop = row.offset().top; var rowHeight = row.height(); var rowBottom = rowTop + rowHeight; var content = this.content; var contentOffset = content.offset(); var contentTop = contentOffset.top; var contentHeight = content.height(); var contentBottom = contentTop + contentHeight; var contentLeft = contentOffset.left; var contentWidth = content.width(); var contentRight = contentLeft + contentWidth; var scrollbarWidth = kendo.support.scrollbar(); if (rowTop < contentTop) { content.scrollTop(content.scrollTop() + (rowTop - contentTop)); } else if (rowBottom > contentBottom) { content.scrollTop(content.scrollTop() + (rowBottom + scrollbarWidth - contentBottom)); } if (elementLeft < contentLeft && elementWidth > contentWidth && elementRight < contentRight || elementRight > contentRight && elementWidth < contentWidth) { kendo.scrollLeft(content, kendo.scrollLeft(content) + (elementRight + scrollbarWidth - contentRight)); } else if (elementRight > contentRight && elementWidth > contentWidth && elementLeft > contentLeft || elementLeft < contentLeft && elementWidth < contentWidth) { kendo.scrollLeft(content, kendo.scrollLeft(content) + (elementLeft - contentLeft)); } }, _scrollToDate: function(date) { var viewStart = this.start; var viewEnd = this.end; var offset; if (date >= viewStart && date < viewEnd) { offset = this._offset(date); if (kendo.support.isRtl(this.element)) { offset = this._tableWidth - offset; } kendo.scrollLeft(this.content, offset); } }, _timeSlots: function() { if (!this._slots || !this._slots.length) { return []; } return this._slots[this._slots.length - 1]; }, _headers: function(columnLevels) { var rows = []; var level; var headers; var column; var headerText; var styles = GanttView.styles; for (var levelIndex = 0, levelCount = columnLevels.length; levelIndex < levelCount; levelIndex++) { level = columnLevels[levelIndex]; headers = []; for (var columnIndex = 0, columnCount = level.length; columnIndex < columnCount; columnIndex++) { column = level[columnIndex]; headerText = kendoHtmlElement(column.text); headers.push(kendoDomElement("td", { colspan: column.span, className: styles.header + (column.isNonWorking ? " " + styles.nonWorking : "") }, [headerText])); } rows.push(kendoDomElement("tr", { className: styles.tableRow }, headers)); } return rows; }, _hours: function(start, end) { var slotEnd; var slots = []; var options = this.options; var workDayStart = options.workDayStart.getHours(); var workDayEnd = options.workDayEnd.getHours(); var isWorkHour; var hours; var hourSpan = options.hourSpan; start = new Date(start); end = new Date(end); if (options.showWorkHours) { start.setHours(workDayStart); } while (start < end) { slotEnd = new Date(start); hours = slotEnd.getHours(); isWorkHour = hours >= workDayStart && hours < workDayEnd; slotEnd.setHours(slotEn