diff --git a/extensions/postfix_autoreply/forms.py b/extensions/postfix_autoreply/forms.py index 2b5cb3b7d..2ad2e10f6 100644 --- a/extensions/postfix_autoreply/forms.py +++ b/extensions/postfix_autoreply/forms.py @@ -1,6 +1,7 @@ from django import forms from models import ARmessage from django.utils.translation import ugettext as _ +import datetime class ARmessageForm(forms.ModelForm): class Meta: @@ -11,3 +12,8 @@ class ARmessageForm(forms.ModelForm): super(ARmessageForm, self).__init__(*args, **kwargs) self.fields['subject'].widget.attrs['size'] = 40 self.fields['content'].widget.attrs['cols'] = 50 + + def clean_untildate(self): + if self.cleaned_data["untildate"] < datetime.date.today(): + raise forms.ValidationError(_("This date is over")) + return self.cleaned_data['untildate'] diff --git a/extensions/postfix_autoreply/models.py b/extensions/postfix_autoreply/models.py index 3ad3ddbb5..f7b99ce60 100644 --- a/extensions/postfix_autoreply/models.py +++ b/extensions/postfix_autoreply/models.py @@ -12,10 +12,14 @@ class Alias(models.Model): class ARmessage(models.Model): mbox = models.ForeignKey(Mailbox) - subject = models.CharField(_('subject'), max_length=255) - content = models.TextField(_('content')) - enabled = models.BooleanField(_('enabled')) - untildate = models.DateTimeField(_('until')) + subject = models.CharField(_('subject'), max_length=255, + help_text=_("The subject that will appear in sended emails")) + content = models.TextField(_('content'), + help_text=_("The content that will appear in sended emails")) + enabled = models.BooleanField(_('enabled'), + help_text=_("Activate/Deactivate your auto reply")) + untildate = models.DateField(_('until'), + help_text=_("Activate your auto reply until this date")) class ARhistoric(models.Model): armessage = models.ForeignKey(ARmessage) diff --git a/extensions/postfix_autoreply/templates/autoreply.html b/extensions/postfix_autoreply/templates/autoreply.html index 6ce9e2e9e..1ee731a23 100644 --- a/extensions/postfix_autoreply/templates/autoreply.html +++ b/extensions/postfix_autoreply/templates/autoreply.html @@ -2,9 +2,6 @@ {% load i18n %} {% block html_head %} - @@ -18,7 +15,7 @@ {% endblock %} diff --git a/extensions/webmail/views.py b/extensions/webmail/views.py index da3da92f7..d19f4cee0 100644 --- a/extensions/webmail/views.py +++ b/extensions/webmail/views.py @@ -149,7 +149,8 @@ def move(request): @is_not_localadmin() def delete(request, fdname, mail_id): mbc = IMAPconnector(user=request.user.username) - mbc.move(mail_id, fdname, "Trash") + mbc.move(mail_id, fdname, parameters.get_user(request.user, "webmail", + "TRASH_FOLDER")) return folder(request, fdname, False) @login_required diff --git a/static/js/calendar.js b/static/js/calendar.js index ab4935248..45befd4b8 100644 --- a/static/js/calendar.js +++ b/static/js/calendar.js @@ -1,1344 +1,1104 @@ -/** - * Calendar - * - * version 1.1.2 - * - added option onValuesStart - * version 1.1.1 - * - added bounds option - * - fixes chrome toggle bug - * version 1.1 - * - fix: events in fx not work bug - * - fix: ie6 iframe bug - * - added displayHead and displayFooter abstract render function - * version 1.0 - * - new option dayLabelLength - * - IE6 Fix works with variable content height - */ - -/** - * Calendar: a Javascript class for Mootools - * - * adds accessible and unobtrusive date pickers to your form elements - * based on a work by Aeron Glemann - * modifyed by Jens Klose - * - * @licence MIT Style License - */ -var Calendar = new Class({ - - Implements: [Events,Options], - - options: { - dayLabelLength: 2, - blocked: [], // blocked dates - bounds: {start:null , end:null}, // - classes: { - calendar:'calendar', - prev:'prev', - next:'next', - month:'month', - year:'year', - today:'today', - invalid:'invalid', - valid:'valid', - inactive:'inactive', - active:'active', - hover:'hover', - hilite:'hilite' - }, - days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], // days of the week starting at sunday - direction: 0, // -1 past, 0 past + future, 1 future - draggable: true, - months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], - navigation: 1, // 0 = no nav; 1 = single nav for month; 2 = dual nav for month and year - offset: 0, // first day of the week: 0 = sunday, 1 = monday, etc.. - onHideStart: $empty, - onHideComplete: $empty, - onShowStart: $empty, - onShowComplete: $empty, - onValuesStart: $empty, - pad: 1, // padding between multiple calendars - tweak: { - x: 0, - y: 0 - } // tweak calendar positioning - }, - - /** - * initialize: calendar constructor - * - * @param obj (obj) a js object containing the form elements and format strings { id: 'format', id: 'format' etc } - * @param props (obj) optional properties - */ - initialize: function(obj, options) { - // basic error checking - if (!obj) { - return false; - } - - this.setOptions(options); - this.classes = this.options.classes; - - // create cal element with css styles required for proper cal functioning - this.calendar = new Element('div', { - 'class':this.classes.calendar, - 'styles': { - left: '-1000px', - opacity: 0, - position: 'absolute', - top: '-1000px', - zIndex: 1000 - } - }).inject(document.body); - - this.calendar.coord = this.calendar.getCoordinates(); - - // iex 6 needs a transparent iframe underneath the calendar in order to not allow select elements to render through - if (Browser.Engine.trident4) { - this.iframe = new IFrame({ - 'styles': { - left: '-1000px', - position: 'absolute', - top: '-1000px', - zIndex: 999 - } - }).inject(document.body); - this.iframe.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'; - } - - // initialize fade method - this.fx = new Fx.Tween(this.calendar,{ - link : 'chain', - duration : 'short', - onStart: function() { - if (this.calendar.getStyle('opacity') == 0) { // show - //this.calendar.setStyle('display', 'block'); - this.fireEvent('onShowStart', this.element); - } - else { // hide - this.fireEvent('onHideStart', this.element); - } - }.bind(this), - onComplete: function() { - if (this.calendar.getStyle('opacity') == 0) { // hidden - //this.calendar.setStyle('display', 'none'); - if (Browser.Engine.trident4) { - this.iframe.setStyles({ - left: '-1000px', - top: '-1000px' - }); - } - this.fireEvent('onHideComplete', this.element); - } - else { // shown - this.fireEvent('onShowComplete', this.element); - } - }.bind(this) - }); - - // initialize drag method - if (window.Drag && this.options.draggable) { - this.drag = new Drag.Move(this.calendar, { - onDrag: function() { - if (Browser.Engine.trident4) { - this.iframe.setStyles({ - left: this.calendar.getStyle('left'), - top: this.calendar.getStyle('top') - }); - } - }.bind(this) - }); - } - - // create calendars array - this.calendars = []; - var id = 0; - var d = new Date(); // today - d.setDate(d.getDate() + this.options.direction.toInt()); // correct today for directional offset - - for (var i in obj) { - var cal = { - button: new Element('button', { - 'type': 'button' - }), - el: $(i), - els: [], - id: id++, - month: d.getMonth(), - visible: false, - year: d.getFullYear() - }; - - // fix for bad element (naughty, naughty element!) - if (!this.element(i, obj[i], cal)) { - continue; - } - // show cal on click - this.fn = function(e, cal) { - e.stop(); - this.toggle(cal); - }.create({ - 'arguments': cal, - 'bind': this, - 'event': true - }); - - cal.el.addClass(this.classes.calendar); - // create cal button - cal.button.addClass(this.classes.calendar) - .addEvent('click', this.fn) - .inject(cal.el,'after'); - // read in default value - cal.val = this.read(cal); - $extend(cal, this.bounds(cal)); // abs bounds of calendar - $extend(cal, this.values(cal)); // valid days, months, years - this.rebuild(cal); - this.calendars.push(cal); // add to cals array - } - return this; - }, - - /** - * blocked: returns an array of blocked days for the month / year - * - * @param cal (obj) - * @returns blocked days (array) - */ - blocked: function(cal) { - var blocked = []; - var offset = new Date(cal.year, cal.month, 1).getDay(); // day of the week (offset) - var last = new Date(cal.year, cal.month + 1, 0).getDate(); // last day of this month - - this.options.blocked.each(function(date) { - var values = date.split(' '); - - for (var i = 0; i < 3; i++) { - if (!values[i]) { - values[i] = '*'; - } // make sure blocked date contains values for at least d, m and y - values[i] = values[i].contains(',') ? values[i].split(',') : new Array(values[i]); // split multiple values - } - - if (values[2].contains(cal.year + '') || values[2].contains('*')) { - if (values[1].contains(cal.month + 1 + '') || values[1].contains('*')) { - values[0].each(function(val) { // if blocked value indicates this month / year - if (val > 0) { - blocked.push(val.toInt()); - } // add date to blocked array - }); - - if (values[3]) { // optional value for day of week - values[3] = values[3].contains(',') ? values[3].split(',') : new Array(values[3]); - - for (var i = 0; i < last; i++) { - var day = (i + offset) % 7; - - if (values[3].contains(day + '')) { - blocked.push(i + 1); // add every date that corresponds to the blocked day of the week to the blocked array - } - } - } - } - } - }, this); - - return blocked; - }, - - /** - * bounds: returns the start / end bounds of the calendar - * - * @param cal (obj) - * @returns obj - */ - bounds: function(cal) { - // check external settings - if ($defined(this.options.bounds.start)&&$defined(this.options.bounds.end)) { - if ($type(this.options.bounds.end) == 'date' && - $type(this.options.bounds.start) == 'date' && - this.options.bounds.end>=this.options.bounds.start) { - return { - 'start': this.options.bounds.start, - 'end': this.options.bounds.end - } - } - } - - // 1. first we assume the calendar has no bounds (or a thousand years in either direction) - - // by default the calendar will accept a millennium in either direction - var start = new Date(1000, 0, 1); // jan 1, 1000 - var end = new Date(2999, 11, 31); // dec 31, 2999 - - // 2. but if the cal is one directional we adjust accordingly - var date = new Date().getDate() + this.options.direction.toInt(); - - if (this.options.direction > 0) { - start = new Date(); - start.setDate(date + this.options.pad * cal.id); - } - - if (this.options.direction < 0) { - end = new Date(); - end.setDate(date - this.options.pad * (this.calendars.length - cal.id - 1)); - } - - // 3. then we can further filter the limits by using the pre-existing values in the selects - cal.els.each(function(el) { - if (el.get('tag') == 'select') { - if (el.retrieve('format').test('(y|Y)')) { // search for a year select - var years = []; - - el.getChildren().each(function(option) { // get options - var values = this.unformat(option.get('value'), el.retrieve('format')); - - if (!years.contains(values[0])) { - years.push(values[0]); - } // add to years array - }, this); - - years.sort(this.sort); - - if (years[0] > start.getFullYear()) { - d = new Date(years[0], start.getMonth() + 1, 0); // last day of new month - - if (start.getDate() > d.getDate()) { - start.setDate(d.getDate()); - } - - start.setYear(years[0]); - } - - if (years.getLast() < end.getFullYear()) { - d = new Date(years.getLast(), end.getMonth() + 1, 0); // last day of new month - - if (end.getDate() > d.getDate()) { - end.setDate(d.getDate()); - } - - end.setYear(years.getLast()); - } - } - - if (el.retrieve('format').test('(F|m|M|n)')) { // search for a month select - var months_start = []; - var months_end = []; - - el.getChildren().each(function(option) { // get options - var values = this.unformat(option.get('value'), el.retrieve('format')); - - if ($type(values[0]) != 'number' || values[0] == years[0]) { // if it's a year / month combo for curr year, or simply a month select - if (!months_start.contains(values[1])) { - months_start.push(values[1]); - } // add to months array - } - - if ($type(values[0]) != 'number' || values[0] == years.getLast()) { // if it's a year / month combo for curr year, or simply a month select - if (!months_end.contains(values[1])) { - months_end.push(values[1]); - } // add to months array - } - }, this); - - months_start.sort(this.sort); - months_end.sort(this.sort); - - if (months_start[0] > start.getMonth()) { - d = new Date(start.getFullYear(), months_start[0] + 1, 0); // last day of new month - - if (start.getDate() > d.getDate()) { - start.setDate(d.getDate()); - } - - start.setMonth(months_start[0]); - } - - if (months_end.getLast() < end.getMonth()) { - d = new Date(start.getFullYear(), months_end.getLast() + 1, 0); // last day of new month - - if (end.getDate() > d.getDate()) { - end.setDate(d.getDate()); - } - - end.setMonth(months_end.getLast()); - } - } - } - }, this); - - return { - 'start': start, - 'end': end - }; - }, - - /** - * caption: returns the caption element with header and navigation - * - * @param cal (obj) - * @returns caption (element) - */ - caption: function(cal) { - // start by assuming navigation is allowed - var navigation = { - prev: { - 'month': true, - 'year': true - }, - next: { - 'month': true, - 'year': true - } - }; - - // if we're in an out of bounds year - if (cal.year == cal.start.getFullYear()) { - navigation.prev.year = false; - if (cal.month == cal.start.getMonth() && this.options.navigation == 1) { - navigation.prev.month = false; - } - } - if (cal.year == cal.end.getFullYear()) { - navigation.next.year = false; - if (cal.month == cal.end.getMonth() && this.options.navigation == 1) { - navigation.next.month = false; - } - } - - // special case of improved navigation but months array with only 1 month we can disable all month navigation - if ($type(cal.months) == 'array') { - if (cal.months.length == 1 && this.options.navigation == 2) { - navigation.prev.month = navigation.next.month = false; - } - } - - var caption = new Element('caption'); - - var prev = new Element('a').addClass(this.classes.prev).appendText('\x3c'); // < - var next = new Element('a').addClass(this.classes.next).appendText('\x3e'); // > - - if (this.options.navigation == 2) { - var month = new Element('span').addClass(this.classes.month).inject(caption); - - if (navigation.prev.month) { - prev.clone().addEvent('click', function(cal) { - this.navigate(cal, 'm', -1); - }.pass(cal, this)).inject(month); - } - - month.adopt(new Element('span').appendText(this.options.months[cal.month])); - - if (navigation.next.month) { - next.clone().addEvent('click', function(cal) { - this.navigate(cal, 'm', 1); - }.pass(cal, this)).inject(month); - } - - var year = new Element('span').addClass(this.classes.year).inject(caption); - - if (navigation.prev.year) { - prev.clone().addEvent('click', function(cal) { - this.navigate(cal, 'y', -1); - }.pass(cal, this)).inject(year); - } - - year.adopt(new Element('span').appendText(cal.year)); - - if (navigation.next.year) { - next.clone().addEvent('click', function(cal) { - this.navigate(cal, 'y', 1); - }.pass(cal, this)).inject(year); - } - } - else { // 1 or 0 - if (navigation.prev.month && this.options.navigation) { - prev.clone().addEvent('click', function(cal) { - this.navigate(cal, 'm', -1); - }.pass(cal, this)).inject(caption); - } - - caption.adopt(new Element('span').addClass(this.classes.month).appendText(this.options.months[cal.month])); - - caption.adopt(new Element('span').addClass(this.classes.year).appendText(cal.year)); - - if (navigation.next.month && this.options.navigation) { - next.clone().addEvent('click', function(cal) { - this.navigate(cal, 'm', 1); - }.pass(cal, this)).inject(caption); - } - - } - - return caption; - }, - - /** - * changed: run when a select value is changed - * - * @param cal (obj) - * @returns caption (element) - */ - changed: function(cal) { - cal.val = this.read(cal); // update calendar val from inputs - - $extend(cal, this.values(cal)); // update bounds - based on curr month - - this.rebuild(cal); // rebuild days select - - if (!cal.val) { - return; - } // in case the same date was clicked the cal has no set date we should exit - - if (cal.val.getDate() < cal.days[0]) { - cal.val.setDate(cal.days[0]); - } - if (cal.val.getDate() > cal.days.getLast()) { - cal.val.setDate(cal.days.getLast()); - } - - cal.els.each(function(el) { // then we can set the value to the field - el.set('value',this.format(cal.val, el.retrieve('format'))); - }, this); - - this.check(cal); // checks other cals - - this.calendars.each(function(kal) { // update cal graphic if visible - if (kal.visible) { - this.display(kal); - } - }, this); - }, - - /** - * check: checks other calendars to make sure no overlapping values - * - * @param cal (obj) - */ - check: function(cal) { - this.calendars.each(function(kal, i) { - if (kal.val) { // if calendar has value set - var change = false; - - if (i < cal.id) { // preceding calendar - var bound = new Date(Date.parse(cal.val)); - - bound.setDate(bound.getDate() - (this.options.pad * (cal.id - i))); - - if (bound < kal.val) { - change = true; - } - } - if (i > cal.id) { // following calendar - var bound = new Date(Date.parse(cal.val)); - - bound.setDate(bound.getDate() + (this.options.pad * (i - cal.id))); - - if (bound > kal.val) { - change = true; - } - } - - if (change) { - if (kal.start > bound) { - bound = kal.start; - } - if (kal.end < bound) { - bound = kal.end; - } - - kal.month = bound.getMonth(); - kal.year = bound.getFullYear(); - - $extend(kal, this.values(kal)); - - // TODO - IN THE CASE OF SELECT MOVE TO NEAREST VALID VALUE - // IN THE CASE OF INPUT DISABLE - - // if new date is not valid better unset cal value - // otherwise it would mean incrementally checking to find the nearest valid date which could be months / years away - kal.val = kal.days.contains(bound.getDate()) ? bound : null; - - this.write(kal); - - if (kal.visible) { - this.display(kal); - } // update cal graphic if visible - } - } - }, this); - }, - - /** - * clicked: run when a valid day is clicked in the calendar - * - * @param td (obj) - * @param day (int) - * @param cal (obj) - */ - clicked: function(td, day, cal) { - cal.val = (this.value(cal) == day) ? null : new Date(cal.year, cal.month, day); // set new value - if same then disable - - this.write(cal); - - // ok - in the special case that it's all selects and there's always a date no matter what (at least as far as the form is concerned) - // we can't let the calendar undo a date selection - it's just not possible!! - if (!cal.val) { - cal.val = this.read(cal); - } - - if (cal.val) { - this.check(cal); // checks other cals - this.toggle(cal); // hide cal - } - else { // remove active class and replace with valid - td.addClass(this.classes.valid); - td.removeClass(this.classes.active); - } - }, - - /** - * display: create calendar element - * - * @param cal (obj) - */ - display: function(cal) { - // 1. header and navigation - this.calendar.empty(); // init div - this.calendar.className = this.classes.calendar + ' ' + this.options.months[cal.month].toLowerCase(); - // render optional head content - var extra = this.displayHead(cal); - if ($defined(extra) && $defined(extra.inject)) extra.inject(this.calendar); - //this.displayHead(cal).inject(this.calendar, 'top'); - - var div = new Element('div').inject(this.calendar); // a wrapper div to help correct browser css problems with the caption element - var table = new Element('table').inject(div).adopt(this.caption(cal)); - // 2. day names - var thead = new Element('thead').inject(table); - var tr = new Element('tr').inject(thead); - - for (var i = 0; i <= 6; i++) { - var th = this.options.days[(i + this.options.offset) % 7]; - - tr.adopt(new Element('th', { - 'title': th - }).appendText(th.substr(0, this.options.dayLabelLength))); - } - - // 3. day numbers - var tbody = new Element('tbody').inject(table); - var tr = new Element('tr').inject(tbody); - var d = new Date(cal.year, cal.month, 1); - var offset = ((d.getDay() - this.options.offset) + 7) % 7; // day of the week (offset) - var last = new Date(cal.year, cal.month + 1, 0).getDate(); // last day of this month - var prev = new Date(cal.year, cal.month, 0).getDate(); // last day of previous month - var active = this.value(cal); // active date (if set and within curr month) - var valid = cal.days; // valid days for curr month - var inactive = []; // active dates set by other calendars - var hilited = []; - this.calendars.each(function(kal, i) { - if (kal != cal && kal.val) { - if (cal.year == kal.val.getFullYear() && cal.month == kal.val.getMonth()) { - inactive.push(kal.val.getDate()); - } - if (cal.val) { - for (var day = 1; day <= last; day++) { - d.setDate(day); - if ((i < cal.id && d > kal.val && d < cal.val) || (i > cal.id && d > cal.val && d < kal.val)) { - if (!hilited.contains(day)) { - hilited.push(day); - } - } - } - } - } - }, this); - var d = new Date(); - var today = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime(); // today obv - - for (var i = 1; i < 43; i++) { // 1 to 42 (6 x 7 or 6 weeks) - if ((i - 1) % 7 == 0) { - tr = new Element('tr').inject(tbody); - } // each week is it's own table row - var td = new Element('td').inject(tr); - var day = i - offset; - var date = new Date(cal.year, cal.month, day); - var cls = ''; - - if (day === active) { - cls = this.classes.active; - } // active - else if (inactive.contains(day)) { - cls = this.classes.inactive; - } // inactive - else if (valid.contains(day)) { - cls = this.classes.valid; - } // valid - else if (day >= 1 && day <= last) { - cls = this.classes.invalid; - } // invalid - - if (date.getTime() == today) { - cls = cls + ' ' + this.classes.today; - } // adds class for today - if (hilited.contains(day)) { - cls = cls + ' ' + this.classes.hilite; - } // adds class if hilited - td.addClass(cls); - - if (valid.contains(day)) { // if it's a valid - clickable - day we add interaction - td.set('title', this.format(date, 'D M jS Y')); - td.addEvents({ - 'click': function(td, day, cal) { - this.clicked(td, day, cal); - }.pass([td, day, cal], this), - 'mouseover': function(td, cls) { - td.addClass(cls); - }.pass([td, this.classes.hover]), - 'mouseout': function(td, cls) { - td.removeClass(cls); - }.pass([td, this.classes.hover]) - }); - } - - // pad calendar with last days of prev month and first days of next month - if (day < 1) { - day = prev + day; - } - else if (day > last) { - day = day - last; - } - td.appendText(day); - } - // render optional footer content - extra = this.displayFooter(cal) - if ($defined(extra) && $defined(extra.inject)) extra.inject(this.calendar); - - this.calendar.coord = this.calendar.getCoordinates(); - }, - - /** - * displayHead - * - * @abstract - */ - displayHead: function(cal) {}, - - /** - * displayFooter - * - * @abstract - */ - displayFooter: function(cal) {}, - - /** - * element: helper function - * - * @param el (string) element id - * @param f (string) format string - * @param cal (obj) - */ - element: function(el, f, cal) { - if ($type(f) == 'object') { // in the case of multiple inputs per calendar - for (var i in f) { - if (!this.element(i, f[i], cal)) { - return false; - } - } - - return true; - } - - el = $(el); - - if (!el) { - return false; - } - - el.store('format',f); - - if (el.get('tag') == 'select') { // select elements allow the user to manually set the date via select option - el.addEvent('change', function(cal) { - this.changed(cal); - }.pass(cal, this)); - } - else { // input (type text) elements restrict the user to only setting the date via the calendar - el.set('readonly','readonly'); - el.addEvent('focus', function(cal) { - this.toggle(cal); - }.pass(cal, this)); - } - - cal.els.push(el); - - return true; - }, - - /** - * format: formats a date object according to passed in instructions - * - * @param date (obj) - * @param f (string) any combination of punctuation / separators and d, j, D, l, S, m, n, F, M, y, Y - * @returns string - */ - format: function(date, f) { - var g = ''; - - if (date) { - var d = date.getDate(); // 1 - 31 - var day = this.options.days[date.getDay()]; // Sunday - Saturday - var m = date.getMonth() + 1; // 1 - 12 - var month = this.options.months[date.getMonth()]; // January - December - var y = date.getFullYear() + ''; // 19xx - 20xx - - for (var i = 0; i < f.length; i++) { - var c = f.charAt(i); // format char - switch(c) { - // year cases - case 'y': // xx - xx - y = y.substr(2); - case 'Y': // 19xx - 20xx - g += y; - break; - - // month cases - case 'm': // 01 - 12 - if (m < 10) { - m = '0' + m; - } - case 'n': // 1 - 12 - g += m; - break; - - case 'M': // Jan - Dec - month = month.substr(0, 3); - case 'F': // January - December - g += month; - break; - - // day cases - case 'd': // 01 - 31 - if (d < 10) { - d = '0' + d; - } - case 'j': // 1 - 31 - g += d; - break; - - case 'D': // Sun - Sat - day = day.substr(0, 3); - case 'l': // Sunday - Saturday - g += day; - break; - - case 'S': // st, nd, rd or th (works well with j) - if (d % 10 == 1 && d != '11') { - g += 'st'; - } - else if (d % 10 == 2 && d != '12') { - g += 'nd'; - } - else if (d % 10 == 3 && d != '13') { - g += 'rd'; - } - else { - g += 'th'; - } - break; - - default: - g += c; - } - } - } - return g; // return format with values replaced - }, - - /** - * navigate: calendar navigation - * - * @param cal (obj) - * @param type (str) m or y for month or year - * @param n (int) + or - for next or prev - */ - navigate: function(cal, type, n) { - switch (type) { - case 'm': // month - if ($type(cal.months) == 'array') { - var i = cal.months.indexOf(cal.month) + n; // index of current month - - if (i < 0 || i == cal.months.length) { // out of range - if (this.options.navigation == 1) { // if type 1 nav we'll need to increment the year - this.navigate(cal, 'y', n); - } - - i = (i < 0) ? cal.months.length - 1 : 0; - } - - cal.month = cal.months[i]; - } - else { - var i = cal.month + n; - - if (i < 0 || i == 12) { - if (this.options.navigation == 1) { - this.navigate(cal, 'y', n); - } - - i = (i < 0) ? 11 : 0; - } - - cal.month = i; - } - break; - - case 'y': // year - if ($type(cal.years) == 'array') { - var i = cal.years.indexOf(cal.year) + n; - - cal.year = cal.years[i]; - } - else { - cal.year += n; - } - break; - } - $extend(cal, this.values(cal)); - if ($type(cal.months) == 'array') { // if the calendar has a months select - var i = cal.months.indexOf(cal.month); // and make sure the curr months exists for the new year - - if (i < 0) { - cal.month = cal.months[0]; - } // otherwise we'll reset the month - } - this.display(cal); - }, - - /** - * read: compiles cal value based on array of inputs passed in - * - * @param cal (obj) - * @returns date (obj) or (null) - */ - read: function(cal) { - var arr = [null, null, null]; - cal.els.each(function(el) { - // returns an array which may contain empty values - var values = this.unformat(el.get('value'), el.retrieve('format')); - - values.each(function(val, i) { - if ($type(val) == 'number') { - arr[i] = val; - } - }); - }, this); - // we can update the cals month and year values - if ($type(arr[0]) == 'number') { - cal.year = arr[0]; - } - if ($type(arr[1]) == 'number') { - cal.month = arr[1]; - } - var val = null; - if (arr.every(function(i) { - return $type(i) == 'number'; - })) { // if valid date - var last = new Date(arr[0], arr[1] + 1, 0).getDate(); // last day of month - - if (arr[2] > last) { - arr[2] = last; - } // make sure we stay within the month (ex in case default day of select is 31 and month is feb) - val = new Date(arr[0], arr[1], arr[2]); - } - return (cal.val == val) ? null : val; // if new date matches old return null (same date clicked twice = disable) - }, - - /** - * rebuild: rebuilds days + months selects - * - * @param cal (obj) - */ - rebuild: function(cal) { - cal.els.each(function(el) { - /* - if (el.getTag() == 'select' && el.format.test('^(F|m|M|n)$')) { // special case for months-only select - if (!cal.options) { cal.options = el.clone(); } // clone a copy of months select - - var val = (cal.val) ? cal.val.getMonth() : el.value.toInt(); - - el.empty(); // initialize select - - cal.months.each(function(month) { - // create an option element - var option = new Element('option', { - 'selected': (val == month), - 'value': this.format(new Date(1, month, 1), el.format); - }).appendText(day).injectInside(el); - }, this); - } - */ - if (el.get('tag') == 'select' && el.retrieve('format').test('^(d|j)$')) { // special case for days-only select - var d = this.value(cal); - - if (!d) { - d = el.get('value').toInt(); - } // if the calendar doesn't have a set value, try to use value from select - - el.empty(); // initialize select - - cal.days.each(function(day) { - // create an option element - var option = new Element('option', { - 'value': ((el.retrieve('format') == 'd' && day < 10) ? '0' + day : day) - }).appendText(day).inject(el); - if(d == day){ - option.set('selected','selected'); - } - }, this); - } - }, this); - }, - - /** - * sort: helper function for numerical sorting - * - * @param a (int) - * @param b (int) - */ - sort: function(a, b) { - return a - b; - }, - - /** - * toggle: show / hide calendar - * - * @param cal (obj) - */ - toggle: function(cal) { - document.removeEvent('mousedown', this.fn); // always remove the current mousedown script first - - if (cal.visible) { // simply hide curr cal - cal.visible = false; - cal.button.removeClass(this.classes.active); // active - this.fx.start('opacity', 0); //*RAL - Fx.Tween.start([property], from, [to]) - } - else { // otherwise show (may have to hide others) - // hide cal on out-of-bounds click - this.fn = function(e, cal) { - var event = new Event(e); - var el = $(event.target); - var stop = false; - - while (el != document.body && el.nodeType == 1) { - if (el == this.calendar) { - stop = true; - } - this.calendars.each(function(kal) { - if (kal.button == el || kal.els.contains(el)) { - stop = true; - } - }); - - if (stop) { - e.stop(); - return false; - } - else { - el = el.getParent(); - } - } - - this.toggle(cal); - }.create({ - 'arguments': cal, - 'bind': this, - 'event': true - }); - - document.addEvent('mousedown', this.fn); - - this.calendars.each(function(kal) { - if (kal == cal) { - kal.visible = true; - kal.button.addClass(this.classes.active); // css c-icon-active - } - else { - kal.visible = false; - kal.button.removeClass(this.classes.active); // css c-icon-active - } - }, this); - - this.display(cal); - - var size = window.getScrollSize(); - var coord = cal.button.getCoordinates(); - var x = coord.right + this.options.tweak.x; - var y = coord.top + this.options.tweak.y; - - // make sure the calendar doesn't open off screen (does't work in safari!?) - if (x + this.calendar.coord.width > size.x) { - x -= (x + this.calendar.coord.width - size.x); - } - if (y + this.calendar.coord.height > size.y) { - y -= (y + this.calendar.coord.height - size.y); - } - - this.calendar.setStyles({ - left: x + 'px', - top: y + 'px' - }); - // IE6 Iframe Fix - if (Browser.Engine.trident4) { - this.iframe.setStyles({ - left: x + 'px', - top: y + 'px', - height: this.calendar.coord.height + 'px', - width: this.calendar.coord.width + 'px' - }); - } - this.fx.start('opacity', 1); //*RAL - Fx.Tween.start([property], from, [to]) - } - }, - - /** - * unformat: takes a value from an input and parses the d, m and y elements - * - * @param val (string) - * @param f (string) any combination of punctuation / separators and d, j, D, l, S, m, n, F, M, y, Y - * @returns array - */ - unformat: function(val, f) { - f = f.escapeRegExp(); - - var re = { - d: '([0-9]{2})', - j: '([0-9]{1,2})', - D: '(' + this.options.days.map(function(day) { - return day.substr(0, 3); - }).join('|') + ')', - l: '(' + this.options.days.join('|') + ')', - S: '(st|nd|rd|th)', - F: '(' + this.options.months.join('|') + ')', - m: '([0-9]{2})', - M: '(' + this.options.months.map(function(month) { - return month.substr(0, 3); - }).join('|') + ')', - N: '([0-9]{1,2})', - n: '([0-9]{1,2})', - Y: '([0-9]{4})', - y: '([0-9]{2})' - }; - - var arr = []; // array of indexes - var g = ''; - - // convert our format string to regexp - for (var i = 0; i < f.length; i++) { - var c = f.charAt(i); - if (re[c]) { - arr.push(c); - g += re[c]; - } - else { - g += c; - } - } - - // match against date - var matches = val.match('^' + g + '$'); - var dates = new Array(3); - if (matches) { - matches = matches.slice(1); // remove first match which is the date - arr.each(function(c, i) { - i = matches[i]; - - switch(c) { - // year cases - case 'y': - i = '19' + i; // 2 digit year assumes 19th century (same as JS) - case 'Y': - dates[0] = i.toInt(); - break; - - // month cases - case 'F': - i = i.substr(0, 3); - case 'M': - i = this.options.months.map(function(month) { - return month.substr(0, 3); - }).indexOf(i) + 1; - case 'm': - case 'n': - dates[1] = i.toInt() - 1; - break; - - // day cases - case 'd': - case 'j': - dates[2] = i.toInt(); - break; - } - }, this); - } - return dates; - }, - - /** - * value: returns day value of calendar if set - * - * @param cal (obj) - * @returns day (int) or null - */ - value: function(cal) { - var day = null; - if (cal.val) { - if (cal.year == cal.val.getFullYear() && cal.month == cal.val.getMonth()) { - day = cal.val.getDate(); - } - } - return day; - }, - - /** - * values: returns the years, months (for curr year) and days (for curr month and year) for the calendar - * - * @param cal (obj) - * @returns obj - */ - values: function(cal) { - // make it possible - this.fireEvent('onValuesStart', this.element); - var years, months, days; - - cal.els.each(function(el) { - if (el.get('tag') == 'select') { - if (el.retrieve('format').test('(y|Y)')) { // search for a year select - years = []; - - el.getChildren().each(function(option) { // get options - var values = this.unformat(option.get('value'), el.retrieve('format')); - - if (!years.contains(values[0])) { - years.push(values[0]); - } // add to years array - }, this); - - years.sort(this.sort); - } - - if (el.retrieve('format').test('(F|m|M|n|N)')) { // search for a month select - months = []; // 0 - 11 should be - - el.getChildren().each(function(option) { // get options - var values = this.unformat(option.get('value'), el.retrieve('format')); - - if ($type(values[0]) != 'number' || values[0] == cal.year) { // if it's a year / month combo for curr year, or simply a month select - if (!months.contains(values[1])) { - months.push(values[1]); - } // add to months array - } - }, this); - - months.sort(this.sort); - } - - if (el.retrieve('format').test('(d|j)') && !el.retrieve('format').test('^(d|j)$')) { // search for a day select, but NOT a days only select - days = []; // 1 - 31 - - el.getChildren().each(function(option) { // get options - var values = this.unformat(option.get('value'), el.retrieve('format')); - - // in the special case of days we dont want the value if its a days only select - // otherwise that will screw up the options rebuilding - // we will take the values if they are exact dates though - if (values[0] == cal.year && values[1] == cal.month) { - if (!days.contains(values[2])) { - days.push(values[2]); - } // add to days array - } - }, this); - } - } - }, this); - - // we start with what would be the first and last days were there no restrictions - var first = 1; - var last = new Date(cal.year, cal.month + 1, 0).getDate(); // last day of the month - - // if we're in an out of bounds year - if (cal.year == cal.start.getFullYear()) { - // in the special case of improved navigation but no months array, we'll need to construct one - if (months == null && this.options.navigation == 2) { - months = []; - - for (var i = 0; i < 12; i ++) { - if (i >= cal.start.getMonth()) { - months.push(i); - } - } - } - - // if we're in an out of bounds month - if (cal.month == cal.start.getMonth()) { - first = cal.start.getDate(); // first day equals day of bound - } - } - if (cal.year == cal.end.getFullYear()) { - // in the special case of improved navigation but no months array, we'll need to construct one - if (months == null && this.options.navigation == 2) { - months = []; - - for (var i = 0; i < 12; i ++) { - if (i <= cal.end.getMonth()) { - months.push(i); - } - } - } - - if (cal.month == cal.end.getMonth()) { - last = cal.end.getDate(); // last day equals day of bound - } - } - - // let's get our invalid days - var blocked = this.blocked(cal); - - // finally we can prepare all the valid days in a neat little array - if ($type(days) == 'array') { // somewhere there was a days select - days = days.filter(function(day) { - if (day >= first && day <= last && !blocked.contains(day)) { - return day; - } - }); - } - else { // no days select we'll need to construct a valid days array - days = []; - - for (var i = first; i <= last; i++) { - if (!blocked.contains(i)) { - days.push(i); - } - } - } - - days.sort(this.sort); // sorting our days will give us first and last of month - - return { - 'days': days, - 'months': months, - 'years': years - }; - }, - - /** - * write: sets calendars value to form elements - * - * @param cal (obj) - */ - write: function(cal) { - this.rebuild(cal); // in the case of options, we'll need to make sure we have the correct number of days available - - cal.els.each(function(el) { // then we can set the value to the field - el.set('value',this.format(cal.val, el.retrieve('format'))); - }, this); - } -}); \ No newline at end of file +// Calendar: a Javascript class for Mootools that adds accessible and unobtrusive date pickers to your form elements +// Calendar RC4, Copyright (c) 2007 Aeron Glemann , MIT Style License. +// Mootools 1.2 compatibility by Davorin Ĺ ego + +var Calendar = new Class({ + + Implements: Options, + + options: { + blocked: [], // blocked dates + classes: [], // ['calendar', 'prev', 'next', 'month', 'year', 'today', 'invalid', 'valid', 'inactive', 'active', 'hover', 'hilite'] + days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], // days of the week starting at sunday + direction: 0, // -1 past, 0 past + future, 1 future + draggable: true, + months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + navigation: 1, // 0 = no nav; 1 = single nav for month; 2 = dual nav for month and year + offset: 0, // first day of the week: 0 = sunday, 1 = monday, etc.. + onHideStart: Class.empty, + onHideComplete: Class.empty, + onShowStart: Class.empty, + onShowComplete: Class.empty, + pad: 1, // padding between multiple calendars + tweak: {x: 0, y: 0} // tweak calendar positioning + }, + + // initialize: calendar constructor + // @param obj (obj) a js object containing the form elements and format strings { id: 'format', id: 'format' etc } + // @param props (obj) optional properties + + initialize: function(obj, options) { + // basic error checking + if (!obj) { return false; } + + this.setOptions(options); + + // create our classes array + var keys = ['calendar', 'prev', 'next', 'month', 'year', 'today', 'invalid', 'valid', 'inactive', 'active', 'hover', 'hilite']; + + var values = keys.map(function(key, i) { + if (this.options.classes[i]) { + if (this.options.classes[i].length) { key = this.options.classes[i]; } + } + return key; + }, this); + + this.classes = values.associate(keys); + + // create cal element with css styles required for proper cal functioning + this.calendar = new Element('div', { + 'styles': { left: '-1000px', opacity: 0, position: 'absolute', top: '-1000px', zIndex: 1000 } + }).addClass(this.classes.calendar).injectInside(document.body); + + // iex 6 needs a transparent iframe underneath the calendar in order to not allow select elements to render through + if (window.ie6) { + this.iframe = new Element('iframe', { + 'styles': { left: '-1000px', position: 'absolute', top: '-1000px', zIndex: 999 } + }).injectInside(document.body); + this.iframe.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'; + } + + // initialize fade method + this.fx = new Fx.Tween(this.calendar, { + onStart: function() { + if (this.calendar.getStyle('opacity') == 0) { // show + if (window.ie6) { this.iframe.setStyle('display', 'block'); } + this.calendar.setStyle('display', 'block'); + this.fireEvent('onShowStart', this.element); + } + else { // hide + this.fireEvent('onHideStart', this.element); + } + }.bind(this), + onComplete: function() { + if (this.calendar.getStyle('opacity') == 0) { // hidden + this.calendar.setStyle('display', 'none'); + if (window.ie6) { this.iframe.setStyle('display', 'none'); } + this.fireEvent('onHideComplete', this.element); + } + else { // shown + this.fireEvent('onShowComplete', this.element); + } + }.bind(this) + }); + + // initialize drag method + if (window.Drag && this.options.draggable) { + this.drag = new Drag.Move(this.calendar, { + onDrag: function() { + if (window.ie6) { this.iframe.setStyles({ left: this.calendar.style.left, top: this.calendar.style.top }); } + }.bind(this) + }); + } + + // create calendars array + this.calendars = []; + + var id = 0; + var d = new Date(); // today + + d.setDate(d.getDate() + this.options.direction.toInt()); // correct today for directional offset + + for (var i in obj) { + var cal = { + button: new Element('button', { 'type': 'button' }), + el: $(i), + els: [], + id: id++, + month: d.getMonth(), + visible: false, + year: d.getFullYear() + }; + + // fix for bad element (naughty, naughty element!) + if (!this.element(i, obj[i], cal)) { continue; } + + cal.el.addClass(this.classes.calendar); + + // create cal button + cal.button.addClass(this.classes.calendar).addEvent('click', function(cal) { this.toggle(cal); }.pass(cal, this)).injectAfter(cal.el); + + // read in default value + cal.val = this.read(cal); + + $extend(cal, this.bounds(cal)); // abs bounds of calendar + + $extend(cal, this.values(cal)); // valid days, months, years + + this.rebuild(cal); + + this.calendars.push(cal); // add to cals array + } + }, + + + // blocked: returns an array of blocked days for the month / year + // @param cal (obj) + // @returns blocked days (array) + + blocked: function(cal) { + var blocked = []; + var offset = new Date(cal.year, cal.month, 1).getDay(); // day of the week (offset) + var last = new Date(cal.year, cal.month + 1, 0).getDate(); // last day of this month + + this.options.blocked.each(function(date){ + var values = date.split(' '); + + // preparation + for (var i = 0; i <= 3; i++){ + if (!values[i]){ values[i] = (i == 3) ? '' : '*'; } // make sure blocked date contains values for at least d, m and y + values[i] = values[i].contains(',') ? values[i].split(',') : new Array(values[i]); // split multiple values + var count = values[i].length - 1; + for (var j = count; j >= 0; j--){ + if (values[i][j].contains('-')){ // a range + var val = values[i][j].split('-'); + for (var k = val[0]; k <= val[1]; k++){ + if (!values[i].contains(k)){ values[i].push(k + ''); } + } + values[i].splice(j, 1); + } + } + } + + // execution + if (values[2].contains(cal.year + '') || values[2].contains('*')){ + if (values[1].contains(cal.month + 1 + '') || values[1].contains('*')){ + values[0].each(function(val){ // if blocked value indicates this month / year + if (val > 0){ blocked.push(val.toInt()); } // add date to blocked array + }); + + if (values[3]){ // optional value for day of week + for (var i = 0; i < last; i++){ + var day = (i + offset) % 7; + + if (values[3].contains(day + '')){ + blocked.push(i + 1); // add every date that corresponds to the blocked day of the week to the blocked array + } + } + } + } + } + }, this); + + return blocked; + }, + + + // bounds: returns the start / end bounds of the calendar + // @param cal (obj) + // @returns obj + + bounds: function(cal) { + // 1. first we assume the calendar has no bounds (or a thousand years in either direction) + + // by default the calendar will accept a millennium in either direction + var start = new Date(1000, 0, 1); // jan 1, 1000 + var end = new Date(2999, 11, 31); // dec 31, 2999 + + // 2. but if the cal is one directional we adjust accordingly + var date = new Date().getDate() + this.options.direction.toInt(); + + if (this.options.direction > 0) { + start = new Date(); + start.setDate(date + this.options.pad * cal.id); + } + + if (this.options.direction < 0) { + end = new Date(); + end.setDate(date - this.options.pad * (this.calendars.length - cal.id - 1)); + } + + // 3. then we can further filter the limits by using the pre-existing values in the selects + cal.els.each(function(el) { + if (el.get('tag') == 'select') { + if (el.format.test('(y|Y)')) { // search for a year select + var years = []; + + el.getChildren().each(function(option) { // get options + var values = this.unformat(option.value, el.format); + + if (!years.contains(values[0])) { years.push(values[0]); } // add to years array + }, this); + + years.sort(this.sort); + + if (years[0] > start.getFullYear()) { + d = new Date(years[0], start.getMonth() + 1, 0); // last day of new month + + if (start.getDate() > d.getDate()) { start.setDate(d.getDate()); } + + start.setYear(years[0]); + } + + if (years.getLast() < end.getFullYear()) { + d = new Date(years.getLast(), end.getMonth() + 1, 0); // last day of new month + + if (end.getDate() > d.getDate()) { end.setDate(d.getDate()); } + + end.setYear(years.getLast()); + } + } + + if (el.format.test('(F|m|M|n)')) { // search for a month select + var months_start = []; + var months_end = []; + + el.getChildren().each(function(option) { // get options + var values = this.unformat(option.value, el.format); + + if ($type(values[0]) != 'number' || values[0] == years[0]) { // if it's a year / month combo for curr year, or simply a month select + if (!months_start.contains(values[1])) { months_start.push(values[1]); } // add to months array + } + + if ($type(values[0]) != 'number' || values[0] == years.getLast()) { // if it's a year / month combo for curr year, or simply a month select + if (!months_end.contains(values[1])) { months_end.push(values[1]); } // add to months array + } + }, this); + + months_start.sort(this.sort); + months_end.sort(this.sort); + + if (months_start[0] > start.getMonth()) { + d = new Date(start.getFullYear(), months_start[0] + 1, 0); // last day of new month + + if (start.getDate() > d.getDate()) { start.setDate(d.getDate()); } + + start.setMonth(months_start[0]); + } + + if (months_end.getLast() < end.getMonth()) { + d = new Date(start.getFullYear(), months_end.getLast() + 1, 0); // last day of new month + + if (end.getDate() > d.getDate()) { end.setDate(d.getDate()); } + + end.setMonth(months_end.getLast()); + } + } + } + }, this); + + return { 'start': start, 'end': end }; + }, + + + // caption: returns the caption element with header and navigation + // @param cal (obj) + // @returns caption (element) + + caption: function(cal) { + // start by assuming navigation is allowed + var navigation = { + prev: { 'month': true, 'year': true }, + next: { 'month': true, 'year': true } + }; + + // if we're in an out of bounds year + if (cal.year == cal.start.getFullYear()) { + navigation.prev.year = false; + if (cal.month == cal.start.getMonth() && this.options.navigation == 1) { + navigation.prev.month = false; + } + } + if (cal.year == cal.end.getFullYear()) { + navigation.next.year = false; + if (cal.month == cal.end.getMonth() && this.options.navigation == 1) { + navigation.next.month = false; + } + } + + // special case of improved navigation but months array with only 1 month we can disable all month navigation + if ($type(cal.months) == 'array') { + if (cal.months.length == 1 && this.options.navigation == 2) { + navigation.prev.month = navigation.next.month = false; + } + } + + var caption = new Element('caption'); + + var prev = new Element('a').addClass(this.classes.prev).appendText('\x3c'); // < + var next = new Element('a').addClass(this.classes.next).appendText('\x3e'); // > + + if (this.options.navigation == 2) { + var month = new Element('span').addClass(this.classes.month).injectInside(caption); + + if (navigation.prev.month) { prev.clone().addEvent('click', function(cal) { this.navigate(cal, 'm', -1); }.pass(cal, this)).injectInside(month); } + + month.adopt(new Element('span').appendText(this.options.months[cal.month])); + + if (navigation.next.month) { next.clone().addEvent('click', function(cal) { this.navigate(cal, 'm', 1); }.pass(cal, this)).injectInside(month); } + + var year = new Element('span').addClass(this.classes.year).injectInside(caption); + + if (navigation.prev.year) { prev.clone().addEvent('click', function(cal) { this.navigate(cal, 'y', -1); }.pass(cal, this)).injectInside(year); } + + year.adopt(new Element('span').appendText(cal.year)); + + if (navigation.next.year) { next.clone().addEvent('click', function(cal) { this.navigate(cal, 'y', 1); }.pass(cal, this)).injectInside(year); } + } + else { // 1 or 0 + if (navigation.prev.month && this.options.navigation) { prev.clone().addEvent('click', function(cal) { this.navigate(cal, 'm', -1); }.pass(cal, this)).injectInside(caption); } + + caption.adopt(new Element('span').addClass(this.classes.month).appendText(this.options.months[cal.month])); + + caption.adopt(new Element('span').addClass(this.classes.year).appendText(cal.year)); + + if (navigation.next.month && this.options.navigation) { next.clone().addEvent('click', function(cal) { this.navigate(cal, 'm', 1); }.pass(cal, this)).injectInside(caption); } + + } + + return caption; + }, + + + // changed: run when a select value is changed + // @param cal (obj) + + changed: function(cal) { + cal.val = this.read(cal); // update calendar val from inputs + + $extend(cal, this.values(cal)); // update bounds - based on curr month + + this.rebuild(cal); // rebuild days select + + if (!cal.val) { return; } // in case the same date was clicked the cal has no set date we should exit + + if (cal.val.getDate() < cal.days[0]) { cal.val.setDate(cal.days[0]); } + if (cal.val.getDate() > cal.days.getLast()) { cal.val.setDate(cal.days.getLast()); } + + cal.els.each(function(el) { // then we can set the value to the field + el.value = this.format(cal.val, el.format); + }, this); + + this.check(cal); // checks other cals + + this.calendars.each(function(kal) { // update cal graphic if visible + if (kal.visible) { this.display(kal); } + }, this); + }, + + + // check: checks other calendars to make sure no overlapping values + // @param cal (obj) + + check: function(cal) { + this.calendars.each(function(kal, i) { + if (kal.val) { // if calendar has value set + var change = false; + + if (i < cal.id) { // preceding calendar + var bound = new Date(Date.parse(cal.val)); + + bound.setDate(bound.getDate() - (this.options.pad * (cal.id - i))); + + if (bound < kal.val) { change = true; } + } + if (i > cal.id) { // following calendar + var bound = new Date(Date.parse(cal.val)); + + bound.setDate(bound.getDate() + (this.options.pad * (i - cal.id))); + + if (bound > kal.val) { change = true; } + } + + if (change) { + if (kal.start > bound) { bound = kal.start; } + if (kal.end < bound) { bound = kal.end; } + + kal.month = bound.getMonth(); + kal.year = bound.getFullYear(); + + $extend(kal, this.values(kal)); + + // TODO - IN THE CASE OF SELECT MOVE TO NEAREST VALID VALUE + // IN THE CASE OF INPUT DISABLE + + // if new date is not valid better unset cal value + // otherwise it would mean incrementally checking to find the nearest valid date which could be months / years away + kal.val = kal.days.contains(bound.getDate()) ? bound : null; + + this.write(kal); + + if (kal.visible) { this.display(kal); } // update cal graphic if visible + } + } + else { + kal.month = cal.month; + kal.year = cal.year; + } + }, this); + }, + + + // clicked: run when a valid day is clicked in the calendar + // @param cal (obj) + + clicked: function(td, day, cal) { + cal.val = (this.value(cal) == day) ? null : new Date(cal.year, cal.month, day); // set new value - if same then disable + + this.write(cal); + + // ok - in the special case that it's all selects and there's always a date no matter what (at least as far as the form is concerned) + // we can't let the calendar undo a date selection - it's just not possible!! + if (!cal.val) { cal.val = this.read(cal); } + + if (cal.val) { + this.check(cal); // checks other cals + this.toggle(cal); // hide cal + } + else { // remove active class and replace with valid + td.addClass(this.classes.valid); + td.removeClass(this.classes.active); + } + }, + + + // display: create calendar element + // @param cal (obj) + + display: function(cal) { + // 1. header and navigation + this.calendar.empty(); // init div + + this.calendar.className = this.classes.calendar + ' ' + this.options.months[cal.month].toLowerCase(); + + var div = new Element('div').injectInside(this.calendar); // a wrapper div to help correct browser css problems with the caption element + + var table = new Element('table').injectInside(div).adopt(this.caption(cal)); + + // 2. day names + var thead = new Element('thead').injectInside(table); + + var tr = new Element('tr').injectInside(thead); + + for (var i = 0; i <= 6; i++) { + var th = this.options.days[(i + this.options.offset) % 7]; + + tr.adopt(new Element('th', { 'title': th }).appendText(th.substr(0, 1))); + } + + // 3. day numbers + var tbody = new Element('tbody').injectInside(table); + var tr = new Element('tr').injectInside(tbody); + + var d = new Date(cal.year, cal.month, 1); + var offset = ((d.getDay() - this.options.offset) + 7) % 7; // day of the week (offset) + var last = new Date(cal.year, cal.month + 1, 0).getDate(); // last day of this month + var prev = new Date(cal.year, cal.month, 0).getDate(); // last day of previous month + var active = this.value(cal); // active date (if set and within curr month) + var valid = cal.days; // valid days for curr month + var inactive = []; // active dates set by other calendars + var hilited = []; + this.calendars.each(function(kal, i) { + if (kal != cal && kal.val) { + if (cal.year == kal.val.getFullYear() && cal.month == kal.val.getMonth()) { inactive.push(kal.val.getDate()); } + + if (cal.val) { + for (var day = 1; day <= last; day++) { + d.setDate(day); + + if ((i < cal.id && d > kal.val && d < cal.val) || (i > cal.id && d > cal.val && d < kal.val)) { + if (!hilited.contains(day)) { hilited.push(day); } + } + } + } + } + }, this); + var d = new Date(); + var today = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime(); // today obv + + for (var i = 1; i < 43; i++) { // 1 to 42 (6 x 7 or 6 weeks) + if ((i - 1) % 7 == 0) { tr = new Element('tr').injectInside(tbody); } // each week is it's own table row + + var td = new Element('td').injectInside(tr); + + var day = i - offset; + var date = new Date(cal.year, cal.month, day); + + var cls = ''; + + if (day === active) { cls = this.classes.active; } // active + else if (inactive.contains(day)) { cls = this.classes.inactive; } // inactive + else if (valid.contains(day)) { cls = this.classes.valid; } // valid + else if (day >= 1 && day <= last) { cls = this.classes.invalid; } // invalid + + if (date.getTime() == today) { cls = cls + ' ' + this.classes.today; } // adds class for today + + if (hilited.contains(day)) { cls = cls + ' ' + this.classes.hilite; } // adds class if hilited + + td.addClass(cls); + + if (valid.contains(day)) { // if it's a valid - clickable - day we add interaction + td.setProperty('title', this.format(date, 'D M jS Y')); + + td.addEvents({ + 'click': function(td, day, cal) { + this.clicked(td, day, cal); + }.pass([td, day, cal], this), + 'mouseover': function(td, cls) { + td.addClass(cls); + }.pass([td, this.classes.hover]), + 'mouseout': function(td, cls) { + td.removeClass(cls); + }.pass([td, this.classes.hover]) + }); + } + + // pad calendar with last days of prev month and first days of next month + if (day < 1) { day = prev + day; } + else if (day > last) { day = day - last; } + + td.appendText(day); + } + }, + + + // element: helper function + // @param el (string) element id + // @param f (string) format string + // @param cal (obj) + + element: function(el, f, cal) { + if ($type(f) == 'object') { // in the case of multiple inputs per calendar + for (var i in f) { + if (!this.element(i, f[i], cal)) { return false; } + } + + return true; + } + + el = $(el); + + if (!el) { return false; } + + el.format = f; + + if (el.get('tag') == 'select') { // select elements allow the user to manually set the date via select option + el.addEvent('change', function(cal) { this.changed(cal); }.pass(cal, this)); + } + else { // input (type text) elements restrict the user to only setting the date via the calendar + el.readOnly = true; + el.addEvent('focus', function(cal) { this.toggle(cal); }.pass(cal, this)); + } + + cal.els.push(el); + + return true; + }, + + + // format: formats a date object according to passed in instructions + // @param date (obj) + // @param f (string) any combination of punctuation / separators and d, j, D, l, S, m, n, F, M, y, Y + // @returns string + + format: function(date, format) { + var str = ''; + + if (date) { + var j = date.getDate(); // 1 - 31 + var w = date.getDay(); // 0 - 6 + var l = this.options.days[w]; // Sunday - Saturday + var n = date.getMonth() + 1; // 1 - 12 + var f = this.options.months[n - 1]; // January - December + var y = date.getFullYear() + ''; // 19xx - 20xx + + for (var i = 0, len = format.length; i < len; i++) { + var cha = format.charAt(i); // format char + + switch(cha) { + // year cases + case 'y': // xx - xx + y = y.substr(2); + case 'Y': // 19xx - 20xx + str += y; + break; + + // month cases + case 'm': // 01 - 12 + if (n < 10) { n = '0' + n; } + case 'n': // 1 - 12 + str += n; + break; + + case 'M': // Jan - Dec + f = f.substr(0, 3); + case 'F': // January - December + str += f; + break; + + // day cases + case 'd': // 01 - 31 + if (j < 10) { j = '0' + j; } + case 'j': // 1 - 31 + str += j; + break; + + case 'D': // Sun - Sat + l = l.substr(0, 3); + case 'l': // Sunday - Saturday + str += l; + break; + + case 'N': // 1 - 7 + w += 1; + case 'w': // 0 - 6 + str += w; + break; + + case 'S': // st, nd, rd or th (works well with j) + if (j % 10 == 1 && j != '11') { str += 'st'; } + else if (j % 10 == 2 && j != '12') { str += 'nd'; } + else if (j % 10 == 3 && j != '13') { str += 'rd'; } + else { str += 'th'; } + break; + + default: + str += cha; + } + } + } + + return str; // return format with values replaced + }, + + + // navigate: calendar navigation + // @param cal (obj) + // @param type (str) m or y for month or year + // @param n (int) + or - for next or prev + + navigate: function(cal, type, n) { + switch (type) { + case 'm': // month + if ($type(cal.months) == 'array') { + var i = cal.months.indexOf(cal.month) + n; // index of current month + + if (i < 0 || i == cal.months.length) { // out of range + if (this.options.navigation == 1) { // if type 1 nav we'll need to increment the year + this.navigate(cal, 'y', n); + } + + i = (i < 0) ? cal.months.length - 1 : 0; + } + + cal.month = cal.months[i]; + } + else { + var i = cal.month + n; + + if (i < 0 || i == 12) { + if (this.options.navigation == 1) { + this.navigate(cal, 'y', n); + } + + i = (i < 0) ? 11 : 0; + } + + cal.month = i; + } + break; + + case 'y': // year + if ($type(cal.years) == 'array') { + var i = cal.years.indexOf(cal.year) + n; + + cal.year = cal.years[i]; + } + else { + cal.year += n; + } + break; + } + + $extend(cal, this.values(cal)); + + if ($type(cal.months) == 'array') { // if the calendar has a months select + var i = cal.months.indexOf(cal.month); // and make sure the curr months exists for the new year + + if (i < 0) { cal.month = cal.months[0]; } // otherwise we'll reset the month + } + + + this.display(cal); + }, + + + // read: compiles cal value based on array of inputs passed in + // @param cal (obj) + // @returns date (obj) or (null) + + read: function(cal) { + var arr = [null, null, null]; + + cal.els.each(function(el) { + // returns an array which may contain empty values + var values = this.unformat(el.value, el.format); + + values.each(function(val, i) { + if ($type(val) == 'number') { arr[i] = val; } + }); + }, this); + + // we can update the cals month and year values + if ($type(arr[0]) == 'number') { cal.year = arr[0]; } + if ($type(arr[1]) == 'number') { cal.month = arr[1]; } + + var val = null; + + if (arr.every(function(i) { return $type(i) == 'number'; })) { // if valid date + var last = new Date(arr[0], arr[1] + 1, 0).getDate(); // last day of month + + if (arr[2] > last) { arr[2] = last; } // make sure we stay within the month (ex in case default day of select is 31 and month is feb) + + val = new Date(arr[0], arr[1], arr[2]); + } + + return (cal.val == val) ? null : val; // if new date matches old return null (same date clicked twice = disable) + }, + + + // rebuild: rebuilds days + months selects + // @param cal (obj) + + rebuild: function(cal) { + cal.els.each(function(el) { + /* + if (el.get('tag') == 'select' && el.format.test('^(F|m|M|n)$')) { // special case for months-only select + if (!cal.options) { cal.options = el.clone(); } // clone a copy of months select + + var val = (cal.val) ? cal.val.getMonth() : el.value.toInt(); + + el.empty(); // initialize select + + cal.months.each(function(month) { + // create an option element + var option = new Element('option', { + 'selected': (val == month), + 'value': this.format(new Date(1, month, 1), el.format); + }).appendText(day).injectInside(el); + }, this); + } + */ + + if (el.get('tag') == 'select' && el.format.test('^(d|j)$')) { // special case for days-only select + var d = this.value(cal); + + if (!d) { d = el.value.toInt(); } // if the calendar doesn't have a set value, try to use value from select + + el.empty(); // initialize select + + cal.days.each(function(day) { + // create an option element + var option = new Element('option', { + 'selected': (d == day), + 'value': ((el.format == 'd' && day < 10) ? '0' + day : day) + }).appendText(day).injectInside(el); + }, this); + } + }, this); + }, + + + // sort: helper function for numerical sorting + + sort: function(a, b) { + return a - b; + }, + + + // toggle: show / hide calendar + // @param cal (obj) + + toggle: function(cal) { + document.removeEvent('mousedown', this.fn); // always remove the current mousedown script first + + if (cal.visible) { // simply hide curr cal + cal.visible = false; + cal.button.removeClass(this.classes.active); // active + + this.fx.start('opacity', 1, 0); + } + else { // otherwise show (may have to hide others) + // hide cal on out-of-bounds click + this.fn = function(e, cal) { + var e = new Event(e); + + var el = e.target; + + var stop = false; + + while (el != document.body && el.nodeType == 1) { + if (el == this.calendar) { stop = true; } + this.calendars.each(function(kal) { + if (kal.button == el || kal.els.contains(el)) { stop = true; } + }); + + if (stop) { + e.stop(); + return false; + } + else { el = el.parentNode; } + } + + this.toggle(cal); + }.create({ 'arguments': cal, 'bind': this, 'event': true }); + + document.addEvent('mousedown', this.fn); + + this.calendars.each(function(kal) { + if (kal == cal) { + kal.visible = true; + kal.button.addClass(this.classes.active); // css c-icon-active + } + else { + kal.visible = false; + kal.button.removeClass(this.classes.active); // css c-icon-active + } + }, this); + + var size = window.getScrollSize(); + + var coord = cal.button.getCoordinates(); + + var x = coord.right + this.options.tweak.x; + var y = coord.top + this.options.tweak.y; + + // make sure the calendar doesn't open off screen + if (!this.calendar.coord) { this.calendar.coord = this.calendar.getCoordinates(); } + + if (x + this.calendar.coord.width > size.x) { x -= (x + this.calendar.coord.width - size.x); } + if (y + this.calendar.coord.height > size.y) { y -= (y + this.calendar.coord.height - size.y); } + + this.calendar.setStyles({ left: x + 'px', top: y + 'px' }); + + if (window.ie6) { + this.iframe.setStyles({ height: this.calendar.coord.height + 'px', left: x + 'px', top: y + 'px', width: this.calendar.coord.width + 'px' }); + } + + this.display(cal); + + this.fx.start('opacity', 0, 1); + } + }, + + + // unformat: takes a value from an input and parses the d, m and y elements + // @param val (string) + // @param f (string) any combination of punctuation / separators and d, j, D, l, S, m, n, F, M, y, Y + // @returns array + + unformat: function(val, f) { + f = f.escapeRegExp(); + + var re = { + d: '([0-9]{2})', + j: '([0-9]{1,2})', + D: '(' + this.options.days.map(function(day) { return day.substr(0, 3); }).join('|') + ')', + l: '(' + this.options.days.join('|') + ')', + S: '(st|nd|rd|th)', + F: '(' + this.options.months.join('|') + ')', + m: '([0-9]{2})', + M: '(' + this.options.months.map(function(month) { return month.substr(0, 3); }).join('|') + ')', + n: '([0-9]{1,2})', + Y: '([0-9]{4})', + y: '([0-9]{2})' + } + + var arr = []; // array of indexes + + var g = ''; + + // convert our format string to regexp + for (var i = 0; i < f.length; i++) { + var c = f.charAt(i); + + if (re[c]) { + arr.push(c); + + g += re[c]; + } + else { + g += c; + } + } + + // match against date + var matches = val.match('^' + g + '$'); + + var dates = new Array(3); + + if (matches) { + matches = matches.slice(1); // remove first match which is the date + + arr.each(function(c, i) { + i = matches[i]; + + switch(c) { + // year cases + case 'y': + i = '19' + i; // 2 digit year assumes 19th century (same as JS) + case 'Y': + dates[0] = i.toInt(); + break; + + // month cases + case 'F': + i = i.substr(0, 3); + case 'M': + i = this.options.months.map(function(month) { return month.substr(0, 3); }).indexOf(i) + 1; + case 'm': + case 'n': + dates[1] = i.toInt() - 1; + break; + + // day cases + case 'd': + case 'j': + dates[2] = i.toInt(); + break; + } + }, this); + } + + return dates; + }, + + + // value: returns day value of calendar if set + // @param cal (obj) + // @returns day (int) or null + + value: function(cal) { + var day = null; + + if (cal.val) { + if (cal.year == cal.val.getFullYear() && cal.month == cal.val.getMonth()) { day = cal.val.getDate(); } + } + + return day; + }, + + + // values: returns the years, months (for curr year) and days (for curr month and year) for the calendar + // @param cal (obj) + // @returns obj + + values: function(cal) { + var years, months, days; + + cal.els.each(function(el) { + if (el.get('tag') == 'select') { + if (el.format.test('(y|Y)')) { // search for a year select + years = []; + + el.getChildren().each(function(option) { // get options + var values = this.unformat(option.value, el.format); + + if (!years.contains(values[0])) { years.push(values[0]); } // add to years array + }, this); + + years.sort(this.sort); + } + + if (el.format.test('(F|m|M|n)')) { // search for a month select + months = []; // 0 - 11 should be + + el.getChildren().each(function(option) { // get options + var values = this.unformat(option.value, el.format); + + if ($type(values[0]) != 'number' || values[0] == cal.year) { // if it's a year / month combo for curr year, or simply a month select + if (!months.contains(values[1])) { months.push(values[1]); } // add to months array + } + }, this); + + months.sort(this.sort); + } + + if (el.format.test('(d|j)') && !el.format.test('^(d|j)$')) { // search for a day select, but NOT a days only select + days = []; // 1 - 31 + + el.getChildren().each(function(option) { // get options + var values = this.unformat(option.value, el.format); + + // in the special case of days we dont want the value if its a days only select + // otherwise that will screw up the options rebuilding + // we will take the values if they are exact dates though + if (values[0] == cal.year && values[1] == cal.month) { + if (!days.contains(values[2])) { days.push(values[2]); } // add to days array + } + }, this); + } + } + }, this); + + // we start with what would be the first and last days were there no restrictions + var first = 1; + var last = new Date(cal.year, cal.month + 1, 0).getDate(); // last day of the month + + // if we're in an out of bounds year + if (cal.year == cal.start.getFullYear()) { + // in the special case of improved navigation but no months array, we'll need to construct one + if (months == null && this.options.navigation == 2) { + months = []; + + for (var i = 0; i < 12; i ++) { + if (i >= cal.start.getMonth()) { months.push(i); } + } + } + + // if we're in an out of bounds month + if (cal.month == cal.start.getMonth()) { + first = cal.start.getDate(); // first day equals day of bound + } + } + if (cal.year == cal.end.getFullYear()) { + // in the special case of improved navigation but no months array, we'll need to construct one + if (months == null && this.options.navigation == 2) { + months = []; + + for (var i = 0; i < 12; i ++) { + if (i <= cal.end.getMonth()) { months.push(i); } + } + } + + if (cal.month == cal.end.getMonth()) { + last = cal.end.getDate(); // last day equals day of bound + } + } + + // let's get our invalid days + var blocked = this.blocked(cal); + + // finally we can prepare all the valid days in a neat little array + if ($type(days) == 'array') { // somewhere there was a days select + days = days.filter(function(day) { + if (day >= first && day <= last && !blocked.contains(day)) { return day; } + }); + } + else { // no days select we'll need to construct a valid days array + days = []; + + for (var i = first; i <= last; i++) { + if (!blocked.contains(i)) { days.push(i); } + } + } + + days.sort(this.sort); // sorting our days will give us first and last of month + + return { 'days': days, 'months': months, 'years': years }; + }, + + + // write: sets calendars value to form elements + // @param cal (obj) + + write: function(cal) { + this.rebuild(cal); // in the case of options, we'll need to make sure we have the correct number of days available + + cal.els.each(function(el) { // then we can set the value to the field + el.value = this.format(cal.val, el.format); + }, this); + } +}); + +Calendar.implement(new Events, new Options); \ No newline at end of file diff --git a/static/js/form.js b/static/js/form.js index 9a6fee9f9..36d391f24 100644 --- a/static/js/form.js +++ b/static/js/form.js @@ -1,11 +1,14 @@ window.addEvent("domready", function() { - sqbsubmit = function(obj, dest, id, callback) { + sqbsubmit = function(obj, dest, id, callback, onerror) { obj.set('send', { onSuccess: function(responseText) { var decode = JSON.decode(responseText); if (decode.status == "ko") { $(dest).set("html", decode.content); $(id).addEvent("submit", callback); + if ($defined(onerror)) { + onerror(); + } } else { if (decode.url != "") parent.location.href = decode.url; @@ -16,9 +19,9 @@ window.addEvent("domready", function() { obj.send(); } - generic_submit = function(obj, event, id, callback) { + generic_submit = function(obj, event, id, callback, onerror) { event.stop(); - sqbsubmit(obj, "content", id, callback); + sqbsubmit(obj, "content", id, callback, onerror); } domain_submit = function(event) { generic_submit(this, event, "domform", domain_submit); @@ -35,8 +38,14 @@ window.addEvent("domready", function() { chpassword_submit = function(event) { generic_submit(this, event, "chpwdform", chpassword_submit); } + + arm_onerror = function() { + new Calendar({id_untildate: "Y-m-d"}, { + tweak: {x: -100, y:-100} + }); + } arm_submit = function(event) { - generic_submit(this, event, "armform", arm_submit); + generic_submit(this, event, "armform", arm_submit, arm_onerror); } if ($("domform")) diff --git a/static/js/preferences.js b/static/js/preferences.js index 6f948c92a..c82a1a837 100644 --- a/static/js/preferences.js +++ b/static/js/preferences.js @@ -11,8 +11,4 @@ window.addEvent('domready', function() { } }, $('accordion')); - new DatePicker("#untildate", { - pickerClass: "datepicker_vista", - allowEmpty: true - }); });