Umbraco Forms - Datepicker Default

Hi

I’m currently trying to create a Date of Birth picker with the default Umbraco Forms v17.5.0 setup.

I’ve specified min an max dates, but the default date still loads as today. Which is obviously unsuitable for a DOB picker.

How can I set the date shown when the picker loads? I’ve already overwrote the default renderer, and tried specifying the value of the field on load, as well as adding a data-default-date parameter to the text picker. Is there a way to do this?

Hi @jonathoncove3

I can’t see anything out-of-the-box that would allow you to do this, and the date picker field lets you set the range but not the default date.

You could either roll your own field type based on the existing date picker field that gives you complete control to do something like this, or amend the date picker field type partial.

A suggestion from AI would be something like this:

@model Umbraco.Forms.Web.Models.FieldViewModel
@using Umbraco.Forms.Core.Providers.FieldTypes
@using Umbraco.Forms.Core.Services
@using Umbraco.Forms.Web
@using Umbraco.Forms.Core.Extensions
@inject IPlaceholderParsingService PlaceholderParsingService
@{
    var ariaLabel = Model.GetSettingValue<string>("AriaLabel", string.Empty);
    if (!string.IsNullOrWhiteSpace(ariaLabel))
    {
        ariaLabel = ariaLabel.ParsePlaceHolders(PlaceholderParsingService, false);
    }

    string val = Model.Values?.LastOrDefault()?.ToString() ?? string.Empty;
    if (Model.ValueAsObject != null && !Equals(Model.ValueAsObject, string.Empty))
    {
        try
        {
            DateTime d;
            d = (DateTime)Model.ValueAsObject;
            val = d.ToShortDateString();
        }
        catch
        {
            //ignore
        }
    }

    // Seed the visible input so Pikaday opens on a sensible year for a DOB.
    // Only applies when there is no stored value and only to the DOB field.
    var seeded = false;
    if (string.IsNullOrEmpty(val) && Model.Alias == "dateOfBirth")
    {
        val = "1990-01-01";
        seeded = true;
    }
}
<input type="hidden" name="@Model.Name" id="@(Model.Id)_1" class="datepickerfieldshadow" value="@(seeded ? string.Empty : val)" data-umb="@Model.Id" />
<input type="text" name="@Model.Name" id="@(Model.Id)" class="datepickerfield" autocomplete="off" value="@val" @{
    if (seeded)
    {
        <text>data-seeded="true" </text>
    }
    if (Model.Mandatory)
    {
        <text>data-val="true" data-val-required="@Model.RequiredErrorMessage" </text>
    }
    if (string.IsNullOrWhiteSpace(Model.PlaceholderText) == false)
    {
        <text>placeholder="@Model.PlaceholderText" </text>
    }
    if (string.IsNullOrWhiteSpace(ariaLabel) == false)
    {
        <text>aria-label="@ariaLabel" </text>
    }
    <text>aria-describedby="@(Model.Id)_validation@(!string.IsNullOrWhiteSpace(Model.ToolTip) ? $" {Model.Id}_description" : "")" </text>
    if (DatePicker.GetRelativeDate(Model.GetSettingValue<string>("MinDate")) is DateTime minDate)
    {
        <text>data-min-date="@minDate.ToString("yyyy-MM-dd")" </text>
    }
    if (DatePicker.GetRelativeDate(Model.GetSettingValue<string>("MaxDate")) is DateTime maxDate)
    {
        <text>data-max-date="@maxDate.ToString("yyyy-MM-dd")" </text>
    }
}/>

And then a script which cleans this up on load:

<script>
    window.addEventListener('load', function () {
        document.querySelectorAll('.datepickerfield[data-seeded]').forEach(function (el) {
            el.value = '';
            var shadow = document.getElementById(el.id + '_1');
            if (shadow) shadow.value = '';
        });
    });
</script>

I’ve not tried the above, but sounds like it may work. A custom field may be the better approach though as you can create a setting for the default date in case you need it to vary by form.

It may also be worth raising this on the Umbraco Forms issue tracker as it sounds like something that it could benefit with being in Umbraco Forms itself.

Justin

Thanks Justin, that did indeed do the trick. It also seems the script isn’t needed, you just need to set ‘val’ .

I think I will go with our own picker, as we need to enforce an age limit, and the existing functionality only allows minimum dates to be entered by days, which isn’t suitable.

I’ll raise an issue, it’s pretty perplexing that the date picker can open on a day outside of the allowed range

Dang just a little late..

same val override… though prob needs to be generic, like set to maxdate if available…

but much nicer to pick a selected date..

so with that in mind.. prob nicer to override the actual pickaday implementation.. so that you aren’t forced to set a value in the field..
\Views\Partials\Forms\Themes\myTheme\DatePicker.cshtml

@using System.Globalization
@using Microsoft.Extensions.Options
@using Umbraco.Forms.Core.Configuration
@using Umbraco.Forms.Web

@inject IOptionsSnapshot<DatePickerSettings> Configuration

@{
    int datePickerYearRange = Configuration.Value.DatePickerYearRange;
    string datePickerFormat = Configuration.Value.DatePickerFormat;
    if (string.IsNullOrWhiteSpace(datePickerFormat))
    {
        datePickerFormat = Umbraco.Forms.Core.Constants.Formats.DefaultDatePickerFormat;
    }

    Html.AddFormThemeCssFile("~/App_Plugins/UmbracoForms/assets/pikaday/pikaday.min.css");
    Html.AddFormThemeScriptFile("~/App_Plugins/UmbracoForms/assets/moment/min/moment-with-locales.min.js");
    Html.AddFormThemeScriptFile("~/App_Plugins/UmbracoForms/assets/pikaday/pikaday.min.js");
    Html.AddFormThemeScriptFile("~/App_Plugins/UmbracoForms/assets/datepicker.init.min.js");

    //only render the script block below one time per page
    var alreadyRendered = Context.Items.ContainsKey("__formDatePickerRendered");
    Context.Items["__formDatePickerRendered"] = true;
}

@if (!alreadyRendered)
{
    <h1>HelloWorld.....</h1>
    <div id="umbraco-forms-date-picker-config"
         class="umbraco-forms-hidden"
         data-name="@CultureInfo.CurrentUICulture.Name"
         data-year-range="@datePickerYearRange"
         data-format="@datePickerFormat"
         data-previous-month="<<"
         data-next-month=">>"
         data-months="@string.Join(",", CultureInfo.CurrentCulture.DateTimeFormat.MonthNames)"
         data-weekdays="@string.Join(",", CultureInfo.CurrentCulture.DateTimeFormat.DayNames)"
         data-weekdays-short="@string.Join(",", CultureInfo.CurrentCulture.DateTimeFormat.AbbreviatedDayNames)"></div>
}

adding data-default-date as you mentioned though you’d also have to replace the assets/datepicker.init.min.js
as doesn’t appear to have default-date support beyond field.value.. or able to set use and don’t select.

defaultDate: defaultDateValue,
setDefaultDate: false // Keeps the input empty on load, but opens the calendar at the defaultDate view

App_Plugins/UmbracoForms/assets/datepicker.init.js

(function () {

  //execute init() on document ready
  if (document.readyState === "complete" || (document.readyState !== "loading" && !document.documentElement.doScroll)) {
    listen();
  } else {
    document.addEventListener("DOMContentLoaded", listen);
  }

  function listen() {

    // Inline script setting umbracoFormsLocale was removed in 8.11/9.3, and replaced by a config element.
    // So if we have that, use it, otherwise fall-back to legacy method.
    var configElement = document.getElementById("umbraco-forms-date-picker-config");
    if (configElement) {
      var umbracoFormsLocaleFromConfig = {
        name: configElement.dataset.name,
        datePickerYearRange: configElement.dataset.yearRange,
        locales: {
          previousMonth: configElement.dataset.previousMonth,
          nextMonth: configElement.dataset.nextMonth,
          months: configElement.dataset.months.split(','),
          weekdays: configElement.dataset.weekdays.split(','),
          weekdaysShort: configElement.dataset.weekdaysShort.split(',')
        },
        format: configElement.dataset.format ?? "LL"
      };
      init({ umbracoFormsLocale: umbracoFormsLocaleFromConfig });
    } else {
      if (typeof umbracoFormsLocale === "undefined") {
        //this will occur if this js file is loaded before the inline scripts, in which case
        //we'll listen for the inline scripts to execute a custom event.
        document.addEventListener("umbracoFormsLocaleLoaded", init);
      }
      else {
        init({ umbracoFormsLocale: umbracoFormsLocale });
      }
    }
  }

  function init(e) {

    if (typeof moment === "undefined") {
      throw "moment lib has not been loaded";
    }

    moment.locale(e.umbracoFormsLocale.name);

    var datePickerFields = document.getElementsByClassName('datepickerfield');
    for (var i = 0; i < datePickerFields.length; i++) {
      var field = datePickerFields[i];

      var options = {
        field: field,
        yearRange: e.umbracoFormsLocale.datePickerYearRange,
        //ariaLabel: ariaLabel,
        i18n: e.umbracoFormsLocale.locales,
        format: e.umbracoFormsLocale.format,
        onSelect: function (date) {
          setShadow(this, date);
          var evt = document.createEvent("HTMLEvents");
          evt.initEvent("input", false, true);
          this._o.field.dispatchEvent(evt);
        },
        minDate: new Date(field.dataset.minDate || '1753-01-01T00:00:00'), //Min value of datetime in SQL Server CE
        maxDate: field.dataset.maxDate ? new Date(field.dataset.maxDate) : undefined,
        defaultDate: new Date(field.value),
        setDefaultDate: true
      };

      // If we've set an aria-label on the field already, use it so we don't have it overwritten by the Pikaday default.
      var ariaLabel = field.getAttribute("aria-label");
      if (ariaLabel) {
        options["ariaLabel"] = ariaLabel;
      }

      new Pikaday(options);
    }

    function setShadow(pickaday, date) {
      var id = pickaday._o.field.id + "_1";
      var value = moment(date).format('YYYY-MM-DD');
      var field = document.getElementById(id);
      field.value = value;
    }
  }
})();


PS

latest min/max have relative option.. which I presume is relative to today.

so that should allow for your age limit..
eg max set to -7665 should be need to be 21… though again has issues with leap years.. so might be better to role your own.

I’ll raise an issue, it’s pretty perplexing that the date picker can open on a day outside of the allowed range

Though it’s pretty common that DOB pickers open on todays date (for age gate use) and you have to scroll back an eternity.. though it is just lazy on the developer side

Even if it’s not age gating.. what pre teen should be registering (COPPA or UK/EU GDPR)

Though could also be pick child’s DOB with adult registering..

Rambling now.. :wink:

But definitely agree should be something native in uForms Date Picker, to coincide with min/max

Also, date of births can vary, so depends on who you are targeting whether you want it to default to dates in which year based on your audience, etc.

Not to hyjack the thread, but did I also just find that in forms 17.5.0 we can’t use RCL path based overrides for the default theme anymore.. I could only get my custom fieldType to work when specifying a custom theme?