Tuesday, July 03, 2012

My journey in Python/Django - Hello World Part 5: Bootstrap and Compass for CSS

In earlier posts (Part 1 herePart 2 here and Part 3 here and Part 4 here) I have set about building a "Hello World" Python/Django application.
So far we have a basic application up and running using a virtual environment, the Django template system and everything has been posted to Github. 
Now let's get a little more sophisticated.  In this post I want to get Compass and SASS working with Twitter's Bootstrap UI tool. Bootstrap  is a combination of HTML, CSS and Javascript to create an easy to use User Interface for web projects. The initial challenge is that Bootstrap was designed using the LESS pre-processor and we want to use the SASS/Compass combination. Fortunately there have already been conversions performed that have ported Bootstrap from LESS to SASS.
Twitter uses Ruby and we are using Django. Consequently Bootstrap makes use of Ruby and the GEM install process. Fortunately, with Compass and SASS pre-compiling code we don't need to add the complication of running a combined Ruby on Rails and Django/Python server configuration. We can contain the Ruby requirements to our local development machines and just upload the compiled code.
Let's get started. In Part 2 we had installed Compass and setup the directory structures.
The Javascript, CSS and Image collateral will be stored in the mainstatic folder structure:
mainstatic
 img
 js
sass
 stylesheets
To implement bootstrap I am using the compass-twitter-bootstrap code from Vincent Waller. Step 1 is to install using gem:

sudo gem install compass_twitter_bootstrap

I then went and added:

require 'compass_twitter_bootstrap'
to the config.rb file in mainstatic folder. This is used by compass to control the compilation and other processes.
I then added: 
@import "compass_twitter_bootstrap";
@import "compass_twitter_bootstrap_responsive";
to the screen.scss file in mainstatic/sass.
I then ran the following command:
compass frameworks
The result shows that bootstrap is installed:
Available Frameworks & Patterns:
  * blueprint
    - blueprint/basic      - A basic blueprint install that mimics the actual blueprint css.
    - blueprint/buttons    - Button Plugin
    - blueprint/link_icons - Icons for common types of links
    - blueprint/project    - The blueprint framework.
    - blueprint/semantic   - The blueprint framework for use with semantic markup.
  * compass
    - compass/ellipsis     - Plugin for cross-browser ellipsis truncated text.
    - compass/extension    - Generate a compass extension.
    - compass/pie          - Integration with http://css3pie.com/
    - compass/project      - The default project layout.
  * twitter_bootstrap
If we want to use all the stylistic goodness of SASS and Bootstrap the first thing we need to do is to make sure that the stylesheets and javascript files are being loaded by the application. To do this we need to edit the base template - base.html. We need to add the necessary instructions to the <head></head> section of the template.  
    <link rel="stylesheet" href="{{ STATIC_URL }}stylesheets/ie.css">
    <link rel="stylesheet" href="{{ STATIC_URL }}stylesheets/screen.css" media="screen, projection" type="text/css">
    <link rel="stylesheet" href="{{ STATIC_URL }}stylesheets/print.css" media="print" />
The template Tag {{STATIC_URL}} is translated from the value in settings.py thanks to the following entries in the TEMPLATE_CONTEXT_PROCESSORS section of settings.py:
    "django.core.context_processors.static",
    "django.core.context_processors.request",
Setting up Compass to watch for stylesheet changes
Now I go to the shell and open up another window.
I navigate to the mainstatic folder and kick off compass to watch for changes to the SCSS/JS/IMG folders. If it detects a change the code gets recompiled automatically.
compass watch
Let's create a NavBar that uses the bootstrap styling:
We do this by updating include/top-nav.html:
<div class="navbar navbar-fixed-top">
    <div class="navbar-inner">
        <div class="container">
            <ul class="nav">
                <li class="active">
                    Hello World
                </li>
                <li>About</li>
                <li>Portfolio</li>
                <li>Contact</li>
            </ul>
        </div>
    </div>
</div>
A quick reload of the page and localhost:8000 now has a flashy black NavBar.
Welcome_to_my_world-2
We have Bootstrap working!
The next step is to prove that we can edit the Bootstrap configuration. We want to do this without directly hacking the Compass_Twitter_Bootstrap setup. 
The first step is to create a partials folder inside the sass folder.
mkdir partials
Rather than edit the original files I take a copy of a couple files from the gem setup and put them in the partials folder. In my installation the gem files were found in:
/Library/Ruby/Gems/1.8/Gems/compass_twitter_bootstrap-2.0.3/stylesheets/compass_twitter_bootstrap/
The files were:
_variables.scss
_navbar.scss
For clarity I rename these files to use a _bootstrao prefix and add them to the import settings in screen.scss:
/*
  revise bootstrap design using snippets in partials
*/
@import "partials/_bootstrap-variables.scss";
@import "partials/_bootstrap-navbar.scss";
To prove this is working I want to go in and make some edits to the navbar setup.
I make a couple of changes to the navbar settings to apply $orange as the background color. Compass is set to watch for changes to the scss files and it recompiles the stylesheets. A quick reload and we have a modified navbar:
Welcome_to_my_world-4
I also did some further editing. I created a partials/_base-page.scss file.
In this file I created some basic CSS styling that can be used in conjunction with the base.hmtl template.
The main new element is the footer. Here I have applied some dark grey background color with white text.
 The base.html file looks like this:
<!doctype html>
<html class="no-js" lang="en">
<head>
    <title>{% block pretitle %}{% endblock %}{% block title %}Hello World! {% endblock %}{% block posttitle %}{% endblock %}</title>
    <link rel="stylesheet" href="{{ STATIC_URL }}stylesheets/ie.css">
    <link rel="stylesheet" href="{{ STATIC_URL }}stylesheets/screen.css" media="screen, projection" type="text/css">
    <link rel="stylesheet" href="{{ STATIC_URL }}stylesheets/print.css" media="print" />
    <meta charset="utf-8">
    {% block head %}
    {% endblock %}
    {% block extra_head %}
    {% endblock %}
</head>
<body class="{% block active_nav_tab %}{% endblock %}" {% block body_load_trigger %}{% endblock %}>
    {% include "include/top-nav.html" %}
    <div class="container-fluid">
        <div class="content">
            <div class="wrapper">
                <div class="proper-content">
                    <div class="row">
                        <div class="span12">
                            {% include "include/messages.html" %}
                            {%block featureBox %}
                            {% endblock %}
                            {%block extra_body %}
                            {% endblock %}
                        </div>
                    </div>
                </div><!-- /.proper-content -->
                <div class="push"></div>
            </div><!-- /.wrapper -->
            {% include "include/footer.html" %}
        </div><!-- /.content -->
    </div><!-- /.container-fluid -->
</body>
</html>
The _base-page.scss file looks like this:
/*
base_page: Objective is a sticky header and footer
based on:


html, body, .container, .content {
    height: 100%;
}
.container, .content {
    position: relative;
}
.proper-content {
    padding-top: 40px; /* >= navbar height */
}
.wrapper {
    min-height: 100%;
    height: auto !important;
    height: 100%;
    margin: 0 auto -50px; /* same as the footer */
}
.push {
    height: 50px; /* same as the footer */
}
.footer-wrapper {
    position: relative;
    height: 50px;
}
*/
$footerHeight: 60px;
html, body, .container, .content {
  height:100%;
}
.container, .content {
  position: relative;
  padding-left: 0px;
}
.proper-content {
  padding-top: $footerHeight; /* >= navbar height */
}
.wrapper  {
  //overflow:auto;
  min-height: 100%;
  height: auto !important;
  height: 100%;
  padding-left: 0;
  border-left: 0;
  margin: 0 auto ($footerHeight * -1); /* same as the footer but a negative value*/
}
.push {
  height: $footerHeight; /* same as the footer */
}
.footer-wrapper {
  border-left: 0px solid $grayDarker;
  position: relative;
  height: $footerHeight;
  width: 100%;
  margin-left: 0;
  padding-left: 0;
  color: $white;
  background-color: $grayDark;
}
One of the neat features of SASS is the ability to do math in your styling. I found that to apply some of the footer styling I needed to specify the height of the footer in pixels in a number of places. Rather than having to edit the value in multiple places I created a variable that I placed at the top of the _base-page.scss file. I define it like this:
$footerHeight: 60px;
In the wrapper section I needed to refer to the margin as equal to the negative value of footer height. I didn't need to create a new variable or set a static value. Instead I used arithmetic:
margin: 0 auto ($footerHeight * -1); /* same as the footer but a negative value*/
That is an amazingly powerful and useful feature! I am no expert when it comes to CSS but I can see the power of SASS in creating stylish websites using easily maintainable code.
Let's commit these changes to git
First I update .gitignore to exclude the mainstatic/.sass-cache folder
Here is the sequence of git commands I have learned to use to keep things in sync:
git add mainstatic/partials 
I use JetBrains PyCharm IDE so it prompts me to add new files and folders to git as they are created. You may have to add these manually using git add.
git status
I use this to do a quick check of what has changed locally and see if I need to add or exclude any files.
git checkout master
git pull
git checkout -b mark
I use my name but you can use any reference you like.
At this point you would now make the changes you need to make to the code. After that we move on to the commit phase:
git commit -a
At this point you need to enter a commit message that describes the changes you have implemented.
git checkout master
git pull
git checkout mark
git rebase master
run any tests you want at this point eg. Unittests.
git checkout master
git merge mark
git push
If I am going to start another round of changes I start the git update sequence as follows:
git branch -d mark
Delete the branch
git checkout master
git pull
git checkout -b mark
Pull the latest version of the code and checkout my working branch - mark.
Recap
Let's recap where we are. 
  • We have created a folder structure for stylesheets and javascript. 
  • We have compass watching for stylesheet changes and recompiling the code
  • We have edited our template file to reference the new stylesheets
  • We have integrated Twitter's Bootstrap in to the SASS/Compass configuration 
  • We have applied some overrides to the Bootstrap design using snippets of code in our partials folder
  • We have used SASS variables and even accomplished arithmetic on the variables.
  • Checked the code in to Git