mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
ENHANCEMENT Using jQuery UI datepicker in DateField and DatetimeField instead of outdated DHTML calendar.js (fixes #5397)
ENHANCEMENT Abstracted optional DateField->setConfig('showcalendar') logic to DateField_View_JQuery (from r107438) git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@112597 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
ab918e8546
commit
d67c43ad7d
@ -1,20 +0,0 @@
|
|||||||
.calendardate .calendar table {
|
|
||||||
width: 200px;
|
|
||||||
}
|
|
||||||
.calendardate img {
|
|
||||||
position: relative;
|
|
||||||
top: 2px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendarpopup {
|
|
||||||
position: absolute;
|
|
||||||
left: 0em;
|
|
||||||
top: 2em;
|
|
||||||
display: none;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendarpopup.focused {
|
|
||||||
display: block;
|
|
||||||
}
|
|
@ -1,21 +1,14 @@
|
|||||||
.popupdatetime ul {
|
.datetime .middleColumn .middleColumn {
|
||||||
list-style:none;
|
margin: 0;
|
||||||
padding-left:0;
|
padding: 0;
|
||||||
font-size:1em;
|
clear: none;
|
||||||
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popupdatetime ul li {
|
.datetime .date .middleColumn {
|
||||||
display:inline;
|
width: 20em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popupdatetime ul li .calendarpopup {
|
.datetime .time .middleColumn {
|
||||||
top: 2em;
|
width: 10em;
|
||||||
left: -1px;
|
|
||||||
|
|
||||||
padding-top: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popupdatetime ul li .dropdownpopup {
|
|
||||||
top: 2em;
|
|
||||||
left: -1px;
|
|
||||||
}
|
}
|
@ -9,13 +9,13 @@ require_once 'Zend/Date.php';
|
|||||||
* # Configuration
|
* # Configuration
|
||||||
*
|
*
|
||||||
* - 'showcalendar' (boolean): Determines if a calendar picker is shown.
|
* - 'showcalendar' (boolean): Determines if a calendar picker is shown.
|
||||||
* By default, "DHTML Calendar" is used,see http://www.dynarch.com/projects/calendar.
|
* By default, jQuery UI datepicker is used (see {@link DateField_View_JQuery}).
|
||||||
* CAUTION: Only works in NZ date format, see calendar-setup.js
|
* - 'jslocale' (string): Overwrites the "Locale" value set in this class.
|
||||||
|
* Only useful in combination with {@link DateField_View_JQuery}.
|
||||||
* - 'dmyfields' (boolean): Show three input fields for day, month and year separately.
|
* - 'dmyfields' (boolean): Show three input fields for day, month and year separately.
|
||||||
* CAUTION: Might not be useable in combination with 'showcalendar', depending on the used javascript library
|
* CAUTION: Might not be useable in combination with 'showcalendar', depending on the used javascript library
|
||||||
* - 'dateformat' (string): Date format compatible with Zend_Date.
|
* - 'dateformat' (string): Date format compatible with Zend_Date.
|
||||||
* Usually set to default format for {@link locale}
|
* Usually set to default format for {@link locale} through {@link Zend_Locale_Format::getDateFormat()}.
|
||||||
* through {@link Zend_Locale_Format::getDateFormat()}.
|
|
||||||
* - 'datavalueformat' (string): Internal ISO format string used by {@link dataValue()} to save the
|
* - 'datavalueformat' (string): Internal ISO format string used by {@link dataValue()} to save the
|
||||||
* date to a database.
|
* date to a database.
|
||||||
* - 'min' (string): Minimum allowed date value (in ISO format, or strtotime() compatible).
|
* - 'min' (string): Minimum allowed date value (in ISO format, or strtotime() compatible).
|
||||||
@ -52,6 +52,7 @@ class DateField extends TextField {
|
|||||||
*/
|
*/
|
||||||
protected $config = array(
|
protected $config = array(
|
||||||
'showcalendar' => false,
|
'showcalendar' => false,
|
||||||
|
'jslocale' => null,
|
||||||
'dmyfields' => false,
|
'dmyfields' => false,
|
||||||
'dmyseparator' => ' <span class="separator">/</span> ',
|
'dmyseparator' => ' <span class="separator">/</span> ',
|
||||||
'dateformat' => null,
|
'dateformat' => null,
|
||||||
@ -85,7 +86,13 @@ class DateField extends TextField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function FieldHolder() {
|
function FieldHolder() {
|
||||||
return parent::FieldHolder();
|
// TODO Replace with properly extensible view helper system
|
||||||
|
$d = Object::create('DateField_View_JQuery', $this);
|
||||||
|
$d->onBeforeRender();
|
||||||
|
$html = parent::FieldHolder();
|
||||||
|
$html = $d->onAfterRender($html);
|
||||||
|
|
||||||
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Field() {
|
function Field() {
|
||||||
@ -120,42 +127,9 @@ class DateField extends TextField {
|
|||||||
$html = parent::Field();
|
$html = parent::Field();
|
||||||
}
|
}
|
||||||
|
|
||||||
$html = $this->FieldDriver($html);
|
|
||||||
|
|
||||||
// wrap in additional div for legacy reasons and to apply behaviour correctly
|
|
||||||
if($this->getConfig('showcalendar')) $html = sprintf('<div class="calendardate">%s</div>', $html);
|
|
||||||
|
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Caution: API might change. This will evolve into a pluggable
|
|
||||||
* API for 'form field drivers' which can add their own
|
|
||||||
* markup and requirements.
|
|
||||||
*
|
|
||||||
* @param String $html
|
|
||||||
* @return $html
|
|
||||||
*/
|
|
||||||
protected function FieldDriver($html) {
|
|
||||||
// Optionally add a "DHTML" calendar icon. Mainly legacy, a date picker
|
|
||||||
// should be unobtrusively added by javascript (e.g. jQuery UI).
|
|
||||||
// CAUTION: Only works in NZ date format, see calendar-setup.js
|
|
||||||
if($this->getConfig('showcalendar')) {
|
|
||||||
Requirements::javascript(THIRDPARTY_DIR . '/prototype/prototype.js');
|
|
||||||
Requirements::javascript(THIRDPARTY_DIR . '/behaviour/behaviour.js');
|
|
||||||
Requirements::javascript(THIRDPARTY_DIR . "/calendar/calendar.js");
|
|
||||||
Requirements::javascript(THIRDPARTY_DIR . "/calendar/lang/calendar-en.js");
|
|
||||||
Requirements::javascript(THIRDPARTY_DIR . "/calendar/calendar-setup.js");
|
|
||||||
Requirements::css(SAPPHIRE_DIR . "/css/DateField.css");
|
|
||||||
Requirements::css(THIRDPARTY_DIR . "/calendar/calendar-win2k-1.css");
|
|
||||||
|
|
||||||
$html .= sprintf('<img src="sapphire/images/calendar-icon.gif" id="%s-icon" alt="Calendar icon" />', $this->id());
|
|
||||||
$html .= sprintf('<div class="calendarpopup" id="%s-calendar"></div>', $this->id());
|
|
||||||
}
|
|
||||||
|
|
||||||
return $html;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the internal value to ISO date format.
|
* Sets the internal value to ISO date format.
|
||||||
*
|
*
|
||||||
@ -442,9 +416,6 @@ JS;
|
|||||||
throw new InvalidArgumentException('Date "%s" is not a valid maximum date format (%s) or strtotime() argument', $val, $format);
|
throw new InvalidArgumentException('Date "%s" is not a valid maximum date format (%s) or strtotime() argument', $val, $format);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'showcalendar':
|
|
||||||
$this->config['dateformat'] = Zend_Locale_Format::getDateFormat('en_NZ');
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->config[$name] = $val;
|
$this->config[$name] = $val;
|
||||||
@ -498,4 +469,165 @@ class DateField_Disabled extends DateField {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preliminary API to separate optional view properties
|
||||||
|
* like calendar popups from the actual datefield logic.
|
||||||
|
*
|
||||||
|
* Caution: This API is highly volatile, and might change without prior deprecation.
|
||||||
|
*
|
||||||
|
* @package sapphire
|
||||||
|
* @subpackage forms
|
||||||
|
*/
|
||||||
|
class DateField_View_JQuery {
|
||||||
|
|
||||||
|
protected $field;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array Maps values from {@link i18n::$all_locales()} to
|
||||||
|
* localizations existing in jQuery UI.
|
||||||
|
*/
|
||||||
|
static $locale_map = array(
|
||||||
|
'en_GB' => 'en-GB',
|
||||||
|
'fr_CH' => 'fr-CH',
|
||||||
|
'pt_BR' => 'pt-BR',
|
||||||
|
'sr_SR' => 'sr-SR',
|
||||||
|
'zh_CN' => 'zh-CN',
|
||||||
|
'zh_HK' => 'zh-HK',
|
||||||
|
'zh_TW' => 'zh-TW',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param DateField $field
|
||||||
|
*/
|
||||||
|
function __construct($field) {
|
||||||
|
$this->field = $field;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return DateField
|
||||||
|
*/
|
||||||
|
function getField() {
|
||||||
|
return $this->field;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function onBeforeRender() {
|
||||||
|
if($this->getField()->getConfig('showcalendar')) {
|
||||||
|
// Inject configuration into existing HTML
|
||||||
|
$format = self::convert_iso_to_jquery_format($this->getField()->getConfig('dateformat'));
|
||||||
|
$this->getField()->addExtraClass(str_replace('"', '\'', Convert::raw2json(array('dateFormat' => $format))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param String $html
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
function onAfterRender($html) {
|
||||||
|
if($this->getField()->getConfig('showcalendar')) {
|
||||||
|
Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
|
||||||
|
Requirements::javascript(SAPPHIRE_DIR . '/javascript/jquery_improvements.js');
|
||||||
|
Requirements::css('http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.1/themes/smoothness/jquery-ui.css');
|
||||||
|
Requirements::javascript('http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.1/jquery-ui.min.js');
|
||||||
|
|
||||||
|
// Include language files (if required)
|
||||||
|
$lang = $this->getLang();
|
||||||
|
if($lang != 'en') {
|
||||||
|
// TODO Check for existence of locale to avoid unnecessary 404s from the CDN
|
||||||
|
Requirements::javascript(
|
||||||
|
sprintf(
|
||||||
|
'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.1/i18n/jquery.ui.datepicker-%s.min.js',
|
||||||
|
// can be a mix between names (e.g. 'de') and combined locales (e.g. 'zh-TW')
|
||||||
|
$lang
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Requirements::javascript(THIRDPARTY_DIR . "/jquery-metadata/jquery.metadata.js");
|
||||||
|
Requirements::javascript(SAPPHIRE_DIR . "/javascript/DateField.js");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines which language to use for jQuery UI, which
|
||||||
|
* can be different from the value set in i18n.
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
protected function getLang() {
|
||||||
|
$locale = $this->getField()->getLocale();
|
||||||
|
if($this->getField()->getConfig('jslocale')) {
|
||||||
|
// Undocumented config property for now, might move to the jQuery view helper
|
||||||
|
$lang = $this->getField()->getConfig('jslocale');
|
||||||
|
} else if(array_key_exists($locale, self::$locale_map)) {
|
||||||
|
// Specialized mapping for combined lang properties
|
||||||
|
$lang = self::$locale_map[$locale];
|
||||||
|
} else {
|
||||||
|
// Fall back to default lang (meaning "en_US" turns into "en")
|
||||||
|
$lang = i18n::get_lang_from_locale($locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert iso to jquery UI date format.
|
||||||
|
* Needs to be consistent with Zend formatting, otherwise validation will fail.
|
||||||
|
* Removes all time settings like hour/minute/second from the format.
|
||||||
|
* See http://docs.jquery.com/UI/Datepicker/formatDate
|
||||||
|
*
|
||||||
|
* @param String $format
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
static function convert_iso_to_jquery_format($format) {
|
||||||
|
$convert = array(
|
||||||
|
'/([^d])d([^d])/' => '$1d$2',
|
||||||
|
'/^d([^d])/' => 'd$1',
|
||||||
|
'/([^d])d$/' => '$1d',
|
||||||
|
'/dd/' => 'dd',
|
||||||
|
'/EEEE/' => 'DD',
|
||||||
|
'/EEE/' => 'D',
|
||||||
|
'/SS/' => '',
|
||||||
|
'/eee/' => 'd',
|
||||||
|
'/e/' => 'N',
|
||||||
|
'/D/' => '',
|
||||||
|
'/w/' => '',
|
||||||
|
'/([^M])M([^M])/' => '$1m$2',
|
||||||
|
'/^M([^M])/' => 'm$1',
|
||||||
|
'/([^M])M$/' => '$1m',
|
||||||
|
'/MMMM/' => 'MM',
|
||||||
|
'/MMM/' => 'M',
|
||||||
|
'/MM/' => 'mm',
|
||||||
|
'/l/' => '',
|
||||||
|
'/YYYY/' => 'yy',
|
||||||
|
'/yyyy/' => 'yy',
|
||||||
|
'/[^y]yy[^y]/' => 'y',
|
||||||
|
'/a/' => '',
|
||||||
|
'/B/' => '',
|
||||||
|
'/hh/' => '',
|
||||||
|
'/h/' => '',
|
||||||
|
'/([^H])H([^H])/' => '',
|
||||||
|
'/^H([^H])/' => '',
|
||||||
|
'/([^H])H$/' => '',
|
||||||
|
'/HH/' => '',
|
||||||
|
// '/mm/' => '',
|
||||||
|
'/ss/' => '',
|
||||||
|
'/zzzz/' => '',
|
||||||
|
'/I/' => '',
|
||||||
|
'/ZZZZ/' => '',
|
||||||
|
'/Z/' => '',
|
||||||
|
'/z/' => '',
|
||||||
|
'/X/' => '',
|
||||||
|
'/r/' => '',
|
||||||
|
'/U/' => '',
|
||||||
|
);
|
||||||
|
$patterns = array_keys($convert);
|
||||||
|
$replacements = array_values($convert);
|
||||||
|
|
||||||
|
return preg_replace($patterns, $replacements, $format);
|
||||||
|
}
|
||||||
|
}
|
||||||
?>
|
?>
|
14
javascript/DateField.js
Normal file
14
javascript/DateField.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
(function($) {
|
||||||
|
$('.field.date input.text').live('click', function() {
|
||||||
|
var holder = $(this).parents('.field.date:first'), config = holder.metadata();
|
||||||
|
|
||||||
|
if(config.locale && $.datepicker.regional[config.locale]) {
|
||||||
|
config = $.extend(config, $.datepicker.regional[config.locale], {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize and open a datepicker
|
||||||
|
// live() doesn't have "onmatch", and jQuery.entwine is a bit too heavyweight for this, so we need to do this onclick.
|
||||||
|
$(this).datepicker(config);
|
||||||
|
$(this).datepicker('show');
|
||||||
|
});
|
||||||
|
}(jQuery));
|
24
tests/forms/DatefieldViewJQueryTest.php
Normal file
24
tests/forms/DatefieldViewJQueryTest.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @package sapphire
|
||||||
|
* @subpackage tests
|
||||||
|
*/
|
||||||
|
class DateFieldViewJQueryTest extends SapphireTest {
|
||||||
|
|
||||||
|
function testConvert() {
|
||||||
|
$this->assertEquals(
|
||||||
|
'M d, yy',
|
||||||
|
DateField_View_JQuery::convert_iso_to_jquery_format('MMM d, yyyy')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'd/mm/yy',
|
||||||
|
DateField_View_JQuery::convert_iso_to_jquery_format('d/MM/yyyy')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'dd.mm.yy',
|
||||||
|
DateField_View_JQuery::convert_iso_to_jquery_format('dd.MM.yyyy')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,40 @@ class DatetimeFieldTest extends SapphireTest {
|
|||||||
i18n::set_locale($this->originalLocale);
|
i18n::set_locale($this->originalLocale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testFormSaveInto() {
|
||||||
|
$form = new Form(
|
||||||
|
new Controller(),
|
||||||
|
'Form',
|
||||||
|
new FieldSet(
|
||||||
|
$f = new DatetimeField('MyDatetime', null)
|
||||||
|
),
|
||||||
|
new FieldSet(
|
||||||
|
new FormAction('doSubmit')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$f->setValue(array(
|
||||||
|
'date' => '29/03/2003',
|
||||||
|
'time' => '23:59:38'
|
||||||
|
));
|
||||||
|
$m = new DatetimeFieldTest_Model();
|
||||||
|
$form->saveInto($m);
|
||||||
|
$this->assertEquals('2003-03-29 23:59:38', $m->MyDatetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testDataValue() {
|
||||||
|
$f = new DatetimeField('Datetime');
|
||||||
|
$this->assertEquals(null, $f->dataValue(), 'Empty field');
|
||||||
|
|
||||||
|
$f = new DatetimeField('Datetime', null, '2003-03-29 23:59:38');
|
||||||
|
$this->assertEquals('2003-03-29 23:59:38', $f->dataValue(), 'From date/time string');
|
||||||
|
|
||||||
|
$f = new DatetimeField('Datetime', null, '2003-03-29');
|
||||||
|
$this->assertEquals('2003-03-29 00:00:00', $f->dataValue(), 'From date string (no time)');
|
||||||
|
|
||||||
|
$f = new DatetimeField('Datetime', null, array('date' => '2003-03-29', 'time' => null));
|
||||||
|
$this->assertEquals('2003-03-29 00:00:00', $f->dataValue(), 'From date array (no time)');
|
||||||
|
}
|
||||||
|
|
||||||
function testConstructorWithoutArgs() {
|
function testConstructorWithoutArgs() {
|
||||||
$f = new DatetimeField('Datetime');
|
$f = new DatetimeField('Datetime');
|
||||||
$this->assertEquals($f->dataValue(), null);
|
$this->assertEquals($f->dataValue(), null);
|
||||||
@ -77,4 +111,16 @@ class DatetimeFieldTest extends SapphireTest {
|
|||||||
$f = new DatetimeField('Datetime', 'Datetime', 'wrong');
|
$f = new DatetimeField('Datetime', 'Datetime', 'wrong');
|
||||||
$this->assertFalse($f->validate(new RequiredFields()));
|
$this->assertFalse($f->validate(new RequiredFields()));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package sapphire
|
||||||
|
* @subpackage tests
|
||||||
|
*/
|
||||||
|
class DatetimeFieldTest_Model extends DataObject implements TestOnly {
|
||||||
|
|
||||||
|
static $db = array(
|
||||||
|
'MyDatetime' => 'SS_Datetime'
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user