Commit 8d796e7c authored by Liam's avatar Liam

Bug #1744191: Updated the datepicker to use the bootstrap one

Includes accessibility features
Includes ability to translate tooltips
Have picker div appear inside a <div> rather than <span> for valid
markup

behatnotneeded

Change-Id: I7e4dfb5026cfa579145ca4c932ad673690a8c55a
parent c3dd695b
Bootstrap Datetimepicker
========================
Website: https://eonasdan.github.io/bootstrap-datetimepicker/
Version: 4.17.47
The bootstrap datetimepicker makes it easy to select dates and times on input fields by using a popup selector. It runs using bootstrap and moment.js.
Changes:
- Added aria-labels
- Added function, press h, to switch between the date and time picker pages
Moment.js
=========
Website: http://momentjs.com/
Version: 2.20.1
Moment.js makes use of javascript to parse, validate, manipulate, and display dates and times in Mahara. It is used in conjunction with the bootstrap datetimepicker.
Changes: none
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -637,37 +637,31 @@ $string['years'] = 'years';
$string['year'] = 'year';
// 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']";
$string['datepicker_today'] = "Go to today";
$string['datepicker_clear'] = "Clear selection";
$string['datepicker_close'] = "Close the picker";
$string['datepicker_selectMonth'] = "Select month";
$string['datepicker_prevMonth'] = "Previous month";
$string['datepicker_nextMonth'] = "Next month";
$string['datepicker_selectYear'] = "Select year";
$string['datepicker_prevYear'] = "Previous year";
$string['datepicker_nextYear'] = "Next year";
$string['datepicker_selectDecade'] = "Select decade";
$string['datepicker_prevDecade'] = "Previous decade";
$string['datepicker_nextDecade'] = "Next decade";
$string['datepicker_prevCentury'] = "Previous century";
$string['datepicker_nextCentury'] = "Next century";
$string['datepicker_pickHour'] = "Pick hour";
$string['datepicker_incrementHour'] = "Increment hour";
$string['datepicker_decrementHour'] = "Decrement hour";
$string['datepicker_pickMinute'] = "Pick minute";
$string['datepicker_incrementMinute'] = "Increment minute";
$string['datepicker_decrementMinute'] = "Decrement minute";
$string['datepicker_pickSecond'] = "Pick second";
$string['datepicker_incrementSecond'] = "Increment second";
$string['datepicker_decrementSecond'] = "Decrement second";
$string['datepicker_togglePeriod'] = "Toggle period";
$string['datepicker_selectTime'] = "Select time";
$string['timelapsestringhour'] = array(
0 => '%2$s hour %s min ago',
......
......@@ -180,7 +180,7 @@ function pieform_element_viewacl(Pieform $form, $element) {
$options = array('stepHour' => 1,
'stepMinute' => 5,
);
$options = pieform_element_calendar_get_lang_strings($options, $LANGDIRECTION);
$tooltips = pieform_element_calendar_tooltip_lang_strings();
$datepickeroptionstr = '';
foreach ($options as $key => $option) {
if (is_numeric($option)) {
......@@ -204,6 +204,7 @@ function pieform_element_viewacl(Pieform $form, $element) {
}
$smarty->assign('datepickeroptions', $datepickeroptionstr);
$smarty->assign('datepickertooltips', json_encode($tooltips));
$smarty->assign('viewtype', $element['viewtype']);
$smarty->assign('potentialpresets', json_encode($allowedpresets));
$smarty->assign('loggedinindex', $loggedinindex);
......
......@@ -45,7 +45,6 @@ function pieform_element_calendar(Pieform $form, $element) {
$options = array_merge($element['caloptions'], array('inputField' => $id));
$options['dateFormat'] = pieform_element_calendar_convert_dateformat(get_string('pieform_calendar_dateformat', 'langconfig'));
$options['timeFormat'] = pieform_element_calendar_convert_timeformat(get_string('pieform_calendar_timeformat', 'langconfig'));
$options = pieform_element_calendar_get_lang_strings($options, $LANGDIRECTION);
$value = $form->get_value($element);
if ($value) {
if (!empty($options['showsTime'])) {
......@@ -66,58 +65,30 @@ function pieform_element_calendar(Pieform $form, $element) {
var input_' . $id . ' = jQuery("input#' . $id . '");
';
if (!empty($options['showsTime'])) {
$result .= 'input_' . $id . '.datetimepicker({';
$result .= 'input_' . $id . '.datetimepicker({
format: "' . $options['dateFormat'] . ' ' . $options['timeFormat'] . '",';
}
else {
$result .= 'input_' . $id . '.datepicker({';
$result .= 'input_' . $id . '.datetimepicker({
format: "' . $options['dateFormat'] . '",';
}
$result .= ' onSelect: function(date) {
if (typeof formchangemanager !== \'undefined\') {
var form = input_' . $id . '.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);
}
}
$option = '[' . implode(',', $option) . ']';
$result .= $key . ': ' . $option . ',';
}
else {
$result .= $key . ': ' . json_encode($option) . ',';
}
}
// Adding prev / next year buttons
$tooltips = json_encode(pieform_element_calendar_tooltip_lang_strings());
$result .= '
beforeShow: function(input, inst) {
setTimeout(function() {
add_prev_next_year(inst);
}, 1);
// We only need to add an in-modal class if element is within a modal
$(inst.dpDiv).removeClass("in-modal");
if ($(input).hasClass("in-modal")) {
$(inst.dpDiv).addClass("in-modal");
locale: "' . strstr(current_language(), '.', true) . '",
useCurrent: false,
showClear: true,
showTodayButton: true,
tooltips: ' . $tooltips . ',
icons: {
clear: "icon icon-trash",
today: "icon icon-crosshairs",
},
}).on("dp.hide", function(selectedDate) {
if (typeof formchangemanager !== \'undefined\') {
var form = input_' . $id . '.closest(\'form\')[0];
formchangemanager.setFormState(form, FORM_CHANGED);
}
},
onChangeMonthYear: function(y, m, inst) {
setTimeout(function() {
add_prev_next_year(inst);
}, 1);
},
';
$result .= '
});
});
</script>';
return $result;
......@@ -206,15 +177,10 @@ function pieform_element_calendar_convert_dateformat($format) {
$replacements = array(
'%e' => 'd', // day of month (no leading zero)
'%d' => 'dd', // day of month (two digit)
'%m' => 'mm', // month of year (two digit)
'%d' => 'DD', // day of month (two digit)
'%m' => 'MM', // month of year (two digit)
'%y' => 'y', // year (two digit)
'%Y' => 'yy', // year (four digit)
// strtotime only works in English. So no non-digit formats
// '%a' => 'D', // day name short (Mon - Sun)
// '%A' => 'DD', // day name long (Monday - Sunday)
// '%b' => 'M', // month name short (Jan - Dec)
// '%B' => 'MM', // month name long (January - December)
'%Y' => 'YYYY', // year (four digit)
);
return str_replace(
array_keys($replacements),
......@@ -241,13 +207,13 @@ function pieform_element_calendar_convert_timeformat($format) {
$replacements = array(
'%k' => "H", // Hour (24-hour, no leading 0)
'%H' => 'HH', // Hour (24-hour, 2 digits)
'%H' => 'hh', // Hour (24-hour, 2 digits)
'%l' => "h", // Hour (12-hour, no leading 0)
'%I' => 'hh', // Hour (12-hour, 2 digits)
'%M' => 'mm', // Minute (2 digits)
'%S' => 'ss', // Second (2 digits)
'%P' => 'tt', // am or pm for AM/PM
'%p' => 'TT', // AM or PM for AM/PM
'%S' => 'a', // Second (2 digits)
'%P' => 't', // am or pm for AM/PM
'%p' => 'T', // AM or PM for AM/PM
);
return str_replace(
array_keys($replacements),
......@@ -281,38 +247,19 @@ function pieform_element_calendar_set_attributes($element) {
function pieform_element_calendar_get_headdata($element) {
global $THEME;
$themefile = $THEME->get_url('style/datepicker.css');
$libjs = $element['jsroot'] . 'js/jquery-ui.min.js';
$libcss = $element['jsroot'] . 'css/smoothness/jquery-ui.min.css';
$timeaddonjs = $element['jsroot'] . 'js/jquery-ui-timepicker-addon.js';
$bootstrapdatetimejs = '/js/bootstrap-datetimepicker/bootstrap-datetimepicker.min.js';
$momentjs = '/js/momentjs/moment-with-locales.min.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" role="presentation" aria-hidden="true">$prev</span></a>');
prevYrBtn.off("click").on("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" role="presentation" aria-hidden="true">$next</span></a>');
nextYrBtn.off("click").on("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="' . append_version_number($libcss) . '">',
'<link rel="stylesheet" type="text/css" media="all" href="' . append_version_number($themefile) . '">',
'<script type="application/javascript" src="' . append_version_number($libjs) . '"></script>',
'<script type="application/javascript" src="' . append_version_number($timeaddonjs) . '"></script>',
'<script type="application/javascript">' . $extrajs . '</script>',
'<script type="application/javascript" src="' . append_version_number($momentjs) . '"></script>',
'<script type="application/javascript" src="' . append_version_number($bootstrapdatetimejs) . '"></script>'
);
return $result;
}
......@@ -366,7 +313,7 @@ function pieform_element_calendar_convert_to_epoch($date) {
// (See http://php.net/manual/en/function.strtotime.php#refsect1-function.strtotime-notes)
$dateformat = get_string('pieform_calendar_dateformat', 'langconfig');
if (preg_match('/%[ed].*%[m].*%[yY]/', $dateformat)) {
$value = strtotime(preg_replace('/[^0-9]/', '.', $date));
$value = strtotime(preg_replace('/[^0-9]/', '.', $date));
}
// If that didn't work, then just try doing strtotime on the plain value
......@@ -384,30 +331,23 @@ function pieform_element_calendar_convert_to_epoch($date) {
}
/**
* Retrieves the values of the internationalised strings for a calendar
* Retrieves the values of the internationalised tooltip 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
* @return array $tooltips The datepicker tooltip 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]);
function pieform_element_calendar_tooltip_lang_strings() {
$tooltips = array();
$tooltip_options = array(
'today', 'clear', 'close', 'selectMonth', 'prevMonth', 'nextMonth', 'selectYear', 'prevYear', 'nextYear',
'selectDecade', 'prevDecade', 'nextDecade', 'prevCentury', 'nextCentury',
'pickHour', 'incrementHour', 'decrementHour', 'pickMinute', 'incrementMinute', 'decrementMinute',
'pickSecond', 'incrementSecond', 'decrementSecond', 'togglePeriod', 'selectTime');
foreach ($tooltip_options as $tooltip) {
if (string_exists('datepicker_' . $tooltip)) {
$tooltips[$tooltip] = get_string('datepicker_' . $tooltip);
}
$options[$lang_option] = $langopt;
}
$options['isRTL'] = ($langdirection == 'rtl') ? true : false;
return $options;
return $tooltips;
}
/*!
* Datetimepicker for Bootstrap 3
* version : 4.17.47
* https://github.com/Eonasdan/bootstrap-datetimepicker/
*/
.bootstrap-datetimepicker-widget {
list-style: none;
}
.bootstrap-datetimepicker-widget.dropdown-menu {
display: block;
margin: 2px 0;
padding: 4px;
width: 19em;
}
@media (min-width: $screen-sm) {
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
width: 38em;
}
}
@media (min-width: $screen-md) {
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
width: 38em;
}
}
@media (min-width: $screen-lg) {
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
width: 38em;
}
}
.bootstrap-datetimepicker-widget.dropdown-menu:before,
.bootstrap-datetimepicker-widget.dropdown-menu:after {
content: '';
display: inline-block;
position: absolute;
}
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before {
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid #ccc;
border-bottom-color: rgba(0, 0, 0, 0.2);
top: -7px;
left: 7px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after {
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid white;
top: -6px;
left: 8px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.top:before {
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-top: 7px solid #ccc;
border-top-color: rgba(0, 0, 0, 0.2);
bottom: -7px;
left: 6px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.top:after {
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid white;
bottom: -6px;
left: 7px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before {
left: auto;
right: 6px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after {
left: auto;
right: 7px;
}
.bootstrap-datetimepicker-widget .list-unstyled {
margin: 0;
}
.bootstrap-datetimepicker-widget a[data-action] {
padding: 6px 0;
}
.bootstrap-datetimepicker-widget a[data-action]:active {
box-shadow: none;
}
.bootstrap-datetimepicker-widget .timepicker-hour,
.bootstrap-datetimepicker-widget .timepicker-minute,
.bootstrap-datetimepicker-widget .timepicker-second {
width: 54px;
font-weight: bold;
font-size: 1.2em;
margin: 0;
}
.bootstrap-datetimepicker-widget button[data-action] {
padding: 6px;
}
.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Increment Hours";
}
.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Increment Minutes";
}
.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Decrement Hours";
}
.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Decrement Minutes";
}
.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Show Hours";
}
.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Show Minutes";
}
.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Toggle AM/PM";
}
.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Clear the picker";
}
.bootstrap-datetimepicker-widget .btn[data-action="today"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Set the date to today";
}
.bootstrap-datetimepicker-widget .picker-switch {
text-align: center;
}
.bootstrap-datetimepicker-widget .picker-switch::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Toggle Date and Time Screens";
}
.bootstrap-datetimepicker-widget .picker-switch td {
padding: 0;
margin: 0;
height: auto;
width: auto;
line-height: inherit;
}
.bootstrap-datetimepicker-widget .picker-switch td span {
line-height: 2.5;
height: 2.5em;
width: 100%;
font-size: 18px;
}
.bootstrap-datetimepicker-widget table {
width: 100%;
margin: 0;
}
.bootstrap-datetimepicker-widget table td,
.bootstrap-datetimepicker-widget table th {
text-align: center;
border-radius: 4px;
}
.bootstrap-datetimepicker-widget table th {
height: 20px;
line-height: 20px;
width: 20px;
}
.bootstrap-datetimepicker-widget table th.picker-switch {
width: 145px;
}
.bootstrap-datetimepicker-widget table th.disabled,
.bootstrap-datetimepicker-widget table th.disabled:hover {
background: none;
color: #777777;
cursor: not-allowed;
}
.bootstrap-datetimepicker-widget table th.prev::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Previous Month";
}
.bootstrap-datetimepicker-widget table th.next::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Next Month";
}
.bootstrap-datetimepicker-widget table thead tr:first-child th {
cursor: pointer;
}
.bootstrap-datetimepicker-widget table thead tr:first-child th:hover {