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:

Departure
10
Jul
2014
Change Date
Return
22
Jul
2014
Change Date

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

Mobiscroll Calendar

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);
});
The jQuery objects are stored in variables to prevent running the selectors over and over again.

2. Using a range picker

Mobiscroll Rangepicker

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!

Start building better products
Sign up and get posts like this in your inbox. We respect your time and will send you only the good stuff!

Tags: , , , ,

  • ivan

    Is there a way to cancel the onSelect event and keep the picker opened. For example, if we have invalid date inside the picked range.

  • wei devel

    hello, the download folder does not contain the js file and css .