Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.757 diff -u -r1.757 system.module --- modules/system/system.module 21 Aug 2009 14:28:52 -0000 1.757 +++ modules/system/system.module 21 Aug 2009 21:58:25 -0000 @@ -402,6 +402,14 @@ '#theme_wrappers' => array('form_element'), ); + $type['datetime'] = array( + '#input' => TRUE, + '#element_validate' => array('datetime_validate'), + '#process' => array('form_process_datetime'), + '#theme' => 'datetime', + '#theme_wrappers' => array('form_element'), + ); + $type['file'] = array( '#input' => TRUE, '#size' => 60, @@ -1177,6 +1185,20 @@ ), ); + // jQuery UI Timepicker + $libraries['timepicker'] = array( + 'title' => 'jQuery UI: Timepicker', + 'website' => 'http://milesich.com/timepicker/', + 'version' => '0.2.1', + 'js' => array( + 'misc/timepicker.js' => array(), + ), + 'dependencies' => array( + array('system', 'ui.datepicker'), + array('system', 'ui.slider'), + ), + ); + return $libraries; } Index: modules/node/node.pages.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.pages.inc,v retrieving revision 1.75 diff -u -r1.75 node.pages.inc --- modules/node/node.pages.inc 19 Aug 2009 13:31:13 -0000 1.75 +++ modules/node/node.pages.inc 21 Aug 2009 21:58:25 -0000 @@ -211,14 +211,15 @@ '#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', t('Anonymous')))), ); $form['author']['date'] = array( - '#type' => 'textfield', + '#type' => 'datetime', '#title' => t('Authored on'), '#maxlength' => 25, '#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the timezone offset from UTC. Leave blank to use the time of form submission.', array('%time' => !empty($node->date) ? $node->date : format_date($node->created, 'custom', 'Y-m-d H:i:s O'), '%timezone' => !empty($node->date) ? $node->date : format_date($node->created, 'custom', 'O'))), ); - if (isset($node->date)) { - $form['author']['date']['#default_value'] = $node->date; + // Only set the authored on date value if the node has already been created. + if (isset($node->nid)) { + $form['author']['date']['#default_value'] = $node->created; } // Node options for administrators Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.1104 diff -u -r1.1104 node.module --- modules/node/node.module 21 Aug 2009 14:27:45 -0000 1.1104 +++ modules/node/node.module 21 Aug 2009 21:58:25 -0000 @@ -919,11 +919,6 @@ // are dealing with an anonymous user we set the user ID to 0. form_set_error('name', t('The username %name does not exist.', array('%name' => $node->name))); } - - // Validate the "authored on" field. - if (!empty($node->date) && strtotime($node->date) === FALSE) { - form_set_error('date', t('You have to specify a valid date.')); - } } // Do node-type-specific validation checks. @@ -949,7 +944,7 @@ $node->uid = 0; } } - $node->created = !empty($node->date) ? strtotime($node->date) : REQUEST_TIME; + $node->created = !empty($node->date) ? $node->date : REQUEST_TIME; $node->validated = TRUE; return $node; Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.965 diff -u -r1.965 common.inc --- includes/common.inc 21 Aug 2009 07:50:07 -0000 1.965 +++ includes/common.inc 21 Aug 2009 21:58:24 -0000 @@ -3161,7 +3161,7 @@ // If any dependent library could not be added, this library will break; // stop here. $added[$module][$name] = FALSE; - return FALSE; + //return FALSE; } } @@ -4367,6 +4367,9 @@ 'date' => array( 'arguments' => array('element' => NULL), ), + 'datetime' => array( + 'arguments' => array('element' => NULL), + ), 'checkbox' => array( 'arguments' => array('element' => NULL), ), Index: includes/form.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/form.inc,v retrieving revision 1.359 diff -u -r1.359 form.inc --- includes/form.inc 19 Aug 2009 08:15:36 -0000 1.359 +++ includes/form.inc 21 Aug 2009 21:58:24 -0000 @@ -1784,6 +1784,59 @@ } /** + * Theme a date selection form element. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used: #title, #value, #options, #description, #required, + * #attributes. + * @return + * A themed HTML string representing the date selection boxes. + * + * @ingroup themeable + */ +function theme_datetime($element) { + $blah = drupal_add_library('system', 'timepicker'); + drupal_add_js('misc/form.datetime.js'); + return drupal_render_children($element); +} + +/** + * Roll out a date and time element. + */ +function form_process_datetime($element) { + // Default to current date + if (empty($element['#value'])) { + $element['#value'] = REQUEST_TIME; + } + // The value is to be processed from an existing text value. + else if (is_array($element['#value'])) { + $element['#value'] = strtotime($element['#value']['datetime']); + } + $element['#tree'] = TRUE; + $element['datetime'] = array( + '#type' => 'textfield', + '#value' => $element['#value'] == REQUEST_TIME ? '' : format_date($element['#value'], 'custom', 'Y-m-d H:i:s O'), + '#size' => 20, + '#maxlength' => 25, + ); + return $element; +} + +/** + * Validates the date type to stop dates like February 30, 2006. + */ +function datetime_validate($element, &$element_state) { + $date = empty($element['#value']['datetime']) ? REQUEST_TIME : strtotime($element['#value']['datetime']); + if ($date == FALSE) { + form_error($element, t('The specified date is invalid.')); + $date = REQUEST_TIME; + } + form_set_value($element, $date, $element_state); + return $element; +} + +/** * Helper function for usage with drupal_map_assoc to display month names. */ function map_month($month) { Index: modules/comment/comment.module =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v retrieving revision 1.755 diff -u -r1.755 comment.module --- modules/comment/comment.module 21 Aug 2009 00:21:48 -0000 1.755 +++ modules/comment/comment.module 21 Aug 2009 21:58:25 -0000 @@ -1653,13 +1653,6 @@ $status = 0; } - if (!empty($comment->date)) { - $date = $comment->date; - } - else { - $date = format_date($comment->timestamp, 'custom', 'Y-m-d H:i O'); - } - $form['admin'] = array( '#type' => 'fieldset', '#title' => t('Administration'), @@ -1710,13 +1703,10 @@ '#default_value' => $comment->homepage, ); } - $form['admin']['date'] = array( - '#type' => 'textfield', - '#parents' => array('date'), + $form['admin']['timestamp'] = array( + '#type' => 'datetime', '#title' => t('Authored on'), - '#size' => 20, - '#maxlength' => 25, - '#default_value' => $date, + '#default_value' => isset($comment->timestamp) ? $comment->timestamp : NULL, '#weight' => -1, ); $form['admin']['status'] = array( @@ -1948,11 +1938,6 @@ } } - if (isset($form_state['values']['date'])) { - if (strtotime($form_state['values']['date']) === FALSE) { - form_set_error('date', t('You have to specify a valid date.')); - } - } if (isset($form_state['values']['author']) && !$account = user_load_by_name($form_state['values']['author'])) { form_set_error('author', t('You have to specify a valid author.')); } @@ -2003,11 +1988,7 @@ */ function comment_submit($comment) { $comment += array('subject' => ''); - if (!isset($comment['date'])) { - $comment['date'] = 'now'; - } - $comment['timestamp'] = strtotime($comment['date']); if (isset($comment['author'])) { $account = user_load_by_name($comment['author']); $comment['uid'] = $account->uid; Index: misc/timepicker.js =================================================================== RCS file: misc/timepicker.js diff -N misc/timepicker.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ misc/timepicker.js 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,413 @@ +/*! + * jQuery UI Timepicker 0.2.1 + * + * Copyright (c) 2009 Martin Milesich (http://milesich.com/) + * + * Some parts are + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * + * $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 += '
'; + html += '08:45
'; + html += ''; + html += ''; + html += '
HourMinute
'; + + 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); Index: misc/form.datetime.js =================================================================== RCS file: misc/form.datetime.js diff -N misc/form.datetime.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ misc/form.datetime.js 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,21 @@ +// $Id$ +(function ($) { + +/** + * Provides the jQuery UI date and time picker for datetime fields. + */ +Drupal.behaviors.datetimePicker = { + attach: function (context, settings) { + // Automatically selects the right radio button in a multiselect control. + $('.form-item-datetime input:text:not(.datetimePicker-processed)', context) + .addClass('datetimePicker-processed').datepicker({ + duration: '', + showTime: true, + time24h: true, + constrainInput: false, + dateFormat: 'yy-mm-dd' + }); + } +}; + +})(jQuery);