Using layouts in HAML files independently of Rails - ruby

My end goal is to create several static HTML files for hand-off to other folks.
But for my workflow, I'd like to have HAML as the basic source files. In doing so, I'd hope to DRY up the process at least on my side.
Now I have a lot of pages that will ultimately be sharing a common layout, and I'm wondering how to incorporate the layouts.
Here's my current code:
./compile.rb
#!/usr/bin/env ruby
require 'rubygems'
require 'rake'
require 'haml'
FileList.new('./src/*.html.haml').each do |filename|
if filename =~ /([^\/]+)\.haml$/
File.open($1, 'w') do |f|
f.write Haml::Engine.new(File.read(filename)).render
end
end
end
./src/layout.html.haml
!!!
%html
%head
%title Yay
%body
= yield
./src/home.html.haml
= render :layout => 'header' do
%p This is awesome
Now this clearly doesn't work because the render method is undefined out of the context of Rails, but I hope it gets the point across what I'm trying to do.
Any suggestions?

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.

content = Haml::Engine.new(content_haml).render(
Object.new,
:local_var_1 => ...,
:local_var_2 => ...
)
Haml::Engine.new(layout_haml).render(Object.new, :content => content)
layout.haml
!!!
%html
%head
%title
%body
= content
You can also use instance variables of Object.new (replace with meaningful object) in haml I believe.

Related

How to set humanized name of padrino module?

How are you.
I have just installed padrino framework admin panel.
And it shows several tabs in admin module.
I want to rename the labels. but how to do it?
This is view side code
<%= link_to project_module.human_name, url(project_module.path) %>
Here I can't set human_name of project_module.
And this is module definition in application.rb
access_control.roles_for :admin do |role|
role.project_module :accounts, '/accounts'
role.project_module :venues, '/venues'
role.project_module :shows, '/shows'
end
Now tabs labels are Accounts, Venues, Shows.
how to set them as Users, MyVenues, MyShows?
Thanks
Just seemed to hit this issue tonight as well. It seems that this is a slight bug that is in the core Padrino framework.
The standard navigation logic out of the box will never produce a proper localized text. It renders the output of function .humanize
https://github.com/padrino/padrino-framework/blob/8bd3796d45eae5e3f7dc52316c8c25c44563f8cd/padrino-admin/lib/padrino-admin/access_control.rb#L176
At best, the humanize function will upcase your text. See https://apidock.com/rails/String/humanize
You can replace the human_name reference with the I18n.t() localizable function:
%ul.nav.navbar-nav.pull-left
- project_modules.each do |project_module|
%li{:class => "navbar-module #{('active' if request.path_info =~ /^#{project_module.path}/)}"}
=link_to I18n.t(project_module.name), url(project_module.path)
See http://padrinorb.com/guides/features/localization/ for reference.
One note, the navigation is not model aware, so your translated text will need to be right under the locale reference in the yml file.
Ex.
en:
accounts: Users
venues: MyVenue
shows: MyShows
This should do the trick. Let me know if you have any questions.

Sinatra - Is a sitewide param possible?

I am building a Sinatra app with haml templates and was wondering if it is possible to implement a sitewide param of some sort. The idea, in my case, would be to allow for a different layout.haml to be able to be selected for every route without having to duplicate/rewrite every route in the app.
For example, I was wondering if it would be possible to be able to GET http://domain.com/route/:normal-params/?layout=layout_b, and be able to append ?template=template_choice to any route in the app and use the appropriate layout.
The only solution I can think of, which seems very inefficient, is to duplicate every single route to look for this parameter. I also feel like it could be achieved somehow with a Filter but am unsure how such a thing could be implemented.
You can specify which layout you want to load in your call to haml:
haml :post, :layout => params[:layout].to_sym
That way you can call http://domain.com/route/foo/bar?layout=layout_b and Sinatra will look for the appropriate layout named layout_b to render in.
You'll probably want to specify a default layout to render if none is provided as a URL parameter:
haml :post, :layout => (params[:layout] || "default").to_sym

How can I resize external images and serve them on-the-fly?

I have a sinatra app that gets image urls from an API and I want to scale them and then serve them without storing them on the server. Most of the gems I have seen only get local images and then processes each one in a queue. I only need to scale five images and display them on the page. Is there a fast way to do this?
More Clarification:
I need a way to get an image externally (e.g. notmysite.com/img.jpg) and for the code to serve the scaled image on the page. I cant do it with css or other front-end methods because this page is going to be rendered by a script that distorts images scaled front-end.
Dragonfly uses imagemagick to scale the images. Here's some code I've cobbled together from previous stuff I've done with MiniMagick, so it'll be fairly similar.
Get yourself the file into a Tempfile. I've done this here with Faraday and Typheous. Then scale it using magick!
require 'faraday'
require 'faraday_middleware'
#require 'faraday/adapter/typhoeus' # see https://github.com/typhoeus/typhoeus/issues/226#issuecomment-9919517 if you get a problem with the requiring
require 'typhoeus/adapters/faraday'
configure do
Faraday.default_connection = Faraday::Connection.new(
:headers => { :accept => 'image/*',
:user_agent => "Sinatra via Faraday"}
) do |conn|
conn.use Faraday::Adapter::Typhoeus
end
end
helpers do
def grab_image_and_scale
response = Faraday.get url # you'll need to supply this variable somehow, your choice
filename = "SOMETHING.jpg"
tempfile = Tempfile.open(filename, 'wb') { |fp| fp.write(response.body) }
thumb = MiniMagick::Image.open( tempfile.path )
thumb.thumbnail( "75x75" )
thumb.write( File.join settings.public, "images", "thumb_#{filename}")
scaled = MiniMagick::Image.open( secure_path )
scaled.resize( "600" )
scaled.write( File.join settings.public, "images", "scaled_#{filename}")
end
end
I'll leave it to you to work out how to change the path to the public images folder into a tempfile (and it'd be nice if you shared how it's done:)
One way, that is not related to Ruby or sinatra, is to add width and height attributes to img HTML tag. You'll end up with something like this:
<img src="img.source.from.API.JPG" width="2000px" height="100px" />
Edit
Another method is to change the dimension using javascript, as suggested here: https://stackoverflow.com/a/11333825/693597. You might consider writing your JS file and included in the header of your HTML.

return to default layout behavior using a Proc

I'm adding unobtrusive ajax to my site, so I need to set the layout depending on the request type :
# app/controllers/application_controller.rb
layout Proc.new { |controller| controller.request.xhr? ? 'ajax' : 'application' }
# app/views/layout/ajax.html.erb
<%= render :partial => "shared/flash", :object => flash %>
<%= yield %>
But I also want to use nested layout and have my javascript deal with where to insert ajax retrieved content. According to the rails api layout nil force default layout behavior with inheritance, so I tried this :
layout Proc.new { |controller| controller.request.xhr? ? 'ajax' : nil }
Which doesn't work, I get no layout at all. The only piece of information I found is an old chat log saying that this, indeed, doesn't work due to the way Rails handle layout.
Is there a way to achieve this ?
Since a proc returning nil doesn't work, is their a way to set the layout on a condition beeing met (request.xhr? here). Something like layout_if.
Or should I just add
layout Proc.new { |controller| controller.request.xhr? ? 'ajax' : 'controller_name' }
to every controllers with a different layout ?
My ajax request actually return html, should I cheat a little and return those html elements in .xml files and use respond_to to set the layout ?
(This may be great, for another reason, having differents urls to access with and without layout content could allow for page cache, but there are other way to achieve this.)
Or maybe there is a way better solution.
Rails can do this automatically. For example you create 2 layouts(application.html.erb and application.js.erb). When request is xhr? it will use js(application.js.erb) layout, if standard request comes to the server rails will render application.html.erb.

How can I generate a navigation in Middleman?

I'm just getting used to Middleman and Ruby in general. What's the best way to generate a navigation with active states?
In the current version of MM (2.x, though 3.0 is close), you can do it with the following additions to config.rb and some tweaks in your nav file(s). Here is a working version in case I leave out some critical bits:
First create a helper function:
helpers do
def nav_active(page)
#page_id == page ? {:class => "Active"} : {}
end
end
Then, in the nav bar include file (in this case its a haml file) you can use the nav_active helper as follows:
#HeaderNavigationBar
%ul
%li{nav_active("index")}= link_to t('top_navigation.home'), t('paths.index')
%li{nav_active("pricing")}= link_to t('top_navigation.pricing'), t('paths.pricing')
%li{nav_active("faq")}= link_to t('top_navigation.faq'), t('paths.faq')
The net result of this is to add the class "Active" to the link in the nav bar when the page is building built for this page. I.e. if the page is a file called "index" then the #page_id will be "index" and that link will have the Active theme.
To complete the example, here is the excerpt from the scss style partial that defines active:
&.Active {
font-weight: bold;
}
In a later version of the header file, we actually removed the link when on the active page. It looks something like - which could clearly be tidied up, but FWIW :D:
%li{nav_active("index")}
-if "index" == #page_id
= t('top_navigation.home')
-else
= link_to t('top_navigation.home'), root()
... (etc)
Note that all the t('stuff') has to do with translation functions for i18n. You can ignore that. I didn't want to make the example syntactically wrong by trying to remove them.
Hope this helps - also see the forum at http://forum.middlemanapp.com/.
Here’s a new gem for marking up a current link in Middleman with aria-current (which you can then use to style off of): https://github.com/thoughtbot/middleman-aria_current

Resources