Behind the New Big Calendar Front Page Style on TPM PollTracker

Design, Rails, TPM, code

New Big Calendar List Style

Below: Massimo Vignelli’s Perpetual Calendar

Today I deployed a new look to the front page, monthly archive pages and individual poll pages on TPM PollTracker. The new look gives more focus to dates, so you can quickly scan all the polls that ended on a certain day. The design itself was inspired by Massimo Vignelli’s Perpetual Calendar— one of my favorite pieces of design ever.

Vignelli Perpetual Calendar

With the thought that the day of the month needed to be big, a couple problems presented themselves. This is a case where Cascading Style Sheets and their float model needed to coexist with tables, but sticking a “big cal” day in a cell would necessarily stretch out the whole data row to the height of the date, even if there is more than one poll on a given day. By the same token, what to do about days with only one poll— wouldn’t the dates run into each other if they were smushed to the height of the data row?

Before even addressing this question, I needed a way to find the latest poll of a given day, and show the date in the left column of just that poll. This was tougher than it looks. Here’s the first_dates method I came up with:

class Poll < ActiveRecord::Base
    def self.first_dates(polls)

        first_polls_of_days = []

        p = polls
        p = p.collect {|q| [, q.end_date]}          

        uniq_dates = p.collect {|s| s[1]}.uniq

        (0..((uniq_dates.size) - 1)).each do |date|
            first_polls_of_days <<{|a| a if a[1] == uniq_dates[date]}[0][0]

        return first_polls_of_days

This method takes an array of polls as its argument (which are the polls delivered by will_paginate as all the polls to show on a given page). It collects each of these polls and filters out everything but the id and the end_date. Then it creates another array of unique dates in the given set of polls. Finally, it matches up the first poll it finds for a given date and returns and array of “first date” poll ids.

Then in my view, there is a simple condition:

<% if @first_dates.include?( %>
    <!-- show the "big cal" -->
<% end %>

So, that covers the cool date look for each set of polls, but what about the second problem, where the first poll row would be too high as a result of the “big cal” date? I solved that with a little bit of CSS and Ruby trickery. First, the CSS issue: the reason the row was stretching out was because the computed height of the row was dependent on the big size of the date. If I tricked the browser into believing the <h2> that contained the “big cal” number was actually 0 height, it would defer to the computed height of the data row:

.big_date h2.multi {
      height:0 !important;

But then, if there was a row with only one poll on a given day, the date would run into the row below it. That row should not have the multi class. In that case, we don’t care that the day takes up its rightful height, and even prefer that the row is a little larger to accommodate the “big cal” date.

Right: Problems: not enough space, and too much space for “big cal” dates. Luckily polls_per_day came to the rescue.

Big Cal Style Problems

To solve this, I wrote another model method:

class Poll < ActiveRecord::Base
    def self.polls_per_day(polls,end_date)
        p = polls
        p = p.collect {|q| q.end_date}
        polls_on_date = {|a| a == end_date}

        return polls_on_date.size

The polls_per_day method takes the same poll array and a specific end_date as its arguments, and then just returns how many polls there are on a certain day.

NB: I’m making use of the same polls array over and over rather than leaning on Poll.find() to avoid spendy database queries. As a result, these methods are not that expensive at all.

In my view, I just check to see if polls_per_day is greater than 1. If so, I’ll add the multi class:

<h2 <% if Poll.polls_per_day(@polls,poll.end_date) > 1 %>class="multi"<% end %>>

It’s a really cool effect, and makes the PollTracker front page that much shinier.