Как я могу создать специализированные компоновщики для семантического макета в рельсах? - PullRequest
4 голосов
/ 23 января 2011

Вот как я хотел бы написать разметку в скажем index.html.erb

<%= 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 %>

Что бы вывести

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

и когда page.has_sidebar?это правда

<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>

Я посмотрел на класс FormHelper в рельсах для руководства, но мне кажется, что мне придется дублировать большую часть работы, которую я стараюсь избегать.На самом деле я просто пытаюсь выяснить, куда вешать классы / модули / методы в фреймворке и какой должен быть объект |p|.

Моим первым желанием было создать класс PageBuilder, которыйреализует header, body и sidebar методы.Но я застрял на конвейере рендеринга, чтобы все было правильно.

Существует ли драгоценный камень, который уже обеспечивает этот тип семантической генерации?Если бы не я, я бы хотел узнать, как это настроить.

Ответы [ 6 ]

1 голос
/ 08 февраля 2011

Я использую нечто подобное в моих шаблонах. Вот модификация. Это должно работать в 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

Теперь, в вашем примере кода, когда page.has_sidebar? == false, вы получите

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

и когда page.has_sidebar? == true, вы получите

<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>

Вы можете изменить порядок вещей в методе page, чтобы получить любой желаемый макет в качестве вывода.

1 голос
/ 23 января 2011

Не совсем то, что вы просите, но пытались ли вы взглянуть на Хамла?

У него гораздо более лаконичный синтаксис, поэтому предложенный вами пример можно записать так:

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

Как вы можете видеть, структура в Haml предоставляется с отступом, который помогает с чтением Хамл источник тоже.

Если, с другой стороны, вы пытаетесь сделать это как учебное упражнение о том, как самостоятельно создать синтаксический анализатор шаблонов, то, возможно, посмотрите на источник Haml или один из других шаблонизаторов.

0 голосов
/ 09 февраля 2011

Вы можете написать крошечный DSL ( Domain Specific Language ) для этого.На самом деле, это хорошо подходит IMO.Код, который я написал, не используется точно так, как вы показали в своем вопросе.

Использование

#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>

Реализация

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

Обратите внимание, что реализация делаетне делайте вложения, например <header><h1>..</h1></header>, но это должно быть просто реализовать.

0 голосов
/ 08 февраля 2011

Посмотрите на камень под названием builder . Возможно, он сможет предоставить основу, на которой вы сможете построить свой пример выше.

Вот грубая попытка реализовать приведенный выше пример:

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

который выводит:

<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 голосов
/ 08 февраля 2011

Я использую помощника для обработки аргументов, который затем вызывает различные партиалы для рендеринга.

0 голосов
/ 23 января 2011

То, что вы делаете, может быть достигнуто с помощью content_for .

Haml это супер круто, Slim может быть даже лучше?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...