Saturday, June 21, 2014

Pick a better date

This post is content previously published on www.cathmhaol.com

In January I wrote an entry about how to integrate eternicode's datepicker, called "Pick a date". Because of a question on LinkedIn, I revisited that entry, and wondered what might make it a little better. Granted, it's probably not necessarily that difficult to think of a few ways – some technical and some related to the style. So, let's improve the styling of the date interface – something a little more "mobile friendly" – something like the lovely rebeccapurple sample shown here. I'll also take just a moment to point out that this method works well only if the date input is a single input. If your date input is broken into parts with number inputs for day, month, and year, you'll want to find a different approach.

HTML

Note: you might want to open the original post in another window so you can compare the code side-by-side.

Let's start by using the "date" type, because native support is always going to be better – and we can do this easily, because the input type fall-back is "text", which is what we used originally. To add to that, we're going to layer the styled elements, because frankly, the simple input box is boring...useful, but boring.

Of course the styled version only improves the interface if the date is populated – if you don't pre-populate the dates, you may want to use the associated CSS (opened and closed classes are shown here) and/or JavaScript to hide the styled interface until there's a value available, or you may wish to give the user other messaging. Also note that we're using the aria-hidden attribute on the styled block. We're doing that because it may be confusing to have the same date repeated and it's really just a styled version of the input, so it's unnecessary for ARIA devices. With these thoughts in mind, here's the HTML we'll be working with.

<fieldset>
  <legend>Date Range</legend>
  <div class="date">
    <div aria-hidden="true" class="styled closed">
      <span class="date-number">01</span>
      <span class="date-name">Thu</span>
      <span class="date-month">January</span>
      <span class="date-year">1970</span>
    </div>
    <div class="input opened">
      <label for="dtfrom">From:</label>
      <input id="dtfrom" type="date" value="1970-01-01" />
    </div>
  </div>
  <div class="date">
    <div aria-hidden="true" class="styled closed">
      <span class="date-number">31</span>
      <span class="date-name">Wed</span>
      <span class="date-month">December</span>
      <span class="date-year">2014</span>
    </div>
    <div class="input opened">
      <label for="dtto">To:</label>
      <input id="dtto" type="date" value="2014-12-31"/>
    </div>
  </div>
</fieldset>
<!-- don't add the 'inline-calendar" or "backToTop" elements if you're not displaying the calendar inline -->
<div id="inline-calendar"></div>
<a aria-hidden="true" name="backToTop">back to top</a>

Now that you have the HTML under control, go ahead and add the code for the datepicker – you can get that code from the "Pick a date" blog entry – just make sure that it's added unobtrusively. If you're going to be using a date range, go ahead and port over any other code you need for setting the range, e.g., the setValidRangeBegin, setValidRangeEnd, or setRange functions. Also, remember that we're only adding the eternicode if the "date" input is not supported, so add a check around the datepicker creation statement – that's the code that looks something like

$('#dtfrom').datepicker({
  language: strLanguageCode
  startDate: datStart,
  endDate: datEnd,
  autoclose: true,
  forceparse: true
})

If you're using modernizr, you can use if (!Modernizr.inputtypes.date), which is relatively simple. If you're not using modernizr for other purposes, don't add it just for this – write your own checker instead. If you're unclear about how to do this, check out "Detecting HTML5 Features" for a description of how modernizr does it.

CSS

Now that we have our HTML constructed, let's add the CSS that will enable us to show this lovely stylized sample. Note that in our HTML we have defaulted the style to have the input block open and the styled block closed – this will handle any user-agents where JavaScript is not running (which happens more often than you might think).

body {
  background:#639; /* use the code, because not all browsers use rebeccapurple */
  color:#fafafa;
  font-size:15px;
}

.date {
  display:inline-block;
  margin:0;
  padding:0;
  overflow:auto;
  text-align:center;
  width:175px;
}
.date > .styled {
  border:1px solid #fafafa;
  border-radius:0.5em;
  cursor:pointer;
  display:inline-block;
  margin:0.5em 0.25em;
  padding:0.2em;
  position:relative;
  width:auto;
  z-index:10;
}
.date > .styled.closed {
  clip:rect(0, 0, 0, 0);
  position:absolute;
}
.date > .styled > .date-number {
  font-size:1.5em;
}
.date > .styled > .date-name {
  font-size:1.5em;
}
.date > .styled > .date-month {
  font-size:0.8em;
}
.date > .styled > .date-month:after {
  content:", ";
}
.date > .styled > .date-year {
  font-size:0.8em;
}
.date > .input {
  clip:rect(0, 0, 0, 0);
  position:absolute;
}
.date > .input.opened {
  position:relative;
}

Of course your style needs may be different. and in fact probably are. Just be sure that you don't fall victim to little gotchas like lacking sufficient contrast between the background and foreground colors. Consider the code provided here an example, not hard, fast rules.

JavaScript

Now that we have the HTML and CSS, let's turn our attention to the JavaScript that handles switching the focus from the styled presentation to the input. This approach allows us to continue to use the focus handlers created by datepicker or the native implementation. Here I'm using jQuery because most everyone is familiar with it – I'm not using it to recommend that you use jQuery, in fact there are a lot of good reasons not to use it (which I won't go into in this entry).

$('.date .styled').click(function() {
  var $element = $(this)
    , $input = $element.next().find('input[type="date"]')
  ;
  $input.focus();
});

That code – which shifts focus to the input when the styled element is clicked – is simple enough. There are, of course, other ways of doing it. For example, you could add an attribute that links the two so you're not dependent on proximity in the code, but I'm trying to show a simple example here, and that solution adds unnecessary complexity.

Next, we're going to add listeners to the focus and blur events of the inputs, which will be executed alongside the datepicker listeners. First, we attach simple code that will just add the 'closed' and 'opened' classes to the elements to hide the stylized date and show the input.

$('.date .input input[type="date"]').focus(function() {
  var $element = $(this)
    , $input = $element.closest('.input')
    , $styled = $input.prev()
  ;

  // transition the visibility
  $styled.addClass('closed');
  $input.addClass('opened');
});

Then we add code that is slightly more complex, but does basically the same thing as the focus handler in reverse. The difference being that if the date input is empty, we do not open the stylized date and close the input – because that would simply show an empty block – and we only update the stylized date parts if we have a valid date.

$('.date .input input[type="date"]').blur(function() {
  var $element = $(this)
    , $input = $element.closest('.input')
    , $styled = $input.prev()
    , dt
  ;

  if ($element.val()) {
      dt = Cathmhaol.Calendar.getLocalizedDate($element.val());
      // update the values in the styled block if we have a valid date
      if (dt.date && dt.dayName && dt.monthName && dt.year) {
        $styled.find('.date-number').text(dt.date);
        $styled.find('.date-name').text((dt.dayName || '').substr(0, 3));
        $styled.find('.date-month').text(dt.monthName);
        $styled.find('.date-year').text(dt.year);
      }

      // transition the visibility
      $input.removeClass('opened');
      $styled.removeClass('closed');
  }
});

You'll also notice that I'm using the Calendar library from cathmhaol.com – you can pick that up from either js.cathmhaol.com or from the github repo – but minify it before using it in a production environment. You should also feel free to modify the section that updates the stylized date using a different library, or native JavaScript you've written.

The final step is to add code into your window load event handler that removes the 'closed' class from the stylized date and the 'opened' class from the input – classes that we had included for those instances when JavaScript either is not available or has crashed due to some error – something like the following code.

$('.date .input.opened').removeClass('opened');
$('.date .styled.closed').removeClass('closed');

Once that's done, you should have a fully functional stylized date.

Happy coding!

No comments:

Post a Comment