3

I created a JavaScript code to auto format some date inputs. The problem is that my code works only on the first input but not the others. How can I make my code applied on all the date inputs in my form ?

Is there a way to make the event listener activated on all the inputs ?

I thought about adding different ID to every input and duplicate code, but this will make my code not readable.

Any hints, i tried to iterate over the input using the for loop but it doesn't work either.

var dates = document.getElementsByTagName('input');
for (i = 0; i < 3; i++) {
date = document.getElementsByTagName('input')[i]
function checkValue(str, max) {
  if (str.charAt(0) !== '0' || str == '00') {
    var num = parseInt(str);
    if (isNaN(num) || num <= 0 || num > max) num = 1;
    str = num > parseInt(max.toString().charAt(0)) && num.toString().length == 1 ? '0' + num : num.toString();
  };
  return str;
};

date.addEventListener('input', function(e) {
  this.type = 'text';
  var input = this.value;
  if (/\D\/$/.test(input)) input = input.substr(0, input.length - 3);
  var values = input.split('/').map(function(v) {
    return v.replace(/\D/g, '')
  });
  if (values[0]) values[0] = checkValue(values[0], 12);
  if (values[1]) values[1] = checkValue(values[1], 31);
  var output = values.map(function(v, i) {
    return v.length == 2 && i < 2 ? v + ' / ' : v;
  });
  this.value = output.join('').substr(0, 14);
});

date.addEventListener('blur', function(e) {
  this.type = 'text';
  var input = this.value;
  var values = input.split('/').map(function(v, i) {
    return v.replace(/\D/g, '')
  });
  var output = '';
  
  if (values.length == 3) {
    var year = values[2].length !== 4 ? parseInt(values[2]) + 2000 : parseInt(values[2]);
    var month = parseInt(values[0]) - 1;
    var day = parseInt(values[1]);
    var d = new Date(year, month, day);
    if (!isNaN(d)) {
      document.getElementById('result').innerText = d.toString();
      var dates = [d.getMonth() + 1, d.getDate(), d.getFullYear()];
      output = dates.map(function(v) {
        v = v.toString();
        return v.length == 1 ? '0' + v : v;
      }).join(' / ');
    };
  };
  this.value = output;
});
}
html {
  box-sizing: border-box;
  font-family: 'PT Sans', sans-serif;
-webkit-font-smoothing: antialiased;
}
*, 
*:before, 
*:after {
  box-sizing: inherit;
}
body {
    background-color: #f3f3f3;
}
form {
    width: 100%;
    max-width: 400px;
    margin: 60px auto;
}
form input {
    font-size: 30px;
    padding: 0 20px;
    border: 2px solid #ccc;
    width: 100%;
    color: #666;
    line-height: 3;
    border-radius: 7px;
    font-family: 'PT Sans', sans-serif;
    font-weight: bold;
}
form input:focus {
    outline: 0;
}
form input.error {
    border-color: #ff0000;  
}
form label.error {
    background-color: #ff0000;
    color: #fff;
    padding: 6px;
    font-size: 11px;
}

label {
    color: #999;
    display: block;
    margin-bottom: 10px;
    text-transform: uppercase;
    font-size: 18px;
    font-weight: bold;
    letter-spacing: 0.05em
}
form small {
    color: #888;
    font-size: 1em;
    margin-top: 10px;
    display: block;
    align-self: ;
}
<form id="form" method="post" action="">
  <label for="amount">Date</label>
  <input type="text" id="date1" />
  <small>Enter date as Month / Day / Year</small>
  </br>
  <label for="amount">Date 2</label>
  <input type="text" id="date2" />
  <small>Enter date as Month / Day / Year</small>
  </br>
  <label for="amount">Date 3</label>
  <input type="text" id="date3" />
  <small>Enter date as Month / Day / Year</small>
  
</form>

Edit: I corrected the inputs Id so that they become unique.

Edit 2 : In my solution, i'm parsing the input and I add spaces before and after every slash. I want to eliminate those spaces. To do so, I modified this line :

this.value = output.join('').substr(0, 14);

I replaced 14 by 10. But this modification made deleting the data impossible. For example : 06/07/ can't be deleted.

Alouani Younes
  • 668
  • 8
  • 14

2 Answers2

1

The getElementsBy* methods return HTMLCollections, which can be difficult to work with. Consider using querySelectorAll instead, which returns a static NodeList - unlike an HTMLCollection, it can be iterated over directly, it won't change while it's being iterated over, and it's much more flexible.

Iterate over each input directly with querySelectorAll('input').forEach. Also note that your HTML is invalid: there should never be more than one element with a particular ID in a document. You left out some of your HTML, which is why the snippet below results in an error at the end:

document.querySelectorAll('input').forEach((date) => {

  function checkValue(str, max) {
    if (str.charAt(0) !== '0' || str == '00') {
      var num = parseInt(str);
      if (isNaN(num) || num <= 0 || num > max) num = 1;
      str = num > parseInt(max.toString().charAt(0)) && num.toString().length == 1 ? '0' + num : num.toString();
    };
    return str;
  };

  date.addEventListener('input', function(e) {
    this.type = 'text';
    var input = this.value;
    if (/\D\/$/.test(input)) input = input.substr(0, input.length - 3);
    var values = input.split('/').map(function(v) {
      return v.replace(/\D/g, '')
    });
    if (values[0]) values[0] = checkValue(values[0], 12);
    if (values[1]) values[1] = checkValue(values[1], 31);
    var output = values.map(function(v, i) {
      return v.length == 2 && i < 2 ? v + ' / ' : v;
    });
    this.value = output.join('').substr(0, 14);
  });

  date.addEventListener('blur', function(e) {
    this.type = 'text';
    var input = this.value;
    var values = input.split('/').map(function(v, i) {
      return v.replace(/\D/g, '')
    });
    var output = '';

    if (values.length == 3) {
      var year = values[2].length !== 4 ? parseInt(values[2]) + 2000 : parseInt(values[2]);
      var month = parseInt(values[0]) - 1;
      var day = parseInt(values[1]);
      var d = new Date(year, month, day);
      if (!isNaN(d)) {
        document.getElementById('result').innerText = d.toString();
        var dates = [d.getMonth() + 1, d.getDate(), d.getFullYear()];
        output = dates.map(function(v) {
          v = v.toString();
          return v.length == 1 ? '0' + v : v;
        }).join(' / ');
      };
    };
    this.value = output;
  });
});
html {
  box-sizing: border-box;
  font-family: 'PT Sans', sans-serif;
  -webkit-font-smoothing: antialiased;
}

*,
*:before,
*:after {
  box-sizing: inherit;
}

body {
  background-color: #f3f3f3;
}

form {
  width: 100%;
  max-width: 400px;
  margin: 60px auto;
}

form input {
  font-size: 30px;
  padding: 0 20px;
  border: 2px solid #ccc;
  width: 100%;
  color: #666;
  line-height: 3;
  border-radius: 7px;
  font-family: 'PT Sans', sans-serif;
  font-weight: bold;
}

form input:focus {
  outline: 0;
}

form input.error {
  border-color: #ff0000;
}

form label.error {
  background-color: #ff0000;
  color: #fff;
  padding: 6px;
  font-size: 11px;
}

label {
  color: #999;
  display: block;
  margin-bottom: 10px;
  text-transform: uppercase;
  font-size: 18px;
  font-weight: bold;
  letter-spacing: 0.05em
}

form small {
  color: #888;
  font-size: 1em;
  margin-top: 10px;
  display: block;
  align-self: ;
}
<form id="form" method="post" action="">
  <label for="amount">Date</label>
  <input type="text" id="date" />
  <small>Enter date as Month / Day / Year</small>
  </br>
  <label for="amount">Date 2</label>
  <input type="text" id="date" />
  <small>Enter date as Month / Day / Year</small>
  </br>
  <label for="amount">Date 3</label>
  <input type="text" id="date" />
  <small>Enter date as Month / Day / Year</small>

</form>
CertainPerformance
  • 260,466
  • 31
  • 181
  • 209
  • Very quick answer. Thank you ! – Alouani Younes Apr 15 '18 at 02:27
  • What should I modify to avoid adding spaces before and after slashes ? I changed this line : this.value = output.join('').substr(0, 14); to : this.value = output.join('').substr(0, 10); But then I can't delete the date to modify it. – Alouani Younes Apr 15 '18 at 04:43
1

You can process events on multiple nodes in the same manner by listening for the events on a common parent node and only processing those fired on particular targets. The method is called "event delegation", discussed in this question for example, with additional discussion and related questions easily found.

To apply the method in this case, I declared the date handler functions as named functions (they could have been written inline), added a data-type="date" attribute value to each of the date inputs to identify them (other ways could be found), and checked events when they reached the form element:

function checkValue(str, max) {
  if (str.charAt(0) !== '0' || str == '00') {
    var num = parseInt(str);
    if (isNaN(num) || num <= 0 || num > max) num = 1;
    str = num > parseInt(max.toString().charAt(0)) && num.toString().length == 1 ? '0' + num : num.toString();
  };
  return str;
};
  
function dateInput(date) {  // date is input object argument
  var input = date.value;
  if (/\D\/$/.test(input))
    input = input.substr(0, input.length - 3);
  var values = input.split('/').map(function(v) {
    return v.replace(/\D/g, '')
  });
  if (values[0]) {
    values[0] = checkValue(values[0], 12);
  }
  if (values[1]) {
    values[1] = checkValue(values[1], 31);
  }
  var output = values.map(function(v, i) {
    return v.length == 2 && i < 2 ? v + ' / ' : v;
  });
  date.value = output.join('').substr(0, 14);
}

function dateBlur(date) {  // date is input object argument
  var input = date.value;
  var values = input.split('/').map(function(v, i) {
    return v.replace(/\D/g, '')
  });
  var output = '';
  if (values.length == 3) {
    var year = values[2].length !== 4 ? parseInt(values[2]) + 2000 : parseInt(values[2]);
    var month = parseInt(values[0]) - 1;
    var day = parseInt(values[1]);
    var d = new Date(year, month, day);
    if (!isNaN(d)) {
      document.getElementById('result').innerText = d.toString();
      var dates = [d.getMonth() + 1, d.getDate(), d.getFullYear()];
      output = dates.map(function(v) {
        v = v.toString();
        return v.length == 1 ? '0' + v : v;
      }).join(' / ');
    }
  }
  date.value = output;
}
// delegate event handling from the form element

var form = document.getElementById("form");
form.addEventListener("blur", function(e) {
  if(e.target.dataset.type == "date") {
    dateBlur(e.target);
  }
},false);
form.addEventListener("input", function(e) {
  if(e.target.dataset.type == "date") {
    dateInput(e.target);
  }
}, false);
html {
  box-sizing: border-box;
  font-family: 'PT Sans', sans-serif;
-webkit-font-smoothing: antialiased;
}
*, 
*:before, 
*:after {
  box-sizing: inherit;
}
body {
    background-color: #f3f3f3;
}
form {
    width: 100%;
    max-width: 400px;
    margin: 60px auto;
}
form input {
    font-size: 30px;
    padding: 0 20px;
    border: 2px solid #ccc;
    width: 100%;
    color: #666;
    line-height: 3;
    border-radius: 7px;
    font-family: 'PT Sans', sans-serif;
    font-weight: bold;
}
form input:focus {
    outline: 0;
}
form input.error {
    border-color: #ff0000;  
}
form label.error {
    background-color: #ff0000;
    color: #fff;
    padding: 6px;
    font-size: 11px;
}

label {
    color: #999;
    display: block;
    margin-bottom: 10px;
    text-transform: uppercase;
    font-size: 18px;
    font-weight: bold;
    letter-spacing: 0.05em
}
form small {
    color: #888;
    font-size: 1em;
    margin-top: 10px;
    display: block;
    align-self: ;
}
<form id="form" method="post" action="">
  <label for="amount">Date</label>
  <input type="text" id="date1" data-type="date" />
  <small>Enter date as Month / Day / Year</small>
  </br>
  <label for="amount">Date 2</label>
  <input type="text" id="date2" data-type="date" />
  <small>Enter date as Month / Day / Year</small>
  </br>
  <label for="amount">Date 3</label>
  <input type="text" id="date3" data-type="date" />
  <small>Enter date as Month / Day / Year</small>
  
</form>
traktor
  • 12,838
  • 3
  • 23
  • 44