jhpyle / docassemble

A free, open-source expert system for guided interviews and document assembly, based on Python, YAML, and Markdown.
https://docassemble.org
MIT License
786 stars 255 forks source link

custom date picker and "js show if" #603

Closed myaptad closed 1 year ago

myaptad commented 1 year ago

Problem: Date dependent controls show and hide perfectly with DA's built-in date picker using "js show if" but stop to show/hide with datereplace.js included in the interview. Question: Is there anything specific that needs to be taken into account while using "js show if" with variable set by custom date picker like as demonstrated here https://docassemble.org/docs/recipes.html#customdate . I see note that datereplace.js adds nameless elements to DOM but for "js show if" DA matches element name mentioned in javascript expression. I also tried to trigger onchange on the hidden element but that does not seem to help. Thanks

jhpyle commented 1 year ago

I think you were on the right track by triggering change on the hidden element. I got it to work with:

$(document).on("daPageLoad", function () {
  $('input[type="date"]').each(function () {
    var dateElement = this;
    $(dateElement).attr("type", "hidden");
    var parentElement = $('<div class="row px-1">');
    var monthParent = $('<div class="col-sm-5 px-0">');
    var dayParent = $('<div class="col-sm-4 px-0">');
    var yearParent = $('<div class="col-sm-3 px-0">');
    var monthElement = $('<select class="form-select">');
    var dayElement = $('<select class="form-select">');
    var yearElement = $('<select class="form-select">');
    var today = new Date();
    var dateEntered;
    if ($(dateElement).val()) {
      var utcDate = new Date($(dateElement).val());
      dateEntered = new Date(
        utcDate.getUTCFullYear(),
        utcDate.getUTCMonth(),
        utcDate.getUTCDate()
      );
    } else {
      dateEntered = null;
    }
    var opt = $("<option>");
    opt.val("");
    opt.text("Month");
    monthElement.append(opt);
    for (var month = 0; month < 12; month++) {
      opt = $("<option>");
      if (month < 9) {
        opt.val("0" + (month + 1));
      } else {
        opt.val(month + 1);
      }
      var dt = new Date(1970, month, 15);
      opt.text(dt.toLocaleString("default", { month: "long" }));
      if (dateEntered && month == dateEntered.getMonth()) {
        opt.attr("selected", "selected");
      }
      monthElement.append(opt);
    }
    opt = $("<option>");
    opt.val("");
    opt.text("Day");
    dayElement.append(opt);
    for (var day = 1; day <= 31; day++) {
      var opt = $("<option>");
      if (day < 10) {
        opt.val("0" + day);
      } else {
        opt.val(day);
      }
      opt.text(day);
      if (dateEntered && day == dateEntered.getDate()) {
        opt.attr("selected", "selected");
      }
      dayElement.append(opt);
    }
    opt = $("<option>");
    opt.val("");
    opt.text("Year");
    yearElement.append(opt);
    for (
      var year = today.getFullYear();
      year > today.getFullYear() - 50;
      year--
    ) {
      opt = $("<option>");
      opt.val(year);
      opt.text(year);
      if (dateEntered && year == dateEntered.getFullYear()) {
        opt.attr("selected", "selected");
      }
      yearElement.append(opt);
    }
    function updateDate() {
      if (
        $(yearElement).val() != "" &&
        $(monthElement).val() != "" &&
        $(dayElement).val() != ""
      ) {
        $(dateElement).val(
          $(yearElement).val() +
            "-" +
            $(monthElement).val() +
            "-" +
            $(dayElement).val()
        );
        $(dateElement).trigger("change");
      }
    }
    $(dateElement).before(parentElement);
    $(monthParent).append(monthElement);
    $(parentElement).append(monthParent);
    $(dayParent).append(dayElement);
    $(parentElement).append(dayParent);
    $(yearParent).append(yearElement);
    $(parentElement).append(yearParent);
    yearElement.on("change", updateDate);
    monthElement.on("change", updateDate);
    dayElement.on("change", updateDate);
    updateDate();
  });
});

and

features:
  javascript: datereplace.js
---
question: |
  When were you born?
fields:
  - Date of birth: date_of_birth
    datatype: date
  - "Do you remember the dinosaurs?": remembers_dinosaurs
    datatype: yesnoradio
    js show if: |
      val('date_of_birth') && Date.parse(val('date_of_birth')) < Date.parse('1980-01-01')
---
mandatory: True
question: |
  You were born on ${ date_of_birth }.

The Bootstrap classes in the JavaScript file were out of date so I changed them.

myaptad commented 1 year ago

Hello, Thanks alot for looking into this. I confirm this issue to have been fixed. I am marking this ticket to closed. Much appreciated. Thanks!

myaptad commented 1 year ago

Hello,

These are related to the date control so re-opening the ticket instead of new issue.

Thanks

jhpyle commented 1 year ago

The min and max validators work for me when I add them to the recipe.

question: |
  When were you born?
fields:
  - Date of birth: date_of_birth
    datatype: date
    min: 1970-01-01
    max: 2020-01-01

The validators work via the jQuery Validation plugin, so the max and min values aren't in the <input> element itself. They are in daValidator.settings.rules.

You could write JavaScript that detects if the device is Android and then converts the field to text:

$(document).on("daPageLoad", function () {
  var isAndroid = // your code here;
  if (isAndroid){
    $('input[type="date"]').each(function () {
      $(this).attr('type', 'text');
    });
  }
});

so that the browser treats the field as plain text.

myaptad commented 1 year ago

Thank you for reverting back on this. I should have been bit more clear earlier. Let's see if I can clarify further:

jhpyle commented 1 year ago

I am not seeing what you are seeing. When I use my datereplace.js, the standard docassemble input validation functionality based on the jQuery Validation plugin works as normal, and I get the warning messages if the date is outside the min or max. Also when I use JavaScript to change the type of the input element to text, the jQuery Validation plugin makes sure that I enter a valid date.

myaptad commented 1 year ago

Hello there, I can confirm that setting the field to text in javascript does work along with its proper validation.

However, after playing around a bit, it seems like there is a usage specific behavior or possibly a bug in the code. We can see the behavior using code below which has minor changes to your example. The validation stops working when the last field is commented/removed as shown. Do you see some issue with usage as such?

features:
  javascript: datereplace.js
---
question: |
  When were you born?
fields:
  - Date of birth: date_of_birth
    datatype: date
    max: ${ today() }
  # - "Do you remember the dinosaurs?": remembers_dinosaurs
  #   datatype: yesnoradio
  #   js show if: |
  #     val('date_of_birth') && Date.parse(val('date_of_birth')) < Date.parse('1980-01-01')
---
mandatory: True
question: |
  You were born on ${ date_of_birth }.
jhpyle commented 1 year ago

In 1.4.27 I changed the jQuery Validation Plugin settings so that this will work more consistently.

myaptad commented 1 year ago

Works now, thank you!