From 56d471ffa2e694092da946682a28a8fcfc9c9497 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=E1bor=20Czigola?=
Date: Sun, 31 Oct 2010 19:56:09 +0100
Subject: [PATCH 1/3] Evolve datepicker to select time as well (a.k.a.
datetimepicker). Thanks to Addy Osmani
http://addyosmani.com/blog/the-missing-date-time-selector-for-jquery-ui/
---
demos/datepicker/datetime.html | 41 ++++
demos/datepicker/index.html | 1 +
ui/jquery.ui.datetimepicker.js | 414 +++++++++++++++++++++++++++++++++
3 files changed, 456 insertions(+)
create mode 100644 demos/datepicker/datetime.html
create mode 100644 ui/jquery.ui.datetimepicker.js
diff --git a/demos/datepicker/datetime.html b/demos/datepicker/datetime.html
new file mode 100644
index 00000000000..a232dcb9638
--- /dev/null
+++ b/demos/datepicker/datetime.html
@@ -0,0 +1,41 @@
+
+
+
+
+ jQuery UI Datepicker - Select date and time as well
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Display the datepicker embedded in the page instead of in an overlay. Simply call .datepicker() on a div instead of an input.
+
+
+
+
diff --git a/demos/datepicker/index.html b/demos/datepicker/index.html
index 37c8ef7148c..e50415225dd 100644
--- a/demos/datepicker/index.html
+++ b/demos/datepicker/index.html
@@ -13,6 +13,7 @@ Examples
Default functionality
Format date
Restrict date range
+ Select date and time as well
Localize calendar
Populate alternate field
Display inline
diff --git a/ui/jquery.ui.datetimepicker.js b/ui/jquery.ui.datetimepicker.js
new file mode 100644
index 00000000000..5bdf0e994c3
--- /dev/null
+++ b/ui/jquery.ui.datetimepicker.js
@@ -0,0 +1,414 @@
+/*!
+ * jQuery UI Timepicker 0.2.1
+ *
+ * Copyright (c) 2009 Martin Milesich (http://milesich.com/)
+ * http://addyosmani.com/blog/the-missing-date-time-selector-for-jquery-ui/
+ *
+ * Copyright (c) 2010 Gábor Czigola (http://gablog.eu)
+ *
+ *
+ * $Id: timepicker.js 28 2009-08-11 20:31:23Z majlo $
+ *
+ * Depends:
+ * ui.core.js
+ * ui.datepicker.js
+ * ui.slider.js
+ */
+(function($) {
+
+/**
+ * Extending default values
+ */
+$.extend($.datepicker._defaults, {
+ 'stepMinutes': 1, // Number of minutes to step up/down
+ 'stepHours': 1, // Number of hours to step up/down
+ 'time24h': false, // True if 24h time
+ 'showTime': false, // Show timepicker with datepicker
+ 'altTimeField': '' // Selector for an alternate field to store time into
+});
+
+/**
+ * _hideDatepicker must be called with null
+ */
+$.datepicker._connectDatepickerOverride = $.datepicker._connectDatepicker;
+$.datepicker._connectDatepicker = function(target, inst) {
+ $.datepicker._connectDatepickerOverride(target, inst);
+
+ // showButtonPanel is required with timepicker
+ if (this._get(inst, 'showTime')) {
+ inst.settings['showButtonPanel'] = true;
+ }
+
+ var showOn = this._get(inst, 'showOn');
+
+ if (showOn == 'button' || showOn == 'both') {
+ // Unbind all click events
+ inst.trigger.unbind('click');
+
+ // Bind new click event
+ inst.trigger.click(function() {
+ if ($.datepicker._datepickerShowing && $.datepicker._lastInput == target)
+ $.datepicker._hideDatepicker(null); // This override is all about the "null"
+ else
+ $.datepicker._showDatepicker(target);
+ return false;
+ });
+ }
+};
+
+/**
+ * Datepicker does not have an onShow event so I need to create it.
+ * What I actually doing here is copying original _showDatepicker
+ * method to _showDatepickerOverload method.
+ */
+$.datepicker._showDatepickerOverride = $.datepicker._showDatepicker;
+$.datepicker._showDatepicker = function (input) {
+ // Call the original method which will show the datepicker
+ $.datepicker._showDatepickerOverride(input);
+
+ input = input.target || input;
+
+ // find from button/image trigger
+ if (input.nodeName.toLowerCase() != 'input') input = $('input', input.parentNode)[0];
+
+ // Do not show timepicker if datepicker is disabled
+ if ($.datepicker._isDisabledDatepicker(input)) return;
+
+ // Get instance to datepicker
+ var inst = $.datepicker._getInst(input);
+
+ var showTime = $.datepicker._get(inst, 'showTime');
+
+ // If showTime = True show the timepicker
+ if (showTime) $.timepicker.show(input);
+};
+
+/**
+ * Same as above. Here I need to extend the _checkExternalClick method
+ * because I don't want to close the datepicker when the sliders get focus.
+ */
+$.datepicker._checkExternalClickOverride = $.datepicker._checkExternalClick;
+$.datepicker._checkExternalClick = function (event) {
+ if (!$.datepicker._curInst) return;
+ var $target = $(event.target);
+
+ if (($target.parents('#' + $.timepicker._mainDivId).length == 0)) {
+ $.datepicker._checkExternalClickOverride(event);
+ }
+};
+
+/**
+ * Datepicker has onHide event but I just want to make it simple for you
+ * so I hide the timepicker when datepicker hides.
+ */
+$.datepicker._hideDatepickerOverride = $.datepicker._hideDatepicker;
+$.datepicker._hideDatepicker = function(input, duration) {
+ // Some lines from the original method
+ var inst = this._curInst;
+
+ if (!inst || (input && inst != $.data(input, PROP_NAME))) return;
+
+ // Get the value of showTime property
+ var showTime = this._get(inst, 'showTime');
+
+ if (input === undefined && showTime) {
+ if (inst.input) {
+ inst.input.val(this._formatDate(inst));
+ inst.input.trigger('change'); // fire the change event
+ }
+
+ this._updateAlternate(inst);
+
+ if (showTime) $.timepicker.update(this._formatDate(inst));
+ }
+
+ // Hide datepicker
+ $.datepicker._hideDatepickerOverride(input, duration);
+
+ // Hide the timepicker if enabled
+ if (showTime) {
+ $.timepicker.hide();
+ }
+};
+
+/**
+ * This is a complete replacement of the _selectDate method.
+ * If showed with timepicker do not close when date is selected.
+ */
+$.datepicker._selectDate = function(id, dateStr) {
+ var target = $(id);
+ var inst = this._getInst(target[0]);
+ var showTime = this._get(inst, 'showTime');
+ dateStr = (dateStr != null ? dateStr : this._formatDate(inst));
+ if (!showTime) {
+ if (inst.input)
+ inst.input.val(dateStr);
+ this._updateAlternate(inst);
+ }
+ var onSelect = this._get(inst, 'onSelect');
+ if (onSelect)
+ onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); // trigger custom callback
+ else if (inst.input && !showTime)
+ inst.input.trigger('change'); // fire the change event
+ if (inst.inline)
+ this._updateDatepicker(inst);
+ else if (!inst.stayOpen) {
+ if (showTime) {
+ this._updateDatepicker(inst);
+ } else {
+ this._hideDatepicker(null, this._get(inst, 'duration'));
+ this._lastInput = inst.input[0];
+ if (typeof(inst.input[0]) != 'object')
+ inst.input[0].focus(); // restore focus
+ this._lastInput = null;
+ }
+ }
+};
+
+/**
+ * We need to resize the timepicker when the datepicker has been changed.
+ */
+$.datepicker._updateDatepickerOverride = $.datepicker._updateDatepicker;
+$.datepicker._updateDatepicker = function(inst) {
+ $.datepicker._updateDatepickerOverride(inst);
+ $.timepicker.resize();
+};
+
+function Timepicker() {}
+
+Timepicker.prototype = {
+ init: function()
+ {
+ this._mainDivId = 'ui-timepicker-div';
+ this._inputId = null;
+ this._orgValue = null;
+ this._orgHour = null;
+ this._orgMinute = null;
+ this._colonPos = -1;
+ this._visible = false;
+ this.tpDiv = $('');
+ this._generateHtml();
+ },
+
+ show: function (input)
+ {
+ // Get instance to datepicker
+ var inst = $.datepicker._getInst(input);
+
+ this._time24h = $.datepicker._get(inst, 'time24h');
+ this._altTimeField = $.datepicker._get(inst, 'altTimeField');
+
+ var stepMinutes = parseInt($.datepicker._get(inst, 'stepMinutes'), 10) || 1;
+ var stepHours = parseInt($.datepicker._get(inst, 'stepHours'), 10) || 1;
+
+ if (60 % stepMinutes != 0) { stepMinutes = 1; }
+ if (24 % stepHours != 0) { stepHours = 1; }
+
+ $('#hourSlider').slider('option', 'max', 24 - stepHours);
+ $('#hourSlider').slider('option', 'step', stepHours);
+
+ $('#minuteSlider').slider('option', 'max', 60 - stepMinutes);
+ $('#minuteSlider').slider('option', 'step', stepMinutes);
+
+ this._inputId = input.id;
+
+ if (!this._visible) {
+ this._parseTime();
+ this._orgValue = $('#' + this._inputId).val();
+ }
+
+ this.resize();
+
+ $('#' + this._mainDivId).show();
+
+ this._visible = true;
+
+ var dpDiv = $('#' + $.datepicker._mainDivId);
+ var dpDivPos = dpDiv.position();
+
+ var viewWidth = (window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth) + $(document).scrollLeft();
+ var tpRight = this.tpDiv.offset().left + this.tpDiv.outerWidth();
+
+ if (tpRight > viewWidth) {
+ dpDiv.css('left', dpDivPos.left - (tpRight - viewWidth) - 5);
+ this.tpDiv.css('left', dpDiv.offset().left + dpDiv.outerWidth() + 'px');
+ }
+ },
+
+ update: function (fd)
+ {
+ var curTime = $('#' + this._mainDivId + ' span.fragHours').text()
+ + ':'
+ + $('#' + this._mainDivId + ' span.fragMinutes').text();
+
+ if (!this._time24h) {
+ curTime += ' ' + $('#' + this._mainDivId + ' span.fragAmpm').text();
+ }
+
+ var curDate = $('#' + this._inputId).val();
+
+ $('#' + this._inputId).val(fd + ' ' + curTime);
+
+ if (this._altTimeField) {
+ $(this._altTimeField).each(function() { $(this).val(curTime); });
+ }
+ },
+
+ hide: function ()
+ {
+ this._visible = false;
+ $('#' + this._mainDivId).hide();
+ },
+
+ resize: function ()
+ {
+ var dpDiv = $('#' + $.datepicker._mainDivId);
+ var dpDivPos = dpDiv.position();
+
+ var hdrHeight = $('#' + $.datepicker._mainDivId + ' > div.ui-datepicker-header:first-child').height();
+
+ $('#' + this._mainDivId + ' > div.ui-datepicker-header:first-child').css('height', hdrHeight);
+
+ this.tpDiv.css({
+ 'height': dpDiv.height(),
+ 'top' : dpDivPos.top,
+ 'left' : dpDivPos.left + dpDiv.outerWidth() + 'px'
+ });
+
+ $('#hourSlider').css('height', this.tpDiv.height() - (3.5 * hdrHeight));
+ $('#minuteSlider').css('height', this.tpDiv.height() - (3.5 * hdrHeight));
+ },
+
+ _generateHtml: function ()
+ {
+ var html = '';
+
+ html += '';
+ html += 'Hour | Minute |
';
+ html += ' | |
';
+ html += '
';
+
+ this.tpDiv.empty().append(html);
+ $('body').append(this.tpDiv);
+
+ var self = this;
+
+ $('#hourSlider').slider({
+ orientation: "vertical",
+ range: 'min',
+ min: 0,
+ max: 23,
+ step: 1,
+ slide: function(event, ui) {
+ self._writeTime('hour', ui.value);
+ },
+ stop: function(event, ui) {
+ $('#' + self._inputId).focus();
+ }
+ });
+
+ $('#minuteSlider').slider({
+ orientation: "vertical",
+ range: 'min',
+ min: 0,
+ max: 59,
+ step: 1,
+ slide: function(event, ui) {
+ self._writeTime('minute', ui.value);
+ },
+ stop: function(event, ui) {
+ $('#' + self._inputId).focus();
+ }
+ });
+
+ $('#hourSlider > a').css('padding', 0);
+ $('#minuteSlider > a').css('padding', 0);
+ },
+
+ _writeTime: function (type, value)
+ {
+ if (type == 'hour') {
+ if (!this._time24h) {
+ if (value < 12) {
+ $('#' + this._mainDivId + ' span.fragAmpm').text('am');
+ } else {
+ $('#' + this._mainDivId + ' span.fragAmpm').text('pm');
+ value -= 12;
+ }
+
+ if (value == 0) value = 12;
+ } else {
+ $('#' + this._mainDivId + ' span.fragAmpm').text('');
+ }
+
+ if (value < 10) value = '0' + value;
+ $('#' + this._mainDivId + ' span.fragHours').text(value);
+ }
+
+ if (type == 'minute') {
+ if (value < 10) value = '0' + value;
+ $('#' + this._mainDivId + ' span.fragMinutes').text(value);
+ }
+ },
+
+ _parseTime: function ()
+ {
+ var dt = $('#' + this._inputId).val();
+
+ this._colonPos = dt.search(':');
+
+ var m = 0, h = 0, a = '';
+
+ if (this._colonPos != -1) {
+ h = parseInt(dt.substr(this._colonPos - 2, 2), 10);
+ m = parseInt(dt.substr(this._colonPos + 1, 2), 10);
+ a = jQuery.trim(dt.substr(this._colonPos + 3, 3));
+ }
+
+ a = a.toLowerCase();
+
+ if (a != 'am' && a != 'pm') {
+ a = '';
+ }
+
+ if (h < 0) h = 0;
+ if (m < 0) m = 0;
+
+ if (h > 23) h = 23;
+ if (m > 59) m = 59;
+
+ if (a == 'pm' && h < 12) h += 12;
+ if (a == 'am' && h == 12) h = 0;
+
+ this._setTime('hour', h);
+ this._setTime('minute', m);
+
+ this._orgHour = h;
+ this._orgMinute = m;
+ },
+
+ _setTime: function (type, value)
+ {
+ if (isNaN(value)) value = 0;
+ if (value < 0) value = 0;
+ if (value > 23 && type == 'hour') value = 23;
+ if (value > 59 && type == 'minute') value = 59;
+
+ if (type == 'hour') {
+ $('#hourSlider').slider('value', value);
+ }
+
+ if (type == 'minute') {
+ $('#minuteSlider').slider('value', value);
+ }
+
+ this._writeTime(type, value);
+ }
+};
+
+$.timepicker = new Timepicker();
+$('document').ready(function () {$.timepicker.init();});
+
+})(jQuery);
From 65586375e031bb44f31a3b4599ca61ec4ddf113f Mon Sep 17 00:00:00 2001
From: Gabor Czigola
Date: Sun, 31 Oct 2010 20:06:33 +0100
Subject: [PATCH 2/3] z-index fix
---
ui/jquery.ui.datetimepicker.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ui/jquery.ui.datetimepicker.js b/ui/jquery.ui.datetimepicker.js
index 5bdf0e994c3..aeb1666ea80 100644
--- a/ui/jquery.ui.datetimepicker.js
+++ b/ui/jquery.ui.datetimepicker.js
@@ -186,7 +186,7 @@ Timepicker.prototype = {
this._orgMinute = null;
this._colonPos = -1;
this._visible = false;
- this.tpDiv = $('');
+ this.tpDiv = $('');
this._generateHtml();
},
From 83975d3f79f5dbcf4d973b51df1407c40629eac1 Mon Sep 17 00:00:00 2001
From: Gabor Czigola
Date: Sun, 31 Oct 2010 22:01:12 +0100
Subject: [PATCH 3/3] Integrate bugfixes.
---
ui/jquery.ui.datetimepicker.js | 34 +++++++++++++++++++++++++++-------
1 file changed, 27 insertions(+), 7 deletions(-)
diff --git a/ui/jquery.ui.datetimepicker.js b/ui/jquery.ui.datetimepicker.js
index aeb1666ea80..3827ac43a3d 100644
--- a/ui/jquery.ui.datetimepicker.js
+++ b/ui/jquery.ui.datetimepicker.js
@@ -1,13 +1,16 @@
/*!
- * jQuery UI Timepicker 0.2.1
+ * jQuery UI DateTimepicker @VERSION
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
*
* Copyright (c) 2009 Martin Milesich (http://milesich.com/)
* http://addyosmani.com/blog/the-missing-date-time-selector-for-jquery-ui/
*
- * Copyright (c) 2010 Gábor Czigola (http://gablog.eu)
- *
+ * Copyright (c) 2010 Gábor Czigola
*
- * $Id: timepicker.js 28 2009-08-11 20:31:23Z majlo $
+ * http://docs.jquery.com/UI/Timepicker
*
* Depends:
* ui.core.js
@@ -92,6 +95,8 @@ $.datepicker._checkExternalClick = function (event) {
if (!$.datepicker._curInst) return;
var $target = $(event.target);
+ $.datepicker._curInst.closeButtonClicked = $target.parents('.ui-datepicker-buttonpane').length;
+
if (($target.parents('#' + $.timepicker._mainDivId).length == 0)) {
$.datepicker._checkExternalClickOverride(event);
}
@@ -108,18 +113,28 @@ $.datepicker._hideDatepicker = function(input, duration) {
if (!inst || (input && inst != $.data(input, PROP_NAME))) return;
+ // Change the field only when Done button clicked or sliders changed.
+ var saveOnHide = $.timepicker._modified;
+ try {
+ saveOnHide |= inst.closeButtonClicked;
+ }
+ catch (err) {}
+
// Get the value of showTime property
var showTime = this._get(inst, 'showTime');
- if (input === undefined && showTime) {
+ if (saveOnHide && input === undefined && showTime) {
if (inst.input) {
inst.input.val(this._formatDate(inst));
- inst.input.trigger('change'); // fire the change event
}
this._updateAlternate(inst);
if (showTime) $.timepicker.update(this._formatDate(inst));
+
+ if (inst.input) {
+ inst.input.trigger('change'); // fire the change event
+ }
}
// Hide datepicker
@@ -129,6 +144,8 @@ $.datepicker._hideDatepicker = function(input, duration) {
if (showTime) {
$.timepicker.hide();
}
+
+ $.timepicker._modified = false;
};
/**
@@ -186,6 +203,7 @@ Timepicker.prototype = {
this._orgMinute = null;
this._colonPos = -1;
this._visible = false;
+ this._modified = false;
this.tpDiv = $('');
this._generateHtml();
},
@@ -302,9 +320,11 @@ Timepicker.prototype = {
max: 23,
step: 1,
slide: function(event, ui) {
+ self._modified = true;
self._writeTime('hour', ui.value);
},
stop: function(event, ui) {
+ self._modified = true;
$('#' + self._inputId).focus();
}
});
@@ -405,7 +425,7 @@ Timepicker.prototype = {
}
this._writeTime(type, value);
- }
+ }
};
$.timepicker = new Timepicker();