-->

Date range picker on jquery ui datepicker

2019-01-16 13:11发布

问题:

I created a date range picker using jquery ui where you can use the same inline calendar to make both of your date selections.

See my fiddle here: http://jsfiddle.net/kVsbq/4/

JS

$(".datepicker").datepicker({
    minDate: 0,
    numberOfMonths: [12, 1],
    beforeShowDay: function (date) {
        var date1 = $.datepicker.parseDate($.datepicker._defaults.dateFormat, $("#input1").val());
        var date2 = $.datepicker.parseDate($.datepicker._defaults.dateFormat, $("#input2").val());
        return [true, date1 && ((date.getTime() == date1.getTime()) || (date2 && date >= date1 && date <= date2)) ? "dp-highlight" : ""];
    },
    onSelect: function (dateText, inst) {
        var date1 = $.datepicker.parseDate($.datepicker._defaults.dateFormat, $("#input1").val());
        var date2 = $.datepicker.parseDate($.datepicker._defaults.dateFormat, $("#input2").val());
        if (!date1 || date2) {
            $("#input1").val(dateText);
            $("#input2").val("");
            $(this).datepicker();
        } else {
            $("#input2").val(dateText);
            $(this).datepicker();
        }
    }
});

What I want to be able to do is a range selector like this: http://jsfiddle.net/D3wLX/1/

If you select an earlier date then the earlier date is automatically made the first date in the range and the middle dates are highlighted. Right now on my original jquery ui solution it will just put the earlier date in the second input and not highlight the dates in between.

回答1:

Your script was exactly what I was looking for. I forked your original fiddle and made only a slight adjustment to your onSelect to get it to work the way that you wanted.

onSelect: function(dateText, inst) {
    var date1 = $.datepicker.parseDate($.datepicker._defaults.dateFormat, $("#input1").val());
    var date2 = $.datepicker.parseDate($.datepicker._defaults.dateFormat, $("#input2").val());
    var selectedDate = $.datepicker.parseDate($.datepicker._defaults.dateFormat, dateText);


    if (!date1 || date2) {
        $("#input1").val(dateText);
        $("#input2").val("");
        $(this).datepicker();
    } else if( selectedDate < date1 ) {
        $("#input2").val( $("#input1").val() );
        $("#input1").val( dateText );
        $(this).datepicker();
    } else {
        $("#input2").val(dateText);
        $(this).datepicker();
    }
}

What was missing from your original section was simply a check to compare the current selected date value to the one that was already captured.

Here is my forked fiddle: http://jsfiddle.net/sWbfk/



回答2:

I found the answer here:

http://www.benknowscode.com/2012/11/selecting-ranges-jquery-ui-datepicker.html

Great tutorial

$.datepicker._defaults.onAfterUpdate = null;
var datepicker__updateDatepicker = $.datepicker._updateDatepicker;
$.datepicker._updateDatepicker = function( inst ) {
   datepicker__updateDatepicker.call( this, inst );
   var onAfterUpdate = this._get(inst, 'onAfterUpdate');
   if (onAfterUpdate)
      onAfterUpdate.apply((inst.input ? inst.input[0] : null),
         [(inst.input ? inst.input.val() : ''), inst]);
}
$(function() {
   var cur = -1, prv = -1;
   $('#jrange div')
      .datepicker({
            //numberOfMonths: 3,
            changeMonth: true,
            changeYear: true,
            showButtonPanel: true,
            beforeShowDay: function ( date ) {
                  return [true, ( (date.getTime() >= Math.min(prv, cur) && date.getTime() <= Math.max(prv, cur)) ? 'date-range-selected' : '')];
               },
            onSelect: function ( dateText, inst ) {
                  var d1, d2;
                  prv = cur;
                  cur = (new Date(inst.selectedYear, inst.selectedMonth, inst.selectedDay)).getTime();
                  if ( prv == -1 || prv == cur ) {
                     prv = cur;
                     $('#jrange input').val( dateText );
                  } else {
                     d1 = $.datepicker.formatDate( 'mm/dd/yy', new Date(Math.min(prv,cur)), {} );
                     d2 = $.datepicker.formatDate( 'mm/dd/yy', new Date(Math.max(prv,cur)), {} );
                     $('#jrange input').val( d1+' - '+d2 );
                  }
               },
            onChangeMonthYear: function ( year, month, inst ) {
                  //prv = cur = -1;
               },
            onAfterUpdate: function ( inst ) {
                  $('<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" data-handler="hide" data-event="click">Done</button>')
                     .appendTo($('#jrange div .ui-datepicker-buttonpane'))
                     .on('click', function () { $('#jrange div').hide(); });
               }
         })
      .position({
            my: 'left top',
            at: 'left bottom',
            of: $('#jrange input')
         })
      .hide();
   $('#jrange input').on('focus', function (e) {
         var v = this.value,
             d;
         try {
            if ( v.indexOf(' - ') > -1 ) {
               d = v.split(' - ');
               prv = $.datepicker.parseDate( 'mm/dd/yy', d[0] ).getTime();
               cur = $.datepicker.parseDate( 'mm/dd/yy', d[1] ).getTime();
            } else if ( v.length > 0 ) {
               prv = cur = $.datepicker.parseDate( 'mm/dd/yy', v ).getTime();
            }
         } catch ( e ) {
            cur = prv = -1;
         }
         if ( cur > -1 )
            $('#jrange div').datepicker('setDate', new Date(cur));
         $('#jrange div').datepicker('refresh').show();
      });
});
.wrapper {
   height: 600px;
}
#jrange input {
   width: 200px;
}
#jrange div {
   font-size: 9pt;
}
.date-range-selected > .ui-state-active,
.date-range-selected > .ui-state-default {
   background: none;
   background-color: lightsteelblue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<div class="wrapper">
   <div id="jrange" class="dates">
    <input />
    <div></div>
   </div>
</div>



回答3:

Dude, your code is really what I needed!

And with Jamie Layne's correction, I decided to use it to make a plugin.

Here is the link for jsfiddle: http://jsfiddle.net/dxLRm/35/ (link updated 2014/01/01)

Since I have to show some code, here is what I have:

(function ($) {
$.prototype.rangedatepicker = function (o,x,y) {
    var dp = $.datepicker,
        cl = dp.markerClassName,
        di = 'data-rdp-i',
        df = 'data-rdp-f';

    switch(o)
    {
        case 'option':
            return $(this).datepicker('option');
        case 'hide':
            return $(this).datepicker('hide');
        case 'show':
            return $(this).datepicker('show');
        case 'getInitialDate':
            return dp.parseDate($(this).eq(0).datepicker('option','dateFormat'),$(this).eq(0).attr(di)||'');
        case 'getFinalDate':
            return dp.parseDate($(this).eq(0).datepicker('option','dateFormat'),$(this).eq(0).attr(df)||'');
        case 'getRange':
            var ini=dp.parseDate($(this).eq(0).datepicker('option','dateFormat'),$(this).eq(0).attr(di)||''),
                fin=dp.parseDate($(this).eq(0).datepicker('option','dateFormat'),$(this).eq(0).attr(df)||'');
            return (!ini&&!fin)?null:[ini,fin];
        case 'getNumDays':
            var ini=dp.parseDate($(this).eq(0).datepicker('option','dateFormat'),$(this).eq(0).attr(di)||''),
                fin=dp.parseDate($(this).eq(0).datepicker('option','dateFormat'),$(this).eq(0).attr(df)||'');
            return (ini+0==0||fin+0==0)?0:Math.round((fin-ini)/86400000)+1;
        case 'removeRange':
            return $(this).attr(di,'').attr(df,'').datepicker('setDate',null);
        case 'destroy':
            return $(this).removeAttr(di).removeAttr(df).datepicker('destroy');
        case 'serialize':
            return this[0].id+'_initial='+this[0].getAttribute(di)+'&'+this[0].id+'_final='+this[0].getAttribute(df);
        default:
        var defaults={
            allowSelectOneDay: false,
            alwaysSetDateToFirstDay: true,
            rangeEnabled: true,
            rangeClass: 'ui-state-default ui-state-active'//'dp-highlight'
        };
            o = $.extend({}, defaults, $.datepicker._defaults, o);
        return $(this).each(function () {
            if (!$.datepicker) return;
            var t = this,
                hd = !! ((' ' + t.className + ' ').indexOf(' ' + cl + ' ') + 1);
            $(t).datepicker($.extend({}, o, {
                beforeShowDay: function (d) {
                    if (o.rangeEnabled) {
                        var d1 = dp.parseDate(o.dateFormat, t.getAttribute(di) || ''),
                            d2 = dp.parseDate(o.dateFormat, t.getAttribute(df) || ''),
                            y = (function (d) {
                                try {
                                    return o.beforeShowDay.call(t, d);
                                } catch (e) {}
                            })(d) || [true, '', null],
                            x = ((y && y[0] !== false) || !y) && d1 && ((d.getTime() == d1.getTime()) || (d2 && d >= d1 && d <= d2));
                        return (!d1||!d2)?y||[true,'',null]:[y[0]&&x, (x ? o.rangeClass || defaults.rangeClass : '') + (y[1] ? ' ' + y[1] : ''), y[2]];
                    } else {
                        return (function (d) {
                            try {
                                return o.beforeShowDay.call(t, d);
                            } catch (e) {}
                        })(d) || [true, '', null];
                    }
                },
                onSelect: function (dt, x) {
                    if (o.rangeEnabled) {
                        var i = t.getAttribute(di) || '',
                            f = t.getAttribute(df) || '',
                            d1 = dp.parseDate(o.dateFormat, i),
                            d2 = dp.parseDate(o.dateFormat, f),
                            s = dp.parseDate(o.dateFormat, dt);
                        if ((dt == i && dt == f) || (!o.allowSelectOneDay && ((dt == i && !f) || (dt == f && !i)))) {
                            t.removeAttribute(di);
                            t.removeAttribute(df);
                            $(t).datepicker('setDate', null);
                        } else if (!d1 || d2) {
                            t.setAttribute(di, dt);
                            t.removeAttribute(df);
                            o.alwaysSetDateToFirstDay && $(t).datepicker('setDate', s);
                        } else if (s < d1) {
                            t.setAttribute(df, i);
                            t.setAttribute(di, dt);
                            o.alwaysSetDateToFirstDay && $(t).datepicker('setDate', s);
                        } else {
                            t.setAttribute(df, dt);
                            o.alwaysSetDateToFirstDay && $(t).datepicker('setDate', d1);
                        }
                    } else {
                        t.removeAttribute(di);
                        t.removeAttribute(df);
                        $(t).datepicker('setDate', dp.parseDate(o.dateFormat, dt));
                    }

                    try {
                        if($(t).datepicker('getDate'))o.onSelect.call(t, dt, x);
                    } catch (e) {}
                }
            }));
        });
    }
};
})(window.jQuery);

You should access the fiddle and read the list of things to do!

Any idea or piece of code is appreciated!



回答4:

I was also looking for a way to extend the Datepicker jQuery plugin to utilize Bootstrap styling and came across this little gem:

Bootstrap-Date Range Picker by Dan Grossman shows some live examples with working code.

Here is the project GitHub as well.

Finally, here is a screenshot of the simplicity and power of the design:



回答5:

Looking for a date range picker myself, I found this page. I tried most ideas suggested and even demonstrated here and turned this all into an easy to use and integrate extension: https://github.com/BuroRaDer/DateRangePicker. Try the demo page to see how it works. I guess I could turn it into a real jQuery extension, but for now am happy with the way it works.

Live demos:

  • http://lescamelias.eu/en/availability
  • http://maisonnera.eu/

Both are Drupal sites using the Availability Calendar module into which it has been integrated now.



回答6:

I was looking for a version that would work even when it is not inlined. I wanted to be able to click on the input fields to fire up the range datepicker. All the range datepicker examples I could find were inlined (including the mcestone and Jamie Layne versions above, which are the basis for this forked code).

Here's the fiddle: http://jsfiddle.net/boson/pjffdtz2/

The hard part seems to be getting datepicker to handle multiple inputs when not inlined. Datepicker won't handle two inputs easily if you want to open datepicker on focus - there was definitely a 'trick'. If you associate the datepicker with a hidden input (display:none), create that hidden input before the visible inputs, then have your visible inputs show the datepicker on a click event, all is good.

So I took the original answer and just made a few minor changes:

  • In html, create a hidden input associated with the datepicker. List it before the visible inputs.
  • In html, use the click event of the visible inputs to show the datepicker associated with the hidden input.
  • Within the Javascript datepicker onSelect, temporarily put the datepicker in inline mode until both dates have been clicked. This allows you to click on multiple dates before datepicker closes (the range 'To' date and 'From' date) - this essentially turns datepicker into a multiclick datepicker.
  • In the datepicker onClose, turn off inline mode. This allows someone to click on the input fields to open datepicker again.
  • In beforeShow, move the datepicker down a few pixels so you can see the input fields and the datepicker

Here's the code:

$(function() {
  $(".rangepicker").datepicker({
    minDate: 0,
    numberOfMonths: [2, 1],
    beforeShow: function (input, inst) {
      var rect = input.getBoundingClientRect();
      setTimeout(function () {
	      inst.dpDiv.css({ top: rect.top + 40, left: rect.left + 0 });
      }, 0);
    },
    beforeShowDay: function(date) {
      var date1 = $.datepicker.parseDate($.datepicker._defaults.dateFormat, $("#input1").val());
      var date2 = $.datepicker.parseDate($.datepicker._defaults.dateFormat, $("#input2").val());
      var isHighlight =
         date1 && ((date.getTime() == date1.getTime()) || (date2 && date >= date1 && date <= date2));
      return [true, isHighlight ? "dp-highlight" : ""];
    },
    onSelect: function(dateText, inst) {
      var date1 = $.datepicker.parseDate($.datepicker._defaults.dateFormat, $("#input1").val());
      var date2 = $.datepicker.parseDate($.datepicker._defaults.dateFormat, $("#input2").val());
      var selectedDate = $.datepicker.parseDate($.datepicker._defaults.dateFormat, dateText);

      if (!date1 || date2) {
        $("#input1").val(dateText);  
        $("#input2").val("");        
      } else if (selectedDate < date1) {
          $("#input2").val($("#input1").val()); 
          $("#input1").val(dateText);  
      } else {
          $("#input2").val(dateText);  
      }
      $(this).data('datepicker').inline = true;           
      $(this).datepicker();
    },
    onClose: function() {
      // Since we went inline as soon as the date input was clicked
      // (to leave the datepicker up for both dates selection),
      // turn inline back off again so date input click will once again
      // display the datepicker
      $(this).data('datepicker').inline = false;
    }
  });
});
.dp-highlight .ui-state-default {
          background: #484;
          color: #FFF;
        }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<input type="text" id="input1_1" class="rangepicker" style="display: none">
<p>
    Dates:
    <label><b>To:</b></label>
    <input type="text" id="input1" onclick="$('.rangepicker').datepicker('show');">
    <label><b>From:</b></label>
    <input type="text" id="input2" onclick="$('.rangepicker').datepicker('show');">
    <button id="done">Done</button>
</p>

Lots of things left to improve. Needs better input validation. Especially would like to get the datepicker 'Done' button to work in inline mode but datepicker wasn't designed for this scenario (sure would be nice to have a settable Done button flag in datepicker). So for now, I have a cheesy done button alongside the input fields that actually does nothing (other than encouraging the user to take the focus off the datepicker to close the datepicker).



回答7:

Thanks I need this kind of code. Here is my code:

<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<link rel="stylesheet" href="/resources/demos/style.css">
<script     src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

 <div id="Datepicker"></div>
<p>
<label><b>Checkin:</b></label> <label id="checkinDate"></label>
<label><b>Checkout:</b></label> <label id="checkoutDate"></label>
</p>

/** Display Checkin Datepicker and Checkout DatePicker */
<script>
datePicker();
function datePicker(){
   $(document).ready(function(){
      $( "#Datepicker" ).datepicker({
         dateFormat: "MM d, yy",
     minDate: 0,
     maxDate: "+3M +0D", 
         beforeShowDay: dateRange,
     onSelect: DRonSelect
      });
   });
}

function dateRange(date){
   var date1 = $.datepicker.parseDate("MM d, yy", $("#checkinDate").text());
   var date2 = $.datepicker.parseDate("MM d, yy", $("#checkoutDate").text());
   var isHighlight = date1 && ((date.getTime() == date1.getTime()) || (date2 && date >= date1 && date <= date2));
      $(document).ready(function(){
  // $("td.dp-highlight").text("Y");

});
   return [true, isHighlight ? "dp-highlight" : ""];
}

function DRonSelect(dateText, inst) {
   var date1 = $.datepicker.parseDate("MM d, yy", $("#checkinDate").text());
   var date2 = $.datepicker.parseDate("MM d, yy", $("#checkoutDate").text());
      if (!date1 || date2) {
         $("#checkinDate").text(dateText);
     $("#checkoutDate").text("");
         $("#Datepicker").datepicker();
      } 
      else {
         if ( $.datepicker.parseDate("MM d, yy", $("#checkinDate").text()) >= 
$.datepicker.parseDate("MM d, yy", dateText)) {
            $("#checkinDate").text(dateText);
            $("#checkoutDate").text("");
            $("#Datepicker").datepicker();
         }
         else {
        $("#checkoutDate").text(dateText);
            $("#Datepicker").datepicker();
         }
      }   
}
</script>

My code is a sample code come from the others but differs in date selection, date range and higlights. I created and save a code in JSFIDDLE

https://jsfiddle.net/kk585b4g/