You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

563 lines
25 KiB

  1. /**
  2. * Copyright © Magento, Inc. All rights reserved.
  3. * See COPYING.txt for license details.
  4. */
  5. /*eslint max-depth: 0*/
  6. define([
  7. 'jquery',
  8. 'jquery-ui-modules/widget',
  9. 'jquery-ui-modules/datepicker',
  10. 'jquery-ui-modules/timepicker'
  11. ], function ($) {
  12. 'use strict';
  13. var calendarBasePrototype,
  14. datepickerPrototype = $.datepicker.constructor.prototype;
  15. $.datepicker.markerClassName = '_has-datepicker';
  16. /**
  17. * Extend JQuery date picker prototype with store local time methods
  18. */
  19. $.extend(datepickerPrototype, {
  20. /**
  21. * Get date/time according to store settings.
  22. * We use serverTimezoneOffset (in seconds) instead of serverTimezoneSeconds
  23. * in order to have ability to know actual store time even if page hadn't been reloaded
  24. * @returns {Date}
  25. */
  26. _getTimezoneDate: function (options) {
  27. // local time in ms
  28. var ms = Date.now();
  29. options = options || $.calendarConfig || {};
  30. // Adjust milliseconds according to store timezone offset,
  31. // mind the GMT zero offset
  32. if (typeof options.serverTimezoneOffset !== 'undefined') {
  33. // Make UTC time and add store timezone offset in seconds
  34. ms += new Date().getTimezoneOffset() * 60 * 1000 + options.serverTimezoneOffset * 1000;
  35. } else if (typeof options.serverTimezoneSeconds !== 'undefined') {
  36. //Set milliseconds according to client local timezone offset
  37. ms = (options.serverTimezoneSeconds + new Date().getTimezoneOffset() * 60) * 1000;
  38. }
  39. return new Date(ms);
  40. },
  41. /**
  42. * Set date/time according to store settings.
  43. * @param {String|Object} target - the target input field or division or span
  44. */
  45. _setTimezoneDateDatepicker: function (target) {
  46. this._setDateDatepicker(target, this._getTimezoneDate());
  47. }
  48. });
  49. /**
  50. * Widget calendar
  51. */
  52. $.widget('mage.calendar', {
  53. options: {
  54. autoComplete: true
  55. },
  56. /**
  57. * Merge global options with options passed to widget invoke
  58. * @protected
  59. */
  60. _create: function () {
  61. this._enableAMPM();
  62. this.options = $.extend(
  63. {},
  64. $.calendarConfig ? $.calendarConfig : {},
  65. this.options.showsTime ? {
  66. showTime: true,
  67. showHour: true,
  68. showMinute: true
  69. } : {},
  70. this.options
  71. );
  72. this._initPicker(this.element);
  73. this._overwriteGenerateHtml();
  74. },
  75. /**
  76. * Get picker name
  77. * @protected
  78. */
  79. _picker: function () {
  80. return this.options.showsTime ? 'datetimepicker' : 'datepicker';
  81. },
  82. /**
  83. * Fix for Timepicker - Set ampm option for Timepicker if timeformat contains string 'tt'
  84. * @protected
  85. */
  86. _enableAMPM: function () {
  87. if (this.options.timeFormat && this.options.timeFormat.indexOf('tt') >= 0) {
  88. this.options.ampm = true;
  89. }
  90. },
  91. /**
  92. * Wrapper for overwrite jQuery UI datepicker function.
  93. */
  94. _overwriteGenerateHtml: function () {
  95. /**
  96. * Overwrite jQuery UI datepicker function.
  97. * Reason: magento date could be set before calendar show
  98. * but local date will be styled as current in original _generateHTML
  99. *
  100. * @param {Object} inst - instance datepicker.
  101. * @return {String} html template
  102. */
  103. $.datepicker.constructor.prototype._generateHTML = function (inst) {
  104. var today = this._getTimezoneDate(),
  105. isRTL = this._get(inst, 'isRTL'),
  106. showButtonPanel = this._get(inst, 'showButtonPanel'),
  107. hideIfNoPrevNext = this._get(inst, 'hideIfNoPrevNext'),
  108. navigationAsDateFormat = this._get(inst, 'navigationAsDateFormat'),
  109. numMonths = this._getNumberOfMonths(inst),
  110. showCurrentAtPos = this._get(inst, 'showCurrentAtPos'),
  111. stepMonths = this._get(inst, 'stepMonths'),
  112. isMultiMonth = parseInt(numMonths[0], 10) !== 1 || parseInt(numMonths[1], 10) !== 1,
  113. currentDate = this._daylightSavingAdjust(!inst.currentDay ? new Date(9999, 9, 9) :
  114. new Date(inst.currentYear, inst.currentMonth, inst.currentDay)),
  115. minDate = this._getMinMaxDate(inst, 'min'),
  116. maxDate = this._getMinMaxDate(inst, 'max'),
  117. drawMonth = inst.drawMonth - showCurrentAtPos,
  118. drawYear = inst.drawYear,
  119. maxDraw,
  120. prevText = this._get(inst, 'prevText'),
  121. prev,
  122. nextText = this._get(inst, 'nextText'),
  123. next,
  124. currentText = this._get(inst, 'currentText'),
  125. gotoDate,
  126. controls,
  127. buttonPanel,
  128. firstDay,
  129. showWeek = this._get(inst, 'showWeek'),
  130. dayNames = this._get(inst, 'dayNames'),
  131. dayNamesMin = this._get(inst, 'dayNamesMin'),
  132. monthNames = this._get(inst, 'monthNames'),
  133. monthNamesShort = this._get(inst, 'monthNamesShort'),
  134. beforeShowDay = this._get(inst, 'beforeShowDay'),
  135. showOtherMonths = this._get(inst, 'showOtherMonths'),
  136. selectOtherMonths = this._get(inst, 'selectOtherMonths'),
  137. defaultDate = this._getDefaultDate(inst),
  138. html = '',
  139. row = 0,
  140. col = 0,
  141. selectedDate,
  142. cornerClass = ' ui-corner-all',
  143. group = '',
  144. calender = '',
  145. dow = 0,
  146. thead,
  147. day,
  148. daysInMonth,
  149. leadDays,
  150. curRows,
  151. numRows,
  152. printDate,
  153. dRow = 0,
  154. tbody,
  155. daySettings,
  156. otherMonth,
  157. unselectable;
  158. if (drawMonth < 0) {
  159. drawMonth += 12;
  160. drawYear--;
  161. }
  162. if (maxDate) {
  163. maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(),
  164. maxDate.getMonth() - numMonths[0] * numMonths[1] + 1, maxDate.getDate()));
  165. maxDraw = minDate && maxDraw < minDate ? minDate : maxDraw;
  166. while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) {
  167. drawMonth--;
  168. if (drawMonth < 0) {
  169. drawMonth = 11;
  170. drawYear--;
  171. }
  172. }
  173. }
  174. inst.drawMonth = drawMonth;
  175. inst.drawYear = drawYear;
  176. prevText = !navigationAsDateFormat ? prevText : this.formatDate(prevText,
  177. this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)),
  178. this._getFormatConfig(inst));
  179. prev = this._canAdjustMonth(inst, -1, drawYear, drawMonth) ?
  180. '<a class="ui-datepicker-prev ui-corner-all" data-handler="prev" data-event="click"' +
  181. ' title="' + prevText + '">' +
  182. '<span class="ui-icon ui-icon-circle-triangle-' + (isRTL ? 'e' : 'w') + '">' +
  183. '' + prevText + '</span></a>'
  184. : hideIfNoPrevNext ? ''
  185. : '<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="' +
  186. '' + prevText + '"><span class="ui-icon ui-icon-circle-triangle-' +
  187. '' + (isRTL ? 'e' : 'w') + '">' + prevText + '</span></a>';
  188. nextText = !navigationAsDateFormat ?
  189. nextText
  190. : this.formatDate(nextText,
  191. this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)),
  192. this._getFormatConfig(inst));
  193. next = this._canAdjustMonth(inst, +1, drawYear, drawMonth) ?
  194. '<a class="ui-datepicker-next ui-corner-all" data-handler="next" data-event="click"' +
  195. 'title="' + nextText + '"><span class="ui-icon ui-icon-circle-triangle-' +
  196. '' + (isRTL ? 'w' : 'e') + '">' + nextText + '</span></a>'
  197. : hideIfNoPrevNext ? ''
  198. : '<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="' + nextText + '">' +
  199. '<span class="ui-icon ui-icon-circle-triangle-' + (isRTL ? 'w' : 'e') + '">' + nextText +
  200. '</span></a>';
  201. gotoDate = this._get(inst, 'gotoCurrent') && inst.currentDay ? currentDate : today;
  202. currentText = !navigationAsDateFormat ? currentText :
  203. this.formatDate(currentText, gotoDate, this._getFormatConfig(inst));
  204. controls = !inst.inline ?
  205. '<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ' +
  206. 'ui-corner-all" data-handler="hide" data-event="click">' +
  207. this._get(inst, 'closeText') + '</button>'
  208. : '';
  209. buttonPanel = showButtonPanel ?
  210. '<div class="ui-datepicker-buttonpane ui-widget-content">' + (isRTL ? controls : '') +
  211. (this._isInRange(inst, gotoDate) ? '<button type="button" class="ui-datepicker-current ' +
  212. 'ui-state-default ui-priority-secondary ui-corner-all" data-handler="today" data-event="click"' +
  213. '>' + currentText + '</button>' : '') + (isRTL ? '' : controls) + '</div>' : '';
  214. firstDay = parseInt(this._get(inst, 'firstDay'), 10);
  215. firstDay = isNaN(firstDay) ? 0 : firstDay;
  216. for (row = 0; row < numMonths[0]; row++) {
  217. this.maxRows = 4;
  218. for (col = 0; col < numMonths[1]; col++) {
  219. selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay));
  220. calender = '';
  221. if (isMultiMonth) {
  222. calender += '<div class="ui-datepicker-group';
  223. if (numMonths[1] > 1) {
  224. switch (col) {
  225. case 0: calender += ' ui-datepicker-group-first';
  226. cornerClass = ' ui-corner-' + (isRTL ? 'right' : 'left');
  227. break;
  228. case numMonths[1] - 1: calender += ' ui-datepicker-group-last';
  229. cornerClass = ' ui-corner-' + (isRTL ? 'left' : 'right');
  230. break;
  231. default: calender += ' ui-datepicker-group-middle'; cornerClass = '';
  232. }
  233. }
  234. calender += '">';
  235. }
  236. calender += '<div class="ui-datepicker-header ' +
  237. 'ui-widget-header ui-helper-clearfix' + cornerClass + '">' +
  238. (/all|left/.test(cornerClass) && parseInt(row, 10) === 0 ? isRTL ? next : prev : '') +
  239. (/all|right/.test(cornerClass) && parseInt(row, 10) === 0 ? isRTL ? prev : next : '') +
  240. this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate,
  241. row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers
  242. '</div><table class="ui-datepicker-calendar"><thead>' +
  243. '<tr>';
  244. thead = showWeek ?
  245. '<th class="ui-datepicker-week-col">' + this._get(inst, 'weekHeader') + '</th>' : '';
  246. for (dow = 0; dow < 7; dow++) { // days of the week
  247. day = (dow + firstDay) % 7;
  248. thead += '<th' + ((dow + firstDay + 6) % 7 >= 5 ?
  249. ' class="ui-datepicker-week-end"' : '') + '>' +
  250. '<span title="' + dayNames[day] + '">' + dayNamesMin[day] + '</span></th>';
  251. }
  252. calender += thead + '</tr></thead><tbody>';
  253. daysInMonth = this._getDaysInMonth(drawYear, drawMonth);
  254. if (drawYear === inst.selectedYear && drawMonth === inst.selectedMonth) {
  255. inst.selectedDay = Math.min(inst.selectedDay, daysInMonth);
  256. }
  257. leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7;
  258. curRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate
  259. numRows = isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows;
  260. this.maxRows = numRows;
  261. printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays));
  262. for (dRow = 0; dRow < numRows; dRow++) { // create date picker rows
  263. calender += '<tr>';
  264. tbody = !showWeek ? '' : '<td class="ui-datepicker-week-col">' +
  265. this._get(inst, 'calculateWeek')(printDate) + '</td>';
  266. for (dow = 0; dow < 7; dow++) { // create date picker days
  267. daySettings = beforeShowDay ?
  268. beforeShowDay.apply(inst.input ? inst.input[0] : null, [printDate]) : [true, ''];
  269. otherMonth = printDate.getMonth() !== drawMonth;
  270. unselectable = otherMonth && !selectOtherMonths || !daySettings[0] ||
  271. minDate && printDate < minDate || maxDate && printDate > maxDate;
  272. tbody += '<td class="' +
  273. ((dow + firstDay + 6) % 7 >= 5 ? ' ui-datepicker-week-end' : '') + // highlight weekends
  274. (otherMonth ? ' ui-datepicker-other-month' : '') + // highlight days from other months
  275. (printDate.getTime() === selectedDate.getTime() &&
  276. drawMonth === inst.selectedMonth && inst._keyEvent || // user pressed key
  277. defaultDate.getTime() === printDate.getTime() &&
  278. defaultDate.getTime() === selectedDate.getTime() ?
  279. // or defaultDate is current printedDate and defaultDate is selectedDate
  280. ' ' + this._dayOverClass : '') + // highlight selected day
  281. (unselectable ? ' ' + this._unselectableClass + ' ui-state-disabled' : '') +
  282. (otherMonth && !showOtherMonths ? '' : ' ' + daySettings[1] + // highlight custom dates
  283. (printDate.getTime() === currentDate.getTime() ? ' ' + this._currentClass : '') +
  284. (printDate.getDate() === today.getDate() && printDate.getMonth() === today.getMonth() &&
  285. printDate.getYear() === today.getYear() ? ' ui-datepicker-today' : '')) + '"' +
  286. ((!otherMonth || showOtherMonths) && daySettings[2] ?
  287. ' title="' + daySettings[2] + '"' : '') + // cell title
  288. (unselectable ? '' : ' data-handler="selectDay" data-event="click" data-month="' +
  289. '' + printDate.getMonth() + '" data-year="' + printDate.getFullYear() + '"') + '>' +
  290. (otherMonth && !showOtherMonths ? '&#xa0;' : // display for other months
  291. unselectable ? '<span class="ui-state-default">' + printDate.getDate() + '</span>'
  292. : '<a class="ui-state-default' +
  293. (printDate.getTime() === today.getTime() ? ' ' : '') +
  294. (printDate.getTime() === currentDate.getTime() ? ' ui-state-active' : '') +
  295. (otherMonth ? ' ui-priority-secondary' : '') +
  296. '" data-date="' + printDate.getDate() + '" href="#">' +
  297. printDate.getDate() + '</a>') + '</td>';
  298. printDate.setDate(printDate.getDate() + 1);
  299. printDate = this._daylightSavingAdjust(printDate);
  300. }
  301. calender += tbody + '</tr>';
  302. }
  303. drawMonth++;
  304. if (drawMonth > 11) {
  305. drawMonth = 0;
  306. drawYear++;
  307. }
  308. calender += '</tbody></table>' + (isMultiMonth ? '</div>' +
  309. (numMonths[0] > 0 && col === numMonths[1] - 1 ? '<div class="ui-datepicker-row-break"></div>'
  310. : '') : '');
  311. group += calender;
  312. }
  313. html += group;
  314. }
  315. html += buttonPanel + ($.ui.ie6 && !inst.inline ?
  316. '<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>' : '');
  317. inst._keyEvent = false;
  318. return html;
  319. };
  320. },
  321. /**
  322. * Set current date if the date is not set
  323. * @protected
  324. * @param {Object} element
  325. */
  326. _setCurrentDate: function (element) {
  327. if (!element.val()) {
  328. element[this._picker()]('setTimezoneDate').val('');
  329. }
  330. },
  331. /**
  332. * Init Datetimepicker
  333. * @protected
  334. * @param {Object} element
  335. */
  336. _initPicker: function (element) {
  337. var picker = element[this._picker()](this.options),
  338. pickerButtonText = picker.next('.ui-datepicker-trigger')
  339. .find('img')
  340. .attr('title');
  341. picker.next('.ui-datepicker-trigger')
  342. .addClass('v-middle')
  343. .text('') // Remove jQuery UI datepicker generated image
  344. .append('<span>' + pickerButtonText + '</span>');
  345. $(element).attr('autocomplete', this.options.autoComplete ? 'on' : 'off');
  346. this._setCurrentDate(element);
  347. },
  348. /**
  349. * destroy instance of datetimepicker
  350. */
  351. _destroy: function () {
  352. this.element[this._picker()]('destroy');
  353. this._super();
  354. },
  355. /**
  356. * Method is kept for backward compatibility and unit-tests acceptance
  357. * see \mage\calendar\calendar-test.js
  358. * @return {Object} date
  359. */
  360. getTimezoneDate: function () {
  361. return datepickerPrototype._getTimezoneDate.call(this, this.options);
  362. }
  363. });
  364. calendarBasePrototype = $.mage.calendar.prototype;
  365. /**
  366. * Extension for Calendar - date and time format convert functionality
  367. * @var {Object}
  368. */
  369. $.widget('mage.calendar', $.extend({}, calendarBasePrototype,
  370. /** @lends {$.mage.calendar.prototype} */ {
  371. /**
  372. * key - backend format, value - jquery format
  373. * @type {Object}
  374. * @private
  375. */
  376. dateTimeFormat: {
  377. date: {
  378. 'EEEE': 'DD',
  379. 'EEE': 'D',
  380. 'EE': 'D',
  381. 'E': 'D',
  382. 'D': 'o',
  383. 'MMMM': 'MM',
  384. 'MMM': 'M',
  385. 'MM': 'mm',
  386. 'M': 'mm',
  387. 'yyyy': 'yy',
  388. 'y': 'yy',
  389. 'Y': 'yy',
  390. 'yy': 'yy' // Always long year format on frontend
  391. },
  392. time: {
  393. 'a': 'TT'
  394. }
  395. },
  396. /**
  397. * Add Date and Time converting to _create method
  398. * @protected
  399. */
  400. _create: function () {
  401. if (this.options.dateFormat) {
  402. this.options.dateFormat = this._convertFormat(this.options.dateFormat, 'date');
  403. }
  404. if (this.options.timeFormat) {
  405. this.options.timeFormat = this._convertFormat(this.options.timeFormat, 'time');
  406. }
  407. calendarBasePrototype._create.apply(this, arguments);
  408. },
  409. /**
  410. * Converting date or time format
  411. * @protected
  412. * @param {String} format
  413. * @param {String} type
  414. * @return {String}
  415. */
  416. _convertFormat: function (format, type) {
  417. var symbols = format.match(/([a-z]+)/ig),
  418. separators = format.match(/([^a-z]+)/ig),
  419. self = this,
  420. convertedFormat = '';
  421. if (symbols) {
  422. $.each(symbols, function (key, val) {
  423. convertedFormat +=
  424. (self.dateTimeFormat[type][val] || val) +
  425. (separators[key] || '');
  426. });
  427. }
  428. return convertedFormat;
  429. }
  430. })
  431. );
  432. /**
  433. * Widget dateRange
  434. * @extends $.mage.calendar
  435. */
  436. $.widget('mage.dateRange', $.mage.calendar, {
  437. /**
  438. * creates two instances of datetimepicker for date range selection
  439. * @protected
  440. */
  441. _initPicker: function () {
  442. var from,
  443. to;
  444. if (this.options.from && this.options.to) {
  445. from = this.element.find('#' + this.options.from.id);
  446. to = this.element.find('#' + this.options.to.id);
  447. this.options.onSelect = $.proxy(function (selectedDate) {
  448. to[this._picker()]('option', 'minDate', selectedDate);
  449. }, this);
  450. $.mage.calendar.prototype._initPicker.call(this, from);
  451. from.on('change', $.proxy(function () {
  452. to[this._picker()]('option', 'minDate', from[this._picker()]('getDate'));
  453. }, this));
  454. this.options.onSelect = $.proxy(function (selectedDate) {
  455. from[this._picker()]('option', 'maxDate', selectedDate);
  456. }, this);
  457. $.mage.calendar.prototype._initPicker.call(this, to);
  458. to.on('change', $.proxy(function () {
  459. from[this._picker()]('option', 'maxDate', to[this._picker()]('getDate'));
  460. }, this));
  461. }
  462. },
  463. /**
  464. * destroy two instances of datetimepicker
  465. */
  466. _destroy: function () {
  467. if (this.options.from) {
  468. this.element.find('#' + this.options.from.id)[this._picker()]('destroy');
  469. }
  470. if (this.options.to) {
  471. this.element.find('#' + this.options.to.id)[this._picker()]('destroy');
  472. }
  473. this._super();
  474. }
  475. });
  476. // Overrides the "today" button functionality to select today's date when clicked.
  477. $.datepicker._gotoTodayOriginal = $.datepicker._gotoToday;
  478. /**
  479. * overwrite jQuery UI _showDatepicker function for proper HTML generation conditions.
  480. *
  481. */
  482. $.datepicker._showDatepickerOriginal = $.datepicker._showDatepicker;
  483. /**
  484. * Triggers original method showDataPicker for rendering calendar
  485. * @param {HTMLObject} input
  486. * @private
  487. */
  488. $.datepicker._showDatepicker = function (input) {
  489. if (!input.disabled) {
  490. $.datepicker._showDatepickerOriginal.call(this, input);
  491. }
  492. };
  493. /**
  494. * _gotoToday
  495. * @param {Object} el
  496. */
  497. $.datepicker._gotoToday = function (el) {
  498. //Set date/time according to timezone offset
  499. $(el).datepicker('setTimezoneDate')
  500. // To ensure that user can re-select date field without clicking outside it first.
  501. .trigger('blur').trigger('change');
  502. };
  503. return {
  504. dateRange: $.mage.dateRange,
  505. calendar: $.mage.calendar
  506. };
  507. });