/** * Kendo UI v2016.1.112 (http://www.telerik.com/kendo-ui) * Copyright 2016 Telerik AD. All rights reserved. * * Kendo UI commercial licenses may be obtained at * http://www.telerik.com/purchase/license-agreement/kendo-ui-complete * If you do not own a commercial license, this file shall be governed by the trial license terms. */ (function (f, define) { define('kendo.gantt.timeline', [ 'kendo.dom', 'kendo.touch', 'kendo.draganddrop' ], f); }(function () { var __meta__ = { id: 'gantt.timeline', name: 'Gantt Timeline', category: 'web', description: 'The Gantt Timeline', depends: [ 'dom', 'touch', 'draganddrop' ], 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 extend = $.extend; var proxy = $.proxy; var browser = kendo.support.browser; 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 = kendo.template('#=kendo.toString(start, \'t\')#'); var DAY_HEADER_TEMPLATE = kendo.template('#=kendo.toString(start, \'ddd M/dd\')#'); var WEEK_HEADER_TEMPLATE = kendo.template('#=kendo.toString(start, \'ddd M/dd\')# - #=kendo.toString(kendo.date.addDays(end, -1), \'ddd M/dd\')#'); var MONTH_HEADER_TEMPLATE = kendo.template('#=kendo.toString(start, \'MMM\')#'); var YEAR_HEADER_TEMPLATE = kendo.template('#=kendo.toString(start, \'yyyy\')#'); var RESIZE_HINT = kendo.template('
' + '
' + '
'); var RESIZE_TOOLTIP_TEMPLATE = kendo.template('
' + '
' + '
#=messages.start#: #=kendo.toString(start, format)#
' + '
#=messages.end#: #=kendo.toString(end, format)#
' + '
' + '
'); var PERCENT_RESIZE_TOOLTIP_TEMPLATE = kendo.template('
' + '
#=text#%
' + '
' + '
'); var TASK_TOOLTIP_TEMPLATE = kendo.template('
' + '#=task.title#' + '
#=kendo.toString(task.percentComplete, "p0")#
' + '' + '
'); var SIZE_CALCULATION_TEMPLATE = '' + '' + '' + '' + '' + '' + '
 
'; 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).blur(); } } var viewStyles = { alt: 'k-alt', reset: 'k-reset', nonWorking: 'k-nonwork-hour', header: 'k-header', 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', task: 'k-task', taskSingle: 'k-task-single', taskMilestone: 'k-task-milestone', taskSummary: 'k-task-summary', taskWrap: 'k-task-wrap', taskMilestoneWrap: 'k-milestone-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', icon: 'k-icon', iconDelete: 'k-si-close', taskResizeHandle: 'k-resize-handle', taskResizeHandleWest: 'k-resize-w', taskResizeHandleEast: 'k-resize-e', taskSummaryProgress: 'k-task-summary-progress', taskSummaryComplete: 'k-task-summary-complete', line: 'k-line', lineHorizontal: 'k-line-h', lineVertical: 'k-line-v', arrowWest: 'k-arrow-w', arrowEast: 'k-arrow-e', dragHint: 'k-drag-hint', dependencyHint: 'k-dependency-hint', tooltipWrapper: 'k-widget k-tooltip k-popup k-group k-reset', tooltipContent: 'k-tooltip-content', tooltipCallout: 'k-callout k-callout-s', callout: 'k-callout', marquee: 'k-marquee k-gantt-marquee', marqueeColor: 'k-marquee-color' }; 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 () { this.content.height(this.element.height() - this.header.outerHeight()); }, createLayout: function (rows) { var headers = this._headers(rows); var colgroup = this._colgroup(); var tree = this._headerTree; var header = kendoDomElement('thead', null, headers); var table = kendoDomElement('table', { style: { width: this._tableWidth + 'px' } }, [ colgroup, header ]); tree.render([table]); this.headerRow = this.header.find('table:first 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 = [ null, { className: styles.alt } ]; for (var i = 0; i < rowCount; i++) { row = kendoDomElement('tr', attributes[i % 2], [kendoDomElement('td', null, [kendoTextElement('\xA0')])]); rows.push(row); } return this._createTable(1, rows, { className: styles.rowsTable }); }, _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 = {}; 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', null, cells); return this._createTable(totalSpan, [row], { className: styles.columnsTable }); }, _tasksTable: function (tasks) { var rows = []; var row; var cell; var position; 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 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); position.borderWidth = taskBorderWidth; row = kendoDomElement('tr', null); cell = kendoDomElement('td', null, [this._renderTask(tasks[i], position)]); 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]))); } row.children.push(cell); rows.push(row); addCoordinates(i); } return this._createTable(1, rows, { className: GanttView.styles.tasksTable }); }, _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', null, rows); if (!styles.style) { styles.style = {}; } styles.style.width = this._tableWidth + 'px'; 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 milestone = $('
'); var boundingClientRect; 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 = $('
'); 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 task = $('
'); var computedStyle; 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) { var taskWrapper; var taskElement; var editable = this.options.editable; var progressHandleOffset; 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' } }; if (calculatedSize) { taskWrapAttr.style.height = calculatedSize.cell + 'px'; } if (task.summary) { taskElement = this._renderSummary(task, position); } else if (task.isMilestone()) { taskElement = this._renderMilestone(task, position); taskWrapAttr.className += ' ' + styles.taskMilestoneWrap; } else { taskElement = this._renderSingleTask(task, position); } taskWrapper = kendoDomElement('div', taskWrapAttr, [taskElement]); if (editable) { taskWrapper.children.push(kendoDomElement('div', { className: styles.taskDot + ' ' + styles.taskDotStart })); taskWrapper.children.push(kendoDomElement('div', { className: styles.taskDot + ' ' + styles.taskDotEnd })); } if (!task.summary && !task.isMilestone() && editable && this._taskTemplate === null) { progressHandleOffset = Math.round(position.width * task.percentComplete); dragHandleStyle[isRtl ? 'right' : 'left'] = progressHandleOffset + 'px'; taskWrapper.children.push(kendoDomElement('div', { className: styles.taskDragHandle, style: dragHandleStyle })); } return taskWrapper; }, _renderSingleTask: function (task, position) { var styles = GanttView.styles; var progressWidth = Math.round(position.width * task.percentComplete); var taskChildren = []; var taskContent; 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' } })); } var content = kendoDomElement('div', { className: styles.taskContent }, [kendoDomElement('div', { className: styles.taskTemplate }, [taskContent])]); taskChildren.push(content); if (this.options.editable) { content.children.push(kendoDomElement('span', { className: styles.taskActions }, [kendoDomElement('a', { className: styles.link + ' ' + styles.taskDelete, href: '#' }, [kendoDomElement('span', { className: styles.icon + ' ' + styles.iconDelete })])])); 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: styles.task + ' ' + styles.taskSingle, 'data-uid': task.uid, style: { width: Math.max(position.width - position.borderWidth * 2, 0) + 'px' } }, taskChildren); return element; }, _renderMilestone: function (task) { var styles = GanttView.styles; var element = kendoDomElement('div', { className: styles.task + ' ' + styles.taskMilestone, 'data-uid': task.uid }); return element; }, _renderSummary: function (task, position) { var styles = GanttView.styles; var progressWidth = Math.round(position.width * task.percentComplete); var element = kendoDomElement('div', { className: styles.task + ' ' + styles.taskSummary, 'data-uid': task.uid, style: { width: position.width + 'px' } }, [kendoDomElement('div', { className: styles.taskSummaryProgress, style: { width: progressWidth + 'px' } }, [kendoDomElement('div', { className: styles.taskSummaryComplete, style: { width: position.width + 'px' } })])]); 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 }; }, _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 - $(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 - $(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) { this._dragHint = element.clone().addClass(GanttView.styles.dragHint).css('cursor', 'move'); element.parent().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({ 'top': 0, 'left': 0 }); this.content.append(this._resizeTooltip); this._resizeTooltipWidth = this._resizeTooltip.outerWidth(); tooltipHeight = this._resizeTooltip.outerHeight(); 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 = $(DOT + GanttView.styles.tasksTable).offset().left - $(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: start, end: end, messages: messages.views, format: options.resizeTooltipFormat })).css({ '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: text })).appendTo(this.element); var tooltipMiddle = Math.round(tooltip.outerWidth() / 2); var arrow = tooltip.find(DOT + GanttView.styles.callout); var arrowHeight = Math.round(arrow.outerWidth() / 2); tooltip.css({ 'top': top - (tooltip.outerHeight() + arrowHeight), 'left': left - tooltipMiddle }); arrow.css('left', tooltipMiddle - arrowHeight); }, _removePercentCompleteTooltip: function () { if (this._percentCompleteResizeTooltip) { this._percentCompleteResizeTooltip.remove(); } this._percentCompleteResizeTooltip = null; }, _updateDependencyDragHint: function (from, to, useVML) { this._removeDependencyDragHint(); if (useVML) { this._creteVmlDependencyDragHint(from, to); } else { 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; } $('
').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); }, _creteVmlDependencyDragHint: function (from, to) { var hint = $('').appendTo(this.content); hint[0].outerHTML = hint[0].outerHTML; }, _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 contentWidth = content.width(); 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 + row.outerHeight() - contentOffset.top + content.scrollTop(); var tooltip = this._taskTooltip = $('
' + '
'); var tooltipWidth; tooltip.css({ 'left': left, 'top': top }).appendTo(content).find(DOT + styles.taskContent).append(template({ styles: styles, task: task, messages: options.messages.views })); if (tooltip.outerHeight() < rowOffset.top - contentOffset.top) { tooltip.css('top', rowOffset.top - contentOffset.top - tooltip.outerHeight() + content.scrollTop()); } tooltipWidth = tooltip.outerWidth(); if (tooltipWidth + left - contentScrollLeft > contentWidth) { left -= tooltipWidth; if (left < contentScrollLeft) { left = contentScrollLeft + contentWidth - (tooltipWidth + 17); } tooltip.css('left', left); } }, _removeTaskTooltip: function () { if (this._taskTooltip) { this._taskTooltip.remove(); } this._taskTooltip = null; }, _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) { content.scrollLeft(content.scrollLeft() + (elementRight + scrollbarWidth - contentRight)); } else if (elementRight > contentRight && elementWidth > contentWidth && elementLeft > contentLeft || elementLeft < contentLeft && elementWidth < contentWidth) { content.scrollLeft(content.scrollLeft() + (elementLeft - contentLeft)); } }, _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 = kendoTextElement(column.text); headers.push(kendoDomElement('th', { colspan: column.span, className: styles.header + (column.isNonWorking ? ' ' + styles.nonWorking : '') }, [headerText])); } rows.push(kendoDomElement('tr', null, 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); while (start < end) { slotEnd = new Date(start); hours = slotEnd.getHours(); isWorkHour = hours >= workDayStart && hours < workDayEnd; slotEnd.setHours(slotEnd.getHours() + hourSpan); if (hours == slotEnd.getHours()) { slotEnd.setHours(slotEnd.getHours() + 2 * hourSpan); } if (!options.showWorkHours || isWorkHour) { slots.push({ start: start, end: slotEnd, isNonWorking: !isWorkHour, span: 1 }); } start = slotEnd; } return slots; }, _days: function (start, end) { var slotEnd; var slots = []; var isWorkDay; start = new Date(start); end = new Date(end); while (start < end) { slotEnd = kendo.date.nextDay(start); isWorkDay = this._isWorkDay(start); if (!this.options.showWorkDays || isWorkDay) { slots.push({ start: start, end: slotEnd, isNonWorking: !isWorkDay, span: 1 }); } start = slotEnd; } return slots; }, _weeks: function (start, end) { var slotEnd; var slots = []; var firstDay = this.calendarInfo().firstDay; var daySlots; var span; start = new Date(start); end = new Date(end); while (start < end) { slotEnd = kendo.date.dayOfWeek(kendo.date.addDays(start, 1), firstDay, 1); if (slotEnd > end) { slotEnd = end; } daySlots = this._days(start, slotEnd); span = daySlots.length; if (span > 0) { slots.push({ start: daySlots[0].start, end: daySlots[span - 1].end, span: span }); } start = slotEnd; } return slots; }, _months: function (start, end) { var slotEnd; var slots = []; var daySlots; var span; start = new Date(start); end = new Date(end); while (start < end) { slotEnd = new Date(start); slotEnd.setMonth(slotEnd.getMonth() + 1); daySlots = this._days(start, slotEnd); span = daySlots.length; if (span > 0) { slots.push({ start: daySlots[0].start, end: daySlots[span - 1].end, span: span }); } start = slotEnd; } return slots; }, _years: function (start, end) { var slotEnd; var slots = []; start = new Date(start); end = new Date(end); while (start < end) { slotEnd = new Date(start); slotEnd.setFullYear(slotEnd.getFullYear() + 1); slots.push({ start: start, end: slotEnd, span: 12 }); start = slotEnd; } return slots; }, _slotHeaders: function (slots, template) { var columns = []; var slot; for (var i = 0, l = slots.length; i < l; i++) { slot = slots[i]; columns.push({ text: template(slot), isNonWorking: !!slot.isNonWorking, span: slot.span }); } return columns; }, _isWorkDay: function (date) { var day = date.getDay(); var workDays = this._workDays; for (var i = 0, l = workDays.length; i < l; i++) { if (workDays[i] === day) { return true; } } return false; }, calendarInfo: function () { return kendo.getCulture().calendars.standard; }, _renderCurrentTime: function () { var currentTime = this._getCurrentTime(); var timeOffset = this._offset(currentTime); var element = $('
'); var viewStyles = GanttView.styles; var tablesWrap = $(DOT + viewStyles.tasksWrapper); var tasksTable = $(DOT + viewStyles.tasksTable); var slot; if (!this.content || !this._timeSlots().length) { return; } this.content.find('.k-current-time').remove(); slot = this._timeSlots()[this._slotIndex('start', currentTime)]; if (currentTime < slot.start || currentTime > slot.end) { return; } if (tablesWrap.length && tasksTable.length) { timeOffset += tasksTable.offset().left - tablesWrap.offset().left; } element.css({ left: timeOffset + 'px', top: '0px', width: '1px', height: this._contentHeight + 'px' }).appendTo(this.content); }, _getCurrentTime: function () { return new Date(); }, _currentTime: function () { var markerOptions = this.options.currentTimeMarker; if (markerOptions !== false && markerOptions.updateInterval !== undefined) { this._renderCurrentTime(); this._currentTimeUpdateTimer = setInterval(proxy(this._renderCurrentTime, this), markerOptions.updateInterval); } } }); extend(true, GanttView, { styles: viewStyles }); kendo.ui.GanttDayView = GanttView.extend({ name: 'day', options: { timeHeaderTemplate: TIME_HEADER_TEMPLATE, dayHeaderTemplate: DAY_HEADER_TEMPLATE, resizeTooltipFormat: 'h:mm tt ddd, MMM d' }, range: function (range) { this.start = kendo.date.getDate(range.start); this.end = kendo.date.getDate(range.end); if (kendo.date.getMilliseconds(range.end) > 0 || this.end.getTime() === this.start.getTime()) { this.end = kendo.date.addDays(this.end, 1); } }, _createSlots: function () { var daySlots; var daySlot; var hourSlots; var hours; var slots = []; daySlots = this._days(this.start, this.end); hourSlots = []; for (var i = 0, l = daySlots.length; i < l; i++) { daySlot = daySlots[i]; hours = this._hours(daySlot.start, daySlot.end); daySlot.span = hours.length; hourSlots.push.apply(hourSlots, hours); } slots.push(daySlots); slots.push(hourSlots); return slots; }, _layout: function () { var rows = []; var options = this.options; rows.push(this._slotHeaders(this._slots[0], kendo.template(options.dayHeaderTemplate))); rows.push(this._slotHeaders(this._slots[1], kendo.template(options.timeHeaderTemplate))); return rows; } }); kendo.ui.GanttWeekView = GanttView.extend({ name: 'week', options: { dayHeaderTemplate: DAY_HEADER_TEMPLATE, weekHeaderTemplate: WEEK_HEADER_TEMPLATE, resizeTooltipFormat: 'h:mm tt ddd, MMM d' }, range: function (range) { var calendarInfo = this.calendarInfo(); var firstDay = calendarInfo.firstDay; var rangeEnd = range.end; if (firstDay === rangeEnd.getDay()) { rangeEnd.setDate(rangeEnd.getDate() + 7); } this.start = kendo.date.getDate(kendo.date.dayOfWeek(range.start, firstDay, -1)); this.end = kendo.date.getDate(kendo.date.dayOfWeek(rangeEnd, firstDay, 1)); }, _createSlots: function () { var slots = []; slots.push(this._weeks(this.start, this.end)); slots.push(this._days(this.start, this.end)); return slots; }, _layout: function () { var rows = []; var options = this.options; rows.push(this._slotHeaders(this._slots[0], kendo.template(options.weekHeaderTemplate))); rows.push(this._slotHeaders(this._slots[1], kendo.template(options.dayHeaderTemplate))); return rows; } }); kendo.ui.GanttMonthView = GanttView.extend({ name: 'month', options: { weekHeaderTemplate: WEEK_HEADER_TEMPLATE, monthHeaderTemplate: MONTH_HEADER_TEMPLATE, resizeTooltipFormat: 'dddd, MMM d, yyyy' }, range: function (range) { this.start = kendo.date.firstDayOfMonth(range.start); this.end = kendo.date.addDays(kendo.date.getDate(kendo.date.lastDayOfMonth(range.end)), 1); }, _createSlots: function () { var slots = []; slots.push(this._months(this.start, this.end)); slots.push(this._weeks(this.start, this.end)); return slots; }, _layout: function () { var rows = []; var options = this.options; rows.push(this._slotHeaders(this._slots[0], kendo.template(options.monthHeaderTemplate))); rows.push(this._slotHeaders(this._slots[1], kendo.template(options.weekHeaderTemplate))); return rows; } }); kendo.ui.GanttYearView = GanttView.extend({ name: 'year', options: { yearHeaderTemplate: YEAR_HEADER_TEMPLATE, monthHeaderTemplate: MONTH_HEADER_TEMPLATE, resizeTooltipFormat: 'dddd, MMM d, yyyy' }, range: function (range) { this.start = kendo.date.firstDayOfMonth(new Date(range.start.setMonth(0))); this.end = kendo.date.firstDayOfMonth(new Date(range.end.setMonth(12))); }, _createSlots: function () { var slots = []; var monthSlots = this._months(this.start, this.end); $(monthSlots).each(function (index, slot) { slot.span = 1; }); slots.push(this._years(this.start, this.end)); slots.push(monthSlots); return slots; }, _layout: function () { var rows = []; var options = this.options; rows.push(this._slotHeaders(this._slots[0], kendo.template(options.yearHeaderTemplate))); rows.push(this._slotHeaders(this._slots[1], kendo.template(options.monthHeaderTemplate))); return rows; } }); var timelineStyles = { wrapper: 'k-timeline k-grid k-widget', gridHeader: 'k-grid-header', gridHeaderWrap: 'k-grid-header-wrap', gridContent: 'k-grid-content', gridContentWrap: 'k-grid-content', tasksWrapper: 'k-gantt-tables', dependenciesWrapper: 'k-gantt-dependencies', task: 'k-task', line: 'k-line', taskResizeHandle: 'k-resize-handle', taskResizeHandleWest: 'k-resize-w', taskDragHandle: 'k-task-draghandle', taskComplete: 'k-task-complete', taskDelete: 'k-task-delete', taskWrapActive: 'k-task-wrap-active', taskWrap: 'k-task-wrap', taskDot: 'k-task-dot', taskDotStart: 'k-task-start', taskDotEnd: 'k-task-end', hovered: 'k-state-hover', selected: 'k-state-selected', origin: 'k-origin' }; var GanttTimeline = kendo.ui.GanttTimeline = Widget.extend({ init: function (element, options) { Widget.fn.init.call(this, element, options); if (!this.options.views || !this.options.views.length) { this.options.views = [ 'day', 'week', 'month' ]; } isRtl = kendo.support.isRtl(element); this._wrapper(); this._domTrees(); this._views(); this._selectable(); this._draggable(); this._resizable(); this._percentResizeDraggable(); this._createDependencyDraggable(); this._attachEvents(); this._tooltip(); }, options: { name: 'GanttTimeline', messages: { views: { day: 'Day', week: 'Week', month: 'Month', year: 'Year', start: 'Start', end: 'End' } }, snap: true, selectable: true, editable: true }, destroy: function () { Widget.fn.destroy.call(this); clearTimeout(this._tooltipTimeout); if (this._currentTimeUpdateTimer) { clearInterval(this._currentTimeUpdateTimer); } this._unbindView(this._selectedView); if (this._moveDraggable) { this._moveDraggable.destroy(); } if (this._resizeDraggable) { this._resizeDraggable.destroy(); } if (this._percentDraggable) { this._percentDraggable.destroy(); } if (this._dependencyDraggable) { this._dependencyDraggable.destroy(); } if (this.touch) { this.touch.destroy(); } this._headerTree = null; this._taskTree = null; this._dependencyTree = null; this.wrapper.off(NS); kendo.destroy(this.wrapper); }, _wrapper: function () { var styles = GanttTimeline.styles; var that = this; var options = this.options; var calculateSize = function () { var rowHeight = typeof options.rowHeight === STRING ? options.rowHeight : options.rowHeight + 'px'; var table = $(kendo.format(SIZE_CALCULATION_TEMPLATE, rowHeight)); var calculatedRowHeight; var calculatedCellHeight; var content = that.wrapper.find(DOT + styles.tasksWrapper); content.append(table); calculatedRowHeight = table.find('tr').outerHeight(); calculatedCellHeight = table.find('td').height(); table.remove(); return { 'row': calculatedRowHeight, 'cell': calculatedCellHeight }; }; this.wrapper = this.element.addClass(styles.wrapper).append('
').append('
'); if (options.rowHeight) { this._calculatedSize = calculateSize(); } }, _domTrees: function () { var styles = GanttTimeline.styles; var tree = kendo.dom.Tree; var wrapper = this.wrapper; this._headerTree = new tree(wrapper.find(DOT + styles.gridHeaderWrap)[0]); this._taskTree = new tree(wrapper.find(DOT + styles.tasksWrapper)[0]); this._dependencyTree = new tree(wrapper.find(DOT + styles.dependenciesWrapper)[0]); }, _views: function () { var views = this.options.views; var view; var isSettings; var name; var defaultView; var selected; this.views = {}; for (var i = 0, l = views.length; i < l; i++) { view = views[i]; isSettings = isPlainObject(view); if (isSettings && view.selectable === false) { continue; } name = isSettings ? typeof view.type !== 'string' ? view.title : view.type : view; defaultView = defaultViews[name]; if (defaultView) { if (isSettings) { view.type = defaultView.type; } defaultView.title = this.options.messages.views[name]; } view = extend({ title: name }, defaultView, isSettings ? view : {}); if (name) { this.views[name] = view; if (!selected || view.selected) { selected = name; } } } if (selected) { this._selectedViewName = selected; } }, view: function (name) { if (name) { this._selectView(name); this.trigger('navigate', { view: name, action: 'changeView' }); } return this._selectedView; }, _selectView: function (name) { if (name && this.views[name]) { if (this._selectedView) { this._unbindView(this._selectedView); } this._selectedView = this._initializeView(name); this._selectedViewName = name; } }, _viewByIndex: function (index) { var view; var views = this.views; for (view in views) { if (!index) { return view; } index--; } }, _initializeView: function (name) { var view = this.views[name]; if (view) { var type = view.type; if (typeof type === 'string') { type = kendo.getter(view.type)(window); } if (type) { view = new type(this.wrapper, trimOptions(extend(true, { headerTree: this._headerTree, taskTree: this._taskTree, dependencyTree: this._dependencyTree, calculatedSize: this._calculatedSize }, view, this.options))); } else { throw new Error('There is no such view'); } } return view; }, _unbindView: function (view) { if (view) { view.destroy(); } }, _range: function (tasks) { var startOrder = { field: 'start', dir: 'asc' }; var endOrder = { field: 'end', dir: 'desc' }; if (!tasks || !tasks.length) { return { start: new Date(), end: new Date() }; } var start = new Query(tasks).sort(startOrder).toArray()[0].start || new Date(); var end = new Query(tasks).sort(endOrder).toArray()[0].end || new Date(); return { start: new Date(start), end: new Date(end) }; }, _render: function (tasks) { var view = this.view(); var range = this._range(tasks); this._tasks = tasks; view.range(range); view.renderLayout(); view.render(tasks); }, _renderDependencies: function (dependencies) { this.view()._renderDependencies(dependencies); }, _taskByUid: function (uid) { var tasks = this._tasks; var length = tasks.length; var task; for (var i = 0; i < length; i++) { task = tasks[i]; if (task.uid === uid) { return task; } } }, _draggable: function () { var that = this; var element; var task; var currentStart; var startOffset; var snap = this.options.snap; var styles = GanttTimeline.styles; var cleanUp = function () { that.view()._removeDragHint(); if (element) { element.css('opacity', 1); } element = null; task = null; that.dragInProgress = false; }; if (!this.options.editable) { return; } this._moveDraggable = new kendo.ui.Draggable(this.wrapper, { distance: 0, filter: DOT + styles.task, holdToDrag: kendo.support.mobileOS, ignore: DOT + styles.taskResizeHandle }); this._moveDraggable.bind('dragstart', function (e) { var view = that.view(); element = e.currentTarget.parent(); task = that._taskByUid(e.currentTarget.attr('data-uid')); if (that.trigger('moveStart', { task: task })) { e.preventDefault(); return; } currentStart = task.start; startOffset = view._timeByPosition(e.x.location, snap) - currentStart; view._createDragHint(element); element.css('opacity', 0.5); clearTimeout(that._tooltipTimeout); that.dragInProgress = true; }).bind('drag', kendo.throttle(function (e) { if (!that.dragInProgress) { return; } var view = that.view(); var date = new Date(view._timeByPosition(e.x.location, snap) - startOffset); var updateHintDate = date; if (!that.trigger('move', { task: task, start: date })) { currentStart = date; if (isRtl) { updateHintDate = new Date(currentStart.getTime() + task.duration()); } view._updateDragHint(updateHintDate); } }, 15)).bind('dragend', function () { that.trigger('moveEnd', { task: task, start: currentStart }); cleanUp(); }).bind('dragcancel', function () { cleanUp(); }).userEvents.bind('select', function () { blurActiveElement(); }); }, _resizable: function () { var that = this; var element; var task; var currentStart; var currentEnd; var resizeStart; var snap = this.options.snap; var styles = GanttTimeline.styles; var cleanUp = function () { that.view()._removeResizeHint(); element = null; task = null; that.dragInProgress = false; }; if (!this.options.editable) { return; } this._resizeDraggable = new kendo.ui.Draggable(this.wrapper, { distance: 0, filter: DOT + styles.taskResizeHandle, holdToDrag: false }); this._resizeDraggable.bind('dragstart', function (e) { resizeStart = e.currentTarget.hasClass(styles.taskResizeHandleWest); if (isRtl) { resizeStart = !resizeStart; } element = e.currentTarget.closest(DOT + styles.task); task = that._taskByUid(element.attr('data-uid')); if (that.trigger('resizeStart', { task: task })) { e.preventDefault(); return; } currentStart = task.start; currentEnd = task.end; that.view()._createResizeHint(task); clearTimeout(that._tooltipTimeout); that.dragInProgress = true; }).bind('drag', kendo.throttle(function (e) { if (!that.dragInProgress) { return; } var view = that.view(); var date = view._timeByPosition(e.x.location, snap, !resizeStart); if (resizeStart) { if (date < currentEnd) { currentStart = date; } else { currentStart = currentEnd; } } else { if (date > currentStart) { currentEnd = date; } else { currentEnd = currentStart; } } if (!that.trigger('resize', { task: task, start: currentStart, end: currentEnd })) { view._updateResizeHint(currentStart, currentEnd, resizeStart); } }, 15)).bind('dragend', function () { that.trigger('resizeEnd', { task: task, resizeStart: resizeStart, start: currentStart, end: currentEnd }); cleanUp(); }).bind('dragcancel', function () { cleanUp(); }).userEvents.bind('select', function () { blurActiveElement(); }); }, _percentResizeDraggable: function () { var that = this; var task; var taskElement; var taskElementOffset; var timelineOffset; var originalPercentWidth; var maxPercentWidth; var currentPercentComplete; var tooltipTop; var tooltipLeft; var styles = GanttTimeline.styles; var delta; var cleanUp = function () { that.view()._removePercentCompleteTooltip(); taskElement = null; task = null; that.dragInProgress = false; }; var updateElement = function (width) { taskElement.find(DOT + styles.taskComplete).width(width).end().siblings(DOT + styles.taskDragHandle).css(isRtl ? 'right' : 'left', width); }; if (!this.options.editable) { return; } this._percentDraggable = new kendo.ui.Draggable(this.wrapper, { distance: 0, filter: DOT + styles.taskDragHandle, holdToDrag: false }); this._percentDraggable.bind('dragstart', function (e) { if (that.trigger('percentResizeStart')) { e.preventDefault(); return; } taskElement = e.currentTarget.siblings(DOT + styles.task); task = that._taskByUid(taskElement.attr('data-uid')); currentPercentComplete = task.percentComplete; taskElementOffset = taskElement.offset(); timelineOffset = this.element.offset(); originalPercentWidth = taskElement.find(DOT + styles.taskComplete).width(); maxPercentWidth = taskElement.outerWidth(); clearTimeout(that._tooltipTimeout); that.dragInProgress = true; }).bind('drag', kendo.throttle(function (e) { if (!that.dragInProgress) { return; } delta = isRtl ? -e.x.initialDelta : e.x.initialDelta; var currentWidth = Math.max(0, Math.min(maxPercentWidth, originalPercentWidth + delta)); currentPercentComplete = Math.round(currentWidth / maxPercentWidth * 100); updateElement(currentWidth); tooltipTop = taskElementOffset.top - timelineOffset.top; tooltipLeft = taskElementOffset.left + currentWidth - timelineOffset.left; if (isRtl) { tooltipLeft += maxPercentWidth - 2 * currentWidth; } that.view()._updatePercentCompleteTooltip(tooltipTop, tooltipLeft, currentPercentComplete); }, 15)).bind('dragend', function () { that.trigger('percentResizeEnd', { task: task, percentComplete: currentPercentComplete / 100 }); cleanUp(); }).bind('dragcancel', function () { updateElement(originalPercentWidth); cleanUp(); }).userEvents.bind('select', function () { blurActiveElement(); }); }, _createDependencyDraggable: function () { var that = this; var originalHandle; var hoveredHandle = $(); var hoveredTask = $(); var startX; var startY; var useVML = browser.msie && browser.version < 9; var styles = GanttTimeline.styles; var cleanUp = function () { originalHandle.css('display', '').removeClass(styles.hovered); originalHandle.parent().removeClass(styles.origin); originalHandle = null; toggleHandles(false); hoveredTask = $(); hoveredHandle = $(); that.view()._removeDependencyDragHint(); that.dragInProgress = false; }; var toggleHandles = function (value) { if (!hoveredTask.hasClass(styles.origin)) { hoveredTask.find(DOT + styles.taskDot).css('display', value ? 'block' : ''); hoveredHandle.toggleClass(styles.hovered, value); } }; if (!this.options.editable) { return; } if (useVML && document.namespaces) { document.namespaces.add('kvml', 'urn:schemas-microsoft-com:vml', '#default#VML'); } this._dependencyDraggable = new kendo.ui.Draggable(this.wrapper, { distance: 0, filter: DOT + styles.taskDot, holdToDrag: false }); this._dependencyDraggable.bind('dragstart', function (e) { if (that.trigger('dependencyDragStart')) { e.preventDefault(); return; } originalHandle = e.currentTarget.css('display', 'block').addClass(styles.hovered); originalHandle.parent().addClass(styles.origin); var elementOffset = originalHandle.offset(); var tablesOffset = that.wrapper.find(DOT + styles.tasksWrapper).offset(); startX = Math.round(elementOffset.left - tablesOffset.left + originalHandle.outerHeight() / 2); startY = Math.round(elementOffset.top - tablesOffset.top + originalHandle.outerWidth() / 2); clearTimeout(that._tooltipTimeout); that.dragInProgress = true; }).bind('drag', kendo.throttle(function (e) { if (!that.dragInProgress) { return; } that.view()._removeDependencyDragHint(); var target = $(kendo.elementUnderCursor(e)); var tablesOffset = that.wrapper.find(DOT + styles.tasksWrapper).offset(); var currentX = e.x.location - tablesOffset.left; var currentY = e.y.location - tablesOffset.top; that.view()._updateDependencyDragHint({ x: startX, y: startY }, { x: currentX, y: currentY }, useVML); toggleHandles(false); hoveredHandle = target.hasClass(styles.taskDot) ? target : $(); hoveredTask = target.closest(DOT + styles.taskWrap); toggleHandles(true); }, 15)).bind('dragend', function () { if (hoveredHandle.length) { var fromStart = originalHandle.hasClass(styles.taskDotStart); var toStart = hoveredHandle.hasClass(styles.taskDotStart); var type = fromStart ? toStart ? 3 : 2 : toStart ? 1 : 0; var predecessor = that._taskByUid(originalHandle.siblings(DOT + styles.task).attr('data-uid')); var successor = that._taskByUid(hoveredHandle.siblings(DOT + styles.task).attr('data-uid')); if (predecessor !== successor) { that.trigger('dependencyDragEnd', { type: type, predecessor: predecessor, successor: successor }); } } cleanUp(); }).bind('dragcancel', function () { cleanUp(); }).userEvents.bind('select', function () { blurActiveElement(); }); }, _selectable: function () { var that = this; var styles = GanttTimeline.styles; if (this.options.selectable) { this.wrapper.on(CLICK + NS, DOT + styles.task, function (e) { e.stopPropagation(); if (!e.ctrlKey) { that.trigger('select', { uid: $(this).attr('data-uid') }); } else { that.trigger('clear'); } }).on(CLICK + NS, DOT + styles.taskWrap, function (e) { e.stopPropagation(); $(this).css('z-index', '0'); var target = $(document.elementFromPoint(e.clientX, e.clientY)); if (target.hasClass(styles.line)) { target.click(); } $(this).css('z-index', ''); }).on(CLICK + NS, DOT + styles.tasksWrapper, function () { if (that.selectDependency().length > 0) { that.clearSelection(); } else { that.trigger('clear'); } }).on(CLICK + NS, DOT + styles.line, function (e) { e.stopPropagation(); that.selectDependency(this); }); } }, select: function (value) { var element = this.wrapper.find(value); var styles = GanttTimeline.styles; if (element.length) { this.clearSelection(); element.addClass(styles.selected); if (kendo.support.mobileOS) { element.parent().addClass(styles.taskWrapActive); } return; } return this.wrapper.find(DOT + styles.task + DOT + styles.selected); }, selectDependency: function (value) { var element = this.wrapper.find(value); var uid; var styles = GanttTimeline.styles; if (element.length) { this.clearSelection(); this.trigger('clear'); uid = $(element).attr('data-uid'); this.wrapper.find(DOT + styles.line + '[data-uid=\'' + uid + '\']').addClass(styles.selected); return; } return this.wrapper.find(DOT + styles.line + DOT + styles.selected); }, clearSelection: function () { var styles = GanttTimeline.styles; this.wrapper.find(DOT + styles.selected).removeClass(styles.selected); if (kendo.support.mobileOS) { this.wrapper.find(DOT + styles.taskWrapActive).removeClass(styles.taskWrapActive); } }, _attachEvents: function () { var that = this; var styles = GanttTimeline.styles; if (this.options.editable) { this._tabindex(); this.wrapper.on(CLICK + NS, DOT + styles.taskDelete, function (e) { that.trigger('removeTask', { uid: $(this).closest(DOT + styles.task).attr('data-uid') }); e.stopPropagation(); e.preventDefault(); }).on(KEYDOWN + NS, function (e) { var selectedDependency; if (e.keyCode === keys.DELETE) { selectedDependency = that.selectDependency(); if (selectedDependency.length) { that.trigger('removeDependency', { uid: selectedDependency.attr('data-uid') }); that.clearSelection(); } } }); if (!kendo.support.mobileOS) { this.wrapper.on(DBLCLICK + NS, DOT + styles.task, function (e) { that.trigger('editTask', { uid: $(this).attr('data-uid') }); e.stopPropagation(); e.preventDefault(); }); } else { this.touch = this.wrapper.kendoTouch({ filter: DOT + styles.task, doubletap: function (e) { that.trigger('editTask', { uid: $(e.touch.currentTarget).attr('data-uid') }); } }).data('kendoTouch'); } } }, _tooltip: function () { var that = this; var tooltipOptions = this.options.tooltip; var styles = GanttTimeline.styles; var currentMousePosition; var mouseMoveHandler = function (e) { currentMousePosition = e.clientX; }; if (tooltipOptions && tooltipOptions.visible === false) { return; } if (!kendo.support.mobileOS) { this.wrapper.on(MOUSEENTER + NS, DOT + styles.task, function () { var element = this; var task = that._taskByUid($(this).attr('data-uid')); if (that.dragInProgress) { return; } that._tooltipTimeout = setTimeout(function () { that.view()._createTaskTooltip(task, element, currentMousePosition); }, 800); $(this).on(MOUSEMOVE, mouseMoveHandler); }).on(MOUSELEAVE + NS, DOT + styles.task, function () { clearTimeout(that._tooltipTimeout); that.view()._removeTaskTooltip(); $(this).off(MOUSEMOVE, mouseMoveHandler); }); } else { this.wrapper.on(CLICK + NS, DOT + styles.taskDelete, function (e) { e.stopPropagation(); that.view()._removeTaskTooltip(); }).on(MOUSELEAVE + NS, DOT + styles.task, function (e) { var parents = $(e.relatedTarget).parents(DOT + styles.taskWrap, DOT + styles.task); if (parents.length === 0) { that.view()._removeTaskTooltip(); } }); this.touch.bind('tap', function (e) { var element = e.touch.target; var task = that._taskByUid($(element).attr('data-uid')); var currentPosition = e.touch.x.client; if (that.view()._taskTooltip) { that.view()._removeTaskTooltip(); } that.view()._createTaskTooltip(task, element, currentPosition); }).bind('doubletap', function () { that.view()._removeTaskTooltip(); }); } } }); extend(true, GanttTimeline, { styles: timelineStyles }); }(window.kendo.jQuery)); return window.kendo; }, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) { (a3 || a2)(); }));