Using Layouts in Haml Files Independently of Rails

Using layouts in HAML files independently of Rails

You're mixing up two distinct Rails feature: partials (using render) and layouts (using yield).

You can add a rails-like version of either (or both) of them to a Haml only program.

Partials

In a rails view, you can use render :partial_name to cause the file _partial_name.html.haml to be rendered at that point in the containing view (actually Rails allows you to use any templating language supported and it will find to correct filename extension to use, but I'll stick to just Haml here). Outside of Rails render isn't available, but it can be added fairly easily.

A simple render method would just find the appropriate haml file, render it, and return the html string for inclusion in the parent:

def render(partial)
# assuming we want to keep the rails practice of prefixing file names
# of partials with "_"
Haml::Engine.new(File.read("_#{partial}.html.haml")).render
end

The first argument to Haml::Engine.render is a scope object, which we can use to add methods available inside the haml template. It defaults to Object.new. In a simple case like this, however, we can define the render method in the top level, and it will be available in the scope of the Haml template. We simply put our render method in the script before the call to Haml::Engine.new(...).render, and call it like this in our template:

!!!
%html
%head
%title Hello
%body
=render :the_partial

Now the file _the_partial.html.haml will appear rendered at the appropriate point of the output.

Local variables

We can take this a step further. Rails allows you to pass in a hash of local variables to a partial. Haml will also accept a hash of variables to be passed as local variables, as the second argument to the Haml render method. So if we expand our render method to look like:

def render(partial, locals = {})
Haml::Engine.new(File.read("_#{partial}.html.haml")).render(Object.new, locals)
end

we can use a partial that looks something like:

%p You passed in #{foo}

and call it from our template with:

%body
=render :partial, :foo => "bar"

which will render

<body>
<p>You passed in bar</p>
</body>

Layouts

In Rails, you can specify a layout for your views, so that all your pages can share the same
header, menu area etc. This is done by specifying a layout file, within which you call yield to render the actual view in question. Layouts are slightly more tricky to add to haml, but can still be done.

Hamls render method also accepts a block, so a simple solution would be to render the layout file, and pass a block that renders the view file:

Haml::Engine.new(File.read("layout.html.haml")).render do
Haml::Engine.new(File.read("view.html.haml")).render
end

This would give the contents of layout.html.haml rendered with the contents of view.html.haml rendered where the layout file contained =yield.

content_for

Rails is a bit more flexible than that though. It allows you to call yield multiple times in your layout file, naming a specific region in each case, and to specify the contents to be added at each region using the content_for method within your views. So in your layout file:

!!!
%html
%head
= yield :title
%body
=yield

and in your view:

-content_for :title do
%title Hello
%p
Here's a paragraph.

The way Rails actually works is to render the view part first, storing all the different sections, and then rendering the layout, passing a block that provides the appropriate chunk whenever yield is called in the layout. We can replicate this using a little helper class to provide the content_for method and keep track of the rendered chunks for each region:

class Regions  
def initialize
@regions_hash={}
end

def content_for(region, &blk)
@regions_hash[region] = capture_haml(&blk)
end

def [](region)
@regions_hash[region]
end
end

Here we're using the capture_haml method to get the rendered haml without it going direct to the output. Note that this doesn't capture the unnamed part of the view.

We can now use our helper class to render the final output.

regions = Regions.new
unnamed = Haml::Engine.new(File.read("view_named.html.haml")).render(regions)

output = Haml::Engine.new(File.read("layout_named.html.haml")).render do |region|
region ? regions[region] : unnamed
end

Now the variable output contains the final rendered output.

Note that the code here doesn't provide all the flexibility that's included with rails, but hopefully it's enough to show you where to start customising Haml to meet your needs.

How to structure a layout template in Haml

This solution is for Ruby on Rails only:

You can use yield(:location) and the content_for(:location) methods. "Using the content_for Method" has more information.

layout.haml:

!!!
%html
%head
%title= yield(:title)
= yield(:head)
%body
= yield

view.haml:

- content_for(:title, 'My title')
- content_for(:head) do
= javascript_include_tag :foo

%h1 My view!

Specifying a layout and a template in a standalone (not rails) ruby app, using slim or haml

The layout.slim file looks like:

h1 Hello
.content
== yield

The contents.slim file looks like:

= name

This can be shortened, but I separated to individual steps for explanation purposes.

require 'slim'

# Simple class to represent an environment
class Env
attr_accessor :name
end

# Intialize it
env = Env.new
# Set the variable we reference in contents.slim
env.name = "test this layout"

# Read the layout file in as a string
layout = File.open("layout.slim", "rb").read

# Read the contents file in as a string
contents = File.open("contents.slim", "rb").read

# Create new template object with the layout
l = Slim::Template.new { layout }

# Render the contents passing in the environment: env
# so that it can resolve: = name
c = Slim::Template.new { contents }.render(env)

# Render the layout passing it the rendered contents
# as the block. This is what yield in layout.slim will get
puts l.render{ c }

This will output:

<h1>Hello</h1><div class="content">test this layout</div>

HAML close tags only in parent file

You need to use your header file as a layout:

# _header.html.haml
%section.page
.wrapper
= yield


#index.html.haml

= render layout: "_header" do
%section.noborder{ id: "fp-1" }
%h2{id: "c1"}= @question_group.title

Converting all .haml files in a dir to .html in another dir

There is a nice html2haml gem for this purpose

and you can run it on a directory using

find . -name \*.erb -print | sed 'p;s/.erb$/.haml/' | xargs -n2 html2haml

Use HAML (for rendering partials) in a non-ruby environment

This is not a Guard issue. The render method is defined by Rails, that's why you get the error.

That said you can find how to define it in https://stackoverflow.com/a/6131411/304055 (the question you already linked).

haml using variable in rails layout for meta keywords

You don't say why your approach doesn't work, so I'm going to guess it's a syntax error in your haml (missing closing bracket). Also, I'm not sure what default keywords is or why you're using the ||= operator - either of those may also be causing your solution to "not work".

Something like the following will work as expected:

# application.html.haml
%meta{ name: "keywords", |
content: content_for?(:keywords) ? yield(:keywords) : "default" }
# this could also be achieved using content_for?(:keywords) || "default"

# client.html.haml
- provide :keywords do
this should work


Related Topics



Leave a reply



Submit