It’s funny how the simplest things crop up that I’ve not had to develop previously. One such thing is a calendar picker that lets a user select a date range. Typically, I would handle this as two separate data selectors, but using a single calendar seemed like a better experience in this particular case.
Angular UI’s Datepicker does not support ranges. It’s relatively limited in that regard. But, that Datepicker can be tweaked a bit to support ranges since it’s relatively customizable with options for setting CSS properties when the calendar is updated. Used in conjunction with the ng-change attribute/directive, it is possible to make a simple date range picker.
The premise is that when the model is changed (ng-change), we’ll call a method that evaluates the selected date. On first selection, the selection will be considered the start date. On second selection, then this will be the end date. Subsequent selections will restart the process. Simple mod math is used to handle this. The only other case we’re interested in evaluating is when the end selection is less than the start selection. The objects that will be used to track start/end will be flipped when this occurs.
uib-datepicker takes options that facilitate what we’re trying to accomplish. We can pass min/max dates and references to methods that we want it to use for applying a CSS class to each calendar cell. Along with the ng-change handler, the code looks like the code snippet below. I have the concept that a user is selecting a date within a specific period. So, I apply the class “period” to any date within the period by telling uib-datepicker to use the method “getDayClass” when the calendar is drawn. dt1/dt2 are the start/end dates. The “getDayClass” method evaluates these dates to determine if the calendar date being rendered is within the start/end date. When this is the case, both classes “period” and “active” are applied.
var select = 0; vm.options = { customClass: getDayClass, minDate: new Date(2017, 2, 1), maxDate: new Date(2017, 2, 15), showWeeks: true }; vm.change = function() { if (select === 0) { vm.dt2 = null; vm.dt1 = vm.dt; } else { vm.dt2 = vm.dt; if (vm.dt1 && vm.dt <= vm.dt1) { vm.dt2 = vm.dt1; vm.dt1 = vm.dt; } } select = (select + 1) % 2; } function getDayClass(data) { var date = data.date, mode = data.mode; if (vm.dt1 && date.getTime() == vm.dt1.getTime()) { return 'period active'; } if (vm.dt2 && date >= vm.dt1 && date <= vm.dt2) { return 'period active'; } if (date >= vm.options.minDate && date <= vm.options.maxDate) { return 'period'; } return ''; } };
Lastly, for fun, I additionally implemented this with two side-by-side uib-datepickers. It works in the same manner as with a single uib-datepicker. Check out the demo plunker below.