Skip to content

Add new Date and Time picker (CFM-107) #10

Merged
merged 12 commits into from
Apr 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions app/resources/locales/en_US/field.po
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ msgstr "Country Code"
msgid "created"
msgstr "Created"

msgid "datepicker.am"
msgstr "AM"

msgid "datepicker.hour"
msgstr "Hour"

msgid "datepicker.pm"
msgstr "PM"

msgid "datepicker.minute"
msgstr "Minute"

msgid "date_of_birth"
msgstr "Date of Birth"

Expand Down
76 changes: 72 additions & 4 deletions app/src/View/Helper/FieldHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
use Cake\View\Helper;

class FieldHelper extends Helper {
public $helpers = ['Form', 'Html'];
public $helpers = ['Form', 'Html', 'Url'];

// Is this read-only or read-write?
protected $editable = true;
Expand Down Expand Up @@ -90,18 +90,86 @@ public function control(string $fieldName,
// Append the timezone to the label
$label = __d('field', $fieldName.".tz", [$this->_View->get('vv_tz')]);

// Render these fields as datepickers instead of plain text boxes
$coptions['class'] = 'datepicker-' . ($fieldName == 'valid_from' ? "f" : "u");
// A datetime field will be rendered as plain text input with adjacent date and time pickers
// that will interact with the field value. Allowing direct access to the input field is for
// accessibility purposes.
$coptions['class'] = 'form-control datepicker';
$coptions['placeholder'] = 'YYYY-MM-DD HH:MM:SS'; // TODO: test for date-only inputs and send only the date
$coptions['id'] = $fieldName;

$entity = $this->_View->get('vv_obj');

$pickerDate = '';
if(!empty($entity->$fieldName)) {
// Adjust the time back to the user's timezone
$coptions['value'] = $entity->$fieldName->i18nFormat("yyyy-MM-dd HH:mm:ss", $this->_View->get('vv_tz'));
$pickerDate = $entity->$fieldName->i18nFormat("yyyy-MM-dd", $this->_View->get('vv_tz'));
}

// Create a text field to hold our value.
$controlCode = $this->Form->text($fieldName, $coptions);
$liClass = " modelbox-data";

// Create a date/time picker. The yyyy-MM-dd format is set above in $pickerDate.
$pickerId = 'datepicker-' . $fieldName;
$pickerTarget = $fieldName;
$pickerTimed = true; // TODO: set false if date-only
$pickerAmPm = false; // TODO: allow change between AM/PM and 24-hour mode

$controlCode .= '
<script type="module">
import CmDateTimePicker from "' . $this->Url->script('comanage/components/datepicker/cm-datetimepicker.js') . '";
const app = Vue.createApp({
data() {
return {
id: "' . $pickerId . '",
target: "' . $pickerTarget . '",
date: "' . $pickerDate . '",
timed: ' . ($pickerTimed ? 'true' : 'false') . ',
ampm: ' . ($pickerAmPm ? 'true' : 'false') . ',
txt: {
hour: "' . __d('field', 'datepicker.hour') . '",
minute: "' . __d('field', 'datepicker.minute') . '",
am: "' . __d('field', 'datepicker.am') . '",
pm: "' . __d('field', 'datepicker.pm') . '"
}
}
},
components: {
CmDateTimePicker
}
});
// Add custom global directives available to all child components.
// "clickout" allows us to pass a function to a click outside behavior which
// is registered and destroyed as the component is mounted and unmounted.
app.directive("clickout", {
mounted(el, binding, vnode) {
el.clickOutEvent = function(event) {
if (!(el === event.target || el.contains(event.target))) {
binding.value(event, el);
}
};
document.body.addEventListener("click", el.clickOutEvent);
},
unmounted(el) {
document.body.removeEventListener("click", el.clickOutEvent);
}
});
app.mount("#' . $pickerId . '-container");
</script>
<div id="' . $pickerId . '-container">
<cm-date-time-picker
:id="id"
:target="target"
:date="date"
:timed="timed"
:ampm="ampm"
:txt="txt">
</cm-date-time-picker>
</div>';

$liClass = "fields-datepicker";
} else {
if($fieldName != 'status'
&& !isset($options['empty'])
Expand Down
46 changes: 3 additions & 43 deletions app/templates/element/javascript.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,50 +169,10 @@
}
});

// Datepickers

<?php /* For all calls to datepicker, wrap the calling date field in a
container of class .modelbox-data: this allows us to show the datepicker next to
the appropriate field because jQuery drops the div at the bottom of the body and
that approach doesn't work well with Material Design Light (MDL). If you do not
do this, the datepicker will float up to the top of the browser window. See
app/View/CoGroupMembers for an example. */ ?>

/* XXX CFM-107 Hide datepickers until replaced with new approach
$(".datepicker-f").datepicker({
changeMonth: true,
changeYear: true,
dateFormat: "yy-mm-dd 00:00:00",
numberOfMonths: 1,
showButtonPanel: false,
showOtherMonths: true,
selectOtherMonths: true,
onSelect: function(selectedDate) {
$(this).closest('.mdl-textfield').addClass('is-dirty');
}
}).bind('click',function () {
$("#ui-datepicker-div").appendTo($(this).closest('.modelbox-data'));
});

$(".datepicker-u").datepicker({
changeMonth: true,
changeYear: true,
dateFormat: "yy-mm-dd 23:59:59",
numberOfMonths: 1,
showButtonPanel: false,
showOtherMonths: true,
selectOtherMonths: true,
onSelect: function(selectedDate) {
$(this).closest('.mdl-textfield').addClass('is-dirty');
}
}).bind('click',function () {
$("#ui-datepicker-div").appendTo($(this).closest('.modelbox-data'));
});
*/

// Add loading animation when a form is submitted, when any item with a "spin" class is clicked,
// or on any button or anchor tag lacking the .nospin class.
$("input[type='submit'], button:not(.nospin), a:not(.nospin), .spin").click(function() {
// or on any anchor tag lacking the .nospin class. We do not automatically add this to buttons
// because they are often on-page controls. Add a "spin" class to buttons that need it.
$("input[type='submit'], a:not('.nospin'), .spin").click(function() {

displaySpinner();

Expand Down
13 changes: 9 additions & 4 deletions app/templates/layout/default.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
<!DOCTYPE html>
<html lang="<?= __('registry.meta.lang'); ?>">
<head>
<?= $this->Html->meta('viewport', 'width=device-width, initial-scale=1, shrink-to-fit=no') . "\n"; ?>
<?= $this->Html->meta('viewport', 'width=device-width, initial-scale=1.0') . "\n"; ?>
<?= $this->Html->charset(); ?>

<title><?= (!empty($vv_title) ? $vv_title : __('registry.meta.registry')); ?></title>
Expand Down Expand Up @@ -191,7 +191,7 @@ class="toast-container"
<?= $this->element('dialog'); ?>

<!-- Get timezone detection -->
<?php print $this->Html->script('jstimezonedetect/jstz.min.js'); ?>
<?= $this->Html->script('jstimezonedetect/jstz.min.js'); ?>
<script>
// Determines the time zone of the browser client
var tz = jstz.determine();
Expand All @@ -202,11 +202,16 @@ class="toast-container"

<!-- Load Javascript -->
<!-- XXX js-cookie should be deprecated -->
<?= $this->Html->script([
<?= $this->Html->script([
'vue/vue-3.2.31.global.prod.js',
'js-cookie/js.cookie-2.1.3.min.js',
'comanage.js'
'comanage/comanage.js'
]) . "\n"; ?>

<!-- Duet Datepicker should be loaded as a module -->
<?= $this->Html->script('duet-datepicker/duet/duet.esm.js',['type' => 'module']); ?>
<?= $this->Html->script('duet-datepicker/duet/duet.js',['nomodule' => '']); ?>

<!-- COmanage JavaScript onload scripts -->
<?php print $this->element('javascript'); ?>

Expand Down
123 changes: 104 additions & 19 deletions app/webroot/css/co-base.css
Original file line number Diff line number Diff line change
Expand Up @@ -1082,31 +1082,116 @@ ul.form-list li.field-stack textarea {
display: inline;
margin: 0;
}
/* Ensure datepicker renders properly.
We are using javascript to move it near its input field so
that it will scroll properly on mobile devices. */
#ui-datepicker-div table tr > *:first-child,
#ui-datepicker-div table tr > *:nth-child(2) {
min-width: 0;
#content .material-icons {
font-size: 17px;
margin-top: 1px;
vertical-align: top;
}
/* DATE and TIME PICKERS */
.cm-datetime-picker {
display: flex;
align-items: center;
margin-left: 2.8em;
}
.duet-date__toggle {
width: 30px;
height: 30px;
background-color: transparent;
border-radius: 0;
margin: -15px 0 0;
box-shadow: none;
}
.duet-date__toggle:focus {
border: 1px dotted var(--cmg-color-black);
box-shadow: 0 0 0 .25rem rgba(13,110,253,.25); /* override Duet to be same as Bootstrap */
}
table.duet-date__table tr th:first-child,
table.duet-date__table tr td:first-child,
table.duet-date__table th,
table.duet-date__table td {
padding: unset;
}
table.duet-date__table th.duet-date__table-header {
padding: 8px;
text-align: center;
}
#ui-datepicker-div table tr td:first-child {
padding-left: 1px;
.cm-time-picker button {
width: 30px;
margin: 0;
padding: 0;
}
#ui-datepicker-div {
top: 0 !important;
#content .cm-time-picker .material-icons {
font-size: 1.5em;
margin: 0;
}
.ui-datepicker select.ui-datepicker-month-year {
width: 100%;
.duet-date__input {
display: none;
}
.ui-datepicker select.ui-datepicker-month,
.ui-datepicker select.ui-datepicker-year {
width: 49%;
ul.fields li.fields-datepicker {
overflow: unset;
}
#content .material-icons {
font-size: 17px;
margin-top: 1px;
vertical-align: top;
ul.fields li.fields-datepicker .field-info {
width: auto;
display: flex;
align-items: center;
}
.cm-time-picker-panel {
position: absolute;
z-index: 100;
right: 0;
width: auto;
background-color: white;
border: 1px solid var(--cmg-color-lightgray-006);
text-align: center;
border-radius: 4px;
align-items: center;
box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.1);
}
.cm-time-picker-title {
padding: 0.25em;
border-bottom: 1px solid var(--cmg-color-lightgray-005);
font-size: 1.1em;
}
.cm-time-picker-vals ul {
display: grid;
/* Mobile view is long. The grid templates are reversed for desktop. */
grid-template-columns: repeat(6,40px);
grid-template-rows: repeat(4,40px);
padding: 0;
align-content: center;
}
.cm-time-picker-minutes .cm-time-picker-vals ul {
grid-template-columns: repeat(4,40px);
grid-template-rows: repeat(1,40px);
}
ul.form-list .cm-time-picker-vals li {
display: block;
padding: 4px 0 0 0;
}
.cm-time-picker-vals button {
background-color: transparent;
border: none;
width: 30px;
height: 30px;
font-size: 0.9rem;
margin: 0;
}
.cm-time-picker-vals button:focus {
background-color: var(--cmg-color-blue-003);
color: var(--cmg-color-white);
border-radius: 14px;
}
.cm-time-picker-colon {
padding: 0 1em;
}
/* Vue transitions */
.v-enter-active,
.v-leave-active {
transition: opacity 0.2s;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
/* DIALOG BOX */
#dialog .modal-header {
Expand Down
17 changes: 17 additions & 0 deletions app/webroot/css/co-color.css
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,21 @@

--cmg-color-white: #fff; /* white */
--cmg-color-black: #000; /* black */

/* Duet Date Picker Colors */
--duet-color-primary: #005fcc;
--duet-color-text: #333;
--duet-color-text-active: #fff;
--duet-color-placeholder: #666;
--duet-color-button: #f5f5f5;
--duet-color-surface: #fff;
--duet-color-overlay: rgba(0, 0, 0, 0.8);
--duet-color-border: #333;

--duet-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
--duet-font-normal: 400;
--duet-font-bold: 600;

--duet-radius: 4px;
--duet-z-index: 600;
}
18 changes: 17 additions & 1 deletion app/webroot/css/co-responsive.css
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@
#pagination.with-pagination-elements .paginationCounter {
margin-top: 2px;
}
/* DATE / TIME PICKER */
.duet-date__dialog {
left: -20em;
}
}

/* MEDIUM - Primary breakpoint */
Expand Down Expand Up @@ -276,7 +280,19 @@
/**************************************************************************************************************/
/* Medium devices (desktops, 992px and up) */
@media only screen and (min-width: 992px) {

/* DATE / TIME PICKER */
.cm-time-picker-panel {
right: -100%;
display: flex;
}
.cm-time-picker-vals ul {
grid-template-columns: repeat(12, 40px);
grid-template-rows: repeat(2, 40px);
}
.cm-time-picker-minutes .cm-time-picker-vals ul {
grid-template-columns: repeat(2,40px);
grid-template-rows: repeat(2,40px);
}
}

/* EXTRA LARGE */
Expand Down
1 change: 1 addition & 0 deletions app/webroot/js/bootstrap/bootstrap.bundle.min.js.map

Large diffs are not rendered by default.

File renamed without changes.
Loading