From b8031b68bd1488e7f27e758d82bbfe765fc093db Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Mon, 7 May 2012 14:50:25 -0700 Subject: [PATCH] Fixing DateTime localization issues 1- Some DateTime editors were not using the expected localization formats 2- The DateTime picker was not correctly initialized with localized formats 3- The TimePicker could not handle different am/pm designators 4- The drivers could not accept different designators Now the am/pm designators used to parse the time and to render it are the one from WorkContext.CurrentCulture, which means that is zn-TW is used as the default culture, times will be displayed with "chinese chars here" instead of AM, and will expect it when parsing it. --HG-- branch : 1.4.x --- .hgsubstate | 2 +- Orchard.proj | 1 + .../Common/DateEditor/DateEditorDriver.cs | 7 +- .../Common/Styles/orchard-common-datetime.css | 6 + .../Views/Parts.Common.Date.Edit.cshtml | 8 +- src/Orchard.Web/Core/Shapes/DateTimeShapes.cs | 6 +- .../Drivers/ArchiveLaterPartDriver.cs | 11 +- .../Styles/orchard-archivelater-datetime.css | 4 +- .../EditorTemplates/Parts/ArchiveLater.cshtml | 6 +- .../Drivers/PublishLaterPartDriver.cs | 6 +- .../Styles/orchard-publishlater-datetime.css | 4 +- .../EditorTemplates/Parts/PublishLater.cshtml | 6 +- .../Orchard.jQuery/Orchard.jQuery.csproj | 5 +- .../Orchard.jQuery/ResourceManifest.cs | 8 +- .../Scripts/jquery.ui.timepicker.js | 1324 +++++++++++++++++ .../Scripts/jquery.ui.timepicker.min.js | 1 + .../Orchard.jQuery/Scripts/ui.timepickr.js | 420 ------ .../Orchard.jQuery/Styles/ui.timepicker.css | 7 + .../Orchard.jQuery/Styles/ui.timepickr.css | 50 - .../Views/DatePickerLocalization.cshtml | 26 + 20 files changed, 1404 insertions(+), 504 deletions(-) create mode 100644 src/Orchard.Web/Modules/Orchard.jQuery/Scripts/jquery.ui.timepicker.js create mode 100644 src/Orchard.Web/Modules/Orchard.jQuery/Scripts/jquery.ui.timepicker.min.js delete mode 100644 src/Orchard.Web/Modules/Orchard.jQuery/Scripts/ui.timepickr.js create mode 100644 src/Orchard.Web/Modules/Orchard.jQuery/Styles/ui.timepicker.css delete mode 100644 src/Orchard.Web/Modules/Orchard.jQuery/Styles/ui.timepickr.css diff --git a/.hgsubstate b/.hgsubstate index e65a39ddd..7df13f6cd 100644 --- a/.hgsubstate +++ b/.hgsubstate @@ -5,4 +5,4 @@ cfc264d41c1d299c104d76c5032d8ffee3047dcd src/Orchard.Web/Modules/Orchard.Rules 0b96793c3737e50863270c24debf6d44065b73bb src/Orchard.Web/Modules/Orchard.Tokens 4ed51e0e76c2aacc2de90ce9984fd00cfdfae2ce src/orchard.web/Modules/Orchard.Alias 0e660c8cfbfb5217abfb7cf6adf6f2cf484174d6 src/orchard.web/Modules/Orchard.Projections -0ce46c4e39390472b82459007c0e5a05c18d22d8 src/orchard.web/modules/Orchard.Fields +bbd96451a4e80b4564dbb23ab947711feed3b5d2 src/orchard.web/modules/Orchard.Fields diff --git a/Orchard.proj b/Orchard.proj index 5ed274adb..7bea6b213 100644 --- a/Orchard.proj +++ b/Orchard.proj @@ -312,6 +312,7 @@ $(MSBuildProjectDirectory)\**\*.user; $(MSBuildProjectDirectory)\**\*.patch; $(MSBuildProjectDirectory)\**\*.hg*\**\*; + $(LibFolder)\nunit\addins\**\*; " /> diff --git a/src/Orchard.Web/Core/Common/DateEditor/DateEditorDriver.cs b/src/Orchard.Web/Core/Common/DateEditor/DateEditorDriver.cs index c4421ccc6..248050db9 100644 --- a/src/Orchard.Web/Core/Common/DateEditor/DateEditorDriver.cs +++ b/src/Orchard.Web/Core/Common/DateEditor/DateEditorDriver.cs @@ -9,7 +9,6 @@ using Orchard.Localization; namespace Orchard.Core.Common.DateEditor { public class DateEditorDriver : ContentPartDriver { private readonly IDateTimeLocalization _dateTimeLocalization; - private readonly Lazy _cultureInfo; public DateEditorDriver( @@ -62,8 +61,8 @@ namespace Orchard.Core.Common.DateEditor { // date and time are formatted using the same patterns as DateTimePicker is, preventing other cultures issues var createdLocal = TimeZoneInfo.ConvertTimeFromUtc(part.CreatedUtc.Value, Services.WorkContext.CurrentTimeZone); - model.CreatedDate = createdLocal.ToString(_dateTimeLocalization.ShortDateFormat.Text); - model.CreatedTime = createdLocal.ToString(_dateTimeLocalization.ShortTimeFormat.Text); + model.CreatedDate = createdLocal.ToString(_dateTimeLocalization.ShortDateFormat.Text, _cultureInfo.Value); + model.CreatedTime = createdLocal.ToString(_dateTimeLocalization.ShortTimeFormat.Text, _cultureInfo.Value); } } @@ -77,7 +76,7 @@ namespace Orchard.Core.Common.DateEditor { var dateTimeFormat = _dateTimeLocalization.ShortDateFormat + " " + _dateTimeLocalization.ShortTimeFormat; // use current culture - if (DateTime.TryParseExact(parseDateTime, dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out createdUtc)) { + if (DateTime.TryParseExact(parseDateTime, dateTimeFormat, _cultureInfo.Value, DateTimeStyles.None, out createdUtc)) { // the date time is entered locally for the configured timezone part.CreatedUtc = TimeZoneInfo.ConvertTimeToUtc(createdUtc, Services.WorkContext.CurrentTimeZone); diff --git a/src/Orchard.Web/Core/Common/Styles/orchard-common-datetime.css b/src/Orchard.Web/Core/Common/Styles/orchard-common-datetime.css index e31c85fe3..8f3f55d48 100644 --- a/src/Orchard.Web/Core/Common/Styles/orchard-common-datetime.css +++ b/src/Orchard.Web/Core/Common/Styles/orchard-common-datetime.css @@ -11,4 +11,10 @@ fieldset.createdutc-datetime input { padding:1px; text-align:center; color:#666; +} +input#DateEditor_CreatedDate { + width:10em; +} +input#DateEditor_CreatedTime { + width:7em; } \ No newline at end of file diff --git a/src/Orchard.Web/Core/Common/Views/Parts.Common.Date.Edit.cshtml b/src/Orchard.Web/Core/Common/Views/Parts.Common.Date.Edit.cshtml index ba2036213..1ae70c628 100644 --- a/src/Orchard.Web/Core/Common/Views/Parts.Common.Date.Edit.cshtml +++ b/src/Orchard.Web/Core/Common/Views/Parts.Common.Date.Edit.cshtml @@ -1,11 +1,11 @@ @model Orchard.Core.Common.DateEditor.DateEditorViewModel @{ var DateEditor = Model; - Script.Require("jQueryUtils_TimePicker"); Script.Require("jQueryUI_DatePicker"); + Script.Require("jQueryUI_TimePicker"); Style.Require("Common_DatePicker"); - Style.Require("jQueryUtils_TimePicker"); - Style.Require("jQueryUI_DatePicker"); + Style.Require("jQueryUI_DatePicker"); + Style.Require("jQueryUI_TimePicker"); }
@Html.LabelFor(m => DateEditor.CreatedDate, T("Created On")) @@ -38,7 +38,7 @@ } }); $('#@Html.FieldIdFor(m => DateEditor.CreatedDate)').datepicker({ showAnim: "" }); - $('#@Html.FieldIdFor(m => DateEditor.CreatedTime)').timepickr({ showAnim: "" }); + $('#@Html.FieldIdFor(m => DateEditor.CreatedTime)').timepicker({ stepMinute: 5}); }) //]]> diff --git a/src/Orchard.Web/Core/Shapes/DateTimeShapes.cs b/src/Orchard.Web/Core/Shapes/DateTimeShapes.cs index 4bf66e24b..46458de00 100644 --- a/src/Orchard.Web/Core/Shapes/DateTimeShapes.cs +++ b/src/Orchard.Web/Core/Shapes/DateTimeShapes.cs @@ -6,12 +6,14 @@ using Orchard.DisplayManagement; using Orchard.Localization; using Orchard.Mvc.Html; using Orchard.Services; +using System.Globalization; namespace Orchard.Core.Shapes { public class DateTimeShapes : IDependency { private readonly IClock _clock; private readonly IDateTimeLocalization _dateTimeLocalization; private readonly IWorkContextAccessor _workContextAccessor; + private readonly Lazy _cultureInfo; public DateTimeShapes( IClock clock, @@ -22,6 +24,8 @@ namespace Orchard.Core.Shapes { _dateTimeLocalization = dateTimeLocalization; _workContextAccessor = workContextAccessor; T = NullLocalizer.Instance; + + _cultureInfo = new Lazy(() => CultureInfo.GetCultureInfo(_workContextAccessor.GetContext().CurrentCulture)); } public Localizer T { get; set; } @@ -52,7 +56,7 @@ namespace Orchard.Core.Shapes { return DateTime(DateTimeUtc, _dateTimeLocalization.LongDateTimeFormat); } - return new MvcHtmlString(ConvertToDisplayTime(DateTimeUtc).ToString(CustomFormat.Text)); + return new MvcHtmlString(ConvertToDisplayTime(DateTimeUtc).ToString(CustomFormat.Text, _cultureInfo.Value)); } /// diff --git a/src/Orchard.Web/Modules/Orchard.ArchiveLater/Drivers/ArchiveLaterPartDriver.cs b/src/Orchard.Web/Modules/Orchard.ArchiveLater/Drivers/ArchiveLaterPartDriver.cs index a59609544..f5e889ed4 100644 --- a/src/Orchard.Web/Modules/Orchard.ArchiveLater/Drivers/ArchiveLaterPartDriver.cs +++ b/src/Orchard.Web/Modules/Orchard.ArchiveLater/Drivers/ArchiveLaterPartDriver.cs @@ -15,6 +15,7 @@ namespace Orchard.ArchiveLater.Drivers { private const string TemplateName = "Parts/ArchiveLater"; private readonly IArchiveLaterService _archiveLaterService; private readonly IDateTimeLocalization _dateTimeLocalization; + private readonly Lazy _cultureInfo; public ArchiveLaterPartDriver( IOrchardServices services, @@ -24,6 +25,10 @@ namespace Orchard.ArchiveLater.Drivers { _dateTimeLocalization = dateTimeLocalization; T = NullLocalizer.Instance; Services = services; + + // initializing the culture info lazy initializer + _cultureInfo = new Lazy(() => CultureInfo.GetCultureInfo(Services.WorkContext.CurrentCulture)); + } public Localizer T { get; set; } @@ -46,8 +51,8 @@ namespace Orchard.ArchiveLater.Drivers { var model = new ArchiveLaterViewModel(part) { ScheduledArchiveUtc = part.ScheduledArchiveUtc.Value, ArchiveLater = part.ScheduledArchiveUtc.Value.HasValue, - ScheduledArchiveDate = part.ScheduledArchiveUtc.Value.HasValue ? localDate.Value.ToString(_dateTimeLocalization.ShortDateFormat.Text) : String.Empty, - ScheduledArchiveTime = part.ScheduledArchiveUtc.Value.HasValue ? localDate.Value.ToString(_dateTimeLocalization.ShortTimeFormat.Text) : String.Empty + ScheduledArchiveDate = part.ScheduledArchiveUtc.Value.HasValue ? localDate.Value.ToString(_dateTimeLocalization.ShortDateFormat.Text, _cultureInfo.Value) : String.Empty, + ScheduledArchiveTime = part.ScheduledArchiveUtc.Value.HasValue ? localDate.Value.ToString(_dateTimeLocalization.ShortTimeFormat.Text, _cultureInfo.Value) : String.Empty }; return ContentShape("Parts_ArchiveLater_Edit", @@ -64,7 +69,7 @@ namespace Orchard.ArchiveLater.Drivers { var dateTimeFormat = _dateTimeLocalization.ShortDateFormat + " " + _dateTimeLocalization.ShortTimeFormat; // use an english culture as it is the one used by jQuery.datepicker by default - if (DateTime.TryParseExact(parseDateTime, dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out scheduled)) { + if (DateTime.TryParseExact(parseDateTime, dateTimeFormat, _cultureInfo.Value, DateTimeStyles.None, out scheduled)) { // the date time is entered locally for the configured timezone var timeZone = Services.WorkContext.CurrentTimeZone; diff --git a/src/Orchard.Web/Modules/Orchard.ArchiveLater/Styles/orchard-archivelater-datetime.css b/src/Orchard.Web/Modules/Orchard.ArchiveLater/Styles/orchard-archivelater-datetime.css index 16c497a79..fde0a474a 100644 --- a/src/Orchard.Web/Modules/Orchard.ArchiveLater/Styles/orchard-archivelater-datetime.css +++ b/src/Orchard.Web/Modules/Orchard.ArchiveLater/Styles/orchard-archivelater-datetime.css @@ -7,10 +7,10 @@ html.dyn input.hinted { font-style:italic; } input#ArchiveLater_ScheduledArchiveDate { - width:49%; + width:10em; } input#ArchiveLater_ScheduledArchiveTime { - width:43%; + width:7em; } .edit-item-sidebar fieldset.archive-later-datetime { diff --git a/src/Orchard.Web/Modules/Orchard.ArchiveLater/Views/EditorTemplates/Parts/ArchiveLater.cshtml b/src/Orchard.Web/Modules/Orchard.ArchiveLater/Views/EditorTemplates/Parts/ArchiveLater.cshtml index e1a0392aa..950e48d9f 100644 --- a/src/Orchard.Web/Modules/Orchard.ArchiveLater/Views/EditorTemplates/Parts/ArchiveLater.cshtml +++ b/src/Orchard.Web/Modules/Orchard.ArchiveLater/Views/EditorTemplates/Parts/ArchiveLater.cshtml @@ -1,11 +1,11 @@ @model Orchard.ArchiveLater.ViewModels.ArchiveLaterViewModel @using System.Web.Mvc.Html; @{ - Style.Require("jQueryUtils_TimePicker"); Style.Require("jQueryUI_DatePicker"); + Style.Require("jQueryUI_TimePicker"); Style.Require("ArchiveLater_DatePicker"); Script.Require("jQueryUI_DatePicker"); - Script.Require("jQueryUtils_TimePicker"); + Script.Require("jQueryUI_TimePicker"); }
@@ -42,7 +42,7 @@ } }); $('#@ViewData.TemplateInfo.GetFullHtmlFieldId("ScheduledArchiveDate")').datepicker({ showAnim: "" }).focus(function () { $('#@ViewData.TemplateInfo.GetFullHtmlFieldId("Command_ArchiveLater")').attr("checked", "checked") }); - $('#@ViewData.TemplateInfo.GetFullHtmlFieldId("ScheduledArchiveTime")').timepickr().focus(function () { $('#@ViewData.TemplateInfo.GetFullHtmlFieldId("Command_ArchiveLater")').attr("checked", "checked") }); + $('#@ViewData.TemplateInfo.GetFullHtmlFieldId("ScheduledArchiveTime")').timepicker({ stepMinute: 5 }).focus(function () { $('#@ViewData.TemplateInfo.GetFullHtmlFieldId("Command_ArchiveLater")').attr("checked", "checked") }); }) //]]> diff --git a/src/Orchard.Web/Modules/Orchard.PublishLater/Drivers/PublishLaterPartDriver.cs b/src/Orchard.Web/Modules/Orchard.PublishLater/Drivers/PublishLaterPartDriver.cs index 0f5e2750b..a118d493d 100644 --- a/src/Orchard.Web/Modules/Orchard.PublishLater/Drivers/PublishLaterPartDriver.cs +++ b/src/Orchard.Web/Modules/Orchard.PublishLater/Drivers/PublishLaterPartDriver.cs @@ -62,8 +62,8 @@ namespace Orchard.PublishLater.Drivers { var localDate = new Lazy( () => TimeZoneInfo.ConvertTimeFromUtc(part.ScheduledPublishUtc.Value.Value, Services.WorkContext.CurrentTimeZone)); var model = new PublishLaterViewModel(part) { ScheduledPublishUtc = part.ScheduledPublishUtc.Value, - ScheduledPublishDate = part.ScheduledPublishUtc.Value.HasValue && !part.IsPublished() ? localDate.Value.ToString(_dateTimeLocalization.ShortDateFormat.Text) : String.Empty, - ScheduledPublishTime = part.ScheduledPublishUtc.Value.HasValue && !part.IsPublished() ? localDate.Value.ToString(_dateTimeLocalization.ShortTimeFormat.Text) : String.Empty, + ScheduledPublishDate = part.ScheduledPublishUtc.Value.HasValue && !part.IsPublished() ? localDate.Value.ToString(_dateTimeLocalization.ShortDateFormat.Text, _cultureInfo.Value) : String.Empty, + ScheduledPublishTime = part.ScheduledPublishUtc.Value.HasValue && !part.IsPublished() ? localDate.Value.ToString(_dateTimeLocalization.ShortTimeFormat.Text, _cultureInfo.Value) : String.Empty, }; return ContentShape("Parts_PublishLater_Edit", @@ -83,7 +83,7 @@ namespace Orchard.PublishLater.Drivers { var dateTimeFormat = _dateTimeLocalization.ShortDateFormat + " " + _dateTimeLocalization.ShortTimeFormat; // use current culture - if (DateTime.TryParseExact(parseDateTime, dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out scheduled)) { + if (DateTime.TryParseExact(parseDateTime, dateTimeFormat, _cultureInfo.Value, DateTimeStyles.None, out scheduled)) { // the date time is entered locally for the configured timezone var timeZone = Services.WorkContext.CurrentTimeZone; diff --git a/src/Orchard.Web/Modules/Orchard.PublishLater/Styles/orchard-publishlater-datetime.css b/src/Orchard.Web/Modules/Orchard.PublishLater/Styles/orchard-publishlater-datetime.css index 6756b7e83..6c70b12f7 100644 --- a/src/Orchard.Web/Modules/Orchard.PublishLater/Styles/orchard-publishlater-datetime.css +++ b/src/Orchard.Web/Modules/Orchard.PublishLater/Styles/orchard-publishlater-datetime.css @@ -23,9 +23,9 @@ fieldset.publish-later-datetime input { } input#PublishLater_ScheduledPublishDate { - width:39%; + width:10em; margin:0 0 0 12px; } input#PublishLater_ScheduledPublishTime { - width:33%; + width:7em; } diff --git a/src/Orchard.Web/Modules/Orchard.PublishLater/Views/EditorTemplates/Parts/PublishLater.cshtml b/src/Orchard.Web/Modules/Orchard.PublishLater/Views/EditorTemplates/Parts/PublishLater.cshtml index bd57650a1..6ee79bdcb 100644 --- a/src/Orchard.Web/Modules/Orchard.PublishLater/Views/EditorTemplates/Parts/PublishLater.cshtml +++ b/src/Orchard.Web/Modules/Orchard.PublishLater/Views/EditorTemplates/Parts/PublishLater.cshtml @@ -1,10 +1,10 @@ @model Orchard.PublishLater.ViewModels.PublishLaterViewModel @{ - Script.Require("jQueryUtils_TimePicker"); Script.Require("jQueryUI_DatePicker"); + Script.Require("jQueryUI_TimePicker"); Style.Require("PublishLater_DatePicker"); - Style.Require("jQueryUtils_TimePicker"); Style.Require("jQueryUI_DatePicker"); + Style.Require("jQueryUI_TimePicker"); }
@T("Publish") @@ -40,7 +40,7 @@ }); $('#@ViewData.TemplateInfo.GetFullHtmlFieldId("ScheduledPublishDate")').datepicker({ showAnim: ""}).focus(function () { $('#@ViewData.TemplateInfo.GetFullHtmlFieldId("Command_PublishLater")').attr("checked", "checked") }); - $('#@ViewData.TemplateInfo.GetFullHtmlFieldId("ScheduledPublishTime")').timepickr().focus(function () { $('#@ViewData.TemplateInfo.GetFullHtmlFieldId("Command_PublishLater")').attr("checked", "checked") }); + $('#@ViewData.TemplateInfo.GetFullHtmlFieldId("ScheduledPublishTime")').timepicker({ stepMinute: 5}).focus(function () { $('#@ViewData.TemplateInfo.GetFullHtmlFieldId("Command_PublishLater")').attr("checked", "checked") }); }) //]]> diff --git a/src/Orchard.Web/Modules/Orchard.jQuery/Orchard.jQuery.csproj b/src/Orchard.Web/Modules/Orchard.jQuery/Orchard.jQuery.csproj index 7ebc0d351..ef476a22a 100644 --- a/src/Orchard.Web/Modules/Orchard.jQuery/Orchard.jQuery.csproj +++ b/src/Orchard.Web/Modules/Orchard.jQuery/Orchard.jQuery.csproj @@ -47,6 +47,7 @@ + @@ -114,7 +115,7 @@ - + @@ -130,7 +131,7 @@ - + diff --git a/src/Orchard.Web/Modules/Orchard.jQuery/ResourceManifest.cs b/src/Orchard.Web/Modules/Orchard.jQuery/ResourceManifest.cs index 7f7b683bb..7b1fe422c 100644 --- a/src/Orchard.Web/Modules/Orchard.jQuery/ResourceManifest.cs +++ b/src/Orchard.Web/Modules/Orchard.jQuery/ResourceManifest.cs @@ -48,15 +48,11 @@ namespace Orchard.jQuery { manifest.DefineScript("jQueryEffects_Slide").SetUrl("jquery.effects.slide.min.js", "jquery.effects.slide.js").SetVersion("1.8.18").SetDependencies("jQueryEffects_Core"); manifest.DefineScript("jQueryEffects_Transfer").SetUrl("jquery.effects.transfer.min.js", "jquery.effects.transfer.js").SetVersion("1.8.18").SetDependencies("jQueryEffects_Core"); - - // Utils - manifest.DefineScript("jQueryUtils").SetUrl("jquery.utils.js").SetDependencies("jQuery"); - manifest.DefineScript("jQueryUtils_TimePicker").SetUrl("ui.timepickr.js").SetVersion("0.7.0a").SetDependencies("jQueryUtils", "jQueryUI_Core", "jQueryUI_Widget"); - manifest.DefineStyle("jQueryUI_Orchard").SetUrl("jquery-ui-1.8.18.custom.css").SetVersion("1.8.18"); manifest.DefineStyle("jQueryUI_DatePicker").SetUrl("ui.datepicker.css").SetDependencies("jQueryUI_Orchard").SetVersion("1.7.2"); - manifest.DefineStyle("jQueryUtils_TimePicker").SetUrl("ui.timepickr.css"); + manifest.DefineScript("jQueryUI_TimePicker").SetUrl("jquery.ui.timepicker.min.js", "jquery.ui.timepicker.js").SetVersion("1.0.0").SetDependencies("jQueryUI_Slider"); + manifest.DefineStyle("jQueryUI_TimePicker").SetUrl("ui.timepicker.css").SetDependencies("jQueryUI_Orchard").SetVersion("1.0.0"); } } } diff --git a/src/Orchard.Web/Modules/Orchard.jQuery/Scripts/jquery.ui.timepicker.js b/src/Orchard.Web/Modules/Orchard.jQuery/Scripts/jquery.ui.timepicker.js new file mode 100644 index 000000000..5e4466ca2 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.jQuery/Scripts/jquery.ui.timepicker.js @@ -0,0 +1,1324 @@ +/* +* jQuery timepicker addon +* By: Trent Richardson [http://trentrichardson.com] +* Version 1.0.0 +* Last Modified: 02/05/2012 +* +* Copyright 2012 Trent Richardson +* Dual licensed under the MIT and GPL licenses. +* http://trentrichardson.com/Impromptu/GPL-LICENSE.txt +* http://trentrichardson.com/Impromptu/MIT-LICENSE.txt +* +* HERES THE CSS: +* .ui-timepicker-div .ui-widget-header { margin-bottom: 8px; } +* .ui-timepicker-div dl { text-align: left; } +* .ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; } +* .ui-timepicker-div dl dd { margin: 0 10px 10px 65px; } +* .ui-timepicker-div td { font-size: 90%; } +* .ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; } +*/ + +(function ($) { + + // Prevent "Uncaught RangeError: Maximum call stack size exceeded" + $.ui.timepicker = $.ui.timepicker || {}; + if ($.ui.timepicker.version) { + return; + } + + $.extend($.ui, { timepicker: { version: "1.0.0"} }); + + /* Time picker manager. + Use the singleton instance of this class, $.timepicker, to interact with the time picker. + Settings for (groups of) time pickers are maintained in an instance object, + allowing multiple different settings on the same page. */ + + function Timepicker() { + this.regional = []; // Available regional settings, indexed by language code + this.regional[''] = { // Default regional settings + currentText: 'Now', + closeText: 'Done', + ampm: false, + amNames: ['AM', 'A'], + pmNames: ['PM', 'P'], + timeFormat: 'hh:mm tt', + timeSuffix: '', + timeOnlyTitle: 'Choose Time', + timeText: 'Time', + hourText: 'Hour', + minuteText: 'Minute', + secondText: 'Second', + millisecText: 'Millisecond', + timezoneText: 'Time Zone' + }; + this._defaults = { // Global defaults for all the datetime picker instances + showButtonPanel: true, + timeOnly: false, + showHour: true, + showMinute: true, + showSecond: false, + showMillisec: false, + showTimezone: false, + showTime: true, + stepHour: 1, + stepMinute: 1, + stepSecond: 1, + stepMillisec: 1, + hour: 0, + minute: 0, + second: 0, + millisec: 0, + timezone: '+0000', + hourMin: 0, + minuteMin: 0, + secondMin: 0, + millisecMin: 0, + hourMax: 23, + minuteMax: 59, + secondMax: 59, + millisecMax: 999, + minDateTime: null, + maxDateTime: null, + onSelect: null, + hourGrid: 0, + minuteGrid: 0, + secondGrid: 0, + millisecGrid: 0, + alwaysSetTime: true, + separator: ' ', + altFieldTimeOnly: true, + showTimepicker: true, + timezoneIso8609: false, + timezoneList: null, + addSliderAccess: false, + sliderAccessArgs: null + }; + $.extend(this._defaults, this.regional['']); + }; + + $.extend(Timepicker.prototype, { + $input: null, + $altInput: null, + $timeObj: null, + inst: null, + hour_slider: null, + minute_slider: null, + second_slider: null, + millisec_slider: null, + timezone_select: null, + hour: 0, + minute: 0, + second: 0, + millisec: 0, + timezone: '+0000', + hourMinOriginal: null, + minuteMinOriginal: null, + secondMinOriginal: null, + millisecMinOriginal: null, + hourMaxOriginal: null, + minuteMaxOriginal: null, + secondMaxOriginal: null, + millisecMaxOriginal: null, + ampm: '', + formattedDate: '', + formattedTime: '', + formattedDateTime: '', + timezoneList: null, + + /* Override the default settings for all instances of the time picker. + @param settings object - the new settings to use as defaults (anonymous object) + @return the manager object */ + setDefaults: function (settings) { + extendRemove(this._defaults, settings || {}); + return this; + }, + + //######################################################################## + // Create a new Timepicker instance + //######################################################################## + _newInst: function ($input, o) { + var tp_inst = new Timepicker(), + inlineSettings = {}; + + for (var attrName in this._defaults) { + var attrValue = $input.attr('time:' + attrName); + if (attrValue) { + try { + inlineSettings[attrName] = eval(attrValue); + } catch (err) { + inlineSettings[attrName] = attrValue; + } + } + } + tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, o, { + beforeShow: function (input, dp_inst) { + if ($.isFunction(o.beforeShow)) + return o.beforeShow(input, dp_inst, tp_inst); + }, + onChangeMonthYear: function (year, month, dp_inst) { + // Update the time as well : this prevents the time from disappearing from the $input field. + tp_inst._updateDateTime(dp_inst); + if ($.isFunction(o.onChangeMonthYear)) + o.onChangeMonthYear.call($input[0], year, month, dp_inst, tp_inst); + }, + onClose: function (dateText, dp_inst) { + if (tp_inst.timeDefined === true && $input.val() != '') + tp_inst._updateDateTime(dp_inst); + if ($.isFunction(o.onClose)) + o.onClose.call($input[0], dateText, dp_inst, tp_inst); + }, + timepicker: tp_inst // add timepicker as a property of datepicker: $.datepicker._get(dp_inst, 'timepicker'); + }); + tp_inst.amNames = $.map(tp_inst._defaults.amNames, function (val) { return val.toUpperCase(); }); + tp_inst.pmNames = $.map(tp_inst._defaults.pmNames, function (val) { return val.toUpperCase(); }); + + if (tp_inst._defaults.timezoneList === null) { + var timezoneList = []; + for (var i = -11; i <= 12; i++) + timezoneList.push((i >= 0 ? '+' : '-') + ('0' + Math.abs(i).toString()).slice(-2) + '00'); + if (tp_inst._defaults.timezoneIso8609) + timezoneList = $.map(timezoneList, function (val) { + return val == '+0000' ? 'Z' : (val.substring(0, 3) + ':' + val.substring(3)); + }); + tp_inst._defaults.timezoneList = timezoneList; + } + + tp_inst.hour = tp_inst._defaults.hour; + tp_inst.minute = tp_inst._defaults.minute; + tp_inst.second = tp_inst._defaults.second; + tp_inst.millisec = tp_inst._defaults.millisec; + tp_inst.ampm = ''; + tp_inst.$input = $input; + + if (o.altField) + tp_inst.$altInput = $(o.altField) + .css({ cursor: 'pointer' }) + .focus(function () { $input.trigger("focus"); }); + + if (tp_inst._defaults.minDate == 0 || tp_inst._defaults.minDateTime == 0) { + tp_inst._defaults.minDate = new Date(); + } + if (tp_inst._defaults.maxDate == 0 || tp_inst._defaults.maxDateTime == 0) { + tp_inst._defaults.maxDate = new Date(); + } + + // datepicker needs minDate/maxDate, timepicker needs minDateTime/maxDateTime.. + if (tp_inst._defaults.minDate !== undefined && tp_inst._defaults.minDate instanceof Date) + tp_inst._defaults.minDateTime = new Date(tp_inst._defaults.minDate.getTime()); + if (tp_inst._defaults.minDateTime !== undefined && tp_inst._defaults.minDateTime instanceof Date) + tp_inst._defaults.minDate = new Date(tp_inst._defaults.minDateTime.getTime()); + if (tp_inst._defaults.maxDate !== undefined && tp_inst._defaults.maxDate instanceof Date) + tp_inst._defaults.maxDateTime = new Date(tp_inst._defaults.maxDate.getTime()); + if (tp_inst._defaults.maxDateTime !== undefined && tp_inst._defaults.maxDateTime instanceof Date) + tp_inst._defaults.maxDate = new Date(tp_inst._defaults.maxDateTime.getTime()); + return tp_inst; + }, + + //######################################################################## + // add our sliders to the calendar + //######################################################################## + _addTimePicker: function (dp_inst) { + var currDT = (this.$altInput && this._defaults.altFieldTimeOnly) ? + this.$input.val() + ' ' + this.$altInput.val() : + this.$input.val(); + + this.timeDefined = this._parseTime(currDT); + this._limitMinMaxDateTime(dp_inst, false); + this._injectTimePicker(); + }, + + //######################################################################## + // parse the time string from input value or _setTime + //######################################################################## + _parseTime: function (timeString, withDate) { + var regstr = this._defaults.timeFormat.toString() + .replace(/h{1,2}/ig, '(\\d?\\d)') + .replace(/m{1,2}/ig, '(\\d?\\d)') + .replace(/s{1,2}/ig, '(\\d?\\d)') + .replace(/l{1}/ig, '(\\d?\\d?\\d)') + .replace(/t{1,2}/ig, this._getPatternAmpm()) + .replace(/z{1}/ig, '(z|[-+]\\d\\d:?\\d\\d)?') + .replace(/\s/g, '\\s?') + this._defaults.timeSuffix + '$', + order = this._getFormatPositions(), + ampm = '', + treg; + + if (!this.inst) this.inst = $.datepicker._getInst(this.$input[0]); + + if (withDate || !this._defaults.timeOnly) { + // the time should come after x number of characters and a space. + // x = at least the length of text specified by the date format + var dp_dateFormat = $.datepicker._get(this.inst, 'dateFormat'); + // escape special regex characters in the seperator + var specials = new RegExp("[.*+?|()\\[\\]{}\\\\]", "g"); + regstr = '^.{' + dp_dateFormat.length + ',}?' + this._defaults.separator.replace(specials, "\\$&") + regstr; + } + + treg = timeString.match(new RegExp(regstr, 'i')); + + if (treg) { + if (order.t !== -1) { + if (treg[order.t] === undefined || treg[order.t].length === 0) { + ampm = ''; + this.ampm = ''; + } else { + ampm = $.inArray(treg[order.t].toUpperCase(), this.amNames) !== -1 ? 'AM' : 'PM'; + this.ampm = this._defaults[ampm == 'AM' ? 'amNames' : 'pmNames'][0]; + } + } + + if (order.h !== -1) { + if (ampm == 'AM' && treg[order.h] == '12') + this.hour = 0; // 12am = 0 hour + else if (ampm == 'PM' && treg[order.h] != '12') + this.hour = (parseFloat(treg[order.h]) + 12).toFixed(0); // 12pm = 12 hour, any other pm = hour + 12 + else this.hour = Number(treg[order.h]); + } + + if (order.m !== -1) this.minute = Number(treg[order.m]); + if (order.s !== -1) this.second = Number(treg[order.s]); + if (order.l !== -1) this.millisec = Number(treg[order.l]); + if (order.z !== -1 && treg[order.z] !== undefined) { + var tz = treg[order.z].toUpperCase(); + switch (tz.length) { + case 1: // Z + tz = this._defaults.timezoneIso8609 ? 'Z' : '+0000'; + break; + case 5: // +hhmm + if (this._defaults.timezoneIso8609) + tz = tz.substring(1) == '0000' + ? 'Z' + : tz.substring(0, 3) + ':' + tz.substring(3); + break; + case 6: // +hh:mm + if (!this._defaults.timezoneIso8609) + tz = tz == 'Z' || tz.substring(1) == '00:00' + ? '+0000' + : tz.replace(/:/, ''); + else if (tz.substring(1) == '00:00') + tz = 'Z'; + break; + } + this.timezone = tz; + } + + return true; + + } + return false; + }, + + //######################################################################## + // pattern for standard and localized AM/PM markers + //######################################################################## + _getPatternAmpm: function () { + var markers = [], + o = this._defaults; + if (o.amNames) + $.merge(markers, o.amNames); + if (o.pmNames) + $.merge(markers, o.pmNames); + markers = $.map(markers, function (val) { return val.replace(/[.*+?|()\[\]{}\\]/g, '\\$&'); }); + return '(' + markers.join('|') + ')?'; + }, + + //######################################################################## + // figure out position of time elements.. cause js cant do named captures + //######################################################################## + _getFormatPositions: function () { + var finds = this._defaults.timeFormat.toLowerCase().match(/(h{1,2}|m{1,2}|s{1,2}|l{1}|t{1,2}|z)/g), + orders = { h: -1, m: -1, s: -1, l: -1, t: -1, z: -1 }; + + if (finds) + for (var i = 0; i < finds.length; i++) + if (orders[finds[i].toString().charAt(0)] == -1) + orders[finds[i].toString().charAt(0)] = i + 1; + + return orders; + }, + + //######################################################################## + // generate and inject html for timepicker into ui datepicker + //######################################################################## + _injectTimePicker: function () { + var $dp = this.inst.dpDiv, + o = this._defaults, + tp_inst = this, + // Added by Peter Medeiros: + // - Figure out what the hour/minute/second max should be based on the step values. + // - Example: if stepMinute is 15, then minMax is 45. + hourMax = parseInt((o.hourMax - ((o.hourMax - o.hourMin) % o.stepHour)), 10), + minMax = parseInt((o.minuteMax - ((o.minuteMax - o.minuteMin) % o.stepMinute)), 10), + secMax = parseInt((o.secondMax - ((o.secondMax - o.secondMin) % o.stepSecond)), 10), + millisecMax = parseInt((o.millisecMax - ((o.millisecMax - o.millisecMin) % o.stepMillisec)), 10), + dp_id = this.inst.id.toString().replace(/([^A-Za-z0-9_])/g, ''); + + // Prevent displaying twice + //if ($dp.find("div#ui-timepicker-div-"+ dp_id).length === 0) { + if ($dp.find("div#ui-timepicker-div-" + dp_id).length === 0 && o.showTimepicker) { + var noDisplay = ' style="display:none;"', + html = '
' + + '
' + o.timeText + '
' + + '
' + + '
' + o.hourText + '
', + hourGridSize = 0, + minuteGridSize = 0, + secondGridSize = 0, + millisecGridSize = 0, + size = null; + + // Hours + html += '
'; + if (o.showHour && o.hourGrid > 0) { + html += '
'; + + for (var h = o.hourMin; h <= hourMax; h += parseInt(o.hourGrid, 10)) { + hourGridSize++; + var tmph = (o.ampm && h > 12) ? h - 12 : h; + if (tmph < 10) tmph = '0' + tmph; + if (o.ampm) { + if (h == 0) tmph = 12 + 'a'; + else if (h < 12) tmph += 'a'; + else tmph += 'p'; + } + html += ''; + } + + html += '
' + tmph + '
'; + } + html += '
'; + + // Minutes + html += '
' + o.minuteText + '
' + + '
'; + + if (o.showMinute && o.minuteGrid > 0) { + html += '
'; + + for (var m = o.minuteMin; m <= minMax; m += parseInt(o.minuteGrid, 10)) { + minuteGridSize++; + html += ''; + } + + html += '
' + ((m < 10) ? '0' : '') + m + '
'; + } + html += '
'; + + // Seconds + html += '
' + o.secondText + '
' + + '
'; + + if (o.showSecond && o.secondGrid > 0) { + html += '
'; + + for (var s = o.secondMin; s <= secMax; s += parseInt(o.secondGrid, 10)) { + secondGridSize++; + html += ''; + } + + html += '
' + ((s < 10) ? '0' : '') + s + '
'; + } + html += '
'; + + // Milliseconds + html += '
' + o.millisecText + '
' + + '
'; + + if (o.showMillisec && o.millisecGrid > 0) { + html += '
'; + + for (var l = o.millisecMin; l <= millisecMax; l += parseInt(o.millisecGrid, 10)) { + millisecGridSize++; + html += ''; + } + + html += '
' + ((l < 10) ? '0' : '') + l + '
'; + } + html += '
'; + + // Timezone + html += '
' + o.timezoneText + '
'; + html += '
'; + + html += '
'; + $tp = $(html); + + // if we only want time picker... + if (o.timeOnly === true) { + $tp.prepend( + '
' + + '
' + o.timeOnlyTitle + '
' + + '
'); + $dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide(); + } + + this.hour_slider = $tp.find('#ui_tpicker_hour_' + dp_id).slider({ + orientation: "horizontal", + value: this.hour, + min: o.hourMin, + max: hourMax, + step: o.stepHour, + slide: function (event, ui) { + tp_inst.hour_slider.slider("option", "value", ui.value); + tp_inst._onTimeChange(); + } + }); + + + // Updated by Peter Medeiros: + // - Pass in Event and UI instance into slide function + this.minute_slider = $tp.find('#ui_tpicker_minute_' + dp_id).slider({ + orientation: "horizontal", + value: this.minute, + min: o.minuteMin, + max: minMax, + step: o.stepMinute, + slide: function (event, ui) { + tp_inst.minute_slider.slider("option", "value", ui.value); + tp_inst._onTimeChange(); + } + }); + + this.second_slider = $tp.find('#ui_tpicker_second_' + dp_id).slider({ + orientation: "horizontal", + value: this.second, + min: o.secondMin, + max: secMax, + step: o.stepSecond, + slide: function (event, ui) { + tp_inst.second_slider.slider("option", "value", ui.value); + tp_inst._onTimeChange(); + } + }); + + this.millisec_slider = $tp.find('#ui_tpicker_millisec_' + dp_id).slider({ + orientation: "horizontal", + value: this.millisec, + min: o.millisecMin, + max: millisecMax, + step: o.stepMillisec, + slide: function (event, ui) { + tp_inst.millisec_slider.slider("option", "value", ui.value); + tp_inst._onTimeChange(); + } + }); + + this.timezone_select = $tp.find('#ui_tpicker_timezone_' + dp_id).append('').find("select"); + $.fn.append.apply(this.timezone_select, + $.map(o.timezoneList, function (val, idx) { + return $("