{"version":3,"file":"slx.dateselector.min.js","sources":["dist/js/slx.dateselector.js"],"sourcesContent":["/**\r\n * SlxControls 1.2.36 ({@link https://www.selerix.com})\r\n * This is unpublished proprietary source code of Selerix Systems, Inc. The\r\n * copyright notice above is not evidence any actual or intended\r\n * publication of such source code.\r\n * @copyright Copyright 2024 Selerix Systems, Inc. All Rights Reserved.\r\n */\r\n/**\r\n * SlxDateSelector class.\r\n * @requires slx.core\r\n * @requires slx.calendar\r\n * @class\r\n */\r\nvar SlxDateSelector = (function (global) {\r\n\t'use strict';\r\n\tvar x = global.SlxControl;\r\n\r\n\t/**\r\n\t * slx.dateSelector\r\n\t * @constructs\r\n\t */\r\n\tx.slx.dateSelector = function () {\r\n\t\tvar that = this;\r\n\t\tx.slx.base.apply(that, arguments);\r\n\r\n\t\t// reference current element\r\n\t\tvar el = that._element;\r\n\t\tif (el === void 0) { return this; }\r\n\t\telse if (el.length === 0) { return el; }\r\n\r\n\t\t// get date locale and language\r\n\t\tvar _getLocale = function(lang) {\r\n\t\t\tif (!Date['locale']) {\r\n\t\t\t\treturn {};\r\n\t\t\t} else if (!lang) {\r\n\t\t\t\treturn Date['locale'];\r\n\t\t\t}\r\n\r\n\t\t\treturn Date['locale'][lang] || {};\r\n\t\t};\r\n\r\n\t\t// set custom settings from data props and attributes\r\n\t\tthat.id = that.id || el.slxUniqueId();\r\n\t\tthat.selector = '#' + that.id;\r\n\t\tthat.container = x(that.selector + '_container');\r\n\t\tthat.showIcon = that.showIcon || el.data('showicon');\r\n\t\tthat.icon = that.icon || 'glyphicons glyphicons-calendar';\r\n\t\tthat.onChange = that.onChange || el.attr('onchange') !== void 0 ? x.eval.call(el[0], el.attr('onchange'), 'event') : void 0;\r\n\t\tthat.view = that.view || el.data('view') || 'month';\r\n\t\tthat.lang = that.lang && that.lang in _getLocale() ? that.lang : 'en';\r\n\t\tthat.format = that.format || el.data('format') || _getLocale(that.lang).format;\r\n\t\tthat.calendar = null;\r\n\t\tthat.mask = that.mask || el.data('mask') || '__/__/____';\r\n\t\tthat.mobileMaxWidth = that.mobileMaxWidth || el.data('mobile-maxwidth') || 767;\r\n\t\tthat.html5 = that.html5 === false || el.data('html5') === false ? false : true;\r\n\t\tthat.readonly = el.prop('readonly');\r\n\t\tthat.disabled = el.prop('disabled');\r\n\r\n\t\t// local flags and references\r\n\t\tvar _isMobile = false, _dateSupported = null;\r\n\t\tvar _btn = this.container.find('.slx-dateselector-icon');\r\n\r\n\t\t// fill the element with remaining chars in mask\r\n\t\tvar _fillMask = function (char) {\r\n\t\t\tvar v = el.val().replace(/[\\D]/g, ''), val;\r\n\t\t\tv += char || '', val = '';\r\n\t\t\t// loop through mask chars\r\n\t\t\tfor (var i = 0, n = 0; i < that.mask.length; i++) {\r\n\t\t\t\tvar j = that.mask.charAt(i);\r\n\t\t\t\tif (/[\\/-]/.test(j)) { val += j; continue; }\r\n\t\t\t\tval += n < v.length ? v.charAt(n) : j;\r\n\t\t\t\tn++;\r\n\t\t\t}\r\n\t\t\treturn val;\r\n\t\t};\r\n\r\n\t\t// feature detection for HTML5 date inputs\r\n\t\tvar _testDateSupport = function () {\r\n\t\t\tif (!that.html5) { return false; }\r\n\t\t\ttry {\r\n\t\t\t\t// test support for date inputs\r\n\t\t\t\tvar input = document.createElement('INPUT');\r\n\t\t\t\tinput.type = 'date';\r\n\t\t\t\treturn input.type === 'date';\r\n\t\t\t} catch (e) {\r\n\t\t\t\t// date inputs not supported\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t};\r\n\r\n\t\t// get date format based on mobile, HTML5 enabled, and date input support\r\n\t\tvar _getFormat = function () {\r\n\t\t\treturn that.html5 && _isMobile && _dateSupported ? 'yyyy-MM-dd' : that.format;\r\n\t\t};\r\n\r\n\t\t// set input type based on HTML5 enabled and date input support\r\n\t\tvar _previousType = null;\r\n\t\tvar _setInputType = function () {\r\n\t\t\tif (_dateSupported === null) { _dateSupported = _testDateSupport(); }\r\n\t\t\tvar t = that.dateValue();\r\n\t\t\tif (_isMobile && _dateSupported) {\r\n\t\t\t\tif (el.prop('type') !== 'date') {\r\n\t\t\t\t\tif (_previousType !== null) {\r\n\t\t\t\t\t\t_previousType = el.prop('type');\r\n\t\t\t\t\t}\r\n\t\t\t\t\tthat.changeFormat();\r\n\t\t\t\t} else {\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// set type when in mobile mode if date input supported\r\n\t\t\t\tif (el.slxVal().length) {\r\n\t\t\t\t\tel.slxVal(t.toString(_getFormat()));\r\n\t\t\t\t}\r\n\t\t\t\tel.prop('type', 'date');\r\n\t\t\t\t_showHideButton(false);\r\n\t\t\t\tel.trigger('viewport.slx.dateselector');\r\n\t\t\t} else {\r\n\t\t\t\tif (el.prop('type') === 'date') {\r\n\t\t\t\t\tthat.changeFormat();\r\n\t\t\t\t} else {\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t\t// maintain default input type\r\n\t\t\t\tel.prop('type', _previousType || 'text');\r\n\t\t\t\tif (el.slxVal().length) { el.slxVal(t.toString(_getFormat())); }\r\n\t\t\t\t_showHideButton(true);\r\n\t\t\t\tel.trigger('viewport.slx.dateselector');\r\n\t\t\t}\r\n\t\t};\r\n\r\n\t\tvar _formatParts;\r\n\t\t/** Change format of date value required for input type change. */\r\n\t\t(that.changeFormat = function() {\r\n\t\t\tvar format = _getFormat();\r\n\r\n\t\t\t// get a part of the format\r\n\t\t\tvar getPart = function(i, part) {\r\n\t\t\t\treturn {\r\n\t\t\t\t\tindex: i,\r\n\t\t\t\t\tlength: part.length,\r\n\t\t\t\t\tvalue: part\r\n\t\t\t\t};\r\n\t\t\t};\r\n\r\n\t\t\t// get a delimeter object\r\n\t\t\tvar getDelimeter = function(i, c) {\r\n\t\t\t\treturn {\r\n\t\t\t\t\tindex: i,\r\n\t\t\t\t\tchar: c\r\n\t\t\t\t};\r\n\t\t\t};\r\n\r\n\t\t\t// get each format part\r\n\t\t\tvar year, month, day;\r\n\t\t\tvar formatParts = format.split(/[\\/-]/);\r\n\t\t\tfor (var i = 0; i < formatParts.length; i++) {\r\n\t\t\t\tvar p = formatParts[i];\r\n\t\t\t\tif (p.indexOf('M') > -1) {\r\n\t\t\t\t\tmonth = getPart(i, p);\r\n\t\t\t\t} else if (p.indexOf('d') > -1) {\r\n\t\t\t\t\tday = getPart(i, p);\r\n\t\t\t\t} else if (p.indexOf('y') > -1) {\r\n\t\t\t\t\tyear = getPart(i, p);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// get delimeters\r\n\t\t\tvar delimeters = [];\r\n\t\t\tfor (var i = 0; i < format.length; i++) {\r\n\t\t\t\tvar c = format[i];\r\n\t\t\t\tvar isDelimeter = /[\\/-]/.test(c);\r\n\t\t\t\tif (isDelimeter) {\r\n\t\t\t\t\tdelimeters.push(getDelimeter(i, c));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// set internal variable\r\n\t\t\t_formatParts = {\r\n\t\t\t\tmonth: month,\r\n\t\t\t\tday: day,\r\n\t\t\t\tyear: year,\r\n\t\t\t\tdelimeters: delimeters,\r\n\t\t\t\tlength: formatParts.length\r\n\t\t\t};\r\n\t\t})();\r\n\r\n\t\t// validate date value and format\r\n\t\tvar _validate = function (val, format) {\r\n\t\t\t// check for valid value\r\n\t\t\tif (!val || typeof val !== 'string') {\r\n\t\t\t\treturn val;\r\n\t\t\t}\r\n\r\n\t\t\t// split date parts by delimeters\r\n\t\t\tvar parts = val.split(/[\\/-]/);\r\n\t\t\tif (parts.length < _formatParts.length && val || val.length < that.format.length) {\r\n\t\t\t\treturn '';\r\n\t\t\t} else if (val.length) {\r\n\t\t\t\t// date parts\r\n\t\t\t\tvar yy = _formatParts.year ? parts[_formatParts.year.index] : null;\r\n\t\t\t\tvar mm = _formatParts.month ? parts[_formatParts.month.index] : null;\r\n\t\t\t\tvar dd = _formatParts.day ? parts[_formatParts.day.index] : null;\r\n\r\n\t\t\t\t// validate date parts\r\n\t\t\t\tvar m = mm ? parseInt(mm, 10) : 0;\r\n\t\t\t\tvar d = dd ? parseInt(dd, 10) : 1;\r\n\t\t\t\tvar y = yy ? parseInt(yy, 10) : 0;\r\n\t\t\t\tm = (m === 0 ? 1 : m > 12 ? 12 : m) - 1;\r\n\t\t\t\tvar days = new Date(y, m + 1, 0).getDate();\r\n\t\t\t\td = d === 0 ? 1 : d > days ? days : d;\r\n\t\t\t\treturn new Date(y, m, d).toString(format || _getFormat());\r\n\t\t\t}\r\n\t\t};\r\n\r\n\t\t// show hide calendar button\r\n\t\tvar _showHideButton = function (val) {\r\n\t\t\tvar grp = el.closest('.input-group, .slx-input-group, .input-group-readonly, .slx-input-group-readonly');\r\n\t\t\tvar hasGrpChildren = grp.find('.input-group-addon, .input-group-prepend').length > 0;\r\n\t\t\tif (val === true && that.showIcon !== false && !that.readonly) {\r\n\t\t\t\t// show calendar button if enabled and editable\r\n\t\t\t\t_btn.parent().show();\r\n\t\t\t\tif (!hasGrpChildren && grp.hasClass('input-group-readonly')) {\r\n\t\t\t\t\tgrp.removeClass('input-group-readonly').addClass('input-group');\r\n\t\t\t\t} else if (grp.hasClass('slx-input-group-readonly')) {\r\n\t\t\t\t\tgrp.removeClass('slx-input-group-readonly').addClass('slx-input-group');\r\n\t\t\t\t}\r\n\t\t\t} else if (val === false) {\r\n\t\t\t\t// hide calendar button\r\n\t\t\t\t_btn.parent().hide();\r\n\t\t\t\tif (!hasGrpChildren && grp.hasClass('input-group')) {\r\n\t\t\t\t\tgrp.removeClass('input-group').addClass('input-group-readonly');\r\n\t\t\t\t} else if (grp.hasClass('slx-input-group')) {\r\n\t\t\t\t\tgrp.removeClass('slx-input-group').addClass('slx-input-group-readonly');\r\n\t\t\t\t}\r\n\t\t\t} else if (val === void 0 && (that.showIcon === false || that.readonly)) {\r\n\t\t\t\t// hide calendar button\r\n\t\t\t\t_showHideButton(false);\r\n\t\t\t}\r\n\t\t};\r\n\t\t\r\n\t\t/**\r\n\t\t * Gets or sets the date value.\r\n\t\t * @param {string} [v] Optional value to set.\r\n\t\t * @param {boolean} [triggerChange=true] Trigger change event flag.\r\n\t\t * @returns The element value.\r\n\t\t */\r\n\t\tthat.value = function (v, triggerChange) {\r\n\t\t\tif (v === void 0) {\r\n\t\t\t\t// get value\r\n\t\t\t\treturn el.slxVal().replace(/[^\\d\\/-]/g, '');\r\n\t\t\t} else {\r\n\t\t\t\t// set value\r\n\t\t\t\tvar old = el.slxVal();\r\n\t\t\t\tvar ischange = v !== old;\r\n\t\t\t\tvar h = el.slxVal(v);\r\n\t\t\t\t// register previous and current values on change\r\n\t\t\t\tif (ischange) {\r\n\t\t\t\t\tel.data('original-val', that.toDateString(old));\r\n\t\t\t\t\tel.data('current-val', that.toDateString(v));\r\n\t\t\t\t\t// trigger change event if not being bypassed\r\n\t\t\t\t\tif (triggerChange !== false) {\r\n\t\t\t\t\t\tel.trigger('change');\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse that.close();\r\n\t\t\t\treturn h;\r\n\t\t\t}\r\n\t\t};\r\n\r\n\t\t/**\r\n\t\t * Gets or sets the display text.\r\n\t\t * @param {string} [v] The text value to set. \r\n\t\t * @returns The display text.\r\n\t\t */\r\n\t\tthat.text = function (v) {\r\n\t\t\tif (v === void 0) {\r\n\t\t\t\t// get text\r\n\t\t\t\treturn el.slxVal();\r\n\t\t\t} else {\r\n\t\t\t\t// set text\r\n\t\t\t\tif (v === '') { that.value(''); }\r\n\t\t\t\treturn el.slxVal(v);\r\n\t\t\t}\r\n\t\t};\r\n\r\n\t\t/**\r\n\t\t * Gets or sets the value as a {@link Date}.\r\n\t\t * @param {Date} [v] The optional date to set.\r\n\t\t * @returns The date.\r\n\t\t */\r\n\t\tthat.dateValue = function (v) {\r\n\t\t\t// get date value\r\n\t\t\tif (v === void 0) {\r\n\t\t\t\tvar parts = el.slxVal().split(/[\\D]/);\r\n\t\t\t\tvar m = _formatParts.month ? parts[_formatParts.month.index] : '01';\r\n\t\t\t\tvar d = _formatParts.day ? parts[_formatParts.day.index] : '01';\r\n\t\t\t\tvar y = _formatParts.year ? parts[_formatParts.year.index] : '0000';\r\n\r\n\t\t\t\treturn new Date(y, m - 1, d);\r\n\t\t\t}\r\n\t\t\t// set date value\r\n\t\t\telse if (v instanceof Date) { that.value(v.toString(that.format)); }\r\n\t\t};\r\n\r\n\t\t/**\r\n\t\t * Converts the date into a string of the current format.\r\n\t\t * @param {string} [v] The optional date format.\r\n\t\t * @returns The formatted date string.\r\n\t\t */\r\n\t\tthat.toDateString = function (v) {\r\n\t\t\tvar val = v || el.slxVal();\r\n\t\t\t// first validate value\r\n\t\t\tvar dt = _validate(val, that.format);\r\n\t\t\tif (typeof dt === 'string') {\r\n\t\t\t\t// return if already string\r\n\t\t\t\treturn dt;\r\n\t\t\t} else if (dt instanceof Date) {\r\n\t\t\t\t// convert Date to string by specified format\r\n\t\t\t\treturn dt.toString(that.format);\r\n\t\t\t}\r\n\t\t};\r\n\r\n\t\t/**\r\n\t\t * Gets the current format based on the viewport mode.\r\n\t\t * @returns The current format for the control.\r\n\t\t */\r\n\t\tthat.currentFormat = function () {\r\n\t\t\treturn _getFormat();\r\n\t\t}\r\n\r\n\t\tvar _blurTimeout = null;\r\n\t\t/** Close calendar window. */\r\n\t\tthat.close = function () {\r\n\t\t\tif (that.html5 && _isMobile && _dateSupported) { return; }\r\n\t\t\tthat.container.removeClass('open');\r\n\t\t\t// trigger closed event and remove click listener\r\n\t\t\tel.off('click.slx').trigger('closed');\r\n\t\t\tx(document).off('click.slx');\r\n\t\t\tif (_blurTimeout !== null) { clearTimeout(_blurTimeout); }\r\n\t\t\tif (that.calendar !== null) { that.calendar._element.remove(); that.calendar = null; }\r\n\t\t};\r\n\t\t/** Open calendar window. */\r\n\t\tthat.open = function () {\r\n\t\t\tif (that.html5 && _isMobile && _dateSupported) { return; }\r\n\r\n\t\t\t// close any already open window\r\n\t\t\tx('.slx-dateselector-calendar').each(function () {\r\n\t\t\t\tvar c = x(this).slxControl('slxCalendar');\r\n\t\t\t\tif (c._parent && (that.calendar === null || that.calendar.prop('id') !== x(this).prop('id'))) { c._parent.close(); }\r\n\t\t\t});\r\n\t\t\t// open current control's window w/ current control's set date value\r\n\t\t\tthat.container.addClass('open');\r\n\t\t\tvar d = that.dateValue().isValid() ? that.value() : null;\r\n\t\t\t// build calendar\r\n\t\t\tthat.calendar = new x.slx.calendar(x('
').data('value', d), { id: that.id + '_calendar', _parent: that, view: that.view, lang: that.lang, format: that.format }).slxControl('slxCalendar');\r\n\t\t\tthat.calendar._element.addClass('slx-dateselector-calendar');\r\n\t\t\tthat.container.append(that.calendar._element);\r\n\t\t\t// adjust width\r\n\t\t\tvar t = that.calendar._element.find('table.slx-calendar-calendar');\r\n\t\t\tif (t.length) {\r\n\t\t\t\tthat.calendar._element.width(t.outerWidth() + 20);\r\n\t\t\t}\r\n\t\t\t// add events for returning value from the calendar back to dateSelector\r\n\t\t\tthat.calendar._element.on('click.slx.dateselector', function (event) { x(this).stopBubble(event); });\r\n\t\t\tthat.calendar._element.on('keyup.slx.dateselector', function (event) {\r\n\t\t\t\tvar k = event.keyCode || event.which;\r\n\t\t\t\tif (k === 27) { that.close(); }\r\n\t\t\t});\r\n\t\t\tthat.calendar._element.on('change', function (event) {\r\n\t\t\t\tthat.value(that.calendar.value());\r\n\t\t\t\tel.focus();\r\n\t\t\t});\r\n\t\t\tx(document).off('click.slx.dateselector').on('click.slx.dateselector', function () { that.close(); });\r\n\t\t\tel.off('click.slx.dateselector').on('click.slx.dateselector', function (event) { x(this).stopBubble(event); }).trigger('open');\r\n\t\t};\r\n\r\n\t\t/* initialization */\r\n\t\t(function () {\r\n\t\t\t_isMobile = global.innerWidth <= that.mobileMaxWidth;\r\n\t\t\tel.attr({ onchange: void 0, maxlength: 10, placeholder: that.mask, 'data-current-val': el.val() });\r\n\r\n\t\t\t// wrap in container div, if not already wrapped\r\n\t\t\tif (!that.container.length) {\r\n\t\t\t\tvar grp = el.closest('.input-group, .slx-input-group');\r\n\t\t\t\tif (!grp.length) {\r\n\t\t\t\t\tgrp = x('
');\r\n\t\t\t\t\t// add class by bootstrap version\r\n\t\t\t\t\tif (x.slx.config.bootstrap) {\r\n\t\t\t\t\t\tgrp.addClass('input-group');\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tgrp.addClass('slx-input-group');\r\n\t\t\t\t\t}\r\n\t\t\t\t\tel.wrap(grp);\r\n\t\t\t\t\tgrp = el.parent();\r\n\t\t\t\t}\r\n\t\t\t\tgrp.wrap(x('
').attr('id', that.id + '_container').attr('data-target', that.selector).addClass('slx-dateselector-container'));\r\n\t\t\t\tthat.container = grp.parent();\r\n\t\t\t}\r\n\r\n\t\t\t// add control CSS class and original value, if not already applied\r\n\t\t\tif (!el.hasClass('slx-dateselector')) { el.addClass('slx-dateselector'); }\r\n\t\t\tif (!el.data('original-val')) el.data('original-val', that.toDateString(el.slxVal()));\r\n\r\n\t\t\t// add calendar button, if not already added\r\n\t\t\tif (_btn.length === 0) {\r\n\t\t\t\tvar grpBtn = x('
');\r\n\t\t\t\t// add class to button group by bootstrap version\r\n\t\t\t\tif (x.slx.config.bootstrap === 3) {\r\n\t\t\t\t\tgrpBtn.addClass('input-group-btn');\r\n\t\t\t\t} else if (x.slx.config.bootstrap === 4) {\r\n\t\t\t\t\tgrpBtn.addClass('input-group-append');\r\n\t\t\t\t} else {\r\n\t\t\t\t\tgrpBtn.addClass('slx-input-group-append');\r\n\t\t\t\t}\r\n\t\t\t\t// add button\r\n\t\t\t\tel.after(grpBtn.append(_btn = x('