UNPKG

@progress/kendo-ui

Version:

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

1,369 lines 69.6 kB
//#region ../src/kendo.scheduler.timelineview.js const __meta__ = { id: "scheduler.timelineview", name: "Scheduler Timeline View", category: "web", description: "The Scheduler Timeline View", depends: ["scheduler.view", "icons"], hidden: true }; (function($, undefined) { var kendo = window.kendo, ui = kendo.ui, setTime = kendo.date.setTime, SchedulerView = ui.SchedulerView, outerWidth = kendo._outerWidth, outerHeight = kendo._outerHeight, extend = $.extend, encode = kendo.htmlEncode, getDate = kendo.date.getDate, getMilliseconds = kendo.date.getMilliseconds, MS_PER_DAY = kendo.date.MS_PER_DAY, MS_PER_MINUTE = kendo.date.MS_PER_MINUTE, CURRENT_TIME_MARKER_CLASS = "k-current-time", CURRENT_TIME_MARKER_ARROW_CLASS = "k-current-time-arrow", SCHEDULER_HEADER_WRAP_CLASS = "k-scheduler-header-wrap", INVERSE_COLOR_CLASS = "k-event-inverse", BORDER_SIZE_COEFF = .8666, DOT = ".", NS = ".kendoTimelineView"; var EVENT_TEMPLATE = kendo.template(({ title, start, end }) => "<div>" + `<div class="k-event-template k-event-time">${encode(kendo.format("{0:t} - {1:t}", start, end))}</div>` + `<div class="k-event-template">${title}</div></div>`), DATA_HEADER_TEMPLATE = kendo.template(({ date }) => `<span class='k-link k-nav-day'>${kendo.format("{0:m}", date)}</span>`), EVENT_WRAPPER_TEMPLATE = (task) => `<div role="button" aria-label="${encode(task.ariaLabel)}" data-${task.ns}uid="${task.uid}" ` + `${task.resources[0] ? `${kendo.attr("style-background-color")}="${task.resources[0].color}" ${kendo.attr("style-border-color")}="${task.resources[0].color}" class="k-event">` : "class=\"k-event\">"}` + "<span class=\"k-event-actions\">" + `${task.tail ? kendo.ui.icon("chevron-left") : ""}` + `${task.isException() ? kendo.ui.icon("arrows-no-repeat") : task.isRecurring() ? kendo.ui.icon("arrow-rotate-cw") : ""}` + "</span>" + `${kendo.template(task.template)(task)}` + "<span class=\"k-event-actions\">" + `${task.showDelete ? `<a href="#" class="k-link k-event-delete" title="${task.messages.destroy}" aria-label="${task.messages.destroy}">${kendo.ui.icon("x")}</a>` : ""}` + `${task.head ? kendo.ui.icon("chevron-right") : ""}` + "</span>" + `${task.resizable && !task.tail ? "<span class=\"k-resize-handle k-resize-w\"></span>" : ""}` + `${task.resizable && !task.head ? "<span class=\"k-resize-handle k-resize-e\"></span>" : ""}` + "</div>"; function toInvariantTime(date) { var staticDate = new Date(1980, 1, 1, 0, 0, 0); setTime(staticDate, getMilliseconds(date)); return staticDate; } function createZeroWidthEvent() { return $("<div class='k-event'></div>").appendTo(document.body); } function getWorkDays(options) { if (options.workDays && options.workDays.length) { return options.workDays; } var workDays = []; var dayIndex = options.workWeekStart % 7; var workWeekEnd = Math.abs(options.workWeekEnd % 7); workDays.push(dayIndex); while (workWeekEnd != dayIndex) { if (dayIndex > 6) { dayIndex -= 7; } else { dayIndex++; } workDays.push(dayIndex); } return workDays; } function setColspan(columnLevel) { var count = 0; if (columnLevel.columns) { for (var i = 0; i < columnLevel.columns.length; i++) { count += setColspan(columnLevel.columns[i]); } columnLevel.colspan = count; return count; } else { columnLevel.colspan = 1; return 1; } } var TimelineGroupedView = kendo.Class.extend({ init: function(view) { this._view = view; }, _getTimeSlotByPosition: function(x, y, groupIndex) { var group = this._view.groups[groupIndex]; return group.timeSlotByPosition(x, y); }, _hideHeaders: function() { var view = this._view; view.timesHeader.find("table tr").last().hide(); view.datesHeader.find("table tr").last().hide(); }, _setColspan: function(timeColumn) { setColspan(timeColumn); }, _createRowsLayout: function(resources, rows, groupHeaderTemplate) { var view = this._view; return view._createRowsLayout(resources, rows, groupHeaderTemplate); }, _createVerticalColumnsLayout: function(resources, rows, groupHeaderTemplate, columns) { return columns; }, _createColumnsLayout: function(resources, columns, groupHeaderTemplate) { var view = this._view; return view._createColumnsLayout(resources, columns, groupHeaderTemplate); }, _getRowCount: function() { var view = this._view; return view._groupCount(); }, _getGroupsCount: function() { return 1; }, _addContent: function(dates, columnCount, groupsCount, rowCount, start, end, slotTemplate, isVerticalGrouped) { var view = this._view; var html = ""; var options = view.options; var resources = function(groupIndex) { return function() { return view._resourceBySlot({ groupIndex }); }; }; var appendRow = function(date) { var content = ""; var classes = ""; var tmplDate; if (kendo.date.isToday(dates[idx])) { classes += "k-today"; } if (kendo.date.getMilliseconds(date) < kendo.date.getMilliseconds(options.workDayStart) || kendo.date.getMilliseconds(date) >= kendo.date.getMilliseconds(options.workDayEnd) || !view._isWorkDay(dates[idx])) { classes += " k-nonwork-hour"; } content += "<td" + (classes !== "" ? " class=\"" + classes + "\"" : "") + ">"; tmplDate = kendo.date.getDate(dates[idx]); kendo.date.setTime(tmplDate, kendo.date.getMilliseconds(date)); content += slotTemplate({ date: tmplDate, resources: resources(isVerticalGrouped ? rowIdx : groupIdx) }); content += "</td>"; return content; }; for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) { html += "<tr>"; for (var groupIdx = 0; groupIdx < groupsCount; groupIdx++) { for (var idx = 0, length = columnCount; idx < length; idx++) { html += view._forTimeRange(start, end, appendRow); } } html += "</tr>"; } return html; }, _addTimeSlotsCollections: function(groupCount, datesCount, tableRows, interval, isVerticallyGrouped) { var view = this._view; var rowCount = tableRows.length; if (isVerticallyGrouped) { rowCount = Math.floor(rowCount / groupCount); } for (var groupIndex = 0; groupIndex < groupCount; groupIndex++) { var rowMultiplier = 0; var group = view.groups[groupIndex]; var time; if (isVerticallyGrouped) { rowMultiplier = groupIndex; } var rowIndex = rowMultiplier * rowCount; var cellMultiplier = 0; if (!isVerticallyGrouped) { cellMultiplier = groupIndex; } var cells = tableRows[rowIndex].children; var cellsPerGroup = cells.length / (!isVerticallyGrouped ? groupCount : 1); var cellsPerDay = cellsPerGroup / datesCount; for (var dateIndex = 0; dateIndex < datesCount; dateIndex++) { var cellOffset = dateIndex * cellsPerDay + cellsPerGroup * cellMultiplier; time = getMilliseconds(new Date(+view.startTime())); for (var cellIndex = 0; cellIndex < cellsPerDay; cellIndex++) { view._addTimeSlotToCollection(group, cells, cellIndex, cellOffset, dateIndex, time, interval); time += interval; } } } }, _getVerticalGroupCount: function(groupsCount) { return groupsCount; }, _getVerticalRowCount: function(eventGroups, groupIndex, maxRowCount) { var view = this._view; return view._isVerticallyGrouped() ? eventGroups[groupIndex].maxRowCount : maxRowCount; }, _renderEvent: function(eventGroup, event, adjustedEvent, group, range, container) { var view = this._view; var element; element = view._createEventElement(adjustedEvent.occurrence, event, range.head || adjustedEvent.head, range.tail || adjustedEvent.tail); element.appendTo(container).css({ top: 0, height: view.options.eventHeight }); var eventObject = { start: adjustedEvent.occurrence._startTime || adjustedEvent.occurrence.start, end: adjustedEvent.occurrence._endTime || adjustedEvent.occurrence.end, element, uid: event.uid, slotRange: range, rowIndex: 0, offsetTop: 0 }; eventGroup.events[event.uid] = eventObject; view._inverseEventColor(element); view.addContinuousEvent(group, range, element, event.isAllDay); view._arrangeRows(eventObject, range, eventGroup); }, _verticalCountForLevel: function(level) { var view = this._view; return view._rowCountForLevel(level); }, _horizontalCountForLevel: function(level) { var view = this._view; return view._columnCountForLevel(level); }, _updateCurrentVerticalTimeMarker: function(ranges, currentTime) { var view = this._view; var elementHtml = "<div class='" + CURRENT_TIME_MARKER_CLASS + "'></div>"; var headerWrap = view.datesHeader.find(DOT + SCHEDULER_HEADER_WRAP_CLASS); var left = Math.round(ranges[0].innerRect(currentTime, new Date(currentTime.getTime() + 1), false).left); var timesTableMarker = $(elementHtml).prependTo(headerWrap).addClass(CURRENT_TIME_MARKER_ARROW_CLASS + "-down"); timesTableMarker.css({ left: view._adjustLeftPosition(left - outerWidth(timesTableMarker) * BORDER_SIZE_COEFF / 2), top: headerWrap.find("tr").last().prev().position().top }); $(elementHtml).prependTo(view.content).css({ left: view._adjustLeftPosition(left), width: "1px", height: view.content[0].scrollHeight - 1, top: 0 }); }, _changeGroup: function() { return undefined; }, _prevGroupSlot: function(slot, group, isDay) { var view = this._view; if (view._isVerticallyGrouped()) { return slot; } else { var collection = group._collection(0, isDay); return collection.last(); } }, _nextGroupSlot: function(slot, group, isDay) { var view = this._view; if (view._isVerticallyGrouped()) { return slot; } else { var collection = group._collection(0, isDay); return collection.first(); } }, _verticalSlots: function(selection, reverse) { var view = this._view; return view._changeGroup(selection, reverse); }, _verticalMethod: function(reverse) { return reverse ? "leftSlot" : "rightSlot"; }, _normalizeVerticalSelection: function() { return undefined; }, _horizontalSlots: function(selection, group, method, startSlot, endSlot, multiple, reverse) { var view = this._view; var result = {}; result.startSlot = group[method](startSlot); result.endSlot = group[method](endSlot); if (!multiple && view._isHorizontallyGrouped() && (!result.startSlot || !result.endSlot)) { result.startSlot = result.endSlot = view._changeGroup(selection, reverse); } return result; }, _changeVerticalViewPeriod: function() { return false; }, _changeHorizontalViewPeriod: function(slots, shift, selection, reverse) { var view = this._view; if ((!slots.startSlot || !slots.endSlot) && !shift && view._changeViewPeriod(selection, reverse, false)) { return true; } return false; }, _updateDirection: function(selection, ranges, shift, reverse) { var view = this._view; view._updateDirection(selection, ranges, shift, reverse, true); }, _createMoveHint: function(range, adjustedEvent) { var view = this._view; var startSlot = range.start; var hint = view._createEventElement(adjustedEvent.occurrence, adjustedEvent.occurrence, false, false); hint.addClass("k-event-drag-hint"); var rect = range.innerRect(adjustedEvent.occurrence.start, adjustedEvent.occurrence.end, view.options.snap); var width = rect.right - rect.left; if (width < 0) { width = 0; } var left = view._adjustLeftPosition(rect.left); var css = { left, top: startSlot.offsetTop, height: startSlot.offsetHeight, width }; hint.css(css); if (adjustedEvent.occurrence.inverseColor) { hint.addClass(INVERSE_COLOR_CLASS); } view._appendMoveHint(hint); }, _adjustLeftPosition: function(left) { var view = this._view; if (view._isRtl) { left -= view.content[0].scrollWidth - view.content[0].clientWidth; } return left; } }); var TimelineGroupedByDateView = kendo.Class.extend({ init: function(view) { this._view = view; }, _getTimeSlotByPosition: function(x, y, groupIndex) { var group = this._view.groups[groupIndex]; return group.timeSlotByPosition(x, y, true); }, _hideHeaders: function() { var view = this._view; if (!view._isVerticallyGrouped()) { view.timesHeader.find("table tr").eq(2).hide(); view.datesHeader.find("table tr").eq(2).hide(); } else { view.times.find(".k-last").hide(); } }, _setColspan: function() {}, _createRowsLayout: function(resources, rows, groupHeaderTemplate, columns) { var view = this._view; return view._createDateLayout(columns, null, true); }, _createVerticalColumnsLayout: function(resources, rows, groupHeaderTemplate) { var view = this._view; return view._createColumnsLayout(resources, null, groupHeaderTemplate); }, _createColumnsLayout: function(resources, columns, groupHeaderTemplate, subColumns) { var view = this._view; return view._createColumnsLayout(resources, columns, groupHeaderTemplate, subColumns, true); }, _getRowCount: function(level) { var view = this._view; return view._rowCountForLevel(level); }, _getGroupsCount: function() { var view = this._view; return view._groupCount(); }, _addContent: function(dates, columnCount, groupsCount, rowCount, start, end, slotTemplate, isVerticalGrouped) { var view = this._view; var html = ""; var options = view.options; var resources = function(groupIndex) { return function() { return view._resourceBySlot({ groupIndex }); }; }; var appendRow = function(date, isMajorTickColumn, isMiddleColumn, isLastSlotColumn, minorTickColumns, groupIdx) { var content = ""; var classes = ""; var tmplDate; var workDateIndex = view._isVerticallyGrouped() ? dateIndex : idx; if (kendo.date.isToday(dates[idx])) { classes += "k-today"; } if (kendo.date.getMilliseconds(date) < kendo.date.getMilliseconds(options.workDayStart) || kendo.date.getMilliseconds(date) >= kendo.date.getMilliseconds(options.workDayEnd) || !view._isWorkDay(dates[workDateIndex])) { classes += " k-nonwork-hour"; } content += "<td" + (classes !== "" ? " class=\"" + classes + "\"" : "") + ">"; tmplDate = kendo.date.getDate(dates[idx]); kendo.date.setTime(tmplDate, kendo.date.getMilliseconds(date)); content += slotTemplate({ date: tmplDate, resources: resources(groupIdx) }); content += "</td>"; return content; }; var tempStart = new Date(start), minorTickCount = view.options.minorTickCount, msMajorInterval = view.options.majorTick * MS_PER_MINUTE, msInterval = msMajorInterval / minorTickCount || 1, dateIndex; for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) { html += "<tr>"; if (rowIdx % (rowCount / view._dates.length) === 0) { dateIndex = rowIdx / (rowCount / view._dates.length); tempStart = new Date(view._dates[dateIndex]); kendo.date.setTime(tempStart, kendo.date.getMilliseconds(start)); } for (var idx = 0, length = columnCount; idx < length; idx++) { html += view._forTimeRange(tempStart, end, appendRow, isVerticalGrouped, groupsCount); if (isVerticalGrouped) { setTime(tempStart, msInterval, false); break; } } html += "</tr>"; } return html; }, _addTimeSlotsCollections: function(groupCount, datesCount, tableRows, interval, isVerticallyGrouped) { var view = this._view; var rowCount = tableRows.length; if (isVerticallyGrouped) { rowCount = rowCount / datesCount; } for (var dateIndex = 0; dateIndex < datesCount; dateIndex++) { var rowMultiplier = 0; var time; if (isVerticallyGrouped) { rowMultiplier = dateIndex; } var rowIndex = rowMultiplier * rowCount; var cellMultiplier = 0; var cells = tableRows[rowIndex].children; var cellsPerGroup = isVerticallyGrouped ? rowCount : cells.length / (datesCount * groupCount); var cellsPerDay = cells.length / datesCount; var cellOffset; time = getMilliseconds(new Date(+view.startTime())); for (var cellIndex = 0; cellIndex < cellsPerGroup; cellIndex++) { if (!isVerticallyGrouped) { cellOffset = dateIndex * cellsPerDay + groupCount * cellIndex; cellMultiplier++; } else { cellOffset = 0; cells = tableRows[cellIndex + cellsPerGroup * dateIndex].children; } for (var groupIndex = 0; groupIndex < groupCount; groupIndex++) { var group = view.groups[groupIndex]; view._addTimeSlotToCollection(group, cells, groupIndex, cellOffset, dateIndex, time, interval); } time += interval; } } }, _getVerticalGroupCount: function() { var view = this._view; return view.content.find("tr").length; }, _getVerticalRowCount: function(eventGroups, groupIndex, maxRowCount) { return maxRowCount; }, _renderEvent: function(eventGroup, event, adjustedEvent, group, range, container, startIndex, endIndex) { var view = this._view; var element; var eventObjects = []; for (var i = range.start.index; i <= range.end.index; i++) { element = view._createEventElement(adjustedEvent.occurrence, event, i !== endIndex, i !== startIndex); element.appendTo(container).css({ top: 0, height: view.options.eventHeight }); var currentSlot = group._timeSlotCollections[0]._slots[i]; var dateRange = group.timeSlotRanges(currentSlot.start, currentSlot.end, false)[0]; var eventObject = { start: i === startIndex ? adjustedEvent.occurrence._startTime || adjustedEvent.occurrence.start : currentSlot.start, end: i === endIndex ? adjustedEvent.occurrence._endTime || adjustedEvent.occurrence.end : currentSlot.end, element, uid: event.uid, slotRange: dateRange, rowIndex: 0, offsetTop: 0 }; eventGroup.events[event.uid] = eventObject; eventObjects.push(eventObject); view.addContinuousEvent(group, dateRange, element, event.isAllDay); view._arrangeRows(eventObject, dateRange, eventGroup); } eventGroup.events[event.uid] = eventObjects; }, _verticalCountForLevel: function(level) { var view = this._view; return view._columnCountForLevel(level); }, _horizontalCountForLevel: function(level, columnLevel) { var view = this._view; return view._columnCountForLevel(columnLevel) / view._columnCountForLevel(2); }, _updateCurrentVerticalTimeMarker: function(ranges, currentTime) { var view = this._view; var firstTimesCell = view.times.find("tr").first().find("th").first(); var lastTimesCell = view.times.find("tr").first().find("th").last(); var elementHtml = "<div class='" + CURRENT_TIME_MARKER_CLASS + "'></div>"; var timesTableMarker = $(elementHtml).prependTo(view.times); var markerTopPosition = Math.round(ranges[0].innerRect(currentTime, new Date(currentTime.getTime() + 1), false).top); var timesTableMarkerCss = {}; if (this._isRtl) { timesTableMarkerCss.right = firstTimesCell.position().left + outerHeight(firstTimesCell) - outerHeight(lastTimesCell); timesTableMarker.addClass(CURRENT_TIME_MARKER_ARROW_CLASS + "-left"); } else { timesTableMarkerCss.left = lastTimesCell.position().left; timesTableMarker.addClass(CURRENT_TIME_MARKER_ARROW_CLASS + "-right"); } timesTableMarkerCss.top = markerTopPosition - outerWidth(timesTableMarker) * BORDER_SIZE_COEFF / 2; timesTableMarker.css(timesTableMarkerCss); $(elementHtml).prependTo(view.content).css({ top: markerTopPosition, height: "1px", right: "1px", width: view.content[0].scrollWidth, left: 0 }); }, _changeGroup: function(selection, previous, slot) { var view = this._view; if (!slot) { selection.groupIndex = previous ? view.groups.length - 1 : 0; } }, _prevGroupSlot: function(slot) { return slot; }, _nextGroupSlot: function(slot) { return slot; }, _changeDate: function(selection, reverse, slot) { var view = this._view; var group = view.groups[selection.groupIndex]; var collections, index; if (reverse) { collections = group._getCollections(false); index = slot.index - 1; if (index >= 0) { return collections[0]._slots[index]; } } else { collections = group._getCollections(false); index = slot.index + 1; if (collections[0] && collections[0]._slots[index]) { return collections[0]._slots[index]; } } }, _verticalSlots: function(selection, reverse, slot) { return this._changeDate(selection, reverse, slot); }, _verticalMethod: function(reverse, multiple) { if (multiple) { return reverse ? "upSlot" : "downSlot"; } else { return reverse ? "leftSlot" : "rightSlot"; } }, _normalizeVerticalSelection: function(selection, ranges, reverse, multiple) { var view = this._view; if (!multiple) { return view._normalizeVerticalSelection(selection, ranges, reverse); } return undefined; }, _horizontalSlots: function(selection, group, method, startSlot, endSlot, multiple, reverse) { var view = this._view; var tempSlot = view._changeGroup(selection, reverse); var result = {}; if (!tempSlot) { if (!view._isVerticallyGrouped()) { result.startSlot = group[method](startSlot); result.endSlot = group[method](endSlot); } } else { result.startSlot = result.endSlot = tempSlot; } return result; }, _changeVerticalViewPeriod: function(slots, shift, selection, reverse) { var view = this._view; if ((!slots.startSlot || !slots.endSlot) && !shift && view._changeViewPeriod(selection, reverse, view._isVerticallyGrouped())) { return true; } return false; }, _changeHorizontalViewPeriod: function(slots, shift, selection, reverse) { var view = this._view; if (view._isVerticallyGrouped()) { return false; } if ((!slots.startSlot || !slots.endSlot) && !shift && view._changeViewPeriod(selection, reverse, false)) { return true; } return false; }, _updateDirection: function(selection, ranges, shift, reverse) { var view = this._view; view._updateDirection(selection, ranges, shift, reverse, !view._isVerticallyGrouped()); }, _createMoveHint: function(range, adjustedEvent) { var view = this._view; var startSlot = range.start; var startEnd = range.end; for (var slotIdx = startSlot.index; slotIdx <= startEnd.index; slotIdx++) { var slot = range.collection._slots[slotIdx]; var hint = view._createEventElement(adjustedEvent.occurrence, adjustedEvent.occurrence, false, false); hint.addClass("k-event-drag-hint"); var css = { left: slot.offsetLeft + 2, top: slot.offsetTop, height: view.options.eventHeight, width: slot.offsetWidth }; hint.css(css); if (adjustedEvent.occurrence.inverseColor) { hint.addClass(INVERSE_COLOR_CLASS); } view._appendMoveHint(hint); } }, _adjustLeftPosition: function(left) { var view = this._view; if (view._isRtl && !view._isVerticallyGrouped()) { left -= view.content[0].scrollWidth - view.content[0].offsetWidth; } return left; } }); kendo.ui.scheduler.TimelineGroupedView = TimelineGroupedView; kendo.ui.scheduler.TimelineGroupedByDateView = TimelineGroupedByDateView; var TimelineView = SchedulerView.extend({ init: function(element, options) { var that = this, zeroWidthEvent = createZeroWidthEvent(); SchedulerView.fn.init.call(that, element, options); that._zeroWidthEventOffset = zeroWidthEvent.outerWidth(); zeroWidthEvent.remove(); that._groupedView = that._getGroupedView(); that.title = that.options.title || that.options.name; that._workDays = getWorkDays(that.options); that._templates(); that._editable(); that.calculateDateRange(); that._groups(); that._currentTime(true); }, name: "timeline", _isVirtualized: function() { return false; }, _getGroupedView: function() { if (this._isGroupedByDate()) { return new kendo.ui.scheduler.TimelineGroupedByDateView(this); } else { return new kendo.ui.scheduler.TimelineGroupedView(this); } }, _getNextEventIndexBySlot: function(slot, sortedEvents, groupIndex) { if (this._isVerticallyGrouped()) { return kendo.ui.SchedulerView.fn._getNextEventIndexBySlot.call(this, slot, sortedEvents, groupIndex); } var tempIndex = 0; for (var i = 0; i < sortedEvents.length; i++) { if (slot.startDate() > sortedEvents[i].start.startDate()) { tempIndex++; continue; } if (slot.startDate().getTime() === sortedEvents[i].start.startDate().getTime() && groupIndex > sortedEvents[i].start.groupIndex) { tempIndex++; continue; } break; } return tempIndex; }, _getSelectedSlot: function(slot, sortedEvents, event, idx, pad, prev) { if (this._isVerticallyGrouped()) { return kendo.ui.SchedulerView.fn._getSelectedSlot.call(this, slot, sortedEvents, event, idx, pad, prev); } return slot; }, _getSortedEvents: function(uniqueAllEvents) { if (this._isVerticallyGrouped()) { return kendo.ui.SchedulerView.fn._getSortedEvents.call(this, uniqueAllEvents); } return uniqueAllEvents.sort(function(first, second) { var result = first.start.startDate().getTime() - second.start.startDate().getTime(); if (result === 0) { if (first.start.isDaySlot && !second.start.isDaySlot) { result = -1; } if (!first.start.isDaySlot && second.start.isDaySlot) { result = 1; } } if (result === 0) { result = first.start.groupIndex - second.start.groupIndex; } if (result === 0) { result = $(first.element).index() - $(second.element).index(); } return result; }); }, _currentTimeMarkerUpdater: function() { this._updateCurrentTimeMarker(new Date()); }, _scrollTo: function(element, container) { SchedulerView.fn._scrollTo.call(this, element, container); var elementOffset = element.offsetLeft, elementOffsetDir = element.offsetWidth, containerScroll = container.scrollLeft, containerOffsetDir = container.clientWidth, rightDistance = elementOffset + elementOffsetDir, result = 0; if (containerScroll > elementOffset) { result = elementOffset; } else if (rightDistance > containerScroll + containerOffsetDir) { if (elementOffsetDir <= containerOffsetDir) { result = rightDistance - containerOffsetDir; } else { result = elementOffset; } } else { result = containerScroll; } container.scrollLeft = result; }, _updateCurrentTimeMarker: function(currentTime) { var options = this.options; this.datesHeader.find(DOT + CURRENT_TIME_MARKER_CLASS).remove(); this.times.find(DOT + CURRENT_TIME_MARKER_CLASS).remove(); this.content.find(DOT + CURRENT_TIME_MARKER_CLASS).remove(); if (!this._isInDateSlot({ start: currentTime, end: currentTime })) { return; } if (options.currentTimeMarker.useLocalTimezone === false) { var timezone = options.dataSource.options.schema.timezone; if (options.dataSource && timezone) { var timezoneOffset = kendo.timezone.offset(currentTime, timezone); currentTime = kendo.timezone.convert(currentTime, currentTime.getTimezoneOffset(), timezoneOffset); } } var groupsCount = !options.group || options.group.orientation == "vertical" ? 1 : this.groups.length; for (var groupIndex = 0; groupIndex < groupsCount; groupIndex++) { var currentGroup = this.groups[groupIndex]; if (!currentGroup) { return; } var utcCurrentTime = kendo.date.toUtcTime(currentTime); var ranges = currentGroup.timeSlotRanges(utcCurrentTime, utcCurrentTime + 1); if (ranges.length === 0) { return; } var collection = ranges[0].collection; var slotElement = collection.slotByStartDate(currentTime); if (slotElement) { if (this._isVerticallyGrouped()) { this._groupedView._updateCurrentVerticalTimeMarker(ranges, currentTime); } else { var elementHtml = "<div class='" + CURRENT_TIME_MARKER_CLASS + "'></div>"; var headerWrap = this.datesHeader.find(DOT + SCHEDULER_HEADER_WRAP_CLASS); var left = Math.round(ranges[0].innerRect(currentTime, new Date(currentTime.getTime() + 1), false).left); var timesTableMarker = $(elementHtml).prependTo(headerWrap).addClass(CURRENT_TIME_MARKER_ARROW_CLASS + "-down"); timesTableMarker.css({ left: this._adjustLeftPosition(left - outerWidth(timesTableMarker) * BORDER_SIZE_COEFF / 2), top: headerWrap.find("tr").last().prev().position().top }); $(elementHtml).prependTo(this.content).css({ left: this._adjustLeftPosition(left), width: "1px", height: this.content[0].scrollHeight - 1, top: 0 }); } } } }, _adjustLeftPosition: function(left) { return this._groupedView._adjustLeftPosition(left); }, _currentTime: function(setUpdateTimer) { var that = this; var markerOptions = that.options.currentTimeMarker; if (markerOptions !== false && markerOptions.updateInterval !== undefined) { that._currentTimeMarkerUpdater(); if (setUpdateTimer) { that._currentTimeUpdateTimer = setInterval(this._currentTimeMarkerUpdater.bind(that), markerOptions.updateInterval); } } }, _editable: function() { if (this.options.editable) { if (this._isMobile()) { this._touchEditable(); } else { this._mouseEditable(); } } }, _mouseEditable: function() { var that = this; that.element.on("click" + NS, ".k-event a:has(.k-i-x),.k-event a:has(.k-svg-i-x)", function(e) { that.trigger("remove", { uid: $(this).closest(".k-event").attr(kendo.attr("uid")) }); e.preventDefault(); }); if (that.options.editable.create !== false) { that.element.on("dblclick" + NS, ".k-scheduler-content > table td", function(e) { var slot = that._slotByPosition(e.pageX, e.pageY); if (slot) { var resourceInfo = that._resourceBySlot(slot); that.trigger("add", { eventInfo: extend({ start: slot.startDate(), end: slot.endDate() }, resourceInfo) }); } e.preventDefault(); }); } if (that.options.editable.update !== false) { that.element.on("dblclick" + NS, ".k-event", function(e) { that.trigger("edit", { uid: $(this).closest(".k-event").attr(kendo.attr("uid")) }); e.preventDefault(); }); } }, _touchEditable: function() { var that = this; var threshold = 0; if (kendo.support.mobileOS.android) { threshold = 5; } if (that.options.editable.create !== false) { that._addUserEvents = new kendo.UserEvents(that.element, { threshold, useClickAsTap: !kendo.support.browser.edge, filter: ".k-scheduler-content td", tap: function(e) { if (that._scrolling) { return; } var x = e.x.location !== undefined ? e.x.location : e.x; var y = e.y.location !== undefined ? e.y.location : e.y; var slot = that._slotByPosition(x, y); if (slot) { var resourceInfo = that._resourceBySlot(slot); that.trigger("add", { eventInfo: extend({ start: slot.startDate(), end: slot.endDate() }, resourceInfo) }); } e.preventDefault(); } }); } if (that.options.editable.update !== false) { that._editUserEvents = new kendo.UserEvents(that.element, { threshold, useClickAsTap: !kendo.support.browser.edge, filter: ".k-event", tap: function(e) { if (that._scrolling) { return; } var eventElement = $(e.target).closest(".k-event"); var touchElement = $(e.touch.initialTouch); if (touchElement.is(".k-i-x,.k-svg-i-x")) { that.trigger("remove", { uid: eventElement.attr(kendo.attr("uid")) }); } else if (!eventElement.hasClass("k-event-active")) { that.trigger("edit", { uid: eventElement.attr(kendo.attr("uid")) }); } e.preventDefault(); } }); } }, _slotByPosition: function(x, y) { var slot; var content = this.content; var offset = content.offset(); var groupIndex; x -= offset.left; y -= offset.top; if (this._isRtl) { var browser = kendo.support.browser; if (browser.mozilla || browser.webkit && browser.version >= 85) { x += content[0].scrollWidth - content[0].offsetWidth; x += content[0].scrollLeft; } else if (browser.msie) { x -= kendo.scrollLeft(content); x += content[0].scrollWidth - content[0].offsetWidth; } else if (browser.webkit) { x += content[0].scrollLeft; } } else { x += content[0].scrollLeft; } y += content[0].scrollTop; x = Math.ceil(x); y = Math.ceil(y); for (groupIndex = 0; groupIndex < this.groups.length; groupIndex++) { slot = this._groupedView._getTimeSlotByPosition(x, y, groupIndex); if (slot) { return slot; } } return null; }, options: { name: "TimelineView", title: "Timeline", selectedDateFormat: "{0:D}", selectedShortDateFormat: "{0:d}", selectedMobileDateFormat: "{0:MMM dd}", date: kendo.date.today(), startTime: kendo.date.today(), endTime: kendo.date.today(), showWorkHours: false, minorTickCount: 2, editable: true, workDayStart: new Date(1980, 1, 1, 8, 0, 0), workDayEnd: new Date(1980, 1, 1, 17, 0, 0), workWeekStart: 1, workWeekEnd: 5, majorTick: 60, eventHeight: 25, eventMinWidth: 0, columnWidth: 100, groupHeaderTemplate: ({ text }) => text, majorTimeHeaderTemplate: ({ date }) => kendo.toString(date, "t"), slotTemplate: () => "&nbsp;", eventTemplate: EVENT_TEMPLATE, dateHeaderTemplate: DATA_HEADER_TEMPLATE, footer: { command: "workDay" }, currentTimeMarker: { updateInterval: 1e4, useLocalTimezone: true }, ongoingEvents: { cssClass: null, enabled: true, updateInterval: 6e4 }, messages: { defaultRowText: "All events", showFullDay: "Show full day", showWorkDay: "Show business hours" } }, events: [ "remove", "add", "edit" ], _templates: function() { var options = this.options, settings = extend({}, kendo.Template, options.templateSettings); this.eventTemplate = kendo.template(EVENT_WRAPPER_TEMPLATE); this.majorTimeHeaderTemplate = kendo.template(options.majorTimeHeaderTemplate, settings); this.dateHeaderTemplate = kendo.template(options.dateHeaderTemplate, settings); this.slotTemplate = kendo.template(options.slotTemplate, settings); this.groupHeaderTemplate = kendo.template(options.groupHeaderTemplate, settings); }, _render: function(dates) { var that = this; dates = dates || []; that._dates = dates; that._startDate = dates[0]; that._endDate = dates[dates.length - 1 || 0]; that._calculateSlotRanges(); that.createLayout(that._layout(dates)); that._content(dates); that._footer(); that._setContentWidth(); that.refreshLayout(); that.datesHeader.on("click" + NS, ".k-nav-day", function(e) { var th = $(e.currentTarget).closest("th"); var slot = that._slotByPosition(th.offset().left, that.content.offset().top); that.trigger("navigate", { view: "timeline", date: slot.startDate() }); }); that._groupedView._hideHeaders(); }, _setContentWidth: function() { var content = this.content; var contentWidth = content.width(); var contentTable = this.content.find("table"); var columnCount = contentTable.find("tr").first().children().length; var minWidth = 100; var calculatedWidth = columnCount * this.options.columnWidth; if (contentWidth < calculatedWidth) { minWidth = Math.ceil(calculatedWidth / contentWidth * 100); } contentTable.add(this.datesHeader.find("table")).css("width", minWidth + "%"); }, _calculateSlotRanges: function() { var dates = this._dates; var slotStartTime = this.startTime(); var slotEndTime = this.endTime(); slotEndTime = getMilliseconds(slotEndTime); slotStartTime = getMilliseconds(slotStartTime); if (slotEndTime === slotStartTime) { slotEndTime += MS_PER_DAY - 1; } else if (slotEndTime < slotStartTime) { slotEndTime += MS_PER_DAY; } var slotRanges = []; for (var i = 0; i < dates.length; i++) { var rangeStart = getDate(dates[i]); setTime(rangeStart, slotStartTime); var rangeEnd = getDate(dates[i]); setTime(rangeEnd, slotEndTime); slotRanges.push({ start: kendo.date.toUtcTime(rangeStart), end: kendo.date.toUtcTime(rangeEnd) }); } this._slotRanges = slotRanges; }, _forTimeRange: function(min, max, action, verticalByDate, groupsCount) { min = toInvariantTime(min); max = toInvariantTime(max); var that = this, msMin = getMilliseconds(min), msMax = getMilliseconds(max), minorTickCount = that.options.minorTickCount, msMajorInterval = that.options.majorTick * MS_PER_MINUTE, msInterval = msMajorInterval / minorTickCount || 1, start = new Date(+min), idx = 0, length, html = ""; length = MS_PER_DAY / msInterval; if (msMin != msMax) { if (msMin > msMax) { msMax += MS_PER_DAY; } length = (msMax - msMin) / msInterval; } length = verticalByDate ? 1 : Math.round(length); if (groupsCount) { length = length * groupsCount; } for (; idx < length; idx++) { var majorTickDivider = idx % (msMajorInterval / msInterval); var isMajorTickColumn = majorTickDivider === 0; var isMiddleColumn = majorTickDivider < minorTickCount - 1; var isLastSlotColumn = majorTickDivider === minorTickCount - 1; var minorTickColumns = minorTickCount; if (length % minorTickCount !== 0) { var isLastMajorSlot = length - (idx + 1) < minorTickCount; if (isMajorTickColumn && isLastMajorSlot) { minorTickColumns = length % minorTickCount; } } html += action(start, isMajorTickColumn, isMiddleColumn, isLastSlotColumn, minorTickColumns, idx % groupsCount); if (!verticalByDate) { if (groupsCount) { if (idx % groupsCount === groupsCount - 1) { setTime(start, msInterval, false); } } else { setTime(start, msInterval, false); } } } return html; }, _layout: function(dates) { var timeColumns = []; var columns = []; var that = this; var rows = [{ text: that.options.messages.defaultRowText }]; var groupedView = that._groupedView; var minorTickSlots = []; for (var minorTickIndex = 0; minorTickIndex < that.options.minorTickCount; minorTickIndex++) { minorTickSlots.push({ text: "&#8203;", minorTicks: true }); } this._forTimeRange(that.startTime(), that.endTime(), function(date, majorTick, middleColumn, lastSlotColumn, minorSlotsCount) { var template = that.majorTimeHeaderTemplate; if (majorTick) { var timeColumn = { text: template({ date }), className: lastSlotColumn ? "k-slot-cell" : "", columns: minorTickSlots.slice(0, minorSlotsCount) }; groupedView._setColspan(timeColumn); timeColumns.push(timeColumn); } }); for (var idx = 0; idx < dates.length; idx++) { columns.push({ text: that.dateHeaderTemplate({ date: dates[idx] }), className: "k-slot-cell", columns: timeColumns.slice(0) }); } var resources = this.groupedResources; if (resources.length) { if (this._groupOrientation() === "vertical") { rows = groupedView._createRowsLayout(resources, null, this.groupHeaderTemplate, columns); columns = groupedView._createVerticalColumnsLayout(resources, null, this.groupHeaderTemplate, columns); } else { columns = groupedView._createColumnsLayout(resources, columns, this.groupHeaderTemplate, columns); } } return { columns, rows }; }, _footerItems: function() { var that = this, items = [], options = this.options, command = options.footer.command; if (that._isMobile()) { items.push(that._defaultTools.todayMobile); } if (command && command === "workDay") { if (that._isMobile()) { items.push(that._defaultTools.fulldayMobile); } else { items.push(that._defaultTools.fulldayDesktop); } } return items; }, toggleFullDay: function() { var options = this.options; this.trigger("navigate", { view: this.name || options.name, date: options.date, isWorkDay: !options.showWorkHours }); }, _columnCountForLevel: function(level) { var columnLevel = this.columnLevels[level]; return columnLevel ? columnLevel.length : 0; }, _rowCountForLevel: function(level) { var rowLevel = this.rowLevels[level]; return rowLevel ? rowLevel.length : 0; }, _isWorkDay: function(date) { var day = date.getDay(); var workDays = this._workDays; for (var i = 0; i < workDays.length; i++) { if (workDays[i] === day) { return true; } } return false; }, _content: function(dates) { var that = this; var start = that.startTime(); var end = this.endTime(); var groupsCount = 1; var rowCount = 1; var columnCount = dates.length; var html = ""; var resources = this.groupedResources; var slotTemplate = this.slotTemplate; var isVerticalGrouped = false; if (resources.length) { isVerticalGrouped = that._groupOrientation() === "vertical"; if (isVerticalGrouped) { rowCount = that._groupedView._getRowCount(this.rowLevels.length - 1); groupsCount = that._groupedView._getGroupsCount(); } else { groupsCount = that._groupCount(); } } html += "<tbody>"; html += that._groupedView._addContent(dates, columnCount, groupsCount, rowCount, start, end, slotTemplate, isVerticalGrouped); html += "</tbody>"; this.content.find("table").append(html); }, _groups: function() { var groupCount = this._groupCount(); var dates = this._dates; var columnCount = dates.length; this.groups = []; for (var idx = 0; idx < groupCount; idx++) { var view = this._addResourceView(idx); var start = dates[0]; var end = dates[dates.length - 1 || 0]; var startTime = getMilliseconds(this.startTime()); var endTime = getMilliseconds(this.endTime()); if (startTime !== 0 && endTime <= startTime) { start = getDate(start); setTime(start, startTime); end = getDate(end); setTime(end, endTime); } view.addTimeSlotCollection(start, kendo.date.addDays(end, 1)); } this._timeSlotGroups(groupCount, columnCount); }, _isHorizontallyGrouped: function() { return this.groupedResources.length && this._groupOrientation() === "horizontal"; }, _timeSlotGroups: function(groupCount, datesCount) { var interval = this._timeSlotInterval(); var isVerticallyGrouped = this._isVerticallyGrouped(); var tableRows = this.content.find("tr"); this._groupedView._addTimeSlotsCollections(groupCount, datesCount, tableRows, interval, isVerticallyGrouped); }, _addTimeSlotToCollection: function(group, cells, cellIndex, cellOffset, dateIndex, time, interval) { var cell = cells[cellIndex + cellOffset]; var collection = group.getTimeSlotCollection(0); var currentDate = this._dates[dateIndex]; var currentTime = Date.UTC(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate()); var start = currentTime + time; var end = start + interval; collection.addTimeSlot(cell, start, end, true); }, startDate: function() { return this._startDate; }, endDate: function() { return this._endDate; }, visibleEndDate: function() { var startTime = getMilliseconds(this.startTime()); var endTime = getMilliseconds(this.endTime()); var endDate = this.endDate(); if (startTime !== 0 && endTime <= startTime) { endDate = kendo.date.addDays(endDate, 1); } return endDate; }, startTime: function() { var options = this.options; return options.showWorkHours ? options.workDayStart : options.startTime; }, endTime: function() { var options = this.options; return options.showWorkHours ? options.workDayEnd : options.endTime; }, _timeSlotInterval: function() { var options = this.options; return options.majorTick / options.minorTickCount * MS_PER_MINUTE; }, nextDate: function() { return kendo.date.nextDay(this.endDate()); }, previousDate: function() { return kendo.date.previousDay(this.startDate()); }, calculateDateRange: function() { this._render([this.options.date]); }, render: function(events) { this._headerColumnCount = 0; this._groups(); this.element.find(".k-event").remove(); events = new kendo.data.Query(events).sort([{ field: "start", dir: "asc" }, { field: "end", dir: "desc" }]).toArray(); var eventsByResource = []; this._eventsByResource(events, this.groupedResources, eventsByResource); var eventGroups = []; var maxRowCount = 0; for (var groupIndex = 0; groupIndex < eventsByResource.length; groupIndex++) { var eventGroup = { groupIndex, maxRowCount: 0, events: {} }; eventGroups.push(eventGroup); this._renderEvents(eventsByResource[groupIndex], groupIndex, eventGroup); if (maxRowCount < eventGroup.maxRowCount) { maxRowCount = eventGroup.maxRowCount; } } this._setRowsHeight(eventGroups, eventsByResource.length, maxRowCount); this._positionEvents(eventGroups, eventsByResource.length); this._currentTime(false); this._ongoingEvents(events); this.trigger("activate"); }, _positionEvents: function(eventGroups, groupsCount) { for (var groupIndex = 0; groupIndex < groupsCount; groupIndex++) { var eventsForGroup = eventGroups[groupIndex].events; for (var eventUid in eventsForGroup) { var eventObject = eventsForGroup[eventUid]; if (Array.isArray(eventObject)) { for (var eventIndex = 0; eventIndex < eventObject.length; eventIndex++) { this._positionEvent(eventObject[eventIndex]); } } else { this._positionEvent(eventObject); } } } }, _setRowsHeight: function(eventGroups, groupsCount, maxRowCount) { var eventHeight = this.options.eventHeight; var eventBottomOffset = this._getBottomRowOffset(); var groupedView = this._groupedView; var verticalGroupCount = groupedView._getVerticalGroupCount(groupsCount); groupsCount = this._isVerticallyGrouped() ? verticalGroupCount : 1; for (var groupIndex = 0; groupIndex < groupsCount; groupIndex++) { var rowsCount = groupedView._getVerticalRowCount(eventGroups, groupIndex, maxRowCount); rowsCount = rowsCount ? rowsCount : 1; var rowHeight = (eventHeight + 2) * rowsCount + eventBottomOffset; var timesRow = $(this.times.find("tr")[groupIndex]); var row = $(this.content.find("tr")[groupIndex]); timesRow.height(rowHeight); row.height(rowHeight); } this._setContentWidth(); this.refreshLayout(); this._refreshSlots(); }, _getBottomRowOffset: function() { var eventBottomOffset = this.options.eventHeight * .5; var isMobile = this._isMobile(); var minOffset; var maxOffset; if (isMobile) { minOffset = 30; maxOffset = 60; } else { minOffset = 15; maxOffset = 30; } if (eventBottomOffset > maxOffset) { eventBottomOffset = maxOffset; } else if (eventBottomOffset < minOffset) { eventBottomOffset = minOffset; } return eventBottomOffset; }, _positionEvent: function(eventObject) { var eventHeight = this.options.eventHeight; var eventOffset = 2; var rect = eventObject.slotRange.innerRect(eventObject.start, eventObject.end, false); var left = this._adjustLeftPosition(rect.left); var width = rect.right - rect.left; if (width < 0) { width = 0; } if (width < this.options.eventMinWidth) { var slotsCollection = eventObject.slotRange.collection; var lastSlot = slotsCollection._slots[slotsCollection._slots.length - 1]; var offsetRight = lastSlot.offsetLeft + lastSlot.offsetWidth; width = this.options.eventMinWidth; if (offsetRight < left + width) { width = offsetRight - rect.left; } } eventObject.element.css({ top: eventObject.slotRange.start.offsetTop + eventObject.rowIndex * (eventHeight + eventOffset) + "px", left, width }); }, _refreshSlots: function() { for (var groupIndex = 0; groupIndex < this.groups.length; groupIndex++) { this.groups[groupIndex].refresh(); } }, _eventsByResource: function(events, resources, result, parentValue) { var resource = resources[0]; if (resource) { var view = resource.dataSource.view(); view = view.filter(function(item) { var itemParentValue = kendo.getter(resource.dataParentValueField)(item); return itemParentValue === null || itemParentValue === undefined || itemParentValue === parentValue; }); for (var itemIdx = 0; itemIdx < view.length; itemIdx++) { var value = this._resourceValue(resource, view[itemIdx]); var eventsFilteredByResource = new kendo.data.Query(events).filter({ field: resource.field, operator: SchedulerView.groupEqFilter(value) }).toArray(); if (resources.length > 1) { this._eventsByResource(eventsFilteredByResource, resources.slice(1), result, value); } else { result.push(eventsFilteredByResource); } } } else { result.push(events); } }, _isInDateSlot: function(event) { var startTime = event.start; var endTime = event.end; var rangeStart = getDate(this._startDate); var rangeEnd = kendo.date.addDays(getDate(this.visibleEndDate()), 1); if (startTime < rangeEnd && rangeStart <= endTime) { return true; } return false; }, _isInTimeSlot: function(event) { var startTime = event._startTime || kendo.date.toUtcTime(event.start); var endTime = event._endTime || kendo.date.toUtcTime(event.end); var slotRanges = this._slotRanges; if (startTime === endTime) { endTime = endTime + 1; } for (var slotIndex = 0;