ui.scheduler.navigator.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. /**
  2. * DevExtreme (ui/scheduler/ui.scheduler.navigator.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 isNumeric = require("../../core/utils/type").isNumeric;
  13. var errors = require("../widget/ui.errors");
  14. var dateUtils = require("../../core/utils/date");
  15. var typeUtils = require("../../core/utils/type");
  16. var extend = require("../../core/utils/extend").extend;
  17. var registerComponent = require("../../core/component_registrator");
  18. var devices = require("../../core/devices");
  19. var Widget = require("../widget/ui.widget");
  20. var Button = require("../button");
  21. var Calendar = require("../calendar");
  22. var Popover = require("../popover");
  23. var Popup = require("../popup");
  24. var publisherMixin = require("./ui.scheduler.publisher_mixin");
  25. var dateLocalization = require("../../localization/date");
  26. var isDefined = require("../../core/utils/type").isDefined;
  27. var ELEMENT_CLASS = "dx-scheduler-navigator";
  28. var CALENDAR_CLASS = "dx-scheduler-navigator-calendar";
  29. var NEXT_BUTTON_CLASS = "dx-scheduler-navigator-next";
  30. var CAPTION_BUTTON_CLASS = "dx-scheduler-navigator-caption";
  31. var PREVIOUS_BUTTON_CLASS = "dx-scheduler-navigator-previous";
  32. var CALENDAR_POPOVER_CLASS = "dx-scheduler-navigator-calendar-popover";
  33. var MONDAY_INDEX = 1;
  34. var getDefaultFirstDayOfWeekIndex = function(shift) {
  35. return shift ? MONDAY_INDEX : dateLocalization.firstDayOfWeekIndex()
  36. };
  37. var getDateMonthFormat = function(short) {
  38. return function(date) {
  39. var monthName = dateLocalization.getMonthNames(short ? "abbreviated" : "wide")[date.getMonth()];
  40. return [dateLocalization.format(date, "day"), monthName].join(" ")
  41. }
  42. };
  43. var getMonthYearFormat = function(date) {
  44. return dateLocalization.getMonthNames("abbreviated")[date.getMonth()] + " " + dateLocalization.format(date, "year")
  45. };
  46. var getCaptionFormat = function getCaptionFormat(short, intervalCount, duration) {
  47. var dateMonthFormat = getDateMonthFormat(short);
  48. return function(date) {
  49. if (intervalCount > 1) {
  50. var lastIntervalDate = new Date(date);
  51. var defaultViewDuration = duration;
  52. lastIntervalDate.setDate(date.getDate() + defaultViewDuration - 1);
  53. var isDifferentMonthDates = date.getMonth() !== lastIntervalDate.getMonth();
  54. var useShortFormat = isDifferentMonthDates || short;
  55. var firstWeekDateText = dateLocalization.format(date, isDifferentMonthDates ? getDateMonthFormat(useShortFormat) : "d");
  56. var lastWeekDateText = dateLocalization.format(lastIntervalDate, getCaptionFormat(useShortFormat));
  57. return firstWeekDateText + "-" + lastWeekDateText
  58. }
  59. return [dateMonthFormat(date), dateLocalization.format(date, "year")].join(" ")
  60. }
  61. };
  62. var getWeekCaption = function(date, shift, rejectWeekend) {
  63. var firstDayOfWeek = this.option("firstDayOfWeek");
  64. var firstDayOfWeekIndex = isDefined(firstDayOfWeek) ? firstDayOfWeek : getDefaultFirstDayOfWeekIndex(shift);
  65. if (0 === firstDayOfWeekIndex && rejectWeekend) {
  66. firstDayOfWeekIndex = MONDAY_INDEX
  67. }
  68. var firstWeekDate = dateUtils.getFirstWeekDate(date, firstDayOfWeekIndex);
  69. var weekendDuration = 2;
  70. if (rejectWeekend) {
  71. firstWeekDate = dateUtils.normalizeDateByWeek(firstWeekDate, date)
  72. }
  73. if (firstDayOfWeek >= 6 && rejectWeekend) {
  74. firstWeekDate.setDate(firstWeekDate.getDate() + (7 - firstDayOfWeek + 1))
  75. }
  76. var lastWeekDate = new Date(firstWeekDate);
  77. var intervalCount = this.option("intervalCount");
  78. shift = shift || 6;
  79. lastWeekDate = new Date(lastWeekDate.setDate(lastWeekDate.getDate() + (intervalCount > 1 ? 7 * (intervalCount - 1) + shift : shift)));
  80. if (lastWeekDate.getDay() % 6 === 0 && rejectWeekend) {
  81. lastWeekDate.setDate(lastWeekDate.getDate() + weekendDuration)
  82. }
  83. return {
  84. text: formatCaptionByMonths.call(this, lastWeekDate, firstWeekDate),
  85. startDate: firstWeekDate,
  86. endDate: lastWeekDate
  87. }
  88. };
  89. var formatCaptionByMonths = function(lastDate, firstDate) {
  90. var isDifferentMonthDates = firstDate.getMonth() !== lastDate.getMonth();
  91. var isDifferentYears = firstDate.getFullYear() !== lastDate.getFullYear();
  92. var useShortFormat = isDifferentMonthDates || this.option("_useShortDateFormat");
  93. var lastDateText;
  94. var firstDateText;
  95. if (isDifferentYears) {
  96. firstDateText = dateLocalization.format(firstDate, getCaptionFormat(true));
  97. lastDateText = dateLocalization.format(lastDate, getCaptionFormat(true))
  98. } else {
  99. firstDateText = dateLocalization.format(firstDate, isDifferentMonthDates ? getDateMonthFormat(useShortFormat) : "d");
  100. lastDateText = dateLocalization.format(lastDate, getCaptionFormat(useShortFormat))
  101. }
  102. return firstDateText + "-" + lastDateText
  103. };
  104. var getMonthCaption = function(date) {
  105. var firstDate = new Date(dateUtils.getFirstMonthDate(date));
  106. var lastDate = new Date(dateUtils.getLastMonthDate(firstDate));
  107. var text;
  108. if (this.option("intervalCount") > 1) {
  109. lastDate = new Date(firstDate);
  110. lastDate.setMonth(firstDate.getMonth() + this.option("intervalCount") - 1);
  111. lastDate = new Date(dateUtils.getLastMonthDate(lastDate));
  112. var isSameYear = firstDate.getYear() === lastDate.getYear();
  113. var lastDateText = getMonthYearFormat(lastDate);
  114. var firstDateText = isSameYear ? dateLocalization.getMonthNames("abbreviated")[firstDate.getMonth()] : getMonthYearFormat(firstDate);
  115. text = firstDateText + "-" + lastDateText
  116. } else {
  117. text = dateLocalization.format(date, "monthandyear")
  118. }
  119. return {
  120. text: text,
  121. startDate: firstDate,
  122. endDate: lastDate
  123. }
  124. };
  125. var dateGetter = function(date, offset) {
  126. return new Date(date[this.setter](date[this.getter]() + offset))
  127. };
  128. var getConfig = function(step) {
  129. var agendaDuration;
  130. switch (step) {
  131. case "day":
  132. return {
  133. duration: 1 * this.option("intervalCount"), setter: "setDate", getter: "getDate", getDate: dateGetter, getCaption: function(date) {
  134. var format = getCaptionFormat(false, this.option("intervalCount"), this._getConfig().duration);
  135. return {
  136. text: dateLocalization.format(date, format),
  137. startDate: date,
  138. endDate: date
  139. }
  140. }
  141. };
  142. case "week":
  143. return {
  144. duration: 7 * this.option("intervalCount"), setter: "setDate", getter: "getDate", getDate: dateGetter, getCaption: getWeekCaption
  145. };
  146. case "workWeek":
  147. return {
  148. duration: 7 * this.option("intervalCount"), setter: "setDate", getter: "getDate", getDate: dateGetter, getCaption: function(date) {
  149. return getWeekCaption.call(this, date, 4, true)
  150. }
  151. };
  152. case "month":
  153. return {
  154. duration: 1 * this.option("intervalCount"), setter: "setMonth", getter: "getMonth", getDate: function(date, offset) {
  155. var currentDate = date.getDate();
  156. date.setDate(1);
  157. date = dateGetter.call(this, date, offset);
  158. var lastDate = dateUtils.getLastMonthDay(date);
  159. date.setDate(currentDate < lastDate ? currentDate : lastDate);
  160. return date
  161. }, getCaption: getMonthCaption
  162. };
  163. case "agenda":
  164. agendaDuration = this.invoke("getAgendaDuration");
  165. agendaDuration = isNumeric(agendaDuration) && agendaDuration > 0 ? agendaDuration : 7;
  166. return {
  167. duration: agendaDuration, setter: "setDate", getter: "getDate", getDate: dateGetter, getCaption: function(date) {
  168. var format = getCaptionFormat(this.option("_useShortDateFormat"));
  169. var firstDate = new Date(date);
  170. var lastDate = new Date(date);
  171. var text;
  172. if (agendaDuration > 1) {
  173. lastDate.setDate(lastDate.getDate() + agendaDuration - 1);
  174. text = formatCaptionByMonths.call(this, lastDate, date)
  175. } else {
  176. text = dateLocalization.format(date, format)
  177. }
  178. return {
  179. text: text,
  180. startDate: firstDate,
  181. endDate: lastDate
  182. }
  183. }
  184. }
  185. }
  186. };
  187. var SchedulerNavigator = Widget.inherit({
  188. _getDefaultOptions: function() {
  189. return extend(this.callBase(), {
  190. date: new Date,
  191. displayedDate: void 0,
  192. step: "day",
  193. intervalCount: 1,
  194. min: void 0,
  195. max: void 0,
  196. firstDayOfWeek: void 0,
  197. _useShortDateFormat: false
  198. })
  199. },
  200. _defaultOptionsRules: function() {
  201. return this.callBase().concat([{
  202. device: function() {
  203. return !devices.real().generic || devices.isSimulator()
  204. },
  205. options: {
  206. _useShortDateFormat: true
  207. }
  208. }])
  209. },
  210. _optionChanged: function(args) {
  211. switch (args.name) {
  212. case "step":
  213. case "date":
  214. case "intervalCount":
  215. case "displayedDate":
  216. this._updateButtonsState();
  217. this._renderCaption();
  218. this._setCalendarOption("value", this.option("date"));
  219. break;
  220. case "min":
  221. case "max":
  222. this._updateButtonsState();
  223. this._setCalendarOption(args.name, args.value);
  224. break;
  225. case "firstDayOfWeek":
  226. this._setCalendarOption(args.name, args.value);
  227. break;
  228. case "customizeDateNavigatorText":
  229. this._renderCaption();
  230. break;
  231. case "tabIndex":
  232. case "focusStateEnabled":
  233. this._next.option(args.name, args.value);
  234. this._caption.option(args.name, args.value);
  235. this._prev.option(args.name, args.value);
  236. this._setCalendarOption(args.name, args.value);
  237. this.callBase(args);
  238. break;
  239. case "_useShortDateFormat":
  240. break;
  241. default:
  242. this.callBase(args)
  243. }
  244. },
  245. _init: function() {
  246. this.callBase();
  247. this.$element().addClass(ELEMENT_CLASS);
  248. this._initButtons()
  249. },
  250. _initButtons: function() {
  251. var $next = $("<div>").addClass(NEXT_BUTTON_CLASS);
  252. this._next = this._createComponent($next, Button, {
  253. icon: "chevronnext",
  254. onClick: this._updateCurrentDate.bind(this, 1),
  255. focusStateEnabled: this.option("focusStateEnabled"),
  256. tabIndex: this.option("tabIndex"),
  257. integrationOptions: {}
  258. });
  259. var $caption = $("<div>").addClass(CAPTION_BUTTON_CLASS);
  260. this._caption = this._createComponent($caption, Button, {
  261. focusStateEnabled: this.option("focusStateEnabled"),
  262. tabIndex: this.option("tabIndex"),
  263. integrationOptions: {}
  264. });
  265. var $prev = $("<div>").addClass(PREVIOUS_BUTTON_CLASS);
  266. this._prev = this._createComponent($prev, Button, {
  267. icon: "chevronprev",
  268. onClick: this._updateCurrentDate.bind(this, -1),
  269. focusStateEnabled: this.option("focusStateEnabled"),
  270. tabIndex: this.option("tabIndex"),
  271. integrationOptions: {}
  272. });
  273. this.setAria("label", "Next period", $next);
  274. this.setAria("label", "Previous period", $prev);
  275. this._updateButtonsState();
  276. this.$element().append($prev, $caption, $next)
  277. },
  278. _updateButtonsState: function() {
  279. var min = this.option("min");
  280. var max = this.option("max");
  281. var caption = this._getConfig().getCaption.call(this, this.option("displayedDate") || this.option("date"));
  282. min = min ? dateUtils.trimTime(min) : min;
  283. max = max ? dateUtils.trimTime(max) : max;
  284. max && max.setHours(23, 59, 59);
  285. this._prev.option("disabled", min && !isNaN(min.getTime()) && this._getNextDate(-1, caption.endDate) < min);
  286. this._next.option("disabled", max && !isNaN(max.getTime()) && this._getNextDate(1, caption.startDate) > max)
  287. },
  288. _updateCurrentDate: function(direction) {
  289. var date = this._getNextDate(direction);
  290. dateUtils.normalizeDate(date, this.option("min"), this.option("max"));
  291. this.notifyObserver("currentDateUpdated", date)
  292. },
  293. _getNextDate: function(direction) {
  294. var initialDate = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : null;
  295. var stepConfig = this._getConfig();
  296. var offset = stepConfig.duration * direction;
  297. var date = stepConfig.getDate(new Date(initialDate || this.option("date")), offset);
  298. return date
  299. },
  300. _renderFocusTarget: noop,
  301. _initMarkup: function() {
  302. this.callBase();
  303. this._renderCaption()
  304. },
  305. _render: function() {
  306. this.callBase();
  307. this._renderPopover();
  308. this._renderCaptionKeys()
  309. },
  310. _renderPopover: function() {
  311. var overlayType = !devices.current().generic ? Popup : Popover;
  312. var popoverContainer = $("<div>").addClass(CALENDAR_POPOVER_CLASS);
  313. this._popover = this._createComponent(popoverContainer, overlayType, {
  314. onContentReady: this._popoverContentReadyHandler.bind(this),
  315. defaultOptionsRules: [{
  316. device: function() {
  317. return !devices.current().generic
  318. },
  319. options: {
  320. fullScreen: true,
  321. showCloseButton: false,
  322. toolbarItems: [{
  323. shortcut: "cancel"
  324. }]
  325. }
  326. }, {
  327. device: function() {
  328. return devices.current().generic
  329. },
  330. options: {
  331. target: this._caption.$element()
  332. }
  333. }]
  334. });
  335. this._popover.$element().appendTo(this.$element())
  336. },
  337. _popoverContentReadyHandler: function() {
  338. this._calendar = this._createComponent($("<div>"), Calendar, this._calendarOptions());
  339. this._calendar.$element().addClass(CALENDAR_CLASS);
  340. this._popover.$content().append(this._calendar.$element())
  341. },
  342. _calendarOptions: function() {
  343. return {
  344. min: this.option("min"),
  345. max: this.option("max"),
  346. firstDayOfWeek: this.option("firstDayOfWeek"),
  347. value: this.option("date"),
  348. focusStateEnabled: this.option("focusStateEnabled"),
  349. onValueChanged: function(e) {
  350. if (!this.option("visible")) {
  351. return
  352. }
  353. this.notifyObserver("currentDateUpdated", e.value);
  354. this._popover.hide()
  355. }.bind(this),
  356. hasFocus: function() {
  357. return true
  358. },
  359. tabIndex: null,
  360. _keyboardProcessor: this._calendarKeyboardProcessor
  361. }
  362. },
  363. _renderCaption: function() {
  364. var date = this.option("displayedDate") || this.option("date");
  365. var captionConfig = this._getConfig().getCaption.call(this, date);
  366. var customizationFunction = this.option("customizeDateNavigatorText");
  367. var caption = typeUtils.isFunction(customizationFunction) ? customizationFunction(captionConfig) : captionConfig.text;
  368. this._caption.option({
  369. text: caption,
  370. onClick: function() {
  371. this._popover.toggle()
  372. }.bind(this)
  373. })
  374. },
  375. _renderCaptionKeys: function() {
  376. if (!this.option("focusStateEnabled") || this.option("disabled")) {
  377. return
  378. }
  379. this._calendarKeyboardProcessor = this._caption._keyboardProcessor.attachChildProcessor();
  380. this._setCalendarOption("_keyboardProcessor", this._calendarKeyboardProcessor);
  381. var that = this;
  382. var executeHandler = function() {
  383. if (that._popover.$content().is(":hidden")) {
  384. that._popover.show()
  385. } else {
  386. return true
  387. }
  388. };
  389. var tabHandler = function() {
  390. that._popover.hide()
  391. };
  392. this._caption.registerKeyHandler("enter", executeHandler);
  393. this._caption.registerKeyHandler("space", executeHandler);
  394. this._caption.registerKeyHandler("tab", tabHandler)
  395. },
  396. _setCalendarOption: function(name, value) {
  397. if (this._calendar) {
  398. this._calendar.option(name, value)
  399. }
  400. },
  401. _getConfig: function() {
  402. var step = this.option("step");
  403. var config = getConfig.call(this, step);
  404. if (!config) {
  405. throw errors.Error("E1033", step)
  406. }
  407. return config
  408. }
  409. }).include(publisherMixin);
  410. registerComponent("dxSchedulerNavigator", SchedulerNavigator);
  411. module.exports = SchedulerNavigator;