ui.scheduler.timeline.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. /**
  2. * DevExtreme (ui/scheduler/workspaces/ui.scheduler.timeline.js)
  3. * Version: 19.1.16
  4. * Build date: Tue Oct 18 2022
  5. *
  6. * Copyright (c) 2012 - 2022 Developer Express Inc. ALL RIGHTS RESERVED
  7. * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
  8. */
  9. "use strict";
  10. var $ = require("../../../core/renderer");
  11. var noop = require("../../../core/utils/common").noop;
  12. var extend = require("../../../core/utils/extend").extend;
  13. var registerComponent = require("../../../core/component_registrator");
  14. var SchedulerWorkSpace = require("./ui.scheduler.work_space.indicator");
  15. var dateUtils = require("../../../core/utils/date");
  16. var tableCreator = require("../ui.scheduler.table_creator");
  17. var HorizontalShader = require("../shaders/ui.scheduler.current_time_shader.horizontal");
  18. var TIMELINE_CLASS = "dx-scheduler-timeline";
  19. var GROUP_TABLE_CLASS = "dx-scheduler-group-table";
  20. var HORIZONTAL_GROUPED_WORKSPACE_CLASS = "dx-scheduler-work-space-horizontal-grouped";
  21. var HEADER_PANEL_CELL_CLASS = "dx-scheduler-header-panel-cell";
  22. var HEADER_PANEL_WEEK_CELL_CLASS = "dx-scheduler-header-panel-week-cell";
  23. var HEADER_ROW_CLASS = "dx-scheduler-header-row";
  24. var HORIZONTAL = "horizontal";
  25. var DATE_TABLE_CELL_BORDER = 1;
  26. var DATE_TABLE_HEADER_MARGIN = 10;
  27. var toMs = dateUtils.dateToMilliseconds;
  28. var SchedulerTimeline = SchedulerWorkSpace.inherit({
  29. _init: function() {
  30. this.callBase();
  31. this.$element().addClass(TIMELINE_CLASS);
  32. this._$sidebarTable = $("<table>").addClass(GROUP_TABLE_CLASS)
  33. },
  34. _getCellFromNextRow: function(direction, isMultiSelection) {
  35. if (!isMultiSelection) {
  36. return this.callBase(direction, isMultiSelection)
  37. }
  38. return this._$focusedCell
  39. },
  40. _getDefaultGroupStrategy: function() {
  41. return "vertical"
  42. },
  43. _toggleGroupingDirectionClass: function() {
  44. this.$element().toggleClass(HORIZONTAL_GROUPED_WORKSPACE_CLASS, this._isHorizontalGroupedWorkSpace())
  45. },
  46. _getDefaultOptions: function() {
  47. return extend(this.callBase(), {
  48. groupOrientation: "vertical"
  49. })
  50. },
  51. _getRightCell: function() {
  52. var $rightCell;
  53. var $focusedCell = this._$focusedCell;
  54. var rowCellCount = this._getCellCount();
  55. var edgeCellIndex = this._isRTL() ? 0 : rowCellCount - 1;
  56. var direction = this._isRTL() ? "prev" : "next";
  57. if ($focusedCell.index() === edgeCellIndex) {
  58. $rightCell = $focusedCell
  59. } else {
  60. $rightCell = $focusedCell[direction]();
  61. $rightCell = this._checkForViewBounds($rightCell)
  62. }
  63. return $rightCell
  64. },
  65. _getLeftCell: function() {
  66. var $leftCell;
  67. var $focusedCell = this._$focusedCell;
  68. var rowCellCount = this._getCellCount();
  69. var edgeCellIndex = this._isRTL() ? rowCellCount - 1 : 0;
  70. var direction = this._isRTL() ? "next" : "prev";
  71. if ($focusedCell.index() === edgeCellIndex) {
  72. $leftCell = $focusedCell
  73. } else {
  74. $leftCell = $focusedCell[direction]();
  75. $leftCell = this._checkForViewBounds($leftCell)
  76. }
  77. return $leftCell
  78. },
  79. _getRowCount: function() {
  80. return 1
  81. },
  82. _getCellCount: function() {
  83. return this._getCellCountInDay() * this.option("intervalCount")
  84. },
  85. getGroupTableWidth: function() {
  86. return this._$sidebarTable ? this._$sidebarTable.outerWidth() : 0
  87. },
  88. _getTotalRowCount: function(groupCount) {
  89. if (this._isHorizontalGroupedWorkSpace()) {
  90. return this._getRowCount()
  91. } else {
  92. groupCount = groupCount || 1;
  93. return this._getRowCount() * groupCount
  94. }
  95. },
  96. _getDateByIndex: function(index) {
  97. var resultDate = new Date(this._firstViewDate);
  98. var dayIndex = Math.floor(index / this._getCellCountInDay());
  99. resultDate.setTime(this._firstViewDate.getTime() + this._calculateCellIndex(0, index) * this._getInterval() + dayIndex * this._getHiddenInterval());
  100. return resultDate
  101. },
  102. _getFormat: function() {
  103. return "shorttime"
  104. },
  105. _needApplyLastGroupCellClass: function() {
  106. return true
  107. },
  108. _calculateHiddenInterval: function(rowIndex, cellIndex) {
  109. var dayIndex = Math.floor(cellIndex / this._getCellCountInDay());
  110. return dayIndex * this._getHiddenInterval()
  111. },
  112. _getMillisecondsOffset: function(rowIndex, cellIndex) {
  113. cellIndex = this._calculateCellIndex(rowIndex, cellIndex);
  114. return this._getInterval() * cellIndex + this._calculateHiddenInterval(rowIndex, cellIndex)
  115. },
  116. _createWorkSpaceElements: function() {
  117. this._createWorkSpaceScrollableElements()
  118. },
  119. _getWorkSpaceHeight: function() {
  120. if (this.option("crossScrollingEnabled")) {
  121. return this._$dateTable.get(0).getBoundingClientRect().height
  122. }
  123. return this.$element().get(0).getBoundingClientRect().height
  124. },
  125. _dateTableScrollableConfig: function() {
  126. var config = this.callBase();
  127. var timelineConfig = {
  128. direction: HORIZONTAL
  129. };
  130. return this.option("crossScrollingEnabled") ? config : extend(config, timelineConfig)
  131. },
  132. _needCreateCrossScrolling: function() {
  133. return true
  134. },
  135. _headerScrollableConfig: function() {
  136. var config = this.callBase();
  137. return extend(config, {
  138. scrollByContent: true
  139. })
  140. },
  141. _renderTimePanel: noop,
  142. _renderAllDayPanel: noop,
  143. _getTableAllDay: function() {
  144. return false
  145. },
  146. _getDateHeaderTemplate: function() {
  147. return this.option("timeCellTemplate")
  148. },
  149. _toggleAllDayVisibility: noop,
  150. _changeAllDayVisibility: noop,
  151. supportAllDayRow: function() {
  152. return false
  153. },
  154. _getGroupHeaderContainer: function() {
  155. if (this._isHorizontalGroupedWorkSpace()) {
  156. return this._$thead
  157. }
  158. return this._$sidebarTable
  159. },
  160. _insertAllDayRowsIntoDateTable: function() {
  161. return false
  162. },
  163. _createAllDayPanelElements: noop,
  164. _renderDateHeader: function() {
  165. var $headerRow = this.callBase();
  166. if (this._needRenderWeekHeader()) {
  167. var firstViewDate = new Date(this._firstViewDate);
  168. var $cells = [];
  169. var colspan = this._getCellCountInDay();
  170. var cellTemplate = this.option("dateCellTemplate");
  171. for (var i = 0; i < this._getWeekDuration() * this.option("intervalCount"); i++) {
  172. var $th = $("<th>");
  173. var text = this._formatWeekdayAndDay(firstViewDate);
  174. if (cellTemplate) {
  175. var templateOptions = {
  176. model: {
  177. text: text,
  178. date: new Date(firstViewDate)
  179. },
  180. container: $th,
  181. index: i
  182. };
  183. cellTemplate.render(templateOptions)
  184. } else {
  185. $th.text(text)
  186. }
  187. $th.addClass(HEADER_PANEL_CELL_CLASS).addClass(HEADER_PANEL_WEEK_CELL_CLASS).attr("colSpan", colspan);
  188. $cells.push($th);
  189. this._incrementDate(firstViewDate)
  190. }
  191. var $row = $("<tr>").addClass(HEADER_ROW_CLASS).append($cells);
  192. $headerRow.before($row)
  193. }
  194. },
  195. _needRenderWeekHeader: function() {
  196. return false
  197. },
  198. _incrementDate: function(date) {
  199. date.setDate(date.getDate() + 1)
  200. },
  201. _getWeekDuration: function() {
  202. return 1
  203. },
  204. _renderView: function() {
  205. this._setFirstViewDate();
  206. var groupCellTemplates = this._renderGroupHeader();
  207. this._renderDateHeader();
  208. this._renderAllDayPanel();
  209. this._renderTimePanel();
  210. this._renderDateTable();
  211. this._shader = new HorizontalShader;
  212. this._updateGroupTableHeight();
  213. this._$sidebarTable.appendTo(this._sidebarScrollable.$content());
  214. this._applyCellTemplates(groupCellTemplates)
  215. },
  216. _setHorizontalGroupHeaderCellsHeight: noop,
  217. getIndicationWidth: function() {
  218. var today = this._getToday();
  219. var cellWidth = this.getCellWidth();
  220. var date = this._getIndicationFirstViewDate();
  221. var hiddenInterval = this._getHiddenInterval();
  222. var timeDiff = today.getTime() - date.getTime();
  223. var differenceInDays = Math.ceil(timeDiff / toMs("day")) - 1;
  224. var duration = timeDiff - differenceInDays * hiddenInterval;
  225. var cellCount = duration / this.getCellDuration();
  226. return cellCount * cellWidth
  227. },
  228. _renderIndicator: function(height, rtlOffset, $container, groupCount) {
  229. var $indicator;
  230. var width = this.getIndicationWidth();
  231. if ("vertical" === this.option("groupOrientation")) {
  232. $indicator = this._createIndicator($container);
  233. $indicator.height($container.get(0).getBoundingClientRect().height);
  234. $indicator.css("left", rtlOffset ? rtlOffset - width : width)
  235. } else {
  236. for (var i = 0; i < groupCount; i++) {
  237. var offset = this._getCellCount() * this.getCellWidth() * i;
  238. $indicator = this._createIndicator($container);
  239. $indicator.height($container.get(0).getBoundingClientRect().height);
  240. $indicator.css("left", rtlOffset ? rtlOffset - width - offset : width + offset)
  241. }
  242. }
  243. },
  244. _isVerticalShader: function() {
  245. return false
  246. },
  247. _isCurrentTimeHeaderCell: function(headerIndex) {
  248. var result = false;
  249. if (this.option("showCurrentTimeIndicator") && this._needRenderDateTimeIndicator()) {
  250. var date = this._getDateByIndex(headerIndex);
  251. var now = this._getToday();
  252. date = new Date(date);
  253. if (dateUtils.sameDate(now, date)) {
  254. var startCellDate = new Date(date);
  255. var endCellDate = new Date(date);
  256. endCellDate = endCellDate.setMilliseconds(date.getMilliseconds() + this.getCellDuration());
  257. result = dateUtils.dateInRange(now, startCellDate, endCellDate)
  258. }
  259. }
  260. return result
  261. },
  262. _cleanView: function() {
  263. this.callBase();
  264. this._$sidebarTable.empty()
  265. },
  266. _visibilityChanged: function(visible) {
  267. this.callBase(visible)
  268. },
  269. _setTableSizes: function() {
  270. var cellHeight = this.getCellHeight();
  271. var minHeight = this._getWorkSpaceMinHeight();
  272. var $groupCells = this._$sidebarTable.find("tr");
  273. var height = cellHeight * $groupCells.length;
  274. if (height < minHeight) {
  275. height = minHeight
  276. }
  277. this._$sidebarTable.height(height);
  278. this._$dateTable.height(height);
  279. this.callBase()
  280. },
  281. _getWorkSpaceMinHeight: function() {
  282. var minHeight = this._getWorkSpaceHeight();
  283. var workspaceContainerHeight = this.$element().outerHeight(true) - this.getHeaderPanelHeight() - 2 * DATE_TABLE_CELL_BORDER - DATE_TABLE_HEADER_MARGIN;
  284. if (minHeight < workspaceContainerHeight) {
  285. minHeight = workspaceContainerHeight
  286. }
  287. return minHeight
  288. },
  289. _makeGroupRows: function(groups, groupByDate) {
  290. var tableCreatorStrategy = "vertical" === this.option("groupOrientation") ? tableCreator.VERTICAL : tableCreator.HORIZONTAL;
  291. return tableCreator.makeGroupedTable(tableCreatorStrategy, groups, {
  292. groupRowClass: this._getGroupRowClass(),
  293. groupHeaderRowClass: this._getGroupRowClass(),
  294. groupHeaderClass: this._getGroupHeaderClass.bind(this),
  295. groupHeaderContentClass: this._getGroupHeaderContentClass()
  296. }, this._getCellCount() || 1, this.option("resourceCellTemplate"), this._getTotalRowCount(this._getGroupCount()), groupByDate)
  297. },
  298. _ensureGroupHeaderCellsHeight: function(cellHeight) {
  299. var minCellHeight = this._calculateMinCellHeight();
  300. if (cellHeight < minCellHeight) {
  301. return minCellHeight
  302. }
  303. return cellHeight
  304. },
  305. _calculateMinCellHeight: function() {
  306. var dateTable = this._getDateTable();
  307. var dateTableRowSelector = "." + this._getDateTableRowClass();
  308. return dateTable.get(0).getBoundingClientRect().height / dateTable.find(dateTableRowSelector).length - 2 * DATE_TABLE_CELL_BORDER
  309. },
  310. _getCellCoordinatesByIndex: function(index) {
  311. return {
  312. cellIndex: index % this._getCellCount(),
  313. rowIndex: 0
  314. }
  315. },
  316. _getCellByCoordinates: function(cellCoordinates, groupIndex) {
  317. var indexes = this._groupedStrategy.prepareCellIndexes(cellCoordinates, groupIndex);
  318. return this._$dateTable.find("tr").eq(indexes.rowIndex).find("td").eq(indexes.cellIndex)
  319. },
  320. _getWorkSpaceWidth: function() {
  321. return this._$dateTable.outerWidth(true)
  322. },
  323. _getGroupIndexByCell: function($cell) {
  324. return $cell.parent().index()
  325. },
  326. _getIndicationFirstViewDate: function() {
  327. return new Date(this._firstViewDate)
  328. },
  329. _getIntervalBetween: function(currentDate, allDay) {
  330. var startDayHour = this.option("startDayHour");
  331. var endDayHour = this.option("endDayHour");
  332. var firstViewDate = this.getStartViewDate();
  333. var firstViewDateTime = firstViewDate.getTime();
  334. var hiddenInterval = (24 - endDayHour + startDayHour) * toMs("hour");
  335. var timeZoneOffset = dateUtils.getTimezonesDifference(firstViewDate, currentDate);
  336. var apptStart = currentDate.getTime();
  337. var fullInterval = apptStart - firstViewDateTime - timeZoneOffset;
  338. var fullDays = Math.floor(fullInterval / toMs("day"));
  339. var tailDuration = fullInterval - fullDays * toMs("day");
  340. var tailDelta = 0;
  341. var cellCount = this._getCellCountInDay() * (fullDays - this._getWeekendsCount(fullDays));
  342. var gapBeforeAppt = apptStart - dateUtils.trimTime(new Date(currentDate)).getTime();
  343. var result = cellCount * this.option("hoursInterval") * toMs("hour");
  344. if (!allDay) {
  345. if (currentDate.getHours() < startDayHour) {
  346. tailDelta = tailDuration - hiddenInterval + gapBeforeAppt
  347. } else {
  348. if (currentDate.getHours() >= startDayHour && currentDate.getHours() < endDayHour) {
  349. tailDelta = tailDuration
  350. } else {
  351. if (currentDate.getHours() >= startDayHour && currentDate.getHours() >= endDayHour) {
  352. tailDelta = tailDuration - (gapBeforeAppt - endDayHour * toMs("hour"))
  353. } else {
  354. if (!fullDays) {
  355. result = fullInterval
  356. }
  357. }
  358. }
  359. }
  360. result += tailDelta
  361. }
  362. return result
  363. },
  364. _getWeekendsCount: function() {
  365. return 0
  366. },
  367. getAllDayContainer: function() {
  368. return null
  369. },
  370. getTimePanelWidth: function() {
  371. return 0
  372. },
  373. getPositionShift: function(timeShift) {
  374. var positionShift = this.callBase(timeShift);
  375. var left = this.getCellWidth() * timeShift;
  376. if (this.option("rtlEnabled")) {
  377. left *= -1
  378. }
  379. left += positionShift.left;
  380. return {
  381. top: 0,
  382. left: left,
  383. cellPosition: left
  384. }
  385. },
  386. getVisibleBounds: function() {
  387. var isRtl = this.option("rtlEnabled");
  388. var result = {};
  389. var $scrollable = this.getScrollable().$element();
  390. var cellWidth = this.getCellWidth();
  391. var scrollableOffset = isRtl ? this.getScrollableOuterWidth() - this.getScrollableScrollLeft() : this.getScrollableScrollLeft();
  392. var scrolledCellCount = scrollableOffset / cellWidth;
  393. var visibleCellCount = $scrollable.width() / cellWidth;
  394. var totalCellCount = isRtl ? scrolledCellCount - visibleCellCount : scrolledCellCount + visibleCellCount;
  395. var leftDate = this._getDateByIndex(scrolledCellCount);
  396. var rightDate = this._getDateByIndex(totalCellCount);
  397. if (isRtl) {
  398. leftDate = this._getDateByIndex(totalCellCount);
  399. rightDate = this._getDateByIndex(scrolledCellCount)
  400. }
  401. result.left = {
  402. hours: leftDate.getHours(),
  403. minutes: leftDate.getMinutes() >= 30 ? 30 : 0,
  404. date: dateUtils.trimTime(leftDate)
  405. };
  406. result.right = {
  407. hours: rightDate.getHours(),
  408. minutes: rightDate.getMinutes() >= 30 ? 30 : 0,
  409. date: dateUtils.trimTime(rightDate)
  410. };
  411. return result
  412. },
  413. needUpdateScrollPosition: function(hours, minutes, bounds, date) {
  414. var isUpdateNeeded = false;
  415. isUpdateNeeded = this._dateWithinBounds(bounds, date);
  416. if (hours < bounds.left.hours || hours > bounds.right.hours) {
  417. isUpdateNeeded = true
  418. }
  419. if (hours === bounds.left.hours && minutes < bounds.left.minutes) {
  420. isUpdateNeeded = true
  421. }
  422. if (hours === bounds.right.hours && minutes > bounds.right.minutes) {
  423. isUpdateNeeded = true
  424. }
  425. return isUpdateNeeded
  426. },
  427. getIntervalDuration: function(allDay) {
  428. return this.getCellDuration()
  429. },
  430. _dateWithinBounds: function(bounds, date) {
  431. var trimmedDate = dateUtils.trimTime(new Date(date));
  432. var isUpdateNeeded = false;
  433. if (trimmedDate < bounds.left.date || trimmedDate > bounds.right.date) {
  434. isUpdateNeeded = true
  435. }
  436. return isUpdateNeeded
  437. },
  438. _supportCompactDropDownAppointments: function() {
  439. return false
  440. },
  441. getCellMinWidth: function() {
  442. return 0
  443. },
  444. getWorkSpaceLeftOffset: function() {
  445. return 0
  446. },
  447. scrollToTime: function(hours, minutes, date) {
  448. var coordinates = this._getScrollCoordinates(hours, minutes, date);
  449. var scrollable = this.getScrollable();
  450. var offset = this.option("rtlEnabled") ? this.getScrollableContainer().get(0).getBoundingClientRect().width : 0;
  451. if (this.option("templatesRenderAsynchronously")) {
  452. setTimeout(function() {
  453. scrollable.scrollBy({
  454. left: coordinates.left - scrollable.scrollLeft() - offset,
  455. top: 0
  456. })
  457. })
  458. } else {
  459. scrollable.scrollBy({
  460. left: coordinates.left - scrollable.scrollLeft() - offset,
  461. top: 0
  462. })
  463. }
  464. }
  465. });
  466. registerComponent("dxSchedulerTimeline", SchedulerTimeline);
  467. module.exports = SchedulerTimeline;