Using Radiant Layouts to Style Extension Controllers

jmlagace edited this page Nov 27, 2012 · 2 revisions
Clone this wiki locally

Introduction

Often when developing a custom site with Radiant, there are tasks that we want our site visitors to be able to accomplish, but that would be inappropriate or cumbersome to do in a static page structure. For these tasks, we obviously would want to use a Rails controller and models to accomplish the task. However, we also want to take advantage of the beautiful work our designers have done for the static pages, without duplicating code. This is the motivation behind the share_layouts extension.

Overview

Here’s the basic steps we’ll take to integrate our custom controller with a Radiant layout.

  1. Create an extension and controller and wire up the routes.
  2. Create a Layout in the Radiant Admin UI for our controller’s output.
  3. Specify the layout we want to use in our controller using the radiant_layout declaration.
  4. Create a view for the controller that renders blocks to be inserted into the Radiant layout.
  5. View the action from our web browser.

Step-by-Step Tutorial

1. Create a extension and controller

Create a new Radiant project and bootstrap with the ‘Styled Blog’ template. We’ll make use of the existing layout in that template. Now install the share_layouts extension:

  $ git clone http://github.com/radiant/radiant-share-layouts-extension.git vendor/extensions/layouts

Edit your config/environment.rb to make sure share_layouts loads first:

  config.extensions = [:layouts, :all]

Now let’s generate an extension called ‘stats’.

$ script/generate extension stats
      create  vendor/extensions/stats/app/controllers
      create  vendor/extensions/stats/app/helpers
      create  vendor/extensions/stats/app/models
      create  vendor/extensions/stats/app/views
      create  vendor/extensions/stats/db/migrate
      create  vendor/extensions/stats/lib/tasks
      create  vendor/extensions/stats/README
      create  vendor/extensions/stats/stats_extension.rb
      create  vendor/extensions/stats/lib/tasks/stats_extension_tasks.rake
      create  vendor/extensions/stats/spec/controllers
      create  vendor/extensions/stats/spec/models
      create  vendor/extensions/stats/spec/views
      create  vendor/extensions/stats/spec/helpers
      create  vendor/extensions/stats/Rakefile
      create  vendor/extensions/stats/spec/spec_helper.rb
      create  vendor/extensions/stats/spec/spec.opts

Next we’ll generate a controller for the extension.

$ script/generate extension_controller stats stats
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/stats
      exists  spec/controllers/
      exists  spec/helpers/
      create  spec/views/stats
      create  spec/controllers/stats_controller_spec.rb
      create  spec/helpers/stats_helper_spec.rb
      create  app/controllers/stats_controller.rb
      create  app/helpers/stats_helper.rb

Our last task in this section is to wire up some routes for our controller. In the example, we plan to have only one action, so we’ll put just one route inside our extension file (stats_extension.rb):

  define_routes do |map|
    map.stats 'stats', :controller => 'stats', :action => 'index'
  end

2. Create or pick a layout to use with the controller

Since we already have a layout that we want to use (the default layout that comes with the Styled Blog template), let’s look at what parts are used in the layout. If you haven’t already, fire up script/server and look at the “Normal” layout. Here’s the source:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
  <html>
    <head>
      <title><r:title /></title>
      <link href="/rss/" rel="alternate" title="RSS" type="application/rss+xml" />
      <link rel="stylesheet" type="text/css" href="/styles.css" />
    </head>
    <body>
      <div id="page">
        <r:snippet name="header" />
        <div id="main">
          <div id="content-wrapper">
            <div id="content">
              <r:unless_url matches="^/$"><h1><r:title /></h1></r:unless_url>
              <r:content />
              <r:if_content part="extended">
              <div id="extended">
                <r:content part="extended" />
              </div>
              </r:if_content>
              <r:if_url matches="^/articles/\d{4}/\d{2}/\d{2}/.+"><r:unless_url matches="-archives/$"><p class="info">Posted by <r:author /> on <r:date /></p></r:unless_url></r:if_url>
            </div>
          </div>
          <div id="sidebar-wrapper">
            <div id="sidebar">
              <r:content part="sidebar" inherit="true" />
            </div>
          </div>
        </div>
        <r:snippet name="footer" />
      </div>
    </body>
  </html>

The main thing to look for are <r:content /> tags. There are three:

  1. <r:content /> in the “content” div.
  2. <r:content part="extended" />, nested in the “content” div, but only displayed if it exists.
  3. <r:content part="sidebar" inherit="true" /> in the “sidebar” div.

We’ll make use of these when we create our view template later.

3. Specify the desired layout in the controller

Open up vendor/extensions/stats/app/controllers/stats_controller.rb and let’s specify our layout and create our action. While we’re at it, we’ll want to make our controller actually do something, so let’s have it gather statistics about our Radiant installation.

class StatsController < ApplicationController
  radiant_layout 'Normal'
  no_login_required

  def index
    @page_count = Page.count
    @snippet_count = Snippet.count
  end
end

The main thing to notice is the first line, radiant_layout 'Normal'. This tells share_layouts to render our controller actions inside the “Normal” layout that we investigated earlier. The second line, no_login_required makes sure that our visitors without accounts can see the controller’s output. In our index action we’ve simply collected some counts of the number of pages and snippets in the database. We’ll output these in the view.

4. Create a view

Now that we’ve got the guts hooked up, let’s render something! We’ll want to make sure our view takes advantage of the content blocks that the layout asks for. The way we do that is to capture blocks of content using the content_for helper that comes with Rails. Here’s the Haml view template app/views/stats/index.html.haml (Haml is included with 0.6.7 and later):

%h1 Statistics

  %dl
    %dt Pages
    %dd= @page_count
    %dt Snippets
    %dd= @snippet_count

  - content_for :sidebar do
    %p 
      This page displays statistics about our Radiant
      installation.

Note that we used content_for :sidebar to capture the paragraph to be inserted into the sidebar block. Everything in the template that is not inside a content_for block will be treated as the “body” block.

5. View our results

Now fire up your web browser and look at the action (Go to /stats). Here’s what mine looks like:

Discussion and Advanced Topics

Setting the title and breadcrumbs

You have three options for setting the title and breadcrumbs for the layout.

1. Set an instance variable

The first and easiest option is to set the @title or @breadcrumbs instance variable in your controller. These will automatically be added to the page that encapsulates your controller’s view. Example:

def index
  @title = "My cool controller"
end

2. Capture some content

You can use content_for with the parameter :title or :breadcrumbs and a content block to capture what should go in the title or breadcrumbs area. Example:

<% content_for :breadcrumbs do %>
  <%= link_to "Home", '/' %> > My Page
<% end %>

3. Use an “endpoint” page

The endpoint page will be used for the title and breadcrumbs attributes if you don’t specify them manually. See below for more info about endpoints.

Choosing a layout dynamically

Similarly to the title and breadcrumb attributes, the layout can be chosen dynamically in the controller.

1. Set an instance variable

You can directly set the layout in a controller action or filter via the @radiant_layout instance variable:

@radiant_layout = "No sidebar"

2. Use a block

You can alternatively pass a block to the radiant_layout directive, which will yield the controller instance. The return value of the block should be a string containing the name of a layout.

radiant_layout do |controller|
  case controller.action_name
  when "index"
    "Normal"
  else
    "No sidebar"
  end
end

Creating “endpoints”

TODO

Generating Radius Code

TODO

How it works

TODO