vendor/assets/javascripts/bootstrap-datetimepicker.js in bootstrap3-datetimepicker-rails-4.7.14 vs vendor/assets/javascripts/bootstrap-datetimepicker.js in bootstrap3-datetimepicker-rails-4.14.30

- old
+ new

@@ -1,6 +1,6 @@ -/*! version : 4.7.14 +/*! version : 4.14.30 ========================================================= bootstrap-datetimejs https://github.com/Eonasdan/bootstrap-datetimepicker Copyright (c) 2015 Jonathan Peterson ========================================================= @@ -82,13 +82,18 @@ }, { clsName: 'years', navFnc: 'y', navStep: 10 + }, + { + clsName: 'decades', + navFnc: 'y', + navStep: 100 } ], - viewModes = ['days', 'months', 'years'], + viewModes = ['days', 'months', 'years', 'decades'], verticalModes = ['top', 'bottom', 'auto'], horizontalModes = ['left', 'right', 'auto'], toolbarPlacements = ['default', 'top', 'bottom'], keyMap = { 'up': 38, @@ -147,11 +152,10 @@ return actualFormat.indexOf('s') !== -1; default: return false; } }, - hasTime = function () { return (isEnabled('h') || isEnabled('m') || isEnabled('s')); }, hasDate = function () { @@ -187,10 +191,15 @@ ), $('<div>').addClass('datepicker-years') .append($('<table>').addClass('table-condensed') .append(headTemplate.clone()) .append(contTemplate.clone()) + ), + $('<div>').addClass('datepicker-decades') + .append($('<table>').addClass('table-condensed') + .append(headTemplate.clone()) + .append(contTemplate.clone()) ) ]; }, getTimePickerMainTemplate = function () { @@ -198,53 +207,53 @@ middleRow = $('<tr>'), bottomRow = $('<tr>'); if (isEnabled('h')) { topRow.append($('<td>') - .append($('<a>').attr({href: '#', tabindex: '-1'}).addClass('btn').attr('data-action', 'incrementHours') + .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Increment Hour'}).addClass('btn').attr('data-action', 'incrementHours') .append($('<span>').addClass(options.icons.up)))); middleRow.append($('<td>') - .append($('<span>').addClass('timepicker-hour').attr('data-time-component', 'hours').attr('data-action', 'showHours'))); + .append($('<span>').addClass('timepicker-hour').attr({'data-time-component':'hours', 'title':'Pick Hour'}).attr('data-action', 'showHours'))); bottomRow.append($('<td>') - .append($('<a>').attr({href: '#', tabindex: '-1'}).addClass('btn').attr('data-action', 'decrementHours') + .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Decrement Hour'}).addClass('btn').attr('data-action', 'decrementHours') .append($('<span>').addClass(options.icons.down)))); } if (isEnabled('m')) { if (isEnabled('h')) { topRow.append($('<td>').addClass('separator')); middleRow.append($('<td>').addClass('separator').html(':')); bottomRow.append($('<td>').addClass('separator')); } topRow.append($('<td>') - .append($('<a>').attr({href: '#', tabindex: '-1'}).addClass('btn').attr('data-action', 'incrementMinutes') + .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Increment Minute'}).addClass('btn').attr('data-action', 'incrementMinutes') .append($('<span>').addClass(options.icons.up)))); middleRow.append($('<td>') - .append($('<span>').addClass('timepicker-minute').attr('data-time-component', 'minutes').attr('data-action', 'showMinutes'))); + .append($('<span>').addClass('timepicker-minute').attr({'data-time-component': 'minutes', 'title':'Pick Minute'}).attr('data-action', 'showMinutes'))); bottomRow.append($('<td>') - .append($('<a>').attr({href: '#', tabindex: '-1'}).addClass('btn').attr('data-action', 'decrementMinutes') + .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Decrement Minute'}).addClass('btn').attr('data-action', 'decrementMinutes') .append($('<span>').addClass(options.icons.down)))); } if (isEnabled('s')) { if (isEnabled('m')) { topRow.append($('<td>').addClass('separator')); middleRow.append($('<td>').addClass('separator').html(':')); bottomRow.append($('<td>').addClass('separator')); } topRow.append($('<td>') - .append($('<a>').attr({href: '#', tabindex: '-1'}).addClass('btn').attr('data-action', 'incrementSeconds') + .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Increment Second'}).addClass('btn').attr('data-action', 'incrementSeconds') .append($('<span>').addClass(options.icons.up)))); middleRow.append($('<td>') - .append($('<span>').addClass('timepicker-second').attr('data-time-component', 'seconds').attr('data-action', 'showSeconds'))); + .append($('<span>').addClass('timepicker-second').attr({'data-time-component': 'seconds', 'title':'Pick Second'}).attr('data-action', 'showSeconds'))); bottomRow.append($('<td>') - .append($('<a>').attr({href: '#', tabindex: '-1'}).addClass('btn').attr('data-action', 'decrementSeconds') + .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Decrement Second'}).addClass('btn').attr('data-action', 'decrementSeconds') .append($('<span>').addClass(options.icons.down)))); } if (!use24Hours) { topRow.append($('<td>').addClass('separator')); middleRow.append($('<td>') - .append($('<button>').addClass('btn btn-primary').attr('data-action', 'togglePeriod'))); + .append($('<button>').addClass('btn btn-primary').attr({'data-action': 'togglePeriod', tabindex: '-1', 'title':'Toggle Period'}))); bottomRow.append($('<td>').addClass('separator')); } return $('<div>').addClass('timepicker-picker') .append($('<table>').addClass('table-condensed') @@ -274,20 +283,20 @@ }, getToolbar = function () { var row = []; if (options.showTodayButton) { - row.push($('<td>').append($('<a>').attr('data-action', 'today').append($('<span>').addClass(options.icons.today)))); + row.push($('<td>').append($('<a>').attr({'data-action':'today', 'title':'Go to today'}).append($('<span>').addClass(options.icons.today)))); } if (!options.sideBySide && hasDate() && hasTime()) { - row.push($('<td>').append($('<a>').attr('data-action', 'togglePicker').append($('<span>').addClass(options.icons.time)))); + row.push($('<td>').append($('<a>').attr({'data-action':'togglePicker', 'title':'Select Time'}).append($('<span>').addClass(options.icons.time)))); } if (options.showClear) { - row.push($('<td>').append($('<a>').attr('data-action', 'clear').append($('<span>').addClass(options.icons.clear)))); + row.push($('<td>').append($('<a>').attr({'data-action':'clear', 'title':'Clear selection'}).append($('<span>').addClass(options.icons.clear)))); } if (options.showClose) { - row.push($('<td>').append($('<a>').attr('data-action', 'close').append($('<span>').addClass(options.icons.close)))); + row.push($('<td>').append($('<a>').attr({'data-action':'close', 'title':'Close the picker'}).append($('<span>').addClass(options.icons.close)))); } return $('<table>').addClass('table-condensed').append($('<tbody>').append($('<tr>').append(row))); }, getTemplate = function () { @@ -302,10 +311,13 @@ } if (use24Hours) { template.addClass('usetwentyfour'); } + if (isEnabled('s') && !use24Hours) { + template.addClass('wider'); + } if (options.sideBySide && hasDate() && hasTime()) { template.addClass('timepicker-sbs'); template.append( $('<div>').addClass('row') .append(dateView.addClass('col-sm-6')) @@ -364,11 +376,11 @@ parent; if (options.widgetParent) { parent = options.widgetParent.append(widget); } else if (element.is('input')) { - parent = element.parent().append(widget); + parent = element.after(widget).parent(); } else if (options.inline) { parent = element.append(widget); return; } else { parent = element; @@ -419,35 +431,46 @@ } widget.css({ top: vertical === 'top' ? 'auto' : position.top + element.outerHeight(), bottom: vertical === 'top' ? position.top + element.outerHeight() : 'auto', - left: horizontal === 'left' ? parent.css('padding-left') : 'auto', - right: horizontal === 'left' ? 'auto' : parent.width() - element.outerWidth() + left: horizontal === 'left' ? (parent === element ? 0 : position.left) : 'auto', + right: horizontal === 'left' ? 'auto' : parent.outerWidth() - element.outerWidth() - (parent === element ? 0 : position.left) }); }, notifyEvent = function (e) { if (e.type === 'dp.change' && ((e.date && e.date.isSame(e.oldDate)) || (!e.date && !e.oldDate))) { return; } element.trigger(e); }, + viewUpdate = function (e) { + if (e === 'y') { + e = 'YYYY'; + } + notifyEvent({ + type: 'dp.update', + change: e, + viewDate: viewDate.clone() + }); + }, + showMode = function (dir) { if (!widget) { return; } if (dir) { - currentViewMode = Math.max(minViewModeNumber, Math.min(2, currentViewMode + dir)); + currentViewMode = Math.max(minViewModeNumber, Math.min(3, currentViewMode + dir)); } widget.find('.datepicker > div').hide().filter('.datepicker-' + datePickerModes[currentViewMode].clsName).show(); }, fillDow = function () { var row = $('<tr>'), - currentDate = viewDate.clone().startOf('w'); + currentDate = viewDate.clone().startOf('w').startOf('d'); if (options.calendarWeeks === true) { row.append($('<th>').addClass('cw').text('#')); } @@ -464,35 +487,61 @@ isInEnabledDates = function (testDate) { return options.enabledDates[testDate.format('YYYY-MM-DD')] === true; }, + isInDisabledHours = function (testDate) { + return options.disabledHours[testDate.format('H')] === true; + }, + + isInEnabledHours = function (testDate) { + return options.enabledHours[testDate.format('H')] === true; + }, + isValid = function (targetMoment, granularity) { if (!targetMoment.isValid()) { return false; } - if (options.disabledDates && isInDisabledDates(targetMoment) && granularity !== 'M') { + if (options.disabledDates && granularity === 'd' && isInDisabledDates(targetMoment)) { return false; } - if (options.enabledDates && !isInEnabledDates(targetMoment) && granularity !== 'M') { + if (options.enabledDates && granularity === 'd' && !isInEnabledDates(targetMoment)) { return false; } if (options.minDate && targetMoment.isBefore(options.minDate, granularity)) { return false; } if (options.maxDate && targetMoment.isAfter(options.maxDate, granularity)) { return false; } - if (granularity === 'd' && options.daysOfWeekDisabled.indexOf(targetMoment.day()) !== -1) { //widget && widget.find('.datepicker-days').length > 0 + if (options.daysOfWeekDisabled && granularity === 'd' && options.daysOfWeekDisabled.indexOf(targetMoment.day()) !== -1) { return false; } + if (options.disabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && isInDisabledHours(targetMoment)) { + return false; + } + if (options.enabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && !isInEnabledHours(targetMoment)) { + return false; + } + if (options.disabledTimeIntervals && (granularity === 'h' || granularity === 'm' || granularity === 's')) { + var found = false; + $.each(options.disabledTimeIntervals, function () { + if (targetMoment.isBetween(this[0], this[1])) { + found = true; + return false; + } + }); + if (found) { + return false; + } + } return true; }, fillMonths = function () { var spans = [], - monthsShort = viewDate.clone().startOf('y').hour(12); // hour is changed to avoid DST issues in some browsers + monthsShort = viewDate.clone().startOf('y').startOf('d'); while (monthsShort.isSame(viewDate, 'y')) { spans.push($('<span>').attr('data-action', 'selectMonth').addClass('month').text(monthsShort.format('MMM'))); monthsShort.add(1, 'M'); } widget.find('.datepicker-months td').empty().append(spans); @@ -501,10 +550,14 @@ updateMonths = function () { var monthsView = widget.find('.datepicker-months'), monthsViewHeader = monthsView.find('th'), months = monthsView.find('tbody').find('span'); + monthsViewHeader.eq(0).find('span').attr('title', 'Previous Year'); + monthsViewHeader.eq(1).attr('title', 'Select Year'); + monthsViewHeader.eq(2).find('span').attr('title', 'Next Year'); + monthsView.find('.disabled').removeClass('disabled'); if (!isValid(viewDate.clone().subtract(1, 'y'), 'y')) { monthsViewHeader.eq(0).addClass('disabled'); } @@ -514,11 +567,11 @@ if (!isValid(viewDate.clone().add(1, 'y'), 'y')) { monthsViewHeader.eq(2).addClass('disabled'); } months.removeClass('active'); - if (date.isSame(viewDate, 'y')) { + if (date.isSame(viewDate, 'y') && !unset) { months.eq(date.month()).addClass('active'); } months.each(function (index) { if (!isValid(viewDate.clone().month(index), 'M')) { @@ -532,10 +585,14 @@ yearsViewHeader = yearsView.find('th'), startYear = viewDate.clone().subtract(5, 'y'), endYear = viewDate.clone().add(6, 'y'), html = ''; + yearsViewHeader.eq(0).find('span').attr('title', 'Previous Decade'); + yearsViewHeader.eq(1).attr('title', 'Select Decade'); + yearsViewHeader.eq(2).find('span').attr('title', 'Next Decade'); + yearsView.find('.disabled').removeClass('disabled'); if (options.minDate && options.minDate.isAfter(startYear, 'y')) { yearsViewHeader.eq(0).addClass('disabled'); } @@ -545,42 +602,79 @@ if (options.maxDate && options.maxDate.isBefore(endYear, 'y')) { yearsViewHeader.eq(2).addClass('disabled'); } while (!startYear.isAfter(endYear, 'y')) { - html += '<span data-action="selectYear" class="year' + (startYear.isSame(date, 'y') ? ' active' : '') + (!isValid(startYear, 'y') ? ' disabled' : '') + '">' + startYear.year() + '</span>'; + html += '<span data-action="selectYear" class="year' + (startYear.isSame(date, 'y') && !unset ? ' active' : '') + (!isValid(startYear, 'y') ? ' disabled' : '') + '">' + startYear.year() + '</span>'; startYear.add(1, 'y'); } yearsView.find('td').html(html); }, + updateDecades = function () { + var decadesView = widget.find('.datepicker-decades'), + decadesViewHeader = decadesView.find('th'), + startDecade = viewDate.isBefore(moment({y: 1999})) ? moment({y: 1899}) : moment({y: 1999}), + endDecade = startDecade.clone().add(100, 'y'), + html = ''; + + decadesViewHeader.eq(0).find('span').attr('title', 'Previous Century'); + decadesViewHeader.eq(2).find('span').attr('title', 'Next Century'); + + decadesView.find('.disabled').removeClass('disabled'); + + if (startDecade.isSame(moment({y: 1900})) || (options.minDate && options.minDate.isAfter(startDecade, 'y'))) { + decadesViewHeader.eq(0).addClass('disabled'); + } + + decadesViewHeader.eq(1).text(startDecade.year() + '-' + endDecade.year()); + + if (startDecade.isSame(moment({y: 2000})) || (options.maxDate && options.maxDate.isBefore(endDecade, 'y'))) { + decadesViewHeader.eq(2).addClass('disabled'); + } + + while (!startDecade.isAfter(endDecade, 'y')) { + html += '<span data-action="selectDecade" class="decade' + (startDecade.isSame(date, 'y') ? ' active' : '') + + (!isValid(startDecade, 'y') ? ' disabled' : '') + '" data-selection="' + (startDecade.year() + 6) + '">' + (startDecade.year() + 1) + ' - ' + (startDecade.year() + 12) + '</span>'; + startDecade.add(12, 'y'); + } + html += '<span></span><span></span><span></span>'; //push the dangling block over, at least this way it's even + + decadesView.find('td').html(html); + }, + fillDate = function () { var daysView = widget.find('.datepicker-days'), daysViewHeader = daysView.find('th'), currentDate, html = [], row, - clsName; + clsName, + i; if (!hasDate()) { return; } + daysViewHeader.eq(0).find('span').attr('title', 'Previous Month'); + daysViewHeader.eq(1).attr('title', 'Select Month'); + daysViewHeader.eq(2).find('span').attr('title', 'Next Month'); + daysView.find('.disabled').removeClass('disabled'); daysViewHeader.eq(1).text(viewDate.format(options.dayViewHeaderFormat)); if (!isValid(viewDate.clone().subtract(1, 'M'), 'M')) { daysViewHeader.eq(0).addClass('disabled'); } if (!isValid(viewDate.clone().add(1, 'M'), 'M')) { daysViewHeader.eq(2).addClass('disabled'); } - currentDate = viewDate.clone().startOf('M').startOf('week'); + currentDate = viewDate.clone().startOf('M').startOf('w').startOf('d'); - while (!viewDate.clone().endOf('M').endOf('w').isBefore(currentDate, 'd')) { + for (i = 0; i < 42; i++) { //always display 42 days (should show 6 weeks) if (currentDate.weekday() === 0) { row = $('<tr>'); if (options.calendarWeeks) { row.append('<td class="cw">' + currentDate.week() + '</td>'); } @@ -603,19 +697,21 @@ clsName += ' today'; } if (currentDate.day() === 0 || currentDate.day() === 6) { clsName += ' weekend'; } - row.append('<td data-action="selectDay" class="day' + clsName + '">' + currentDate.date() + '</td>'); + row.append('<td data-action="selectDay" data-day="' + currentDate.format('L') + '" class="day' + clsName + '">' + currentDate.date() + '</td>'); currentDate.add(1, 'd'); } daysView.find('tbody').empty().append(html); updateMonths(); updateYears(); + + updateDecades(); }, fillHours = function () { var table = widget.find('.timepicker-hours table'), currentHour = viewDate.clone().startOf('d'), @@ -671,13 +767,23 @@ table.empty().append(html); }, fillTime = function () { - var timeComponents = widget.find('.timepicker span[data-time-component]'); + var toggle, newDate, timeComponents = widget.find('.timepicker span[data-time-component]'); + if (!use24Hours) { - widget.find('.timepicker [data-action=togglePeriod]').text(date.format('A')); + toggle = widget.find('.timepicker [data-action=togglePeriod]'); + newDate = date.clone().add((date.hours() >= 12) ? -12 : 12, 'h'); + + toggle.text(date.format('A')); + + if (isValid(newDate, 'h')) { + toggle.removeClass('disabled'); + } else { + toggle.addClass('disabled'); + } } timeComponents.filter('[data-time-component=hours]').text(date.format(use24Hours ? 'HH' : 'hh')); timeComponents.filter('[data-time-component=minutes]').text(date.format('mm')); timeComponents.filter('[data-time-component=seconds]').text(date.format('ss')); @@ -702,11 +808,11 @@ unset = true; input.val(''); element.data('date', ''); notifyEvent({ type: 'dp.change', - date: null, + date: false, oldDate: oldDate }); update(); return; } @@ -720,12 +826,12 @@ if (isValid(targetMoment)) { date = targetMoment; viewDate = date.clone(); input.val(date.format(actualFormat)); element.data('date', date.format(actualFormat)); - update(); unset = false; + update(); notifyEvent({ type: 'dp.change', date: date.clone(), oldDate: oldDate }); @@ -739,10 +845,11 @@ }); } }, hide = function () { + ///<summary>Hides the widget. Possibly will emit dp.hide</summary> var transitioning = false; if (!widget) { return picker; } // Ignore event if in the middle of a picker transition @@ -785,17 +892,21 @@ * Widget UI interaction functions * ********************************************************************************/ actions = { next: function () { - viewDate.add(datePickerModes[currentViewMode].navStep, datePickerModes[currentViewMode].navFnc); + var navFnc = datePickerModes[currentViewMode].navFnc; + viewDate.add(datePickerModes[currentViewMode].navStep, navFnc); fillDate(); + viewUpdate(navFnc); }, previous: function () { - viewDate.subtract(datePickerModes[currentViewMode].navStep, datePickerModes[currentViewMode].navFnc); + var navFnc = datePickerModes[currentViewMode].navFnc; + viewDate.subtract(datePickerModes[currentViewMode].navStep, navFnc); fillDate(); + viewUpdate(navFnc); }, pickerSwitch: function () { showMode(1); }, @@ -810,10 +921,11 @@ } } else { showMode(-1); fillDate(); } + viewUpdate('M'); }, selectYear: function (e) { var year = parseInt($(e.target).text(), 10) || 0; viewDate.year(year); @@ -824,12 +936,28 @@ } } else { showMode(-1); fillDate(); } + viewUpdate('YYYY'); }, + selectDecade: function (e) { + var year = parseInt($(e.target).data('selection'), 10) || 0; + viewDate.year(year); + if (currentViewMode === minViewModeNumber) { + setValue(date.clone().year(viewDate.year())); + if (!options.inline) { + hide(); + } + } else { + showMode(-1); + fillDate(); + } + viewUpdate('YYYY'); + }, + selectDay: function (e) { var day = viewDate.clone(); if ($(e.target).is('.old')) { day.subtract(1, 'M'); } @@ -841,31 +969,49 @@ hide(); } }, incrementHours: function () { - setValue(date.clone().add(1, 'h')); + var newDate = date.clone().add(1, 'h'); + if (isValid(newDate, 'h')) { + setValue(newDate); + } }, incrementMinutes: function () { - setValue(date.clone().add(options.stepping, 'm')); + var newDate = date.clone().add(options.stepping, 'm'); + if (isValid(newDate, 'm')) { + setValue(newDate); + } }, incrementSeconds: function () { - setValue(date.clone().add(1, 's')); + var newDate = date.clone().add(1, 's'); + if (isValid(newDate, 's')) { + setValue(newDate); + } }, decrementHours: function () { - setValue(date.clone().subtract(1, 'h')); + var newDate = date.clone().subtract(1, 'h'); + if (isValid(newDate, 'h')) { + setValue(newDate); + } }, decrementMinutes: function () { - setValue(date.clone().subtract(options.stepping, 'm')); + var newDate = date.clone().subtract(options.stepping, 'm'); + if (isValid(newDate, 'm')) { + setValue(newDate); + } }, decrementSeconds: function () { - setValue(date.clone().subtract(1, 's')); + var newDate = date.clone().subtract(1, 's'); + if (isValid(newDate, 's')) { + setValue(newDate); + } }, togglePeriod: function () { setValue(date.clone().add((date.hours() >= 12) ? -12 : 12, 'h')); }, @@ -951,11 +1097,13 @@ }, clear: clear, today: function () { - setValue(moment()); + if (isValid(moment(), 'd')) { + setValue(moment()); + } }, close: hide }, @@ -966,10 +1114,11 @@ actions[$(e.currentTarget).data('action')].apply(picker, arguments); return false; }, show = function () { + ///<summary>Shows the widget. Possibly will emit dp.show and dp.change</summary> var currentMoment, useCurrentGranularity = { 'year': function (m) { return m.month(0).date(1).hours(0).seconds(0).minutes(0); }, @@ -988,11 +1137,13 @@ }; if (input.prop('disabled') || (!options.ignoreReadonly && input.prop('readonly')) || widget) { return picker; } - if (options.useCurrent && unset && ((input.is('input') && input.val().trim().length === 0) || options.inline)) { + if (input.val() !== undefined && input.val().trim().length !== 0) { + setValue(parseInputDate(input.val().trim())); + } else if (options.useCurrent && unset && ((input.is('input') && input.val().trim().length === 0) || options.inline)) { currentMoment = moment(); if (typeof options.useCurrent === 'string') { currentMoment = useCurrentGranularity[options.useCurrent](currentMoment); } setValue(currentMoment); @@ -1018,45 +1169,40 @@ component.toggleClass('active'); } widget.show(); place(); - if (!input.is(':focus')) { + if (options.focusOnShow && !input.is(':focus')) { input.focus(); } notifyEvent({ type: 'dp.show' }); return picker; }, toggle = function () { + /// <summary>Shows or hides the widget</summary> return (widget ? hide() : show()); }, parseInputDate = function (inputDate) { - if (moment.isMoment(inputDate) || inputDate instanceof Date) { - inputDate = moment(inputDate); + if (options.parseInputDate === undefined) { + if (moment.isMoment(inputDate) || inputDate instanceof Date) { + inputDate = moment(inputDate); + } else { + inputDate = moment(inputDate, parseFormats, options.useStrict); + } } else { - inputDate = moment(inputDate, parseFormats, options.useStrict); + inputDate = options.parseInputDate(inputDate); } inputDate.locale(options.locale); return inputDate; }, keydown = function (e) { - //if (e.keyCode === 27 && widget) { // allow escape to hide picker - // hide(); - // return false; - //} - //if (e.keyCode === 40 && !widget) { // allow down to show picker - // show(); - // e.preventDefault(); - //} - //return true; - var handler = null, index, index2, pressedKeys = [], pressedModifiers = {}, @@ -1119,11 +1265,12 @@ attachDatePickerElementEvents = function () { input.on({ 'change': change, 'blur': options.debug ? '' : hide, 'keydown': keydown, - 'keyup': keyup + 'keyup': keyup, + 'focus': options.allowInputToggle ? show : '' }); if (element.is('input')) { input.on({ 'focus': show @@ -1137,11 +1284,12 @@ detachDatePickerElementEvents = function () { input.off({ 'change': change, 'blur': hide, 'keydown': keydown, - 'keyup': keyup + 'keyup': keyup, + 'focus': options.allowInputToggle ? hide : '' }); if (element.is('input')) { input.off({ 'focus': show @@ -1164,10 +1312,21 @@ } }); return (Object.keys(givenDatesIndexed).length) ? givenDatesIndexed : false; }, + indexGivenHours = function (givenHoursArray) { + // Store given enabledHours and disabledHours as keys. + // This way we can check their existence in O(1) time instead of looping through whole array. + // (for example: options.enabledHours['2014-02-27'] === true) + var givenHoursIndexed = {}; + $.each(givenHoursArray, function () { + givenHoursIndexed[this] = true; + }); + return (Object.keys(givenHoursIndexed).length) ? givenHoursIndexed : false; + }, + initFormatting = function () { var format = options.format || 'L LT'; actualFormat = format.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput) { var newinput = date.localeData().longDateFormat(formatInput) || formatInput; @@ -1180,11 +1339,11 @@ parseFormats = options.extraFormats ? options.extraFormats.slice() : []; if (parseFormats.indexOf(format) < 0 && parseFormats.indexOf(actualFormat) < 0) { parseFormats.push(actualFormat); } - use24Hours = (actualFormat.toLowerCase().indexOf('a') < 1 && actualFormat.indexOf('h') < 1); + use24Hours = (actualFormat.toLowerCase().indexOf('a') < 1 && actualFormat.replace(/\[.*?\]/g, '').indexOf('h') < 1); if (isEnabled('y')) { minViewModeNumber = 2; } if (isEnabled('M')) { @@ -1210,10 +1369,11 @@ * object to the outer world. Always return a clone when returning values or make * a clone when setting a private variable. * ********************************************************************************/ picker.destroy = function () { + ///<summary>Destroys the widget and removes all attached event listeners</summary> hide(); detachDatePickerElementEvents(); element.removeData('DateTimePicker'); element.removeData('date'); }; @@ -1223,19 +1383,22 @@ picker.show = show; picker.hide = hide; picker.disable = function () { + ///<summary>Disables the input element, the component is attached to, by adding a disabled="true" attribute to it. + ///If the widget was visible before that call it is hidden. Possibly emits dp.hide</summary> hide(); if (component && component.hasClass('btn')) { component.addClass('disabled'); } input.prop('disabled', true); return picker; }; picker.enable = function () { + ///<summary>Enables the input element, the component is attached to, by removing disabled attribute from it.</summary> if (component && component.hasClass('btn')) { component.removeClass('disabled'); } input.prop('disabled', false); return picker; @@ -1270,10 +1433,18 @@ }); return picker; }; picker.date = function (newDate) { + ///<signature helpKeyword="$.fn.datetimepicker.date"> + ///<summary>Returns the component's model current date, a moment object or null if not set.</summary> + ///<returns type="Moment">date.clone()</returns> + ///</signature> + ///<signature> + ///<summary>Sets the components model current moment to it. Passing a null value unsets the components model current moment. Parsing of the newDate parameter is made using moment library with the options.format and options.useStrict components configuration.</summary> + ///<param name="newDate" locid="$.fn.datetimepicker.date_p:newDate">Takes string, Date, moment, null parameter.</param> + ///</signature> if (arguments.length === 0) { if (unset) { return null; } return date.clone(); @@ -1286,10 +1457,13 @@ setValue(newDate === null ? null : parseInputDate(newDate)); return picker; }; picker.format = function (newFormat) { + ///<summary>test su</summary> + ///<param name="newFormat">info about para</param> + ///<returns type="string|boolean">returns foo</returns> if (arguments.length === 0) { return options.format; } if ((typeof newFormat !== 'string') && ((typeof newFormat !== 'boolean') || (newFormat !== false))) { @@ -1331,10 +1505,19 @@ } return picker; }; picker.disabledDates = function (dates) { + ///<signature helpKeyword="$.fn.datetimepicker.disabledDates"> + ///<summary>Returns an array with the currently set disabled dates on the component.</summary> + ///<returns type="array">options.disabledDates</returns> + ///</signature> + ///<signature> + ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of + ///options.enabledDates if such exist.</summary> + ///<param name="dates" locid="$.fn.datetimepicker.disabledDates_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param> + ///</signature> if (arguments.length === 0) { return (options.disabledDates ? $.extend({}, options.disabledDates) : options.disabledDates); } if (!dates) { @@ -1350,10 +1533,18 @@ update(); return picker; }; picker.enabledDates = function (dates) { + ///<signature helpKeyword="$.fn.datetimepicker.enabledDates"> + ///<summary>Returns an array with the currently set enabled dates on the component.</summary> + ///<returns type="array">options.enabledDates</returns> + ///</signature> + ///<signature> + ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of options.disabledDates if such exist.</summary> + ///<param name="dates" locid="$.fn.datetimepicker.enabledDates_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param> + ///</signature> if (arguments.length === 0) { return (options.enabledDates ? $.extend({}, options.enabledDates) : options.enabledDates); } if (!dates) { @@ -1373,10 +1564,16 @@ picker.daysOfWeekDisabled = function (daysOfWeekDisabled) { if (arguments.length === 0) { return options.daysOfWeekDisabled.splice(0); } + if ((typeof daysOfWeekDisabled === 'boolean') && !daysOfWeekDisabled) { + options.daysOfWeekDisabled = false; + update(); + return picker; + } + if (!(daysOfWeekDisabled instanceof Array)) { throw new TypeError('daysOfWeekDisabled() expects an array parameter'); } options.daysOfWeekDisabled = daysOfWeekDisabled.reduce(function (previousValue, currentValue) { currentValue = parseInt(currentValue, 10); @@ -1386,10 +1583,21 @@ if (previousValue.indexOf(currentValue) === -1) { previousValue.push(currentValue); } return previousValue; }, []).sort(); + if (options.useCurrent && !options.keepInvalid) { + var tries = 0; + while (!isValid(date, 'd')) { + date.add(1, 'd'); + if (tries === 7) { + throw 'Tried 7 times to find a valid date'; + } + tries++; + } + setValue(date); + } update(); return picker; }; picker.maxDate = function (maxDate) { @@ -1416,11 +1624,11 @@ } if (options.minDate && parsedDate.isBefore(options.minDate)) { throw new TypeError('maxDate() date parameter is before options.minDate: ' + parsedDate.format(actualFormat)); } options.maxDate = parsedDate; - if (options.maxDate.isBefore(maxDate)) { + if (options.useCurrent && !options.keepInvalid && date.isAfter(maxDate)) { setValue(options.maxDate); } if (viewDate.isAfter(parsedDate)) { viewDate = parsedDate.clone(); } @@ -1452,21 +1660,29 @@ } if (options.maxDate && parsedDate.isAfter(options.maxDate)) { throw new TypeError('minDate() date parameter is after options.maxDate: ' + parsedDate.format(actualFormat)); } options.minDate = parsedDate; - if (options.minDate.isAfter(minDate)) { + if (options.useCurrent && !options.keepInvalid && date.isBefore(minDate)) { setValue(options.minDate); } if (viewDate.isBefore(parsedDate)) { viewDate = parsedDate.clone(); } update(); return picker; }; picker.defaultDate = function (defaultDate) { + ///<signature helpKeyword="$.fn.datetimepicker.defaultDate"> + ///<summary>Returns a moment with the options.defaultDate option configuration or false if not set</summary> + ///<returns type="Moment">date.clone()</returns> + ///</signature> + ///<signature> + ///<summary>Will set the picker's inital date. If a boolean:false value is passed the options.defaultDate parameter is cleared.</summary> + ///<param name="defaultDate" locid="$.fn.datetimepicker.defaultDate_p:defaultDate">Takes a string, Date, moment, boolean:false</param> + ///</signature> if (arguments.length === 0) { return options.defaultDate ? options.defaultDate.clone() : options.defaultDate; } if (!defaultDate) { options.defaultDate = false; @@ -1487,11 +1703,11 @@ throw new TypeError('defaultDate() date passed is invalid according to component setup validations'); } options.defaultDate = parsedDate; - if (options.defaultDate && input.val().trim() === '' && input.attr('placeholder') === undefined) { + if (options.defaultDate && options.inline || (input.val().trim() === '' && input.attr('placeholder') === undefined)) { setValue(options.defaultDate); } return picker; }; @@ -1762,10 +1978,23 @@ options.keepOpen = keepOpen; return picker; }; + picker.focusOnShow = function (focusOnShow) { + if (arguments.length === 0) { + return options.focusOnShow; + } + + if (typeof focusOnShow !== 'boolean') { + throw new TypeError('focusOnShow() expects a boolean parameter'); + } + + options.focusOnShow = focusOnShow; + return picker; + }; + picker.inline = function (inline) { if (arguments.length === 0) { return options.inline; } @@ -1794,10 +2023,23 @@ options.debug = debug; return picker; }; + picker.allowInputToggle = function (allowInputToggle) { + if (arguments.length === 0) { + return options.allowInputToggle; + } + + if (typeof allowInputToggle !== 'boolean') { + throw new TypeError('allowInputToggle() expects a boolean parameter'); + } + + options.allowInputToggle = allowInputToggle; + return picker; + }; + picker.showClose = function (showClose) { if (arguments.length === 0) { return options.showClose; } @@ -1832,10 +2074,155 @@ options.datepickerInput = datepickerInput; return picker; }; + picker.parseInputDate = function (parseInputDate) { + if (arguments.length === 0) { + return options.parseInputDate; + } + + if (typeof parseInputDate !== 'function') { + throw new TypeError('parseInputDate() sholud be as function'); + } + + options.parseInputDate = parseInputDate; + + return picker; + }; + + picker.disabledTimeIntervals = function (disabledTimeIntervals) { + ///<signature helpKeyword="$.fn.datetimepicker.disabledTimeIntervals"> + ///<summary>Returns an array with the currently set disabled dates on the component.</summary> + ///<returns type="array">options.disabledTimeIntervals</returns> + ///</signature> + ///<signature> + ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of + ///options.enabledDates if such exist.</summary> + ///<param name="dates" locid="$.fn.datetimepicker.disabledTimeIntervals_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param> + ///</signature> + if (arguments.length === 0) { + return (options.disabledTimeIntervals ? $.extend({}, options.disabledTimeIntervals) : options.disabledTimeIntervals); + } + + if (!disabledTimeIntervals) { + options.disabledTimeIntervals = false; + update(); + return picker; + } + if (!(disabledTimeIntervals instanceof Array)) { + throw new TypeError('disabledTimeIntervals() expects an array parameter'); + } + options.disabledTimeIntervals = disabledTimeIntervals; + update(); + return picker; + }; + + picker.disabledHours = function (hours) { + ///<signature helpKeyword="$.fn.datetimepicker.disabledHours"> + ///<summary>Returns an array with the currently set disabled hours on the component.</summary> + ///<returns type="array">options.disabledHours</returns> + ///</signature> + ///<signature> + ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of + ///options.enabledHours if such exist.</summary> + ///<param name="hours" locid="$.fn.datetimepicker.disabledHours_p:hours">Takes an [ int ] of values and disallows the user to select only from those hours.</param> + ///</signature> + if (arguments.length === 0) { + return (options.disabledHours ? $.extend({}, options.disabledHours) : options.disabledHours); + } + + if (!hours) { + options.disabledHours = false; + update(); + return picker; + } + if (!(hours instanceof Array)) { + throw new TypeError('disabledHours() expects an array parameter'); + } + options.disabledHours = indexGivenHours(hours); + options.enabledHours = false; + if (options.useCurrent && !options.keepInvalid) { + var tries = 0; + while (!isValid(date, 'h')) { + date.add(1, 'h'); + if (tries === 24) { + throw 'Tried 24 times to find a valid date'; + } + tries++; + } + setValue(date); + } + update(); + return picker; + }; + + picker.enabledHours = function (hours) { + ///<signature helpKeyword="$.fn.datetimepicker.enabledHours"> + ///<summary>Returns an array with the currently set enabled hours on the component.</summary> + ///<returns type="array">options.enabledHours</returns> + ///</signature> + ///<signature> + ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of options.disabledHours if such exist.</summary> + ///<param name="hours" locid="$.fn.datetimepicker.enabledHours_p:hours">Takes an [ int ] of values and allows the user to select only from those hours.</param> + ///</signature> + if (arguments.length === 0) { + return (options.enabledHours ? $.extend({}, options.enabledHours) : options.enabledHours); + } + + if (!hours) { + options.enabledHours = false; + update(); + return picker; + } + if (!(hours instanceof Array)) { + throw new TypeError('enabledHours() expects an array parameter'); + } + options.enabledHours = indexGivenHours(hours); + options.disabledHours = false; + if (options.useCurrent && !options.keepInvalid) { + var tries = 0; + while (!isValid(date, 'h')) { + date.add(1, 'h'); + if (tries === 24) { + throw 'Tried 24 times to find a valid date'; + } + tries++; + } + setValue(date); + } + update(); + return picker; + }; + + picker.viewDate = function (newDate) { + ///<signature helpKeyword="$.fn.datetimepicker.viewDate"> + ///<summary>Returns the component's model current viewDate, a moment object or null if not set.</summary> + ///<returns type="Moment">viewDate.clone()</returns> + ///</signature> + ///<signature> + ///<summary>Sets the components model current moment to it. Passing a null value unsets the components model current moment. Parsing of the newDate parameter is made using moment library with the options.format and options.useStrict components configuration.</summary> + ///<param name="newDate" locid="$.fn.datetimepicker.date_p:newDate">Takes string, viewDate, moment, null parameter.</param> + ///</signature> + if (arguments.length === 0) { + return viewDate.clone(); + } + + if (!newDate) { + viewDate = date.clone(); + return picker; + } + + if (typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) { + throw new TypeError('viewDate() parameter must be one of [string, moment or Date]'); + } + + viewDate = parseInputDate(newDate); + viewUpdate(); + return picker; + }; + // initializing element and component attributes if (element.is('input')) { input = element; } else { input = element.find(options.datepickerInput); @@ -1923,11 +2310,11 @@ clear: 'glyphicon glyphicon-trash', close: 'glyphicon glyphicon-remove' }, useStrict: false, sideBySide: false, - daysOfWeekDisabled: [], + daysOfWeekDisabled: false, calendarWeeks: false, viewMode: 'days', toolbarPlacement: 'default', showTodayButton: false, showClear: false, @@ -1937,10 +2324,11 @@ vertical: 'auto' }, widgetParent: null, ignoreReadonly: false, keepOpen: false, + focusOnShow: true, inline: false, keepInvalid: false, datepickerInput: '.datepickerinput', keyBinds: { up: function (widget) { @@ -2044,8 +2432,13 @@ }, 'delete': function () { this.clear(); } }, - debug: false + debug: false, + allowInputToggle: false, + disabledTimeIntervals: false, + disabledHours: false, + enabledHours: false, + viewDate: false }; }));