/** * Zabuto Calendar jQuery Plugin */ (function ($, window, document, undefined) { "use strict"; /** * Plugin name */ var pluginName = 'zabuto_calendar'; /** * Current date */ var now = new Date(); /** * Plugin constructor */ function ZabutoCalendar(element, options) { this.element = element; this._name = pluginName; this._defaults = $.fn[pluginName].defaults; this.settings = $.extend({}, this._defaults, options); if (null !== this.settings.translation) { this.settings.language = null; } else { this.settings.language = this.settings.language.toLowerCase(); } this.init(); } /** * Plugin wrapper */ $.fn[pluginName] = function (options) { var argsArray; if (options !== undefined) { var args = $.makeArray(arguments); argsArray = args.slice(1); } return this.each(function () { var instance = $.data(this, 'plugin_' + pluginName); if (!instance) { $.data(this, 'plugin_' + pluginName, new ZabutoCalendar(this, options)); } else { if (typeof options === 'string' && typeof instance[options] === 'function') { instance[options].apply(instance, argsArray); } } }); }; /** * Defaults */ $.fn[pluginName].defaults = { year: now.getFullYear(), month: (now.getMonth() + 1), language: 'en', translation: null, week_starts: 'monday', show_days: true, classname: null, header_format: '[month] [year]', date_format: 'y-m-d', navigation_prev: true, navigation_next: true, navigation_markup: { prev: '◄', next: '►' }, today_markup: null, events: null, ajax: null }; /** * Languages */ $.fn[pluginName].languages = {}; /** * Functions */ $.extend(ZabutoCalendar.prototype, { /** * Initialize */ init: function () { var event = $.Event('zabuto:calendar:init'); event.settings = this.settings; var element = $(this.element); element.trigger(event); this.goto(this.settings.year, this.settings.month); }, /** * Destroy */ destroy: function () { var element = $(this.element); element.removeData('plugin_' + pluginName); element.removeData('year'); element.removeData('month'); element.removeData('event-data'); element.empty(); element.trigger('zabuto:calendar:destroy'); }, /** * Reload */ reload: function () { var element = $(this.element); var event = $.Event('zabuto:calendar:reload'); event.year = element.data('year'); event.month = element.data('month'); element.trigger(event); this.data(); }, /** * Go to month/year */ goto: function (year, month) { if (false === this._isValidDate(year, month, 1)) { return; } var event = $.Event('zabuto:calendar:goto'); event.year = year; event.month = month; var element = $(this.element); element.data('year', year); element.data('month', month); element.trigger(event); this.data(); }, /** * Get data */ data: function () { var self = this; var element = $(this.element); var handle = self._getEventHandle(); if (null === handle) { element.data('event-data', []); this.render(); } else if (handle.type === 'fixed') { var data = self._eventsToDays(handle.data); var event = $.Event('zabuto:calendar:data'); event.type = 'fixed'; event.eventlist = handle.data; event.eventdata = data; element.data('event-data', data); element.trigger(event); self.render(); } else if (handle.type === 'ajax') { var ajaxSettings = handle.settings; ajaxSettings.data = {year: element.data('year'), month: element.data('month')}; ajaxSettings.dataType = 'json'; $.ajax(ajaxSettings) .done(function (response) { var data = self._eventsToDays(response); var event = $.Event('zabuto:calendar:data'); event.type = 'ajax'; event.eventlist = response; event.eventdata = data; element.data('event-data', data); element.trigger(event); self.render(); }) .fail(function (jqXHR, textStatus, errorThrown) { var event = $.Event('zabuto:calendar:data-fail'); event.text = textStatus; event.error = errorThrown; element.data('event-data', []); element.trigger(event); self.render(); }); } }, /** * Render calendar */ render: function () { var element = $(this.element); var year = element.data('year'); var month = element.data('month'); var preEvent = $.Event('zabuto:calendar:preRender'); preEvent.year = year; preEvent.month = month; element.trigger(preEvent); element.empty(); if (this._isValidDate(year, month, 1)) { element.append(this._renderTable(year, month)); } var postEvent = $.Event('zabuto:calendar:render'); postEvent.year = year; postEvent.month = month; element.trigger(postEvent); }, /** * Render table */ _renderTable: function (year, month) { var table = $('
').addClass('zabuto-calendar'); if (this.settings.classname) { table.addClass(this.settings.classname); } var thead = $(''); thead.append(this._renderNavigation(year, month)); if (true === this.settings.show_days) { thead.append(this._renderDaysOfWeek()); } var tbody = this._renderDaysInMonth(year, month); table.append(thead); table.append(tbody); return table; }, /** * Render navigation */ _renderNavigation: function (year, month) { var self = this; var label = self.settings.header_format; label = label.replace('[year]', year.toString()); var translation = self._getTranslation(); if (null !== translation && 'months' in translation) { var labels = translation['months']; label = label.replace('[month]', labels[month.toString()]); } else { label = label.replace('[month]', month.toString()); } var nav = $('').addClass('zabuto-calendar__navigation').attr('role', 'navigation'); var toPrev = self._calculatePrevious(year, month); var toNext = self._calculateNext(year, month); var title = $('').text(label).data('to', { year: self.settings.year, month: self.settings.month }); title.addClass('zabuto-calendar__navigation__item--header__title'); if (null !== toPrev || null !== toNext) { title.on('zabuto:calendar:navigate-init', function (event) { var to = $(this).data('to'); event.year = to.year; event.month = to.month; self.goto(to.year, to.month); }).on('dblclick', function () { $(this).trigger('zabuto:calendar:navigate-init'); }); } var header = $(''); header.addClass('zabuto-calendar__navigation__item--header'); header.append(title); if (null === toPrev && null === toNext) { nav.append(header.attr('colspan', 7)); } else { nav.append(self._renderNavigationItem('prev', toPrev)); nav.append(header.attr('colspan', 5)); nav.append(self._renderNavigationItem('next', toNext)); } return nav; }, /** * Render navigation item */ _renderNavigationItem: function (type, to) { var self = this; type = type.toString(); var item = $('').data('nav', type).data('to', to); item.addClass('zabuto-calendar__navigation__item--' + type); if (null !== to) { if (type in self.settings.navigation_markup) { item.html(self.settings.navigation_markup[type]); } else { item.html(type); } item.on('zabuto:calendar:navigate', function (event) { var to = $(this).data('to'); event.year = to.year; event.month = to.month; self.goto(to.year, to.month); }).on('click', function () { $(this).trigger('zabuto:calendar:navigate'); }); } return item; }, /** * Render days of week row */ _renderDaysOfWeek: function () { var start = this.settings.week_starts; var labels = {"0": "0", "1": "1", "2": "2", "3": "3", "4": "4", "5": "5", "6": "6"}; var translation = this._getTranslation(); if (null !== translation && 'days' in translation) { labels = translation['days']; } var dow = $('').addClass('zabuto-calendar__days-of-week'); if (start === 0 || start === '0' || start === 'sunday') { dow.append($('').data('dow', 0).text(labels['0']).addClass('zabuto-calendar__days-of-week__item')); } dow.append($('').data('dow', 1).text(labels['1']).addClass('zabuto-calendar__days-of-week__item')); dow.append($('').data('dow', 2).text(labels['2']).addClass('zabuto-calendar__days-of-week__item')); dow.append($('').data('dow', 3).text(labels['3']).addClass('zabuto-calendar__days-of-week__item')); dow.append($('').data('dow', 4).text(labels['4']).addClass('zabuto-calendar__days-of-week__item')); dow.append($('').data('dow', 5).text(labels['5']).addClass('zabuto-calendar__days-of-week__item')); dow.append($('').data('dow', 6).text(labels['6']).addClass('zabuto-calendar__days-of-week__item')); if (start === 1 || start === '1' || start === 'monday') { dow.append($('').data('dow', 0).text(labels['0']).addClass('zabuto-calendar__days-of-week__item')); } return dow; }, /** * Render days of the month */ _renderDaysInMonth: function (year, month) { var self = this; var start = self.settings.week_starts; var weeks = self._calculateWeeksInMonth(year, month); var days = self._calculateLastDayOfMonth(year, month); var firstDow = self._calculateDayOfWeek(year, month, 1); var dows = [0, 1, 2, 3, 4, 5, 6]; var checkDow = firstDow; if (start === 1 || start === '1' || start === 'monday') { dows = [1, 2, 3, 4, 5, 6, 7]; checkDow = (firstDow === 0) ? 7 : firstDow; } var tbody = $(''); var day = 1; for (var wk = 1; wk <= weeks; wk++) { var row = self._renderWeek(wk, weeks); $.each(dows, function (i, dow) { if ((wk === 1 && dow < checkDow) || day > days) { row.append($('').addClass('zabuto-calendar__day--empty')); } else { var cell = self._renderDay(year, month, day, dow); row.append(cell); day++; } }); tbody.append(row); } return tbody; }, /** * Render single week */ _renderWeek: function (week, weeks) { var row = $(''); if (week === 1) { row.addClass('zabuto-calendar__week--first'); } else if (week === weeks) { row.addClass('zabuto-calendar__week--last'); } else { row.addClass('zabuto-calendar__week'); } return row; }, /** * Render single day */ _renderDay: function (year, month, day, dow) { var date = this._dateAsString(year, month, day); var eventdata = this._eventsForDay(date); var cell = $(''); cell.data('date', date); cell.data('year', year); cell.data('month', month); cell.data('day', day); cell.data('dow', (dow === 7 ? 0 : dow)); cell.data('eventdata', eventdata); if (this._isToday(year, month, day)) { cell.data('today', 1); cell.addClass('zabuto-calendar__day--today'); if (this.settings.today_markup) { var todayMarkup = this.settings.today_markup; todayMarkup = todayMarkup.replace('[day]', day); cell.html(todayMarkup); } else { cell.text(day); } } else { cell.data('today', 0); cell.addClass('zabuto-calendar__day'); cell.text(day); } if (null !== eventdata) { cell.data('hasEvent', 1); cell.addClass('zabuto-calendar__event'); $.each(eventdata.classnames, function (i, val) { cell.addClass(val); }); if (null !== eventdata.markup) { var eventMarkup = eventdata.markup; eventMarkup = eventMarkup.replace('[day]', day); cell.html(eventMarkup); } } else { cell.data('hasEvent', 0); } cell.on('zabuto:calendar:day', function (event) { event.element = $(this); event.date = new Date($(this).data('year'), ($(this).data('month') - 1), $(this).data('day')); event.value = $(this).data('date'); event.today = !!($(this).data('today')); event.hasEvent = !!($(this).data('hasEvent')); event.eventdata = eventdata; }).on('click', function () { $(this).trigger('zabuto:calendar:day'); }); return cell; }, /** * Get translation */ _getTranslation: function () { var translation = this.settings.translation; if (null !== translation && typeof translation === 'object' && 'months' in translation && 'days' in translation) { return translation; } var locale = this.settings.language; var languages = $.fn[pluginName].languages; if (locale in languages) { return languages[locale]; } return null; }, /** * Calculate number of weeks in the month */ _calculateWeeksInMonth: function (year, month) { var start = this.settings.week_starts; var daysInMonth = this._calculateLastDayOfMonth(year, month); var firstDow = this._calculateDayOfWeek(year, month, 1); var lastDow = this._calculateDayOfWeek(year, month, daysInMonth); var first = firstDow; var last = lastDow; if (start === 1 || start === '1' || start === 'monday') { first = (firstDow === 0) ? 7 : firstDow; last = (lastDow === 0) ? 7 : lastDow; } var offset = first - last; var days = daysInMonth + offset; return Math.ceil(days / 7); }, /** * Calculate the last day of the month */ _calculateLastDayOfMonth: function (year, month) { var jsMonth = month - 1; var date = new Date(year, jsMonth + 1, 0); return date.getDate(); }, /** * Calculate day of the week (from 0 to 6) */ _calculateDayOfWeek: function (year, month, day) { var jsMonth = month - 1; var date = new Date(year, jsMonth, day); return date.getDay(); }, /** * Calculate previous month/year */ _calculatePrevious: function (year, month) { if (false === this.settings.navigation_prev) { return null; } var prevYear = year; var prevMonth = (month - 1); if (prevMonth === 0) { prevYear = (year - 1); prevMonth = 12; } return {year: prevYear, month: prevMonth}; }, /** * Calculate next month/year */ _calculateNext: function (year, month) { if (false === this.settings.navigation_next) { return null; } var nextYear = year; var nextMonth = (month + 1); if (nextMonth === 13) { nextYear = (year + 1); nextMonth = 1; } return {year: nextYear, month: nextMonth}; }, /** * Check if date is valid */ _isValidDate: function (year, month, day) { if (month < 1 || month > 12) { return false; } var jsMonth = month - 1; var date = new Date(year, jsMonth, day); return date.getFullYear() === year && (date.getMonth()) === jsMonth && date.getDate() === Number(day); }, /** * Check if date is today */ _isToday: function (year, month, day) { var jsMonth = month - 1; var today = new Date(); var date = new Date(year, jsMonth, day); return (date.toDateString() === today.toDateString()); }, /** * Parse date string */ _dateAsString: function (year, month, day) { var string = this.settings.date_format; day = (day < 10) ? '0' + day : day; month = (month < 10) ? '0' + month : month; string = string.replace('y', year); string = string.replace('m', month); string = string.replace('d', day); return string; }, /** * Get event data handling */ _getEventHandle: function () { var events = this.settings.events; if (null !== events && typeof events === 'object') { return {type: 'fixed', data: events}; } var ajaxSettings = this.settings.ajax; if (null !== ajaxSettings) { if (typeof ajaxSettings === 'string') { ajaxSettings = { type: 'GET', url: ajaxSettings, cache: false }; } return {type: 'ajax', settings: ajaxSettings}; } return null; }, /** * Convert events array to day-with-events object */ _eventsToDays: function (events) { var data = []; $.each(events, function (idx, event) { if (typeof event === 'object' && 'date' in event) { var date = event.date; var day = {count: 0, classnames: [], markup: null, events: []}; if (date in data) { day = data[date]; } day.count = day.count + 1; day.events.push(event); if ('classname' in event && event.classname !== null) { day.classnames.push(event.classname); } if ('markup' in event && event.markup !== null) { day.markup = event.markup; } data[date] = day; } }); return data; }, /** * Get day-with-events object for specific day */ _eventsForDay: function (date) { var element = $(this.element); var eventData = element.data('event-data'); if (!(date in eventData)) { return null; } var dayData = eventData[date]; var event = $.Event('zabuto:calendar:day-event'); event.value = date; event.eventdata = dayData; element.trigger(event); return dayData; } }); })(jQuery, window, document, undefined);