Today we'll transform the famous jQuery UI datepicker into a weekpicker. This means that when we hover over the calendar cells, the whole week row needs to be highlighted; and when we select a date, the week start and date will be returned. As usual, we'll add some nice styling to our new widget.

Check out the demo above for an example; the full code is available on github.

The Widget

We'll use the jQuery UI's Widget factory to create the picker:

js
$.widget('lugolabs.weekpicker', {});

This is a skeleton widget outlined by the factory; we have used the lugolabs namespace here, but you can use any namespace you like. The Widget factory allows inheritance from an existing jQuery UI widget (or any other function for that matter), but we cannot inherit from the jquery.ui.datepicker widget as it's an Object rather than a Function.

The factory calls a _create method for creating the widget, so let's add that now to the skeleton:

js
_create: function() {
  var self = this;
  this._dateFormat = this.options.dateFormat || $.datepicker._defaults.dateFormat;
  var date = this._initialDate();
  this._setWeek(date);
  var onSelect = this.options.onSelect;
  this._picker = $(this.element).datepicker($.extend(this.options, this._weekOptions, {
    onSelect: function(dateText, inst) {
      self._select(dateText, inst, onSelect);
    },
    beforeShowDay: function(date) {
      return self._showDay(date);
    },
    onChangeMonthYear: function(year, month, inst) {
      self._selectCurrentWeek();
    }
  }));
  this._picker.datepicker('setDate', date);
}

We'll create an instance of the datepicker and keep a reference to it on the _picker variable. Even though we'll override some of the options passed to the datepicker, it's important that we take into account the options passed to our widget. E.g. the _dateFormat instance variable stores the dateFormat passed with the options, if supplied, otherwise it stores the default.

The same idea is valid for the initial date:

js
_initialDate: function() {
  if (this.options.currentText) {
    return $.datepicker.parseDate(this._dateFormat, this.options.currentText);
  } else {
    return new Date;
  }
}

Again here, we check that a currentText is supplied with the options; if it is, we use that as the initial date, otherwise we use today's date.

We'll use this date to extract the week's start and end;

js
_setWeek: function(date) {
  var year = date.getFullYear(),
    month = date.getMonth(),
    day   = date.getDate() - date.getDay();
  this._startDate = new Date(year, month, day);
  this._endDate   = new Date(year, month, day + 6);
}

The _start and _end instance variables will be used to check that a date is within a week.

js
_select: function(dateText, inst, onSelect) {
  this._setWeek(this._picker.datepicker('getDate'));
  var startDateText = $.datepicker.formatDate(this._dateFormat, this._startDate, inst.settings);
  this._picker.val(startDateText);
  if (onSelect) onSelect(dateText, startDateText, this._startDate, this._endDate, inst);
}

When the user selects a date, our widget selects the whole week (using the _setWeek function). We then update the value of input with the week start. If a onSelect option is supplied, we call that by passing it all our dates.

text
_showDay: function(date) {
  var cssClass = date >= this._startDate && date <= this._endDate ? 'ui-datepicker-current-day' : '';
  return [true, cssClass];
}

The code above adds the ui-datepicker-current-day to the all the days within the selected week. We'll use that class in our styling later.

js
_selectCurrentWeek: function() {
  $('.ui-datepicker-calendar')
    .find('.ui-datepicker-current-day a')
    .addClass('ui-state-active');
}

This methods highlights the selected week when the calendar navigates across months. For that we also need to show and select other months:

js
_weekOptions: {
  showOtherMonths:   true,
  selectOtherMonths: true
}

At the end we toggle the ui-state-hover class when hovering over the days:

js
$(document)
  .on('mousemove',  '.ui-datepicker-calendar tr', function() { $(this).find('td a').addClass('ui-state-hover'); })
  .on('mouseleave', '.ui-datepicker-calendar tr', function() { $(this).find('td a').removeClass('ui-state-hover'); });

Styles

It's important we add a visual cue to our picker, that the whole week is selected/highlighted rather than a single day. To do this we remove all the borders and paddings that come with the jQuery UI styles:

css
.ui-datepicker {
  border-radius: 0;
  padding: 0 0 1.5em;
  border: 0;
  box-shadow: 2px 2px 5px #aaa;
  width: 14.5em;
}

.ui-datepicker table {
  font-size: 13px;
}

.ui-datepicker th,
.ui-datepicker td {
  padding: 0;
  width: 10px;
}

.ui-datepicker th {
  text-transform: uppercase;
  color: #E3797D;
  font-size: .8em;
  padding-bottom: 1em;
}

.ui-datepicker .ui-datepicker-header {
  border: none;
  border-radius: 0;
  background: #4ECDC4;
  color: #fff;
  margin-bottom: 2em;
}

.ui-datepicker .ui-datepicker-title {
  line-height: 1;
  padding: 3em 0;
  font-family: 'Oswald', sans-serif;
  text-transform: uppercase;
}

.ui-datepicker .ui-datepicker-month {
  display: block;
  font-size: 1.8em;
  margin-bottom: .4em;
}

.ui-datepicker .ui-datepicker-year {
  color: rgba(255, 255, 255, 0.7);
}

.ui-datepicker .ui-datepicker-prev,
.ui-datepicker .ui-datepicker-next {
  top: 50%;
  margin-top: -16px;
  border: 0;
  cursor: pointer;
}

.ui-datepicker .ui-datepicker-prev {
  left: 10px;
}

.ui-datepicker .ui-datepicker-next {
  right: 10px;
}

.ui-datepicker .ui-datepicker-prev .ui-icon {
  background-position: -96px -32px;
}

.ui-datepicker .ui-datepicker-prev:hover .ui-icon {
  background-position: -96px -48px;
}

.ui-datepicker .ui-datepicker-next .ui-icon {
  background-position: -32px -32px;
}

.ui-datepicker .ui-datepicker-next:hover .ui-icon {
  background-position: -32px -48px;
}

.ui-datepicker .ui-state-default {
  border: none;
  background: transparent;
  padding: 0;
  height: 30px;
  line-height: 30px;
  text-align: center;
}

.ui-datepicker td.ui-datepicker-current-day .ui-state-default,
.ui-datepicker .ui-state-hover {
  background: #4ECDC4;
  color: #fff;
}

.ui-datepicker .ui-priority-secondary,
.ui-datepicker .ui-widget-content .ui-priority-secondary,
.ui-datepicker .ui-widget-header .ui-priority-secondary {
  opacity: .4;
  filter: Alpha(Opacity=40);
}

We use a the Oswald font for the weekpicker's header, so make sure you add the Google Font's link to your HTML document's header:

html
<link href='https://fonts.googleapis.com/css?family=Oswald:300' rel='stylesheet' type='text/css'>

Testing

To test the widget, we add some simple elements to a HTML page:

html
<div class="">Week starting: <b id="week-start"></b></div>
<div id="weekpicker"></div>

Then we instantiate our widget in a script block:

js
$(function() {
  var dateText = '12/20/2015',
    display = $('#week-start');
  display.text(dateText);

  $('#weekpicker').weekpicker({
    currentText: dateText,
    onSelect: function(dateText, startDateText, startDate, endDate, inst) {
      display.text(startDateText);
    }
  });
});

When we select a week, the week start is shown in our display box.

Happy coding!

Credits

Design by Freebiesbug. Original JavaScript solution by manishma.