UNPKG

@progress/kendo-ui

Version:

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

1,371 lines (1,103 loc) 121 kB
import './kendo.dom.js'; import './kendo.touch.js'; import './kendo.draganddrop.js'; import './kendo.icons.js'; import './kendo.core.js'; import './kendo.licensing.js'; import '@progress/kendo-licensing'; import './kendo.userevents.js'; import './kendo.html.icon.js'; import './kendo.html.base.js'; import '@progress/kendo-svg-icons'; 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-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 k-table-md", table: "k-table k-table-md", 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: 10000 } }, 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("\u00a0") ]) ]); 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("\u00a0") ])); } 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: 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"; } 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: 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: 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: 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; // Add one pixel for border } 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: 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() { 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: styles })).css({ "top": 0, "height": this._contentHeight }); this.content.append(this._resizeHint); this._resizeTooltip = $(RESIZE_TOOLTIP_TEMPLATE({ styles: 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; v