UNPKG

material-design-for-react

Version:

A React package that implements Google Material Design Web Components

1,523 lines (1,451 loc) 49.2 kB
/** */ /** * import necessary components */ import moment from 'moment' import Draggabilly from 'draggabilly' class DateTimePicker { /** * [constructor of the DateTimePicker] * * @method constructor * * @param {String} type = 'date' or 'time [type of dialog] * @param {moment} init [initial value for the dialog date or time, defaults to today] [@default = today] * @param {moment} past [the past moment till which the calendar shall render] [@default = exactly 21 Years ago from init] * @param {moment} future [the future moment till which the calendar shall render] [@default = init] * @param {Boolean} mode [this value tells whether the time dialog will have the 24 hour mode (true) or 12 hour mode (false)] [@default = false] * @param {String} orientation = 'LANDSCAPE' or 'PORTRAIT' [force the orientation of the picker @default = 'LANDSCAPE'] * @param {element} trigger [element on which all the events will be dispatched e.g var foo = document.getElementById('bar'), here element = foo] * @param {String} ok = 'ok' [ok button's text] * @param {String} cancel = 'cancel' [cancel button's text] * @param {Boolean} colon = true [add an option to enable quote in 24 hour mode] * @param {Boolean} autoClose = false [close dialog on date/time selection] * @param {Boolean} inner24 = false [if 24-hour mode and (true), the PM hours shows in an inner dial] * @param {String} prevHandle = <div class="mddtp-prev-handle"></div> [The HTML content of the handle to go to previous month] * @param {String} nextHandle = <div class="mddtp-next-handle"></div> [The HTML content of the handle to go to next month] * * @return {Object} [DateTimePicker] */ constructor({ type, init = moment(), past = moment().subtract(21, 'years'), future = init, mode = false, orientation = 'LANDSCAPE', trigger = '', onCancel = null, onChange = null, ok = 'ok', cancel = 'cancel', colon = true, autoClose = false, inner24 = false, prevHandle = '<div><i class="material-icons">keyboard_arrow_left</i></div>', nextHandle = '<div><i class="material-icons">keyboard_arrow_right</i></div>' }) { this._type = type this._init = init this._past = past this._future = future this._mode = mode this._orientation = orientation this._onCancel = onCancel; this._onChange = onChange; this._trigger = trigger this._ok = ok this._cancel = cancel this._colon = colon this._autoClose = autoClose this._inner24 = inner24 this._prevHandle = prevHandle this._nextHandle = nextHandle /** * [dialog selected classes have the same structure as dialog but one level down] * @type {Object} * All declarations starting with _ are considered @private * e.g * sDialog = { * picker: 'some-picker-selected' * } */ this._sDialog = {} // attach the dialog if not present if (typeof document !== 'undefined' && !document.getElementById(`mddtp-picker__${this._type}`)) { this._buildDialog() } } /** * [time to get or set the current picker's moment] * * @method time * */ get time() { return this._init } set time(m) { if (m) { this._init = m } } /** * [trigger sets a new trigger for the dialog] * * @method trigger * */ get trigger() { return this._trigger } set trigger(el) { if (el) { this._trigger = el } } /** * [hide hide the dialog] * * @method hide * */ hide() { this._selectDialog() this._hideDialog() } /** * [show show the dialog] * * @method show * */ show() { this._selectDialog() if (this._type === 'date') { this._initDateDialog(this._init) } else if (this._type === 'time') { this._initTimeDialog(this._init) } this._showDialog(); } /** * [isOpen check if current Picker is open] * * @method isOpen * */ isOpen() { this._selectDialog(); return !!DateTimePicker.dialog.state; } /** * [isCloses check if current Picker is closed] * * @method isClosed * */ isClosed() { this._selectDialog(); return !DateTimePicker.dialog.state; } /** * [toggle toggle the dialog's between the visible and invisible state] * * @method toggle * */ toggle() { this._selectDialog() // work according to the current state of the dialog if (DateTimePicker.dialog.state) { this.hide() } else { this.show() } } /** * [dialog getter and setter for _dialog value] * * @method dialog * * @return {_dialog} [static or prototype value for the _dialog of the component] */ static get dialog() { return DateTimePicker._dialog } // noinspection JSAnnotator static set dialog(value) { DateTimePicker._dialog = value } _selectDialog() { // now do what you normally would do this._sDialog.picker = document.getElementById(`mddtp-picker__${[this._type]}`) /** * [sDialogEls stores all inner components of the selected dialog or sDialog to be later getElementById] * * @type {Array} */ const sDialogEls = [ 'viewHolder', 'years', 'header', 'cancel', 'ok', 'left', 'right', 'previous', 'current', 'next', 'subtitle', 'title', 'titleDay', 'titleMonth', 'AM', 'PM', 'needle', 'hourView', 'minuteView', 'hour', 'minute', 'fakeNeedle', 'circularHolder', 'circle', 'dotSpan' ] let i = sDialogEls.length while (i--) { this._sDialog[sDialogEls[i]] = document.getElementById(`mddtp-${this._type}__${sDialogEls[i]}`) } this._sDialog.tDate = this._init.clone() this._sDialog.sDate = this._init.clone() } /** * [_showDialog make the dialog visible with animation] * * @method _showDialog * */ _showDialog() { const me = this const zoomIn = 'zoomIn' DateTimePicker.dialog.state = true this._sDialog.picker.classList.remove('mddtp-picker--inactive') this._sDialog.picker.classList.add(zoomIn) // if the dialog is forced into portrait mode if (this._orientation === 'PORTRAIT') { this._sDialog.picker.classList.add('mddtp-picker--portrait') } setTimeout(() => { me._sDialog.picker.classList.remove(zoomIn) }, 300) } /** * [_hideDialog make the dialog invisible with animation] * * @method _hideDialog * */ _hideDialog() { const me = this const years = this._sDialog.years const title = me._sDialog.title const subtitle = me._sDialog.subtitle const viewHolder = this._sDialog.viewHolder const AM = this._sDialog.AM const PM = this._sDialog.PM const minute = this._sDialog.minute const hour = this._sDialog.hour const minuteView = this._sDialog.minuteView const hourView = this._sDialog.hourView const picker = this._sDialog.picker const needle = this._sDialog.needle const dotSpan = this._sDialog.dotSpan const active = 'mddtp-picker__color--active' const inactive = 'mddtp-picker--inactive' const invisible = 'mddtp-picker__years--invisible' const zoomIn = 'zoomIn' const zoomOut = 'zoomOut' const hidden = 'mddtp-picker__circularView--hidden' const selection = 'mddtp-picker__selection' DateTimePicker.dialog.state = false DateTimePicker.dialog.view = true this._sDialog.picker.classList.add(zoomOut) // reset classes if (this._type === 'date') { years.classList.remove(zoomIn, zoomOut) years.classList.add(invisible) title.classList.remove(active) subtitle.classList.add(active) viewHolder.classList.remove(zoomOut) } else { AM.classList.remove(active) PM.classList.remove(active) minute.classList.remove(active) hour.classList.add(active) minuteView.classList.add(hidden) hourView.classList.remove(hidden) subtitle.setAttribute('style', 'display: none') dotSpan.setAttribute('style', 'display: none') needle.className = selection } setTimeout(() => { // remove portrait mode me._sDialog.picker.classList.remove('mddtp-picker--portrait') me._sDialog.picker.classList.remove(zoomOut) me._sDialog.picker.classList.add(inactive) // clone elements and add them again to clear events attached to them // const pickerClone = picker.cloneNode(true) // picker.parentNode.replaceChild(pickerClone, picker) }, 300) } /** * [_buildDialog make the dialog elements and add them to the document] * * @method _buildDateDialog * */ _buildDialog() { const type = this._type const docfrag = document.createDocumentFragment() // outer most container of the picker const container = document.createElement('div') // header container of the picker const header = document.createElement('div') // body container of the picker const body = document.createElement('div') // action elements container const action = document.createElement('div') const cancel = document.createElement('button') const ok = document.createElement('button') // ... add properties to them container.id = `mddtp-picker__${type}` container.classList.add('mddtp-picker') container.classList.add(`mddtp-picker-${type}`) container.classList.add('mddtp-picker--inactive') container.classList.add('animated') this._addId(header, 'header') this._addClass(header, 'header') // add header to container container.appendChild(header) this._addClass(body, 'body') body.appendChild(action) // add body to container container.appendChild(body) // add stuff to header and body according to dialog type if (this._type === 'date') { const subtitle = document.createElement('div') const title = document.createElement('div') const titleDay = document.createElement('div') const titleMonth = document.createElement('div') const viewHolder = document.createElement('div') const views = document.createElement('ul') const previous = document.createElement('li') const current = document.createElement('li') const next = document.createElement('li') const left = document.createElement('button') const right = document.createElement('button') const years = document.createElement('ul') // inside header // adding properties to them this._addId(subtitle, 'subtitle') this._addClass(subtitle, 'subtitle') this._addId(title, 'title') this._addClass(title, 'title', ['mddtp-picker__color--active']) this._addId(titleDay, 'titleDay') this._addId(titleMonth, 'titleMonth') // add title stuff to it title.appendChild(titleDay) title.appendChild(titleMonth) // add them to header header.appendChild(subtitle) header.appendChild(title) // inside body // inside viewHolder this._addId(viewHolder, 'viewHolder') this._addClass(viewHolder, 'viewHolder', ['animated']) this._addClass(views, 'views') this._addId(previous, 'previous') previous.classList.add('mddtp-picker__view') this._addId(current, 'current') current.classList.add('mddtp-picker__view') this._addId(next, 'next') next.classList.add('mddtp-picker__view') // fill the views this._addView(previous) this._addView(current) this._addView(next) // add them viewHolder.appendChild(views) views.appendChild(previous) views.appendChild(current) views.appendChild(next) // inside body again this._addId(left, 'left') left.classList.add('mddtp-button') this._addClass(left, 'left') left.setAttribute('type', 'button') left.innerHTML = this._prevHandle this._addId(right, 'right') right.classList.add('mddtp-button') this._addClass(right, 'right') right.setAttribute('type', 'button') right.innerHTML = this._nextHandle this._addId(years, 'years') this._addClass(years, 'years', ['mddtp-picker__years--invisible', 'animated']) // add them to body body.appendChild(viewHolder) body.appendChild(left) body.appendChild(right) body.appendChild(years) } else { const title = document.createElement('div') const hour = document.createElement('span') const span = document.createElement('span') const minute = document.createElement('span') const subtitle = document.createElement('div') const AM = document.createElement('div') const PM = document.createElement('div') const circularHolder = document.createElement('div') const needle = document.createElement('div') const dot = document.createElement('span') const line = document.createElement('span') const circle = document.createElement('span') const minuteView = document.createElement('div') const fakeNeedle = document.createElement('div') const hourView = document.createElement('div') // add properties to them // inside header this._addId(title, 'title') this._addClass(title, 'title') this._addId(hour, 'hour') hour.classList.add('mddtp-picker__color--active') span.textContent = ':' this._addId(span, 'dotSpan') span.setAttribute('style', 'display: none') this._addId(minute, 'minute') this._addId(subtitle, 'subtitle') this._addClass(subtitle, 'subtitle') subtitle.setAttribute('style', 'display: none') this._addId(AM, 'AM') // AM.textContent = 'AM' // Change to 'AM' to Locale Meridiem AM.textContent = moment().localeData().meridiem(1, 1, true) this._addId(PM, 'PM') // PM.textContent = 'PM' // Change to 'PM' to Locale Meridiem PM.textContent = moment().localeData().meridiem(13, 1, true) // add them to title and subtitle title.appendChild(hour) title.appendChild(span) title.appendChild(minute) subtitle.appendChild(AM) subtitle.appendChild(PM) // add them to header header.appendChild(title) header.appendChild(subtitle) // inside body this._addClass(circularHolder, 'circularHolder') this._addId(needle, 'needle') this._addId(circularHolder, 'circularHolder') needle.classList.add('mddtp-picker__selection') this._addClass(dot, 'dot') this._addClass(line, 'line') this._addId(circle, 'circle') this._addClass(circle, 'circle') this._addId(minuteView, 'minuteView') minuteView.classList.add('mddtp-picker__circularView') minuteView.classList.add('mddtp-picker__circularView--hidden') this._addId(fakeNeedle, 'fakeNeedle') fakeNeedle.classList.add('mddtp-picker__circle--fake') this._addId(hourView, 'hourView') hourView.classList.add('mddtp-picker__circularView') // add them to needle needle.appendChild(dot) needle.appendChild(line) needle.appendChild(circle) // add them to circularHolder circularHolder.appendChild(needle) circularHolder.appendChild(minuteView) circularHolder.appendChild(fakeNeedle) circularHolder.appendChild(hourView) // add them to body body.appendChild(circularHolder) } action.classList.add('mddtp-picker__action') if (this._autoClose === true) { action.style.display = 'none' } this._addId(cancel, 'cancel') // cancel.classList.add('mddtp-button') cancel.classList.add( 'mdc-button' ); cancel.style.willChange = 'onchangenecessary'; cancel.setAttribute('type', 'button') this._addId(ok, 'ok') // ok.classList.add('mddtp-button') ok.classList.add( 'mdc-button' ); ok.style.willChange = 'onchangenecessary'; ok.setAttribute('type', 'button') // add actions action.appendChild(cancel) action.appendChild(ok) // add actions to body body.appendChild(action) docfrag.appendChild(container) // add the container to the end of body document.getElementsByTagName('body').item(0).appendChild(docfrag) } /** * [_initTimeDialog to initiate the date picker dialog usage e.g initDateDialog(moment())] * @param {moment} m [date for today or current] */ _initTimeDialog(m) { const hour = this._sDialog.hour const minute = this._sDialog.minute const subtitle = this._sDialog.subtitle const dotSpan = this._sDialog.dotSpan // switch according to 12 hour or 24 hour mode if (this._mode) { // CHANGED exception case for 24 => 0 issue #57 let text = parseInt(m.format('H'), 10) if (text === 0) { text = '00' } this._fillText(hour, text) // add the configurable colon in this mode issue #56 if (this._colon) { dotSpan.removeAttribute('style') } } else { this._fillText(hour, m.format('h')) // this._sDialog[m.format('A')].classList.add('mddtp-picker__color--active') // Using isPM function for Find PM if (m._locale.isPM(m.format('A'))) { this._sDialog.PM.classList.add('mddtp-picker__color--active') } else { this._sDialog.AM.classList.add('mddtp-picker__color--active') } subtitle.removeAttribute('style') dotSpan.removeAttribute('style') } this._fillText(minute, m.format('mm')) this._initHour() this._initMinute() this._attachEventHandlers() this._changeM() this._dragDial() this._switchToView(hour) this._switchToView(minute) this._addClockEvent() this._setButtonText() } _initHour() { const hourView = this._sDialog.hourView const needle = this._sDialog.needle const hour = 'mddtp-hour__selected' const selected = 'mddtp-picker__cell--selected' const rotate = 'mddtp-picker__cell--rotate-' const rotate24 = 'mddtp-picker__cell--rotate24' const cell = 'mddtp-picker__cell' const docfrag = document.createDocumentFragment() let hourNow if (this._mode) { const degreeStep = (this._inner24 === true) ? 10 : 5 hourNow = parseInt(this._sDialog.tDate.format('H'), 10) for (let i = 1, j = degreeStep; i <= 24; i++ , j += degreeStep) { const div = document.createElement('div') const span = document.createElement('span') div.classList.add(cell) // CHANGED exception case for 24 => 0 issue #57 if (i === 24) { span.textContent = '00' } else { span.textContent = i } let position = j if (this._inner24 === true && i > 12) { position -= 120 div.classList.add(rotate24) } div.classList.add(rotate + position) if (hourNow === i) { div.id = hour div.classList.add(selected) needle.classList.add(rotate + position) } // CHANGED exception case for 24 => 0 issue #58 if (i === 24 && hourNow === 0) { div.id = hour div.classList.add(selected) needle.classList.add(rotate + position) } div.appendChild(span) docfrag.appendChild(div) } } else { hourNow = parseInt(this._sDialog.tDate.format('h'), 10) for (let i = 1, j = 10; i <= 12; i++ , j += 10) { const div = document.createElement('div') const span = document.createElement('span') div.classList.add(cell) span.textContent = i div.classList.add(rotate + j) if (hourNow === i) { div.id = hour div.classList.add(selected) needle.classList.add(rotate + j) } div.appendChild(span) docfrag.appendChild(div) } } // empty the hours while (hourView.lastChild) { hourView.removeChild(hourView.lastChild) } // set inner html accordingly hourView.appendChild(docfrag) } _initMinute() { const minuteView = this._sDialog.minuteView let minuteNow = parseInt(this._sDialog.tDate.format('m'), 10) const sMinute = 'mddtp-minute__selected' const selected = 'mddtp-picker__cell--selected' const rotate = 'mddtp-picker__cell--rotate-' const cell = 'mddtp-picker__cell' const docfrag = document.createDocumentFragment() for (let i = 5, j = 10; i <= 60; i += 5, j += 10) { const div = document.createElement('div') const span = document.createElement('span') div.classList.add(cell) if (i === 60) { span.textContent = this._numWithZero(0) } else { span.textContent = this._numWithZero(i) } if (minuteNow === 0) { minuteNow = 60 } div.classList.add(rotate + j) // (minuteNow === 1 && i === 60) for corner case highlight 00 at 01 if ((minuteNow === i) || (minuteNow - 1 === i) || (minuteNow + 1 === i) || (minuteNow === 1 && i === 60)) { div.id = sMinute div.classList.add(selected) } div.appendChild(span) docfrag.appendChild(div) } // empty the hours while (minuteView.lastChild) { minuteView.removeChild(minuteView.lastChild) } // set inner html accordingly minuteView.appendChild(docfrag) } /** * [initDateDialog to initiate the date picker dialog usage e.g initDateDialog(moment())] * @param {moment} m [date for today or current] */ _initDateDialog(m) { const subtitle = this._sDialog.subtitle const title = this._sDialog.title const titleDay = this._sDialog.titleDay const titleMonth = this._sDialog.titleMonth this._fillText(subtitle, m.format('YYYY')) this._fillText(titleDay, m.format('ddd, ')) this._fillText(titleMonth, m.format('MMM D')) this._initYear() this._initViewHolder() this._attachEventHandlers() this._changeMonth() this._switchToView(subtitle) this._switchToView(title) this._setButtonText() } _initViewHolder() { let m = this._sDialog.tDate const current = this._sDialog.current const previous = this._sDialog.previous const next = this._sDialog.next const past = this._past const future = this._future if (m.isBefore(past, 'month')) { m = past.clone() } if (m.isAfter(future, 'month')) { m = future.clone() } this._sDialog.tDate = m this._initMonth(current, m) this._initMonth(next, moment(this._getMonth(m, 1))) this._initMonth(previous, moment(this._getMonth(m, -1))) this._toMoveMonth() } _initMonth(view, m) { const displayMonth = m.format('MMMM YYYY') // get the .mddtp-picker__month element using innerDivs[0] const innerDivs = view.getElementsByTagName('div') this._fillText(innerDivs[0], displayMonth) const docfrag = document.createDocumentFragment() // get the .mddtp-picker__tr element using innerDivs[3] const tr = innerDivs[3] const firstDayOfMonth = moment.weekdays(true).indexOf(moment.weekdays(false, moment(m).date(1).day())) /* * @netTrek - first day of month dependented from moment.locale */ let today = -1 let selected = -1 const lastDayOfMonth = parseInt(moment(m).endOf('month').format('D'), 10) + firstDayOfMonth - 1 let past = firstDayOfMonth const cellClass = 'mddtp-picker__cell' let future = lastDayOfMonth if (moment().isSame(m, 'month')) { today = parseInt(moment().format('D'), 10) today += firstDayOfMonth - 1 } if (this._past.isSame(m, 'month')) { past = parseInt(this._past.format('D'), 10) past += firstDayOfMonth - 1 } if (this._future.isSame(m, 'month')) { future = parseInt(this._future.format('D'), 10) future += firstDayOfMonth - 1 } if (this._sDialog.sDate.isSame(m, 'month')) { selected = parseInt(moment(m).format('D'), 10) selected += firstDayOfMonth - 1 } for (let i = 0; i < 42; i++) { // create cell const cell = document.createElement('span') const currentDay = i - firstDayOfMonth + 1 if ((i >= firstDayOfMonth) && (i <= lastDayOfMonth)) { if (i > future || i < past) { cell.classList.add(`${cellClass}--disabled`) } else { cell.classList.add(cellClass) } this._fillText(cell, currentDay) } if (today === i) { cell.classList.add(`${cellClass}--today`) } if (selected === i) { cell.classList.add(`${cellClass}--selected`) cell.id = 'mddtp-date__selected' } docfrag.appendChild(cell) } // empty the tr while (tr.lastChild) { tr.removeChild(tr.lastChild) } // set inner html accordingly tr.appendChild(docfrag) this._addCellClickEvent(tr) } /** * [_initYear Adds year elements] * * @method _initYear * * @return {type} [description] */ _initYear() { const years = this._sDialog.years const currentYear = this._sDialog.tDate.year() const docfrag = document.createDocumentFragment() const past = this._past.year() const future = this._future.year() for (let year = past; year <= future; year++) { const li = document.createElement('li') li.textContent = year if (year === currentYear) { li.id = 'mddtp-date__currentYear' li.classList.add('mddtp-picker__li--current') } docfrag.appendChild(li) } // empty the years ul while (years.lastChild) { years.removeChild(years.lastChild) } // set inner html accordingly years.appendChild(docfrag) // attach event handler to the ul to get the benefit of event delegation this._changeYear(years) } /** * Points the needle to the correct hour or minute */ _pointNeedle(me) { let spoke = 60 let value const circle = this._sDialog.circle const fakeNeedle = this._sDialog.fakeNeedle const circularHolder = this._sDialog.circularHolder const selection = 'mddtp-picker__selection' const needle = me._sDialog.needle // move the needle to correct position needle.className = '' needle.classList.add(selection) if (!DateTimePicker.dialog.view) { value = me._sDialog.sDate.format('m') // Need to desactivate for the autoClose mode as it mess things up. If you have an idea, feel free to give it a shot ! if (me._autoClose !== true) { // move the fakeNeedle to correct position setTimeout(() => { const hOffset = circularHolder.getBoundingClientRect() const cOffset = circle.getBoundingClientRect() fakeNeedle.setAttribute('style', `left:${cOffset.left - hOffset.left}px;top:${cOffset.top - hOffset.top}px`) }, 300) } } else if (me._mode) { spoke = 24 value = parseInt(me._sDialog.sDate.format('H'), 10) // CHANGED exception for 24 => 0 issue #58 if (value === 0) { value = 24 } } else { spoke = 12 value = me._sDialog.sDate.format('h') } const rotationClass = me._calcRotation(spoke, parseInt(value, 10)) if (rotationClass) { needle.classList.add(rotationClass) } } /** * [_switchToView Adds event handler for the feature: switch between date and year view in date dialog] * * @method _switchToView * * @param {type} picker [description] * * @param {type} el [description] * */ _switchToView(el) { const me = this // attach the view change button if (this._type === 'date') { el.onclick = function () { me._switchToDateView(el, me) } } else { if (this._inner24 === true && me._mode) { if (parseInt(me._sDialog.sDate.format('H'), 10) > 12) { me._sDialog.needle.classList.add('mddtp-picker__cell--rotate24') } else { me._sDialog.needle.classList.remove('mddtp-picker__cell--rotate24') } } el.onclick = function () { me._switchToTimeView(me) } } } /** * [_switchToTimeView the actual switchToDateView function so that it can be called by other elements as well] * * @method _switchToTimeView * * @param {type} me [context] * */ _switchToTimeView(me) { const hourView = me._sDialog.hourView const minuteView = me._sDialog.minuteView const hour = me._sDialog.hour const minute = me._sDialog.minute const activeClass = 'mddtp-picker__color--active' const hidden = 'mddtp-picker__circularView--hidden' // toggle view classes hourView.classList.toggle(hidden) minuteView.classList.toggle(hidden) hour.classList.toggle(activeClass) minute.classList.toggle(activeClass) // move the needle to correct position // toggle the view type DateTimePicker.dialog.view = !DateTimePicker.dialog.view me._pointNeedle(me) } /** * [_switchToDateView the actual switchToDateView function so that it can be called by other elements as well] * * @method _switchToDateView * * @param {type} el [element to attach event to] * @param {type} me [context] * */ _switchToDateView(el, me) { el.setAttribute('disabled', '') const viewHolder = me._sDialog.viewHolder const years = me._sDialog.years const title = me._sDialog.title const subtitle = me._sDialog.subtitle const currentYear = document.getElementById('mddtp-date__currentYear') if (DateTimePicker.dialog.view) { viewHolder.classList.add('zoomOut') years.classList.remove('mddtp-picker__years--invisible') years.classList.add('zoomIn') // scroll into the view currentYear.scrollIntoViewIfNeeded && currentYear.scrollIntoViewIfNeeded() } else { years.classList.add('zoomOut') viewHolder.classList.remove('zoomOut') viewHolder.classList.add('zoomIn') setTimeout(() => { years.classList.remove('zoomIn', 'zoomOut') years.classList.add('mddtp-picker__years--invisible') viewHolder.classList.remove('zoomIn') }, 300) } title.classList.toggle('mddtp-picker__color--active') subtitle.classList.toggle('mddtp-picker__color--active') DateTimePicker.dialog.view = !DateTimePicker.dialog.view setTimeout(() => { el.removeAttribute('disabled') }, 300) } _addClockEvent() { const me = this const hourView = this._sDialog.hourView const minuteView = this._sDialog.minuteView const sClass = 'mddtp-picker__cell--selected' hourView.onclick = function (e) { const sHour = 'mddtp-hour__selected' const selectedHour = document.getElementById(sHour) let setHour = 0 if (e.target && e.target.nodeName === 'SPAN') { // clear the previously selected hour selectedHour.id = '' selectedHour.classList.remove(sClass) // select the new hour e.target.parentNode.classList.add(sClass) e.target.parentNode.id = sHour // set the sDate according to 24 or 12 hour mode if (me._mode) { setHour = parseInt(e.target.textContent, 10) } else if (me._sDialog.sDate.format('A') === 'AM') { setHour = e.target.textContent } else { setHour = parseInt(e.target.textContent, 10) + 12 } me._sDialog.sDate.hour(setHour) // set the display hour me._sDialog.hour.textContent = e.target.textContent // switch the view me._pointNeedle(me) setTimeout(() => { me._switchToTimeView(me) }, 700) } } minuteView.onclick = function (e) { const sMinute = 'mddtp-minute__selected' const selectedMinute = document.getElementById(sMinute) let setMinute = 0 if (e.target && e.target.nodeName === 'SPAN') { // clear the previously selected hour if (selectedMinute) { selectedMinute.id = '' selectedMinute.classList.remove(sClass) } // select the new minute e.target.parentNode.classList.add(sClass) e.target.parentNode.id = sMinute // set the sDate minute setMinute = e.target.textContent me._sDialog.sDate.minute(setMinute) // set the display minute me._sDialog.minute.textContent = setMinute me._pointNeedle(me) if (me._autoClose === true) { me._sDialog.ok.onclick() } } } } _addCellClickEvent(el) { const me = this el.onclick = function (e) { if (e.target && e.target.nodeName === 'SPAN' && e.target.classList.contains('mddtp-picker__cell')) { const day = e.target.textContent const currentDate = me._sDialog.tDate.date(day) const sId = 'mddtp-date__selected' const sClass = 'mddtp-picker__cell--selected' const selected = document.getElementById(sId) const subtitle = me._sDialog.subtitle const titleDay = me._sDialog.titleDay const titleMonth = me._sDialog.titleMonth if (selected) { selected.classList.remove(sClass) selected.id = '' } e.target.classList.add(sClass) e.target.id = sId // update temp date object with the date selected me._sDialog.sDate = currentDate.clone() me._fillText(subtitle, currentDate.year()) me._fillText(titleDay, currentDate.format('ddd, ')) me._fillText(titleMonth, currentDate.format('MMM D')) if (me._autoClose === true) { me._sDialog.ok.onclick() } } } } _toMoveMonth() { const m = this._sDialog.tDate const left = this._sDialog.left const right = this._sDialog.right const past = this._past const future = this._future left.removeAttribute('disabled') right.removeAttribute('disabled') left.classList.remove('mddtp-button--disabled') right.classList.remove('mddtp-button--disabled') if (m.isSame(past, 'month')) { left.setAttribute('disabled', '') left.classList.add('mddtp-button--disabled') } if (m.isSame(future, 'month')) { right.setAttribute('disabled', '') right.classList.add('mddtp-button--disabled') } } _changeMonth() { const me = this const left = this._sDialog.left const right = this._sDialog.right const mLeftClass = 'mddtp-picker__view--left' const mRightClass = 'mddtp-picker__view--right' const pause = 'mddtp-picker__view--pause' left.onclick = function () { moveStep(mRightClass, me._sDialog.previous) } right.onclick = function () { moveStep(mLeftClass, me._sDialog.next) } function moveStep(aClass, to) { /** * [stepBack to know if the to step is going back or not] * * @type {Boolean} */ let stepBack = false let next = me._sDialog.next let current = me._sDialog.current let previous = me._sDialog.previous left.setAttribute('disabled', '') right.setAttribute('disabled', '') current.classList.add(aClass) previous.classList.add(aClass) next.classList.add(aClass) const clone = to.cloneNode(true) let del if (to === next) { del = previous current.parentNode.appendChild(clone) next.id = current.id current.id = previous.id previous = current current = next next = clone } else { stepBack = true del = next previous.id = current.id current.id = next.id next = current current = previous } setTimeout(() => { if (to === previous) { current.parentNode.insertBefore(clone, current) previous = clone } // update real values to match these values me._sDialog.next = next me._sDialog.current = current me._sDialog.previous = previous current.classList.add(pause) next.classList.add(pause) previous.classList.add(pause) current.classList.remove(aClass) next.classList.remove(aClass) previous.classList.remove(aClass) del.parentNode.removeChild(del) }, 300) // REVIEW replace below code with requestAnimationFrame setTimeout(() => { current.classList.remove(pause) next.classList.remove(pause) previous.classList.remove(pause) if (stepBack) { me._sDialog.tDate = me._getMonth(me._sDialog.tDate, -1) } else { me._sDialog.tDate = me._getMonth(me._sDialog.tDate, 1) } me._initViewHolder() }, 350) setTimeout(() => { if (!(left.classList.contains('mddtp-button--disabled'))) { left.removeAttribute('disabled') } if (!(right.classList.contains('mddtp-button--disabled'))) { right.removeAttribute('disabled') } }, 400) } } /** * [_changeYear the on click event handler for year] * * @method _changeYear * * @param {type} el [description] * */ _changeYear(el) { const me = this el.onclick = function (e) { if (e.target && e.target.nodeName === 'LI') { const selected = document.getElementById('mddtp-date__currentYear') // clear previous selected selected.id = '' selected.classList.remove('mddtp-picker__li--current') // add the properties to the newer one e.target.id = 'mddtp-date__currentYear' e.target.classList.add('mddtp-picker__li--current') // switch view me._switchToDateView(el, me) // set the tdate to it me._sDialog.tDate.year(parseInt(e.target.textContent, 10)) // update the dialog me._initViewHolder() } } } /** * [_changeM switch between am and pm modes] * * @method _changeM * * @return {type} [description] */ _changeM() { const me = this const AM = this._sDialog.AM const PM = this._sDialog.PM AM.onclick = function () { // let m = me._sDialog.sDate.format('A') // Change Locale Meridiem to AM/PM String let m = 'AM' if (me._sDialog.sDate._locale.isPM(me._sDialog.sDate.format('A'))) { m = 'PM' } if (m === 'PM') { me._sDialog.sDate.subtract(12, 'h') AM.classList.toggle('mddtp-picker__color--active') PM.classList.toggle('mddtp-picker__color--active') } } PM.onclick = function () { // let m = me._sDialog.sDate.format('A') // Change Locale Meridiem to AM/PM String let m = 'AM' if (me._sDialog.sDate._locale.isPM(me._sDialog.sDate.format('A'))) { m = 'PM' } if (m === 'AM') { me._sDialog.sDate.add(12, 'h') AM.classList.toggle('mddtp-picker__color--active') PM.classList.toggle('mddtp-picker__color--active') } } } _dragDial() { const me = this const needle = this._sDialog.needle const circle = this._sDialog.circle const fakeNeedle = this._sDialog.fakeNeedle const circularHolder = this._sDialog.circularHolder const minute = this._sDialog.minute const quick = 'mddtp-picker__selection--quick' const selection = 'mddtp-picker__selection' const selected = 'mddtp-picker__cell--selected' const rotate = 'mddtp-picker__cell--rotate-' let hOffset = circularHolder.getBoundingClientRect() let divides const fakeNeedleDraggabilly = new Draggabilly(fakeNeedle, { containment: true }) fakeNeedleDraggabilly.on('pointerDown', () => { // console.info ( 'pointerDown' , e ); hOffset = circularHolder.getBoundingClientRect() }) /** * netTrek * fixes for iOS - drag */ fakeNeedleDraggabilly.on('pointerMove', (e) => { let clientX = e.clientX let clientY = e.clientY if (clientX === undefined) { if (e.pageX === undefined) { if (e.touches && e.touches.length > 0) { clientX = e.touches[0].clientX clientY = e.touches[0].clientY } else { throw new Error('coult not detect pageX, pageY') } } else { clientX = e.pageX - document.body.scrollLeft - document.documentElement.scrollLeft clientY = e.pageY - document.body.scrollTop - document.documentElement.scrollTop } } // console.info ( 'Drag clientX' , clientX, clientY, e ); const xPos = clientX - hOffset.left - (hOffset.width / 2) const yPos = clientY - hOffset.top - (hOffset.height / 2) let slope = Math.atan2(-yPos, xPos) needle.className = '' if (slope < 0) { slope += 2 * Math.PI } slope *= 180 / Math.PI slope = 360 - slope if (slope > 270) { slope -= 360 } divides = parseInt(slope / 6) const same = Math.abs((6 * divides) - slope) const upper = Math.abs((6 * (divides + 1)) - slope) if (upper < same) { divides++ } divides += 15 needle.classList.add(selection) needle.classList.add(quick) needle.classList.add(rotate + (divides * 2)) }) /** * netTrek * fixes for iOS - drag */ const onDragEnd = function () { const minuteViewChildren = me._sDialog.minuteView.getElementsByTagName('div') const sMinute = 'mddtp-minute__selected' const selectedMinute = document.getElementById(sMinute) const cOffset = circle.getBoundingClientRect() fakeNeedle.setAttribute('style', `left:${cOffset.left - hOffset.left}px;top:${cOffset.top - hOffset.top}px`) needle.classList.remove(quick) let select = divides if (select === 1) { select = 60 } select = me._nearestDivisor(select, 5) // normalize 60 => 0 if (divides === 60) { divides = 0 } // remove previously selected value if (selectedMinute) { selectedMinute.id = '' selectedMinute.classList.remove(selected) } // add the new selected if (select > 0) { select /= 5 select-- minuteViewChildren[select].id = sMinute minuteViewChildren[select].classList.add(selected) } minute.textContent = me._numWithZero(divides) me._sDialog.sDate.minutes(divides) } fakeNeedleDraggabilly.on('pointerUp', onDragEnd) fakeNeedleDraggabilly.on('dragEnd', onDragEnd) } /** * [_attachEventHandlers attach event handlers for actions to the date or time picker dialog] * * @method _attachEventHandlers * */ _attachEventHandlers() { const me = this const ok = this._sDialog.ok const cancel = this._sDialog.cancel // create cutom events to dispatch cancel.onclick = function () { me.toggle() if (me._onCancel) { me._onCancel(); } if (me._trigger) { //me._trigger.dispatchEvent(onCancel) } } ok.onclick = function () { me._init = me._sDialog.sDate me.toggle() if (me._onChange) { me._onChange(); } if (me._trigger) { //me._trigger.dispatchEvent(onOk) } } } /** * [_setButtonText Set the ok and cancel button text] * @method _setButtonText */ _setButtonText() { this._sDialog.cancel.textContent = this._cancel this._sDialog.ok.textContent = this._ok } /** * [_getMonth get the next or previous month] * * @method _getMonth * * @param {type} moment [description] * @param {type} count [pass -ve values for past months and positive ones for future values] * * @return {moment} [returns the relative moment] */ _getMonth(moment, count) { let m m = moment.clone() if (count > 0) { return m.add(Math.abs(count), 'M') } return m.subtract(Math.abs(count), 'M') } /** * [_nearestDivisor gets the nearest number which is divisible by a number] * * @method _nearestDivisor * * @param {int} number [number to check] * @param {int} divided [number to be divided by] * * @return {int} [returns -1 if not found] */ _nearestDivisor(number, divided) { if (number % divided === 0) { return number } else if ((number - 1) % divided === 0) { return number - 1 } else if ((number + 1) % divided === 0) { return number + 1 } return -1 } /** * [_numWithZero returns string number (n) with a prefixed 0 if 0 <= n <= 9] * * @method _numWithZero * * @param {int} n [description] * * @return {String} [description] */ _numWithZero(n) { return n > 9 ? `${n}` : `0${n}` } /** * [_fillText fills element with text] * * @method _fillText * * @param {type} el [description] * @param {type} text [description] * * @return {type} [description] */ _fillText(el, text) { if (el.firstChild) { el.firstChild.nodeValue = text } else { el.appendChild(document.createTextNode(text)) } } /** * [_addId add id to picker element] * * @method _addId * * @param {type} el [description] * @param {string} id [the id] */ _addId(el, id) { el.id = `mddtp-${this._type}__${id}` } /** * [_addClass add the default class to picker element] * * @method _addClass * * @param {type} el [description] * @param {type} class [description] * @param {type} more [description] */ _addClass(el, aClass, more) { el.classList.add(`mddtp-picker__${aClass}`) let i = 0 if (more) { i = more.length more.reverse() } while (i--) { el.classList.add(more[i]) } } /** * [_addView add view] * * @method _addView * * @param {type} view [description] */ _addView(view) { const month = document.createElement('div') const grid = document.createElement('div') const th = document.createElement('div') const tr = document.createElement('div') /** * @netTrek - weekday dependented from moment.locale */ const weekDays = moment.weekdaysMin(true).reverse() let week = 7 while (week--) { const span = document.createElement('span') span.textContent = weekDays[week] th.appendChild(span) } // add properties to them this._addClass(month, 'month') this._addClass(grid, 'grid') this._addClass(th, 'th') this._addClass(tr, 'tr') // add them to the view view.appendChild(month) view.appendChild(grid) grid.appendChild(th) grid.appendChild(tr) } /** * [_calcRotation calculate rotated angle and return the appropriate class for it] * * @method _calcRotation * * @param {int} spoke [spoke is the spoke count = [12,24,60]] * * @param {int} value [value for the spoke] * * @return {String} [appropriate class] */ _calcRotation(spoke, value) { // set clocks top and right side value if (spoke === 12) { value *= 10 } else if (spoke === 24) { value *= 5 } else { value *= 2 } // special case for 00 => 60 if (spoke === 60 && value === 0) { value = 120 } return `mddtp-picker__cell--rotate-${value}` } } DateTimePicker._dialog = { view: true, state: false } export default DateTimePicker