questionnaire/assets/js/custom/mithril-datepicker.js

394 lines
10 KiB
JavaScript
Vendored

;(function () {
var m = (typeof global !== 'undefined')
? (global.m || require('mithril'))
: window.m
if (!m) throw ("mithril-datepicker can't find Mithril.js")
var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
// var prevNextTitles = ['1 Mo', '1 Yr', '10 Yr']
var prevNextTitles = ['', '', '']
var weekStart = 0
var locale = 'en-us'
var formatOptions = null
/***************************************
*
* actions
*
***************************************/
function chooseDate(props, e) {
var box = e.target
var selectedDate = parseInt(box.textContent)
var dateObj = props.date
if (box.classList.contains('other-scope')) {
dateObj.setFullYear(dateObj.getFullYear(), dateObj.getMonth() + (selectedDate > 6 ? -1 : 1), selectedDate)
} else {
dateObj.setDate(selectedDate)
}
}
function dismissAndCommit(props, onchange) {
props.view = 0
props.active = false
if (onchange) onchange(props.date)
}
function prevNext(props, delta){
var newDate = new Date(props.date)
switch (props.view) {
case 0:
newDate.setMonth(newDate.getMonth() + delta)
break
case 1:
newDate.setFullYear(newDate.getFullYear() + delta)
break
default:
newDate.setFullYear(newDate.getFullYear() + (delta * 10))
}
props.date = pushToLastDay(props.date, newDate)
}
/***************************************
*
* utility
*
***************************************/
function adjustedProps(date, delta) {
var month = date.getMonth() + delta, year = date.getFullYear()
var over = month > 11, under = month < 0
return {
month: over ? 0 : under ? 11 : month,
year: over ? year + 1 : under ? year - 1 : year
}
}
function lastDateInMonth(date, delta) {
var obj = adjustedProps(date, delta)
if ([0, 2, 4, 6, 7, 9, 11].indexOf(obj.month) > -1) return 31 // array of 31-day props.months
if (obj.month === 1) { // February
if (!(obj.year % 400)) return 29
if (!(obj.year % 100)) return 28
return (obj.year % 4) ? 28 : 29
}
return 30
}
function pushToLastDay(oldDate, newDate) {
if (oldDate.getDate() !== newDate.getDate()) {
newDate.setMonth(newDate.getMonth() - 1, lastDateInMonth(newDate, -1))
}
return newDate
}
function stringsForLocale(locale) {
var date = new Date('jan 1 2017'), _months = [], _days = [] // 1/1/2017 was month:0 and weekday:0, so perfect
while (_days.length < 7) {
_days.push(date.toLocaleDateString(locale, { weekday: 'long' }))
date.setDate(date.getDate() + 1)
}
while (_months.length < 12) {
_months.push(date.toLocaleDateString(locale, { month: 'long' }))
date.setMonth(date.getMonth() + 1)
}
return { days: _days, months: _months }
}
function wrapAround(idx, array) {
var len = array.length
var n = idx >= len ? idx - len : idx
return array[n]
}
/***************************************
*
* generators
*
***************************************/
function daysFromLastMonth(props){
var month = props.date.getMonth(), year = props.date.getFullYear()
var firstDay = (new Date(year, month, 1)).getDay() - props.weekStart
if (firstDay < 0) firstDay += 7
var array = []
var lastDate = lastDateInMonth(props.date, -1)
var offsetStart = lastDate - firstDay + 1
for (var i=offsetStart; i<=lastDate; i++) { array.push(i) }
return array
}
function daysFromThisMonth(props) {
var max = lastDateInMonth(props.date, 0)
var array = []
for (var i=1; i<=max; i++) {
array.push(i)
}
return array
}
function daysFromNextMonth(prev, these) {
var soFar = prev.concat(these)
var mod = soFar.length % 7
var array = []
if (mod > 0) {
var n = 7 - mod
for (var i=1; i<=n; i++) { array.push(i) }
}
return array
}
function defaultDate() {
var now = new Date()
now.setHours(0, 0, 0, 0)
return now
}
function yearsForDecade(date) {
var year = date.getFullYear()
var array = []
var start = year - (year % 10)
for (var i=start; i<=start+10; i++) { array.push(i) }
return array
}
/***************************************
*
* view helpers
*
***************************************/
function classForBox(a, b) { return a === b ? 'chosen' : '' }
function displayDate(props) {
return props.date
.toLocaleDateString(props.locale, props.formatOptions || {
weekday: 'short',
month: 'short',
day: 'numeric',
year: 'numeric'
})
}
/***************************************
*
* components
*
***************************************/
var Header = {
view: function (vnode) {
var props = vnode.attrs.props
var date = props.date
var theseMonths = props.months || months
return m('.header'
// , m('.button-bg', { class: 'v' + props.view })
// , m('.fake-border')
, m('.button.prev'
, { onclick: prevNext.bind(null, props, -1) }
, prevNextTitles[props.view]
)
, m('.button.segment', { onclick: function () { props.view = 0 } }, m(".number", date.getDate()))
, m('.button.segment', { onclick: function () { props.view = 1 } }, m(".number", theseMonths[date.getMonth()].substr(0, 3)))
, m('.button.segment', { onclick: function () { props.view = 2 } }, m(".number", date.getFullYear()))
, m('.button.next'
, { onclick: prevNext.bind(null, props, 1) }
, prevNextTitles[props.view]
)
)
}
}
var MonthView = {
view: function (vnode) {
var props = vnode.attrs.props
var prevDates = daysFromLastMonth(props)
var theseDates = daysFromThisMonth(props)
var nextDates = daysFromNextMonth(prevDates, theseDates)
var theseWeekdays = props.days || days
return m('.calendar'
, m('.weekdays'
, theseWeekdays.map(function (_, idx) {
var day = wrapAround(idx + props.weekStart, theseWeekdays)
return m('.day.dummy', day.substring(0, 2))
})
)
, m('.weekdays'
, {
onclick: function(e){
chooseDate(props, e)
dismissAndCommit(props, vnode.attrs.onchange)
}
}
, prevDates.map(function (date) {
return m('.button.day.other-scope', m(".number", date))
})
, theseDates.map(function (date) {
return m('.button.day'
, { class: classForBox(props.date.getDate(), date) }
, m('.number', date)
)
})
, nextDates.map(function (date) {
return m('.button.day.other-scope', date)
})
)
)
}
}
var YearView = {
view: function (vnode) {
var props = vnode.attrs.props
var theseMonths = props.months || months
return m('.calendar'
, m('.months'
, theseMonths.map(function (month, idx) {
return m('.button.month'
, {
class: classForBox(props.date.getMonth(), idx),
onclick: function () {
var newDate = new Date(props.date)
newDate.setMonth(idx)
props.date = pushToLastDay(props.date, newDate)
props.view = 0
}
}
, m('.number', month.substring(0, 3))
)
})
)
)
}
}
var DecadeView = {
view: function (vnode) {
var props = vnode.attrs.props
var decade = yearsForDecade(props.date)
return m('.calendar'
, m('.years'
, {
style: {
display: "flex",
flexWrap: "wrap"
}
}
, decade.map(function (year) {
return m('.button.year'
, {
class: classForBox(props.date.getFullYear(), year),
onclick: function () {
var newDate = new Date(props.date)
newDate.setFullYear(year)
props.date = pushToLastDay(props.date, newDate)
props.view = 1
}
}
, m('.number', year)
)
})
)
)
}
}
var Editor = {
oncreate: function (vnode) {
requestAnimationFrame(function () { vnode.dom.classList.add('active') })
},
onbeforeremove: function (vnode) {
vnode.dom.classList.remove('active')
return new Promise(function (done) { setTimeout(done, 200) })
},
view: function (vnode) {
var props = vnode.attrs.props
return m('.editor'
, {
style: {
display: "table-cell",
verticalAlign: "middle"
}
}
, m(Header, { props: props })
, m('.sled'
, { class: 'p' + props.view }
, m(MonthView, { props: props, onchange: vnode.attrs.onchange })
, m(YearView, { props: props })
, m(DecadeView, {props: props })
)
)
}
}
var DatePicker = {
localize: function (loc) {
if (loc) {
prevNextTitles = loc.prevNextTitles || prevNextTitles
locale = loc.locale || locale
formatOptions = loc.formatOptions || formatOptions
weekStart = typeof loc.weekStart === 'number'
? loc.weekStart
: weekStart
var strings = stringsForLocale(locale)
days = strings.days
months = strings.months
}
},
oninit: function (vnode) {
var attrs = vnode.attrs
var props = {
date: new Date(attrs.date || defaultDate()),
active: false,
view: 0
}
;['prevNextTitles', 'locale', 'formatOptions'].forEach(function (prop) {
props[prop] = attrs[prop] || eval(prop)
})
props.weekStart = typeof attrs.weekStart === 'number' ? attrs.weekStart : weekStart
if (attrs.locale && attrs.locale !== locale) {
var strings = stringsForLocale(props.locale)
props.days = strings.days
props.months = strings.months
}
vnode.state.props = props
},
view: function(vnode){
var props = vnode.state.props
var displayText = displayDate(props)
return m('.mithril-date-picker-container'
, { class: props.active ? 'active' : '' }
, m('.mithril-date-picker'
, {
style: {
display: "table"
}
}
, m('.button.current-date'
, {
onclick: function(){
if (props.active) props.view = 0
props.active = !props.active
}
}
, displayText
)
, props.active && m('.overlay', { onclick: dismissAndCommit.bind(null, props, vnode.attrs.onchange) })
, props.active && m(Editor, { props: props, onchange: vnode.attrs.onchange })
)
)
}
}
if (typeof module === 'object') module.exports = DatePicker
else if (typeof window !== 'undefined') window.DatePicker = DatePicker
else global.DatePicker = DatePicker
})()