/** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ /*eslint max-depth: 0*/ (function (factory) { 'use strict'; if (typeof define === 'function' && define.amd) { define([ 'jquery', 'jquery/ui', 'jquery/jquery-ui-timepicker-addon' ], factory); } else { factory(window.jQuery); } }(function ($) { 'use strict'; var calendarBasePrototype, datepickerPrototype = $.datepicker.constructor.prototype; $.datepicker.markerClassName = '_has-datepicker'; /** * Extend JQuery date picker prototype with store local time methods */ $.extend(datepickerPrototype, { /** * Get date/time according to store settings. * We use serverTimezoneOffset (in seconds) instead of serverTimezoneSeconds * in order to have ability to know actual store time even if page hadn't been reloaded * @returns {Date} */ _getTimezoneDate: function (options) { // local time in ms var ms = Date.now(); options = options || $.calendarConfig || {}; // Adjust milliseconds according to store timezone offset, // mind the GMT zero offset if (typeof options.serverTimezoneOffset !== 'undefined') { // Make UTC time and add store timezone offset in seconds ms += new Date().getTimezoneOffset() * 60 * 1000 + options.serverTimezoneOffset * 1000; } else if (typeof options.serverTimezoneSeconds !== 'undefined') { //Set milliseconds according to client local timezone offset ms = (options.serverTimezoneSeconds + new Date().getTimezoneOffset() * 60) * 1000; } return new Date(ms); }, /** * Set date/time according to store settings. * @param {String|Object} target - the target input field or division or span */ _setTimezoneDateDatepicker: function (target) { this._setDateDatepicker(target, this._getTimezoneDate()); } }); /** * Widget calendar */ $.widget('mage.calendar', { options: { autoComplete: true }, /** * Merge global options with options passed to widget invoke * @protected */ _create: function () { this._enableAMPM(); this.options = $.extend( {}, $.calendarConfig ? $.calendarConfig : {}, this.options.showsTime ? { showTime: true, showHour: true, showMinute: true } : {}, this.options ); this._initPicker(this.element); this._overwriteGenerateHtml(); }, /** * Get picker name * @protected */ _picker: function () { return this.options.showsTime ? 'datetimepicker' : 'datepicker'; }, /** * Fix for Timepicker - Set ampm option for Timepicker if timeformat contains string 'tt' * @protected */ _enableAMPM: function () { if (this.options.timeFormat && this.options.timeFormat.indexOf('tt') >= 0) { this.options.ampm = true; } }, /** * Wrapper for overwrite jQuery UI datepicker function. */ _overwriteGenerateHtml: function () { /** * Overwrite jQuery UI datepicker function. * Reason: magento date could be set before calendar show * but local date will be styled as current in original _generateHTML * * @param {Object} inst - instance datepicker. * @return {String} html template */ $.datepicker.constructor.prototype._generateHTML = function (inst) { var today = this._getTimezoneDate(), isRTL = this._get(inst, 'isRTL'), showButtonPanel = this._get(inst, 'showButtonPanel'), hideIfNoPrevNext = this._get(inst, 'hideIfNoPrevNext'), navigationAsDateFormat = this._get(inst, 'navigationAsDateFormat'), numMonths = this._getNumberOfMonths(inst), showCurrentAtPos = this._get(inst, 'showCurrentAtPos'), stepMonths = this._get(inst, 'stepMonths'), isMultiMonth = parseInt(numMonths[0], 10) !== 1 || parseInt(numMonths[1], 10) !== 1, currentDate = this._daylightSavingAdjust(!inst.currentDay ? new Date(9999, 9, 9) : new Date(inst.currentYear, inst.currentMonth, inst.currentDay)), minDate = this._getMinMaxDate(inst, 'min'), maxDate = this._getMinMaxDate(inst, 'max'), drawMonth = inst.drawMonth - showCurrentAtPos, drawYear = inst.drawYear, maxDraw, prevText = this._get(inst, 'prevText'), prev, nextText = this._get(inst, 'nextText'), next, currentText = this._get(inst, 'currentText'), gotoDate, controls, buttonPanel, firstDay, showWeek = this._get(inst, 'showWeek'), dayNames = this._get(inst, 'dayNames'), dayNamesMin = this._get(inst, 'dayNamesMin'), monthNames = this._get(inst, 'monthNames'), monthNamesShort = this._get(inst, 'monthNamesShort'), beforeShowDay = this._get(inst, 'beforeShowDay'), showOtherMonths = this._get(inst, 'showOtherMonths'), selectOtherMonths = this._get(inst, 'selectOtherMonths'), defaultDate = this._getDefaultDate(inst), html = '', row = 0, col = 0, selectedDate, cornerClass = ' ui-corner-all', group = '', calender = '', dow = 0, thead, day, daysInMonth, leadDays, curRows, numRows, printDate, dRow = 0, tbody, daySettings, otherMonth, unselectable; if (drawMonth < 0) { drawMonth += 12; drawYear--; } if (maxDate) { maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(), maxDate.getMonth() - numMonths[0] * numMonths[1] + 1, maxDate.getDate())); maxDraw = minDate && maxDraw < minDate ? minDate : maxDraw; while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) { drawMonth--; if (drawMonth < 0) { drawMonth = 11; drawYear--; } } } inst.drawMonth = drawMonth; inst.drawYear = drawYear; prevText = !navigationAsDateFormat ? prevText : this.formatDate(prevText, this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)), this._getFormatConfig(inst)); prev = this._canAdjustMonth(inst, -1, drawYear, drawMonth) ? '' + '' + '' + prevText + '' : hideIfNoPrevNext ? '' : '' + prevText + ''; nextText = !navigationAsDateFormat ? nextText : this.formatDate(nextText, this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)), this._getFormatConfig(inst)); next = this._canAdjustMonth(inst, +1, drawYear, drawMonth) ? '' + nextText + '' : hideIfNoPrevNext ? '' : '' + '' + nextText + ''; gotoDate = this._get(inst, 'gotoCurrent') && inst.currentDay ? currentDate : today; currentText = !navigationAsDateFormat ? currentText : this.formatDate(currentText, gotoDate, this._getFormatConfig(inst)); controls = !inst.inline ? '' : ''; buttonPanel = showButtonPanel ? '
' + (isRTL ? controls : '') + (this._isInRange(inst, gotoDate) ? '' : '') + (isRTL ? '' : controls) + '
' : ''; firstDay = parseInt(this._get(inst, 'firstDay'), 10); firstDay = isNaN(firstDay) ? 0 : firstDay; for (row = 0; row < numMonths[0]; row++) { this.maxRows = 4; for (col = 0; col < numMonths[1]; col++) { selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay)); calender = ''; if (isMultiMonth) { calender += '
'; } calender += '
' + (/all|left/.test(cornerClass) && parseInt(row, 10) === 0 ? isRTL ? next : prev : '') + (/all|right/.test(cornerClass) && parseInt(row, 10) === 0 ? isRTL ? prev : next : '') + this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate, row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers '
' + ''; thead = showWeek ? '' : ''; for (dow = 0; dow < 7; dow++) { // days of the week day = (dow + firstDay) % 7; thead += '= 5 ? ' class="ui-datepicker-week-end"' : '') + '>' + '' + dayNamesMin[day] + ''; } calender += thead + ''; daysInMonth = this._getDaysInMonth(drawYear, drawMonth); if (drawYear === inst.selectedYear && drawMonth === inst.selectedMonth) { inst.selectedDay = Math.min(inst.selectedDay, daysInMonth); } leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7; curRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate numRows = isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows; this.maxRows = numRows; printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays)); for (dRow = 0; dRow < numRows; dRow++) { // create date picker rows calender += ''; tbody = !showWeek ? '' : ''; for (dow = 0; dow < 7; dow++) { // create date picker days daySettings = beforeShowDay ? beforeShowDay.apply(inst.input ? inst.input[0] : null, [printDate]) : [true, '']; otherMonth = printDate.getMonth() !== drawMonth; unselectable = otherMonth && !selectOtherMonths || !daySettings[0] || minDate && printDate < minDate || maxDate && printDate > maxDate; tbody += ''; printDate.setDate(printDate.getDate() + 1); printDate = this._daylightSavingAdjust(printDate); } calender += tbody + ''; } drawMonth++; if (drawMonth > 11) { drawMonth = 0; drawYear++; } calender += '
' + this._get(inst, 'weekHeader') + '
' + this._get(inst, 'calculateWeek')(printDate) + '' + (otherMonth && !showOtherMonths ? ' ' : // display for other months unselectable ? '' + printDate.getDate() + '' : '' + printDate.getDate() + '') + '
' + (isMultiMonth ? '
' + (numMonths[0] > 0 && col === numMonths[1] - 1 ? '
' : '') : ''); group += calender; } html += group; } html += buttonPanel + ($.ui.ie6 && !inst.inline ? '' : ''); inst._keyEvent = false; return html; }; }, /** * Set current date if the date is not set * @protected * @param {Object} element */ _setCurrentDate: function (element) { if (!element.val()) { element[this._picker()]('setTimezoneDate').val(''); } }, /** * Init Datetimepicker * @protected * @param {Object} element */ _initPicker: function (element) { var picker = element[this._picker()](this.options), pickerButtonText = picker.next('.ui-datepicker-trigger') .find('img') .attr('title'); picker.next('.ui-datepicker-trigger') .addClass('v-middle') .text('') // Remove jQuery UI datepicker generated image .append('' + pickerButtonText + ''); $(element).attr('autocomplete', this.options.autoComplete ? 'on' : 'off'); this._setCurrentDate(element); }, /** * destroy instance of datetimepicker */ _destroy: function () { this.element[this._picker()]('destroy'); this._super(); }, /** * Method is kept for backward compatibility and unit-tests acceptance * see \mage\calendar\calendar-test.js * @return {Object} date */ getTimezoneDate: function () { return datepickerPrototype._getTimezoneDate.call(this, this.options); } }); calendarBasePrototype = $.mage.calendar.prototype; /** * Extension for Calendar - date and time format convert functionality * @var {Object} */ $.widget('mage.calendar', $.extend({}, calendarBasePrototype, /** @lends {$.mage.calendar.prototype} */ { /** * key - backend format, value - jquery format * @type {Object} * @private */ dateTimeFormat: { date: { 'EEEE': 'DD', 'EEE': 'D', 'EE': 'D', 'E': 'D', 'D': 'o', 'MMMM': 'MM', 'MMM': 'M', 'MM': 'mm', 'M': 'mm', 'yyyy': 'yy', 'y': 'yy', 'Y': 'yy', 'yy': 'yy' // Always long year format on frontend }, time: { 'a': 'TT' } }, /** * Add Date and Time converting to _create method * @protected */ _create: function () { if (this.options.dateFormat) { this.options.dateFormat = this._convertFormat(this.options.dateFormat, 'date'); } if (this.options.timeFormat) { this.options.timeFormat = this._convertFormat(this.options.timeFormat, 'time'); } calendarBasePrototype._create.apply(this, arguments); }, /** * Converting date or time format * @protected * @param {String} format * @param {String} type * @return {String} */ _convertFormat: function (format, type) { var symbols = format.match(/([a-z]+)/ig), separators = format.match(/([^a-z]+)/ig), self = this, convertedFormat = ''; if (symbols) { $.each(symbols, function (key, val) { convertedFormat += (self.dateTimeFormat[type][val] || val) + (separators[key] || ''); }); } return convertedFormat; } }) ); /** * Widget dateRange * @extends $.mage.calendar */ $.widget('mage.dateRange', $.mage.calendar, { /** * creates two instances of datetimepicker for date range selection * @protected */ _initPicker: function () { var from, to; if (this.options.from && this.options.to) { from = this.element.find('#' + this.options.from.id); to = this.element.find('#' + this.options.to.id); this.options.onSelect = $.proxy(function (selectedDate) { to[this._picker()]('option', 'minDate', selectedDate); }, this); $.mage.calendar.prototype._initPicker.call(this, from); from.on('change', $.proxy(function () { to[this._picker()]('option', 'minDate', from[this._picker()]('getDate')); }, this)); this.options.onSelect = $.proxy(function (selectedDate) { from[this._picker()]('option', 'maxDate', selectedDate); }, this); $.mage.calendar.prototype._initPicker.call(this, to); to.on('change', $.proxy(function () { from[this._picker()]('option', 'maxDate', to[this._picker()]('getDate')); }, this)); } }, /** * destroy two instances of datetimepicker */ _destroy: function () { if (this.options.from) { this.element.find('#' + this.options.from.id)[this._picker()]('destroy'); } if (this.options.to) { this.element.find('#' + this.options.to.id)[this._picker()]('destroy'); } this._super(); } }); // Overrides the "today" button functionality to select today's date when clicked. $.datepicker._gotoTodayOriginal = $.datepicker._gotoToday; /** * overwrite jQuery UI _showDatepicker function for proper HTML generation conditions. * */ $.datepicker._showDatepickerOriginal = $.datepicker._showDatepicker; /** * Triggers original method showDataPicker for rendering calendar * @param {HTMLObject} input * @private */ $.datepicker._showDatepicker = function (input) { if (!input.disabled) { $.datepicker._showDatepickerOriginal.call(this, input); } }; /** * _gotoToday * @param {Object} el */ $.datepicker._gotoToday = function (el) { //Set date/time according to timezone offset $(el).datepicker('setTimezoneDate') // To ensure that user can re-select date field without clicking outside it first. .blur().trigger('change'); }; return { dateRange: $.mage.dateRange, calendar: $.mage.calendar }; }));