Skip to content

Instantly share code, notes, and snippets.

@dotmh
Created August 17, 2011 11:40
Show Gist options
  • Save dotmh/1151374 to your computer and use it in GitHub Desktop.
Save dotmh/1151374 to your computer and use it in GitHub Desktop.
A Prototype Calander
/**
* Options
* =======
* Required
* --------
* element : a protoype element / or an DOM Id [Required]
* from : The date to go from can ether be a string or a offest (see below about offests and date string)
* to : The date to go to can ether be string of a offset (see below about offests and date string)
*
* Optional
* --------
* field : the field that will be populated with the date [Optional] if ommited then the date param will containing the date
* exclude_weekends : Whether to include weekends that fall in the range given Boolean true , false
* exclude_dates : An array of dates to be excluded using ether an offset or date string
* events : An object literal containing events see below for event list
* format : the format of the date to be returned see below for format options
* local_days : Allows you to localise the day labels , should be an array [sun..sat]
* local_months : Allows you to localise the month labels , should be an array [jan..dec]
*
* Offest And Date string
* ======================
* You can enter the date range as ether a offset using the following format
* <number><unit>
* where number is the offset number and unit is weeks , days or months
* d / D = days
* w / W = weeks
* m / M = months
*
* Date strings need to formatted as <YYYY>-<MM>-<DD>
*
* Return Format
* =============
* This is the return format as a string with placeholders as follows
* d = day
* D = day (with leading zero)
* m = month
* M = month ( with leading zero)
* y = short year i.e 11
* Y = Full Year i.e 2011
*
* i.e
* For 1st July 2011
*
* D/M/Y 01/07/2011
* D-M-Y 01-07-2011
* Y-M-D 2011-07-01
*
* Events
* ======
* You can bind event listners to the following events by passing an event
* object litral i.e event : { event_name : function(event) { some code }}
* The event will be in the event var.
* Events can be passed on the constuct
*
* *next_event : When the user clicks the next button
* *back_event : when the user clicks the back button
* *date_event : When a user clicks on a date field
* *inactive_date_event : When a user clicks a date thats not active
*
* There are also <event name>_before that is triggered before that event and
* <event name>_after that is triggered after that event.
*
* Styling
* Dates that can be selected are given a class of active ,
* Dates that can not be selected are given a class of inactive
*/
var dateCalander = Class.create({
element : '',
field : '',
date : '',
fromDate : -1,
toDate : -1,
events : -1,
format : 'd m Y',
exclude_weekends : true,
exclude_dates : [],
local_months : [],
local_days : [],
initialize : function(options) {
this.element = options.element;
this.field = options.field || '';
this.exclude_weekends = options.exclude_weekends == undefined ? this.exclude_weekends : options.exclude_weekends
this.fromDate = this._calDate(options.from , new Date());
this.toDate = this._calDate(options.to);
this.setCurrent(this.fromDate);
if ( options.format ) {
this.format = options.format;
}
this.events = this._defaultEvents(options.events || {});
this.exclude_dates = this._calExcludes(options.exclude_dates || []);
this.local_days = options.local_days || [];
this.local_months = options.local_months || [];
},
_defaultEvents : function( event_options ) {
event_options.next_event = event_options.next_event || this.next_event;
event_options.back_event = event_options.back_event || this.back_event;
event_options.date_event = event_options.date_event || this.date_event;
event_options.self = this;
return event_options;
},
draw : function( calanderObject ) {
var calander = -1;
if ( calanderObject == undefined ) {
calander = this.draw_helper(this.toDate, this.fromDate);
}
else {
calander = calanderObject
}
var element = $(this.element);
element.update();
element.insert({top: calander.draw()});
},
draw_helper : function ( to , from , bind ) {
if ( bind == undefined ) {
bind = this;
}
return new bind.calander({'to':to ,
'from':from ,
events: bind.events ,
exclude_weekends_flag: bind.exclude_weekends,
header_array : bind.local_days,
months_array : bind.local_months,
exclude_dates : bind.exclude_dates
});
},
_calExcludes : function( dates ) {
if ( dates.length < 1 ) return [];
var return_dates = [];
for ( var i =0; i < dates.length; i++) {
return_dates[i] = new Date(this._calDate(dates[i]));
}
return return_dates;
},
_calDate : function(string , base)
{
var date_array = -1;
if ( string.match(/^\d\D/) ) {
date_array = string.split('');
var from = new Date(this.fromDate);
var additonal = parseInt(date_array[0]);
switch ( date_array[1]) {
// D is add additonal days
case 'd':
case 'D':
return new Date().setDate((base.getDate()|| from.getDate())+additonal)
break;
// W is add additional weeks
case 'w' :
case 'W' :
return new Date().setDate((base.getDate()|| from.getDate())+(additonal+7))
break;
// M is add on additonal months
case 'm':
case 'M':
return new Date((base.getFullYear() || from.getFullYear()) , (base.getMonth() || from.getMonth())+additonal , 1);
break;
// Is the same as M but removes a specified number of months i.e 1f would go auguest to july;
case 'F':
case 'f':
return new Date((base.getFullYear() || from.getFullYear()) , (base.getMonth() || from.getMonth())-additonal , 1);
break;
}
}
else {
var date_sections = string.split(' ');
date_array = date_sections[0].split('-');
if ( date_array.length > 3 ) throw ('A date string must be formated as YYYY-MM-DD not '+string);
// You have to correct for the offset months start at 0 not 1 so for example August is 7 not 8
var date = new Date().setFullYear(date_array[0], (date_array[1]-1), date_array[2])
return date
}
},
setCurrent : function(date) {
this.current = new Date(date)
},
getCurrent : function() {
return this.current
},
next_event : function(e) {
var from = new Date(this.self._calDate('1m', this.self.getCurrent()));
if ( from.getMonth() > new Date(this.self.toDate).getMonth() ) {
alert('You can only select dates up to and including '+new Date(this.self.toDate).toLocaleDateString());
return;
}
this.self.setCurrent(from);
this.self.draw(new this.self.draw_helper(this.self.toDate, from , this.self));
},
back_event : function(e) {
var from = new Date(this.self._calDate('1f', this.self.getCurrent()));
var base_from = new Date(this.self.fromDate)
if (from.getMonth() == base_from.getMonth() ) {
from.setDate(base_from.getDate());
}
if ( from.getMonth() < new Date(this.self.fromDate).getMonth() ) {
alert('You can only select dates from and including '+new Date(this.self.fromDate).toLocaleDateString());
return;
}
this.self.setCurrent(from);
this.self.draw(new this.self.draw_helper(this.self.toDate, from , this.self));
},
date_event : function(e) {
var element = $(e.element());
var date_array = element.id.split('-');
var parts = {d : date_array[0],
D : date_array[0].length < 2 ? '0'+date_array[0] : date_array[0],
m : date_array[1],
M : date_array[1].length < 2 ? '0'+date_array[1] : date_array[1],
Y : date_array[2],
y : date_array[2].slice(2)}
var date = this.self.format
$H(parts).each(function(format){
date = date.replace(format.key , format.value);
})
this.self.date = date
if ( this.self.field != '' && this.self.field != undefined ) {
$(this.self.field).value = this.self.date;
}
},
calander : Class.create({
to : -1,
from: -1,
calander : false,
current_day : 0,
row : false,
header_array : ['Su' , 'Mo' , 'Tu' , 'We' , 'Th' , 'Fr' , 'Sa'],
months_array : ['January' , 'February' , 'March' , 'April' , 'May' , 'June' , 'July' , 'August' , 'September' , 'October' , 'November' , 'December'],
exclude_dates : [],
exclude_weekends_flag : true,
started_flag : false,
events : -1,
initialize : function(options) {
this.to = options.to;
this.from = options.from;
this.calander = new Element('table').addClassName('calander');
if ( options.exclude_weekends != undefined ) this.exclude_weekends_flag = options.exclude_weekends;
if ( options.header_array && options.header_array.length > 0 ) this.header_array = options.header_array;
if ( options.months_array && options.months_array.length > 0 ) this.months_array = options.months_array;
if ( options.events ) this.events = options.events;
if ( options.exclude_dates && options.exclude_dates.length > 0 ) this.exclude_dates = options.exclude_dates;
},
header : function() {
var header = new Element('thead');
var contents = new Element('tr');
this.header_array.each(function(day){
contents.insert({bottom: new Element('th').update(day)});
})
header.insert({bottom:contents});
this.calander.insert({bottom: header});
},
footer : function() {
var self = this;
var footer = new Element('tfoot');
var content = new Element('tr');
var from = new Date(this.from);
if ( from.getMonth() > 0 ) {
content.insert({bottom : new Element('td').update('<').observe('click' , function(event) {self.trigger_event('back_event' , event)} )})
}
else {
content.insert({bottom : new Element('td').update('&nbsp;')})
}
content.insert({bottom : new Element('td' , {'colspan' : 5 , 'class' : 'month'}).update(this.months_array[from.getMonth()]+' '+from.getFullYear())});
if ( from.getMonth() < (this.months_array.length -1) ) {
content.insert({bottom : new Element('td').update('>').observe('click' , function(event) {self.trigger_event('next_event' , event)})})
}
else {
content.insert({bottom : new Element('td').update('&nbsp;')})
}
footer.insert({bottom: content});
this.calander.insert({bottom : footer});
},
trigger_event : function(event_name , event) {
if ( $(event.element()).hasClassName('inactive') ) {
event_name = 'inactive_'+event_name;
}
if ( this.events[event_name+'_before'] != undefined && this.events[event_name+'_before'] != -1) {
try {
this.events[event_name+'_before'](event)
}
catch (e) {
throw (e)
}
}
if ( this.events[event_name] != undefined && this.events[event_name] != -1) {
try {
this.events[event_name](event)
}
catch (e) {
throw (e);
}
}
else {
if ( console ) {
console.debug('No Event Called '+event_name)
}
}
if ( this.events[event_name+'_after'] != undefined && this.events[event_name+'_after'] != -1) {
try {
this.events[event_name+'_after'](event)
}
catch (e) {
throw (e)
}
}
},
draw_range : function(from , to , cssClass) {
cssClass = cssClass || 'inactive';
var start = new Date(from);
var self = this;
to = to == undefined ? 31 : new Date(to).getDate();
for ( var i = 0; i <= to-2 ; i++) {
// Finish when we hit a new month and we are not on the first loop
// this is because a range may start on the 1st
if ( start.getDate() == 1 && i > 0) break;
// create new row after each week
if ( this.current_day == 0 ) {
if ( this.row ) {
this.calander.insert({bottom : this.row});
}
this.row = new Element('tr');
}
// This spaces the start date if its not a sunday
if (start.getDay() > 0 && this.started_flag == false) {
for ( var n = 0; n <= (start.getDay()-1) ; n++) {
this.row.insert({bottom : new Element('td').update('&nbsp;')});
this.current_day = this.current_day == 6 ? 0 : this.current_day+1;
}
this.started_flag = true;
}
var timestamps = {from:new Date(this.from).getTime() , to:new Date(this.to).getTime()}
var active = ((start.getTime() >= timestamps.from && start.getTime() <= timestamps.to) && ((start.getDay() > 0 && start.getDay() < 6) || this.exclude_weekends_flag == false)) ? true : false;
if ( this.isExcluded(start) ) {
active = false;
}
var cellClass = active ? cssClass : 'inactive';
this.row.insert({bottom : new Element('td' , {id:start.getDate()+'-'+(start.getMonth()+1)+'-'+start.getFullYear()}).addClassName(cellClass).update(start.getDate()).observe('click' , function(event){self.trigger_event('date_event' , event)})});
start.setDate((start.getDate()+1));
this.current_day = this.current_day == 6 ? 0 : this.current_day+1;
}
},
isExcluded : function(date) {
if ( this.exclude_dates.length < 1 ) return false;
var excluded = false;
for ( var i=0; i <= this.exclude_dates.length-1; i++ ) {
if (date.getTime() == this.exclude_dates[i].getTime()) {
excluded = true;
}
}
return excluded;
},
open : function() {
var from = new Date(this.from);
if (from.getDate() == 1) {
return false;
}
var prefix_date = new Date(this.from);
prefix_date.setDate(1);
this.draw_range(prefix_date , from , 'active')
return true;
},
close : function() {
if ( this.current_day < 6 ) {
for (var d = this.current_day; d <= 6; d++){
this.row.insert({bottom : new Element('td').update('&nbsp;')});
}
}
},
draw : function() {
this.header();
this.footer();
this.open();
this.draw_range(this.from , undefined , 'active');
this.close();
this.calander.insert({bottom: this.row});
return this.calander
}
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment