From 4375a0d346449f57fff029fbd36c1d2c54135207 Mon Sep 17 00:00:00 2001 From: vsn4ik Date: Sun, 8 Nov 2015 18:41:34 +0300 Subject: [PATCH] Fix navigation with disabled elements --- js/bootstrap-datepicker.js | 172 +++++++++++------------- tests/suites/keyboard_navigation/all.js | 36 +++++ tests/suites/options.js | 3 - 3 files changed, 117 insertions(+), 94 deletions(-) diff --git a/js/bootstrap-datepicker.js b/js/bootstrap-datepicker.js index c3a8662..c5491c1 100644 --- a/js/bootstrap-datepicker.js +++ b/js/bootstrap-datepicker.js @@ -229,7 +229,7 @@ o.multidateSeparator = String(o.multidateSeparator); o.weekStart %= 7; - o.weekEnd = ((o.weekStart + 6) % 7); + o.weekEnd = (o.weekStart + 6) % 7; var format = DPGlobal.parseFormat(o.format); if (o.startDate !== -Infinity){ @@ -789,8 +789,7 @@ }, this)); dates = $.grep(dates, $.proxy(function(date){ return ( - date < this.o.startDate || - date > this.o.endDate || + !this.dateWithinRange(date) || !date ); }, this), true); @@ -880,18 +879,12 @@ } if (this.dates.contains(date) !== -1) cls.push('active'); - if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate || - $.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1){ + if (!this.dateWithinRange(date) || this.dateIsDisabled(date)){ cls.push('disabled'); } if ($.inArray(date.getUTCDay(), this.o.daysOfWeekHighlighted) !== -1){ cls.push('highlighted'); } - if (this.o.datesDisabled.length > 0 && - $.grep(this.o.datesDisabled, function(d){ - return isUTCEquals(date, d); }).length > 0) { - cls.push('disabled', 'disabled-date'); - } if (this.range){ if (date > this.range[0] && date < this.range[this.range.length-1]){ @@ -1147,12 +1140,9 @@ this.fill(); break; case 'today': - var date = new Date(); - date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); - this.showMode(-2); var which = this.o.todayBtn === 'linked' ? null : 'view'; - this._setDate(date, which); + this._setDate(UTCToday(), which); break; case 'clear': this.clearDates(); @@ -1249,12 +1239,12 @@ _setDate: function(date, which){ if (!which || which === 'date') this._toggle_multidate(date && new Date(date)); - if (!which || which === 'view') + if (!which || which === 'view') this.viewDate = date && new Date(date); this.fill(); this.setValue(); - if (!which || which !== 'view') { + if (!which || which !== 'view') { this._trigger('changeDate'); } var element; @@ -1272,6 +1262,17 @@ } }, + moveDay: function(date, dir){ + var newDate = new Date(date); + newDate.setUTCDate(date.getUTCDate() + dir); + + return newDate; + }, + + moveWeek: function(date, dir){ + return this.moveDay(date, dir * 7); + }, + moveMonth: function(date, dir){ if (!isValidDate(date)) return this.o.defaultViewDate; @@ -1326,6 +1327,33 @@ return this.moveMonth(date, dir*12); }, + moveAvailableDate: function(date, dir, fn){ + do { + date = this[fn](date, dir); + + if (!this.dateWithinRange(date)) + return false; + + fn = 'moveDay'; + } + while (this.dateIsDisabled(date)); + + return date; + }, + + weekOfDateIsDisabled: function(date){ + return $.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1; + }, + + dateIsDisabled: function(date){ + return ( + this.weekOfDateIsDisabled(date) || + $.grep(this.o.datesDisabled, function(d){ + return isUTCEquals(date, d); + }).length > 0 + ); + }, + dateWithinRange: function(date){ return date >= this.o.startDate && date <= this.o.endDate; }, @@ -1339,7 +1367,7 @@ return; } var dateChanged = false, - dir, newDate, newViewDate, + dir, newViewDate, focusDate = this.focusDate || this.viewDate; switch (e.keyCode){ case 27: // escape @@ -1354,69 +1382,40 @@ e.stopPropagation(); break; case 37: // left - case 39: // right - if (!this.o.keyboardNavigation) - break; - dir = e.keyCode === 37 ? -1 : 1; - if (e.ctrlKey){ - newDate = this.moveYear(this.dates.get(-1) || UTCToday(), dir); - newViewDate = this.moveYear(focusDate, dir); - this._trigger('changeYear', this.viewDate); - } - else if (e.shiftKey){ - newDate = this.moveMonth(this.dates.get(-1) || UTCToday(), dir); - newViewDate = this.moveMonth(focusDate, dir); - this._trigger('changeMonth', this.viewDate); - } - else { - newDate = new Date(this.dates.get(-1) || UTCToday()); - newDate.setUTCDate(newDate.getUTCDate() + dir); - newViewDate = new Date(focusDate); - newViewDate.setUTCDate(focusDate.getUTCDate() + dir); - } - if (this.dateWithinRange(newViewDate)){ - this.focusDate = this.viewDate = newViewDate; - this.setValue(); - this.fill(); - e.preventDefault(); - } - break; case 38: // up + case 39: // right case 40: // down - if (!this.o.keyboardNavigation) + if (!this.o.keyboardNavigation || this.o.daysOfWeekDisabled.length === 7) break; - dir = e.keyCode === 38 ? -1 : 1; + dir = e.keyCode === 37 || e.keyCode === 38 ? -1 : 1; if (e.ctrlKey){ - newDate = this.moveYear(this.dates.get(-1) || UTCToday(), dir); - newViewDate = this.moveYear(focusDate, dir); - this._trigger('changeYear', this.viewDate); + newViewDate = this.moveAvailableDate(focusDate, dir, 'moveYear'); + + if (newViewDate) + this._trigger('changeYear', this.viewDate); } else if (e.shiftKey){ - newDate = this.moveMonth(this.dates.get(-1) || UTCToday(), dir); - newViewDate = this.moveMonth(focusDate, dir); - this._trigger('changeMonth', this.viewDate); + newViewDate = this.moveAvailableDate(focusDate, dir, 'moveMonth'); + + if (newViewDate) + this._trigger('changeMonth', this.viewDate); } - else { - newDate = new Date(this.dates.get(-1) || UTCToday()); - newDate.setUTCDate(newDate.getUTCDate() + dir * 7); - newViewDate = new Date(focusDate); - newViewDate.setUTCDate(focusDate.getUTCDate() + dir * 7); + else if (e.keyCode === 37 || e.keyCode === 39){ + newViewDate = this.moveAvailableDate(focusDate, dir, 'moveDay'); } - if (this.dateWithinRange(newViewDate)){ + else if (!this.weekOfDateIsDisabled(focusDate)){ + newViewDate = this.moveAvailableDate(focusDate, dir, 'moveWeek'); + } + if (newViewDate){ this.focusDate = this.viewDate = newViewDate; this.setValue(); this.fill(); e.preventDefault(); } break; - case 32: // spacebar - // Spacebar is used in manually typing dates in some formats. - // As such, its behavior should not be hijacked. - break; case 13: // enter - if (!this.o.forceParse) { - break; - } + if (!this.o.forceParse) + break; focusDate = this.focusDate || this.dates.get(-1) || this.viewDate; if (this.o.keyboardNavigation) { this._toggle_multidate(focusDate); @@ -1428,11 +1427,7 @@ this.fill(); if (this.picker.is(':visible')){ e.preventDefault(); - if (typeof e.stopPropagation === 'function') { - e.stopPropagation(); // All modern browsers, IE9+ - } else { - e.cancelBubble = true; // IE6,7,8 ignore "stopPropagation" - } + e.stopPropagation(); if (this.o.autoclose) this.hide(); } @@ -1607,14 +1602,15 @@ // Options priority: js args, data-attrs, locales, defaults opts = $.extend({}, defaults, locopts, elopts, options); if ($this.hasClass('input-daterange') || opts.inputs){ - var ropts = { + $.extend(opts, { inputs: opts.inputs || $this.find('input').toArray() - }; - data = new DateRangePicker(this, $.extend(opts, ropts)); + }); + data = new DateRangePicker(this, opts); } else { - data =new Datepicker(this, opts); + data = new Datepicker(this, opts); } + $this.data('datepicker', data); } if (typeof option === 'string' && typeof data[option] === 'function'){ internal_return = data[option].apply(data, args); @@ -1735,28 +1731,22 @@ return format.toValue(date, format, language); var part_re = /([\-+]\d+)([dmwy])/, parts = date.match(/([\-+]\d+)([dmwy])/g), - part, dir, i; + fn_map = { + d: 'moveDay', + m: 'moveMonth', + w: 'moveWeek', + y: 'moveYear' + }, + part, dir, i, fn; if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)){ date = new Date(); for (i=0; i < parts.length; i++){ part = part_re.exec(parts[i]); dir = parseInt(part[1]); - switch (part[2]){ - case 'd': - date.setUTCDate(date.getUTCDate() + dir); - break; - case 'm': - date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir); - break; - case 'w': - date.setUTCDate(date.getUTCDate() + dir * 7); - break; - case 'y': - date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir); - break; - } + fn = fn_map[part[2]]; + date = Datepicker.prototype[fn](date, dir); } - return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0); + return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()); } parts = date && date.match(this.nonpunctuation) || []; date = new Date(); @@ -1787,7 +1777,7 @@ val, filtered; setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m']; setters_map['dd'] = setters_map['d']; - date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); + date = UTCToday(); var fparts = format.parts.slice(); // Remove noop parts if (parts.length !== fparts.length){ diff --git a/tests/suites/keyboard_navigation/all.js b/tests/suites/keyboard_navigation/all.js index 6c00fd4..c0bcb23 100644 --- a/tests/suites/keyboard_navigation/all.js +++ b/tests/suites/keyboard_navigation/all.js @@ -24,3 +24,39 @@ test('TAB hides picker', function(){ ok(this.picker.is(':not(:visible)'), 'Picker is hidden'); }); + +test('by day (right/left arrows) with daysOfWeekDisabled', function(){ + var target; + + this.input.val('04-03-2013'); + this.dp.setDaysOfWeekDisabled('0,6'); + this.dp.update(); + + this.input.focus(); + + // Navigation: -1 day left arrow key + this.input.trigger({ + type: 'keydown', + keyCode: 37 + }); + + datesEqual(this.dp.viewDate, UTCDate(2013, 2, 1)); +}); + +test('by day (right/left arrows) with datesDisabled', function(){ + var target; + + this.input.val('04-03-2013'); + this.dp.setDatesDisabled(['05-03-2013']); + this.dp.update(); + + this.input.focus(); + + // Navigation: +1 day right arrow key + this.input.trigger({ + type: 'keydown', + keyCode: 39 + }); + + datesEqual(this.dp.viewDate, UTCDate(2013, 2, 6)); +}); diff --git a/tests/suites/options.js b/tests/suites/options.js index a591ef9..fe27297 100644 --- a/tests/suites/options.js +++ b/tests/suites/options.js @@ -591,17 +591,14 @@ test('DatesDisabled', function(){ target = picker.find('.datepicker-days tbody td:nth(1)'); ok(target.hasClass('disabled'), 'Day of week is disabled'); - ok(target.hasClass('disabled-date'), 'Date is disabled'); target = picker.find('.datepicker-days tbody td:nth(2)'); ok(!target.hasClass('disabled'), 'Day of week is enabled'); target = picker.find('.datepicker-days tbody td:nth(10)'); ok(target.hasClass('disabled'), 'Day of week is disabled'); - ok(target.hasClass('disabled-date'), 'Date is disabled'); target = picker.find('.datepicker-days tbody td:nth(11)'); ok(!target.hasClass('disabled'), 'Day of week is enabled'); target = picker.find('.datepicker-days tbody td:nth(20)'); ok(target.hasClass('disabled'), 'Day of week is disabled'); - ok(target.hasClass('disabled-date'), 'Date is disabled'); target = picker.find('.datepicker-days tbody td:nth(21)'); ok(!target.hasClass('disabled'), 'Day of week is enabled'); });