From 49363cfe364ccf5add432f6375895ee283149dab Mon Sep 17 00:00:00 2001 From: Francis Lachapelle Date: Fri, 11 Apr 2014 21:37:55 -0400 Subject: [PATCH] Update datepicker to latest version From https://github.com/eternicode/bootstrap-datepicker/ --- NEWS | 1 + UI/WebServerResources/UIxAppointmentEditor.js | 15 +- UI/WebServerResources/UIxContactEditor.js | 145 +- UI/WebServerResources/UIxTaskEditor.js | 367 ++-- UI/WebServerResources/datepicker.css | 455 +++-- UI/WebServerResources/datepicker.js | 1646 +++++++++++++---- 6 files changed, 1776 insertions(+), 853 deletions(-) diff --git a/NEWS b/NEWS index 1e64cf736..629313fcd 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,7 @@ Enhancements - SOGo version is now displayed in preferences window (#2612) - added the SOGoMaximumSyncWindowSize system default to overwrite the maximum number of items returned during an ActiveSync sync operation +- updated datepicker Bug fixes - fixed saved HTML content of draft when attaching a file diff --git a/UI/WebServerResources/UIxAppointmentEditor.js b/UI/WebServerResources/UIxAppointmentEditor.js index 98af88261..24c2c92b1 100644 --- a/UI/WebServerResources/UIxAppointmentEditor.js +++ b/UI/WebServerResources/UIxAppointmentEditor.js @@ -293,8 +293,11 @@ function setEndDate(newEndDate) { function onAdjustTime(event) { var endDate = window.getEndDate(); var startDate = window.getStartDate(); - - if ($(this).readAttribute("id").startsWith("start")) { + var input = $(this); + if (input.tagName != 'INPUT') + input = input.down('input'); + + if (input.id.startsWith("start")) { // Start date was changed if (startDate == null) { var oldStartDate = window.getShadowStartDate(); @@ -350,15 +353,15 @@ function initTimeWidgets(widgets) { this.timeWidgets = widgets; if (widgets['start']['date']) { - jQuery(widgets['start']['date']).closest('.date').datepicker({autoclose: true, weekStart: firstDayOfWeek}); - jQuery(widgets['start']['date']).change(onAdjustTime); + jQuery(widgets['start']['date']).closest('.date').datepicker({autoclose: true, weekStart: firstDayOfWeek}) + .on('changeDate', onAdjustTime); widgets['start']['time'].on("time:change", onAdjustTime); widgets['start']['time'].addInterface(SOGoTimePickerInterface); } if (widgets['end']['date']) { - jQuery(widgets['end']['date']).closest('.date').datepicker({autoclose: true, weekStart: firstDayOfWeek}); - jQuery(widgets['end']['date']).change(onAdjustTime); + jQuery(widgets['end']['date']).closest('.date').datepicker({autoclose: true, weekStart: firstDayOfWeek}) + .on('changeDate', onAdjustTime); widgets['end']['time'].on("time:change", onAdjustTime); widgets['end']['time'].addInterface(SOGoTimePickerInterface); } diff --git a/UI/WebServerResources/UIxContactEditor.js b/UI/WebServerResources/UIxContactEditor.js index 2ea317035..0488cb154 100644 --- a/UI/WebServerResources/UIxContactEditor.js +++ b/UI/WebServerResources/UIxContactEditor.js @@ -1,25 +1,25 @@ /* -*- Mode: js2-mode; tab-width: 4; c-label-minimum-indentation: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* - Copyright (C) 2005 SKYRIX Software AG - Copyright (C) 2006-2011 Inverse + Copyright (C) 2005 SKYRIX Software AG + Copyright (C) 2006-2011 Inverse - This file is part of OpenGroupware.org. + This file is part of OpenGroupware.org. - OGo is 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 2, or (at your option) any - later version. + OGo is 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 2, or (at your option) any + later version. - OGo is 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. + OGo is 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. - You should have received a copy of the GNU Lesser General Public - License along with OGo; see the file COPYING. If not, write to the - Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. + You should have received a copy of the GNU Lesser General Public + License along with OGo; see the file COPYING. If not, write to the + Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. */ var dateRegex = /^(([0-9]{2})?[0-9])?[0-9]-[0-9]?[0-9]-[0-9]?[0-9]$/; @@ -78,38 +78,35 @@ function copyContact(type, email, uid, sn, displayname, }; function validateContactEditor() { - var rc = true; + var rc = true; - var e = $('mail'); - if (e.value.length > 0 - && !emailRE.test(e.value)) { - alert(_("invalidemailwarn")); - rc = false; - } + var e = $('mail'); + if (e.value.length > 0 + && !emailRE.test(e.value)) { + alert(_("invalidemailwarn")); + rc = false; + } - e = $('mozillasecondemail'); - if (e.value.length > 0 - && !emailRE.test(e.value)) { - alert(_("invalidemailwarn")); - rc = false; - } - return rc + e = $('mozillasecondemail'); + if (e.value.length > 0 + && !emailRE.test(e.value)) { + alert(_("invalidemailwarn")); + rc = false; + } + return rc } -this.initTimeWidgets = function (widgets) { - this.timeWidgets = widgets; - var firstDay = new Date(); - firstDay.setFullYear(1900,0,1); - var lastDay = new Date(); - - jQuery(widgets['birthday']['date']).closest('.date').datepicker({autoclose: true, - weekStart: 0, - endDate: lastDay, - startDate: firstDay, - setStartDate: lastDay, - startView: 2, - position: "below-shifted-left"}); -}; +function initTimeWidget(input) { + var firstDay = new Date(); + firstDay.setFullYear(1900,0,1); + var lastDay = new Date(); + + jQuery(input).closest('.date').datepicker({autoclose: true, + endDate: lastDay, + startDate: firstDay, + setStartDate: lastDay, + startView: 2}) +} function onDisplaynameKeyDown() { var fn = $("displayname"); @@ -269,43 +266,41 @@ function onEmptyCategoryClick(event) { } function initEditorForm() { - var tabsContainer = $("editorTabs"); - var controller = new SOGoTabsController(); - controller.attachToTabsContainer(tabsContainer); + var tabsContainer = $("editorTabs"); + var controller = new SOGoTabsController(); + controller.attachToTabsContainer(tabsContainer); - displaynameChanged = ($("displayname").value.length > 0); - $("displayname").onkeydown = onDisplaynameKeyDown; - $("sn").onkeyup = onDisplaynameNewValue; - $("givenname").onkeyup = onDisplaynameNewValue; + displaynameChanged = ($("displayname").value.length > 0); + $("displayname").onkeydown = onDisplaynameKeyDown; + $("sn").onkeyup = onDisplaynameNewValue; + $("givenname").onkeyup = onDisplaynameNewValue; - $("cancelButton").observe("click", onEditorCancelClick); - var submitButton = $("submitButton"); - if (submitButton) { - submitButton.observe("click", onEditorSubmitClick); - } - - Event.observe(document, "keydown", onDocumentKeydown); - - if (typeof(gCategories) != "undefined") { - regenerateCategoriesMenu(); - } - var catsInput = $("jsonContactCategories"); - if (catsInput && catsInput.value.length > 0) { - var contactCats = $(catsInput.value.evalJSON(false)); - for (var i = 0; i < contactCats.length; i++) { - appendCategoryInput(contactCats[i]); + $("cancelButton").observe("click", onEditorCancelClick); + var submitButton = $("submitButton"); + if (submitButton) { + submitButton.observe("click", onEditorSubmitClick); } - } - var emptyCategory = $("emptyCategory"); - if (emptyCategory) { - emptyCategory.tabIndex = 10000; - emptyCategory.observe("click", onEmptyCategoryClick); - } - - var widgets = {'birthday': {'date': $("birthdayDate")}}; - initTimeWidgets(widgets); + Event.observe(document, "keydown", onDocumentKeydown); + if (typeof(gCategories) != "undefined") { + regenerateCategoriesMenu(); + } + var catsInput = $("jsonContactCategories"); + if (catsInput && catsInput.value.length > 0) { + var contactCats = $(catsInput.value.evalJSON(false)); + for (var i = 0; i < contactCats.length; i++) { + appendCategoryInput(contactCats[i]); + } + } + + var emptyCategory = $("emptyCategory"); + if (emptyCategory) { + emptyCategory.tabIndex = 10000; + emptyCategory.observe("click", onEmptyCategoryClick); + } + + initTimeWidget($("birthdayDate")); } document.observe("dom:loaded", initEditorForm); diff --git a/UI/WebServerResources/UIxTaskEditor.js b/UI/WebServerResources/UIxTaskEditor.js index be1be3eb9..38e0d0d30 100644 --- a/UI/WebServerResources/UIxTaskEditor.js +++ b/UI/WebServerResources/UIxTaskEditor.js @@ -3,169 +3,101 @@ var contactSelectorAction = 'calendars-contacts'; function uixEarlierDate(date1, date2) { - // can this be done in a sane way? - if (date1 && date2) { - if (date1.getYear() < date2.getYear()) return date1; - if (date1.getYear() > date2.getYear()) return date2; - // same year - if (date1.getMonth() < date2.getMonth()) return date1; - if (date1.getMonth() > date2.getMonth()) return date2; - // same month - if (date1.getDate() < date2.getDate()) return date1; - if (date1.getDate() > date2.getDate()) return date2; - } - // same day - return null; + // can this be done in a sane way? + if (date1 && date2) { + if (date1.getYear() < date2.getYear()) return date1; + if (date1.getYear() > date2.getYear()) return date2; + // same year + if (date1.getMonth() < date2.getMonth()) return date1; + if (date1.getMonth() > date2.getMonth()) return date2; + // same month + if (date1.getDate() < date2.getDate()) return date1; + if (date1.getDate() > date2.getDate()) return date2; + } + // same day + return null; } function validateDate(which, label) { - var result, dateValue; + var result, dateValue; - dateValue = this._getDate(which); - if (dateValue == null) { - alert(label); - result = false; - } else - result = dateValue; + dateValue = this._getDate(which); + if (dateValue == null) { + alert(label); + result = false; + } else + result = dateValue; - return result; + return result; } function validateTaskEditor() { - var e, startdate, enddate, tmpdate; + var e, startdate, enddate, tmpdate; - e = document.getElementById('summary'); - if (e.value.length == 0 - && !confirm(labels.validate_notitle)) - return false; + e = document.getElementById('summary'); + if (e.value.length == 0 + && !confirm(labels.validate_notitle)) + return false; - e = document.getElementById('startTime_date'); - if (!e.disabled) { - startdate = validateDate('start', labels.validate_invalid_startdate); - if (!startdate) - return false; - } - - e = document.getElementById('dueTime_date'); - if (!e.disabled) { - enddate = validateDate('due', labels.validate_invalid_enddate); - if (!enddate) - return false; - } - - if (startdate && enddate) { - tmpdate = uixEarlierDate(startdate, enddate); - if (tmpdate == enddate) { - // window.alert(cuicui); - alert(labels.validate_endbeforestart); - return false; + e = document.getElementById('startTime_date'); + if (!e.disabled) { + startdate = validateDate('start', labels.validate_invalid_startdate); + if (!startdate) + return false; } - else if (tmpdate == null /* means: same date */) { - // TODO: check time - var startHour, startMinute, endHour, endMinute; - var matches; + e = document.getElementById('dueTime_date'); + if (!e.disabled) { + enddate = validateDate('due', labels.validate_invalid_enddate); + if (!enddate) + return false; + } + + if (startdate && enddate) { + tmpdate = uixEarlierDate(startdate, enddate); + if (tmpdate == enddate) { + alert(labels.validate_endbeforestart); + return false; + } + else if (tmpdate == null /* means: same date */) { + // TODO: check time + + var startHour, startMinute, endHour, endMinute; + var matches; - matches = document.forms[0]['startTime_time'].value.match(/([0-9]+):([0-9]+)/); - if (matches) { - startHour = parseInt(matches[1]); - startMinute = parseInt(matches[2]); - matches = document.forms[0]['dueTime_time'].value.match(/([0-9]+):([0-9]+)/); + matches = document.forms[0]['startTime_time'].value.match(/([0-9]+):([0-9]+)/); if (matches) { - endHour = parseInt(matches[1]); - endMinute = parseInt(matches[2]); + startHour = parseInt(matches[1]); + startMinute = parseInt(matches[2]); + matches = document.forms[0]['dueTime_time'].value.match(/([0-9]+):([0-9]+)/); + if (matches) { + endHour = parseInt(matches[1]); + endMinute = parseInt(matches[2]); - if (startHour > endHour) { - alert(labels.validate_endbeforestart); - return false; - } - else if (startHour == endHour) { - if (startMinute > endMinute) { + if (startHour > endHour) { alert(labels.validate_endbeforestart); return false; } + else if (startHour == endHour) { + if (startMinute > endMinute) { + alert(labels.validate_endbeforestart); + return false; + } + } + } + else { + alert(labels.validate_invalid_enddate); + return false; } } else { - alert(labels.validate_invalid_enddate); + alert(labels.validate_invalid_startdate); return false; } } - else { - alert(labels.validate_invalid_startdate); - return false; - } - } - } - - return true; -} - -function toggleDetails() { - var div = $("details"); - var buttons = $("buttons"); - var buttonsHeight = buttons.clientHeight * 3; - - if (div.style.visibility) { - div.style.visibility = null; - window.resizeBy(0, -(div.clientHeight + buttonsHeight)); - $("detailsButton").innerHTML = _("Show Details"); - } else { - div.style.visibility = 'visible;'; - window.resizeBy(0, (div.clientHeight + buttonsHeight)); - $("detailsButton").innerHTML = _("Hide Details"); - } - - return false; -} - -function toggleCycleVisibility(node, nodeName, hiddenValue) { - var spanNode = $(nodeName); - var newVisibility = ((node.value == hiddenValue) ? null : 'visible;'); - spanNode.style.visibility = newVisibility; - - if (nodeName == 'cycleSelectionFirstLevel') { - var otherSpanNode = $('cycleSelectionSecondLevel'); - if (!newVisibility) - { - otherSpanNode.superVisibility = otherSpanNode.style.visibility; - otherSpanNode.style.visibility = null; - } - else - { - otherSpanNode.style.visibility = otherSpanNode.superVisibility; - otherSpanNode.superVisibility = null; - } - } -} - -function addContact(tag, fullContactName, contactId, contactName, contactEmail) { - var uids = $('uixselector-participants-uidList'); - log ("contactId: " + contactId); - if (contactId) - { - var re = new RegExp("(^|,)" + contactId + "($|,)"); - - log ("uids: " + uids); - if (!re.test(uids.value)) - { - log ("no match... realling adding"); - if (uids.value.length > 0) - uids.value += ',' + contactId; - else - uids.value = contactId; - - var names = $('uixselector-participants-display'); - names.innerHTML += ('
  • ' - + contactName + '
  • '); - } - else - log ("match... ignoring contact"); } - return false; + return true; } function onTimeControlCheck(checkBox) { @@ -184,140 +116,141 @@ function onTimeControlCheck(checkBox) { } function saveEvent(sender) { - if (validateTaskEditor()) - document.forms['editform'].submit(); + if (validateTaskEditor()) + document.forms['editform'].submit(); - return false; + return false; } function startDayAsShortString() { - return dayAsShortDateString($('startTime_date')); + return dayAsShortDateString($('startTime_date')); } function dueDayAsShortString() { - return dayAsShortDateString($('dueTime_date')); + return dayAsShortDateString($('dueTime_date')); } this._getDate = function(which) { - var date = window.timeWidgets[which]['date'].inputAsDate(); - var time = window.timeWidgets[which]['time'].value.split(":"); - date.setHours(time[0]); - date.setMinutes(time[1]); + var date = window.timeWidgets[which]['date'].inputAsDate(); + var time = window.timeWidgets[which]['time'].value.split(":"); + date.setHours(time[0]); + date.setMinutes(time[1]); - if (isNaN(date.getTime())) - return null; + if (isNaN(date.getTime())) + return null; - return date; + return date; }; this._getShadowDate = function(which) { - var date = window.timeWidgets[which]['date'].getAttribute("shadow-value").asDate(); - var time = window.timeWidgets[which]['time'].getAttribute("shadow-value").split(":"); - date.setHours(time[0]); - date.setMinutes(time[1]); + var date = window.timeWidgets[which]['date'].getAttribute("shadow-value").asDate(); + var time = window.timeWidgets[which]['time'].getAttribute("shadow-value").split(":"); + date.setHours(time[0]); + date.setMinutes(time[1]); - return date; + return date; }; this.getStartDate = function() { - return this._getDate('start'); + return this._getDate('start'); }; this.getDueDate = function() { - return this._getDate('due'); + return this._getDate('due'); }; this.getShadowStartDate = function() { - return this._getShadowDate('start'); + return this._getShadowDate('start'); }; this.getShadowDueDate = function() { - return this._getShadowDate('due'); + return this._getShadowDate('due'); }; this._setDate = function(which, newDate) { - window.timeWidgets[which]['date'].setInputAsDate(newDate); - window.timeWidgets[which]['time'].value = newDate.getDisplayHoursString(); + window.timeWidgets[which]['date'].setInputAsDate(newDate); + window.timeWidgets[which]['time'].value = newDate.getDisplayHoursString(); - // Update date picker - var dateComponent = jQuery(window.timeWidgets[which]['date']).closest('.date'); - dateComponent.data('date', window.timeWidgets[which]['date'].value); - dateComponent.datepicker('update'); + // Update date picker + var dateComponent = jQuery(window.timeWidgets[which]['date']).closest('.date'); + dateComponent.data('date', window.timeWidgets[which]['date'].value); + dateComponent.datepicker('update'); }; this.setStartDate = function(newStartDate) { - this._setDate('start', newStartDate); + this._setDate('start', newStartDate); }; this.setDueDate = function(newDueDate) { - this._setDate('due', newDueDate); + this._setDate('due', newDueDate); }; this.onAdjustTime = function(event) { - onAdjustDueTime(event); + onAdjustDueTime(event); }; this.onAdjustDueTime = function(event) { - if (!window.timeWidgets['due']['date'].disabled) { - var dateDelta = (window.getStartDate().valueOf() - - window.getShadowStartDate().valueOf()); - var newDueDate = new Date(window.getDueDate().valueOf() + dateDelta); - window.setDueDate(newDueDate); - } - window.timeWidgets['start']['date'].updateShadowValue(); - window.timeWidgets['start']['time'].updateShadowValue(); + if (!window.timeWidgets['due']['date'].disabled) { + var dateDelta = (window.getStartDate().valueOf() + - window.getShadowStartDate().valueOf()); + var newDueDate = new Date(window.getDueDate().valueOf() + dateDelta); + window.setDueDate(newDueDate); + } + window.timeWidgets['start']['date'].updateShadowValue(); + window.timeWidgets['start']['time'].updateShadowValue(); }; this.initTimeWidgets = function (widgets) { - this.timeWidgets = widgets; - - jQuery(widgets['start']['date']).closest('.date').datepicker({autoclose: true, weekStart: firstDayOfWeek}); - jQuery(widgets['due']['date']).closest('.date').datepicker({autoclose: true, weekStart: firstDayOfWeek}); - jQuery('#statusTime_date').closest('.date').datepicker({autoclose: true, weekStart: firstDayOfWeek}); + this.timeWidgets = widgets; - jQuery(widgets['start']['date']).change(onAdjustTime); - widgets['start']['time'].on("time:change", onAdjustDueTime); - widgets['start']['time'].addInterface(SOGoTimePickerInterface); - widgets['due']['time'].addInterface(SOGoTimePickerInterface); + jQuery(widgets['start']['date']).closest('.date').datepicker({autoclose: true, weekStart: firstDayOfWeek}) + .on('changeDate', onAdjustTime); + widgets['start']['time'].on("time:change", onAdjustDueTime); + widgets['start']['time'].addInterface(SOGoTimePickerInterface); + + jQuery(widgets['due']['date']).closest('.date').datepicker({autoclose: true, weekStart: firstDayOfWeek}); + widgets['due']['time'].addInterface(SOGoTimePickerInterface); + + jQuery('#statusTime_date').closest('.date').datepicker({autoclose: true, weekStart: firstDayOfWeek}); }; - + function onStatusListChange(event) { - var value = $("statusList").value; - var statusTimeDate = $("statusTime_date"); - var statusPercent = $("statusPercent"); + var value = $("statusList").value; + var statusTimeDate = $("statusTime_date"); + var statusPercent = $("statusPercent"); - if (value == "WONoSelectionString") { - statusTimeDate.disabled = true; - statusPercent.disabled = true; - statusPercent.value = ""; - } - else if (value == "0") { - statusTimeDate.disabled = true; - statusPercent.disabled = false; - } - else if (value == "1") { - statusTimeDate.disabled = true; - statusPercent.disabled = false; - } - else if (value == "2") { - statusTimeDate.disabled = false; - statusPercent.disabled = false; - statusPercent.value = "100"; - } - else if (value == "3") { - statusTimeDate.disabled = true; - statusPercent.disabled = true; - } - else { - statusTimeDate.disabled = true; - } + if (value == "WONoSelectionString") { + statusTimeDate.disabled = true; + statusPercent.disabled = true; + statusPercent.value = ""; + } + else if (value == "0") { + statusTimeDate.disabled = true; + statusPercent.disabled = false; + } + else if (value == "1") { + statusTimeDate.disabled = true; + statusPercent.disabled = false; + } + else if (value == "2") { + statusTimeDate.disabled = false; + statusPercent.disabled = false; + statusPercent.value = "100"; + } + else if (value == "3") { + statusTimeDate.disabled = true; + statusPercent.disabled = true; + } + else { + statusTimeDate.disabled = true; + } } function initializeStatusLine() { - var statusList = $("statusList"); - if (statusList) { - statusList.observe("change", onStatusListChange); - } + var statusList = $("statusList"); + if (statusList) { + statusList.observe("change", onStatusListChange); + } } function onTaskEditorLoad() { diff --git a/UI/WebServerResources/datepicker.css b/UI/WebServerResources/datepicker.css index 16d10d4be..6f061df8b 100644 --- a/UI/WebServerResources/datepicker.css +++ b/UI/WebServerResources/datepicker.css @@ -8,39 +8,75 @@ * */ .datepicker { - top: 0; - left: 0; padding: 4px; - margin-top: 1px; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; + direction: ltr; /*.dow { border-top: 1px solid #ddd !important; }*/ - } -.datepicker:before { +.datepicker-inline { + width: 220px; +} +.datepicker.datepicker-rtl { + direction: rtl; +} +.datepicker.datepicker-rtl table tr td span { + float: right; +} +.datepicker-dropdown { + top: 0; + left: 0; +} +.datepicker-dropdown:before { content: ''; display: inline-block; border-left: 7px solid transparent; border-right: 7px solid transparent; border-bottom: 7px solid #ccc; + border-top: 0; border-bottom-color: rgba(0, 0, 0, 0.2); position: absolute; - top: -7px; - left: 6px; } -.datepicker:after { +.datepicker-dropdown:after { content: ''; display: inline-block; border-left: 6px solid transparent; border-right: 6px solid transparent; border-bottom: 6px solid #ffffff; + border-top: 0; position: absolute; - top: -6px; +} +.datepicker-dropdown.datepicker-orient-left:before { + left: 6px; +} +.datepicker-dropdown.datepicker-orient-left:after { left: 7px; } +.datepicker-dropdown.datepicker-orient-right:before { + right: 6px; +} +.datepicker-dropdown.datepicker-orient-right:after { + right: 7px; +} +.datepicker-dropdown.datepicker-orient-top:before { + top: -7px; +} +.datepicker-dropdown.datepicker-orient-top:after { + top: -6px; +} +.datepicker-dropdown.datepicker-orient-bottom:before { + bottom: -7px; + border-bottom: 0; + border-top: 7px solid #999; +} +.datepicker-dropdown.datepicker-orient-bottom:after { + bottom: -6px; + border-bottom: 0; + border-top: 6px solid #ffffff; +} .datepicker > div { display: none; } @@ -55,6 +91,12 @@ } .datepicker table { margin: 0; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } .datepicker td, .datepicker th { @@ -64,25 +106,31 @@ -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; + border: none; } -.datepicker td.day:hover { +.table-striped .datepicker table tr td, +.table-striped .datepicker table tr th { + background-color: transparent; +} +.datepicker table tr td.day:hover, +.datepicker table tr td.day.focused { background: #eeeeee; cursor: pointer; } -.datepicker td.old, -.datepicker td.new { +.datepicker table tr td.old, +.datepicker table tr td.new { color: #999999; } -.datepicker td.disabled, -.datepicker td.disabled:hover { +.datepicker table tr td.disabled, +.datepicker table tr td.disabled:hover { background: none; color: #999999; cursor: default; } -.datepicker td.today, -.datepicker td.today:hover, -.datepicker td.today.disabled, -.datepicker td.today.disabled:hover { +.datepicker table tr td.today, +.datepicker table tr td.today:hover, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today.disabled:hover { background-color: #fde19a; background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a); background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a); @@ -94,53 +142,112 @@ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0); border-color: #fdf59a #fdf59a #fbed50; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:dximagetransform.microsoft.gradient(enabled=false); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + color: #000; } -.datepicker td.today:hover, -.datepicker td.today:hover:hover, -.datepicker td.today.disabled:hover, -.datepicker td.today.disabled:hover:hover, -.datepicker td.today:active, -.datepicker td.today:hover:active, -.datepicker td.today.disabled:active, -.datepicker td.today.disabled:hover:active, -.datepicker td.today.active, -.datepicker td.today:hover.active, -.datepicker td.today.disabled.active, -.datepicker td.today.disabled:hover.active, -.datepicker td.today.disabled, -.datepicker td.today:hover.disabled, -.datepicker td.today.disabled.disabled, -.datepicker td.today.disabled:hover.disabled, -.datepicker td.today[disabled], -.datepicker td.today:hover[disabled], -.datepicker td.today.disabled[disabled], -.datepicker td.today.disabled:hover[disabled] { +.datepicker table tr td.today:hover, +.datepicker table tr td.today:hover:hover, +.datepicker table tr td.today.disabled:hover, +.datepicker table tr td.today.disabled:hover:hover, +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active, +.datepicker table tr td.today.disabled, +.datepicker table tr td.today:hover.disabled, +.datepicker table tr td.today.disabled.disabled, +.datepicker table tr td.today.disabled:hover.disabled, +.datepicker table tr td.today[disabled], +.datepicker table tr td.today:hover[disabled], +.datepicker table tr td.today.disabled[disabled], +.datepicker table tr td.today.disabled:hover[disabled] { background-color: #fdf59a; } -.datepicker td.today:active, -.datepicker td.today:hover:active, -.datepicker td.today.disabled:active, -.datepicker td.today.disabled:hover:active, -.datepicker td.today.active, -.datepicker td.today:hover.active, -.datepicker td.today.disabled.active, -.datepicker td.today.disabled:hover.active { +.datepicker table tr td.today:active, +.datepicker table tr td.today:hover:active, +.datepicker table tr td.today.disabled:active, +.datepicker table tr td.today.disabled:hover:active, +.datepicker table tr td.today.active, +.datepicker table tr td.today:hover.active, +.datepicker table tr td.today.disabled.active, +.datepicker table tr td.today.disabled:hover.active { background-color: #fbf069 \9; } -.datepicker td.range, -.datepicker td.range:hover, -.datepicker td.range.disabled, -.datepicker td.range.disabled:hover { +.datepicker table tr td.today:hover:hover { + color: #000; +} +.datepicker table tr td.today.active:hover { + color: #fff; +} +.datepicker table tr td.range, +.datepicker table tr td.range:hover, +.datepicker table tr td.range.disabled, +.datepicker table tr td.range.disabled:hover { background: #eeeeee; -webkit-border-radius: 0; -moz-border-radius: 0; border-radius: 0; } -.datepicker td.selected, -.datepicker td.selected:hover, -.datepicker td.selected.disabled, -.datepicker td.selected.disabled:hover { +.datepicker table tr td.range.today, +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today.disabled:hover { + background-color: #f3d17a; + background-image: -moz-linear-gradient(top, #f3c17a, #f3e97a); + background-image: -ms-linear-gradient(top, #f3c17a, #f3e97a); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a)); + background-image: -webkit-linear-gradient(top, #f3c17a, #f3e97a); + background-image: -o-linear-gradient(top, #f3c17a, #f3e97a); + background-image: linear-gradient(top, #f3c17a, #f3e97a); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0); + border-color: #f3e97a #f3e97a #edde34; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.datepicker table tr td.range.today:hover, +.datepicker table tr td.range.today:hover:hover, +.datepicker table tr td.range.today.disabled:hover, +.datepicker table tr td.range.today.disabled:hover:hover, +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active, +.datepicker table tr td.range.today.disabled, +.datepicker table tr td.range.today:hover.disabled, +.datepicker table tr td.range.today.disabled.disabled, +.datepicker table tr td.range.today.disabled:hover.disabled, +.datepicker table tr td.range.today[disabled], +.datepicker table tr td.range.today:hover[disabled], +.datepicker table tr td.range.today.disabled[disabled], +.datepicker table tr td.range.today.disabled:hover[disabled] { + background-color: #f3e97a; +} +.datepicker table tr td.range.today:active, +.datepicker table tr td.range.today:hover:active, +.datepicker table tr td.range.today.disabled:active, +.datepicker table tr td.range.today.disabled:hover:active, +.datepicker table tr td.range.today.active, +.datepicker table tr td.range.today:hover.active, +.datepicker table tr td.range.today.disabled.active, +.datepicker table tr td.range.today.disabled:hover.active { + background-color: #efe24b \9; +} +.datepicker table tr td.selected, +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected.disabled:hover { background-color: #9e9e9e; background-image: -moz-linear-gradient(top, #b3b3b3, #808080); background-image: -ms-linear-gradient(top, #b3b3b3, #808080); @@ -152,46 +259,46 @@ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0); border-color: #808080 #808080 #595959; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:dximagetransform.microsoft.gradient(enabled=false); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); color: #fff; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); } -.datepicker td.selected:hover, -.datepicker td.selected:hover:hover, -.datepicker td.selected.disabled:hover, -.datepicker td.selected.disabled:hover:hover, -.datepicker td.selected:active, -.datepicker td.selected:hover:active, -.datepicker td.selected.disabled:active, -.datepicker td.selected.disabled:hover:active, -.datepicker td.selected.active, -.datepicker td.selected:hover.active, -.datepicker td.selected.disabled.active, -.datepicker td.selected.disabled:hover.active, -.datepicker td.selected.disabled, -.datepicker td.selected:hover.disabled, -.datepicker td.selected.disabled.disabled, -.datepicker td.selected.disabled:hover.disabled, -.datepicker td.selected[disabled], -.datepicker td.selected:hover[disabled], -.datepicker td.selected.disabled[disabled], -.datepicker td.selected.disabled:hover[disabled] { +.datepicker table tr td.selected:hover, +.datepicker table tr td.selected:hover:hover, +.datepicker table tr td.selected.disabled:hover, +.datepicker table tr td.selected.disabled:hover:hover, +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active, +.datepicker table tr td.selected.disabled, +.datepicker table tr td.selected:hover.disabled, +.datepicker table tr td.selected.disabled.disabled, +.datepicker table tr td.selected.disabled:hover.disabled, +.datepicker table tr td.selected[disabled], +.datepicker table tr td.selected:hover[disabled], +.datepicker table tr td.selected.disabled[disabled], +.datepicker table tr td.selected.disabled:hover[disabled] { background-color: #808080; } -.datepicker td.selected:active, -.datepicker td.selected:hover:active, -.datepicker td.selected.disabled:active, -.datepicker td.selected.disabled:hover:active, -.datepicker td.selected.active, -.datepicker td.selected:hover.active, -.datepicker td.selected.disabled.active, -.datepicker td.selected.disabled:hover.active { +.datepicker table tr td.selected:active, +.datepicker table tr td.selected:hover:active, +.datepicker table tr td.selected.disabled:active, +.datepicker table tr td.selected.disabled:hover:active, +.datepicker table tr td.selected.active, +.datepicker table tr td.selected:hover.active, +.datepicker table tr td.selected.disabled.active, +.datepicker table tr td.selected.disabled:hover.active { background-color: #666666 \9; } -.datepicker td.active, -.datepicker td.active:hover, -.datepicker td.active.disabled, -.datepicker td.active.disabled:hover { +.datepicker table tr td.active, +.datepicker table tr td.active:hover, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active.disabled:hover { background-color: #006dcc; background-image: -moz-linear-gradient(top, #0088cc, #0044cc); background-image: -ms-linear-gradient(top, #0088cc, #0044cc); @@ -203,43 +310,43 @@ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); border-color: #0044cc #0044cc #002a80; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:dximagetransform.microsoft.gradient(enabled=false); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); color: #fff; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); } -.datepicker td.active:hover, -.datepicker td.active:hover:hover, -.datepicker td.active.disabled:hover, -.datepicker td.active.disabled:hover:hover, -.datepicker td.active:active, -.datepicker td.active:hover:active, -.datepicker td.active.disabled:active, -.datepicker td.active.disabled:hover:active, -.datepicker td.active.active, -.datepicker td.active:hover.active, -.datepicker td.active.disabled.active, -.datepicker td.active.disabled:hover.active, -.datepicker td.active.disabled, -.datepicker td.active:hover.disabled, -.datepicker td.active.disabled.disabled, -.datepicker td.active.disabled:hover.disabled, -.datepicker td.active[disabled], -.datepicker td.active:hover[disabled], -.datepicker td.active.disabled[disabled], -.datepicker td.active.disabled:hover[disabled] { +.datepicker table tr td.active:hover, +.datepicker table tr td.active:hover:hover, +.datepicker table tr td.active.disabled:hover, +.datepicker table tr td.active.disabled:hover:hover, +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active, +.datepicker table tr td.active.disabled, +.datepicker table tr td.active:hover.disabled, +.datepicker table tr td.active.disabled.disabled, +.datepicker table tr td.active.disabled:hover.disabled, +.datepicker table tr td.active[disabled], +.datepicker table tr td.active:hover[disabled], +.datepicker table tr td.active.disabled[disabled], +.datepicker table tr td.active.disabled:hover[disabled] { background-color: #0044cc; } -.datepicker td.active:active, -.datepicker td.active:hover:active, -.datepicker td.active.disabled:active, -.datepicker td.active.disabled:hover:active, -.datepicker td.active.active, -.datepicker td.active:hover.active, -.datepicker td.active.disabled.active, -.datepicker td.active.disabled:hover.active { +.datepicker table tr td.active:active, +.datepicker table tr td.active:hover:active, +.datepicker table tr td.active.disabled:active, +.datepicker table tr td.active.disabled:hover:active, +.datepicker table tr td.active.active, +.datepicker table tr td.active:hover.active, +.datepicker table tr td.active.disabled.active, +.datepicker table tr td.active.disabled:hover.active { background-color: #003399 \9; } -.datepicker td span { +.datepicker table tr td span { display: block; width: 23%; height: 54px; @@ -251,19 +358,19 @@ -moz-border-radius: 4px; border-radius: 4px; } -.datepicker td span:hover { +.datepicker table tr td span:hover { background: #eeeeee; } -.datepicker td span.disabled, -.datepicker td span.disabled:hover { +.datepicker table tr td span.disabled, +.datepicker table tr td span.disabled:hover { background: none; color: #999999; cursor: default; } -.datepicker td span.active, -.datepicker td span.active:hover, -.datepicker td span.active.disabled, -.datepicker td span.active.disabled:hover { +.datepicker table tr td span.active, +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active.disabled:hover { background-color: #006dcc; background-image: -moz-linear-gradient(top, #0088cc, #0044cc); background-image: -ms-linear-gradient(top, #0088cc, #0044cc); @@ -275,59 +382,69 @@ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); border-color: #0044cc #0044cc #002a80; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:dximagetransform.microsoft.gradient(enabled=false); + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); color: #fff; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); } -.datepicker td span.active:hover, -.datepicker td span.active:hover:hover, -.datepicker td span.active.disabled:hover, -.datepicker td span.active.disabled:hover:hover, -.datepicker td span.active:active, -.datepicker td span.active:hover:active, -.datepicker td span.active.disabled:active, -.datepicker td span.active.disabled:hover:active, -.datepicker td span.active.active, -.datepicker td span.active:hover.active, -.datepicker td span.active.disabled.active, -.datepicker td span.active.disabled:hover.active, -.datepicker td span.active.disabled, -.datepicker td span.active:hover.disabled, -.datepicker td span.active.disabled.disabled, -.datepicker td span.active.disabled:hover.disabled, -.datepicker td span.active[disabled], -.datepicker td span.active:hover[disabled], -.datepicker td span.active.disabled[disabled], -.datepicker td span.active.disabled:hover[disabled] { +.datepicker table tr td span.active:hover, +.datepicker table tr td span.active:hover:hover, +.datepicker table tr td span.active.disabled:hover, +.datepicker table tr td span.active.disabled:hover:hover, +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active, +.datepicker table tr td span.active.disabled, +.datepicker table tr td span.active:hover.disabled, +.datepicker table tr td span.active.disabled.disabled, +.datepicker table tr td span.active.disabled:hover.disabled, +.datepicker table tr td span.active[disabled], +.datepicker table tr td span.active:hover[disabled], +.datepicker table tr td span.active.disabled[disabled], +.datepicker table tr td span.active.disabled:hover[disabled] { background-color: #0044cc; } -.datepicker td span.active:active, -.datepicker td span.active:hover:active, -.datepicker td span.active.disabled:active, -.datepicker td span.active.disabled:hover:active, -.datepicker td span.active.active, -.datepicker td span.active:hover.active, -.datepicker td span.active.disabled.active, -.datepicker td span.active.disabled:hover.active { +.datepicker table tr td span.active:active, +.datepicker table tr td span.active:hover:active, +.datepicker table tr td span.active.disabled:active, +.datepicker table tr td span.active.disabled:hover:active, +.datepicker table tr td span.active.active, +.datepicker table tr td span.active:hover.active, +.datepicker table tr td span.active.disabled.active, +.datepicker table tr td span.active.disabled:hover.active { background-color: #003399 \9; } -.datepicker td span.old { +.datepicker table tr td span.old, +.datepicker table tr td span.new { color: #999999; } -.datepicker th.switch { +.datepicker th.datepicker-switch { width: 145px; } .datepicker thead tr:first-child th, -.datepicker tfoot tr:first-child th { +.datepicker tfoot tr th { cursor: pointer; } .datepicker thead tr:first-child th:hover, -.datepicker tfoot tr:first-child th:hover { +.datepicker tfoot tr th:hover { background: #eeeeee; } +.datepicker .cw { + font-size: 10px; + width: 12px; + padding: 0 2px 0 5px; + vertical-align: middle; +} +.datepicker thead tr:first-child th.cw { + cursor: default; + background-color: transparent; +} .input-append.date .add-on i, .input-prepend.date .add-on i { - display: block; cursor: pointer; width: 16px; height: 16px; @@ -349,10 +466,10 @@ display: inline-block; width: auto; min-width: 16px; - height: 18px; + height: 20px; padding: 4px 5px; font-weight: normal; - line-height: 18px; + line-height: 20px; text-align: center; text-shadow: 0 1px 0 #ffffff; vertical-align: middle; @@ -387,19 +504,11 @@ color: #333333; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 13px; - line-height: 18px; + line-height: 20px; } .datepicker.dropdown-menu th, -.datepicker.dropdown-menu td { +.datepicker.datepicker-inline th, +.datepicker.dropdown-menu td, +.datepicker.datepicker-inline td { padding: 4px 5px; } -.datepicker .prev, -.datepicker .next { - font-style: normal; -} -.datepicker .prev:after { - content: "«"; -} -.datepicker .next:after { - content: "»"; -} diff --git a/UI/WebServerResources/datepicker.js b/UI/WebServerResources/datepicker.js index 91aa6c029..5d6277feb 100644 --- a/UI/WebServerResources/datepicker.js +++ b/UI/WebServerResources/datepicker.js @@ -1,10 +1,11 @@ /* ========================================================= * bootstrap-datepicker.js - * http://www.eyecon.ro/bootstrap-datepicker + * Repo: https://github.com/eternicode/bootstrap-datepicker/ + * Demo: http://eternicode.github.io/bootstrap-datepicker/ + * Docs: http://bootstrap-datepicker.readthedocs.org/ + * Forked from http://www.eyecon.ro/bootstrap-datepicker * ========================================================= - * Copyright 2012 Stefan Petre - * Improvements by Andrew Rowls - * Adapted for SOGo translation by Francis Lachapelle + * Started by Stefan Petre; improvements by Andrew Rowls + contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,286 +20,815 @@ * limitations under the License. * ========================================================= */ -!function( $ ) { +(function($, undefined){ + + var $window = $(window); function UTCDate(){ return new Date(Date.UTC.apply(Date, arguments)); } + function UTCToday(){ + var today = new Date(); + return UTCDate(today.getFullYear(), today.getMonth(), today.getDate()); + } + function alias(method){ + return function(){ + return this[method].apply(this, arguments); + }; + } + + var DateArray = (function(){ + var extras = { + get: function(i){ + return this.slice(i)[0]; + }, + contains: function(d){ + // Array.indexOf is not cross-browser; + // $.inArray doesn't work with Dates + var val = d && d.valueOf(); + for (var i=0, l=this.length; i < l; i++) + if (this[i].valueOf() === val) + return i; + return -1; + }, + remove: function(i){ + this.splice(i,1); + }, + replace: function(new_array){ + if (!new_array) + return; + if (!$.isArray(new_array)) + new_array = [new_array]; + this.clear(); + this.push.apply(this, new_array); + }, + clear: function(){ + this.length = 0; + }, + copy: function(){ + var a = new DateArray(); + a.replace(this); + return a; + } + }; + + return function(){ + var a = []; + a.push.apply(a, arguments); + $.extend(a, extras); + return a; + }; + })(); + // Picker object - var Datepicker = function(element, options) { - var that = this; + var Datepicker = function(element, options){ + this.dates = new DateArray(); + this.viewDate = UTCToday(); + this.focusDate = null; + + this._process_options(options); this.element = $(element); - this.position = options.position||this.element.data('position'); - this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy'); - this.picker = $(DPGlobal.template).addClass(this.position) - .appendTo('body') - .on({click: $.proxy(this.click, this)}); + this.isInline = false; this.isInput = this.element.is('input'); - this.component = this.element.is('.date') ? this.element.find('.add-on') : false; + this.component = this.element.is('.date') ? this.element.find('.add-on, .input-group-addon, .btn') : false; this.hasInput = this.component && this.element.find('input').length; - if(this.component && this.component.length === 0) + if (this.component && this.component.length === 0) this.component = false; - if (this.isInput) { - this.element.on({ - focus: $.proxy(this.show, this), - keyup: $.proxy(this.update, this), - keydown: $.proxy(this.keydown, this) - }); + this.picker = $(DPGlobal.template); + this._buildEvents(); + this._attachEvents(); + + if (this.isInline){ + this.picker.addClass('datepicker-inline').appendTo(this.element); } - else { - if (this.component && this.hasInput){ - // For components that are not readonly, allow keyboard nav - this.element.find('input').on({ - focus: $.proxy(this.show, this), - keyup: $.proxy(this.update, this), - keydown: $.proxy(this.keydown, this) - }); - this.component.on('click', $.proxy(this.show, this)); - } - else { - this.element.on('click', $.proxy(this.show, this)); - } + else { + this.picker.addClass('datepicker-dropdown dropdown-menu'); } - $(document).on('mousedown', function (e) { - // Clicked outside the datepicker, hide it - if ($(e.target).closest('.datepicker').length == 0) { - that.hide(); - } - }); - - this.autoclose = false; - if ('autoclose' in options) { - this.autoclose = options.autoclose; - } - else if ('dateAutoclose' in this.element.data()) { - this.autoclose = this.element.data('date-autoclose'); + if (this.o.rtl){ + this.picker.addClass('datepicker-rtl'); } - this.keyboardNavigation = true; - if ('keyboardNavigation' in options) { - this.keyboardNavigation = options.keyboardNavigation; - } - else if ('dateKeyboardNavigation' in this.element.data()) { - this.keyboardNavigation = this.element.data('date-keyboard-navigation'); - } + this.viewMode = this.o.startView; - switch(options.startView || this.element.data('date-start-view')){ - case 2: - case 'decade': - this.viewMode = this.startViewMode = 2; - break; - case 1: - case 'year': - this.viewMode = this.startViewMode = 1; - break; - case 0: - case 'month': - default: - this.viewMode = this.startViewMode = 0; - break; - } + if (this.o.calendarWeeks) + this.picker.find('tfoot th.today') + .attr('colspan', function(i, val){ + return parseInt(val) + 1; + }); + + this._allow_update = false; + + this.setStartDate(this._o.startDate); + this.setEndDate(this._o.endDate); + this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled); - this.weekStart = ((options.weekStart||this.element.data('date-weekstart')||0) % 7); - this.weekEnd = ((this.weekStart + 6) % 7); - this.startDate = -Infinity; - this.endDate = Infinity; - this.setStartDate(options.startDate||this.element.data('date-startdate')); - this.setEndDate(options.endDate||this.element.data('date-enddate')); this.fillDow(); this.fillMonths(); + + this._allow_update = true; + this.update(); this.showMode(); + + if (this.isInline){ + this.show(); + } }; Datepicker.prototype = { constructor: Datepicker, - show: function(e) { - this.picker.show(); - this.height = this.component ? this.component.outerHeight() : this.element.outerHeight(); - this.update(); - this.place(); - $(window).on('resize', $.proxy(this.place, this)); - if (e ) { - e.stopPropagation(); - e.preventDefault(); + _process_options: function(opts){ + // Store raw options for reference + this._o = $.extend({}, this._o, opts); + // Processed options + var o = this.o = $.extend({}, this._o); + + // Check if "de-DE" style date is available, if not language should + // fallback to 2 letter code eg "de" + var lang = o.language; + if (!dates[lang]){ + lang = lang.split('-')[0]; + if (!dates[lang]) + lang = defaults.language; } + o.language = lang; + + switch (o.startView){ + case 2: + case 'decade': + o.startView = 2; + break; + case 1: + case 'year': + o.startView = 1; + break; + default: + o.startView = 0; + } + + switch (o.minViewMode){ + case 1: + case 'months': + o.minViewMode = 1; + break; + case 2: + case 'years': + o.minViewMode = 2; + break; + default: + o.minViewMode = 0; + } + + o.startView = Math.max(o.startView, o.minViewMode); + + // true, false, or Number > 0 + if (o.multidate !== true){ + o.multidate = Number(o.multidate) || false; + if (o.multidate !== false) + o.multidate = Math.max(0, o.multidate); + else + o.multidate = 1; + } + o.multidateSeparator = String(o.multidateSeparator); + + o.weekStart %= 7; + o.weekEnd = ((o.weekStart + 6) % 7); + + var format = DPGlobal.parseFormat(o.format); + if (o.startDate !== -Infinity){ + if (!!o.startDate){ + if (o.startDate instanceof Date) + o.startDate = this._local_to_utc(this._zero_time(o.startDate)); + else + o.startDate = DPGlobal.parseDate(o.startDate, format, o.language); + } + else { + o.startDate = -Infinity; + } + } + if (o.endDate !== Infinity){ + if (!!o.endDate){ + if (o.endDate instanceof Date) + o.endDate = this._local_to_utc(this._zero_time(o.endDate)); + else + o.endDate = DPGlobal.parseDate(o.endDate, format, o.language); + } + else { + o.endDate = Infinity; + } + } + + o.daysOfWeekDisabled = o.daysOfWeekDisabled||[]; + if (!$.isArray(o.daysOfWeekDisabled)) + o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/); + o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function(d){ + return parseInt(d, 10); + }); + + var plc = String(o.orientation).toLowerCase().split(/\s+/g), + _plc = o.orientation.toLowerCase(); + plc = $.grep(plc, function(word){ + return (/^auto|left|right|top|bottom$/).test(word); + }); + o.orientation = {x: 'auto', y: 'auto'}; + if (!_plc || _plc === 'auto') + ; // no action + else if (plc.length === 1){ + switch (plc[0]){ + case 'top': + case 'bottom': + o.orientation.y = plc[0]; + break; + case 'left': + case 'right': + o.orientation.x = plc[0]; + break; + } + } + else { + _plc = $.grep(plc, function(word){ + return (/^left|right$/).test(word); + }); + o.orientation.x = _plc[0] || 'auto'; + + _plc = $.grep(plc, function(word){ + return (/^top|bottom$/).test(word); + }); + o.orientation.y = _plc[0] || 'auto'; + } + }, + _events: [], + _secondaryEvents: [], + _applyEvents: function(evs){ + for (var i=0, el, ch, ev; i < evs.length; i++){ + el = evs[i][0]; + if (evs[i].length === 2){ + ch = undefined; + ev = evs[i][1]; + } + else if (evs[i].length === 3){ + ch = evs[i][1]; + ev = evs[i][2]; + } + el.on(ev, ch); + } + }, + _unapplyEvents: function(evs){ + for (var i=0, el, ev, ch; i < evs.length; i++){ + el = evs[i][0]; + if (evs[i].length === 2){ + ch = undefined; + ev = evs[i][1]; + } + else if (evs[i].length === 3){ + ch = evs[i][1]; + ev = evs[i][2]; + } + el.off(ev, ch); + } + }, + _buildEvents: function(){ + if (this.isInput){ // single input + this._events = [ + [this.element, { + focus: $.proxy(this.show, this), + keyup: $.proxy(function(e){ + if ($.inArray(e.keyCode, [27,37,39,38,40,32,13,9]) === -1) + this.update(); + }, this), + keydown: $.proxy(this.keydown, this) + }] + ]; + } + else if (this.component && this.hasInput){ // component: input + button + this._events = [ + // For components that are not readonly, allow keyboard nav + [this.element.find('input'), { + focus: $.proxy(this.show, this), + keyup: $.proxy(function(e){ + if ($.inArray(e.keyCode, [27,37,39,38,40,32,13,9]) === -1) + this.update(); + }, this), + keydown: $.proxy(this.keydown, this) + }], + [this.component, { + click: $.proxy(this.show, this) + }] + ]; + } + else if (this.element.is('div')){ // inline datepicker + this.isInline = true; + } + else { + this._events = [ + [this.element, { + click: $.proxy(this.show, this) + }] + ]; + } + this._events.push( + // Component: listen for blur on element descendants + [this.element, '*', { + blur: $.proxy(function(e){ + this._focused_from = e.target; + }, this) + }], + // Input: listen for blur on element + [this.element, { + blur: $.proxy(function(e){ + this._focused_from = e.target; + }, this) + }] + ); + + this._secondaryEvents = [ + [this.picker, { + click: $.proxy(this.click, this) + }], + [$(window), { + resize: $.proxy(this.place, this) + }], + [$(document), { + 'mousedown touchstart': $.proxy(function(e){ + // Clicked outside the datepicker, hide it + if (!( + this.element.is(e.target) || + this.element.find(e.target).length || + this.picker.is(e.target) || + this.picker.find(e.target).length + )){ + this.hide(); + } + }, this) + }] + ]; + }, + _attachEvents: function(){ + this._detachEvents(); + this._applyEvents(this._events); + }, + _detachEvents: function(){ + this._unapplyEvents(this._events); + }, + _attachSecondaryEvents: function(){ + this._detachSecondaryEvents(); + this._applyEvents(this._secondaryEvents); + }, + _detachSecondaryEvents: function(){ + this._unapplyEvents(this._secondaryEvents); + }, + _trigger: function(event, altdate){ + var date = altdate || this.dates.get(-1), + local_date = this._utc_to_local(date); + this.element.trigger({ - type: 'show', - date: this.date + type: event, + date: local_date, + dates: $.map(this.dates, this._utc_to_local), + format: $.proxy(function(ix, format){ + if (arguments.length === 0){ + ix = this.dates.length - 1; + format = this.o.format; + } + else if (typeof ix === 'string'){ + format = ix; + ix = this.dates.length - 1; + } + format = format || this.o.format; + var date = this.dates.get(ix); + return DPGlobal.formatDate(date, format, this.o.language); + }, this) }); }, - hide: function(e){ - this.picker.hide(); - $(window).off('resize', this.place); - this.viewMode = this.startViewMode; - this.showMode(); - if (!this.isInput) { - $(document).off('mousedown', this.hide); - } - if (e && e.currentTarget.value) - this.setValue(); + show: function(){ + if (!this.isInline) + this.picker.appendTo('body'); + this.picker.show(); + this.place(); + this._attachSecondaryEvents(); + this._trigger('show'); }, - setValue: function() { - var formatted = DPGlobal.formatDate(this.date, this.format); - if (!this.isInput) { - if (this.component){ - this.element.find('input').prop('value', formatted); - } - this.element.data('date', formatted); - } else { - this.element.prop('value', formatted); + hide: function(){ + if (this.isInline) + return; + if (!this.picker.is(':visible')) + return; + this.focusDate = null; + this.picker.hide().detach(); + this._detachSecondaryEvents(); + this.viewMode = this.o.startView; + this.showMode(); + + if ( + this.o.forceParse && + ( + this.isInput && this.element.val() || + this.hasInput && this.element.find('input').val() + ) + ) + this.setValue(); + //this._trigger('hide'); + }, + + remove: function(){ + this.hide(); + this._detachEvents(); + this._detachSecondaryEvents(); + this.picker.remove(); + delete this.element.data().datepicker; + if (!this.isInput){ + delete this.element.data().date; } }, + _utc_to_local: function(utc){ + return utc && new Date(utc.getTime() + (utc.getTimezoneOffset()*60000)); + }, + _local_to_utc: function(local){ + return local && new Date(local.getTime() - (local.getTimezoneOffset()*60000)); + }, + _zero_time: function(local){ + return local && new Date(local.getFullYear(), local.getMonth(), local.getDate()); + }, + _zero_utc_time: function(utc){ + return utc && new Date(Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate())); + }, + + getDates: function(){ + return $.map(this.dates, this._utc_to_local); + }, + + getUTCDates: function(){ + return $.map(this.dates, function(d){ + return new Date(d); + }); + }, + + getDate: function(){ + return this._utc_to_local(this.getUTCDate()); + }, + + getUTCDate: function(){ + return new Date(this.dates.get(-1)); + }, + + setDates: function(){ + var args = $.isArray(arguments[0]) ? arguments[0] : arguments; + this.update.apply(this, args); + this._trigger('changeDate'); + this.setValue(); + }, + + setUTCDates: function(){ + var args = $.isArray(arguments[0]) ? arguments[0] : arguments; + this.update.apply(this, $.map(args, this._utc_to_local)); + this._trigger('changeDate'); + this.setValue(); + }, + + setDate: alias('setDates'), + setUTCDate: alias('setUTCDates'), + + setValue: function(){ + var formatted = this.getFormattedDate(); + if (!this.isInput){ + if (this.component){ + this.element.find('input').val(formatted).change(); + } + } + else { + this.element.val(formatted).change(); + } + }, + + getFormattedDate: function(format){ + if (format === undefined) + format = this.o.format; + + var lang = this.o.language; + return $.map(this.dates, function(d){ + return DPGlobal.formatDate(d, format, lang); + }).join(this.o.multidateSeparator); + }, + setStartDate: function(startDate){ - this.startDate = startDate||-Infinity; - if (this.startDate !== -Infinity) { - this.startDate = DPGlobal.parseDate(this.startDate, this.format); - } + this._process_options({startDate: startDate}); this.update(); this.updateNavArrows(); }, setEndDate: function(endDate){ - this.endDate = endDate||Infinity; - if (this.endDate !== Infinity) { - this.endDate = DPGlobal.parseDate(this.endDate, this.format); - } + this._process_options({endDate: endDate}); + this.update(); + this.updateNavArrows(); + }, + + setDaysOfWeekDisabled: function(daysOfWeekDisabled){ + this._process_options({daysOfWeekDisabled: daysOfWeekDisabled}); this.update(); this.updateNavArrows(); }, place: function(){ - var zIndex = parseInt(this.element.parents().filter(function() { - return $(this).css('z-index') != 'auto'; - }).first().css('z-index'))+10; - var offset = this.component ? this.component.offset() : this.element.offset(); - var width = this.component ? this.component.width() : this.element.width(); - if (this.position == 'bellow') - this.picker.css({top: offset.top + this.height, - left: offset.left, - right: 'auto', - zIndex: zIndex - }); - - if (this.position == 'below-shifted-left') - this.picker.css({top: offset.top + this.height, - left: offset.left - 83, - right: 'auto', - zIndex: zIndex - }); - else // above - this.picker.css({ top: offset.top - this.height - this.picker.outerHeight(), - right: window.width() - offset.left - width, - left: 'auto', - zIndex: zIndex - }); + if (this.isInline) + return; + var calendarWidth = this.picker.outerWidth(), + calendarHeight = this.picker.outerHeight(), + visualPadding = 10, + windowWidth = $window.width(), + windowHeight = $window.height(), + scrollTop = $window.scrollTop(); + + var zIndex = parseInt(this.element.parents().filter(function(){ + return $(this).css('z-index') !== 'auto'; + }).first().css('z-index'))+10; + var offset = this.component ? this.component.parent().offset() : this.element.offset(); + var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(false); + var width = this.component ? this.component.outerWidth(true) : this.element.outerWidth(false); + var left = offset.left, + top = offset.top; + + this.picker.removeClass( + 'datepicker-orient-top datepicker-orient-bottom '+ + 'datepicker-orient-right datepicker-orient-left' + ); + + if (this.o.orientation.x !== 'auto'){ + this.picker.addClass('datepicker-orient-' + this.o.orientation.x); + if (this.o.orientation.x === 'right') + left -= calendarWidth - width; + } + // auto x orientation is best-placement: if it crosses a window + // edge, fudge it sideways + else { + // Default to left + this.picker.addClass('datepicker-orient-left'); + if (offset.left < 0) + left -= offset.left - visualPadding; + else if (offset.left + calendarWidth > windowWidth) + left = windowWidth - calendarWidth - visualPadding; + } + + // auto y orientation is best-situation: top or bottom, no fudging, + // decision based on which shows more of the calendar + var yorient = this.o.orientation.y, + top_overflow, bottom_overflow; + if (yorient === 'auto'){ + top_overflow = -scrollTop + offset.top - calendarHeight; + bottom_overflow = scrollTop + windowHeight - (offset.top + height + calendarHeight); + if (Math.max(top_overflow, bottom_overflow) === bottom_overflow) + yorient = 'top'; + else + yorient = 'bottom'; + } + this.picker.addClass('datepicker-orient-' + yorient); + if (yorient === 'top') + top += height; + else + top -= calendarHeight + parseInt(this.picker.css('padding-top')); + + this.picker.css({ + top: top, + left: left, + zIndex: zIndex + }); }, + _allow_update: true, update: function(){ - this.date = DPGlobal.parseDate( - this.isInput ? this.element.prop('value') : this.element.data('date') || this.element.find('input').prop('value'), - this.format - ); - if (this.date < this.startDate) { - this.viewDate = new Date(this.startDate); - } else if (this.date > this.endDate) { - this.viewDate = new Date(this.endDate); - } else { - this.viewDate = new Date(this.date); + if (!this._allow_update) + return; + + var oldDates = this.dates.copy(), + dates = [], + fromArgs = false; + if (arguments.length){ + $.each(arguments, $.proxy(function(i, date){ + if (date instanceof Date) + date = this._local_to_utc(date); + dates.push(date); + }, this)); + fromArgs = true; } + else { + dates = this.isInput + ? this.element.val() + : this.element.data('date') || this.element.find('input').val(); + if (dates && this.o.multidate) + dates = dates.split(this.o.multidateSeparator); + else + dates = [dates]; + delete this.element.data().date; + } + + dates = $.map(dates, $.proxy(function(date){ + return DPGlobal.parseDate(date, this.o.format, this.o.language); + }, this)); + dates = $.grep(dates, $.proxy(function(date){ + return ( + date < this.o.startDate || + date > this.o.endDate || + !date + ); + }, this), true); + this.dates.replace(dates); + + if (this.dates.length) + this.viewDate = new Date(this.dates.get(-1)); + else if (this.viewDate < this.o.startDate) + this.viewDate = new Date(this.o.startDate); + else if (this.viewDate > this.o.endDate) + this.viewDate = new Date(this.o.endDate); + + if (fromArgs){ + // setting date by clicking + this.setValue(); + } + else if (dates.length){ + // setting date by typing + if (String(oldDates) !== String(this.dates)) + this._trigger('changeDate'); + } + if (!this.dates.length && oldDates.length) + this._trigger('clearDate'); + this.fill(); }, fillDow: function(){ - var dowCnt = this.weekStart; - var html = ''; - while (dowCnt < this.weekStart + 7) { - html += ''+_('a2_'+dates.days[(dowCnt++)%7])+''; + var dowCnt = this.o.weekStart, + html = ''; + if (this.o.calendarWeeks){ + var cell = ' '; + html += cell; + this.picker.find('.datepicker-days thead tr:first-child').prepend(cell); + } + while (dowCnt < this.o.weekStart + 7){ + html += ''+_(dates[this.o.language].daysMin[(dowCnt++)%7])+''; } html += ''; this.picker.find('.datepicker-days thead').append(html); }, fillMonths: function(){ - var html = ''; - var i = 0 - while (i < 12) { - html += ''+_(dates.monthsShort[i++])+''; + var html = '', + i = 0; + while (i < 12){ + html += ''+_(dates[this.o.language].monthsShort[i++])+''; } this.picker.find('.datepicker-months td').html(html); }, - fill: function() { + setRange: function(range){ + if (!range || !range.length) + delete this.range; + else + this.range = $.map(range, function(d){ + return d.valueOf(); + }); + this.fill(); + }, + + getClassNames: function(date){ + var cls = [], + year = this.viewDate.getUTCFullYear(), + month = this.viewDate.getUTCMonth(), + today = new Date(); + if (date.getUTCFullYear() < year || (date.getUTCFullYear() === year && date.getUTCMonth() < month)){ + cls.push('old'); + } + else if (date.getUTCFullYear() > year || (date.getUTCFullYear() === year && date.getUTCMonth() > month)){ + cls.push('new'); + } + if (this.focusDate && date.valueOf() === this.focusDate.valueOf()) + cls.push('focused'); + // Compare internal UTC date with local today, not UTC today + if (this.o.todayHighlight && + date.getUTCFullYear() === today.getFullYear() && + date.getUTCMonth() === today.getMonth() && + date.getUTCDate() === today.getDate()){ + cls.push('today'); + } + if (this.dates.contains(date) !== -1) + cls.push('active'); + if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate || + $.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1){ + cls.push('disabled'); + } + if (this.range){ + if (date > this.range[0] && date < this.range[this.range.length-1]){ + cls.push('range'); + } + if ($.inArray(date.valueOf(), this.range) !== -1){ + cls.push('selected'); + } + } + return cls; + }, + + fill: function(){ var d = new Date(this.viewDate), year = d.getUTCFullYear(), month = d.getUTCMonth(), - startYear = this.startDate !== -Infinity ? this.startDate.getUTCFullYear() : -Infinity, - startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() : -Infinity, - endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity, - endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity, - currentDate = this.date.valueOf(); - this.picker.find('.datepicker-days th:eq(1)') - .text(_(dates.months[month])+' '+year); + startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity, + startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity, + endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity, + endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity, + todaytxt = _(dates[this.o.language].today) || dates['en'].today || '', + cleartxt = _(dates[this.o.language].clear) || dates['en'].clear || '', + tooltip; + this.picker.find('.datepicker-days thead th.datepicker-switch') + .text(_(dates[this.o.language].months[month])+' '+year); + this.picker.find('tfoot th.today') + .text(todaytxt) + .toggle(this.o.todayBtn !== false); + this.picker.find('tfoot th.clear') + .text(cleartxt) + .toggle(this.o.clearBtn !== false); this.updateNavArrows(); this.fillMonths(); - var prevMonth = UTCDate(year, month-1, 28,0,0,0,0), + var prevMonth = UTCDate(year, month-1, 28), day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth()); prevMonth.setUTCDate(day); - prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7)%7); + prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7); var nextMonth = new Date(prevMonth); nextMonth.setUTCDate(nextMonth.getUTCDate() + 42); nextMonth = nextMonth.valueOf(); var html = []; var clsName; - while(prevMonth.valueOf() < nextMonth) { - if (prevMonth.getUTCDay() == this.weekStart) { + while (prevMonth.valueOf() < nextMonth){ + if (prevMonth.getUTCDay() === this.o.weekStart){ html.push(''); + if (this.o.calendarWeeks){ + // ISO 8601: First week contains first thursday. + // ISO also states week starts on Monday, but we can be more abstract here. + var + // Start of current week: based on weekstart/current date + ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5), + // Thursday of this week + th = new Date(Number(ws) + (7 + 4 - ws.getUTCDay()) % 7 * 864e5), + // First Thursday of year, year from thursday + yth = new Date(Number(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5), + // Calendar week: ms between thursdays, div ms per day, div 7 days + calWeek = (th - yth) / 864e5 / 7 + 1; + html.push(''+ calWeek +''); + + } } - clsName = ''; - if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) { - clsName += ' old'; - } else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() > month)) { - clsName += ' new'; + clsName = this.getClassNames(prevMonth); + clsName.push('day'); + + if (this.o.beforeShowDay !== $.noop){ + var before = this.o.beforeShowDay(this._utc_to_local(prevMonth)); + if (before === undefined) + before = {}; + else if (typeof(before) === 'boolean') + before = {enabled: before}; + else if (typeof(before) === 'string') + before = {classes: before}; + if (before.enabled === false) + clsName.push('disabled'); + if (before.classes) + clsName = clsName.concat(before.classes.split(/\s+/)); + if (before.tooltip) + tooltip = before.tooltip; } - if (prevMonth.valueOf() == currentDate) { - clsName += ' active'; - } - if (prevMonth.valueOf() < this.startDate || prevMonth.valueOf() > this.endDate) { - clsName += ' disabled'; - } - html.push(''+prevMonth.getUTCDate() + ''); - if (prevMonth.getUTCDay() == this.weekEnd) { + + clsName = $.unique(clsName); + html.push(''+prevMonth.getUTCDate() + ''); + if (prevMonth.getUTCDay() === this.o.weekEnd){ html.push(''); } prevMonth.setUTCDate(prevMonth.getUTCDate()+1); } this.picker.find('.datepicker-days tbody').empty().append(html.join('')); - var currentYear = this.date.getUTCFullYear(); var months = this.picker.find('.datepicker-months') .find('th:eq(1)') .text(year) .end() .find('span').removeClass('active'); - if (currentYear == year) { - months.eq(this.date.getUTCMonth()).addClass('active'); - } - if (year < startYear || year > endYear) { + + $.each(this.dates, function(i, d){ + if (d.getUTCFullYear() === year) + months.eq(d.getUTCMonth()).addClass('active'); + }); + + if (year < startYear || year > endYear){ months.addClass('disabled'); } - if (year == startYear) { + if (year === startYear){ months.slice(0, startMonth).addClass('disabled'); } - if (year == endYear) { + if (year === endYear){ months.slice(endMonth+1).addClass('disabled'); } @@ -310,90 +840,139 @@ .end() .find('td'); year -= 1; - for (var i = -1; i < 11; i++) { - html += ''+year+''; + var years = $.map(this.dates, function(d){ + return d.getUTCFullYear(); + }), + classes; + for (var i = -1; i < 11; i++){ + classes = ['year']; + if (i === -1) + classes.push('old'); + else if (i === 10) + classes.push('new'); + if ($.inArray(year, years) !== -1) + classes.push('active'); + if (year < startYear || year > endYear) + classes.push('disabled'); + html += ''+year+''; year += 1; } yearCont.html(html); }, - updateNavArrows: function() { + updateNavArrows: function(){ + if (!this._allow_update) + return; + var d = new Date(this.viewDate), year = d.getUTCFullYear(), month = d.getUTCMonth(); - switch (this.viewMode) { + switch (this.viewMode){ case 0: - if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear() && month <= this.startDate.getUTCMonth()) { + if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()){ this.picker.find('.prev').css({visibility: 'hidden'}); - } else { + } + else { this.picker.find('.prev').css({visibility: 'visible'}); } - if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear() && month >= this.endDate.getUTCMonth()) { + if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()){ this.picker.find('.next').css({visibility: 'hidden'}); - } else { + } + else { this.picker.find('.next').css({visibility: 'visible'}); } break; case 1: case 2: - if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()) { + if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear()){ this.picker.find('.prev').css({visibility: 'hidden'}); - } else { + } + else { this.picker.find('.prev').css({visibility: 'visible'}); } - if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()) { + if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear()){ this.picker.find('.next').css({visibility: 'hidden'}); - } else { + } + else { this.picker.find('.next').css({visibility: 'visible'}); } break; } }, - click: function(e) { - e.stopPropagation(); + click: function(e){ e.preventDefault(); - var target = $(e.target).closest('span, td, th'); - if (target.length == 1) { - switch(target[0].nodeName.toLowerCase()) { + var target = $(e.target).closest('span, td, th'), + year, month, day; + if (target.length === 1){ + switch (target[0].nodeName.toLowerCase()){ case 'th': - switch(target[0].className) { - case 'switch': + switch (target[0].className){ + case 'datepicker-switch': this.showMode(1); break; case 'prev': case 'next': - var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1); - switch(this.viewMode){ + var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1); + switch (this.viewMode){ case 0: this.viewDate = this.moveMonth(this.viewDate, dir); + this._trigger('changeMonth', this.viewDate); break; case 1: case 2: this.viewDate = this.moveYear(this.viewDate, dir); + if (this.viewMode === 1) + this._trigger('changeYear', this.viewDate); break; } this.fill(); break; + case 'today': + var date = new Date(); + date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); + + this.showMode(-2); + var which = this.o.todayBtn === 'linked' ? null : 'view'; + this._setDate(date, which); + break; + case 'clear': + var element; + if (this.isInput) + element = this.element; + else if (this.component) + element = this.element.find('input'); + if (element) + element.val("").change(); + this.update(); + this._trigger('changeDate'); + if (this.o.autoclose) + this.hide(); + break; } break; case 'span': - if (!target.is('.disabled')) { + if (!target.is('.disabled')){ this.viewDate.setUTCDate(1); - if (target.is('.month')) { - var month = target.parent().find('span').index(target); + if (target.is('.month')){ + day = 1; + month = target.parent().find('span').index(target); + year = this.viewDate.getUTCFullYear(); this.viewDate.setUTCMonth(month); - this.element.trigger({ - type: 'changeMonth', - date: this.viewDate - }); - } else { - var year = parseInt(target.text(), 10)||0; + this._trigger('changeMonth', this.viewDate); + if (this.o.minViewMode === 1){ + this._setDate(UTCDate(year, month, day)); + } + } + else { + day = 1; + month = 0; + year = parseInt(target.text(), 10)||0; this.viewDate.setUTCFullYear(year); - this.element.trigger({ - type: 'changeYear', - date: this.viewDate - }); + this._trigger('changeYear', this.viewDate); + if (this.o.minViewMode === 2){ + this._setDate(UTCDate(year, month, day)); + } } this.showMode(-1); this.fill(); @@ -401,80 +980,118 @@ break; case 'td': if (target.is('.day') && !target.is('.disabled')){ - var day = parseInt(target.text(), 10)||1; - var year = this.viewDate.getUTCFullYear(), - month = this.viewDate.getUTCMonth(); - if (target.is('.old')) { - if (month == 0) { + day = parseInt(target.text(), 10)||1; + year = this.viewDate.getUTCFullYear(); + month = this.viewDate.getUTCMonth(); + if (target.is('.old')){ + if (month === 0){ month = 11; year -= 1; - } else { + } + else { month -= 1; } - } else if (target.is('.new')) { - if (month == 11) { + } + else if (target.is('.new')){ + if (month === 11){ month = 0; year += 1; - } else { + } + else { month += 1; } } - this.date = UTCDate(year, month, day,0,0,0,0); - this.viewDate = UTCDate(year, month, day,0,0,0,0); - this.fill(); - this.setValue(); - this.element.trigger({ - type: 'changeDate', - date: this.date - }); - var element; - if (this.isInput) { - element = this.element; - } else if (this.component){ - element = this.element.find('input'); - } - if (element) { - element.change(); - if (this.autoclose) { - this.hide(); - } - } + this._setDate(UTCDate(year, month, day)); } break; } } + if (this.picker.is(':visible') && this._focused_from){ + $(this._focused_from).focus(); + } + delete this._focused_from; + }, + + _toggle_multidate: function(date){ + var ix = this.dates.contains(date); + if (!date){ + this.dates.clear(); + } + else if (ix !== -1){ + this.dates.remove(ix); + } + else { + this.dates.push(date); + } + if (typeof this.o.multidate === 'number') + while (this.dates.length > this.o.multidate) + this.dates.remove(0); + }, + + _setDate: function(date, which){ + if (!which || which === 'date') + this._toggle_multidate(date && new Date(date)); + if (!which || which === 'view') + this.viewDate = date && new Date(date); + + this.fill(); + this.setValue(); + this._trigger('changeDate'); + var element; + if (this.isInput){ + element = this.element; + } + else if (this.component){ + element = this.element.find('input'); + } + if (element){ + element.change(); + } + if (this.o.autoclose && (!which || which === 'date')){ + this.hide(); + } }, moveMonth: function(date, dir){ - if (!dir) return date; + if (!date) + return undefined; + if (!dir) + return date; var new_date = new Date(date.valueOf()), day = new_date.getUTCDate(), month = new_date.getUTCMonth(), mag = Math.abs(dir), new_month, test; dir = dir > 0 ? 1 : -1; - if (mag == 1){ - test = dir == -1 + if (mag === 1){ + test = dir === -1 // If going back one month, make sure month is not current month // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02) - ? function(){ return new_date.getUTCMonth() == month; } + ? function(){ + return new_date.getUTCMonth() === month; + } // If going forward one month, make sure month is as expected // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02) - : function(){ return new_date.getUTCMonth() != new_month; }; + : function(){ + return new_date.getUTCMonth() !== new_month; + }; new_month = month + dir; new_date.setUTCMonth(new_month); // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11 if (new_month < 0 || new_month > 11) new_month = (new_month + 12) % 12; - } else { + } + else { // For magnitudes >1, move one month at a time... - for (var i=0; i= this.startDate && date <= this.endDate; + return date >= this.o.startDate && date <= this.o.endDate; }, keydown: function(e){ if (this.picker.is(':not(:visible)')){ - if (e.keyCode == 27) // allow escape to hide and re-show picker + if (e.keyCode === 27) // allow escape to hide and re-show picker this.show(); return; } var dateChanged = false, - dir, day, month, - newDate, newViewDate; - switch(e.keyCode){ + dir, newDate, newViewDate, + focusDate = this.focusDate || this.viewDate; + switch (e.keyCode){ case 27: // escape - this.hide(); + if (this.focusDate){ + this.focusDate = null; + this.viewDate = this.dates.get(-1) || this.viewDate; + this.fill(); + } + else + this.hide(); e.preventDefault(); break; case 37: // left case 39: // right - if (!this.keyboardNavigation) break; - dir = e.keyCode == 37 ? -1 : 1; + if (!this.o.keyboardNavigation) + break; + dir = e.keyCode === 37 ? -1 : 1; if (e.ctrlKey){ - newDate = this.moveYear(this.date, dir); - newViewDate = this.moveYear(this.viewDate, dir); - } else if (e.shiftKey){ - newDate = this.moveMonth(this.date, dir); - newViewDate = this.moveMonth(this.viewDate, dir); - } else { - newDate = new Date(this.date); - newDate.setUTCDate(this.date.getUTCDate() + dir); - newViewDate = new Date(this.viewDate); - newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir); + newDate = this.moveYear(this.dates.get(-1) || UTCToday(), dir); + newViewDate = this.moveYear(focusDate, dir); + this._trigger('changeYear', this.viewDate); + } + else if (e.shiftKey){ + newDate = this.moveMonth(this.dates.get(-1) || UTCToday(), dir); + newViewDate = this.moveMonth(focusDate, dir); + this._trigger('changeMonth', this.viewDate); + } + else { + newDate = new Date(this.dates.get(-1) || UTCToday()); + newDate.setUTCDate(newDate.getUTCDate() + dir); + newViewDate = new Date(focusDate); + newViewDate.setUTCDate(focusDate.getUTCDate() + dir); } if (this.dateWithinRange(newDate)){ - this.date = newDate; - this.viewDate = newViewDate; + this.focusDate = this.viewDate = newViewDate; this.setValue(); - this.update(); + this.fill(); e.preventDefault(); - dateChanged = true; } break; case 38: // up case 40: // down - if (!this.keyboardNavigation) break; - dir = e.keyCode == 38 ? -1 : 1; + if (!this.o.keyboardNavigation) + break; + dir = e.keyCode === 38 ? -1 : 1; if (e.ctrlKey){ - newDate = this.moveYear(this.date, dir); - newViewDate = this.moveYear(this.viewDate, dir); - } else if (e.shiftKey){ - newDate = this.moveMonth(this.date, dir); - newViewDate = this.moveMonth(this.viewDate, dir); - } else { - newDate = new Date(this.date); - newDate.setUTCDate(this.date.getUTCDate() + dir * 7); - newViewDate = new Date(this.viewDate); - newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7); + newDate = this.moveYear(this.dates.get(-1) || UTCToday(), dir); + newViewDate = this.moveYear(focusDate, dir); + this._trigger('changeYear', this.viewDate); + } + else if (e.shiftKey){ + newDate = this.moveMonth(this.dates.get(-1) || UTCToday(), dir); + newViewDate = this.moveMonth(focusDate, dir); + this._trigger('changeMonth', this.viewDate); + } + else { + newDate = new Date(this.dates.get(-1) || UTCToday()); + newDate.setUTCDate(newDate.getUTCDate() + dir * 7); + newViewDate = new Date(focusDate); + newViewDate.setUTCDate(focusDate.getUTCDate() + dir * 7); } if (this.dateWithinRange(newDate)){ - this.date = newDate; - this.viewDate = newViewDate; + this.focusDate = this.viewDate = newViewDate; this.setValue(); - this.update(); + this.fill(); e.preventDefault(); - dateChanged = true; } break; + case 32: // spacebar + // Spacebar is used in manually typing dates in some formats. + // As such, its behavior should not be hijacked. + break; case 13: // enter - this.hide(); - e.preventDefault(); + focusDate = this.focusDate || this.dates.get(-1) || this.viewDate; + this._toggle_multidate(focusDate); + dateChanged = true; + this.focusDate = null; + this.viewDate = this.dates.get(-1) || this.viewDate; + this.setValue(); + this.fill(); + if (this.picker.is(':visible')){ + e.preventDefault(); + if (this.o.autoclose) + this.hide(); + } break; case 9: // tab + this.focusDate = null; + this.viewDate = this.dates.get(-1) || this.viewDate; + this.fill(); this.hide(); break; } if (dateChanged){ - this.element.trigger({ - type: 'changeDate', - date: this.date - }); + if (this.dates.length) + this._trigger('changeDate'); + else + this._trigger('clearDate'); var element; - if (this.isInput) { + if (this.isInput){ element = this.element; - } else if (this.component){ + } + else if (this.component){ element = this.element.find('input'); } - if (element) { + if (element){ element.change(); } } }, - showMode: function(dir) { - if (dir) { - this.viewMode = Math.max(0, Math.min(2, this.viewMode + dir)); + showMode: function(dir){ + if (dir){ + this.viewMode = Math.max(this.o.minViewMode, Math.min(2, this.viewMode + dir)); } - this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show(); + this.picker + .find('>div') + .hide() + .filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName) + .css('display', 'block'); this.updateNavArrows(); } }; - $.fn.datepicker = function ( option ) { - var args = Array.apply(null, arguments); - args.shift(); - return this.each(function () { - var $this = $(this), - data = $this.data('datepicker'), - options = typeof option == 'object' && option; - if (!data) { - $this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options)))); - } - if (typeof option == 'string' && typeof data[option] == 'function') { - data[option].apply(data, args); - } + var DateRangePicker = function(element, options){ + this.element = $(element); + this.inputs = $.map(options.inputs, function(i){ + return i.jquery ? i[0] : i; }); + delete options.inputs; + + $(this.inputs) + .datepicker(options) + .bind('changeDate', $.proxy(this.dateUpdated, this)); + + this.pickers = $.map(this.inputs, function(i){ + return $(i).data('datepicker'); + }); + this.updateDates(); + }; + DateRangePicker.prototype = { + updateDates: function(){ + this.dates = $.map(this.pickers, function(i){ + return i.getUTCDate(); + }); + this.updateRanges(); + }, + updateRanges: function(){ + var range = $.map(this.dates, function(d){ + return d.valueOf(); + }); + $.each(this.pickers, function(i, p){ + p.setRange(range); + }); + }, + dateUpdated: function(e){ + // `this.updating` is a workaround for preventing infinite recursion + // between `changeDate` triggering and `setUTCDate` calling. Until + // there is a better mechanism. + if (this.updating) + return; + this.updating = true; + + var dp = $(e.target).data('datepicker'), + new_date = dp.getUTCDate(), + i = $.inArray(e.target, this.inputs), + l = this.inputs.length; + if (i === -1) + return; + + $.each(this.pickers, function(i, p){ + if (!p.getUTCDate()) + p.setUTCDate(new_date); + }); + + if (new_date < this.dates[i]){ + // Date being moved earlier/left + while (i >= 0 && new_date < this.dates[i]){ + this.pickers[i--].setUTCDate(new_date); + } + } + else if (new_date > this.dates[i]){ + // Date being moved later/right + while (i < l && new_date > this.dates[i]){ + this.pickers[i++].setUTCDate(new_date); + } + } + this.updateDates(); + + delete this.updating; + }, + remove: function(){ + $.map(this.pickers, function(p){ p.remove(); }); + delete this.element.data().datepicker; + } }; - $.fn.datepicker.defaults = { + function opts_from_el(el, prefix){ + // Derive options from element data-attrs + var data = $(el).data(), + out = {}, inkey, + replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])'); + prefix = new RegExp('^' + prefix.toLowerCase()); + function re_lower(_,a){ + return a.toLowerCase(); + } + for (var key in data) + if (prefix.test(key)){ + inkey = key.replace(replace, re_lower); + out[inkey] = data[key]; + } + return out; + } + + function opts_from_locale(lang){ + // Derive options from locale plugins + var out = {}; + // Check if "de-DE" style date is available, if not language should + // fallback to 2 letter code eg "de" + if (!dates[lang]){ + lang = lang.split('-')[0]; + if (!dates[lang]) + return; + } + var d = dates[lang]; + $.each(locale_opts, function(i,k){ + if (k in d) + out[k] = d[k]; + }); + return out; + } + + var old = $.fn.datepicker; + $.fn.datepicker = function(option){ + var args = Array.apply(null, arguments); + args.shift(); + var internal_return; + this.each(function(){ + var $this = $(this), + data = $this.data('datepicker'), + options = typeof option === 'object' && option; + if (!data){ + var elopts = opts_from_el(this, 'date'), + // Preliminary otions + xopts = $.extend({}, defaults, elopts, options), + locopts = opts_from_locale(xopts.language), + // Options priority: js args, data-attrs, locales, defaults + opts = $.extend({}, defaults, locopts, elopts, options); + if ($this.is('.input-daterange') || opts.inputs){ + var ropts = { + inputs: opts.inputs || $this.find('input').toArray() + }; + $this.data('datepicker', (data = new DateRangePicker(this, $.extend(opts, ropts)))); + } + else { + $this.data('datepicker', (data = new Datepicker(this, opts))); + } + } + if (typeof option === 'string' && typeof data[option] === 'function'){ + internal_return = data[option].apply(data, args); + if (internal_return !== undefined) + return false; + } + }); + if (internal_return !== undefined) + return internal_return; + else + return this; }; + + var defaults = $.fn.datepicker.defaults = { + autoclose: false, + beforeShowDay: $.noop, + calendarWeeks: false, + clearBtn: false, + daysOfWeekDisabled: [], + endDate: Infinity, + forceParse: true, + format: 'mm/dd/yyyy', + keyboardNavigation: true, + language: 'en', + minViewMode: 0, + multidate: false, + multidateSeparator: ',', + orientation: "auto", + rtl: false, + startDate: -Infinity, + startView: 0, + todayBtn: false, + todayHighlight: false, + weekStart: 0 + }; + var locale_opts = $.fn.datepicker.locale_opts = [ + 'format', + 'rtl', + 'weekStart' + ]; $.fn.datepicker.Constructor = Datepicker; var dates = $.fn.datepicker.dates = { + en: { days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"], months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], - monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] - } + monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], + today: "Today", + clear: "Clear" + } + }; var DPGlobal = { modes: [ @@ -635,35 +1445,40 @@ navFnc: 'FullYear', navStep: 10 }], - isLeapYear: function (year) { - return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)) + isLeapYear: function(year){ + return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)); }, - getDaysInMonth: function (year, month) { - return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month] + getDaysInMonth: function(year, month){ + return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; }, - validParts: /dd?|mm?|MM?|yy(?:yy)?/g, - nonpunctuation: /[^ -\/:-@\[-`{-~\t\n\r]+/g, + validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g, + nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g, parseFormat: function(format){ // IE treats \0 as a string end in inputs (truncating the value), // so it's a bad format delimiter, anyway var separators = format.replace(this.validParts, '\0').split('\0'), parts = format.match(this.validParts); - if (!separators || !separators.length || !parts || parts.length == 0){ + if (!separators || !separators.length || !parts || parts.length === 0){ throw new Error("Invalid date format."); } return {separators: separators, parts: parts}; }, - parseDate: function(date, format) { - if (date instanceof Date) return date; - if (/^[-+]\d+[dmwy]([\s,]+[-+]\d+[dmwy])*$/.test(date)) { - var part_re = /([-+]\d+)([dmwy])/, - parts = date.match(/([-+]\d+)([dmwy])/g), - part, dir; + parseDate: function(date, format, language){ + if (!date) + return undefined; + if (date instanceof Date) + return date; + if (typeof format === 'string') + format = DPGlobal.parseFormat(format); + var part_re = /([\-+]\d+)([dmwy])/, + parts = date.match(/([\-+]\d+)([dmwy])/g), + part, dir, i; + if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)){ date = new Date(); - for (var i=0; i'+ ''+ - ''+ - ''+ - ''+ + '«'+ + ''+ + '»'+ ''+ '', - contTemplate: '' + contTemplate: '', + footTemplate: ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + '' }; - DPGlobal.template = '