Commit 3340ef27 authored by Robert Lyon's avatar Robert Lyon
Browse files

Updating calendar pieform element to use datepicker/timepicker



(Bug #1267239)

The datepicker is now keyboard accessible but not fully screenreader
accessible - but the folks at datepicker are working on that

The key bindings you need to know for datepicker:
PAGE UP: Move to the previous month.
PAGE DOWN: Move to the next month.
CTRL+PAGE UP: Move to the previous year.
CTRL+PAGE DOWN: Move to the next year.
CTRL+HOME: Move to the current month. Open the datepicker if closed.
CTRL+LEFT: Move to the previous day.
CTRL+RIGHT: Move to the next day.
CTRL+UP: Move to the previous week.
CTRL+DOWN: Move the next week.
ENTER: Select the focused date.
ESC: Close the datepicker

The key bindings added for timepicker:
(to avoid clashes with existing key bindings)
ALT+UP: Move hour slider up one step
ALT+DOWN: Move hour slider down one step
SHIFT+LEFT: Move minute slider down one step
SHIFT+RIGHT: Move minute slider up one step

Also added the prev/next year button options

Change-Id: Iff857ba9efb23fd123de6af8f78ceffee9c0ac79
Signed-off-by: Robert Lyon's avatarRobert Lyon <robertl@catalyst.net.nz>
parent bdac0f06
......@@ -9,7 +9,8 @@ Dependency package: jquery 2.1.1
Changes:
* None
* Added call to function after datepicker arrow key is pressed - to
allow the year arrows to be re-added on day change
Notes:
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -583,7 +583,39 @@ $string['month'] = 'month';
$string['months'] = 'months';
$string['years'] = 'years';
$string['year'] = 'year';
// Boolean site option
// Datepicker options
$string['datepicker_clearText'] = 'Clear';
$string['datepicker_clearStatus'] = '';
$string['datepicker_closeText'] = 'Done';
$string['datepicker_closeStatus'] = 'Close without change';
$string['datepicker_prevText'] = 'Prev';
$string['datepicker_prevStatus'] = 'Show the previous month';
$string['datepicker_nextText'] = 'Next';
$string['datepicker_nextStatus'] = 'Show the next month';
$string['datepicker_currentText'] = 'Now';
$string['datepicker_currentStatus'] = 'Show the current month';
$string['datepicker_monthNames'] = "['January','February','March','April','May','June','July','August','September','October','November','December']";
$string['datepicker_monthNamesShort'] = "['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']";
$string['datepicker_monthStatus'] = 'Show a different month';
$string['datepicker_yearStatus'] = 'See another year';
$string['datepicker_weekHeader'] = 'Wk';
$string['datepicker_weekStatus'] = '';
$string['datepicker_dayNames'] = "['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']";
$string['datepicker_dayNamesShort'] = "['Sun','Mon','Tue','Wed','Thu','Fri','Sat']";
$string['datepicker_dayNamesMin'] = "['Su','Mo','Tu','We','Th','Fr','Sa']";
$string['datepicker_dayStatus'] = 'Using DD as the first day of the week';
$string['datepicker_dateStatus'] = 'Select DD, MM d, yy';
$string['datepicker_initStatus'] = 'Choose the date';
$string['datepicker_timeOnlyTitle'] = 'Select time';
$string['datepicker_timeText'] = 'Time';
$string['datepicker_hourText'] = 'Hour';
$string['datepicker_minuteText'] = 'Minute';
$string['datepicker_secondText'] = 'Second';
$string['datepicker_millisecText'] = 'Millisecond';
$string['datepicker_timezoneText'] = 'Timezone';
$string['datepicker_amNames'] = "['AM', 'A']";
$string['datepicker_pmNames'] = "['PM', 'P']";
// Site content pages
$string['sitecontentnotfound'] = '%s text not available';
......
......@@ -17,7 +17,7 @@
* @return string The HTML for the element
*/
function pieform_element_viewacl(Pieform $form, $element) {
global $USER, $SESSION;
global $USER, $SESSION, $LANGDIRECTION;
$strlen = function_exists('mb_strlen') ? 'mb_strlen' : 'strlen';
......@@ -139,7 +139,35 @@ function pieform_element_viewacl(Pieform $form, $element) {
}
$faves[] = $fave;
}
require_once(get_config('libroot') . 'pieforms/pieform/elements/calendar.php');
$options = array('dateFormat' => 'yy/mm/dd',
'timeFormat' => 'HH:mm',
'stepHour' => 1,
'stepMinute' => 5,
);
$options = pieform_element_calendar_get_lang_strings($options, $LANGDIRECTION);
$datepickeroptionstr = '';
foreach ($options as $key => $option) {
if (is_numeric($option)) {
$datepickeroptionstr .= $key . ': ' . $option . ',';
}
else if (is_array($option)) {
foreach ($option as $k => $v) {
if (!is_numeric($v)) {
if (preg_match('/^\'(.*)\'$/', $v, $match)) {
$v = $match[1];
}
$option[$k] = json_encode($v);
}
}
$option = '[' . implode(',', $option) . ']';
$datepickeroptionstr .= $key . ': ' . $option . ',';
}
else {
$datepickeroptionstr .= $key . ': ' . json_encode($option) . ',';
}
}
$smarty->assign('datepickeroptions', $datepickeroptionstr);
$smarty->assign('viewtype', $element['viewtype']);
$smarty->assign('potentialpresets', json_encode($allowedpresets));
$smarty->assign('loggedinindex', $loggedinindex);
......
......@@ -2130,8 +2130,8 @@ function pieform_reply($code, $data) {
function pieform_element_calendar_configure($element) {
global $THEME;
$element['jsroot'] = get_config('wwwroot') . 'js/jscalendar/';
$element['themefile'] = $THEME->get_url('style/calendar.css');
$element['jsroot'] = get_config('wwwroot') . 'js/jquery/jquery-ui/';
$element['themefile'] = $THEME->get_url('style/datepicker.css');
$element['imagefile'] = $THEME->get_url('images/btn_calendar.png');
$language = substr(current_language(), 0, 2);
$element['language'] = $language;
......
......@@ -25,60 +25,93 @@
*/
/**
* Provides a javascript calendar for inputting a date.
* Provides a javascript calendar for inputting a date/time.
*
* General documentation about the calendar is available at
* http://www.dynarch.com/static/jscalendar-1.0/doc/html/reference.html
* http://api.jqueryui.com/datepicker/
* General documentation about the timepicker addon is available at
* http://trentrichardson.com/examples/timepicker/
*
* @param Pieform $form The form to render the element for
* @param array $element The element to render
* @return string The HTML for the element
*/
function pieform_element_calendar(Pieform $form, $element) {/*{{{*/
global $LANGDIRECTION;
$id = $form->get_name() . '_' . $element['name'];
$value = $form->get_value($element);
if ($value) {
$value = Pieform::hsc(strftime($element['caloptions']['ifFormat'], $value));
}
// Build the configuring javascript
$options = array_merge($element['caloptions'], array('inputField' => $id));
// Set up default timeFormat if needed
if (!empty($options['showTime']) && empty($options['timeFormat'])) {
$options['timeFormat'] = 'HH:mm';
}
$options = pieform_element_calendar_get_lang_strings($options, $LANGDIRECTION);
// Build the HTML
$result = '<input type="text"'
. $form->element_attributes($element)
. ' value="' . $value . '">';
if (isset($element['imagefile'])) {
$result .= '<a href="" id="'. $id . '_btn" onclick="return false;" class="pieform-calendar-toggle"'
. ' tabindex="' . $element['tabindex'] . '">'
. '<img src="' . $element['imagefile'] . '" alt="' . get_string('element.calendar.opendatepicker', 'pieforms') . '"></a>';
$result .= '<script type="text/javascript">
var input = jQuery("input#' . $id . '");';
if (!empty($options['showsTime'])) {
$result .= 'input.datetimepicker({';
}
else {
$result .= '<button type="button" id="' . $id . '_btn" onclick="return false;" class="pieform-calendar-toggle"'
. ' tabindex="' . $element['tabindex'] . '">';
$result .= '<span class="accessible-hidden">' . get_string('element.calendar.opendatepicker', 'pieforms') . '</span>';
$result .= '...</button>';
$result .= 'input.datepicker({';
}
// Build the configuring javascript
$options = array_merge($element['caloptions'], array('inputField' => $id, 'button' => $id . '_btn'));
// Update the formchangechecker when the user selects a date
$onselectfunction = 'updateFormChangerChecker_' . $id;
$options['onUpdate'] = empty($options['onUpdate']) ? $onselectfunction : $options['onUpdate'] . $onselectfunction;
$encodedoptions = json_encode($options);
// Some options are callbacks and need their quoting removed
foreach (array('dateStatusFunc', 'flatCallback', 'onSelect', 'onClose', 'onUpdate') as $function) {
$encodedoptions = preg_replace('/("' . $function . '"):"([a-zA-Z0-9_$]+)"/', '\1:\2', $encodedoptions);
}
$result .= '<script type="text/javascript">
var updateFormChangerChecker_' . $id . ' = function () {
var input = jQuery("input#' . $id . '");
if (typeof formchangemanager !== \'undefined\') {
var form = input.closest(\'form\')[0];
formchangemanager.setFormState(form, FORM_CHANGED);
$result .= ' onSelect: function(date) {
if (typeof formchangemanager !== \'undefined\') {
var form = input.closest(\'form\')[0];
formchangemanager.setFormState(form, FORM_CHANGED);
}
},';
foreach ($options as $key => $option) {
if (is_numeric($option)) {
$result .= $key . ': ' . $option . ',';
}
else if (is_array($option)) {
foreach ($option as $k => $v) {
if (!is_numeric($v)) {
if (preg_match('/^\'(.*)\'$/', $v, $match)) {
$v = $match[1];
}
$option[$k] = json_encode($v);
}
}
input.change();
$option = '[' . implode(',', $option) . ']';
$result .= $key . ': ' . $option . ',';
}
Calendar.setup(' . $encodedoptions . ');
else {
$result .= $key . ': ' . json_encode($option) . ',';
}
}
// Adding prev / next year buttons
$result .= '
beforeShow: function(input, inst) {
setTimeout(function() {
add_prev_next_year(inst);
}, 1);
},
onChangeMonthYear: function(y, m, inst) {
setTimeout(function() {
add_prev_next_year(inst);
}, 1);
},
';
if (isset($element['imagefile'])) {
$result .= 'showOn: "both",
buttonImage: "' . $element['imagefile'] . '",
buttonImageOnly: true,
buttonText: "' . get_string('element.calendar.opendatepicker', 'pieforms') . '",';
}
$result .= '
});
</script>';
return $result;
......@@ -93,9 +126,10 @@ function pieform_element_calendar(Pieform $form, $element) {/*{{{*/
function pieform_element_calendar_set_attributes($element) {/*{{{*/
$element['jsroot'] = isset($element['jsroot']) ? $element['jsroot'] : '';
$element['language'] = isset($element['language']) ? $element['language'] : 'en';
$element['theme'] = isset($element['theme']) ? $element['theme'] : 'calendar-win2k-2';
$element['theme'] = isset($element['theme']) ? $element['theme'] : 'raw';
$element['caloptions']['ifFormat'] = isset($element['caloptions']['ifFormat']) ? $element['caloptions']['ifFormat'] : '%Y/%m/%d';
$element['caloptions']['daFormat'] = isset($element['caloptions']['daFormat']) ? $element['caloptions']['daFormat'] : '%Y/%m/%d';
$element['caloptions']['dateFormat'] = isset($element['caloptions']['dateFormat']) ? $element['caloptions']['dateFormat'] : 'yy/mm/dd';
return $element;
}/*}}}*/
......@@ -110,19 +144,48 @@ function pieform_element_calendar_get_headdata($element) {/*{{{*/
$themefile = $element['themefile'];
}
else if (isset($element['theme'])) {
$themefile = $element['jsroot'] . $element['theme'] . '.css';
if (file_exists(get_config('docroot') . 'theme/' . $element['theme'] . '/static/style/datepicker.css')) {
$themefile = get_config('wwwroot') . 'theme/' . $element['theme'] . '/static/style/datepicker.css';
}
else {
throw new PieformException('No theme file for calendar "' . $element['name'] . '": please make sure themefile "' . get_config('docroot') . 'theme/' . $element['theme'] . '/static/style/datepicker.css" exists');
}
}
else {
throw new PieformException('No theme chosen for calendar "' . $element['name'] . '": please set themefile or theme');
}
$libfile = $element['jsroot'] . 'calendar_stripped.js';
$langfile = $element['jsroot'] . 'lang/calendar-' . $element['language'] . '.js';
$setupfile = $element['jsroot'] . 'calendar-setup_stripped.js';
$libjs = $element['jsroot'] . 'js/jquery-ui-1.10.2.min.js';
$libcss = $element['jsroot'] . 'css/ui-lightness/jquery-ui-1.10.2.min.css';
$timeaddonjs = $element['jsroot'] . 'js/jquery-ui-timepicker-addon.js';
$prev = get_string('datepicker_prevText');
$next = get_string('datepicker_nextText');
$extrajs = <<<EOF
/**
* Add the prev and next year button to a datepicker
*/
function add_prev_next_year(inst) {
var widgetHeader = jQuery("#ui-datepicker-div").find(".ui-datepicker-header");
var prevYrBtn = jQuery('<a class="ui-datepicker-prev-year ui-corner-all" title="$prev"><span class="ui-icon ui-icon-circle-triangle-wy">$prev</span></a>');
prevYrBtn.unbind("click").bind("click", function() {
jQuery.datepicker._adjustDate(inst.input, -1, "Y");
}).hover(function() { \$j(this).addClass('ui-datepicker-prev-year-hover ui-state-hover')},
function() { \$j(this).removeClass('ui-datepicker-prev-year-hover ui-state-hover')});
var nextYrBtn = jQuery('<a class="ui-datepicker-next-year ui-corner-all" title="$next"><span class="ui-icon ui-icon-circle-triangle-ey">$next</span></a>');
nextYrBtn.unbind("click").bind("click", function() {
jQuery.datepicker._adjustDate(inst.input, +1, "Y");
}).hover(function() { \$j(this).addClass('ui-datepicker-next-year-hover ui-state-hover')},
function() { \$j(this).removeClass('ui-datepicker-next-year-hover ui-state-hover')});
nextYrBtn.prependTo(widgetHeader);
prevYrBtn.prependTo(widgetHeader);
}
EOF;
$result = array(
'<link rel="stylesheet" type="text/css" media="all" href="' . $libcss . '?v=' . get_config('release'). '">',
'<link rel="stylesheet" type="text/css" media="all" href="' . $themefile . '?v=' . get_config('release'). '">',
'<script type="text/javascript" src="' . $libfile . '?v=' . get_config('release'). '"></script>',
'<script type="text/javascript" src="' . $langfile . '?v=' . get_config('release'). '"></script>',
'<script type="text/javascript" src="' . $setupfile . '?v=' . get_config('release'). '"></script>'
'<script type="text/javascript" src="' . $libjs . '?v=' . get_config('release'). '"></script>',
'<script type="text/javascript" src="' . $timeaddonjs . '?v=' . get_config('release'). '"></script>',
'<script type="text/javascript">' . $extrajs . '</script>',
);
return $result;
}/*}}}*/
......@@ -162,3 +225,32 @@ function pieform_element_calendar_get_value(Pieform $form, $element) {/*{{{*/
return null;
}/*}}}*/
/**
* Retrieves the values of the internationalised strings for a calendar
* The $form is not passed in so that we can fetch this array from outside a pieform
* on the viewacl.tpl
*
* @param array $options The datepicker options array
* @return array $options The datepicker options array with the new lang strings added
*/
function pieform_element_calendar_get_lang_strings($options, $langdirection = 'ltr') {/*{{{*/
// Set up internationalisation
$lang_options = array('clearText','closeText','closeStatus','prevText','prevStatus',
'nextText','nextStatus','currentText','currentStatus',
'monthNames','monthNamesShort','monthStatus',
'yearStatus','weekHeader','weekStatus',
'dayNames','dayNamesShort','dayNamesMin','dayStatus',
'dateStatus','initStatus',
'timeOnlyTitle', 'timeText', 'hourText', 'minuteText', 'secondText',
'millisecText', 'timezoneText', 'amNames', 'pmNames');
foreach ($lang_options as $lang_option) {
$langopt = get_string('datepicker_' . $lang_option);
if (preg_match('/^\[(.*)\]$/', $langopt, $match)) {
$langopt = explode(',', $match[1]);
}
$options[$lang_option] = $langopt;
}
$options['isRTL'] = ($langdirection == 'rtl') ? true : false;
return $options;
}/*}}}*/
\ No newline at end of file
......@@ -1290,6 +1290,18 @@ a.persona-button:before {
a.pieform-calendar-toggle img {
margin-bottom: 3px;
}
/* WYSIWYG */
#resumewrap .pieform table.mceLayout td,
.pieform table.mceLayout td {
width: 100%;
padding: 0;
}
#resumewrap .pieform table.mceLayout td.mceToolbar td,
.pieform table.mceLayout td.mceToolbar td {
display: inline-block;
padding: 0;
width: auto;
}
/* tables */
.pieform th {
float: left;
......@@ -2065,4 +2077,4 @@ a.persona-button:before {
#header, #main-nav, #sub-nav, #mainmiddle, #footer {
min-width: 300px;
}
}
\ No newline at end of file
}
......@@ -2,6 +2,74 @@
.ui-datepicker {
font-size: 0.7em;
}
/* Styles for the new year prev/next css */
.ui-datepicker .ui-datepicker-prev-year,
.ui-datepicker .ui-datepicker-next-year {
height: 1.8em;
position: absolute;
top: 2px;
width: 1.8em;
}
.ui-datepicker .ui-datepicker-prev-year {
left: 2px;
}
.ui-datepicker .ui-datepicker-next-year {
right: 2px;
}
.ui-datepicker .ui-datepicker-prev-year span,
.ui-datepicker .ui-datepicker-next-year span {
display: block;
left: 50%;
margin-left: -7px;
margin-top: -8px;
position: absolute;
top: 50%;
}
.ui-datepicker .ui-datepicker-prev-year-hover,
.ui-datepicker .ui-datepicker-next-year-hover {
top: 1px;
}
.ui-datepicker .ui-datepicker-prev-year-hover span,
.ui-datepicker .ui-datepicker-next-year-hover span {
margin-left: -8px;
}
.ui-datepicker .ui-datepicker-next-year span {
margin-left: -9px;
}
.ui-datepicker .ui-datepicker-next-year-hover span {
margin-left: -8px;
}
/* Styles changes to get prev/next to work with year prev/next css */
.ui-datepicker .ui-datepicker-prev span,
.ui-datepicker .ui-datepicker-next span {
margin-left: -7px;
}
.ui-datepicker .ui-datepicker-prev-hover span,
.ui-datepicker .ui-datepicker-next-hover span {
margin-left: -8px;
}
.ui-datepicker .ui-datepicker-prev {
left: 23px;
}
.ui-datepicker .ui-datepicker-next {
right: 23px;
}
.ui-datepicker .ui-datepicker-next span {
margin-left: -9px;
}
.ui-datepicker .ui-datepicker-next-hover span {
margin-left: -8px;
}
.ui-datepicker button.ui-priority-secondary {
opacity: 1;
font-weight: 700;
}
.ui-icon-circle-triangle-wy {
background-position: -144px -192px;
}
.ui-icon-circle-triangle-ey {
background-position: -112px -192px;
}
/* Changes for timepicker */
.ui-timepicker-div .ui-widget-header {
......
......@@ -771,8 +771,9 @@ textarea.error {
input.calendar {
margin-right: 5px;
}
a.pieform-calendar-toggle img {
.ui-datepicker-trigger {
vertical-align: bottom;
cursor: pointer;
}
input.calendar.required {
background-color: #FFF2F2;
......
......@@ -169,8 +169,8 @@ function renderAccessListItem(item) {
var row = TR({'class': cssClass, 'id': 'accesslistitem' + count},
TD({'class': 'icon-container'}, icon),
TD({'class': 'accesslistname'}, name),
TD(null, makeCalendarInput(item, 'start', notpublicorallowed), makeCalendarLink(item, 'start', notpublicorallowed)),
TD(null, makeCalendarInput(item, 'stop', notpublicorallowed), makeCalendarLink(item, 'stop', notpublicorallowed)),
TD(null, makeCalendarInput(item, 'start', notpublicorallowed)),
TD(null, makeCalendarInput(item, 'stop', notpublicorallowed)),
TD({'class': 'center comments' + (allowcomments ? ' hidden' : '')}
, allowfdbk, allowfdbklabel, ' ', approvefdbk, approvefdbklabel),
TD({'class': 'right removebutton'}, removeButton,
......@@ -230,7 +230,7 @@ function renderAccessListItem(item) {
// Disable date inputs
$j(row).find("input[name*='startdate']").attr('disabled', 'disabled');
$j(row).find("input[name*='stopdate']").attr('disabled', 'disabled');
$j(row).find('.pieform-calendar-toggle').hide();
$j(row).find('.ui-datepicker-trigger').hide();
}
count++;
// Update the formchangechecker state
......@@ -259,52 +259,28 @@ function makeCalendarInput(item, type, disabled) {
return SPAN(null, label, input);
}
function makeCalendarLink(item, type) {
var link = A({
'href' : '',
'id' : type + 'date_' + count + '_btn',
'onclick': 'return false;', // @todo do with mochikit connect
'class' : 'pieform-calendar-toggle'},
IMG({
'src': '{{theme_url filename='images/btn_calendar.png'}}',
'alt': get_string('element.calendar.opendatepicker', 'pieforms')})
);
return link;
}
function setupCalendar(item, type) {
//log(type);
var dateStatusFunc, selectedFunc;
//if (type == 'start') {
// dateStatusFunc = function(date) {
// startDateDisallowed(date, $(item.id + '_stopdate'));
// };
// selectedFunc = function(calendar, date) {
// startSelected(calendar, date, $(item.id + '_startdate'), $(item.id + '_stopdate'));
// }
//}
//else {
// dateStatusFunc = function(date) {
// stopDateDisallowed(date, $(item.id + '_startdate'));
// };
// selectedFunc = function(calendar, date) {
// stopSelected(calendar, date, $(item.id + '_startdate'), $(item.id + '_stopdate'));
// }
//}
// var dateStatusFunc, selectedFunc;
if (!$(type + 'date_' + count)) {
logWarn('Couldn\'t find element: ' + type + 'date_' + count);
return;
}
Calendar.setup({
"ifFormat" :{{jstr tag=strftimedatetimeshort}},
"daFormat" :{{jstr tag=strftimedatetimeshort}},
"inputField": type + 'date_' + count,
"button" : type + 'date_' + count + '_btn',
//"dateStatusFunc" : dateStatusFunc,
//"onSelect" : selectedFunc
"onUpdate" : updateFormChangeChecker,
"showsTime" : true
var input = jQuery('#' + type + 'date_' + count).datetimepicker({
{{$datepickeroptions|safe}}
beforeShow: function(input, inst) {
setTimeout(function() {
add_prev_next_year(inst);
}, 1);
},
onChangeMonthYear: function(y, m, inst) {
setTimeout(function() {
add_prev_next_year(inst);
}, 1);
},
showOn: "both",
buttonImage: "{{theme_url filename='images/btn_calendar.png'}}",
buttonImageOnly: true,
buttonText: get_string('element.calendar.opendatepicker', 'pieforms'),
});
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment