Skip to content

Commit

Permalink
Add new Date and Time picker (CFM-107) (#10)
Browse files Browse the repository at this point in the history
* First working implementation of Date and Time pickers (CFM-107)

* Include Duet Datepicker library (CFM-107)

* Ensure that Time Picker is using internationalized strings (CFM-107)

* Remove FrozenTime inclusion that was added by IDE (CFM-107)

* Finish i18n string substitution for Time Picker (CFM-107)

* Provide "click outside" behavior for Time Picker (CFM-107)

* Set mobile/desktop responsive behaviors for Date and Time pickers (CFM-107)

* Ensure all properties are passed into the date/time component from their PHP equivalents (CFM-107)

* Remove unused property (CFM-107)

* Remove unused property (one more location) (CFM-107)

* Remove unused property (one more location) (CFM-107)

* Group datepicker texts in field.po (CFM-107)
  • Loading branch information
arlen authored Apr 6, 2022
1 parent 6a758aa commit 2863dbd
Show file tree
Hide file tree
Showing 27 changed files with 701 additions and 71 deletions.
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

0 comments on commit 2863dbd

Please sign in to comment.