Picking date ranges with Mobiscroll
Picking a date range is a very common task in web apps or mobile apps, just think about a booking of any kind (flight, car rental, vacation etc.), but often it can be a nightmare for your customers, especially on mobile devices.
At Mobiscroll we try to bring the best UX solutions to work out of the box, but also leave space for customization through a rich API.
Let’s have this simple, very common scenario:
- Need to pick a range with a Start and End date
- Start date cannot be earlier than today
- End date needs to be after Start date
- The selected range cannot exceed 2 weeks
We will display the two dates with custom styling and two buttons to change the start and end dates:
The html markup looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <div class="range-cont"> <div id="range"></div> <div id="startDateBox" class="range-date"> <div class="range-lbl">Departure</div> <div class="range-day"></div> <div class="range-mon"></div> <div class="range-year"></div> <button id="startDate">Change Date</button> </div> <div id="endDateBox" class="range-date"> <div class="range-lbl">Return</div> <div class="range-day"></div> <div class="range-mon"></div> <div class="range-year"></div> <button id="endDate">Change Date</button> </div> </div> |
Below we will discuss two possible solutions, the first one uses two instances of the Mobiscroll Calendar, the second one uses the Mobiscroll Range.
1. Using two calendars for start and end dates
A less known but powerful feature about Mobiscroll components is that they can be initialized not only on input elements, but on any other HTML element. If not attached to an input the selected values will not appear on the UI automatically, so it’s up to the developer to channel it to the right place.
The calendars are linked to our two buttons (focus and tap events will bring up mobiscroll). In the onSelect
event we maintain the content of the labels with the formatted dates. If Start date was selected, we do a simple validation (start cannot be after end. If it is, we adjust the End date accordingly), and update the minDate
and maxDate
options of the second calendar:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | $(function () { function fillDateBox(box, date) { var d = date.getDate(); $('.range-day', box).html((d < 10 ? '0' : '') + d); $('.range-mon', box).html(monthNames[date.getMonth()]); $('.range-year', box).html(date.getFullYear()); } var monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], startDate = new Date(), // Initial start date // Initial end date endDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + 6), $startBox = $('#startDateBox'), $endBox = $('#endDateBox'), // Init start calendar $startCal = $('#startDate').mobiscroll().calendar({ minDate: startDate, defaultValue: startDate, onSelect: function (dateText, inst) { var newMinDate, newMaxDate; // Update start date and start label startDate = inst.getDate(); fillDateBox($startBox, startDate); // Validate selection if (startDate > endDate) { // If start is after end, modify end date endDate = new Date(startDate); // Set the new end date to the calendar, also update the label $endCal.mobiscroll('setDate', endDate); fillDateBox($endBox, endDate); } // Update minDate and maxDate for end date newMinDate = startDate, newMaxDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + 13); $endCal.mobiscroll('option', { minDate: newMinDate, maxDate: newMaxDate }); } }), // Init end calendar $endCal = $('#endDate').mobiscroll().calendar({ minDate: startDate, maxDate: new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + 13), defaultValue: endDate, onSelect: function (dateText, inst) { // Update end date and end label endDate = inst.getDate(); fillDateBox($endBox, endDate); } }); // Fill initial values fillDateBox($startBox, startDate); fillDateBox($endBox, endDate); }); |
2. Using a range picker
With the Range picker we can easily select a date range on a single view. We’ll still use the same markup from the previous example, but are going to initialize the range picker on the empty div, which was not used previously.
Using the startInput
and endInput
settings we ensure that the rangepicker will open in “Start” selection mode, when we open it with the startDate button, and in “End” selection mode, if opened with endDate button. The rangepicker automatically checks that the end date is after start date, so we don’t need to bother with that, and with the newly introduced (2.12.0-beta) maxRange
we can control the maximal length of the selection:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | $(function () { var monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], startDate = new Date(), endDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + 6), $startBox = $('#startDateBox'), $endBox = $('#endDateBox'); function fillDateBox(box, date) { var d = date.getDate(); $('.range-day', box).html((d < 10 ? '0' : '') + d); $('.range-mon', box).html(monthNames[date.getMonth()]); $('.range-year', box).html(date.getFullYear()); } fillDateBox($startBox, startDate); fillDateBox($endBox, endDate); $('#range').mobiscroll().rangepicker({ defaultValue: [startDate, endDate], startInput: '#startDate', endInput: '#endDate', maxRange: 14 * 24 * 60 * 60 * 1000, onSelect: function (dateText, inst) { var dates = inst.getValue(); startDate = dates[0]; endDate = dates[1]; fillDateBox($startBox, startDate); fillDateBox($endBox, endDate); } }); }); |
Another advantage of using the Range picker is the selected days will be highlighted on the UI, so the user can visually see the selection.
Downloads
You can download the example here Download Example
How are you handling date range selection in your mobile apps and webpages? Let us know your thoughts in the comment section below!