Logo

Have you ever written the same template or helper twice? Do you have trouble sleeping at night because of code duplication? Then Fifty may right for you. It allows you to save keystrokes by leveraging:

  • HAML as a markup language for the static part of your templates.
  • Handlebars to render dynamic fields on the server or in the browser.
  • Javascript to write helpers that run both client- and server-side.

Is it ready?

No. The proof of concept is there for you to see, but I need to work on this some more before I release it as a gem.

What does it do?

Hopefully this is self-explanatory:

1. Start with some model data

$data = {
  posts: [
    { title: 'Hello, world!',
      text: 'This is fu-man-chu!',
      likes: 2,
      comments: [
        { user: 'Louis',
          text: 'Cool!' },
        { user: 'Justin',
          text: 'What do you think?'  }
    ] },
    { title: 'This is a post!',
      text: 'With lots of love.',
      likes: 1,
      comments: [
        { user: 'Chris',
          text: 'Nice!' }
    ] }
  ]
}

2. Include Fifty in our app.

require 'sinatra'
require 'fifty'

class MyApp < Sinatra::Base

  helpers Fifty

  # Share a copy of the data to the client.
  # Made available as `window.client`
  shared_data :client_data, $data.to_json

  get '/posts?.format?' do |format|
    if format == 'json'
      $data.to_json
    elsif format == 'html'
      fifty :posts, $data
    end
  end

end

MyApp.run!

3. Define your views

@@ layout

%html
  %head
    %script{src: fifty_cdn(:handlebars)}
    %script{src: fifty_cdn(:jquery)}

    = fifty_partials
    = fifty_scripts

  %body
    #posts
      = yield

  :javascript

    // Reload from route /posts.json
    $('#reload').click(function () {
      $('#posts').getAndReplace('posts');
    });

    // Submit form and add post to feed.
    $('#form').submit(function (event) {
      event.preventDefault();
      var data = $(this).serialize;
      $('#posts').postAndAppend('post', data);
    });

    // Delete post and remove from feed.
    $('.delete').click(function (e) {
      $(this).postAndRemove('post/delete', {
        id: $(this).closest('.post')
      });
    });

@@ posts

.container
  .row
    {{#each posts}}
    {{> post}}
    {{/each}}

  %button#reload Reload

@@ post

.post{id: "{{id}}"}
  %h3 {{title}}
  %p {{text}}
  %p
    {{likes}}
    {{t "like"}}
  %hr
  %p
    {{#each comments}}
    {{> comment}}
    {{/each}}

  %a.delete{href: '#'} Delete

@@ comment

.comment{id: "{{id}}"}
  %b {{user}}:
  %span {{text}}

@@ form

%form{method: 'post'}
  Name:
  %input#name{type: 'text'}
  Message:
  %textarea#message

Configuration Options

You can put these anywhere before you run your routes. For a Sinatra app, the canonical path is config/fifty.rb.

Paths to Views and Locales

Set the path(s) to your views (shown is the default):

Fifty.views = ['./views/**/*.haml']

If using locales with I18n, set the path to your files:

Fifty.locales = ['./config/locales/*.yml']

Shared Helpers and JSON data

# Share a copy of the data to the client.
#
# Made available as `window.client`

js_data :client_data, $data.to_json

# Define a shared helper function.
#
# Can be invoked within a HBS
# template in Ruby or Javascript.

hbs_helper :add, %t{
  function (a, b) { return a + b; }
}