How to create specialized collectors for semantic layout in rails?

This is how I would like to write markup in the index.html.erb file

<%= page_for "Super Cool Page" do |p| %> <%= p.header do %> Ruby is Cool <% end %> <%= p.body do %> Witty discourse on Ruby. <% end %> <% if page.has_sidebar? %> <%= p.sidebar do %> <ul><li>Option 1</li></ul> <% end %> <% end %> <% end %> 

To bring

 <div class="page"> <header><h1>Super Cool Page</h1></header> <section> Witty discourse on Ruby. </section> </div> 

and when page.has_sidebar? truly

 <div class="page"> <header><h1>Super Cool Page</h1></header> <asside><ul><li>Option 1</li></ul></asside> <section> Witty discourse on Ruby. </section> </div> 

I looked at the FormHelper class in rails for guidance, but it seems to me that I will have to duplicate a lot of the work that I am trying to avoid. I'm just trying to figure out where to hang classes / modules / methods within a framework, and some kind of object |p| it should be.

My first inclination was to create a PageBuilder class that implements the header , body and sidebar . But I was stuck on the rendering pipeline so that everything worked out right away.

Is there a stone that already provides this type of semantic generation? If not, I would like to understand how to do it.

+4
source share
6 answers

I use something similar in my templates. Here is a modification. This should work in Rails 3.

application_helper.rb:

 module ApplicationHelper class PageBuilder def initialize(title, template) @title, @template = title, template @header, @body, @sidebar = nil, nil, nil @options = { :page => {} , :header => {}, :sidebar => {}, :body => {}, :title => {} } @logger = Rails.logger end def parse(&block) if block_given? if @template.respond_to?(:is_haml?) && @template.is_haml? contents = @template.capture_haml(&block) else #erb contents = @template.capture(&block) end else contents = "" end contents end def page (options,&block) options[:class] ||= "page" @options[:page] = options parse(&block) content = "" content += @template.content_tag(:title, @options[:title]) { @title } unless @title.nil? content += @template.content_tag(:header,@options[:header]) do @template.content_tag( :h1) { @header } end unless @header.nil? content += @template.content_tag(:asside, @options[:sidebar]) { @sidebar } unless @sidebar.nil? content += @template.content_tag(:section, @options[:section]) { @body } unless @body.nil? return @template.content_tag(:div, @options[:page]) { content.html_safe } end def header(options={},&block) @options[:header] = options @header = parse(&block) nil end def sidebar(options={},&block) @options[:sidebar] = options @sidebar = parse(&block) nil end def body(options={},&block) @options[:body] = options @body = parse(&block) nil end end def page_for(title, options = {}, &block ) raise ArgumentError, "Missing block" unless block_given? builder = PageBuilder.new(title, view_context ) return builder.page(options) do block.call(builder) end end end 

Now, in your code example, when is page.has_sidebar? == false page.has_sidebar? == false , you will get

 <div class="page"><title>Super Cool Page</title><header><h1> Ruby is Cool </h1></header><section> Witty discourse on Ruby. </section></div> 

and when page.has_sidebar? == true page.has_sidebar? == true , you will get

 <div class="page"><title>Super Cool Page</title><header><h1> Ruby is Cool </h1></header><asside> <ul><li>Option 1</li></ul> </asside><section> Witty discourse on Ruby. </section></div> 

You can change the order of things in the page method to get the desired layout as output.

+1
source

Not what you are asking for, but have you tried to look at Haml?

It has a much more concise syntax, so the example you propose can be written as:

 .page %header %h1 Super Cool Page %asside %ul %li Option 1 %section Witty Discourse on Ruby 

As you can see, the structure in Haml is provided with an indent that helps with reading the Haml source.

If, on the other hand, you are trying to do this as an exercise in learning how to create a template parser yourself, then maybe look at the source of Haml or one of the other template mechanisms.

+1
source

What you do can be achieved with content_for .

Haml - supercool, Slim maybe even better?

0
source

I use the helper to handle args, which then calls various partial elements for rendering.

0
source

Take a look at a gem called builder . It can provide a framework in which you can build your example above.

Here is a crude attempt to implement the above example:

 require 'builder' page = Page.new # you'll have to implement this class builder = Builder::XmlMarkup.new(:target=>STDOUT, :indent=>2) builder.div(:class => 'page') do |div| if page.has_header? div.header do |header| header.h1("Super Cool Page") end end div.body("whitty discourse on Ruby") if page.has_sidebar? div.sidebar do |side| side.ul do |ul| ul.li("Option 1") end end end end 

which outputs:

 <div class="page"> <header> <h1>Super Cool Page</h1> </header> <body>whitty discourse on Ruby</body> <sidebar> <ul> <li>Option 1</li> </ul> </sidebar> </div> 
0
source

For this you can write a tiny DSL (Domain Specific Language). Actually, this fits IMO well. The code I wrote is not used exactly as you showed in your question.

Using

 #Example 1: def some_boolean_method? true end output = String.new PageDsl.generate(output) do #or, use STDOUT to output to console page_for do "Super Cool Page" header do "Ruby is Cool" end body do "Witty discourse on Ruby." end if some_boolean_method? sidebar do "<ul><li>Option 1</li></ul>" end end end end p output # => "<div class='page'><header>Ruby is Cool</header><body>Witty discourse on Ruby.</body><section><ul><li>Option 1</li></ul></section></div>" #Example 2: PageDsl.generate(STDOUT) do some_cool_tag do "Super Cool Page" gak! do { :class=> "ff" } "Ruby is Cool" end end end # => <some_cool_tag><gak!>Ruby is Cool</gak!></some_cool_tag> 

Implementation

 class PageDsl def initialize(output) @output = output end def content(text) @output << text.to_s nil end def translate_semantic_tag(tagname,attributes={}) newline = "" # "\r\n" uncomment to add newlines case tagname.to_sym when :page_for tagname = "div" attributes[:class] = "page" when :header tagname = "header" when :body tagname = "section" when :sidebar tagname = "asside" end @output << "<#{tagname}" attributes.each { |attr,value| @output << " #{attr}='#{value}'" } if block_given? @output << ">" << newline content = yield if content @output << content.to_s << newline end @output << "</#{tagname}>" << newline else @output << "/>" << newline end nil end alias method_missing translate_semantic_tag def self.generate(output, &block) PageDsl.new(output).instance_eval(&block) end end 

Note that the implementation does not perform nesting, for example <header><h1>..</h1></header> , but this should be easy to implement.

0
source

Source: https://habr.com/ru/post/1336631/


All Articles