diff --git a/module/calendar.js b/module/calendar.js index 2adce05..2da317a 100644 --- a/module/calendar.js +++ b/module/calendar.js @@ -335,7 +335,7 @@ export class Calendar { // Not in the current month (blocked) if (date.getMonth() !== this.month) { - classList.add(css['blocked']) + classList.add(css['outside-month']) } // Blocked by the dates test (blocked) @@ -411,6 +411,11 @@ Calendar.css = { */ 'next': 'mh-calendar__next', + /** + * Applied to a date in the calendar which is outside of the current month. + */ + 'outside-month': 'mh-calendar__date--outside-month', + /** * Applied to the calendar's 'previous month' navigation element. */ diff --git a/module/date-picker.js b/module/date-picker.js index 18f67e0..50389fb 100644 --- a/module/date-picker.js +++ b/module/date-picker.js @@ -42,6 +42,12 @@ export class DatePicker { */ 'format': 'human', + /** + * Another element (input) this date picker is linked to, the + * element must also be a date picker. + */ + 'linkedTo': null, + /** * The latest date that can be selected. */ @@ -64,6 +70,13 @@ export class DatePicker { */ 'noPopup': false, + /** + * The number of days to offset the open date when opening the + * date picker from a linked date picker (see behaviours > + * openDate > offset. + */ + 'openOffset': 0, + /** * A list of parsers that will be used to attempt to parse * an input string as a date (strings are parsed using each @@ -107,6 +120,9 @@ export class DatePicker { }) } + if (typeof this._options.linkedTo === 'string') { + this._options.linkedTo = $.one(this._options.linkedTo) + } if (typeof this._options.monthNames === 'string') { this._options.monthNames = _toArray(this._options.monthNames) } @@ -375,11 +391,13 @@ export class DatePicker { this._options.parsers, this.input.value ) + if (date === null) { const openDate = this.constructor .behaviours - .openDate[this._behaviours.openDate] - this.calendar.goto(...openDate(this)) + .openDate[this._behaviours.openDate](this) + this.calendar.goto(openDate.getMonth(), openDate.getFullYear()) + this.calendar.date = openDate } else { this.calendar.goto(date.getMonth(), date.getFullYear()) this.calendar.date = date @@ -512,36 +530,19 @@ DatePicker.behaviours = { 'openDate': { /** - * Offset by X months from the value of another field or the current + * Offset by X days from the value of another field or the current * month. */ 'offset': (inst) => { // Get the date value for the linked to field - const linkedSelector = inst - ._dom - .input - .getAttribute('data-mh-date-picker--linked-to') - const linkedInput = $.one(linkedSelector) - let date = linkedInput._mhDatePicker.calendar.date - - // Get the month offset to apply - let offset = inst - ._dom - .input - .getAttribute('data-mh-date-picker--open-offset') || 0 - offset = parseInt(offset, 10) + let date = inst._options.linkedTo._mhDatePicker.calendar.date // Apply the offset - for (let i = 0; i < offset; i += 1) { - if (date.getMonth() == 11) { - date = new Date(date.getFullYear() + 1, 0, 1) - } else { - date = new Date(date.getFullYear(), date.getMonth() + 1, 1) - } - } + date.setDate(date.getDate() + inst._options.openOffset) + date.setHours(0, 0, 0, 0) - return [date.getMonth(), date.getFullYear()] + return date }, /** @@ -550,7 +551,7 @@ DatePicker.behaviours = { 'today': (inst) => { const date = new Date() date.setHours(0, 0, 0, 0) - return [date.getMonth(), date.getFullYear()] + return date } } diff --git a/package.json b/package.json index 50a7ecc..7b3c249 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "manhattan-date-picker", - "version": "1.0.1", + "version": "1.0.2", "description": "Date parsing and picking for form fields.", "engines": { "node": ">=8.9.4" diff --git a/scss/_calendar.scss b/scss/_calendar.scss index 75dd17f..093fd8c 100644 --- a/scss/_calendar.scss +++ b/scss/_calendar.scss @@ -102,7 +102,7 @@ &--selected { background: #e26a6a; border-radius: 20px; - color: #fff; + color: #fff !important; font-weight: bold; &:hover { @@ -132,8 +132,13 @@ } } + &--outside-month { + color: #999; + font-weight: normal; + } + &--selected.mh-calendar__date--blocked { color: #fff; } } -} \ No newline at end of file +} diff --git a/spec/calendar.spec.js b/spec/calendar.spec.js index 7e0f630..e51cc4d 100644 --- a/spec/calendar.spec.js +++ b/spec/calendar.spec.js @@ -381,35 +381,19 @@ describe('Calendar', () => { .true }) - it('should add blocked class to dates that outside of the current ' - + 'month', () => { + it('should add outside month class to dates that outside of the current ' + + 'current month', () => { for (let dateElm of calendar._dom.dates.childNodes) { if (dateElm._date.getMonth() === 0) { dateElm.classList - .contains(Calendar.css['blocked']) + .contains(Calendar.css['outside-month']) .should .be .false } else { dateElm.classList - .contains(Calendar.css['blocked']) - .should - .be - .true - } - } - - }) - - it('should add blocked class to dates that don\'t pass the given ' - + 'testDate function', () => { - - calendar.goto(1, 2010) - for (let dateElm of calendar._dom.dates.childNodes) { - if (dateElm._date.getDate() === 5) { - dateElm.classList - .contains(Calendar.css['blocked']) + .contains(Calendar.css['outside-month']) .should .be .true @@ -485,10 +469,9 @@ describe('Calendar', () => { }) it('should do nothing if the date picked is blocked', () => { - - calendar.goto(6, 2010) const {date} = calendar const [dateElm] = calendar._dom.dates.childNodes + dateElm.classList.add(Calendar.css['blocked']) $.dispatch(dateElm, 'click') calendar.date.getTime().should.equal(date.getTime()) onPicked.should.not.have.been.called diff --git a/spec/date-picker.spec.js b/spec/date-picker.spec.js index 3cde011..81e0ad6 100644 --- a/spec/date-picker.spec.js +++ b/spec/date-picker.spec.js @@ -14,6 +14,7 @@ chai.use(require('sinon-chai')) describe('DatePicker', () => { let inputElm = null + let otherInputElm = null beforeEach(() => { inputElm = $.create( @@ -24,10 +25,21 @@ describe('DatePicker', () => { } ) document.body.appendChild(inputElm) + + otherInputElm = $.create( + 'input', + { + 'name': 'other_input', + 'type': 'text', + 'value': '1st March 2017' + } + ) + document.body.appendChild(otherInputElm) }) afterEach(() => { document.body.removeChild(inputElm) + document.body.removeChild(otherInputElm) }) describe('constructor', () => { @@ -59,6 +71,19 @@ describe('DatePicker', () => { }) }) + describe('linkedTo', () => { + it('should accept a selector string and convert it to an ' + + 'element', () => { + + const datePicker = new DatePicker( + inputElm, + {'linkedTo': 'input[name="other_input"]'} + ) + const {linkedTo} = datePicker._options + linkedTo.should.equal(otherInputElm) + }) + }) + describe('maxDate', () => { it('should accept a date string and convert it to a date', () => { const datePicker = new DatePicker( @@ -421,17 +446,19 @@ describe('DatePicker', () => { datePicker.calendar.year.should.equal(2017) }) - it('should not set the date for the calendar the input\'s ' - + 'value is not a valid date', () => { + it('should set a default open date for the calendar if the ' + + 'input\'s value is not a valid date', () => { datePicker.calendar.goto(1, 2010) datePicker.calendar.date = new Date(2010, 1, 5) inputElm.value = 'querty' datePicker.open() const {date} = datePicker.calendar - date.getTime().should.equal((new Date(2010, 1, 5)).getTime()) - datePicker.calendar.month.should.equal(1) - datePicker.calendar.year.should.equal(2010) + const today = new Date() + today.setHours(0, 0, 0, 0) + date.getTime().should.equal(today.getTime()) + datePicker.calendar.month.should.equal(today.getMonth()) + datePicker.calendar.year.should.equal(today.getFullYear()) }) it('should call _track to make sure the picker is inline with ' @@ -664,6 +691,54 @@ describe('DatePicker', () => { }) }) + describe('behaviours > openDate', () => { + const behaviours = DatePicker.behaviours.openDate + let datePicker = null + let otherDatePicker = null + + beforeEach(() => { + datePicker = new DatePicker( + inputElm, + { + 'linkedTo': 'input[name="other_input"]', + 'openOffset': 1 + } + ) + datePicker.init() + otherDatePicker = new DatePicker(otherInputElm) + otherDatePicker.init() + }) + + afterEach(() => { + datePicker.destroy() + otherDatePicker.destroy() + }) + + describe('offset', () => { + + it('should return the day after the linked input\'s date', () => { + const date = behaviours.offset(datePicker) + const tomorrow = new Date() + tomorrow.setHours(0, 0, 0, 0) + tomorrow.setDate(tomorrow.getDate() + 1) + date.getTime().should.equal(tomorrow.getTime()) + }) + + }) + + describe('today', () => { + + it('should return todays date', () => { + const date = behaviours.today(datePicker) + const today = new Date() + today.setHours(0, 0, 0, 0) + date.getTime().should.equal(today.getTime()) + }) + + }) + + }) + describe('events', () => { let datePicker = null