How to bind dates in input type=date

Hi
I’m trying to show in this tag this date
<input type=“date” value.bind="currentProject.planningEndDate | formatDate:‘yyyy/MM/dd’>
but always get this error:

The specified value “yyyy/11/Th” does not conform to the required format, “yyyy-MM-dd”.

I got this value converter too:

import * as moment from “Moment”;
import { Moment } from “Moment”;

export class FormatDateValueConverter {
toView(value: Moment | any, format: string): string {
let valueAsMoment: Moment;

if (!moment.isMoment(value)) {
  valueAsMoment = moment(value);
} else {
  valueAsMoment = value;
}

return valueAsMoment.format(format);

}
}

Any idea please?

Regards

Example I have used before (simplified to remove error handling):

import moment from 'moment';

export class DateFormatValueConverter {
  toView(date, dateFormat) {
    return moment(date).format(dateFormat);
  }
}
<span>Some date : ${myDate | dateFormat: 'DD/MM/YYYY''}</span>

Of course, if it does not need to be configurable from the view, you can hard-code the format into the value converter.

export class DateFormatValueConverter {
  toView(date) {
    return moment(date).format('DD/MM/YYYY'');
  }
}
<span>Some date : ${myDate | dateFormat}</span>

Two issues:

  1. input type date has fixed format, the value must be

A DOMString representing a date in YYYY-MM-DD format, or empty

It’s confusing to see what Chrome displays. See comments in my code.

  1. for two-way binding, you need both toView and fromView methods to translate moment object to DOM string and backward.
1 Like

Ah - did not notice you were using:

<input type=“date”>

Your gist works fine with:

<input type=“text”>

Given the limitations, goes anyone really use native date inputs?

There is some info here: html - Is there any way to change input type="date" format? - Stack Overflow

1 Like

@stevies Nice question. @huochunpeng 's suggestion didn’t work for you, or you want to know more what else are being used in the wild?

Not me who needs this. Was just wondering if anyone has ever used a native date control given they are horrible looking, impossible to style and come with lots of limitations. I have never seen one used, and have no plans to ever use one :-}

I’ve been using native controls, and I prefer it over many other libs. Despite being impossible to style / apply theme, they come with great capability of locale, mobile native friendliness, usability and probably dwarf all custom made libs, and they are automatically upgraded overtime with browser agents progress. And to me, and the applications I have (luckily) been doing so far, date picker look is the least important, plus they don’t look too bad :slight_smile:

here is a gif of what satisfies me and my client :smiley: (mouse wheel, date range, month range, year range, spinner buttons, dropdown button, keyboard input friendly)
native%20datepicker

2 Likes

I don’t like native input either. I wrapped around https://github.com/Eonasdan/bootstrap-datetimepicker and customised it to bootstrap-4 style,
For bootstrap-4 this one requires less work https://github.com/tempusdominus/bootstrap-4, but I didn’t migrate since the API changed a lot.

Did not publish any code for general usage, because my component only exposes enough features for me to use, plus some customised css to match my apps.

Short enough to share the code here.

date-input.js

import {bindable, bindingMode, containerless, computedFrom} from 'aurelia-framework';

// still using containerless
// because of supporting "subfix" icon.
@containerless()
export class DateInput {
  @bindable({defaultBindingMode: bindingMode.twoWay}) value;
  @bindable({defaultBindingMode: bindingMode.oneTime}) format = 'DD/MM/YYYY';
  @bindable({defaultBindingMode: bindingMode.oneTime}) toTop = false;
  @bindable class = '';
  @bindable minDate = false;
  @bindable maxDate = false;
  @bindable placeholder = '';
  @bindable defaultFocus = false;

  constructor() {
    this.show = this.show.bind(this);
  }

  valueChanged(newValue, oldValue) {
    const picker = $(this.el).data("DateTimePicker");
    if (picker) picker.date(this.value || null);
  }

  attached() {
    let opts = {
      format: this.format,
      minDate: this._minDate,
      maxDate: this._maxDate
    };
    if (this.toTop) opts.widgetPositioning = {vertical: 'top'};

    $(this.el).datetimepicker(opts);
    $(this.el).data("DateTimePicker").date(this.value);
    $(this.el).on('dp.change', e => {
      const {date} = e;
      this.value = date ? date.format(this.format) : '';
    });
    $(this.subfix).on('click', this.show);
    $(this.el).on('click', this.show);
  }

  show(event) {
    if (event && event.stopPropagation) {
      event.stopPropagation();
    }
    const picker = $(this.el).data("DateTimePicker");
    if (picker) picker.show();
  }

  detached() {
    const picker = $(this.el).data("DateTimePicker");
    if (picker) picker.destroy();
  }

  @computedFrom('minDate', 'format')
  get _minDate() {
    let {minDate, format} = this;
    if (!minDate) return false;

    if (!moment.isMoment(minDate)) {
      minDate = moment(minDate, format);
    }

    if (minDate.isValid()) {
      return minDate;
    } else {
      return false;
    }
  }

  @computedFrom('maxDate', 'format')
  get _maxDate() {
    let {maxDate, format} = this;
    if (!maxDate) return false;

    if (!moment.isMoment(maxDate)) {
      maxDate = moment(maxDate, format);
    }

    if (maxDate.isValid()) {
      return maxDate;
    } else {
      return false;
    }
  }
}

date-input.html

<template>
  <input
    ref="el"
    type="text"
    class.bind="class"
    placeholder.bind="placeholder"
    autocomplete="off"
    focus.one-time="defaultFocus">

  <div class="fas fa-th subfix" ref="subfix"></div>
</template>

_bootstrap-datetimepicker.scss
partial scss file, doesn’t include the subfix style in form field. I use FontAwesome5pro to replace missing glyphicon in bootstrap v4.

.glyphicon {
    position: relative;
    top: 1px;
    display: inline-block;
    font-family: 'Font Awesome 5 Pro';
    font-style: normal;
    font-weight: normal;
    line-height: 1;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

.glyphicon-time::before {
    content: "\f017";
}

.glyphicon-chevron-left::before {
    content: "\f053";
}

.glyphicon-chevron-right::before {
    content: "\f054";
}

.glyphicon-chevron-up::before {
    content: "\f077";
}

.glyphicon-chevron-down::before {
    content: "\f078";
}

.glyphicon-calendar::before {
    content: "\f073";
}

/*!
 * 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: 768px) {
  .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
    width: 38em;
  }
}
@media (min-width: 992px) {
  .bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
    width: 38em;
  }
}
@media (min-width: 1200px) {
  .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 {
  float: right;
}
.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%;
}
.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: $gray-300;
  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 {
  background: #eeeeee;
}
.bootstrap-datetimepicker-widget table td {
  height: 54px;
  line-height: 54px;
  width: 54px;
  font-weight: 500;
}
.bootstrap-datetimepicker-widget table td.cw {
  font-size: .8em;
  height: 20px;
  line-height: 20px;
  color: #777777;
}
.bootstrap-datetimepicker-widget table td.day {
  height: 20px;
  line-height: 20px;
  width: 20px;
}
.bootstrap-datetimepicker-widget table td.day:hover,
.bootstrap-datetimepicker-widget table td.hour:hover,
.bootstrap-datetimepicker-widget table td.minute:hover,
.bootstrap-datetimepicker-widget table td.second:hover {
  background: #eeeeee;
  cursor: pointer;
}
.bootstrap-datetimepicker-widget table td.old,
.bootstrap-datetimepicker-widget table td.new {
  color: #777777;
}
.bootstrap-datetimepicker-widget table td.today {
  position: relative;
}
.bootstrap-datetimepicker-widget table td.today::before {
  content: '';
  display: inline-block;
  border: solid transparent;
  border-width: 0 0 7px 7px;
  border-bottom-color: #337ab7;
  border-top-color: rgba(0, 0, 0, 0.2);
  position: absolute;
  bottom: 4px;
  right: 4px;
}
.bootstrap-datetimepicker-widget table td.active,
.bootstrap-datetimepicker-widget table td.active:hover {
  background-color: #337ab7;
  color: #fff;
  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.bootstrap-datetimepicker-widget table td.active.today::before {
  border-bottom-color: #fff;
}
.bootstrap-datetimepicker-widget table td.disabled,
.bootstrap-datetimepicker-widget table td.disabled:hover {
  background: none;
  color: $gray-300;
  cursor: not-allowed;
}
.bootstrap-datetimepicker-widget table td span {
  display: inline-block;
  width: 54px;
  height: 54px;
  line-height: 54px;
  margin: 2px 1.5px;
  cursor: pointer;
  border-radius: 4px;
}
.bootstrap-datetimepicker-widget table td span:hover {
  background: #eeeeee;
}
.bootstrap-datetimepicker-widget table td span.active {
  background-color: #337ab7;
  color: #fff;
  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.bootstrap-datetimepicker-widget table td span.old {
  color: #777777;
}
.bootstrap-datetimepicker-widget table td span.disabled,
.bootstrap-datetimepicker-widget table td span.disabled:hover {
  background: none;
  color: $gray-300;
  cursor: not-allowed;
}
.bootstrap-datetimepicker-widget.usetwentyfour td.hour {
  height: 27px;
  line-height: 27px;
}
.bootstrap-datetimepicker-widget.wider {
  width: 21em;
}

.bootstrap-datetimepicker-widget .datepicker-decades .decade {
  line-height: 1.8em !important;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
}
1 Like

Hi @bigopon could you send me the resume code of your native control? I do not need anything more than to be able to show a date collected from an API and modify it. Without more.
what I see that your native control makes me better off

Thanks

I believe @bigopon did not write many code :slight_smile: rather than the basic value converter plus basic bootstrap style. His picker UI is provided by Chrome.

@kintela It’s the same with the gist provided by @huochunpeng if you only want string. If you want it to be always Date or Moment instance , then it involves a bit more code

I need to show the date but also allow it to be edited and the best way is with a calendar no?

@bigopon your gif is using a simple <input type=“date”…>???
If you need to initialize the calendar with a value???

Thanks