UNPKG

daisho

Version:
537 lines (517 loc) 21.9 kB
# Design by Chris Meeks # Code by Tyler van der Hoeven # https://github.com/Baremetrics/calendar import moment from 'moment-timezone' Calendar = (settings) -> self = this @settings = settings @calIsOpen = false @presetIsOpen = false @sameDayRange = settings.same_day_range or false @element = settings.element or $('.daterange') @selected = null @type = if @element.hasClass('daterange--single') then 'single' else 'double' @required = if settings.required == false then false else true @format = settings.format or {} @format.input = settings.format and settings.format.input or 'MMMM D, YYYY' @format.preset = settings.format and settings.format.preset or 'll' @format.jump_month = settings.format and settings.format.jump_month or 'MMMM' @format.jump_year = settings.format and settings.format.jump_year or 'YYYY' @placeholder = settings.placeholder or @format.input @days_array = if settings.days_array and settings.days_array.length == 7 then settings.days_array else moment.weekdaysMin() @orig_start_date = null @orig_end_date = null @orig_current_date = null @earliest_date = if settings.earliest_date then moment(settings.earliest_date) else moment('1900-01-01') @latest_date = if settings.latest_date then moment(settings.latest_date) else moment('2900-12-31') @end_date = if settings.end_date then moment(settings.end_date) else if @type == 'double' then moment() else null @start_date = if settings.start_date then moment(settings.start_date) else if @type == 'double' then @end_date.clone().subtract(1, 'month') else null @current_date = if settings.current_date then moment(settings.current_date) else if @type == 'single' then moment() else null @presets = if settings.presets == false or @type == 'single' then false else true @callback = settings.callback or @calendarSetDates @calendarHTML @type $('.dr-presets', @element).click -> self.presetToggle() return $('.dr-list-item', @element).click -> start = $('.dr-item-aside', this).data('start') end = $('.dr-item-aside', this).data('end') self.start_date = self.calendarCheckDate(start) self.end_date = self.calendarCheckDate(end) self.calendarSetDates() self.presetToggle() self.calendarSaveDates() return $('.dr-date', @element).on 'click': -> self.calendarOpen this return 'keyup': (event) -> if event.keyCode == 9 and !self.calIsOpen and !self.start_date and !self.end_date self.calendarOpen this return 'keydown': (event) -> `var timeframe` switch event.keyCode when 9 # Tab if $(self.selected).hasClass('dr-date-start') event.preventDefault() self.calendarCheckDates() self.calendarSetDates() $('.dr-date-end', self.element).trigger 'click' else self.calendarCheckDates() self.calendarSetDates() self.calendarSaveDates() self.calendarClose 'force' when 13 # Enter event.preventDefault() self.calendarCheckDates() self.calendarSetDates() self.calendarSaveDates() self.calendarClose 'force' when 27 # ESC self.calendarSetDates() self.calendarClose 'force' when 38 # Up event.preventDefault() timeframe = 'day' if event.shiftKey timeframe = 'week' if event.metaKey timeframe = 'month' back = moment(self.current_date).subtract(1, timeframe) $(this).html back.format(self.format.input) self.current_date = back when 40 # Down event.preventDefault() timeframe = 'day' if event.shiftKey timeframe = 'week' if event.metaKey timeframe = 'month' forward = moment(self.current_date).add(1, timeframe) $(this).html forward.format(self.format.input) self.current_date = forward return $('.dr-month-switcher i', @element).click -> m = $('.dr-month-switcher span', self.element).data('month') y = $('.dr-year-switcher span', self.element).data('year') this_moment = moment([ y m 1 ]) back = this_moment.clone().subtract(1, 'month') forward = this_moment.clone().add(1, 'month').startOf('day') if $(this).hasClass('dr-left') self.calendarOpen self.selected, back else if $(this).hasClass('dr-right') self.calendarOpen self.selected, forward return $('.dr-year-switcher i', @element).click -> m = $('.dr-month-switcher span', self.element).data('month') y = $('.dr-year-switcher span', self.element).data('year') this_moment = moment([ y m 1 ]) back = this_moment.clone().subtract(1, 'year') forward = this_moment.clone().add(1, 'year').startOf('day') if $(this).hasClass('dr-left') self.calendarOpen self.selected, back else if $(this).hasClass('dr-right') self.calendarOpen self.selected, forward return $('.dr-dates-dash', @element).click -> $('.dr-date-start', self.element).trigger 'click' return # Once you click into a selection.. this lets you click out @element.on 'click', -> document.addEventListener 'click', (f) -> contains = self.element.find(f.path[0]) if !contains.length if self.presetIsOpen self.presetToggle() if self.calIsOpen if $(self.selected).hasClass('dr-date-end') self.calendarSaveDates() self.calendarSetDates() self.calendarClose 'force' return return return selectOneDate = (other, cal, date) -> string = moment(date).format(cal.format.input) if other $('.dr-date', cal.element).not(cal.selected).html other.format(cal.format.input) $(cal.selected).html string cal.calendarOpen cal.selected if $(cal.selected).hasClass('dr-date-start') $('.dr-date-end', cal.element).trigger 'click' else cal.calendarSaveDates() cal.calendarClose 'force' return Calendar::presetToggle = -> if @presetIsOpen == false @orig_start_date = @start_date @orig_end_date = @end_date @orig_current_date = @current_date @presetIsOpen = true else if @presetIsOpen @presetIsOpen = false if @calIsOpen == true @calendarClose() $('.dr-preset-list', @element).slideToggle 200 $('.dr-input', @element).toggleClass 'dr-active' $('.dr-presets', @element).toggleClass 'dr-active' @element.toggleClass 'dr-active' return Calendar::presetCreate = -> self = this ul_presets = $('<ul class="dr-preset-list" style="display: none;"></ul>') presets = if typeof self.settings.presets == 'object' then self.settings.presets else [ { label: 'Last 30 days' start: moment(self.latest_date).subtract(29, 'days') end: self.latest_date } { label: 'Last month' start: moment(self.latest_date).subtract(1, 'month').startOf('month') end: moment(self.latest_date).subtract(1, 'month').endOf('month') } { label: 'Last 3 months' start: moment(self.latest_date).subtract(3, 'month').startOf('month') end: moment(self.latest_date).subtract(1, 'month').endOf('month') } { label: 'Last 6 months' start: moment(self.latest_date).subtract(6, 'month').startOf('month') end: moment(self.latest_date).subtract(1, 'month').endOf('month') } { label: 'Last year' start: moment(self.latest_date).subtract(12, 'month').startOf('month') end: moment(self.latest_date).subtract(1, 'month').endOf('month') } { label: 'All time' start: self.earliest_date end: self.latest_date } ] if moment(self.latest_date).diff(moment(self.latest_date).startOf('month'), 'days') >= 6 and typeof self.settings.presets != 'object' presets.splice 1, 0, label: 'This month' start: moment(self.latest_date).startOf('month') end: self.latest_date $.each presets, (i, d) -> if moment(d.start).isBefore(self.earliest_date) d.start = self.earliest_date if moment(d.start).isAfter(self.latest_date) d.start = self.latest_date if moment(d.end).isBefore(self.earliest_date) d.end = self.earliest_date if moment(d.end).isAfter(self.latest_date) d.end = self.latest_date startISO = moment(d.start).toISOString() endISO = moment(d.end).toISOString() string = moment(d.start).format(self.format.preset) + ' &ndash; ' + moment(d.end).format(self.format.preset) if $('.dr-preset-list', self.element).length item = $('.dr-preset-list .dr-list-item:nth-of-type(' + i + 1 + ') .dr-item-aside', self.element) item.data 'start', startISO item.data 'end', endISO item.html string else ul_presets.append '<li class="dr-list-item">' + d.label + '<span class="dr-item-aside" data-start="' + startISO + '" data-end="' + endISO + '">' + string + '</span>' + '</li>' return ul_presets Calendar::calendarSetDates = -> $('.dr-date-start', @element).html moment(@start_date).format(@format.input) $('.dr-date-end', @element).html moment(@end_date).format(@format.input) if !@start_date and !@end_date old_date = $('.dr-date', @element).html() new_date = moment(@current_date).format(@format.input) if old_date.length == 0 and !@required new_date = '' if old_date != new_date $('.dr-date', @element).html new_date return Calendar::calendarSaveDates = -> if @type == 'double' if !moment(@orig_end_date).isSame(@end_date) or !moment(@orig_start_date).isSame(@start_date) return @callback() else if !@required or !moment(@orig_current_date).isSame(@current_date) return @callback() return Calendar::calendarCheckDate = (d) -> # Today if d == 'today' or d == 'now' return if moment().isAfter(@latest_date) then @latest_date else if moment().isBefore(@earliest_date) then @earliest_date else moment() # Earliest if d == 'earliest' return @earliest_date # Latest if d == 'latest' return @latest_date # Convert string to a date if keyword ago or ahead exists if d and (/\bago\b/.test(d) or /\bahead\b/.test(d)) return @stringToDate(d) regex = /(?:\d)((?:st|nd|rd|th)?,?)/ d_array = if d then d.replace(regex, '').split(' ') else [] # Add current year if year is not included if d_array.length == 2 d_array.push moment().format(@format.jump_year) d = d_array.join(' ') # Convert using settings format parsed_d = @parseDate(d) if !parsed_d.isValid() return moment(d) # occurs when parsing preset dates parsed_d Calendar::calendarCheckDates = -> startTxt = $('.dr-date-start', @element).html() endTxt = $('.dr-date-end', @element).html() c = @calendarCheckDate($(@selected).html()) s = undefined e = undefined # Modify strings via some specific keywords to create valid dates # Finally set all strings as dates if startTxt == 'ytd' or endTxt == 'ytd' # Year to date s = moment().startOf('year') e = moment().endOf('year') else s = @calendarCheckDate(startTxt) e = @calendarCheckDate(endTxt) if c.isBefore(@earliest_date) c = @earliest_date if s.isBefore(@earliest_date) s = @earliest_date if e.isBefore(@earliest_date) or e.isBefore(s) e = s.clone().add(6, 'day') if c.isAfter(@latest_date) c = @latest_date if e.isAfter(@latest_date) e = @latest_date if s.isAfter(@latest_date) or s.isAfter(e) s = e.clone().subtract(6, 'day') # Push and save if it's valid otherwise return to previous state if @type == 'double' # Is this a valid date? if s.isSame(e) and !@sameDayRange return @calendarSetDates() @start_date = if s.isValid() then s else @start_date @end_date = if e.isValid() then e else @end_date @current_date = if c.isValid() then c else @current_date return Calendar::stringToDate = (str) -> date_arr = str.split(' ') if date_arr[2] == 'ago' return moment(@current_date).subtract(date_arr[0], date_arr[1]) else if date_arr[2] == 'ahead' return moment(@current_date).add(date_arr[0], date_arr[1]) @current_date Calendar::calendarOpen = (selected, switcher) -> self = this other = undefined cal_width = $('.dr-dates', @element).innerWidth() - 8 @selected = selected or @selected if @presetIsOpen == true @presetToggle() if @calIsOpen == true @calendarClose if switcher then 'switcher' else undefined else if $(@selected).html().length @orig_start_date = @start_date @orig_end_date = @end_date @orig_current_date = @current_date @calendarCheckDates() @calendarCreate switcher @calendarSetDates() next_month = moment(switcher or @current_date).add(1, 'month').startOf('month').startOf('day') past_month = moment(switcher or @current_date).subtract(1, 'month').endOf('month') next_year = moment(switcher or @current_date).add(1, 'year').startOf('month').startOf('day') past_year = moment(switcher or @current_date).subtract(1, 'year').endOf('month') this_moment = moment(switcher or @current_date) $('.dr-month-switcher span', @element).data('month', this_moment.month()).html this_moment.format(@format.jump_month) $('.dr-year-switcher span', @element).data('year', this_moment.year()).html this_moment.format(@format.jump_year) $('.dr-switcher i', @element).removeClass 'dr-disabled' if next_month.isAfter(@latest_date) $('.dr-month-switcher .dr-right', @element).addClass 'dr-disabled' if past_month.isBefore(@earliest_date) $('.dr-month-switcher .dr-left', @element).addClass 'dr-disabled' if next_year.isAfter(@latest_date) $('.dr-year-switcher .dr-right', @element).addClass 'dr-disabled' if past_year.isBefore(@earliest_date) $('.dr-year-switcher .dr-left', @element).addClass 'dr-disabled' $('.dr-day', @element).on mouseenter: -> `var selected` selected = $(this) start_date = moment(self.start_date) end_date = moment(self.end_date) current_date = moment(self.current_date) setMaybeRange = (type) -> other = undefined self.range(6 * 7).forEach (i) -> next = selected.next().data('date') prev = selected.prev().data('date') curr = selected.data('date') if !curr return false if !prev prev = curr if !next next = curr if type == 'start' if moment(next).isSame(self.end_date) or self.sameDayRange and moment(curr).isSame(self.end_date) return false if moment(curr).isAfter(self.end_date) other = other or moment(curr).add(6, 'day').startOf('day') if i > 5 or (if next then moment(next).isAfter(self.latest_date) else false) $(selected).addClass 'dr-end' other = moment(curr) return false selected = selected.next().addClass('dr-maybe') else if type == 'end' if moment(prev).isSame(self.start_date) or self.sameDayRange and moment(curr).isSame(self.start_date) return false if moment(curr).isBefore(self.start_date) other = other or moment(curr).subtract(6, 'day') if i > 5 or (if prev then moment(prev).isBefore(self.earliest_date) else false) $(selected).addClass 'dr-start' other = moment(curr) return false selected = selected.prev().addClass('dr-maybe') return return if $(self.selected).hasClass('dr-date-start') selected.addClass 'dr-hover dr-hover-before' $('.dr-start', self.element).css 'border': 'none' 'padding-left': '0.3125rem' setMaybeRange 'start' if $(self.selected).hasClass('dr-date-end') selected.addClass 'dr-hover dr-hover-after' $('.dr-end', self.element).css 'border': 'none' 'padding-right': '0.3125rem' setMaybeRange 'end' if !self.start_date and !self.end_date selected.addClass 'dr-maybe' $('.dr-selected', self.element).css 'background-color', 'transparent' return mouseleave: -> if $(this).hasClass('dr-hover-before dr-end') $(this).removeClass 'dr-end' if $(this).hasClass('dr-hover-after dr-start') $(this).removeClass 'dr-start' $(this).removeClass 'dr-hover dr-hover-before dr-hover-after' $('.dr-start, .dr-end', self.element).css 'border': '' 'padding': '' $('.dr-maybe:not(.dr-current)', self.element).removeClass 'dr-start dr-end' $('.dr-day', self.element).removeClass 'dr-maybe' $('.dr-selected', self.element).css 'background-color', '' return if /iPad|iPhone|iPod/.test(navigator.userAgent) and !window.MSStream $('.dr-day', @element).on touchstart: -> selectOneDate other, self, $(this).data('date') return $('div[contenteditable]', @element).removeAttr 'contenteditable' else $('.dr-day', @element).on mousedown: -> selectOneDate other, self, $(this).data('date') return $('.dr-calendar', @element).css('width', cal_width).slideDown 200 $('.dr-input', @element).addClass 'dr-active' $(selected).addClass('dr-active').focus() @element.addClass 'dr-active' @calIsOpen = true return Calendar::calendarClose = (type) -> self = this if !@calIsOpen or @presetIsOpen or type == 'force' $('.dr-calendar', @element).slideUp 200, -> $('.dr-day', self.element).remove() return else $('.dr-day', @element).remove() if type == 'switcher' return false $('.dr-input, .dr-date', @element).removeClass 'dr-active' @element.removeClass 'dr-active' @calIsOpen = false return Calendar::calendarCreate = (switcher) -> self = this array = @calendarArray(@start_date, @end_date, @current_date, switcher) array.forEach (d, i) -> classString = 'dr-day' if d.fade classString += ' dr-fade' if d.start classString += ' dr-start' if d.end classString += ' dr-end' if d.current classString += ' dr-current' if d.selected classString += ' dr-selected' if d.outside classString += ' dr-outside' $('.dr-day-list', self.element).append '<li class="' + classString + '" data-date="' + d.date + '">' + d.str + '</li>' return return Calendar::calendarArray = (start, end, current, switcher) -> self = this current = moment(current or start or end).startOf('day') reference = switcher or current or start or end startRange = moment(reference).startOf('month').startOf('week') endRange = moment(startRange).add(6 * 7 - 1, 'days').endOf('day') daysInRange = [] d = moment(startRange) while d.isBefore(endRange) daysInRange.push str: +d.format('D') start: start and d.isSame(start, 'day') end: end and d.isSame(end, 'day') current: current and d.isSame(current, 'day') selected: start and end and d.isBetween(start, end) date: d.toISOString() outside: d.isBefore(self.earliest_date) or d.isAfter(self.latest_date) fade: !d.isSame(reference, 'month') d.add 1, 'd' daysInRange Calendar::calendarHTML = (type) -> ul_days_of_the_week = $('<ul class="dr-days-of-week-list"></ul>') days = @days_array.splice(moment.localeData().firstDayOfWeek()).concat(@days_array.splice(0, moment.localeData().firstDayOfWeek())) $.each days, (i, elem) -> ul_days_of_the_week.append '<li class="dr-day-of-week">' + elem + '</li>' return if type == 'double' return @element.append('<div class="dr-input">' + '<div class="dr-dates">' + '<div class="dr-date dr-date-start" contenteditable>' + moment(@start_date).format(@format.input) + '</div>' + '<span class="dr-dates-dash">&ndash;</span>' + '<div class="dr-date dr-date-end" contenteditable>' + moment(@end_date).format(@format.input) + '</div>' + '</div>' + (if @presets then '<div class="dr-presets">' + '<span class="dr-preset-bar"></span>' + '<span class="dr-preset-bar"></span>' + '<span class="dr-preset-bar"></span>' + '</div>' else '') + '</div>' + '<div class="dr-selections">' + '<div class="dr-calendar" style="display: none;">' + '<div class="dr-range-switcher">' + '<div class="dr-switcher dr-month-switcher">' + '<i class="dr-left"></i>' + '<span>April</span>' + '<i class="dr-right"></i>' + '</div>' + '<div class="dr-switcher dr-year-switcher">' + '<i class="dr-left"></i>' + '<span>2015</span>' + '<i class="dr-right"></i>' + '</div>' + '</div>' + ul_days_of_the_week[0].outerHTML + '<ul class="dr-day-list"></ul>' + '</div>' + (if @presets then @presetCreate()[0].outerHTML else '') + '</div>') @element.append '<div class="dr-input">' + '<div class="dr-dates">' + '<div class="dr-date" contenteditable placeholder="' + @placeholder + '">' + (if @settings.current_date then moment(@current_date).format(@format.input) else '') + '</div>' + '</div>' + '</div>' + '<div class="dr-selections">' + '<div class="dr-calendar" style="display: none;">' + '<div class="dr-range-switcher">' + '<div class="dr-switcher dr-month-switcher">' + '<i class="dr-left"></i>' + '<span></span>' + '<i class="dr-right"></i>' + '</div>' + '<div class="dr-switcher dr-year-switcher">' + '<i class="dr-left"></i>' + '<span></span>' + '<i class="dr-right"></i>' + '</div>' + '</div>' + ul_days_of_the_week[0].outerHTML + '<ul class="dr-day-list"></ul>' + '</div>' + '</div>' Calendar::parseDate = (d) -> if moment.defaultZone != null and moment.hasOwnProperty('tz') moment.tz d, @format.input, moment.defaultZone.name else moment d, @format.input Calendar::range = (length) -> range = new Array(length) idx = 0 while idx < length range[idx] = idx idx++ range export default Calendar