Your friends at Viget present Inspire, a Design & Interaction Blog

Building an ExpressionEngine Mini Calendar Scroller

I recently decided I wanted to add a calendar of blog entries on my personal site. Luckily, ExpressionEngine has a tag for that, the Calendar Tag. The functionality that I wanted was a little bit different from the two examples in the EE user guide. I wanted to show a calendar by month, and link the days that had an entry to that specific entry.

The EE Template

So, if we start from the “mini-calendar” code that EE gives, strip out some of the crap, and create a blank template called calendar in the blog template group, we have this:

{preload_replace:channel="blog"}
  
{exp
:channel:calendar channel="{channel}"
show_future_entries="no" display_by="day"}
  
<div class="content">
    <
h3>{date format="%F %Y"}</h3>
    <
ul class="nav-calendar">
      <
li class="prev">
        <
a href="{previous_path='{channel}/calendar'}">
          &
larr;
        </
a>
      </
li>
      <
li class="next">
        <
a href="{next_path='{channel}/calendar'}">
          &
rarr;
        </
a>
      </
li>
    </
ul>
    
    <
table border="0" cellpadding="0" cellspacing="0">
      <
thead>
        <
tr>
          
{calendar_heading}
            
<th>{lang:weekday_abrev}</th>
          
{/calendar_heading}
        
</tr>
      </
thead>
      
{calendar_rows }
        {row_start}
<tr>{/row_start}
          {if entries}
            
<td><a href="#">{day_number}</a></td>
          
{/if}
          {if not_entries}
            
<td>{day_number}</td>
          
{/if}
          {if blank}
            
<td>&nbsp;</td>
          
{/if}
        {row_end}
</tr>{/row_end}
      {
/calendar_rows}
    
</table>
  </
div>
{/exp:channel:calendar} 

September Calendar

So that's a pretty good start. Now, we need to link the date to the actual entry. So we can take a part from the “big calendar” in the EE user guide:

{if entries}
  {entries}
    
<td>
      <
a href="{url_title_path='{channel}'}">
        
{day_number}
      
</a>
    </
td>
  
{/entries}
{
/if} 

Perfect, now the days are linked to the actual entry that was published. But, what happens if there are multiple entries on a single day? Uh oh, it duplicates the table cell:

June Calendar

We just need a little PHP to fix that. Make sure you allow PHP on output for the calendar template.

{if entries}
  <?php $count 
1?>
  {entries}
    <?php 
if ($count == 1) : ?>
      
<td>
        <
a href="{url_title_path='{channel}'}">
          
{day_number}
        
</a>
      </
td>
      
<?php $count++; ?>
    <?php 
endif; ?>
  {
/entries}
{
/if} 

So the downside is that if there are multiple entries on a single say, the day links to the first entry, but I can live with that.

I also only wanted to show the previous and next arrows when there were actually months that have passed to scroll through (since I started my blog). So we add a little PHP to solve that problem too:

<ul class="nav-calendar">
  
<?php if('{date format="%Y"}' == '2007') : ?>
    <?php 
if ('{date format="%n"}' '1') : ?>
      
<li class="prev">
        <
a href="{previous_path='{channel}/calendar'}">
          &
larr;
        </
a>
      </
li>
    
<?php endif; ?>
  <?php 
else : ?>
    
<li class="prev">
      <
a href="{previous_path='{channel}/calendar'}">
        &
larr;
      </
a>
    </
li>
  
<?php endif; ?>    
  <?php 
if('{date format="%Y"}' == date('Y')) : ?>
    <?php 
if ('{date format="%n"}' date('n')) : ?>
      
<li class="next">
        <
a href="{next_path='{channel}/calendar'}">
          &
rarr;
        </
a>
      </
li>
    
<?php endif; ?>
  <?php 
else : ?>
    
<li class="next">
      <
a href="{next_path='{channel}/calendar'}">
        &
rarr;
      </
a>
    </
li>
  
<?php endif; ?>
</ul

Here is the entire template (in gist format here):

{preload_replace:channel="blog"}
  
{exp
:channel:calendar channel="{channel}"
show_future_entries="no" display_by="day"}
  
<div class="content">
    <
h3>{date format="%F %Y"}</h3>
    <
ul class="nav-calendar">
      
<?php if('{date format="%Y"}' == '2007') : ?>
        <?php 
if ('{date format="%n"}' '1') : ?>
          
<li class="prev">
            <
a href="{previous_path='{channel}/calendar'}">
              &
larr;
            </
a>
          </
li>
        
<?php endif; ?>
      <?php 
else : ?>
        
<li class="prev">
          <
a href="{previous_path='{channel}/calendar'}">
            &
larr;
          </
a>
        </
li>
      
<?php endif; ?>    
    
      <?php 
if('{date format="%Y"}' == date('Y')) : ?>
        <?php 
if ('{date format="%n"}' date('n')) : ?>
          
<li class="next">
            <
a href="{next_path='{channel}/calendar'}">
              &
rarr;
            </
a>
          </
li>
        
<?php endif; ?>
      <?php 
else : ?>
        
<li class="next">
          <
a href="{next_path='{channel}/calendar'}">
            &
rarr;
          </
a>
        </
li>
      
<?php endif; ?>
    
</ul>
    
    <
table border="0" cellpadding="0" cellspacing="0">
      <
thead>
        <
tr>
          
{calendar_heading}
            
<th>{lang:weekday_abrev}</th>
          
{/calendar_heading}
        
</tr>
      </
thead>
      
{calendar_rows }
        {row_start}
<tr>{/row_start}
          {if entries}
            <?php $count 
1?>
            {entries}
              <?php 
if ($count == 1) : ?>
                
<td>
                  <
a href="{url_title_path='{channel}'}">
                    
{day_number}
                  
</a>
                </
td>
                
<?php $count++; ?>
              <?php 
endif; ?>
            {
/entries}
          {
/if}
          {if not_entries}
            
<td>{day_number}</td>
          
{/if}
          {if blank}
            
<td>&nbsp;</td>
          
{/if}
        {row_end}
</tr>{/row_end}
      {
/calendar_rows}
    
</table>
  </
div>
{/exp:channel:calendar} 

Then, in my blog sidebar template, I embed the calendar:

<div class="section calendar">
    <
div class="scroller-wrapper">
            
{embed="blog/calendar"}
    
</div>
</
div

A Little CSS

There really is nothing special to the CSS here. The important parts to the JavaScript effect are emphasized:

.calendar .scroller-wrapper {
	overflow: hidden;
	position: relative;
}
.calendar .content {
	position: relative;
	width: 100%;
}
.calendar h3 {
	font-size: 14px;
	line-height: 30px;
	text-align: center;
}
.calendar table {
	font-size: 11px;
	margin-bottom: 0;
	width: 100%;
}
.calendar th,
.calendar td {
	border: 1px solid rgba(0,0,0,0.1);
	text-align: center;
}
.calendar th {
	border-width: 0 0 1px;
	font-size: 13px;
}
.calendar td:first-child {
	border-left: none;
}
.calendar td:last-child {
	border-right: none;
}
.calendar tr:last-child td {
	border-bottom: none;
}
.calendar table a {
	border: none;
	display: block;
	padding: 0;
}

August Calendar

Now onto the JavaScript

Basically I am just going a GET request when a previous or next arrow is clicked, appending it, sliding the old month out of the way, and sliding the new month in place. I'm not going to go through step by step, but I can certainly answer any questions in the comments if you have any.

var TD = TD || {};

TD.calendar: function(e) {
  var $this = $(this),
      action = $this.parent().attr('class'),
      $wrapper = $this.closest('div.scroller-wrapper'),
      $cal = $this.closest('div.content');
  
  $.get($this.attr('href'), function(data) {
    var $d = $(data),
        delta = (action === 'prev') ? '-100' : '100';
    
    $d.css({
      'left' : delta + '%',
      'position' : 'absolute'
    }).appendTo($wrapper);
    
    var newHeight = $d.height();
    
    $cal.add($d).animate({
      'left' : '-='+delta+'%'
    }, 250, function() {
      $cal.fadeOut(250).remove();
    });
    
    $wrapper.animate({
      'height' : newHeight
    }, 350);
  });
  e.preventDefault();
},

$(document).ready(function() {
  $('#content div.calendar').delegate('ul.nav-calendar a', 'click', TD.calendar);
});

That is all

You can see the final version in the sidebar of my site.


Get More From Viget

Subscribe to get our monthly newsletter and occasional special announcements.