Question

I Am trying to build a custom START and END date selector, but unfortunatelly because of the design I won't be able to use the jquery UI Datepicker, so I am stucked with the old fashionate of splitting the dates in 3 <select>s

In order to keep this feature usable, we find this -at least- complicated parts:

  • Let days make sense with each month (Dont want 31 of feb selectable , ..)
  • Set next thay from START to the END selector

So I thought better to delegate the date calculation to the javascript Date() object so at least I can abstrat that part.

I am almost there,

But some how the Date() object tell the right date, but both selectors show each set of Days for the previous one (For example, top 28 days happen in March instead of FEB)

    $(function(){

months = ['jan','feb','mar','apr','may','jun','jul','ago','sep','oct','nov','dec'];
        /* Cachear selects */
        var $ld = $('select[name=llegada-dia]');
        var $lm = $('select[name=llegada-mes]');
        var $ly = $('select[name=llegada-ano]');
        var $sd = $('select[name=salida-dia]');
        var $sm = $('select[name=salida-mes]');
        var $sy = $('select[name=salida-ano]');
        var manyDays = function( month, year ){
            var fecha = new Date(year, (month) , 0);
            return fecha.getDate();
        }
        var paintCals = function( day, month , year ){
            if(day == '') day = 1;
            if(month == '') month = 0;
            if(year == '' ) year = 2013;
            //month = month -1;
            var fecha = new Date( year, month , day );          
            var dia = fecha.getDate();
            var mes = fecha.getMonth();
            var anyo = fecha.getFullYear();
            var dias_mes = manyDays( mes,anyo );
            /* Generate next date = fecha + 1 */
            var next_fecha = fecha;

            next_fecha.setDate(next_fecha.getDate() + 1); 
            next_fecha.setMonth(fecha.getMonth() + (dia == dias_mes ? 1 : 0)   ); 
            next_fecha.setFullYear(fecha.getFullYear() + (mes == 12 ? 1 : 0)  ); 

            var next_dia = next_fecha.getDate();
            var next_mes = next_fecha.getMonth();
            var next_anyo = next_fecha.getFullYear();
            var next_dias_mes = manyDays( next_mes, next_anyo ) ;
            $ld.empty();
            for(var tmpdia = 1; tmpdia <= dias_mes; tmpdia++){
                var doption = $('<option>').attr( 'value',tmpdia )
                                           .text( tmpdia );
                if(dia == tmpdia && dia != ''){
                    doption.attr('selected', 'selected');
                }
                $ld.append(doption);
            }
            /* Actualizar dias salida */
            $sd.empty();
            for(var tmpdia = next_dia; tmpdia <= next_dias_mes; tmpdia++){
                var doption = $('<option>').attr( 'value' , tmpdia )
                                           .text(tmpdia);
                if(next_dia == tmpdia && next_dia != ''){
                    doption.attr('selected', 'selected');
                }
                $sd.append(doption);
            }
            /* Actualizar meses salida */
            $sm.empty();
            for(var tmpmes = next_mes ; tmpmes < 12; tmpmes++){
                var doption = $('<option>').attr('value',tmpmes)
                                           .text(months[tmpmes]);
                if(dia == tmpdia && dia != ''){
                    doption.attr('selected', 'selected');
                }
                $sm.append(doption);
            }
            /* Actualizar anyos salida */
            $sy.empty();
            for(var tmpanyo = next_anyo; tmpanyo <= 2020; tmpanyo++){
                var doption = $('<option>').attr('value',tmpanyo)
                                           .text(tmpanyo);
                if(next_anyo == tmpanyo && next_anyo != ''){
                    doption.attr('selected', 'selected');
                }
                $sy.append(doption);
            }
        } 
        $('.arrival select').on('change',function(){
            var ldia = $ld.val();
            var lmes = $lm.val();
            var lano = $ly.val();
            var ldias = paintCals(ldia,lmes,lano);
        });
    })

And here it can be fiddled:

http://jsfiddle.net/96qyH/8/

Any idea what I'm missing here?

Was it helpful?

Solution 3

Edit: Refactored some code to remove extra logic

I have used three utility function to update the start and end date drop down.

  1. daysInMonth - gets the number of days in a specified month of a year
  2. adjustDates - updates the number of days in the drop down based on the selected month. Note: This simply appends/remove options instead of rebuilding from scratch.
  3. resetDates - Reset the start month and year

I rewrote your code and tried to achieve the same logic you had in your demo code. Check out my demo and let me know if it works for you.

DEMO: http://jsfiddle.net/ByWhz/1/

Full Code:

$(function () {
    months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dec'];

    /* Cachear selects */
    var $ld = $('select[name=llegada-dia]');
    var $lm = $('select[name=llegada-mes]');
    var $ly = $('select[name=llegada-ano]');
    var $sd = $('select[name=salida-dia]');
    var $sm = $('select[name=salida-mes]');
    var $sy = $('select[name=salida-ano]');

    //http://stackoverflow.com/a/1184359/297641
    function daysInMonth(month, year) {
        return new Date(year, month, 0).getDate();
    }

    function adjustDates(selMonthDates, $sel) {
        var $options = $sel.find('option');
        var dates = $options.length;
        //append/remove missing dates
        if (dates > selMonthDates) { //remove
            $options.slice(selMonthDates).remove();
        } else { //append
            var dateOptions = [];
            for (var date = dates + 1; date <= selMonthDates; date++) {
                dateOptions.push('<option value="' + date + '">' + date + '</option>');
            }
            $sel.append(dateOptions.join('')); //reduces DOM call
        }
    }

    function resetDates() {
        $lm.val(function (i, v) {
            return (v == '') ? '0' : v;
        });
        $ly.val(function (i, v) {
            return (v == '') ? '2013' : v;
        });
    }

    var paintCals = function (day, month, year) {

        resetDates();

        //adjust start date
        var selMonthDates = daysInMonth((parseInt($lm.val(), 10) + 1), $ly.val());
        adjustDates(selMonthDates, $ld);

        //If current day selection > number of days in selected month then set the day to max allowed day
        if (day > selMonthDates) {
            day = selMonthDates;
            $ld.val(day); //update selection
        }

        //selected start date
        var selectedDate = new Date(year, month, day);

        //next day from start date
        var nextDay = new Date(selectedDate.getTime() + 86400000);

        //lets build the end year drop down
        var tmpArr = [];
        for (var yrs = parseInt(nextDay.getFullYear(), 10); yrs <= 2020; yrs++) {
            tmpArr.push('<option value="' + yrs + '">' + yrs + '</option>');
        }
        $sy.empty().append(tmpArr.join('')); //set the YEARS
        //simply set the month
        $sm.val(nextDay.getMonth()); //set the month

        //adjust end date
        selMonthDates = daysInMonth(parseInt(nextDay.getMonth(), 10) + 1, nextDay.getFullYear());
        adjustDates(selMonthDates, $sd);

        $sd.val(nextDay.getDate()); //set the date

        $('#log').empty().append('Fecha: ' + selectedDate).append('<br>');
        $('#log').append('Siguiente: ' + nextDay);
    }

    $('.arrival select').on('change', function () {
        var ldia = $ld.val();
        var lmes = $lm.val();
        var lano = $ly.val();
        //console.log('lD/lM/lY:'+ldia,lmes,lano);

        var ldias = paintCals(ldia, lmes, lano);
    });
});

OTHER TIPS

First: manyDays fn needs to be:

var days_in_month = new Date(year, month+1, 0);

Because otherwise it goes back to previous month. You can read more about it in the comments of the most popular answer here: Get number days in a specified month using javascript?

Second: I removed

next_fecha.setMonth(fecha.getMonth() + (dia == dias_mes ? 1 : 0)   );

Since you already have

next_fecha.setDate(next_fecha.getDate()+1);

It switches month automatically in the end of month.

Third: this part had dia instead of month:

/* Actualizar meses salida */
        $sm.empty();
        for(var tmpmes = next_mes ; tmpmes < 12; tmpmes++){
    var doption = $('<option>').attr('value',tmpmes).text(months[tmpmes]);
            if(next_mes == tmpmes && next_mes != ''){
                console.log('fired');
      doption.attr('selected', 'selected');
            }
            $sm.append(doption);
        }

---- ADDED ----

Fourth: It also needs a check to see if Feb 31 exists. And if it is not, day should be the last day of that month. Otherwise the Date object didn't know what to do with the date and was giving wrong date.

Just needs to add a check:

var check_fecha = new Date( year, month );          
        check_mes = check_fecha.getMonth();
  check_anyo = check_fecha.getFullYear();
  var check_dias_mes = manyDays( check_mes, check_anyo );

  if(day > check_dias_mes)
    day = check_dias_mes;

Hope it solves it, check it out here: http://jsfiddle.net/96qyH/13/

If source code is required

I have found some answers for you! That might help you out! :) I have created a plugin too, but you won't need it to be the only answer. So here is what would help you. If you want to have a look at the code, you can get the source code here: https://github.com/the-vs-organisation/vdatetimepicker

Solution to the first one

Let days make sense with each month (Dont want 31 of feb selectable , ..)

For this issue, I used the value from the list of month as:

var month = $('#monthval').attr('id');
if(month == "February") {
  // hide the last few list items..
}

And then so on. You can see the source code in my plugin. You will find out how to interact with the other months too.

You can use id of the container as its value, as we won't use select Because you cannot style the style attribute. So you will require a custom created datetimepicker.

Disabing any of the item in the list won't be a good idea for the UI, so you might need to hide it. Hiding it won't bother UI or user himself. As the item won't be available he won't have to worry about the thing.

Second one

I am not sure what you wanted in this one, so I will leave it instead of giving a vague answer.

My Assumptions

If you want to get the time remaining or if you want to compare them, then you can use this:

var time = "some_string_of_time";
var curTime = new Date();
// now time to get the seconds between it..
var seconds = Math.floor((curTime - Time) / (1000));
// now write them..
if (seconds > 0 && seconds < 60) {// seconds..
    $('element') = seconds + " seconds ago";
}

Hiding the values

But some how the Date() object tell the right date, but both selectors show each set of Days for the previous one (For example, top 28 days happen in March instead of FEB)

This can be minimized in select as well as in my code. All you need to use is a simple class or id and on the click event for the month selector. Hide the excessive list items if any. Otherwise show. In my plugin, January and December will always have 31 days, Feb will have 28. It all depends on the list item selected in the Month dropdown. You can update the code in other div using jQuery.

If you don't care about UI

If you don't want to style the UI, have you tried using the input tool?

<input type="date" name="datetime" />

This is a browser generated one. You can always use it when nothing else gets into handy.

How to access data from my plugin.

You can always try to get the data from the .vdtpckr once you have the fields filled by using their ID for example:

var month = $('.vmonthval').attr('id');

This way, you can get the values from this.

If you need any thing more, comment below! :) Also try to check out the source code from github. You will get many ideas from the code.

Where Date is called as a constructor with more than one argument, if values are greater than their logical range (e.g. 13 is provided as the month value or 70 for the minute value), the adjacent value will be adjusted. E.g. new Date(2013,13,1) is equivalent to new Date(2014,1,1), both create a date for 2014-02-01. Similarly for other values: new Date(2013,2,1,0,70) is equivalent to new Date(2013,2,1,1,10) which both create a date for 2013-03-01T01:10:00.

That's from the Note for date constructor at MDN

The issue is with the following function

var manyDays = function( month, year ){
  var fecha = new Date(year, (month) , 0);
  return fecha.getDate();
}

When you use 0 as the date you get the previous month

for example

var date1 = new Date(2013,2,0)
console.log(date1.getMonth()); // will print 1 = Feb

var date2 = new Date(2013,2,1)
console.log(date2.getMonth()); // will print 2 = March

var date3 = new Date(2013,2,32)
console.log(date3.getMonth()); // will print 3 = April

I don't know of any proper way to get the number of days of a month in Javascript. A know hack is to modify your manyDays method like this.

var manyDays = function( month, year ){
  // Note: month + 1
  var fecha = new Date(year, month+1 , 0);
  return fecha.getDate();
}

You dont need to do the following

next_fecha.setDate(next_fecha.getDate() + 1); 
next_fecha.setMonth(fecha.getMonth() + (dia == dias_mes ? 1 : 0)   ); 
next_fecha.setFullYear(fecha.getFullYear() + (mes == 12 ? 1 : 0)  );

It can be replace with just

next_fecha.setDate(next_fecha.getDate() + 1);

Because adding a day to the date object will update date,month,year etc appropriately.

Answer to the problem in comment

The for loop handling the months in this version has an error. You are using the wrong variable. Change dia and tmpdia to next_mes and tmpmes respectively.

it should be like this

$sm.empty();
for(var tmpmes = next_mes ; tmpmes < 12; tmpmes++){
  var doption = $('<option>').attr('value',tmpmes).text(months[tmpmes]);
  if(next_mes == tmpmes && next_mes != ''){
    doption.attr('selected', 'selected');
  }
  $sm.append(doption);
}

The following is the updated version with the above problem fixed.

http://jsfiddle.net/96qyH/14/

check out this fiddle by me, hope this works for you

For HTML

    <form name="myForm" id="myForm">
    <fieldset id='dbOne'>
        <legend>Start Date</legend>
        <label for="yearOne">Year</label>
        <select name="yearOne" id="yearOne" title="Year" class='year'></select>
        <label for="monthOne">Month</label>
        <select name="monthOne" id="monthOne" title="Month"  class='month'></select>

        <label for="dayOne">Day</label>
        <select name="dayOne" id="dayOne" title="Day"  class='day'></select>
    </fieldset>
    <fieldset id='dbTwo'>
        <legend>End Date</legend>
        <label for="yearTwo">Year</label>
        <select name="yearTwo" id="yearTwo" title="Year" class='year'></select>
        <label for="monthTwo">Month</label>
        <select name="monthTwo" id="monthTwo" title="Month"  class='month'></select>
        <label for="dayTwo">Day</label>
        <select name="dayTwo" id="dayTwo" title="Day"  class='day'></select>
    </fieldset>
</form>

AND JS

  $(function () {
        // get days in month
        function daysInMonth(month, year) {

            month = parseInt(month,10)+1;
            month=month.toString();
            return new Date(year, month, 0).getDate();
        }
        //creates an array of number, a = array size, b starting num
        var numberArray = function (a, b) {
            c = [];
            for (var i = 0; i < a; i++) {
                c[i] = i + b;
            }
            return c;
        };
        //generates numeric drop down
        function createOptions(parent, options) {
            var l = options.length;
            for (var i = 0; i < l; i++) {
                var val = options[i];
                var text = options[i];
                var node = document.createElement("option");
                node.textContent = text;
                node.value = val;
                if(i===0) node.selected='selected';
                parent.appendChild(node);

            }
        }
        //generates drop down with numeric value string text
        function getOptionFromMap(parent, map) {
            for (var i = 0; i < map.length; i++) {
                var x = map[i];
                var val = x.key;
                var text = x.val;
                var node = document.createElement("option");
                node.textContent = text;
                node.value = val;
                if(i===0) node.selected='selected';
                parent.appendChild(node);
            }

        }
        var years = numberArray(20, 2000);
        var days = numberArray(31, 1);
        var months = [{
            key: 00,
            val: "jan"
        }, {
            key: 01,
            val: "feb"
        }, {
            key: 02,
            val: "mar"
        }, {
            key: 03,
            val: "apr"
        }, {
            key: 04,
            val: "may"
        }, {
            key: 05,
            val: "jun"
        }, {
            key: 06,
            val: "jul"
        }, {
            key: 07,
            val: "aug"
        }, {
            key: 08,
            val: "sep"
        }, {
            key: 09,
            val: "oct"
        }, {
            key: 10,
            val: "nov"
        }, {
            key: 11,
            val: "dec"
        }];
        createOptions(document.getElementById('dayOne'), days);
        createOptions(document.getElementById('dayTwo'), days);
        createOptions(document.getElementById('yearOne'), years);
        createOptions(document.getElementById('yearTwo'), years);
        getOptionFromMap(document.getElementById('monthOne'), months);
        getOptionFromMap(document.getElementById('monthTwo'), months);

        $(".year,.month").bind({
            change:function(){
                var dInMonth = daysInMonth($('.month',$(this).parent()).val(), $('.year',$(this).parent()).val());
                $('.day',$(this).parent()).children().each(function(){

                    var cEle = $(this);
                    var cValue = parseInt(cEle.html(),10);
                    if(!cEle.is(':disabled') && (cValue>dInMonth)) {
                        cEle.attr({disabled:true});
                    } else if(cEle.is(':disabled') && (cValue<=dInMonth)) {
                        cEle.attr({disabled:false});
                    }
                });
            }
        });

        $('select',$('#dbOne,#dbTwo')).bind({
            change:function(){
                var dBoxOne = $('#dbOne');
                var dBoxtwo = $('#dbTwo');
                var dtOne = getDateString(dBoxOne);
                var dOne = new Date(dtOne.y,dtOne.m,dtOne.d);
                var dtTwo = getDateString(dBoxtwo);
                var dTwo = new Date(dtTwo.y,dtTwo.m,dtTwo.d);
                if(dOne>dTwo) {
                    var nextDay = parseInt(dtOne.d,10)+1;
                    //alert('a');
                    dTwo = new Date(dtOne.y,dtOne.m,nextDay.toString());
                    setDate(dBoxtwo,dTwo);
                } else {

                }
            }
        });

        function getDateString(dateBox){
            var year = $('.year',dateBox).val();
            var month = $('.month',dateBox).val();
            var day = $('.day',dateBox).val();
            var da = year+"/"+month+"/"+day;
            var d = {y:year,m:month,d:day};
            return d;

        }

        function setDate(dateBox,dateObj){
            var year = dateObj.getFullYear();
            var month = dateObj.getMonth();
            var day = dateObj.getDate();

            $('.year',dateBox).val(year);
            $('.month',dateBox).val(month);
            $('.day',dateBox).val(day);

        }



    });

Here is my effort - Covers all Edge Cases

It is a robust well-tested date picker by now and answers the following questions:

  • Let days make sense with each month (Dont want 31 of feb selectable , ..)
  • Set next thay from START to the END selector

...

But some how the Date() object tell the right date, but both selectors show each set of Days for the previous one (For example, top 28 days happen in March instead of FEB)

If you look below I take care of all the above and more using small modular functions:

  • maxDay() - given a month and year, it returns the max numbers of days in that month for that year
  • createAry() - given min, max and optional array of values returns an array from min to max or portion of values array between min and max indices
  • days() - given min day and month and year uses maxDay() to get max day. Uses createAry() to return an array ready to use to populate day <select> options
  • months() - given min month and an array of month text values (like 'jan', 'feb', etc.) uses createAry() to return an array ready to use to populate month <select> options
  • years() - given min year uses MAXYEAR constant and createAry() to return an array ready to use to populate year <select> options
  • updateSelectOptions() - given a jQuery DOM <select> element, an array of option texts from day(), month() or year() and selected option index, empties then fills <select>; with array values and selects appropriate option.
  • updateTos() - updates the End Date rolling past Start Date if necessary and doing min month, day and year calculations based on Start Date values. Calls updateSelectOptions() for month, day and year giving jQuery <select> DOM element and arrays returned by month(), day() and year() to update <select> options with appropriate text values and selecting correct current option.
  • update() - updates either start or end date internally with new Date() using month, day and year from selected <select> options. For Start Date, calls updateSelectOptions() using days() and maxDay() to limit days according to month and year and select current option. Calls updateTos() to update End Date and its <select> options.

jsFiddle:

FIDDLE

HTML:

<form>
    <fieldset class="dateField">
        <legend>Start Date</legend>
        <label for="fromMonth">Month</label>
        <select id="fromMonth" class='monthSelect'></select>
        <label for="fromDay">Day</label>
        <select id="fromDay" class='daySelect'></select>
        <label for="fromYear">Year</label>
        <select id="fromYear" class='yearSelect'></select>
    </fieldset>
    <fieldset class="dateField">
        <legend>End Date</legend>
        <label for="toMonth">Month</label>
        <select id="toMonth" class='monthSelect'></select>
        <label for="toDay">Day</label>
        <select id="toDay" class='daySelect'></select>
        <label for="toYear">Year</label>
        <select id="toYear" class='yearSelect'></select>
    </fieldset>
</form>
<input type="button" id="reset" class="resetButton" value="Reset" />

JavaScript:

'use strict';

var fromDay = $('#fromDay'),
    fromMonth = $('#fromMonth'),
    fromYear = $('#fromYear'),
    toDay = $('#toDay'),
    toMonth = $('#toMonth'),
    toYear = $('#toYear'),
    reset = $('#reset'),
    CURDATE = new Date(),
    curFromDate,
    curToDate,
    MINYEAR = 2000,
    NUMYEARS = 40,
    MAXYEAR = MINYEAR + NUMYEARS - 1,
    MAXDATE = new Date(MAXYEAR, 11, 31),
    MONTHS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];

function maxDay(params) { // {month, year}

    params.month = params.month || 0;
    params.year = params.year || MINYEAR;

    return new Date(params.year, (params.month - 0) + 1, 0).getDate();
}

function createAry(params) { // { min, max, values }

    var i,
        ary = [];

    params.values = params.values || [];

    if (params.values.length !== 0) {
        return params.values.slice(params.min, params.max + 1);
    }

    for (i = 0; i < params.max - params.min + 1; i++) {
        ary[i] = params.min + i;
    }

    return ary;
}

function days(params) { // {minDay, month, year}

    var max;

    params.month = params.month || 0;
    params.year = params.year || MINYEAR;

    max = maxDay({
        month: params.month,
        year: params.year
    });

    params.minDay = params.minDay || 1;

    return createAry({
        min: params.minDay,
        max: max
    });
}

function months(params) { // {minMonth, months}

    params.minMonth = params.minMonth || 0;

    return createAry({
        min: params.minMonth,
        max: 11,
        values: MONTHS
    })
}

function years(params) { // {minYear}

    params.minYear = params.minYear || MINYEAR;

    return createAry({
        min: params.minYear,
        max: MAXYEAR
    });
}

function updateSelectOptions(params) { // {select, options, current]

    params.select.empty();

    params.options.forEach(function (e, i) {
        params.select.append($('<option></option>').prop("selected", i === params.current).text(e));
    });

}

function updateTos() {

    var minDay = 1,
        minMonth = 0,
        minYear = curFromDate.getFullYear();

    if (curToDate <= curFromDate) {
        curToDate = new Date(curFromDate.getFullYear(), curFromDate.getMonth(), curFromDate.getDate() + 1);

        minYear = curToDate.getFullYear();

        if (minYear === curFromDate.getFullYear()) {
            minMonth = curToDate.getMonth();
        }
        if (curFromDate.getMonth() === curToDate.getMonth()) {
            minDay = curToDate.getDate();
        }
    } else if (curFromDate.getFullYear() === curToDate.getFullYear()) {
        minMonth = curFromDate.getMonth();
        if (curFromDate.getMonth() === curToDate.getMonth()) {
            minDay = curFromDate.getDate() + 1;
        }
    } else if (curFromDate.getDate() === 31 && curFromDate.getMonth() === 11) {
        minYear++;
    }

    updateSelectOptions({
        select: toDay,
        options: days({
            minDay: minDay,
            month: curToDate.getMonth(),
            year: curToDate.getFullYear()
        }),
        current: curToDate.getDate() - minDay
    });
    updateSelectOptions({
        select: toMonth,
        options: months({
            minMonth: minMonth,
            months: MONTHS
        }),
        current: curToDate.getMonth() - minMonth
    });
    updateSelectOptions({
        select: toYear,
        options: years({
            minYear: minYear
        }),
        current: curToDate.getFullYear() - minYear
    });
}

function update(params) { // {toOrFrom}

    var day,
        month,
        year,
        max,
        date;

    if (params.toOrFrom === 'from') {
        day = fromDay.find("option:selected").text();
        month = MONTHS.indexOf(fromMonth.find("option:selected").text());
        year = fromYear.find("option:selected").text();
    } else {
        day = toDay.find("option:selected").text();
        month = MONTHS.indexOf(toMonth.find("option:selected").text());
        year = toYear.find("option:selected").text();
    }
    max = maxDay({
        month: month,
        year: year
    });

    if (day > max) {
        day = max;
    }
    date = new Date(year, month, day);

    if (params.toOrFrom === 'from') {
        if (date >= MAXDATE) {
            alert('The date you entered is later than the latest allowed date.  Please enter a different date.');

            fromDay.prop("selectedIndex", curFromDate.getDate() - 1);
            fromMonth.prop("selectedIndex", curFromDate.getMonth());
            fromYear.prop("selectedIndex", curFromDate.getFullYear() - MINYEAR);

            return;
        }
        curFromDate = date;

        updateSelectOptions({
            select: fromDay,
            options: days({
                minDay: 1,
                month: month,
                year: year
            }),
            current: day - 1
        });

    } else {
        curToDate = date;
    }
    updateTos();
}

function onFromChange() {
    update({
        toOrFrom: 'from'
    });
}

function onToChange() {
    update({
        toOrFrom: 'to'
    });
}

function init() {
    curFromDate = new Date(CURDATE.getFullYear(), CURDATE.getMonth(), CURDATE.getDate());
    curToDate = new Date(CURDATE.getFullYear(), CURDATE.getMonth(), CURDATE.getDate());

    updateSelectOptions({
        select: fromDay,
        options: days({
            minDay: 1,
            month: curFromDate.getMonth(),
            year: curFromDate.getFullYear()
        }),
        current: curFromDate.getDate() - 1
    });
    updateSelectOptions({
        select: fromMonth,
        options: months({
            months: MONTHS
        }),
        current: curFromDate.getMonth()
    });
    updateSelectOptions({
        select: fromYear,
        options: years({
            minYear: MINYEAR
        }),
        current: curFromDate.getFullYear() - MINYEAR
    });

    updateTos();
}

fromDay.change(onFromChange);
fromMonth.change(onFromChange);
fromYear.change(onFromChange);
toDay.change(onToChange);
toMonth.change(onToChange);
toYear.change(onToChange);
reset.click(init);

init();

CSS:

fieldset.dateField {
    width: 375px;
}

select.daySelect {
    width: 45px;
    margin: 5px 10px 0 0;
}

select.monthSelect {
    width: 55px;
    margin: 5px 10px 0 10px;
}

select.yearSelect {
    width: 65px;
    margin: 5px 10px 0 0;
}

input.resetButton {
    margin: 10px 20px 0 0
}

Please have a look at this demo : http://jsfiddle.net/wared/XvEzz/. I'm sorry for having rewritten your code entirely but that was an easier way for me to figure out the issue. The code below should help you to resolve your problem. It's located at line 24 and executed when the user chooses a month. I've added some comments here in order to explain the process. The basic idea is to set the day to 1 temporarily, then to restore last selected day if possible :

// saves last selected day 
day = date.getDate();
// sets day to 1 in order to prevent the month from changing
date.setDate(1);
// sets selected month
date.setMonth(this.selectedIndex);
// retrieves number of days for this month
days = getDays(date);
// restores last selected day if included in this month
if (day <= days) date.setDate(day);
// refreshes days options
feedCombo(0, getRange(1, days));
// refreshes selected day
$select.eq(idx - 1).val(date.getDate());
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top