BTMash

Blob of contradictions

Using the JQuery UI Date Picker for event navigation

Fri, 04/15/2011 - 12:03 -- btmash
The jQuery UI Datepicker is a fantastic way to browse through dates.

For a site I am currently creating in Drupal 7, I have a bunch of events and I need to show a view of the content in a non-traditional calendar way (Listing of events for a week, a pager to go back and forth in the week, and a calendar block which lets the user select the date (week) they want to see events occurring on).

As I was trying to use the date browser from the date module with the new (absolutely kickass) views module, I was finding that there were issues that seemed to surround the weekly browser. But getting past the weekly browser bugs, I was finding that I couldn't do a listing of events occurring in the week (I had events set up with a longer date range when the event was being held along with individual event dates. I could solely utilize the event dates but that could have its own set of issues) and the weekly browser contexts would only show events that started or ended within that week (any events that were running longer than a week would not show up). And the calendar module had its own set of issues (some of which were shared with the date module, but the calendar block and how it interacts with the calendar page has traditionally required a fair amount of reworking at the theme layer).

So for this functionality, I decided to instead utilize the fact that JQuery UI is now included with Drupal 7 and implement a JQuery UI Datepicker block for date selection and altering the views build to filter with dates. So the tutorial below involves a few things.

  1. Creating Blocks
  2. Utilizing Jquery Libraries
  3. Some JQuery Magic
  4. Views and alterations
  5. Putting it all together

Creating Blocks

There are now multiple hooks for blocks. We define new blocks by implementing hook_block_info():

/**
* Implementation of hook_block_info().
*/
function my_calendar_style_block_info() {
  $blocks['calendar-picker'] = array(
    'info' => t('Calendar Picker'),
    'cache' => DRUPAL_NO_CACHE,
  );
 
  return $blocks;
}

A mixture: Creating Blocks and Utilizing JQuery Libraries

After that, we define our calendar block by implementing hook_block_view():

/**
* Implementation of hook_block_view().
*/
function my_calendar_style_block_view($delta = '') {
  $block = array();
  switch ($delta) {
    case 'calendar-picker':
      // Add extra libraries     
      drupal_add_library('system', 'ui.datepicker');
      drupal_add_js(drupal_get_path('module', 'my_calendar_style_customizations') .'/my_calendar_style.js');
      drupal_add_css(drupal_get_path('module', 'date_popup')  .'/themes/datepicker.1.7.css');
      drupal_add_css(drupal_get_path('module', 'my_calendar_style_customizations'). '/my_calendar_style.css');
     
      // Add the customized settings
      $calendar_uri = url('calendar', array('absolute' => TRUE));
      $date = date('Y-m-d');
      $view = views_get_page_view();
      if (!empty($view) && $view->name == 'my_calendar' && isset($view->args[0])) {
        $date = date($view->args[0]);
      }
      $date = new DateObject($date);
      drupal_add_js(array('my_calendar_style_event_base_uri' => $calendar_uri), 'setting');
      drupal_add_js(array('my_calendar_style_event_default_date' => date_format_date($date, 'custom', 'm/d/Y')), 'setting');
     
      $block['subject'] = t('Calendar');
      $block['content'] = "<div id='my-calendar-style-event-nojs'><a href="$calendar_uri">Events this week</a> will be a link. You must have javascript enabled in order to use the date picker</div><div id='my-calendar-style-datepicker'> </div>";
      break;
  }
  return $block;
}

To give a gist of what is happening above, we are adding the jquery-ui datepicker to be included if this block is being shown on the page along with the js and css to go with it. We check if we're on our view page which has date up in the arguments and use that to be the 'selected' date for our datepicker. In my case, I'm including some dummy content to show in the event that someone is viewing the page without using javascript (so they get a link to go to the calendar - this can be changed to whatever you like or you can remove it if you wish). So now let's jump into the js file that's being created

/**
* @file my_calendar_style_customizations.js
*
*/
(function ($) {
  Drupal.behaviors.my_calendar_style_customizations = {
    attach: function() {
      $('#my-calendar-style-event-nojs').hide();
      $('#my-calendar-style-datepicker').datepicker({
        defaultDate: Drupal.settings.my_calendar_style_event_default_date,
        onSelect: function(dateText, inst) {
          //Use date text to figure out week.
          var date = new Date(dateText);
          date.setDate(date.getDate() - date.getDay());
          var new_date_text = date.getFullYear() +
            '-' + my_calendar_style_customizations_pad(date.getMonth() + 1) +
            '-' + my_calendar_style_customizations_pad(date.getDate());
          var uri = Drupal.settings.my_calendar_style_event_base_uri + '/' + new_date_text;
          window.location = uri;
        }
      });
    }
  }
})(jQuery);

function my_calendar_style_customizations_pad(number) {
  return (number < 10 ? '0' : '') + number;
}

The code above hides the 'nojs' version of the block and shows the calendar. It sets what current date (or date based on where you are in the view page) and clicking on a date inside will set the date to the first day in the week (the week starts on a sunday) and sends the user to that date in the week in the calendar.

Views and alterations

With those pieces out of the way, we now take a look at what needs to change in the view. As I'd mentioned above, the date browser was only showing me events that had started and/or ended within the week. This would not work (as we had exhibitions which were a few months long/whatnot) and while I could use the specific dates, this might show some strange behavior as well. The larger date range was what we really wanted. So what I did was create 2 filters. One is to check that the date_from field is less than or equal to some date (I give it a default value of '+7 days' from current date. Another is a filter to check that the date_to field is greater than or equal to some date (I give it a default value of today). Their overlap will allow me to see events that fit the span I have specified. However, this would only give me events that are happening in the upcoming week. So our views alteration consists of grabbing the date that is specified and injecting them into the query instead.

Before we begin on altering the views, let's add some helper functions. These will help get our date ranges in place (and they are flexible enough to change so that we can have a varied intervals).

/**
* Get a week range based on the date.
* @param date a date in the format 'Y-m-d'
*/
function _my_calendar_style_customizations_get_date_week_range($date = NULL) {
  static $date_range;
  if (!isset($date_range)) {
    $date_range = array();
  }
  if (!isset($date)) {
    $date = date('Y-m-d');
  }
  if (!isset($date_range[$date])) {
    $date_object = new DateObject($date);
    $temp_date_range = array();
    $temp_date_range[] = _redcat_customizations_date_add_interval($date_object, '-'. date_format_date($date_object, 'custom', 'w') .' day');
    $temp_date_range[] = _redcat_customizations_date_add_interval($temp_date_range[0], '+ 6 day');
    $date_range[$date] = $temp_date_range;
  }
 
  return $date_range[$date];
}

/**
* Add a time interval to a given date
*/
function _my_calendar_style_customizations_date_add_interval($date, $interval) {
  $date_string = (string) $date ;
  $date_object = new DateObject($date_string . $interval);
  return $date_object;
}

Now, we will use hook_views_post_build to implement this. Note that there are many (MANY) hooks in views where this information could be added but this seems to be one of the more logical places to add it in.

/**
* Implementation of hook_views_query_alter().
*/
function my_calendar_style_customizations_views_query_alter(&$view, &$query) {
  // Remember to add any other necessary conditions
  if ($view->name == 'my_calendar') {
    // Figure out the date to resolve all this information for.
    $date = isset($view->args[0]) ? $view->args[0] : date('Y-m-d');
    $date_week_range = _my_calendar_style_customizations_get_date_week_range($date);

    foreach($query->where[0]['conditions'] as $key => $condition) {
      if (is_array($condition['value'])) {
        foreach ($condition['value'] as $field_name => $value) {
          if (strpos($field_name,':field_data_field_content_date_range_field_content_date_range_value2') === 0) {
            $query->where[0]['conditions'][$key]['value'][$field_name] = substr((string) $date_week_range[0], 0, 10);
          }
          else if (strpos($field_name,':field_data_field_content_date_range_field_content_date_range_value') === 0) {
            $query->where[0]['conditions'][$key]['value'][$field_name] = substr((string) $date_week_range[1], 0, 10);
          }
        }
      }
    }
  }
}

The database layer changes easily allow for substituting my own date range in with my new values. I just check through the various conditions (filters) that are there, see which field name matches what I am looking to change and substitute the value in the field.

What is the point

That would be the big question. After all, there is the Calendar module and Date should get its issues resolved. Well, this solution tries to utilize what is already there in Drupal (namely, the jquery date picker). It allows us to switch back and forth between months without requiring a page reload (or an ajax reload), the calendar is easily customizable, and this option provides me with with a lot more flexibility regarding how to show the information to the user (I implement hook_views_post_build and add in my own pager at the top based on the date). This solution also allows us to get by in the event that whatever the Date module does may be by design (understandably, my needs are not necessarily the needs of the many others that use the module). By understanding what the various hooks from other modules do, you can easily get around the possible limitations that are there from other modules.