HAML Pass partial dynamically to layout in Sinatra app - ruby

I am trying to build a Sinatra app with HAML and use a layout to be able to break my site down into partials:
layout.haml
!!!
%html
%head
= partial :head
%body
= partial :header
= partial :#{#template}
= partial :footer
Where my Sinatra app is calling layout.haml like so:
get '/test' do
#template = "\"test/index\""
haml :"layout"
end
To try to pull in:
views/
|----test/
|----_index.haml
Which gives me the error:
wrong number of arguments (0 for 1..2)
I have also tried redefining a number of different combinations with no success:
#template = ":\"test/index\""
= partial #{#template}
#template = "test/index"
= partial :#{#template}
Note: I am using Sinatra Partials
Am I going about this entirely the wrong way? My brain is really suffering trying to figure out how to employ HAML for such a basic concept of DRY templating.

Ah it was so simple afterall.. With the help of this question, I realized now of course that everything is wrapped with layout.haml and I simply needed to place the appopriate yield statement.
layout.haml
!!!
%html
%head
= partial :head
%body
= partial :header
= yield
= partial :footer
And call the template as usual:
get '/test' do
haml :"test/index"
end

Related

Ruby ERB - Create a content_for method

I'm currently working on an ERB View class for a gem. With this class I would like to have some helper methods for ERB templates.
It's okay about basic helpers like h(string). I found erbh gem who help me to understand more how context works.
But now I'm trying to create a content_for method like there is in Rails or Sinatra.
On first time I was using simple Proc to capture the view block and then just calling call method to print it. It was working enough at the beginning.
But after having completed views I saw wired thinks, some content are printed multiple times.
So I take a look on the Sinatra ContentFor helper to understand how they did it and I copy some methods of this helper. I have no errors, but the block return are always empty... and I don't really know why.
My knowledge about ERB are not good enough to know how ERB buffering works.
Code
Here a gist who explain the status of my code. I extracted the code from my library and simplified it a bit.
https://gist.github.com/nicolas-brousse/ac7f5454a1a45bae30c52dae826d587f/66cf76c97c35a02fc6bf4a3bc13d8d3b587356de
What I would like?
I just would like to have content_for methods works like they do with Rails and Sinatra.
Thanks!
After reading this blog article I finally found why it wasn't working. I don't know if I did it in the best way and cleaner way but it works.
So the bug was mainly from the ERB initilization. By using a property instead a local variable as eoutvar it now works.
erb = ERB.new(str, nil, "<>", "#_erbout")
I also change a bit the capture method who is used by content_for helper.
It looks like this now (gist)
def content_for(key, content = nil, &block)
block ||= proc { |*| content }
content_blocks[key.to_sym] << capture_later(&block)
end
def content_for?(key)
content_blocks[key.to_sym].any?
end
def yield_content(key, default = nil)
return default if content_blocks[key.to_sym].empty?
content_blocks[key.to_sym].map { |b| capture(&b) }.join
end
def capture(&block)
#capture = nil
#_erbout, _buf_was = '', #_erbout
result = yield
#_erbout = _buf_was
result.strip.empty? && #capture ? #capture : result
end
def capture_later(&block)
proc { |*| #capture = capture(&block) }
end

Sinatra method to set layout

I want to write a method in sinatra to set the layout, something like
def admin_layout
set :layout, 'admin/layout'
end
I know I can do things like set :erb, layout: :'main/layout' or specify the layout for each action like
get 'admin/login' do
erb :'admin/login', layout: :'admin/layout'
end
but I'm wondering if there is a way to just abstract that into a method so I don't need to set the layout for each route. I am making an app with a main site and then an admin site, but the admin part is very lightweight, just logging in and being able to edit posts so I am not trying to get too crazy but my current file structure is like this:
db/
models/
public/
views/
admin/
main/
app.rb
config.ru
Define a layout argument:
def admin_layout
{:layout, 'admin/layout'}
end
Then you can use this method as a param like
get 'admin/login' do
## do ......
erb :'admin/login', admin_layout
end
Or if you want to judge which layout it will use, change the admin_layout function like:
def admin_layout
if request.path.start_with?('/admin')
{:layout, 'admin/layout'}
else
{:layout, 'layout'}
end
end
I know I can do things like: set :erb, layout: :'main/layout'
No you can't do that--or at least erb will ignore it because it's not an erb specific option. If you could do that, wouldn't that do what you are asking for here:
but I'm wondering if there is a way to just abstract that into a
method so I don't need to set the layout for each route.
The method already exists--template():
#Templates:
application_layout = <<END_OF_HAML
%html
%head
%title My App
%body
=yield
END_OF_HAML
template :layout do #Creates a template named :layout
application_layout
end
template :page1 do
'%div.greet Hello World!' #=> <div class="greet">Hello World!</div>
end
template :page2 do
'%div#first_name John' #=> <div id="first_name">John</div>
end
#Routes: -------------------
get '/page1' do
haml :page1 #If there is a template named :layout, then the :page1 template will be inserted into the :layout template automatically.
end
get '/page2' do
haml :page2 #If there is a template named :layout, then the :page1 template will be inserted into the :layout template automatically.
end
Then you can override the default layout, like this:
admin_layout = <<END_OF_HAML
%html
%head
%title Admin Only
%body
=yield
END_OF_HAML
template :special_layout do #Creates a template named :special_layout
admin_layout
end
get '/page3' do
haml :special_layout, :layout => false do #Don't use default layout, i.e the :layout template, instead use :special_layout template
haml :page3
end
end
See "Named Templates" here: http://www.sinatrarb.com/intro.html#Named%20Templates

How can I use views and layouts with Ruby and ERB (not Rails)?

How can I use views and layouts with Ruby and ERB (not Rails)?
Today i'm using this code to render my view:
def render(template_path, context = self)
template = File.read(template_path)
ERB.new(template).result(context.get_binding)
end
This works very well, but how can I implement the same function, but to render the template inside a layout? I want to call render_with_layout(template_path, context = self), and so that it will have a default layout.
Since you tagged it with Sinatra I assume that you us Sinatra.
By default you view is rendered in your default layout called layout.erb
get "/" do
erb :index
end
This renders your view index with the default layout.
If you need multiple layouts you can specify them.
get "/foo" do
erb :index, :layout => :nameofyourlayoutfile
end
* If you don't use Sinatra you may want to borrow the code from there.
If you use the Tilt gem (which I think is what Sinatra uses) you could do something like
template_layout = Tilt::ERBTemplate.new(layout)
template_layout.render {
Tilt::ERBTemplate.new(template).render(context.get_binding)
}
If you are using Sinatra so it has a good docimentation and one of the topics it's nested layouts (see Sinatra README)
Also good idea to use special default layout file (layout.haml or layout.erb in your view directory) This file will be always use to render others. This is example for layout.haml:
!!!5
%html
%head
##<LOADING CSS AND JS, TILE, DESC., KEYWORDS>
%body
=yield ## THE OTHER LAYOUTS WILL BE DISPALYED HERE
%footer
# FOOTER CONTENT
Thanks for all the answers!
I solved it finally by doing this, I hope someone else also can find this code useful:
def render_with_layout(template_path, context = self)
template = File.read(template_path)
render_layout do
ERB.new(template).result(context.get_binding)
end
end
def render_layout
layout = File.read('views/layouts/app.html.erb')
ERB.new(layout).result(binding)
end
And I call it like this:
def index
#books = Book.all
body = render_with_layout('views/books/index.html.erb')
[200, {}, [body]]
end
Then it will render my view, with the hardcoded (so far) layout..

Rails helper for Haml

I have a bit of HAML code that gets repeated in something like 10 views and would like to put it into a helper of some kind. Doing a search here produced some interesting results but ultimately no answers, so:
In application_helper.rb I have this:
def pagination_helper(object)
haml_tag :div, :class => 'apple_pagination page_info' do
page_entries_info #book_formats
paginate #book_formats
end
end
In the view template I have this:
- pagination_helper(#book_formats)
If I try calling it with = to output something I get an error.
The above will not give me an error but it won't call the methods either. I get empty divs.
Ultimately the code I want to repeat is this:
.apple_pagination.page_info
= page_entries_info #book_formats
= paginate #book_formats
The code would be the same except the object would change and I send that from the view template. For example #book_formats would change to #dvds, etc.
The block passed to haml_tag doesn’t automatically get added to the output. You need to use haml_concat:
def pagination_helper(object)
haml_tag :div, :class => 'apple_pagination page_info' do
haml_concat(page_entries_info #book_formats)
haml_concat(paginate #book_formats)
end
end

View specific stylesheets in Sinatra (by naming convention)

I would like to have my Sinatra app include a view specific stylesheet in the layout.
Consider this simple app:
app_folder_root/
| my_app/
| my_app.rb
| public/
| css/
| index.css
| layout.css
| views/
| index.haml
| layout.haml
config.ru
config.ru:
require 'rubygems'
require './my_app/my_app'
map '/' do
run MyApp
end
app.rb:
require 'sinatra/base'
class MyApp < Sinatra::Base
get '/' do
haml :index
end
end
I tried setting a variable in my_app.rb that sets the name of the view and tried to reference it in layout.haml, but that did not work (I probably wouldn't have went with this as a final solution to the problem since I felt this was a code smell, but was just trying different possibilities).
This is using Haml, but I am hoping that is irrelevant - thinking it should be the same for erb, etc.
In layout.haml, I would like to reference the view that will be rendered and include a view specific stylesheet by a naming convention. For example, if index.haml is going to render, I would like to include css/index.css. What is the best way to go about doing this?
I solved this by doing the following:
In index.haml (at the top) I created or appended an array named views:
- #views = Array.new unless defined? #views
- #views << 'index'
In layout.haml I reference #views:
%head
- #views.each do |view|
- haml_tag :link, {:rel => 'stylesheet', :type => 'text/css', :href => "css/#{view}.css"}
I am a little disappointed with having to check #views in the view to make sure it is defined before appending to it, but for now it is manageable.
EDIT: Solved the problem with having to check if #views is defined in each view. In config.ru add the following:
before do
#views = Array.new
end
I can now remove this line from the views:
- #views = Array.new unless defined? #views

Resources