GeekTool

Inspired by Brett Terpstra’s series of posts about GeekTool I’ve been using the iheartquotes API to display quotes on my desktop for quite some time now. So long, as a matter of fact, that I was starting to get bored by seeing the same quotes all the time. In comes this movie-trivia page I made a long time ago. It’s basically the first rails app I ever wrote, so really nothing special, but since I’ve been posting those little titbits almost daily for over two years now, they amassed to quite a nice count. The ideal target for my Geeklet.

I pretty much modelled the API for my page after the iheartquotes API, but since I’m currently playing with Status Board I also added an API call for status board.

We’re starting by adding the needed routes and controller actions:

MTotD::Application.routes.draw do
  get 'api/random',       to: 'trivia#random', defaults: { :format => 'text' }
  get 'api/status_board', to: 'trivia#status_board'
end
class TriviaController < ApplicationController
  def random
    @trivia = Trivia.published.order("random()")

    if params[:min_characters]
      @trivia = @trivia.where("length(body) > ?", params[:min_characters])
    end
    if params[:max_characters]
      @trivia = @trivia.where("? > length(body)", params[:max_characters])
    end
    if params[:min_lines]
      @trivia = @trivia.select { |trivium| word_wrap(trivium.body, :line_width => 70).lines.count + 1 >= params[:min_lines].to_i }
    end
    if params[:max_lines]
      @trivia = @trivia.select { |trivium| word_wrap(trivium.body, :line_width => 70).lines.count + 1 <= params[:max_lines].to_i }
    end

    @trivia = @trivia.first

    respond_to do |format|
      format.text
      format.html { render layout: false }
    end
  end

  def status_board
    render layout: false
  end
end

With the random action we are giving the user a few options to filter the trivia and are then loading a random one of the remaining. For the min_lines and max_lines filter we’re calling word_wrap on our text, which inserts a line break every 70 characters.1 This is to ensure that every line has about the same length. The action defaults to text format because it’s more useful for GeekTool, but can also be called as HTML, which we’ll use for the Status Board API call. Furthermore we have to make sure that our two HTML views won’t render the application layout, if we want them to look shiny in Status Board.

Now we create our random.text.erb view:

<% if  @trivia.present? %>
  <%= raw word_wrap(@trivia.body.gsub(/\r\n\r\n/, "\r\n").gsub(/[*,_]/, ''), :line_width => 70) %>
  — <%= raw @trivia.film.title %>
<% end  %>

First we’re making sure we’re not calling the attributes of a nil object,2 then we just word_wrap the trivia text as mentioned above and add a line with the film title. I’m also removing empty lines and some Markdown markup.

The random.html.erb view looks pretty similar:

<% if  @trivia.present? %>
  <div id="trivia">
    <%= markdown(@trivia.body + " <span>— " + @trivia.film.title + "</span>") %>
  </div>
<% end  %>

The only difference here is, that I don’t word_wrap the trivia, but instead call my markdown helper method to parse it to HTML.

All that’s left is the status_board.html.erb view. Since Status Board won’t refresh our side automatically, we’ll have to handle this ourselves:

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="Cache-control" content="no-cache" />
  <title>Trivia</title>
  <style type="text/css">
  ...
  </style>
  <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
  <script type="text/javascript">
    function refresh() {
      var req = new XMLHttpRequest();

      req.onreadystatechange=function() {
        if (req.readyState==4 && req.status==200) {
          document.getElementById('trivia-container').innerHTML = req.responseText;
        }
      }
      req.open("GET", 'http://movietriviaoftheday.com/api/random.html?max_lines=6', true);
      req.send(null);
    }
    function init() { 
      refresh()
        var int=self.setInterval(function(){refresh()},600000);
    }
  </script>
</head>
<body onload="init()">
  <div id="main">
    <div id="trivia-container"></div>
  </div>
</body>
</html>

So rather than loading a view with trivia in it, we load an empty page, replace the #trivia-container with the contents of our random.html.erb view via JavaScript and refresh them every 10 minutes.3

And that’s our simple API.

If you want to try a Geeklet with random trivia from my page just create a new shell Geeklet and use the following one-line command:

$ curl -s 'http://movietriviaoftheday.com/api/random?max_lines=6'

Adjust the font and colour to your liking and the result could look like this:

Trivia Geeklet

On you Status Board you just need to create a new Do-It-Yourself panel and point it at:

http://movietriviaoftheday.com/api/status_board

You can also check out the API Documentation.


  1. Wraps the text into lines no longer than line_width width. This method breaks on the first whitespace character that does not exceed line_width (which is 80 by default). ↩︎

  2. Since @trivia could become nil duo to the filter options. ↩︎

  3. Or 600000 milliseconds. ↩︎