// the semi-colon before function invocation is a safety net against concatenated
// scripts and/or other plugins which may not be closed properly.
;(function ($, window, document, undefined) {
"use strict";
// Create the defaults once
var pluginName = "simpleCalendar",
defaults = {
months: ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'], //string of months starting from january
days: ['sunday', 'monday', 'tuesday', 'wenesday', 'thursday', 'friday', 'saturday'], //string of days starting from sunday
displayYear: true, // display year in header
fixedStartDay: true, // Week begin always by monday
displayEvent: true, // display existing event
disableEventDetails: false, // disable showing event details
disableEmptyDetails: false, // disable showing empty date details
events: [], // List of event
onInit: function (calendar) {}, // Callback after first initialization
onMonthChange: function (month, year) {}, // Callback on month change
onDateSelect: function (date, events) {}, // Callback on date selection
onEventSelect: function () {}, // Callback fired when an event is selected - see $(this).data('event')
onEventCreate: function ( $el ) {}, // Callback fired when an event is created
onDayCreate: function( $el ) {} // Callback fired when an event is being created - see $(this).data('date'), $(this).data('todayEvents')
};
// The actual plugin constructor
function Plugin(element, options) {
this.element = element;
this.settings = $.extend({}, defaults, options);
this._defaults = defaults;
this._name = pluginName;
this.currentDate = new Date();
this.init();
}
// Avoid Plugin.prototype conflicts
$.extend(Plugin.prototype, {
init: function () {
var container = $(this.element);
var todayDate = this.currentDate;
var calendar = $('
');
var header = $('');
this.updateHeader(todayDate, header);
calendar.append(header);
this.buildCalendar(todayDate, calendar);
container.append(calendar);
this.bindEvents();
this.settings.onInit(this);
},
//Update the current month header
updateHeader: function (date, header) {
var monthText = this.settings.months[date.getMonth()];
monthText += this.settings.displayYear ? ' ' + date.getFullYear() : '
';
header.find('.month').html(monthText);
},
//Build calendar of a month from date
buildCalendar: function (fromDate, calendar) {
var plugin = this;
calendar.find('table').remove();
var body = $('');
var thead = $('');
var tbody = $('');
//Header day in a week ( (1 to 8) % 7 to start the week by monday)
for (var i = 1; i <= this.settings.days.length; i++) {
thead.append($('' + this.settings.days[i % 7].substring(0, 3) + ' | '));
}
//setting current year and month
var y = fromDate.getFullYear(), m = fromDate.getMonth();
//first day of the month
var firstDay = new Date(y, m, 1);
//If not monday set to previous monday
while (firstDay.getDay() != 1) {
firstDay.setDate(firstDay.getDate() - 1);
}
//last day of the month
var lastDay = new Date(y, m + 1, 0);
//If not sunday set to next sunday
while (lastDay.getDay() != 0) {
lastDay.setDate(lastDay.getDate() + 1);
}
//For firstDay to lastDay
for (var day = firstDay; day <= lastDay; day.setDate(day.getDate())) {
var tr = $('
');
//For each row
for (var i = 0; i < 7; i++) {
var td = $('' + day.getDate() + ' | ');
// this will be accessed multiple times
var $day = td.find( '.day' );
//if today is this day
if (day.toDateString() === (new Date).toDateString()) {
$day.addClass("today");
}
//if day is not in this month
if (day.getMonth() != fromDate.getMonth()) {
$day.addClass("wrong-month");
}
// filter today's events
var todayEvents = plugin.getDateEvents(day);
if (todayEvents.length && plugin.settings.displayEvent) {
$day.addClass(plugin.settings.disableEventDetails ? "has-event disabled" : "has-event");
} else {
$day.addClass(plugin.settings.disableEmptyDetails ? "disabled" : "");
}
// simplify further customization
$day.data( 'date', todayEvents.date );
$day.data( 'todayEvents', todayEvents );
this.settings.onDayCreate( $day );
tr.append(td);
day.setDate(day.getDate() + 1);
}
tbody.append(tr);
}
body.append(thead);
body.append(tbody);
var eventContainer = $('');
calendar.append(body);
calendar.append(eventContainer);
},
changeMonth: function (value) {
this.currentDate.setMonth(this.currentDate.getMonth() + value);
this.buildCalendar(this.currentDate, $(this.element).find('.calendar'));
this.updateHeader(this.currentDate, $(this.element).find('.calendar header'));
this.settings.onMonthChange(this.currentDate.getMonth(), this.currentDate.getFullYear())
},
//Init global events listeners
bindEvents: function () {
var plugin = this;
//Click previous month
$(plugin.element).on('click', '.btn-prev', function ( e ) {
plugin.changeMonth(-1)
e.preventDefault();
});
//Click next month
$(plugin.element).on('click', '.btn-next', function ( e ) {
plugin.changeMonth(1);
e.preventDefault();
});
//Binding day event
$(plugin.element).on('click', '.day', function (e) {
var date = new Date($(this).data('date'));
var events = plugin.getDateEvents(date);
if (!$(this).hasClass('disabled')) {
plugin.fillUp(e.pageX, e.pageY);
plugin.displayEvents(events);
}
plugin.settings.onDateSelect(date, events);
});
//Binding event container close
$(plugin.element).on('click', '.event-container .close', function (e) {
plugin.empty(e.pageX, e.pageY);
});
},
displayEvents: function (events) {
var plugin = this;
var container = $(this.element).find('.event-wrapper');
events.forEach(function (event) {
var startDate = new Date(event.startDate);
var endDate = new Date(event.endDate);
var $event = $('' +
'' +
'
' + startDate.getHours() + ':' + (startDate.getMinutes() < 10 ? '0' : '') + startDate.getMinutes() + '
' +
'
' + plugin.formatDateEvent(startDate, endDate) + '
' +
'
' + event.summary + '
' +
'
');
$event.data( 'event', event )
$event.click( plugin.settings.onEventSelect )
plugin.settings.onEventCreate( $event );
container.append($event);
})
},
//Small effect to fillup a container
fillUp: function (x, y) {
var plugin = this;
var elem = $(plugin.element);
var elemOffset = elem.offset();
var filler = $('');
filler.css("left", x - elemOffset.left);
filler.css("top", y - elemOffset.top);
elem.find('.calendar').append(filler);
filler.animate({
width: "300%",
height: "300%"
}, 500, function () {
elem.find('.event-container').show();
filler.hide();
});
},
//Small effect to empty a container
empty: function (x, y) {
var plugin = this;
var elem = $(plugin.element);
var elemOffset = elem.offset();
var filler = elem.find('.filler');
filler.css("width", "300%");
filler.css("height", "300%");
filler.show();
elem.find('.event-container').hide().find('.event').remove();
filler.animate({
width: "0%",
height: "0%"
}, 500, function () {
filler.remove();
});
},
getDateEvents: function (d) {
var plugin = this;
return plugin.settings.events.filter(function (event) {
return plugin.isDayBetween(d, new Date(event.startDate), new Date(event.endDate));
});
},
isDayBetween: function (d, dStart, dEnd) {
dStart.setHours(0,0,0);
dEnd.setHours(23,59,59,999);
d.setHours(12,0,0);
return dStart <= d && d <= dEnd;
},
formatDateEvent: function (dateStart, dateEnd) {
var formatted = '';
formatted += this.settings.days[dateStart.getDay()] + ' - ' + dateStart.getDate() + ' ' + this.settings.months[dateStart.getMonth()].substring(0, 3);
if (dateEnd.getDate() !== dateStart.getDate()) {
formatted += ' to ' + dateEnd.getDate() + ' ' + this.settings.months[dateEnd.getMonth()].substring(0, 3)
}
return formatted;
}
});
// A really lightweight plugin wrapper around the constructor,
// preventing against multiple instantiations
$.fn[pluginName] = function (options) {
return this.each(function () {
if (!$.data(this, "plugin_" + pluginName)) {
$.data(this, "plugin_" + pluginName, new Plugin(this, options));
}
});
};
})(jQuery, window, document);