Populated select boxes for date

I am using the following function to generate three select boxes to select a date dd/mm/yyyy., with the current date selected:


<script type="text/javascript">
  var monthtext=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sept','Oct','Nov','Dec'];
  function populatedropdown(dayfield, monthfield, yearfield){
		
    var today=new Date()
    var thisyear = today.getFullYear()
    var dayfield = document.getElementById(dayfield)
    var monthfield=document.getElementById(monthfield)
    var yearfield=document.getElementById(yearfield)
		
    for (var i=1; i<31; i++)
    dayfield.options[i]=new Option(i, i+1)
    dayfield.options[today.getDate()]=new Option(today.getDate(), today.getDate(), true, true) //select today's day

    for (var m=0; m<12; m++)
    monthfield.options[m]=new Option(monthtext[m], monthtext[m])
    monthfield.options[today.getMonth()]=new Option(monthtext[today.getMonth()], monthtext[today.getMonth()], true, true) //select today's month

    for (var y=0; y<20; y++){
      yearfield.options[y]=new Option(thisyear, thisyear)
      thisyear+=1
    }
    yearfield.options[0]=new Option(today.getFullYear(), today.getFullYear(), true, true) //select today's year
}
</script>

This is working actually fine. The only thing i’m fighting with is that with using this method every month has 31 days. Is there a existing plugin that can handle this or should I adjust this function? And if so where is the best place to look or find a tutorial about this subject!

Thank you in advance

Hey donboe,

This is nothing that we can’t sort out :slight_smile:
Do you have some HTML to go with the JS?
A link to a page would be even better.

Hi Pullo. The link to the test page is: here

Like I said it is working fine, only when the month should display 30 days(April,June etc) or 28/29 days (Feb) it’s obviously not working!

Hi donboe,

Here is an idea of how one could go about things:

<!doctype html>
  <html>
  <head>
    <meta charset="utf-8">
    <title>Untitled Document</title>
  </head>

  <body>
    <form action="" name="someform">
      <select id="daydropdown"></select> 
      <select id="monthdropdown"></select> 
      <select id="yeardropdown"></select> 
    </form>

    <script type="text/javascript">
      var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sept','Oct','Nov','Dec'];

      function daysInMonth(month, year) {
        return new Date(year, month, 0).getDate();
      }

      function populateDates(){
        var today = new Date(),
            day = today.getUTCDate(),
            month = today.getUTCMonth(),
            year = today.getUTCFullYear(),
            daysInCurrMonth = daysInMonth(month, year);

        // Year
        for(var i = 0; i < 21; i++){
          var opt = document.createElement('option');
          opt.value = i + year;
          opt.text = i + year;
          yeardropdown.appendChild(opt);
        }

        // Month
        for(var i = 0; i < 12; i++){
          var opt = document.createElement('option');
          opt.value = months[i];
          opt.text = months[i];
          monthdropdown.appendChild(opt);
        }

        // Day
        for(var i = 0; i < daysInCurrMonth; i++){
          var opt = document.createElement('option');
          opt.value = i + 1;
          opt.text = i + 1;
          daydropdown.appendChild(opt);
        }
      }

      var daydropdown = document.getElementById("daydropdown"),
          monthdropdown = document.getElementById("monthdropdown"),
          yeardropdown = document.getElementById("yeardropdown");

      // Change handler for months
      monthdropdown.onchange = function(){
        var newMonth = months.indexOf(monthdropdown.value) + 1,
            newYear = yeardropdown.value;
        
        daysInCurrMonth = daysInMonth(newMonth, newYear);

        daydropdown.innerHTML = "";
        for(var i = 0; i < daysInCurrMonth; i++){
          var opt = document.createElement('option');
          opt.value = i + 1;
          opt.text = i + 1;
          daydropdown.appendChild(opt);
        }        
      }

      populateDates()
    </script>
  </body>
</html>

Is this going in the right direction?

Note: the code is full of duplication and needs tidying up. Let me know if you would like to use this and then I will make everything more generic, as well as build in some code to handle leap years.

Hi Pullo. This is in one word brilliant :tup: The only small thing I am missing now is that the current day (5th) is not selected when I land on the page. What should I adjust to get the current day?

Otherwise brilliant!

No probs :slight_smile:

Well you could do it like this:

// Day
for(var i = 0; i < daysInCurrMonth; i++){
  var opt = document.createElement('option');
  opt.value = i + 1;
  opt.text = i + 1;
  if(i === day-1){
    opt.setAttribute("selected", "selected")
  }
  daydropdown.appendChild(opt);
}

But this is all getting ugly and hacky.
I’m a bit tied up right now, but I’ll have a look at this tonight and tidy thing up a little.

One question, what should happen when the user changes the month.
Should the day default back to 1 or should it stay on the current value.

For example, if I am on the 5th January and I change the month to the February, should the day dropdown display 1 or 5?

Also, are you using jQuery for this project?
We can easily do this in plain JS but jQuery makes the syntax a little nicer.

Oh and by the way, the code already deals with leap years (just try going to Feb 2016 and seeing how many days there are).
:slight_smile:

Like I said brilliant Pullo! I saw that form the leap year :slight_smile: Yes when people change month I think the best way would be to default back to 1. Thanks a lot!!!

By the way. Yes im using jquery in this project

Hi donboe,

I made this a bit more modular.

You now specify a div with a class of dateDropdown and fill it with some fallback HTML which will be served to those people with JS disabled:

<div class="dateDropdown">
  <label for="dateField1">Please enter the date:</label>
  <input id="dateField1" type="text" placeholder="dd.mm.yyyy"/>
</div>

You can have as many of these dateDropdowns as you fancy on your page.

You then include jQuery and my code which is wrapped in this:

DateWidget = { ... }

Then you initialize it and it will add the appropriate dropdowns to all of the aforementioned divs.

DateWidget.init();

It will also initialize each date widget to the current date.

Here’s a demo.

And here’s the code:

<!doctype html>
  <html>
  <head>
    <meta charset="utf-8">
    <title>Date widget</title>
    <style>div{ margin:15px; }</style>
  </head>

  <body>
    <div class="dateDropdown">
      <label for="dateField1">Please enter the date:</label>
      <input id="dateField1" type="text" placeholder="dd.mm.yyyy"/>
    </div>

    <div class="dateDropdown">
      <label for="dateField2">Please enter the date:</label>
      <input id="dateField2" type="text" placeholder="dd.mm.yyyy"/>
    </div>

    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <script type="text/javascript">
      var s,
      DateWidget = {
        settings: {
          months: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sept','Oct','Nov','Dec'],
          day: new Date().getUTCDate(),
          currMonth: new Date().getUTCMonth(),
          currYear: new Date().getUTCFullYear(),
          yearOffset: 21,
          containers: $(".dateDropdown")
        },

        init: function() {
          s = this.settings;
          DW = this;
          s.containers.each(function(){
            DW.removeFallback(this);
            DW.createSelects(this);
            DW.populateSelects(this);
            DW.initializeSelects(this);
            DW.bindUIActions();
          })
        },

        getDaysInMonth: function(month, year) {
           return new Date(year, month, 0).getDate();
        },

        addDays: function(daySelect, numDays){
          $(daySelect).empty();

          for(var i = 0; i < numDays; i++){
            $("<option />")
              .text(i + 1)
              .val(i + 1)
              .appendTo(daySelect);
          }
        },

        addMonths: function(monthSelect){
          for(var i = 0; i < 12; i++){
            $("<option />")
              .text(s.months[i])
              .val(s.months[i])
              .appendTo(monthSelect);
          }
        },

        addYears: function(yearSelect){
          for(var i = 0; i < s.yearOffset; i++){
            $("<option />")
              .text(i + s.currYear)
              .val(i + s.currYear)
              .appendTo(yearSelect);
          }
        },

        removeFallback: function(container) {
          $(container).empty();
        },

        createSelects: function(container) {
          $("<select class='day'>").appendTo(container);
          $("<select class='month'>").appendTo(container);
          $("<select class='year'>").appendTo(container);
        },

        populateSelects: function(container) {
          DW.addDays($(container).find('.day'), DW.getDaysInMonth(s.currMonth, s.currYear));
          DW.addMonths($(container).find('.month'));
          DW.addYears($(container).find('.year'));
        },

        initializeSelects: function(container) {
          $(container).find('.day').val(s.day);
          $(container).find('.currMonth').val(s.month);
          $(container).find('.currYear').val(s.year);
        },

        bindUIActions: function() {
          $(".month").on("change", function(){
            var daySelect = $(this).prev(), 
                yearSelect = $(this).next(),
                month = s.months.indexOf($(this).val()) + 1,
                days = DW.getDaysInMonth(month, yearSelect.val());
            DW.addDays(daySelect, days);
          });

          $(".year").on("change", function(){
            var daySelect = $(this).prev().prev(), 
                monthSelect = $(this).prev(),
                month = s.months.indexOf(monthSelect.val()) + 1,
                days = DW.getDaysInMonth(month, $(this).val());
            DW.addDays(daySelect, days);
          });
        }
      };

      DateWidget.init();
    </script>
  </body>
</html>

Very nice indeed Pullo. :slight_smile: Thanks a lot.

Hi Pullo.

Can I ask a question? Why are you creating three Date objects to get the current day, month and year in settings? One should be enough.

Yeah, I wasn’t happy with that:
I essentially wanted to do:

settings: {
  today: new.Date(),
  currDay: today.getUTCDate(),
  currMonth: today.getUTCMonth(),
  currYear: today.getUTCFullYear()
},

but didn’t think it was possible to reference a previously declared attribute in this way.

What would be the better approach here?


        init: function() {
          var today = new Date();

          this.settings = {
            months: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sept','Oct','Nov','Dec'],
            day: today.getUTCDate(),
            currMonth: today.getUTCMonth(),
            currYear: today.getUTCFullYear(),
            yearOffset: 21
          };

          s = this.settings;
          DW = this;
        }

Yeah, ok, should’ve seen that :slight_smile:

What about s? You are caching the settings, but you’re doing that outside the DateWidget object. It’s not pretty. Also, settings are DateWidget property, do you think it needs additional caching like this? Or DW?

I wanted to try and make this a little more modular for the OP, for example so it can just be dropped into a page with little configuration.

The s thing sure doesn’t look pretty, I agree with you there.
I got the skeleton for everything from here: http://css-tricks.com/how-do-you-structure-javascript-the-module-pattern-edition/

If this can be improved, or if this was the wrong pattern to use, I’d be glad to hear it.

Unfortunately for Chris Coyier, that’s not the module pattern. The module pattern involves a private scope created by an IIFE.

Anyway, caching a property like what you’re trying to do with s is not useful. Consider what happens in the most likely to occur misuse scenario: if you fail to include its declaration. It will simply be added as a property to DateWidget, creating a reference for settings property in the same DateWidget object. Redundant.

The current case, where you include its declaration, it’s simply a reference, it doesn’t cache more than an object property would.

It’s the right pattern, not using IIFE, because you don’t need hidden state, and you need shared behavior. Just that s and DW are not really doing anything useful. I’ll try and see if I can amend your widget.

I’d appreciate that!
Look forward to your suggestions.

Let’s take this step by step. First step: let’s disregard the HTML and the DOM.

This is my idea of a starter MyDate class:


function MyDate() {};

MyDate.prototype = {

    MONTHS: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sept','Oct','Nov','Dec'],

    YEAR_OFFSET: 21,

    getToday: function () {
        var today = new Date();

        return {
            day: today.getUTCDate(),
            month: today.getUTCMonth(),
            year: today.getUTCFullYear()
        };
    },

    getDaysInMonth: function(month, year) {
        return new Date(year, month, 0).getDate();
    }

};

> var date = new MyDate();
> date
> MyDate {MONTHS: Array[12], YEAR_OFFSET: 21, getToday: function, getDaysInMonth: function}