Website (Dutch)

Responsive design for DIY-developers with Susy

These days Twitter Bootstrap seems to be the new hype for front-end work. It’s so popular that it makes you forget there are other frameworks out there. And while there surely is a reason for bootstrap’s popularity, it might not be the best fit for your project. Sometimes you want something more generic, elegant and flexible.

You want to write CSS yourself. You want control over every line of code. You want Susy.

What is Susy?

Susy is a set of Sass and Compass extensions to make responsive design easy for you. It is packaged as a ruby gem, so inclusion in your project means adding a line to your Gemfile!

So it’s another bootstrap? No, it gives you the power to roll your own bootstrap quickly. And that might be a blesing or a curse, depending on your skill, your project and time constraints.

The basics

I assume you already use compass and you are familiar using Sass (or LESS). Susy uses grids to define your layout. And a grid has

  • Amount of columns. Each column has:
    • Width (in px, em, %, …)
    • Margin (called gutters in Susy)
  • Padding

susy grids visualized

Enough talk for now, let’s start coding! We start by creating a simple html file containing the grid.

 1<html>
 2<head>
 3    <link href="stylesheets/style0.css" media="screen, projection" rel="stylesheet" type="text/css" />
 4</head>
 5<body>
 6<div id="container">
 7        <div id="main">
 8                This is main content.
 9        </div>
10        <div id="side">
11                This is side content.
12        </div>
13</div>
14</body>
15</html>

With the following css:

 1#container {
 2  padding: 5px;
 3  background-color: black; }
 4
 5#main {
 6  padding: 5px;
 7  background-color: #F99; }
 8
 9#side {
10  padding: 5px;
11  background-color: #99F; }

What we want to achieve

When viewed on a desktop computer:

  • The container must be 750px wide, centered on the page
  • Main content (2/3 of page) is on the left hand side in the container
  • Side content (1/3 of page) is on the right hand side in the container

When viewed on a tablet or phone:

  • The container is 300px, also centered.
  • Main content is visible
  • Side content is hidden

Using Compass and Sass

Since Susy uses Sass, we need to make sure we get a working Sass environment.

  1. We need to intall Compass
  2. Initialise a new Compass project
  3. Create a .scss file in the sass folder
  4. compile the .scss files with compass
# install compass
$ gem install compass

# initialise the project
$ cd path/to/project
$ compass init

# create the scss file
$ touch sass/style.css.scss

# compile
$ compass compile

Now that Compass and Sass are ready, we are ready to create our first SCSS files.

The mobile layout

We’ll use a mobile-first approach (as documented by susy) and we start off by setting variables to initialise the Susy-grid:

$total-columns: 6;
$column-width: 40px;
$gutter-width: 10px;
$grid-padding: 5px;

// 6 * 40px + 5 * 10px + 2 * 5px = 300px
// $total-columns * $column-width + ($total-columns - 1) * gutter-width + 2 * $grid-padding = container width

#container {
	padding: 5px;
	background-color: black;
}

#main {
	padding: 5px;
	background-color: #F99;
}

#side {
	padding: 5px;
	background-color: #99F;
}

Because we’ve done nothing more than setting some variables, we can’t expect any change in output. We don’t use these variables yet…

  1. Add Susy to your config.rb in compass. If you like an example, here is my config.rb.

  2. Instruct Sass to import the Susy library. We do this by using an @import command. Example: @import “susy”

  3. Use functions in the susy library do define our columns and containers. We do this using @include.

    • @include container; sets the current element as the page container
    • @include span-columns 2; tells susy to make the element span 2 columns.

Now we’ll add the definitions for our grid. Like this:

$total-columns: 6;
$column-width: 40px;
$gutter-width: 10px;
$grid-padding: 5px;

// 6 * 40px + 5 * 10px + 2 * 5px = 300px
// $total-columns * $column-width + ($total-columns - 1) * gutter-width + 2 * $grid-padding = container width

@import "susy";

$break-desktop: 15;

#container {
	padding-top: 5px;
	padding-bottom: 5px;
	background-color: black;
	@include container;
}

#main {
	background-color: #F99;
	@include span-columns($total-columns);
}

#side {
	padding: 5px;
	background-color: #99F;
	display: none;
}

And you end up with this result. Exactly the mobile layout we had in our mind when designing the website.

The desktop layout

Now we’ll add the responsive mix-ins to make an alternate desktop layout.

  1. add a variable named $desktop-break, which has an amount of columns.
  2. update our container definition to allow for multiple containers
  3. use an at-breakpoint to update our CSS if the user can support the resolution.

And now we end up with this file:

$total-columns: 6;
$column-width: 40px;
$gutter-width: 10px;
$grid-padding: 5px;

// 6 * 40px + 5 * 10px + 2 * 5px = 300px
// $total-columns * $column-width + ($total-columns - 1) * gutter-width + 2 * $grid-padding = container width

@import "susy";

$break-desktop: 15;

#container {
	padding-top: 5px;
	padding-bottom: 5px;
	background-color: black;
	@include container($total-columns, $break-desktop);
}

#main {
	background-color: #F99;
	@include span-columns($total-columns);
}

#side {
	background-color: #99F;
	display: none;
}

@include at-breakpoint($break-desktop) {
	#main {
		@include span-columns($break-desktop - 5, $break-desktop);
	}

	#side {
		@include span-columns(5 omega, $break-desktop);
		display: block;
	}
}

Using these mix-ins we have now created a solution which uses the desktop layout if possible, and otherwise switches to the mobile layout. If you only see the desktop version, try resizing your browser window. You are now ready to start your first responsive design.

Good luck!

Set up Unicorn with Ruby On Rails and Nginx

Unicorn Logo Remember the upstream block from last post’s Nginx config file? That’s what we’ll be configuring today. For your convenience I’ll include the snippet of the config here:

upstream app_server {
    server unix:/tmp/yoursite.sock fail_timeout=0;
  }

For the Ruby webserver, I prefer Unicorn, but it doesn’t really matter. You could use any other and you still get a working web application. That being said, this post assumes your using unicorn.

Make a config file, called unicorn.rb (Yes, it’s ruby code) and put it in the config/ directory of your rails project. Now paste the following snippet into the config.

 1#Unicorn minimal configuration 
 2worker_processes 1
 3preload_app true
 4timeout 30
 5
 6#Use your rails application name here
 7listen '/tmp/yoursite.sock', :backlog => 1024 #  2007
 8
 9pid "tmp/pids/unicorn.pid"
10
11#set up the log paths
12stderr_path "log/unicorn.stderr.log"
13stdout_path "log/unicorn.stdout.log"
14
15after_fork do |server, worker|
16  ActiveRecord::Base.establish_connection
17end

The most important thing is that the server line in your nginx-config matches the listen line in unicorn.rb.

Because we use unicorn, our rails project needs the unicorn gem in it’s Gemfile. If it’s not already there, you should add it. Now all that’s left for you to do is to start the ruby webserver. This is done by these commands:

$ cd /path/to/your/rails/app_root
$ bundle exec unicorn_rails -c config/unicorn.rb  -E production -D

This command executes “unicorn_rails -c config/unicorn.rb -E production -D” in the correct environment. Bundle exec makes sure the correct versions of the gems are loaded and installed.

The unicorn_rails command starts the webserver. It’s options are

  • -c CONFIG: specifies the location of the unicorn config file
  • -E ENVIRONMENT: tells rails to run in development, test or production. By default this is development.
  • -D: Daemonize the process. This is Linux-speak for keep on running in the background.

Recursive FTP script in Mac OS X, Part Two

My previous FTP-script is a very usefull tool for updating new websites. But what if you just want to mirror a remote ftp to a local folder? You can’t. So I made some modifications to it.

On google I found various solutions pointing to ncFTP but I don’t like installing custom software on my Macbook. Only appstore or open source please.

What I want

A script which puts or gets all the files from local folder to a remote directory. Something I can call like this:

$ r-ftp p /folder/ ftp://user:password@remote.com/pub/

With two possible values for the first commandline option:

  • p: PUT, move files from local directory to remote
  • g: GET, move files from remote to local directory

The code

And a little bit later r-ftp (for Section R - Ftp) is born:

 1#!/usr/bin/env ruby
 2require 'net/ftp'
 3require 'fileutils'
 4
 5# documentation
 6def usage
 7  puts "r-ftp COMMAND LOCAL REMOTE"
 8  puts "  COMMAND"
 9  puts "    g: GET copy from remote to local"
10  puts "    p: PUT copy from local to remote"
11  puts "  LOCAL  local directory"
12  puts '  REMOTE ftp-server in format "ftp://username:password@host/directory"'
13  puts ''
14  puts 'EXAMPLE'
15  puts '  Get all files from a ftp server with a folder'
16  puts '  r-ftp g /home/user/dump/ ftp://example.org/pub/'
17  exit
18end
19
20# put recursively all files from path to the ftp server
21def put(path, ftp)
22  Dir.entries(path).each do |file|
23    next if ['.', '..'].include? file
24    
25    file_path = path + '/' + file
26    if File.directory?(file_path)
27      ftp.mkdir(file) unless ftp.nlst.index(file)
28      ftp.chdir(file)
29      put(file_path, ftp)
30      ftp.chdir('..')
31    else
32      puts "Put: " + file_path
33      ftp.putbinaryfile(path + '/' + file)
34    end
35  end
36end
37
38# get recursively all files from path to the ftp server
39def get(path, ftp)
40  initial_path ||= path
41  ftp.nlst.each do |file|
42    next if ['.', '..'].include? file
43    
44    file_path = path + '/' + file
45    if ftp_directory?(ftp, file)
46      FileUtils.mkdir(file_path) unless File.directory?(file_path)
47      ftp.chdir(file)
48      get(file_path, ftp)
49      ftp.chdir('..')
50    else
51      puts "Get: " + ftp.pwd + "/" + file
52      ftp.getbinaryfile(file, file_path)
53    end
54  end
55end
56
57#helper function to check if a file on a ftp-server is a directory
58def ftp_directory?(ftp, file_name)
59  ftp.chdir(file_name)
60  ftp.chdir('..')
61  true
62rescue
63  false
64end
65
66# returns a hash with the correct data from server string
67def parse_ftp_format server
68  matchdata = /ftp:\/\/((?<username>[[:alnum:]]*)[:,@])?((?<password>[[:alnum:]]*)@)?(?<server>[\w\d\.]+\.[[:alnum:]]{2,})(?<target>\/.*)?/.match(server)
69  return nil unless matchdata
70  return Hash[ matchdata.names.zip matchdata.captures ]
71end
72
73usage if not (ARGV[0] and ['g', 'p'].include? ARGV[0] and ARGV[1] and File.directory?(ARGV[1]) and ARGV[2] and (ftp_server = parse_ftp_format ARGV[2]))
74
75puts ftp_server.to_s
76
77#initialise ftp connection
78ftp = Net::FTP.new(ftp_server['server']);
79ftp.login(ftp_server['username'], ftp_server['password'])
80ftp.chdir(ftp_server['target'])
81
82# is it get or put?
83if ARGV[0] == 'g'
84  get ARGV[1], ftp
85else
86  put ARGV[1], ftp
87end
88
89ftp.quit
90

If you have downloaded this script you can install it with these commands:

$ chmod +x /path/to/r-ftp.rb
$ sudo mv /path/to/r-ftp.rb /urs/local/bin/r-ftp