ui.calendar.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923
  1. /**
  2. * DevExtreme (ui/calendar/ui.calendar.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 Guid = require("../../core/guid");
  12. var registerComponent = require("../../core/component_registrator");
  13. var noop = require("../../core/utils/common").noop;
  14. var typeUtils = require("../../core/utils/type");
  15. var inRange = require("../../core/utils/math").inRange;
  16. var extend = require("../../core/utils/extend").extend;
  17. var Button = require("../button");
  18. var Editor = require("../editor/editor");
  19. var Swipeable = require("../../events/gesture/swipeable");
  20. var Navigator = require("./ui.calendar.navigator");
  21. var Views = require("./ui.calendar.views");
  22. var translator = require("../../animation/translator");
  23. var browser = require("../../core/utils/browser");
  24. var dateUtils = require("../../core/utils/date");
  25. var dateSerialization = require("../../core/utils/date_serialization");
  26. var devices = require("../../core/devices");
  27. var fx = require("../../animation/fx");
  28. var windowUtils = require("../../core/utils/window");
  29. var messageLocalization = require("../../localization/message");
  30. var FunctionTemplate = require("../widget/function_template");
  31. var CALENDAR_CLASS = "dx-calendar";
  32. var CALENDAR_BODY_CLASS = "dx-calendar-body";
  33. var CALENDAR_CELL_CLASS = "dx-calendar-cell";
  34. var CALENDAR_FOOTER_CLASS = "dx-calendar-footer";
  35. var CALENDAR_TODAY_BUTTON_CLASS = "dx-calendar-today-button";
  36. var CALENDAR_HAS_FOOTER_CLASS = "dx-calendar-with-footer";
  37. var CALENDAR_VIEWS_WRAPPER_CLASS = "dx-calendar-views-wrapper";
  38. var CALENDAR_VIEW_CLASS = "dx-calendar-view";
  39. var FOCUSED_STATE_CLASS = "dx-state-focused";
  40. var ANIMATION_DURATION_SHOW_VIEW = 250;
  41. var POP_ANIMATION_FROM = .6;
  42. var POP_ANIMATION_TO = 1;
  43. var CALENDAR_INPUT_STANDARD_PATTERN = "yyyy-MM-dd";
  44. var CALENDAR_DATE_VALUE_KEY = "dxDateValueKey";
  45. var LEVEL_COMPARE_MAP = {
  46. month: 3,
  47. year: 2,
  48. decade: 1,
  49. century: 0
  50. };
  51. var ZOOM_LEVEL = {
  52. MONTH: "month",
  53. YEAR: "year",
  54. DECADE: "decade",
  55. CENTURY: "century"
  56. };
  57. var Calendar = Editor.inherit({
  58. _activeStateUnit: "." + CALENDAR_CELL_CLASS,
  59. _getDefaultOptions: function() {
  60. return extend(this.callBase(), {
  61. hoverStateEnabled: true,
  62. activeStateEnabled: true,
  63. currentDate: new Date,
  64. value: null,
  65. dateSerializationFormat: void 0,
  66. min: new Date(1e3, 0),
  67. max: new Date(3e3, 0),
  68. firstDayOfWeek: void 0,
  69. zoomLevel: ZOOM_LEVEL.MONTH,
  70. maxZoomLevel: ZOOM_LEVEL.MONTH,
  71. minZoomLevel: ZOOM_LEVEL.CENTURY,
  72. showTodayButton: false,
  73. cellTemplate: "cell",
  74. disabledDates: null,
  75. onCellClick: null,
  76. onContouredChanged: null,
  77. hasFocus: function(element) {
  78. return element.hasClass(FOCUSED_STATE_CLASS)
  79. }
  80. })
  81. },
  82. _defaultOptionsRules: function() {
  83. return this.callBase().concat([{
  84. device: function() {
  85. return "desktop" === devices.real().deviceType && !devices.isSimulator()
  86. },
  87. options: {
  88. focusStateEnabled: true
  89. }
  90. }])
  91. },
  92. _supportedKeys: function() {
  93. return extend(this.callBase(), {
  94. rightArrow: function(e) {
  95. e.preventDefault();
  96. if (e.ctrlKey) {
  97. this._waitRenderView(1)
  98. } else {
  99. this._moveCurrentDate(1 * this._getRtlCorrection())
  100. }
  101. },
  102. leftArrow: function(e) {
  103. e.preventDefault();
  104. if (e.ctrlKey) {
  105. this._waitRenderView(-1)
  106. } else {
  107. this._moveCurrentDate(-1 * this._getRtlCorrection())
  108. }
  109. },
  110. upArrow: function(e) {
  111. e.preventDefault();
  112. if (e.ctrlKey) {
  113. this._navigateUp()
  114. } else {
  115. if (fx.isAnimating(this._view.$element())) {
  116. return
  117. }
  118. this._moveCurrentDate(-1 * this._view.option("colCount"))
  119. }
  120. },
  121. downArrow: function(e) {
  122. e.preventDefault();
  123. if (e.ctrlKey) {
  124. this._navigateDown()
  125. } else {
  126. if (fx.isAnimating(this._view.$element())) {
  127. return
  128. }
  129. this._moveCurrentDate(1 * this._view.option("colCount"))
  130. }
  131. },
  132. home: function(e) {
  133. e.preventDefault();
  134. var zoomLevel = this.option("zoomLevel");
  135. var currentDate = this.option("currentDate");
  136. var min = this._dateOption("min");
  137. var date = dateUtils.sameView(zoomLevel, currentDate, min) ? min : dateUtils.getViewFirstCellDate(zoomLevel, currentDate);
  138. this._moveToClosestAvailableDate(date, 1)
  139. },
  140. end: function(e) {
  141. e.preventDefault();
  142. var zoomLevel = this.option("zoomLevel");
  143. var currentDate = this.option("currentDate");
  144. var max = this._dateOption("max");
  145. var date = dateUtils.sameView(zoomLevel, currentDate, max) ? max : dateUtils.getViewLastCellDate(zoomLevel, currentDate);
  146. this._moveToClosestAvailableDate(date, -1)
  147. },
  148. pageUp: function(e) {
  149. e.preventDefault();
  150. this._waitRenderView(-1)
  151. },
  152. pageDown: function(e) {
  153. e.preventDefault();
  154. this._waitRenderView(1)
  155. },
  156. tab: noop,
  157. enter: function(e) {
  158. if (!this._isMaxZoomLevel()) {
  159. this._navigateDown()
  160. } else {
  161. var value = this._updateTimeComponent(this.option("currentDate"));
  162. this._dateValue(value, e)
  163. }
  164. }
  165. })
  166. },
  167. _getSerializationFormat: function(optionName) {
  168. var value = this.option(optionName || "value");
  169. if (this.option("dateSerializationFormat")) {
  170. return this.option("dateSerializationFormat")
  171. }
  172. if (typeUtils.isNumeric(value)) {
  173. return "number"
  174. }
  175. if (!typeUtils.isString(value)) {
  176. return
  177. }
  178. return dateSerialization.getDateSerializationFormat(value)
  179. },
  180. _convertToDate: function(value, optionName) {
  181. return dateSerialization.deserializeDate(value)
  182. },
  183. _dateValue: function(value, dxEvent) {
  184. if (dxEvent) {
  185. this._saveValueChangeEvent(dxEvent)
  186. }
  187. this._dateOption("value", value)
  188. },
  189. _dateOption: function(optionName, optionValue) {
  190. if (1 === arguments.length) {
  191. return this._convertToDate(this.option(optionName), optionName)
  192. }
  193. var serializationFormat = this._getSerializationFormat(optionName);
  194. this.option(optionName, dateSerialization.serializeDate(optionValue, serializationFormat))
  195. },
  196. _moveCurrentDate: function(offset, baseDate) {
  197. var currentDate = baseDate || new Date(this.option("currentDate"));
  198. var maxDate = this._getMaxDate();
  199. var minDate = this._getMinDate();
  200. var zoomLevel = this.option("zoomLevel");
  201. var currentDateInRange = inRange(currentDate, minDate, maxDate);
  202. var dateForward = new Date(currentDate);
  203. var dateBackward = new Date(currentDate);
  204. var dateForwardInRange = currentDateInRange;
  205. var dateBackwardInRange = currentDateInRange;
  206. while (!offset && (dateForwardInRange || dateBackwardInRange) || offset && dateForwardInRange) {
  207. var step = offset || 1;
  208. switch (zoomLevel) {
  209. case ZOOM_LEVEL.MONTH:
  210. dateForward.setDate(dateForward.getDate() + step);
  211. dateBackward.setDate(dateBackward.getDate() - step);
  212. break;
  213. case ZOOM_LEVEL.YEAR:
  214. dateForward.setMonth(dateForward.getMonth() + step);
  215. dateBackward.setMonth(dateBackward.getMonth() - step);
  216. break;
  217. case ZOOM_LEVEL.DECADE:
  218. dateForward.setFullYear(dateForward.getFullYear() + step);
  219. dateBackward.setFullYear(dateBackward.getFullYear() - step);
  220. break;
  221. case ZOOM_LEVEL.CENTURY:
  222. dateForward.setFullYear(dateForward.getFullYear() + 10 * step);
  223. dateBackward.setFullYear(dateBackward.getFullYear() - 10 * step)
  224. }
  225. if (!this._view.isDateDisabled(dateForward)) {
  226. currentDate = dateForward;
  227. break
  228. }
  229. if (!offset && !this._view.isDateDisabled(dateBackward)) {
  230. currentDate = dateBackward;
  231. break
  232. }
  233. dateBackwardInRange = inRange(dateBackward, minDate, maxDate);
  234. dateForwardInRange = inRange(dateForward, minDate, maxDate)
  235. }
  236. this.option("currentDate", currentDate)
  237. },
  238. _moveToClosestAvailableDate: function(baseDate, offset) {
  239. if (this._view.isDateDisabled(baseDate)) {
  240. this._moveCurrentDate(offset, baseDate)
  241. } else {
  242. this.option("currentDate", baseDate)
  243. }
  244. },
  245. _init: function() {
  246. this.callBase();
  247. this._correctZoomLevel();
  248. this._initCurrentDate();
  249. this._initActions()
  250. },
  251. _correctZoomLevel: function() {
  252. var minZoomLevel = this.option("minZoomLevel");
  253. var maxZoomLevel = this.option("maxZoomLevel");
  254. var zoomLevel = this.option("zoomLevel");
  255. if (LEVEL_COMPARE_MAP[maxZoomLevel] < LEVEL_COMPARE_MAP[minZoomLevel]) {
  256. return
  257. }
  258. if (LEVEL_COMPARE_MAP[zoomLevel] > LEVEL_COMPARE_MAP[maxZoomLevel]) {
  259. this.option("zoomLevel", maxZoomLevel)
  260. } else {
  261. if (LEVEL_COMPARE_MAP[zoomLevel] < LEVEL_COMPARE_MAP[minZoomLevel]) {
  262. this.option("zoomLevel", minZoomLevel)
  263. }
  264. }
  265. },
  266. _initCurrentDate: function() {
  267. var currentDate = this._getNormalizedDate(this._dateOption("value")) || this._getNormalizedDate(this.option("currentDate"));
  268. this.option("currentDate", currentDate)
  269. },
  270. _getNormalizedDate: function(date) {
  271. date = dateUtils.normalizeDate(date, this._getMinDate(), this._getMaxDate());
  272. return typeUtils.isDefined(date) ? new Date(date) : date
  273. },
  274. _initActions: function() {
  275. this._cellClickAction = this._createActionByOption("onCellClick");
  276. this._onContouredChanged = this._createActionByOption("onContouredChanged")
  277. },
  278. _initTemplates: function() {
  279. this.callBase();
  280. this._defaultTemplates.cell = new FunctionTemplate(function(options) {
  281. var data = options.model;
  282. $(options.container).append($("<span>").text(data && data.text || String(data)))
  283. }, this)
  284. },
  285. _updateCurrentDate: function(date) {
  286. if (fx.isAnimating(this._$viewsWrapper)) {
  287. fx.stop(this._$viewsWrapper, true)
  288. }
  289. var min = this._getMinDate();
  290. var max = this._getMaxDate();
  291. if (min > max) {
  292. this.option("currentDate", new Date);
  293. return
  294. }
  295. var normalizedDate = this._getNormalizedDate(date);
  296. if (date.getTime() !== normalizedDate.getTime()) {
  297. this.option("currentDate", new Date(normalizedDate));
  298. return
  299. }
  300. var offset = this._getViewsOffset(this._view.option("date"), normalizedDate);
  301. if (0 !== offset && !this._isMaxZoomLevel() && this._isOtherViewCellClicked) {
  302. offset = 0
  303. }
  304. if (this._view && 0 !== offset && !this._suppressNavigation) {
  305. this._navigate(offset, normalizedDate)
  306. } else {
  307. this._renderNavigator();
  308. this._setViewContoured(normalizedDate);
  309. this._updateAriaId(normalizedDate)
  310. }
  311. },
  312. _setViewContoured: function(date) {
  313. if (this.option("hasFocus")(this._focusTarget())) {
  314. this._view.option("contouredDate", date)
  315. }
  316. },
  317. _getMinDate: function() {
  318. if (this.min) {
  319. return this.min
  320. }
  321. this.min = this._dateOption("min") || new Date(1e3, 0);
  322. return this.min
  323. },
  324. _getMaxDate: function() {
  325. if (this.max) {
  326. return this.max
  327. }
  328. this.max = this._dateOption("max") || new Date(3e3, 0);
  329. return this.max
  330. },
  331. _getViewsOffset: function(startDate, endDate) {
  332. var zoomLevel = this.option("zoomLevel");
  333. if (zoomLevel === ZOOM_LEVEL.MONTH) {
  334. return this._getMonthsOffset(startDate, endDate)
  335. }
  336. var zoomCorrection;
  337. switch (zoomLevel) {
  338. case ZOOM_LEVEL.CENTURY:
  339. zoomCorrection = 100;
  340. break;
  341. case ZOOM_LEVEL.DECADE:
  342. zoomCorrection = 10;
  343. break;
  344. default:
  345. zoomCorrection = 1
  346. }
  347. return parseInt(endDate.getFullYear() / zoomCorrection) - parseInt(startDate.getFullYear() / zoomCorrection)
  348. },
  349. _getMonthsOffset: function(startDate, endDate) {
  350. var yearOffset = endDate.getFullYear() - startDate.getFullYear();
  351. var monthOffset = endDate.getMonth() - startDate.getMonth();
  352. return 12 * yearOffset + monthOffset
  353. },
  354. _waitRenderView: function(offset) {
  355. if (this._alreadyViewRender) {
  356. return
  357. }
  358. this._alreadyViewRender = true;
  359. var date = this._getDateByOffset(offset * this._getRtlCorrection());
  360. this._moveToClosestAvailableDate(date, offset);
  361. setTimeout(function() {
  362. this._alreadyViewRender = false
  363. }.bind(this))
  364. },
  365. _getRtlCorrection: function() {
  366. return this.option("rtlEnabled") ? -1 : 1
  367. },
  368. _getDateByOffset: function(offset, date) {
  369. date = new Date(date || this.option("currentDate"));
  370. var currentDay = date.getDate();
  371. var difference = dateUtils.getDifferenceInMonth(this.option("zoomLevel")) * offset;
  372. date.setDate(1);
  373. date.setMonth(date.getMonth() + difference);
  374. var lastDay = dateUtils.getLastMonthDate(date).getDate();
  375. date.setDate(currentDay > lastDay ? lastDay : currentDay);
  376. return date
  377. },
  378. _focusTarget: function() {
  379. return this.$element()
  380. },
  381. _initMarkup: function() {
  382. this._renderSubmitElement();
  383. this.callBase();
  384. var $element = this.$element();
  385. $element.addClass(CALENDAR_CLASS);
  386. this._renderBody();
  387. $element.append(this.$body);
  388. this._renderViews();
  389. this._renderNavigator();
  390. $element.append(this._navigator.$element());
  391. this._renderSwipeable();
  392. this._renderFooter();
  393. this._updateAriaSelected();
  394. this._updateAriaId();
  395. if (this._view.isDateDisabled(this.option("currentDate"))) {
  396. this._moveCurrentDate(0)
  397. }
  398. },
  399. _render: function() {
  400. this.callBase();
  401. this._setViewContoured(this.option("currentDate"))
  402. },
  403. _renderBody: function() {
  404. if (!this._$viewsWrapper) {
  405. this.$body = $("<div>").addClass(CALENDAR_BODY_CLASS);
  406. this._$viewsWrapper = $("<div>").addClass(CALENDAR_VIEWS_WRAPPER_CLASS);
  407. this.$body.append(this._$viewsWrapper)
  408. }
  409. },
  410. _renderViews: function() {
  411. this.$element().addClass(CALENDAR_VIEW_CLASS + "-" + this.option("zoomLevel"));
  412. var currentDate = this.option("currentDate");
  413. this._view = this._renderSpecificView(currentDate);
  414. this._view.option("_keyboardProcessor", this._viewKeyboardProcessor);
  415. if (windowUtils.hasWindow()) {
  416. var beforeDate = this._getDateByOffset(-1, currentDate);
  417. this._beforeView = this._isViewAvailable(beforeDate) ? this._renderSpecificView(beforeDate) : null;
  418. var afterDate = this._getDateByOffset(1, currentDate);
  419. afterDate.setDate(1);
  420. this._afterView = this._isViewAvailable(afterDate) ? this._renderSpecificView(afterDate) : null
  421. }
  422. this._translateViews()
  423. },
  424. _renderSpecificView: function(date) {
  425. var specificView = Views[this.option("zoomLevel")];
  426. var $view = $("<div>").appendTo(this._$viewsWrapper);
  427. var config = this._viewConfig(date);
  428. return new specificView($view, config)
  429. },
  430. _viewConfig: function(date) {
  431. var disabledDates = this.option("disabledDates");
  432. disabledDates = typeUtils.isFunction(disabledDates) ? this._injectComponent(disabledDates.bind(this)) : disabledDates;
  433. return {
  434. date: date,
  435. min: this._getMinDate(),
  436. max: this._getMaxDate(),
  437. firstDayOfWeek: this.option("firstDayOfWeek"),
  438. value: this._dateOption("value"),
  439. rtlEnabled: this.option("rtlEnabled"),
  440. disabled: this.option("disabled"),
  441. tabIndex: void 0,
  442. focusStateEnabled: this.option("focusStateEnabled"),
  443. hoverStateEnabled: this.option("hoverStateEnabled"),
  444. disabledDates: disabledDates,
  445. onCellClick: this._cellClickHandler.bind(this),
  446. cellTemplate: this._getTemplateByOption("cellTemplate"),
  447. allowValueSelection: this._isMaxZoomLevel()
  448. }
  449. },
  450. _injectComponent: function(func) {
  451. var that = this;
  452. return function(params) {
  453. extend(params, {
  454. component: that
  455. });
  456. return func(params)
  457. }
  458. },
  459. _isViewAvailable: function(date) {
  460. var zoomLevel = this.option("zoomLevel");
  461. var min = dateUtils.getViewMinBoundaryDate(zoomLevel, this._getMinDate());
  462. var max = dateUtils.getViewMaxBoundaryDate(zoomLevel, this._getMaxDate());
  463. return dateUtils.dateInRange(date, min, max)
  464. },
  465. _translateViews: function() {
  466. translator.move(this._view.$element(), {
  467. left: 0,
  468. top: 0
  469. });
  470. this._beforeView && translator.move(this._beforeView.$element(), {
  471. left: this._getViewPosition(-1),
  472. top: 0
  473. });
  474. this._afterView && translator.move(this._afterView.$element(), {
  475. left: this._getViewPosition(1),
  476. top: 0
  477. })
  478. },
  479. _getViewPosition: function(coefficient) {
  480. var rtlCorrection = this.option("rtlEnabled") && !browser.msie ? -1 : 1;
  481. return 100 * coefficient * rtlCorrection + "%"
  482. },
  483. _cellClickHandler: function(e) {
  484. var zoomLevel = this.option("zoomLevel");
  485. var nextView = dateUtils.getViewDown(zoomLevel);
  486. var isMaxZoomLevel = this._isMaxZoomLevel();
  487. if (nextView && !isMaxZoomLevel) {
  488. this._navigateDown(e.event.currentTarget)
  489. } else {
  490. var newValue = this._updateTimeComponent(e.value);
  491. this._dateValue(newValue, e.event);
  492. this._cellClickAction(e)
  493. }
  494. },
  495. _updateTimeComponent: function(date) {
  496. var result = new Date(date);
  497. var currentValue = this._dateOption("value");
  498. if (currentValue) {
  499. result.setHours(currentValue.getHours());
  500. result.setMinutes(currentValue.getMinutes());
  501. result.setSeconds(currentValue.getSeconds());
  502. result.setMilliseconds(currentValue.getMilliseconds())
  503. }
  504. return result
  505. },
  506. _isMaxZoomLevel: function() {
  507. return this.option("zoomLevel") === this.option("maxZoomLevel")
  508. },
  509. _navigateDown: function(cell) {
  510. var zoomLevel = this.option("zoomLevel");
  511. if (this._isMaxZoomLevel()) {
  512. return
  513. }
  514. var nextView = dateUtils.getViewDown(zoomLevel);
  515. if (!nextView) {
  516. return
  517. }
  518. var newCurrentDate = this._view.option("contouredDate") || this._view.option("date");
  519. if (cell) {
  520. newCurrentDate = $(cell).data(CALENDAR_DATE_VALUE_KEY)
  521. }
  522. this._isOtherViewCellClicked = true;
  523. this.option("currentDate", newCurrentDate);
  524. this.option("zoomLevel", nextView);
  525. this._isOtherViewCellClicked = false;
  526. this._renderNavigator();
  527. this._animateShowView();
  528. this._setViewContoured(this._getNormalizedDate(newCurrentDate))
  529. },
  530. _renderNavigator: function() {
  531. if (!this._navigator) {
  532. this._navigator = new Navigator($("<div>"), this._navigatorConfig())
  533. }
  534. this._navigator.option("text", this._view.getNavigatorCaption());
  535. this._updateButtonsVisibility()
  536. },
  537. _navigatorConfig: function() {
  538. return {
  539. text: this._view.getNavigatorCaption(),
  540. onClick: this._navigatorClickHandler.bind(this),
  541. onCaptionClick: this._navigateUp.bind(this),
  542. rtlEnabled: this.option("rtlEnabled")
  543. }
  544. },
  545. _navigatorClickHandler: function(e) {
  546. var currentDate = this._getDateByOffset(e.direction, this.option("currentDate"));
  547. this._moveToClosestAvailableDate(currentDate, 1 * e.direction);
  548. this._updateNavigatorCaption(-e.direction * this._getRtlCorrection())
  549. },
  550. _navigateUp: function() {
  551. var zoomLevel = this.option("zoomLevel");
  552. var nextView = dateUtils.getViewUp(zoomLevel);
  553. if (!nextView || this._isMinZoomLevel(zoomLevel)) {
  554. return
  555. }
  556. var contouredDate = this._view.option("contouredDate");
  557. this.option("zoomLevel", nextView);
  558. this.option("currentDate", contouredDate || this._view.option("date"));
  559. this._renderNavigator();
  560. this._animateShowView().done(function() {
  561. this._setViewContoured(contouredDate)
  562. }.bind(this))
  563. },
  564. _isMinZoomLevel: function(zoomLevel) {
  565. var min = this._getMinDate();
  566. var max = this._getMaxDate();
  567. return dateUtils.sameView(zoomLevel, min, max) || this.option("minZoomLevel") === zoomLevel
  568. },
  569. _updateButtonsVisibility: function() {
  570. this._navigator.toggleButton("next", !typeUtils.isDefined(this._getRequiredView("next")));
  571. this._navigator.toggleButton("prev", !typeUtils.isDefined(this._getRequiredView("prev")))
  572. },
  573. _renderSwipeable: function() {
  574. if (!this._swipeable) {
  575. this._swipeable = this._createComponent(this.$element(), Swipeable, {
  576. onStart: this._swipeStartHandler.bind(this),
  577. onUpdated: this._swipeUpdateHandler.bind(this),
  578. onEnd: this._swipeEndHandler.bind(this),
  579. itemSizeFunc: this._viewWidth.bind(this)
  580. })
  581. }
  582. },
  583. _swipeStartHandler: function(e) {
  584. fx.stop(this._$viewsWrapper, true);
  585. e.event.maxLeftOffset = this._getRequiredView("next") ? 1 : 0;
  586. e.event.maxRightOffset = this._getRequiredView("prev") ? 1 : 0
  587. },
  588. _getRequiredView: function(name) {
  589. var view;
  590. var isRtl = this.option("rtlEnabled");
  591. if ("next" === name) {
  592. view = isRtl ? this._beforeView : this._afterView
  593. } else {
  594. if ("prev" === name) {
  595. view = isRtl ? this._afterView : this._beforeView
  596. }
  597. }
  598. return view
  599. },
  600. _swipeUpdateHandler: function(e) {
  601. var offset = e.event.offset;
  602. translator.move(this._$viewsWrapper, {
  603. left: offset * this._viewWidth(),
  604. top: 0
  605. });
  606. this._updateNavigatorCaption(offset)
  607. },
  608. _swipeEndHandler: function(e) {
  609. var targetOffset = e.event.targetOffset;
  610. var moveOffset = !targetOffset ? 0 : targetOffset / Math.abs(targetOffset);
  611. if (0 === moveOffset) {
  612. this._animateWrapper(0, ANIMATION_DURATION_SHOW_VIEW);
  613. return
  614. }
  615. var date = this._getDateByOffset(-moveOffset * this._getRtlCorrection());
  616. if (this._isDateInInvalidRange(date)) {
  617. if (moveOffset >= 0) {
  618. date = new Date(this._getMinDate())
  619. } else {
  620. date = new Date(this._getMaxDate())
  621. }
  622. }
  623. this.option("currentDate", date)
  624. },
  625. _viewWidth: function() {
  626. if (!this._viewWidthValue) {
  627. this._viewWidthValue = this.$element().width()
  628. }
  629. return this._viewWidthValue
  630. },
  631. _updateNavigatorCaption: function(offset) {
  632. offset *= this._getRtlCorrection();
  633. var view = this._view;
  634. if (offset > .5 && this._beforeView) {
  635. view = this._beforeView
  636. } else {
  637. if (offset < -.5 && this._afterView) {
  638. view = this._afterView
  639. }
  640. }
  641. this._navigator.option("text", view.getNavigatorCaption())
  642. },
  643. _isDateInInvalidRange: function(date) {
  644. if (this._view.isBoundary(date)) {
  645. return
  646. }
  647. var min = this._getMinDate();
  648. var max = this._getMaxDate();
  649. var normalizedDate = dateUtils.normalizeDate(date, min, max);
  650. return normalizedDate === min || normalizedDate === max
  651. },
  652. _renderFooter: function() {
  653. var showTodayButton = this.option("showTodayButton");
  654. if (showTodayButton) {
  655. var $todayButton = this._createComponent($("<a>"), Button, {
  656. focusStateEnabled: false,
  657. text: messageLocalization.format("dxCalendar-todayButtonText"),
  658. onClick: function() {
  659. this._toTodayView()
  660. }.bind(this),
  661. integrationOptions: {}
  662. }).$element().addClass(CALENDAR_TODAY_BUTTON_CLASS);
  663. this._$footer = $("<div>").addClass(CALENDAR_FOOTER_CLASS).append($todayButton);
  664. this.$element().append(this._$footer)
  665. }
  666. this.$element().toggleClass(CALENDAR_HAS_FOOTER_CLASS, showTodayButton)
  667. },
  668. _renderSubmitElement: function() {
  669. this._$submitElement = $("<input>").attr("type", "hidden").appendTo(this.$element());
  670. this._setSubmitValue(this.option("value"))
  671. },
  672. _setSubmitValue: function(value) {
  673. var dateValue = this._convertToDate(value);
  674. this._getSubmitElement().val(dateSerialization.serializeDate(dateValue, CALENDAR_INPUT_STANDARD_PATTERN))
  675. },
  676. _getSubmitElement: function() {
  677. return this._$submitElement
  678. },
  679. _animateShowView: function() {
  680. fx.stop(this._view.$element(), true);
  681. return this._popAnimationView(this._view, POP_ANIMATION_FROM, POP_ANIMATION_TO, ANIMATION_DURATION_SHOW_VIEW).promise()
  682. },
  683. _popAnimationView: function(view, from, to, duration) {
  684. return fx.animate(view.$element(), {
  685. type: "pop",
  686. from: {
  687. scale: from,
  688. opacity: from
  689. },
  690. to: {
  691. scale: to,
  692. opacity: to
  693. },
  694. duration: duration
  695. })
  696. },
  697. _navigate: function(offset, value) {
  698. if (0 !== offset && 1 !== Math.abs(offset) && this._isViewAvailable(value)) {
  699. var newView = this._renderSpecificView(value);
  700. if (offset > 0) {
  701. this._afterView && this._afterView.$element().remove();
  702. this._afterView = newView
  703. } else {
  704. this._beforeView && this._beforeView.$element().remove();
  705. this._beforeView = newView
  706. }
  707. this._translateViews()
  708. }
  709. var rtlCorrection = this._getRtlCorrection();
  710. var offsetSign = offset > 0 ? 1 : offset < 0 ? -1 : 0;
  711. var endPosition = -rtlCorrection * offsetSign * this._viewWidth();
  712. var viewsWrapperPosition = this._$viewsWrapper.position().left;
  713. if (viewsWrapperPosition !== endPosition) {
  714. if (this._preventViewChangeAnimation) {
  715. this._wrapperAnimationEndHandler(offset, value)
  716. } else {
  717. this._animateWrapper(endPosition, ANIMATION_DURATION_SHOW_VIEW).done(this._wrapperAnimationEndHandler.bind(this, offset, value))
  718. }
  719. }
  720. },
  721. _animateWrapper: function(to, duration) {
  722. return fx.animate(this._$viewsWrapper, {
  723. type: "slide",
  724. from: {
  725. left: this._$viewsWrapper.position().left
  726. },
  727. to: {
  728. left: to
  729. },
  730. duration: duration
  731. })
  732. },
  733. _toTodayView: function() {
  734. var today = new Date;
  735. if (this._isMaxZoomLevel()) {
  736. this._dateOption("value", today);
  737. return
  738. }
  739. this._preventViewChangeAnimation = true;
  740. this.option("zoomLevel", this.option("maxZoomLevel"));
  741. this._dateOption("value", today);
  742. this._animateShowView();
  743. this._preventViewChangeAnimation = false
  744. },
  745. _wrapperAnimationEndHandler: function(offset, newDate) {
  746. this._rearrangeViews(offset);
  747. this._translateViews();
  748. this._resetLocation();
  749. this._renderNavigator();
  750. this._setViewContoured(newDate);
  751. this._updateAriaId(newDate)
  752. },
  753. _rearrangeViews: function(offset) {
  754. if (0 === offset) {
  755. return
  756. }
  757. var viewOffset;
  758. var viewToCreateKey;
  759. var viewToRemoveKey;
  760. if (offset < 0) {
  761. viewOffset = 1;
  762. viewToCreateKey = "_beforeView";
  763. viewToRemoveKey = "_afterView"
  764. } else {
  765. viewOffset = -1;
  766. viewToCreateKey = "_afterView";
  767. viewToRemoveKey = "_beforeView"
  768. }
  769. if (!this[viewToCreateKey]) {
  770. return
  771. }
  772. var destinationDate = this[viewToCreateKey].option("date");
  773. if (this[viewToRemoveKey]) {
  774. this[viewToRemoveKey].$element().remove()
  775. }
  776. if (offset === viewOffset) {
  777. this[viewToRemoveKey] = this._view
  778. } else {
  779. this[viewToRemoveKey] = this._renderSpecificView(this._getDateByOffset(viewOffset, destinationDate));
  780. this._view.$element().remove()
  781. }
  782. this._view = this[viewToCreateKey];
  783. var dateByOffset = this._getDateByOffset(-viewOffset, destinationDate);
  784. this[viewToCreateKey] = this._isViewAvailable(dateByOffset) ? this._renderSpecificView(dateByOffset) : null
  785. },
  786. _resetLocation: function() {
  787. translator.move(this._$viewsWrapper, {
  788. left: 0,
  789. top: 0
  790. })
  791. },
  792. _clean: function() {
  793. this.callBase();
  794. this._clearViewWidthCache();
  795. delete this._$viewsWrapper;
  796. delete this._navigator;
  797. delete this._$footer
  798. },
  799. _clearViewWidthCache: function() {
  800. delete this._viewWidthValue
  801. },
  802. _disposeViews: function() {
  803. this._view.$element().remove();
  804. this._beforeView && this._beforeView.$element().remove();
  805. this._afterView && this._afterView.$element().remove();
  806. delete this._view;
  807. delete this._beforeView;
  808. delete this._afterView
  809. },
  810. _refreshViews: function() {
  811. this._disposeViews();
  812. this._renderViews()
  813. },
  814. _visibilityChanged: function() {
  815. this._translateViews()
  816. },
  817. _focusInHandler: function() {
  818. this.callBase.apply(this, arguments);
  819. this._view.option("contouredDate", this.option("currentDate"))
  820. },
  821. _focusOutHandler: function() {
  822. this.callBase.apply(this, arguments);
  823. this._view.option("contouredDate", null)
  824. },
  825. _updateViewsValue: function(value) {
  826. var newValue = value ? new Date(value) : null;
  827. this._view.option("value", newValue);
  828. this._beforeView && this._beforeView.option("value", newValue);
  829. this._afterView && this._afterView.option("value", newValue)
  830. },
  831. _updateAriaSelected: function(value, previousValue) {
  832. value = value || this._dateOption("value");
  833. var $prevSelectedCell = this._view._getCellByDate(previousValue);
  834. var $selectedCell = this._view._getCellByDate(value);
  835. this.setAria("selected", void 0, $prevSelectedCell);
  836. this.setAria("selected", true, $selectedCell);
  837. if (value && this.option("currentDate").getTime() === value.getTime()) {
  838. this._updateAriaId(value)
  839. }
  840. },
  841. _updateAriaId: function(value) {
  842. value = value || this.option("currentDate");
  843. var ariaId = "dx-" + new Guid;
  844. var $newCell = this._view._getCellByDate(value);
  845. this.setAria("id", ariaId, $newCell);
  846. this.setAria("activedescendant", ariaId);
  847. this._onContouredChanged(ariaId)
  848. },
  849. _suppressingNavigation: function(callback, args) {
  850. this._suppressNavigation = true;
  851. callback.apply(this, args);
  852. delete this._suppressNavigation
  853. },
  854. _optionChanged: function(args) {
  855. var value = args.value;
  856. var previousValue = args.previousValue;
  857. switch (args.name) {
  858. case "width":
  859. this.callBase(args);
  860. this._clearViewWidthCache();
  861. break;
  862. case "min":
  863. case "max":
  864. this.min = void 0;
  865. this.max = void 0;
  866. this._suppressingNavigation(this._updateCurrentDate, [this.option("currentDate")]);
  867. this._refreshViews();
  868. this._renderNavigator();
  869. break;
  870. case "firstDayOfWeek":
  871. this._refreshViews();
  872. this._updateButtonsVisibility();
  873. break;
  874. case "currentDate":
  875. this.setAria("id", void 0, this._view._getCellByDate(previousValue));
  876. this._updateCurrentDate(value);
  877. break;
  878. case "zoomLevel":
  879. this.$element().removeClass(CALENDAR_VIEW_CLASS + "-" + previousValue);
  880. this._correctZoomLevel();
  881. this._refreshViews();
  882. this._renderNavigator();
  883. this._updateAriaId();
  884. break;
  885. case "minZoomLevel":
  886. case "maxZoomLevel":
  887. this._correctZoomLevel();
  888. this._updateButtonsVisibility();
  889. break;
  890. case "value":
  891. value = this._convertToDate(value);
  892. previousValue = this._convertToDate(previousValue);
  893. this._updateAriaSelected(value, previousValue);
  894. this.option("currentDate", typeUtils.isDefined(value) ? new Date(value) : new Date);
  895. this._updateViewsValue(value);
  896. this._setSubmitValue(value);
  897. this.callBase(args);
  898. break;
  899. case "disabled":
  900. this._view.option("disabled", value);
  901. this.callBase(args);
  902. break;
  903. case "onCellClick":
  904. this._view.option("onCellClick", value);
  905. break;
  906. case "onContouredChanged":
  907. this._onContouredChanged = this._createActionByOption("onContouredChanged");
  908. break;
  909. case "disabledDates":
  910. case "dateSerializationFormat":
  911. case "cellTemplate":
  912. case "showTodayButton":
  913. this._invalidate();
  914. break;
  915. case "hasFocus":
  916. break;
  917. default:
  918. this.callBase(args)
  919. }
  920. }
  921. });
  922. registerComponent("dxCalendar", Calendar);
  923. module.exports = Calendar;