Skip to content

Instantly share code, notes, and snippets.

@adamsilver
Created October 14, 2016 11:11
Show Gist options
  • Save adamsilver/aa72f04f67b02dbe2221008f0297737b to your computer and use it in GitHub Desktop.
Save adamsilver/aa72f04f67b02dbe2221008f0297737b to your computer and use it in GitHub Desktop.
Adoro.Calendar
// ---------------------------------------------------------------------------------------------------------
// --- license header; ---
// ---------------------------------------------------------------------------------------------------------
/* Copyright (c) 2008 - 2009 by Adoro Media
info@adoromedia.com
This file is part of Adoro Media open source projects
Adoro Media projects are free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
The projects are distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
See <http://www.gnu.org/licenses/> for information on the GNU Lesser General Public License.
*/
// ---------------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------
if (typeof Adoro !== "object") { var Adoro = {}; }
/**
*
* @class Create a calendar control to help fill in form elements that require a date format
* @constructor
* @param {Object} options Optional properties for this calendar instance
* @param {Date} options.dateStart The start date
* @param {Date} options.dateEnd The end date
* @param {Date} options.currentDate The current date
* @param {String} options.title The title text
* @param {String} options.calendarClass The class name for the calendar
* @param {Boolean} options.closeOnDateSelection To close the calendar when a date is selected
* @param {Boolean} options.hasCloseButton To include a close button for the calendar
* @param {Boolean} options.hasLinkActivator To tell the calendar if it has a link activator
* @param {String} options.linkActivatorClass Class name for the link activator
* @param {String} options.linkActivatorText The text for the link activator
* @param {Boolean} options.startHidden Tell the calendar to start hidden or shown on load.
* @param {HTMLElement} options.selectDay DOM reference to select element
* @param {HTMLElement} options.selectMonth DOM reference to select element
* @param {HTMLElement} options.selectYear DOM reference to select element
* @param {String} options.outOfRangeMessage Message to display when the date is out of range.
* @param {HTMLElement} options.appendCalendarTo The element that the calendar will be appended into the DOM
* @param {String} options.hasLinkClass The class name for the associated link
*/
Adoro.Calendar = function() {
var config = {
dateStart: (function() {
var d = new Date();
d.setYear(d.getFullYear()-2);
return d;
}()),
dateEnd: (function() {
var d = new Date();
d.setYear(d.getFullYear()+2);
return d;
}()),
currentDate: (function() {
var d = new Date();
d.setHours(0,0,0,0);
return d;
}()),
dayInMs: 86400000,
monthsContainer: null,
title: "Calendar",
calendarClass: "calendarControl",
dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],
monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],
closeOnDateSelection: true,
hasCloseButton: true,
hasLinkActivator: true,
hasLinkClass: "calendarHasLink",
linkActivatorClass: "activateCalendar",
linkActivatorText: "Show calendar",
startHidden: true,
selectDay: null,
selectMonth: null,
selectYear: null,
hasDropDowns: false,
outOfRangeMessage: "Date selected is not in correct range.",
appendCalendarTo: null,
calendarActivator: null
};
// set from values in object passed in as param to constructor.
if (typeof arguments[0] == "object") {
config.dateStart = arguments[0].dateStart || config.dateStart;
config.dateEnd = arguments[0].dateEnd || config.dateEnd;
// todo: check if the currentDate is in range
config.currentDate = arguments[0].currentDate || config.currentDate;
config.title = arguments[0].title || config.title;
config.calendarClass = arguments[0].calendarClass || config.calendarClass;
config.closeOnDateSelection = (typeof arguments[0].closeOnDateSelection === "boolean") ? arguments[0].closeOnDateSelection : config.closeOnDateSelection;
config.hasCloseButton = (typeof arguments[0].hasCloseButton === "boolean") ? arguments[0].hasCloseButton : config.hasCloseButton;
config.hasLinkActivator = (typeof arguments[0].hasLinkActivator === "boolean") ? arguments[0].hasLinkActivator : config.hasLinkActivator;
config.linkActivatorClass = arguments[0].linkActivatorClass || config.linkActivatorClass;
config.linkActivatorText = arguments[0].linkActivatorText || config.linkActivatorText;
config.startHidden = (typeof arguments[0].startHidden === "boolean") ? arguments[0].startHidden : config.startHidden;
config.selectDay = arguments[0].selectDay || null;
config.selectMonth = arguments[0].selectMonth || null;
config.selectYear = arguments[0].selectYear || null;
config.hasDropDowns = (config.selectDay !== null || config.selectMonth !== null || config.selectYear !== null) || false;
config.outOfRangeMessage = arguments[0].outOfRangeMessage || config.outOfRangeMessage;
config.appendCalendarTo = arguments[0].appendCalendarTo || null;
config.hasLinkClass = arguments[0].hasLinkClass || config.hasLinkClass;
}
/*
* This handles the state of the calendar while in use.
* For example we may change months (previous/next) but
* not actually set a date. So we need to keep track of
* what date we are showing without having to inspect DOM
* for class/Id names etc
*/
var state = {
currentSelectedDate: config.currentDate
}
// public
this.calendar = document.createElement("div"); // calendar so user can place whereever they want
this.calendar.className = config.calendarClass; // so the user can style with own class name
this.selectedDate = config.currentDate; // so user can utilise the selected date in some other situation not handled by this class
this.hideCalendar = hideCalendar; // so user can hide the calendar from external functionality
this.showCalendar = showCalendar; // so user can show the calendar from external functionality
var me = this;
buildCalendar();
function buildCalendar() {
createPrimaryStructure();
if (dateInRange(me.selectedDate, config.dateStart, config.dateEnd)) {
createMonth(me.selectedDate.getFullYear(),me.selectedDate.getMonth());
}
prepareCalendarControls();
if (config.startHidden) {
hideCalendar();
}
else {
showCalendar();
}
if(config.hasLinkActivator) {
$(me.calendar).addClass(config.hasLinkClass);
createCalendarActivator();
}
if(config.appendCalendarTo !== null) {
placeCalendar();
}
if(config.hasDropDowns) {
prepareDropDowns();
setCalendarDateFromDropDown();
}
}
/*
* Position calendar to calendarActivator
* @param void
* @return void
*/
function positionCalendar() {
var pos = $(config.calendarActivator).position();
$("div.calendar",me.calendar).css({
left: pos.left+"px",
top: pos.top+"px",
position: "absolute"
});
}
/*
* Appends calendar to specified location
* @param void
* @return void
*/
function placeCalendar() {
var node = config.appendCalendarTo;
node.appendChild(me.calendar);
}
/*
* Creates and appends a link to the calendar to show the calendar
* @param void
* @return void
*/
function createCalendarActivator() {
var a = document.createElement("a");
a.href = "#";
a.className = config.linkActivatorClass;
a.innerHTML = config.linkActivatorText;
a.onclick = showCalendar;
config.calendarActivator = a;
$(me.calendar).prepend(a);
}
/*
* This creates the calendar mark-up with action buttons
* @param void
* @return void
*/
function createPrimaryStructure() {
var html = '';
html += '<div class="calendar">';
html += '<div class="title">';
html += config.title;
html += '</div>';
html += '<div class="actions">';
html += '<ul>';
html += '<li class="backward"><a href="#" class="previous">Back</a></li>';
html += '<li class="forward"><a href="#" class="next">Forward</a></li>';
html += '</ul>';
html += '</div>';
html += '<div class="months">';
html += '</div>';
if (config.hasCloseButton) {
html += '<div class="actions2">';
html += '<a href="#" class="close">Close</a>';
html += '</div>';
}
html += '</div>'
me.calendar.innerHTML = html;
config.monthsContainer = $("div.months", me.calendar)[0];
}
/*
* Gets the HTML for a month
* @param month as number
* @param year as number
* @return html as HTML string
*/
function getCalendarTableRows( month, year ) {
var html = "<tr>";
var d = new Date();
//d.setMonth(month);
//d.setYear(year);
//d.setDate(1);
d.setFullYear(year,month,1,0);
d.setHours(0,0,0,0);
var firstDay = d.getDay();
var i = 0;
var tdClassDefault = "dayActivator";
while (i < firstDay) {
html += "<td>&nbsp;</td>";
i++;
}
while (d.getMonth() == month) {
if (i % 7 === 0) {
html += "</tr><tr>";
}
var tdClass = tdClassDefault;
if (d.getTime() === config.currentDate.getTime()) {
tdClass += " currentDate";
}
if (d.getTime() === me.selectedDate.getTime()) {
tdClass += " selectedDate";
}
html += '<td class="'+tdClass+'">' + '' + d.getDate() + '' + '</td>';
d.setDate( d.getDate()+1 );
i++;
}
while (i % 7 !== 0) {
html += "<td>&nbsp;</td>";
i++;
}
html += "</tr>";
return html;
}
/*
* Creates a month by appending the HTML
* @param year (as number)
* @param month (as number) (months run from 0-11)
* @return void
*/
function createMonth(year,month) {
var monthHTML = '';
monthHTML += '<div class="month date' + year +"-" + (month+1) + '">';
monthHTML += '<div class="title">';
monthHTML += config.monthNames[month] + " " + year;
monthHTML += '</div>';
monthHTML += '<div class="days">';
monthHTML += '<table>';
monthHTML += '<thead>';
monthHTML += '<tr>';
monthHTML += '<th>S</th>';
monthHTML += '<th>M</th>';
monthHTML += '<th>T</th>';
monthHTML += '<th>W</th>';
monthHTML += '<th>T</th>';
monthHTML += '<th>F</th>';
monthHTML += '<th>S</th>';
monthHTML += '</tr>';
monthHTML += '</thead>';
monthHTML += '<tbody>';
monthHTML += '</tbody>';
monthHTML += '</table>';
monthHTML += '</div>';
monthHTML += '</div>';
config.monthsContainer.innerHTML += monthHTML;
$("div.months div.days table tbody", me.calendar).append(getCalendarTableRows(month, year));
prepareDayActivators();
}
/*
* Apply events to calendar controls
* @param void
* @return void
*/
function prepareCalendarControls() {
var previous = $("a.previous", me.calendar)[0];
var next = $("a.next", me.calendar)[0];
var close = $("a.close", me.calendar)[0];
$(previous).click(setPreviousMonth);
$(next).click(setNextMonth);
$(close).click(hideCalendar);
}
function prepareDropDowns() {
if (config.selectDay !== null) {
$(config.selectDay).bind("change", setCalendarDateFromDropDown);
}
if (config.selectMonth !== null) {
$(config.selectMonth).bind("change", setCalendarDateFromDropDown);
}
if (config.selectYear !== null) {
$(config.selectYear).bind("change", setCalendarDateFromDropDown);
}
}
/*
* The calendar will update if the drop down fields have a valid date.
* @param void
* @return void
*/
function setCalendarDateFromDropDown() {
var d = new Date();
d.setHours(0,0,0,0);
if (config.selectDay !== null) {
d.setDate(parseInt(config.selectDay.value));
}
if (config.selectMonth !== null) {
d.setMonth(parseInt(config.selectMonth.value)-1);
}
if (config.selectYear !== null) {
d.setYear(config.selectYear.value);
}
if(dateInRange(d, config.dateStart, config.dateEnd)) {
me.selectedDate = d;
state.currentSelectedDate = d;
removeCurrentMonth();
createMonth(me.selectedDate.getFullYear(),me.selectedDate.getMonth());
}
}
/*
* When the calendar date changes the drop downs will be configured to match
* if the drop downs have the correct range of values.
* @param void
* @return void
*/
function setDropDownFromCalendarDate() {
if (config.selectDay !== null) {
setSelectBoxValue(config.selectDay, me.selectedDate.getDate().toString());
}
if (config.selectMonth !== null) {
setSelectBoxValue(config.selectMonth, (me.selectedDate.getMonth()+1).toString());
}
if (config.selectYear !== null) {
setSelectBoxValue(config.selectYear, me.selectedDate.getFullYear().toString());
}
}
/*
* Helper to set a value in a select field
* @param selectNode (as DOM reference to select field)
* @param selectValue (as string)
* @return void
*/
function setSelectBoxValue(selectNode, selectValue) {
var options = selectNode.options;
for(var i = options.length-1;i>=0;i--) {
if(selectValue === options[i].value) {
selectNode.selectedIndex = i;
break;
}
}
}
/*
* Sets the calendar to previous month if within range
* @param void
* @return false (as boolean) - stops default action on a link
*/
function setPreviousMonth() {
var pm = getPreviousMonth();
var hasPreviousMonth = dateInRange(pm, config.dateStart, config.dateEnd);
if(!hasPreviousMonth) {
return false;
}
removeCurrentMonth();
createMonth(pm.getFullYear(),pm.getMonth());
state.currentSelectedDate = pm;
return false;
}
/*
* Sets the calendar to the next month if within range
* @param void
* @return false (as boolean) - stops default action on a link
*/
function setNextMonth() {
var nm = getNextMonth();
var hasNextMonth = dateInRange(nm, config.dateStart, config.dateEnd);
if(!hasNextMonth) {
return false;
}
removeCurrentMonth();
createMonth(nm.getFullYear(),nm.getMonth());
state.currentSelectedDate = nm;
return false;
}
/*
* Get the previous month
* See "state" for more information
* @param void
* @return d (as date object)
*/
function getPreviousMonth() {
var d = new Date(state.currentSelectedDate.getFullYear(), state.currentSelectedDate.getMonth(),1);
d = d.getTime() - config.dayInMs;
d = new Date(d);
return d;
}
/*
* Get the next month
* See "state" for more information
* @param void
* @return d (as date object)
*/
function getNextMonth() {
var d = new Date(state.currentSelectedDate.getFullYear(), state.currentSelectedDate.getMonth());
d = d.setMonth(d.getMonth()+1);
d = new Date(d);
return d;
}
/*
* Check to see a date is within a certain range
* @param date (as date object)
* @param dateRangeFrom (as date object)
* @param dateRangeTo (as date object)
* @return true (as boolean) if date is in range otherwise false
*/
function dateInRange(date, dateRangeFrom, dateRangeTo) {
d = date.getTime();
drf = dateRangeFrom.getTime();
drt = dateRangeTo.getTime();
if(d > drf && d < drt) {
return true;
}
else {
return false;
}
}
/*
* Removes current month from DOM
* @param void
* @return void
*/
function removeCurrentMonth() {
config.monthsContainer.innerHTML = "";
}
/*
* Applies event handlers to the table cells for picking a date
* @param void
* @return void
*/
function prepareDayActivators() {
$("td.dayActivator",me.calendar).click(setSelectedDate);
}
/*
* Set the selected date if the date is within range
* @param void
* @return void
*/
function setSelectedDate() {
var day = parseInt(this.innerHTML);
var yearMonth = $("div.month",config.monthsContainer)[0].className.split(" ")[1].split("date")[1].split("-");
var year = yearMonth[0];
var month = yearMonth[1]-1;
var d = new Date(year,month,day);
if(!dateInRange(d, config.dateStart, config.dateEnd)) {
return;
}
me.selectedDate = d;
removeCurrentMonth();
createMonth(me.selectedDate.getFullYear(),me.selectedDate.getMonth());
if(config.closeOnDateSelection) {
hideCalendar();
}
if(config.hasDropDowns) {
setDropDownFromCalendarDate();
}
}
/*
* Hide the calendar
* @param void
* @return void
*/
function hideCalendar() {
$(me.calendar).find("div.calendar").css({"display": "none", "zIndex":"0"});
return false;
}
/*
* Show the calendar
* @param void
* @return void
*/
function showCalendar() {
$(me.calendar).find("div.calendar").css({"display": "block", "zIndex": "999"});
return false;
}
}
/*
var options = {
appendTo: domNode,
calendarCssClass: 'string',
selectDay: domNode,
selectMonth: domNode,
selectYear: domNode,
textInput: domNode
}
var cal = new Adoro.Calendar(options);
cal.getDate();
cal.setDate(dateObject);
cal.show();
cal.hide();
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment