/** * @requires $ jQuery * @requires _ UnderscoreJS * @requires moment * @requires $.fn.fullCalendar * @requires $.fn.required */ (function (/* Object */ ns) { 'use strict'; /** * @alias app.event.Calendar * @param {Object} options * @constructor */ ns.Calendar = function (options) { this.construct.apply(this, arguments); }; ns.Calendar.prototype.construct = function (options) { this.debug = qs.constant('DEBUG'); this.options = $.extend({ wideModeWidth: 600, defaultDate: undefined, defaultView: undefined, containerId: undefined, calendarId: undefined, eventListId: undefined, finalUrl: undefined, detailsUrl: undefined, list: undefined, calendarAlias: {}, tip: { overwrite: false, content: { text: ' ' }, position: { my: 'bottom left', at: 'top left', target: 'mouse', viewport: $('body'), adjust: { mouse: false, x: 10, y: -10 } }, show: { solo: true, ready: true }, hide: { fixed: true, delay: 500 }, style: { classes: 'qtip-bootstrap calendar-tip' } }, calendarOptions: { lang: 'en', allDaySlot: false, editable: false, eventStartEditable: false, eventDurationEditable: false, scrollTime: '00:00:00', header: { left: 'prev,next today', center: 'title', right: 'month,agendaWeek,agendaDay' }, axisFormat: 'h:mm a', titleFormat: { month: 'MMMM YYYY', week: 'MMM D, YYYY', day: 'MMM D, YYYY' }, columnFormat: { month: 'ddd', week: 'ddd M/D', day: 'dddd' }, timeFormat: { 'agenda': 'h:mm a', 'default': '' }, buttonTitle: { prev: 'Back', next: 'Forward' } } }, options); this.wideMode = true; this.eventcache = {}; this.historySupported = (typeof this.options.historySupported != 'undefined') ? this.historySupported : !!(window.history && history.pushState); this.$container = $('#' + this.options.containerId).required(); this.$calendar = $('#' + this.options.calendarId).required(); this.$eventList = $('#' + this.options.eventListId).required(); this.tip = null; this.initEvents(); this.initCalendar(); return this; }; ns.Calendar.prototype.initEvents = function () { $(window) .on('popstate', _.bind(this.onPopState, this)) .on('resize', _.bind(this.onResize, this)); this.onResize(); if ('onorientationchange' in window) { $(window).on('orientationchange', _.bind(this.onOrientationChange, this)) } return this; }; ns.Calendar.prototype.onOrientationChange = function () { if (this.tip && this.tip.length) { this.tip.qtip('api').hide(); } }; ns.Calendar.prototype.onPopState = function () { this.debug && console.log('Calendar popstate', document.location.href, JSON.stringify(event.state)); var state = event.state; var viewName = this.$calendar.fullCalendar('getView'), date = this.$calendar.fullCalendar('getDate').format(); if (state && state.viewName !== viewName && state.date !== date) { this.$calendar.fullCalendar('gotoDate', state.date); this.$calendar.fullCalendar('changeView', state.viewName); } }; ns.Calendar.prototype.onResize = function () { var docElement = document.documentElement, body; var width = docElement.clientWidth || document.getElementsByTagName('body')[0].clientWidth; if (this.wideMode) { if (width < this.options.wideModeWidth) { this.wideMode = false; this.$calendar.hide(); this.$eventList.show(); } } else { if (width >= this.options.wideModeWidth) { this.wideMode = true; this.$calendar.show(); this.$eventList.hide(); } } }; ns.Calendar.prototype.initCalendar = function () { var options = $.extend({}, this.options.calendarOptions, { viewDestroy: _.bind(this.onViewDestroy, this), viewRender: _.bind(this.onViewRender, this), eventSources: [] }); if ('ontouchstart' in document.documentElement) { options.eventClick = _.bind(this.onEventTouch, this); } else { options.eventMouseover = _.bind(this.onEventMouseOver, this); options.eventClick = _.bind(this.onEventClick, this); } if (this.options.defaultDate) { options.defaultDate = this.options.defaultDate; } if (this.options.defaultView) { options.defaultView = this.options.defaultView; } options.eventSources.push({ events: _.bind(this.getEvents, this) }); this.$calendar.fullCalendar(options); return this; }; ns.Calendar.prototype.onEventMouseOver = function (event, e, view) { this.debug && console.log('Calendar onEventMouseOver', event.id); var options = $.extend(true, {}, this.options.tip, { content: { text: _.bind(this.loadTipText, this, event.id) }, show: { event: e.type } }); this.tip = $(e.currentTarget).qtip(options); }; ns.Calendar.prototype.onViewDestroy = function (view, element) { $('.calendar-tip').remove(); }; ns.Calendar.prototype.getEvents = function (start, end, timezone, callback) { this.debug && console.log('Calendar getEvents', start.format(), end.format(), timezone); var self = this; if (this.options.list) { callback(this.options.list); this.options.list = null; return; } qs.ajax(this.options.finalUrl, { action: 'load', date: this.$calendar.fullCalendar('getDate').format(), start: start.format(), end: end.format(), timezone: timezone }).done(function (data) { self.$eventList.html(data.listHtml); callback(data.list); }); }; ns.Calendar.prototype.onEventTouch = function (event, e, view) { this.debug && console.log('Calendar onEventTouch', event.id); var options = $.extend(true, {}, this.options.tip, { position: { target: false }, content: { text: _.bind(this.loadTipText, this, event.id) }, show: { event: e.type } }); this.tip = $(e.currentTarget).qtip(options); }; ns.Calendar.prototype.onEventClick = function (event, e, view) { this.debug && console.log('Calendar onEventClick', event.id); location.href = this.options.detailsUrl + '/' + event.alias; }; ns.Calendar.prototype.onViewRender = function (view, element) { this.debug && console.log('Calendar onViewRender', view.name, view.start.format(), view.end.format()); this.updateState(view, element); }; ns.Calendar.prototype.updateState = function (view, element) { var viewAlias, url; var printLink = this.$container.find('[data-print-url]'); printLink.prop('href', printLink.data('printUrl') + '/' + view.intervalStart.format('YYYY-MM')); if (this.historySupported) { viewAlias = this.options.calendarAlias[view.name]; url = this.options.finalUrl + '/' + viewAlias + '/' + view.intervalStart.format(); this.debug && console.log('Calendar replaceState', view.name, view.intervalStart.format()); history.replaceState({date: view.intervalStart.format(), viewName: view.name}, null, url); } return this; }; ns.Calendar.prototype.loadTipText = function (eventId, e, api) { var self = this; $.ajax({ url: this.options.finalUrl, data: { action: 'renderEvent', eventId: +eventId }, type: 'post', dataType: 'json' }).then(function (data) { if (data.eventId && data.eventHtml) { self.eventcache[data.eventId] = data.eventHtml; } api.set('content.text', data.eventHtml); }, function (xhr, status, error) { self.debug && console.warn('Calendar loadTipText error', status, error); api.hide(); }); return $('', {'src': 'images/loading-arrow-16x16.gif', 'alt': 'Loading...'}); }; })(qs.defineNS('app.event'));