/*! * ZUI: 日历 - v1.8.1 - 2018-01-18 * http://zui.sexy * GitHub: https://github.com/easysoft/zui.git * Copyright (c) 2018 cnezsoft.com; Licensed MIT */ /* ======================================================================== * ZUI: calendar.js * http://zui.sexy * ======================================================================== * Copyright (c) 2014 cnezsoft.com; Licensed MIT * ======================================================================== */ (function($, window) { 'use strict'; var NAME = 'zui.calendar'; var NUMBER_TYPE_NAME = 'number'; var STRING_TYPE_NAME = 'string'; var UNDEFINED_TYPE_NAME = 'undefined'; var presetColors = { "primary": 1, "green": 2, "red": 3, "blue": 4, "yellow": 5, "brown": 6, "purple": 7 }; var getNearbyLastWeekDay = function(date, lastWeek) { lastWeek = lastWeek || 1; var d = date.clone(); while(d.getDay() != lastWeek) { d.addDays(-1); } d.clearTime(); return d; }, getFirstDayOfMonth = function(date) { var d = date.clone(); d.setDate(1); return d; }, calculateDays = function(start, end) { var s = start.clone().clearTime(); var e = end.clone().clearTime(); return Math.round((e.getTime() - s.getTime()) / Date.ONEDAY_TICKS) + 1; }, everyDayTo = function(start, end, callback) { var a = start.clone(); var i = 0; while(a <= end) { callback(a.clone(), i++); a.addDays(1); } }; var Calendar = function(element, options) { this.name = NAME; this.$ = $(element); this.id = this.$.attr('id') || (NAME + $.zui.uuid()); this.$.attr('id', this.id); this.storeName = NAME + '.' + this.id; this.getOptions(options); this.getLang(); this.data = this.options.data; this.addCalendars(this.data.calendars); this.addEvents(this.data.events); this.sortEvents(); this.storeData = $.zui.store.pageGet(this.storeName, { date: 'today', view: 'month' }); this.date = this.options.startDate || 'today'; this.view = this.options.startView || 'month'; this.$.toggleClass('limit-event-title', options.limitEventTitle); if(this.options.withHeader) { var $header = this.$.children('.calender-header'); if(!$header.length) { $header = $('
'.format(this.lang)); this.$.append($header); } this.$caption = $header.find('.calendar-caption'); this.$todayBtn = $header.find('.btn-today'); this.$header = $header; } var $views = this.$.children('.calendar-views'); if(!$views.length) { $views = $('
'); this.$.append($views); } this.$views = $views; this.$monthView = $views.children('.calendar-view.month'); this.display(); this.bindEvents(); }; // default options Calendar.DEFAULTS = { langs: { zh_cn: { weekNames: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], monthNames: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], today: '今天', year: '{0}年', month: '{0}月', yearMonth: '{0}年{1}月' }, zh_tw: { weekNames: ['週一', '週二', '週三', '週四', '週五', '週六', '週日'], monthNames: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], today: '今天', year: '{0}年', month: '{0}月', yearMonth: '{0}年{1}月' }, en: { weekNames: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], monthNames: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], today: 'Today', year: '{0}', month: '{0}', yearMonth: '{2}, {0}' } }, data: { calendars: { defaultCal: { color: '#229F24' } }, events: [] }, // startView: "month", // default view when load complete // startDate: 'today', // default date when load complete limitEventTitle: true, storage: true, withHeader: true, dragThenDrop: true, // drag an event and drop at another day, // hideEmptyWeekends: false // Auto hide empty weekends }; // Sort events by start datetime Calendar.prototype.sortEvents = function() { var events = this.events; if(!$.isArray(events)) { events = []; } events.sort(function(a, b) { return a.start < b.start ? 1 : (a.start > b.start ? (-1) : 0); }); this.events = events; }; Calendar.prototype.bindEvents = function() { var $e = this.$, self = this; $e.on('click', '.btn-today', function() { self.date = new Date(); self.display(); self.callEvent('clickTodayBtn'); }).on('click', '.btn-next', function() { if(self.view === 'month') { self.date.addMonths(1); } self.display(); self.callEvent('clickNextBtn'); }).on('click', '.btn-prev', function() { if(self.view === 'month') { self.date.addMonths(-1); } self.display(); self.callEvent('clickPrevBtn'); }).on('click', '.event', function(event) { self.callEvent('clickEvent', { element: this, event: $(this).data('event'), events: self.events }); event.stopPropagation(); }).on('click', '.cell-day', function() { self.callEvent('clickCell', { element: this, view: self.view, date: new Date($(this).children('.day').attr('data-date')), events: self.events }); }); }; Calendar.prototype.addCalendars = function(calendars, silence) { var that = this; if(!that.calendars) that.calendars = {}; if($.isPlainObject(calendars)) { calendars = [calendars]; } $.each(calendars, function(index, cal) { if(!silence && !that.callEvent('beforeAddCalendars', { newCalendar: cal, data: that.data })) { return; } if(!cal.color) cal.color = 'primary'; if(!presetColors[cal.color.toLowerCase()]) { var c = new $.zui.Color(cal.color); cal.textColor = c.contrast().hexStr(); } else { cal.presetColor = true; } that.calendars[cal.name] = cal; }); if(!silence) { that.display(); that.callEvent('addCalendars', { newCalendars: calendars, data: that.data }); } }; Calendar.prototype.addEvents = function(events, silence) { var that = this; if(!that.events) that.events = []; if($.isPlainObject(events)) { events = [events]; } $.each(events, function(index, e) { if(!silence && !that.callEvent('beforeAddEvent', { newEvent: e, data: that.data })) { return; } var startType = typeof e.start; var endType = typeof e.end; if(startType === NUMBER_TYPE_NAME || startType === STRING_TYPE_NAME) { e.start = new Date(e.start); } if(endType === NUMBER_TYPE_NAME || endType === STRING_TYPE_NAME) { e.end = new Date(e.end); } if(typeof e.id === UNDEFINED_TYPE_NAME) { e.id = $.zui.uuid(); } if(e.allDay) { e.start.clearTime(); e.end.clearTime().addDays(1).addMilliseconds(-1); } e.days = calculateDays(e.start, e.end); that.events.push(e); }); if(!silence) { that.sortEvents(); that.display(); that.callEvent('addEvents', { newEvents: events, data: that.data }); } }; Calendar.prototype.getEvent = function(id) { var events = this.events; for(var i = 0; i < events.length; i++) { if(events[i].id == id) { return events[i]; } } return null; }; Calendar.prototype.updateEvents = function(events) { var eventsParams = { data: this.data, changes: [] }, that = this; if($.isPlainObject(events)) { events = [events]; } var event, chgs, eventParam; $.each(events, function(index, changes) { event = changes.event; chgs = changes.changes; eventParam = { event: event, changes: [] }; if(typeof event === STRING_TYPE_NAME) { event = that.getEvent(event); } if(event) { if($.isPlainObject(chgs)) { chgs = [chgs]; } $.each(changes, function(idx, chge) { if(that.callEvent('beforeChange', { event: event, change: chge.change, to: chge.to, from: event[chge.change] })) { eventParam.changes.push($.extend(true, {}, chge, { from: event[chge.change] })); event[chge.change] = chge.to; } }); } eventsParams.changes.push(eventParam); }); that.sortEvents(); that.display(); that.callEvent('change', eventsParams); }; Calendar.prototype.removeEvents = function(events) { if(!$.isArray(events)) { events = [events]; } var id, event, idx, evts = this.events, that = this, removedEvents = []; $.each(events, function(index, value) { id = $.isPlainObject(value) ? value.id : value; idx = -1; for(var i = 0; i < evts.length; i++) { if(evts[i].id == id) { idx = i; event = evts[i]; break; } } if(idx >= 0 && that.callEvent('beforeRemoveEvent', { event: event, eventId: id, data: that.data })) { evts.splice(idx, 1); removedEvents.push(event); } }); that.sortEvents(); that.display(); that.callEvent('removeEvents', { removedEvents: removedEvents, data: that.data }); }; Calendar.prototype.getOptions = function(options) { this.options = $.extend({}, Calendar.DEFAULTS, this.$.data(), options); }; Calendar.prototype.getLang = function() { this.lang = this.options.langs[this.options.lang || ($.zui && $.zui.clientLang ? $.zui.clientLang() : 'zh-cn')]; }; Calendar.prototype.display = function(view, date) { var that = this; var viewType = typeof view; var dateType = typeof date; if(viewType === UNDEFINED_TYPE_NAME) { view = that.view; } else { that.view = view; } if(dateType === UNDEFINED_TYPE_NAME) { date = that.date; } else { that.date = date; } if(date === 'today') { date = new Date(); that.date = date; } if(typeof date === STRING_TYPE_NAME) { date = new Date(date); that.date = date; } if(that.options.storage) { $.zui.store.pageSet(that.storeName, { date: date, view: view }); } var eventPramas = { view: view, date: date }; if(that.callEvent('beforeDisplay', eventPramas)) { switch(view) { case 'month': that.displayMonth(date); break; } that.callEvent('display', eventPramas); } }; Calendar.prototype.displayMonth = function(date) { var that = this; date = date || that.date; var options = that.options, lang = that.lang, i, $views = that.$views, $e = that.$; var $view = that.$monthView; if(!$view.length) { $view = $('
'); var $weekHead = $view.find('.week-head'), $monthDays = $view.find('.month-days'), $tr; for(i = 0; i < 7; i++) { $('' + lang.weekNames[i] + '').toggleClass('weekend-head', i >= 5).appendTo($weekHead); } for(i = 0; i < 6; i++) { $tr = $(''); for(var j = 0; j < 7; j++) { $('
').toggleClass('weekend-day', j >= 5).appendTo($tr); } $monthDays.append($tr); } $views.append($view); that.$monthView = $view; } var $weeks = $view.find('.week-days'), $days = $view.find('.day'), firstDayOfMonth = getFirstDayOfMonth(date), // lastDayOfMonth = getLastDayOfMonth(date), $week, $day, $cell, year, day, month, today = new Date(); var firstDay = getNearbyLastWeekDay(firstDayOfMonth), thisYear = date.getFullYear(), thisMonth = date.getMonth(), todayMonth = today.getMonth(), todayYear = today.getFullYear(), todayDate = today.getDate(); var lastDay = firstDay.clone().addDays(6 * 7).addMilliseconds(-1), printDate = firstDay.clone().addDays(1).addMilliseconds(-1); var events = that.getEvents(firstDay, lastDay), calendars = that.calendars, printDateId, isFirstDayOfWeek, $event, cal, $dayEvents; var isEmptyWeekends = true; $weeks.each(function(weekIdx) { $week = $(this); $week.find('.day').each(function(dayIndex) { isFirstDayOfWeek = dayIndex === 0; $day = $(this); $cell = $day.closest('.cell-day'); year = printDate.getFullYear(); day = printDate.getDate(); month = printDate.getMonth(); printDateId = printDate.toDateString(); $day.attr('data-date', printDateId); $day.find('.heading > .number').text(day); $day.find('.heading > .month') .toggle((weekIdx === 0 && dayIndex === 0) || day === 1) .text(((month === 0 && day === 1) ? (lang.year.format(year) + ' ') : '') + lang.monthNames[month]); $cell.toggleClass('current-month', month === thisMonth); $cell.toggleClass('current', (day === todayDate && month === todayMonth && year === todayYear)); $cell.toggleClass('past', printDate < today); $cell.toggleClass('future', printDate > today); $dayEvents = $day.find('.events').empty(); var dayEvents = events[printDateId]; if(dayEvents) { var eventsMap = dayEvents.events, stripCount = 0, e; for(i = 0; i <= dayEvents.maxPos; ++i) { e = eventsMap[i]; if(!e || (e.placeholder && !isFirstDayOfWeek)) { stripCount++; continue; } if (isEmptyWeekends && dayIndex >= 5) { isEmptyWeekends = false; } $event = $('
' + e.start.format('hh:mm') + ' ' + e.title + '
'); $event.find('.time').toggle(!e.allDay); $event.data('event', e); $event.attr('data-days', e.days); if(e.calendar) { cal = calendars[e.calendar]; if(cal) { if(cal.presetColor) { $event.addClass('color-' + cal.color); } else { $event.css({ 'background-color': cal.color, color: cal.textColor }); } } } if(e.days) { if(!e.placeholder) { $event.css('width', Math.min(7 - dayIndex, e.days) + '00%'); } else if(isFirstDayOfWeek) { $event.css('width', Math.min(7, e.days - e.holderPos) + '00%'); } } if(stripCount > 0) { $event.css('margin-top', stripCount * 22); stripCount = 0; } $dayEvents.append($event); } } printDate.addDays(1); }); }); if (options.hideEmptyWeekends) { $view.toggleClass('weekends-empty', isEmptyWeekends); } if(options.withHeader) { that.$caption.text(lang.yearMonth.format(thisYear, thisMonth + 1, lang.monthNames[thisMonth])); that.$todayBtn.toggleClass('disabled', thisMonth === todayMonth && thisYear === todayYear); } if(options.dragThenDrop) { if(!$.fn.droppable) { return console.error('Calendar dragThenDrop option requires droppable.js'); } if(!$view.data('zui.droppable')) { $view.droppable($.extend({ target: '.cell-day', selector: '.event', flex: true, start: function() { that.$.addClass('event-dragging'); }, drop: function(e) { var et = e.element.data('event'), newDate = e.target.children('.day').attr('data-date'); if(!et || !newDate) return; var startDate = et.start.clone(); if(startDate.toDateString() != newDate) { newDate = new Date(newDate); newDate.setHours(startDate.getHours()); newDate.setMinutes(startDate.getMinutes()); newDate.setSeconds(startDate.getSeconds()); if(that.callEvent('beforeChange', { event: et, change: 'start', to: newDate })) { var oldEnd = et.end.clone(); et.end.addMilliseconds(et.end.getTime() - startDate.getTime()); et.start = newDate; that.display(); that.callEvent('change', { data: that.data, changes: [{ event: et, changes: [{ change: 'start', from: startDate, to: et.start }, { change: 'end', from: oldEnd, to: et.end }] }] }); } } }, finish: function() { that.$.removeClass('event-dragging'); } }, ($.isPlainObject(options.dragThenDrop)) ? options.dragThenDrop : null)); } } }; Calendar.prototype.getEvents = function(start, end) { var events = {}; var calendars = this.calendars; var addEventToDay = function(day, event, position) { var dayId = day.toDateString(); var dayEvents = events[dayId]; if(!dayEvents) { dayEvents = { maxPos: -1, events: {} }; } if(typeof position === 'undefined') { for(var i = 0; i < 100; ++i) { if(!dayEvents.events[i]) { position = i; break; } } } dayEvents.maxPos = Math.max(position, dayEvents.maxPos); dayEvents.events[position] = event; events[dayId] = dayEvents; return position; }; $.each(this.events, function(index, e) { if(e.start >= start && e.start <= end) { var position = addEventToDay(e.start, e); if(e.days > 1) { var placeholder = $.extend({ placeholder: true }, e); everyDayTo(e.start.clone().addDays(1), e.end, function(thisDay, i) { addEventToDay(thisDay.clone(), $.extend({ holderPos: i }, placeholder), position); }); } } }); return events; }; Calendar.prototype.callEvent = function(name, params) { var result = this.$.callEvent(name + '.' + NAME, params, this); return !(result.result !== undefined && (!result.result)); }; $.fn.calendar = function(option) { return this.each(function() { var $this = $(this); var data = $this.data(NAME); var options = typeof option == 'object' && option; if(!data) $this.data(NAME, (data = new Calendar(this, options))); if(typeof option == STRING_TYPE_NAME) data[option](); }); }; $.fn.calendar.Constructor = Calendar; }(jQuery, window));