RangeAttributeにて入力範囲を制限する

前回の続きです。

mrgchr.hatenablog.com

環境

Range範囲外の値でも入力できる

前回では触れませんでしたが、Rangeを指定してもその範囲外の値を指定できます。
(もちろん、検証時エラーになりますが)
これはあまりユーザーフレンドリーとは言えないので、そもそも入力できないようにフォームを制限する方法を考えます。

HTMLを確認する

Range属性付きのプロパティとして生成されるHTMLは下記の通り(前回のものなのでエラー時のもの)で、検証用のdata-val-range-maxdata-val-range-maxがありますが、maxminはありません。

<input class="text-box single-line input-validation-error"
  data-val="true"
  data-val-range="Value for DemoDate must be between 2019/09/09 and 2019/09/19"
  data-val-range-max="09/19/2019 00:00:00"
  data-val-range-min="09/09/2019 00:00:00"
  data-val-required="The DemoDate field is required."
  id="RangeDateDemo_DemoDate"
  name="RangeDateDemo.DemoDate"
  type="date"
  value=""
  aria-describedby="RangeDateDemo_DemoDate-error"
  aria-invalid="true">

当然maxminを生やすことになるのですが、一つ注意点として、どうやらその日付書式は"YYYY-MM-dd"である必要があるということです。
これを考慮すると作成するコードは下記のようになります。

<!-- Pages/_Shared/ValidationScriptsPartial.cshtml -->
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
@*前回はここから追加*@
<script>
  //original: https://stackoverflow.com/questions/52543413/datetime-client-side-validation-fails-due-to-formatting
  $.validator.methods.range = function (value, element, param) {
    if ($(element).attr('type') == 'date') {
      var min = $(element).attr('data-val-range-min');
      var max = $(element).attr('data-val-range-max');
      var date = new Date(value).getTime();
      var minDate = new Date(min).getTime() || 0;
      var maxDate = new Date(max).getTime() || 8640000000000000;
      return this.optional(element) || (date >= minDate && date <= maxDate);
    }
    // use the default method
    return this.optional(element) || (value >= param[0] && value <= param[1]);
  };
@*今回はここから追加*@
  (function () {
    function formatDate(date) {
      var d = new Date(date);
      var year = d.getFullYear();
      var month = (d.getMonth() + 1).toString();
      var day = d.getDate().toString();

      if (month.length < 2) month = '0' + month;
      if (day.length < 2) day = '0' + day;

      return [year, month, day].join('-');
    }

    function setMinMax(inputElem, f) {
      Object.keys(inputElem).forEach(function (key) {
        var elem = inputElem[key];
        var rangeMin = elem.getAttribute("data-val-range-min");
        var rangeMax = elem.getAttribute("data-val-range-max");
        if (rangeMin) elem.setAttribute("min", f(rangeMin));
        if (rangeMax) elem.setAttribute("max", f(rangeMax));
      });
    }

    var inputDateElements = document.querySelectorAll('input[type="date"]');
    setMinMax(inputDateElements, formatDate);

    var inputNumberElements = document.querySelectorAll('input[type="number"]');
    setMinMax(inputNumberElements, function (x) { return x; });
  })()
@*今回はここまで追加*@
</script>
@*前回はここまで追加*@

これにより目的のmaxminを生やします。最後のものは標準の整数に対応したものです。

<input class="text-box single-line" 
  data-val="true" 
  data-val-range="Value for DemoDate must be between 2019/09/09 and 2019/09/19" 
  data-val-range-max="09/19/2019 00:00:00" 
  data-val-range-min="09/09/2019 00:00:00" 
  data-val-required="The DemoDate field is required." 
  id="RangeDateDemo_DemoDate" 
  name="RangeDateDemo.DemoDate" 
  type="date" 
  value="" 
  min="2019-09-09" 
  max="2019-09-19">

下図のように入力フォームがRangeの範囲で制限されています。 f:id:mrgchr:20190930214149p:plain

おまけ

MS Edge 18で確認したら、制限後はあまり見ない感じのUIとなっていました。

f:id:mrgchr:20190930214328p:plain