Nick
Nick

Reputation: 33

Html input time, select time within min and max - Jquery

As you see in the code, i want to let users select the time between opening and closing time (min.max), but its not working, it'll take all time entered and some times it'll work only for max time. I've seen similar questions, i couldn't find any solutions.

part of code -

   temple_name| temple_opentime | temple_closetime
   -----------------------------------------------
    temple1   |      9:30       |    18:30       |
    temple2   |      7:30       |    21:00       |
    temple3   |     10:00       |    18:00       |
    temple4   |      8:30       |    20:00       |



<select id="temple" class="form-control">
           <?php 
             require "config/database.php";
             
             $sql = "select * from entity_temple";
             $result = mysqli_query($link, $sql);
             while($row = mysqli_fetch_array($result))
            {
             echo '<option name="'.$row["temple_opentime"].'" data-value="'. $row["temple_closetime"].'" value="'. $row["temple_name"].'">'. $row["temple_name"].'</option>';
            }
            ?>
      </select>

<div class="form-group col-md-4">
          <label for="time" class="form-label">Time:</label>
          <input type="time" id="time" name="time" min="" max="" step="600" class="form-control">
        </div>

<div class="form-group d-grid col-4 mx-auto">
  <button type="submit" name="save" class="btn btn-block btn-outline-dark" id="butsave">Add Request</button>
    </div>

$("#temple").on("change", function () {
    
  var value = $('#temple').val();
  
  var nm = $('#temple option[value="' + value + '"]').attr('name');
    
  var dv = $('#temple [value="' + value + '"]').data('value');
     
   $("#time").attr({
   "max" : dv,
   "min" : nm
});

Upvotes: 0

Views: 3034

Answers (3)

terrymorse
terrymorse

Reputation: 7086

As stated above, the problem with the OP code was merely a typo:

The input's min and max attribute values must be strings in the form "hh:mm" ("h:mm" is invalid):

<input type="time" min="08:30" max="16:00">

However, this typo fix alone does not address browser incompatibilities. As MDN states:

... in Safari, and any other browsers that don't support <time>, it [<input type="time">] degrades gracefully to <input type="text">.

and a degraded input will not be checked for min/max errors.

To address this browser incompatibility, the code below detects inputs that have been degraded to the form <input type="text" min="hh:mm" max="hh:mm">, and adds a validator script to the form.

If a validation error is detected, the error message will be shown in an overlay above the input (matching the native behavior in Chrome and Firefox).

// the "real" form submits
  const timeForm = document.getElementById('formtime');
  timeForm.onsubmit = function () {
    alert('time form was submitted');
  };
  const textForm = document.getElementById('formtext');
  textForm.onsubmit = function () {
    alert('text form was submitted');
  };

// time validator for degraded time inputs
// Note: This must be loaded AFTER all user form submitters have
// been added
(function () {

  // error overlay custom element
  class ErrorOverlay extends HTMLElement {
    constructor () {
      super();

      // create a shadow root
      let shadowRoot = this.attachShadow({mode: 'open'});

      // create shadow DOM element
      const overlay = document.createElement('div');
      this.overlay = overlay;
      overlay.className = 'error-overlay';

      // create css for the shadow DOM
      const style = document.createElement('style');
      style.textContent = `
          .error-overlay {
            font-family: Helvetica, sans-serif;
            position: absolute;
            z-index: 1000;
            border: 1px solid #808080;
            border-radius: 4px;
            color: #f8f8f8;
            background-color: #808080;
            padding: 0.2rem 0.4rem;
            display: none;
          }
          .error-overlay::after {
            content: " ";
            position: absolute;
            top: 100%;
            left: 24px;
            margin-left: -5px;
            border-width: 5px;
            border-style: solid;
            border-color: #808080 transparent transparent transparent;
          }
        `;

      // attach shadow DOM to shadow root
      shadowRoot.appendChild(style);
      shadowRoot.appendChild(overlay);
    }

    // show error overlay above the input
    show (input, message) {
      const {top, right, bottom, left} = input.getBoundingClientRect();
      const overlay = this.overlay;
      overlay.style.top = Math.round(top - 34 + window.scrollY) + 'px';
      overlay.style.left = Math.round(left) + 'px';
      overlay.innerText = message;
      overlay.style.display = 'block';

      // hide overlay
      function overlayHide () {
        overlay.style.display = 'none';
        // remove listeners
        input.removeEventListener('keyup', overlayHide);
        input.removeEventListener('blur', overlayHide);
      }

      // add input listeners and focus the input
      input.addEventListener('keyup', overlayHide);
      input.addEventListener('blur', overlayHide);
      input.focus();
    }
  }

  // create overlay instance & add to body
  customElements.define('error-overlay', ErrorOverlay);
  const errorOverlay = document.createElement('error-overlay');
  document.body.appendChild(errorOverlay);

  // convert time string to decimal hours
  // time string patterns: "hh:mm AM", "hh:mm PM", "hh:mm"
  function timeStringToDecimalHours (timeStr) {
    let [hhmm, ampm] = timeStr.split(' ');
    ampm = (ampm || '').toLowerCase();
    let [hour, minute] = hhmm.split(':').map(el => Number(el));
    if (ampm === 'pm' && hour < 12) {
      hour += 12;
    } else if (ampm === 'am' && hour === 12) {
      hour = 0;
    }
    return hour + (minute / 60);
  }

  // get array of inputs that have this pattern:
  // <input type="text" min="hh:mm" max="hh:mm">
  function getDegradedTimeInputs (form) {
    return Array.from(form.querySelectorAll('input'))
      .filter(input => {
        if (input.type !== 'text') { return false; }
        return input.hasAttribute('min') ||
          input.hasAttribute('max');
      });
  }

  // validate inputs when form is submitted
  function validateTimeOnFormSubmit (evt) {

    // get form's inputs that have type="text", `min` and/or `max`
    const form = evt.target;
    const tInputs = getDegradedTimeInputs(form);

    // validate that each input value is between min and max
    for (const input of tInputs) {
      const {value, max, min} = input;

      // convert time strings into decimal hours
      const valTime = timeStringToDecimalHours(value),
        minTime = timeStringToDecimalHours(min),
        maxTime = timeStringToDecimalHours(max);

      if (valTime < minTime || maxTime < valTime) {
        // show the error overlay and prevent the submit
        errorOverlay.show(input, `Time must be between ${min} and ${max}`);
        return false;
      }
    }
    // complete the submit
    return form.realOnSubmit(evt);
  }

  // add time validator to all forms that contain degraded time inputs
  const forms = document.querySelectorAll('form');

  function submitHandlerStub () { return true; }

  for (const form of forms) {
    if (getDegradedTimeInputs(form).length > 0) {
      // store the real submit handler as `form.realOnSubmit`
      const realOnSubmit = form.onsubmit || submitHandlerStub;
      Object.defineProperty(form, 'realOnSubmit', {
        value: realOnSubmit,
        enumerable: false
      });
      form.onsubmit = validateTimeOnFormSubmit;
    }
  }

})();
form {
  border: 1px solid black;
  max-width: 30rem;
  margin: 1rem 0;
  padding: 0.5rem;
}
<h1>Validate a form's degraded time inputs</h1>

<p>Some browsers do not support <code>&lt;input type="time"></code>, degrading it to <code>&lt;input type="text"></code>.</p>

<p>This script detects these degraded inputs and validates them accordingly.</p>

<form id="formtime">
  <label for="timeinput">TIME input (min 09:30, max: 14:00):</label>
  <input id="timeinput" type="time" min="09:30" max="14:00" placeholder="hh:mm AM/PM" />
  <button type="submit">Submit</button>
</form>
<form id="formtext">
  <label for="degraded">TEXT input (min 09:30, max: 14:00):</label>
  <input id="degraded" type="text" min="09:30" max="14:00" style="width: 5rem" placeholder="hh:mm AM/PM" />
  <button type="submit">Submit</button>
</form>

Upvotes: 1

Twisty
Twisty

Reputation: 30883

Consider the following.

$(function() {
  $("#temple").on("change", function() {
    var item = $(":selected", this),
      op = item.data("open-time"),
      cl = item.data("close-time");
    $("#time").attr({
      min: op,
      max: cl,
      value: op
    }).trigger("input");
  });
  $("#time").on("change", function(e) {
    if ($(this).val() < $(this).attr("min")) {
      $(this).val($(this).attr("min"));
    } else if ($(this).val() > $(this).attr("max")) {
      $(this).val($(this).attr("min"));
    }
  });
});
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!--
   temple_name| temple_opentime | temple_closetime
   -----------------------------------------------
    temple1   |      9:30       |    18:30       |
    temple2   |      7:30       |    21:00       |
    temple3   |     10:00       |    18:00       |
    temple4   |      8:30       |    20:00       |
-->
<select id="temple" class="form-control">
  <option>---</option>
  <option data-open-time="09:30" data-close-time="18:30" value="Temple1">Temple1</option>
  <option data-open-time="07:30" data-close-time="21:00" value="Temple2">Temple2</option>
  <option data-open-time="10:00" ddata-close-time="18:00" value="Temple3">Temple3</option>
  <option data-open-time="08:30" data-close-time="20:00" value="Temple1">Temple4</option>
</select>

<div class="form-group col-md-4">
  <label for="time" class="form-label">Time:</label>
  <input type="time" id="time" name="time" min="" max="" step="600" class="form-control" required>
</div>

<div class="form-group d-grid col-4 mx-auto">
  <button type="submit" name="save" class="btn btn-block btn-outline-dark" id="butsave">Add Request</button>
</div>

Here, when a Option is selected, the Min, Max, and Value of the Time input are update. once updated, we trigger an Input event, this will cause the Value to appear to the User. Values can be in the Input that are outside the Min and Max, this will cause the Input to be invalid. If the User then adjusts the time, it will forced to match the Min Max via jQuery.

See More: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/time#Time_value_format

Note: When the data entered by the user doesn't adhere to the stepping configuration, the user agent may round to the nearest valid value, preferring numbers in the positive direction when there are two equally close options.

This property has some strange effects across browsers, so is not completely reliable.

Upvotes: 1

Louys Patrice Bessette
Louys Patrice Bessette

Reputation: 33933

I would use the data attributes for the open and close time... And with meaningful names. ;)

Your SQL should be:

while($row = mysqli_fetch_array($result)){
  echo '<option value="'.$row["temple_name"].'" data-opentime="'. $row["temple_opentime"].'" data-closetime="'. $row["temple_closetime"].'">'. $row["temple_name"].'</option>';
}

I noticed the time is NOT showing in the input if the value does not have a leading zero... So 7:30 doesn't work but 07:30 works.


Now about the min and max attributes of the time input...
There is no validation made out of the box by the browser yet...

You can notice that even the MDN example is not behaving has it should.

So I added somekind of a basic validation starter. It at least prevents the form submit and adds a class to the time element if the chosen value is incorrect.

$("#temple").on("change", function() {

  var value = $('#temple').val();

  var min = $('#temple option[value="' + value + '"]').data('opentime');

  var max = $('#temple [value="' + value + '"]').data('closetime');

  console.log(value, min, max)

  $("#time").attr({
    value: min,
    max,
    min
  });
});

$("#timeForm").on("submit", function(e){
  e.preventDefault();
  
  let time = $("#time");
  if( time.val() < time.attr("min") || time.val() > time.attr("max") ){
    time.addClass("error");
    return;
  }
  
  // if the time is correct, submit
  $(this).submit();
});

$("#timeForm").on("change",function(){
  $("#time").removeClass("error");
});
.error{
  background: red;
}
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.bundle.min.js"></script>

<form id="timeForm">
  <select id="temple" class="form-control">
    <option disabled selected>Select a temple</option>
    <option value="temple1" data-opentime="09:30" data-closetime="18H30">temple1</option>
    <option value="temple2" data-opentime="07:30" data-closetime="21:00">temple2</option>
    <option value="temple3" data-opentime="10:00" data-closetime="18:00">temple3</option>
    <option value="temple4" data-opentime="08:30" data-closetime="20:00">temple4</option>
  </select>

  <div class="form-group col-md-4">
    <label for="time" class="form-label">Time:</label>
    <input type="time" id="time" name="time" min="" max="" step="600" class="form-control" required>
  </div>

  <div class="form-group d-grid col-4 mx-auto">
    <button type="submit" name="save" class="btn btn-block btn-outline-dark" id="butsave">Add Request</button>
  </div>
</form>

Upvotes: 1

Related Questions