Rails renderizam parcial com bloco


119

Estou tentando reutilizar um componente html que escrevi que fornece estilo de painel. Algo como:

  <div class="v-panel">
    <div class="v-panel-tr"></div>
    <h3>Some Title</h3>
    <div class="v-panel-c">
      .. content goes here
    </div>
    <div class="v-panel-b"><div class="v-panel-br"></div><div class="v-panel-bl"></div></div>
  </div>

Portanto, vejo que a renderização leva um bloco. Achei então que poderia fazer algo assim:

# /shared/_panel.html.erb
<div class="v-panel">
  <div class="v-panel-tr"></div>
  <h3><%= title %></h3>
  <div class="v-panel-c">
    <%= yield %>
  </div>
  <div class="v-panel-b"><div class="v-panel-br"></div><div class="v-panel-bl"></div></div>
</div>

E eu quero fazer algo como:

#some html view
<%= render :partial => '/shared/panel', :locals =>{:title => "Some Title"} do %>
  <p>Here is some content to be rendered inside the panel</p>
<% end %>

Infelizmente, isso não funciona com este erro:

ActionView::TemplateError (/Users/bradrobertson/Repos/VeloUltralite/source/trunk/app/views/sessions/new.html.erb:1: , unexpected tRPAREN

old_output_buffer = output_buffer;;@output_buffer = '';  __in_erb_template=true ; @output_buffer.concat(( render :partial => '/shared/panel', :locals => {:title => "Welcome"} do ).to_s)
on line #1 of app/views/sessions/new.html.erb:
1: <%= render :partial => '/shared/panel', :locals => {:title => "Welcome"} do -%>
...

Portanto, ele não gosta do =obviamente com um bloco, mas se eu removê-lo, ele simplesmente não produz nada.

Alguém sabe fazer o que estou tentando alcançar aqui? Eu gostaria de reutilizar este painel html em muitos lugares do meu site.


1
A resposta aceita está correta, mas desde o Rails 5.0.0 isso é possível sem a layout-workaround, veja guias.rubyonrails.org/…
fabi

Respostas:


208

Embora ambas as respostas acima funcionem (bem, o exemplo para o qual tony tem um link), acabei encontrando a resposta mais sucinta no post acima (comentário de Kornelis Sietsma)

Acho que render :layoutfaz exatamente o que eu procurava:

# Some View
<%= render :layout => '/shared/panel', :locals => {:title => 'some title'} do %>
  <p>Here is some content</p>
<% end %>

combinado com:

# /shared/_panel.html.erb
<div class="v-panel">
  <div class="v-panel-tr"></div>
  <h3><%= title -%></h3>
  <div class="v-panel-c">
    <%= yield %>
  </div>
</div>

6
no Rails 3.2.2, acho que você deve usar ao <%= %>invés de <% %>, por exemplo<%= render :layout => '/shared/panel',
Siwei Shen 申思维

você está certo, este post não faz suposições sobre a versão Rails, tenho certeza que as pessoas podem descobrir isso.
brad

como este (resolve meu problema então +1), mas isso é uma boa prática? não haverá desaceleração por algum motivo? Talvez o banco de dados seja acionado mais cedo do que deveria por causa de outro rendimento (não tenho tempo para investigar isso sozinho agora, apenas pensando)
equivalente em

1
Alguma maneira de saber se um conteúdo foi produzido? Caso seja opcional.
Vadorequest

3
No Rails 5.0 houve uma mudança, então isso é geral para todos os parciais, não apenas para layouts. Você pode alterar a primeira linha do código de chamada para:<%= render '/shared/panel', title: 'some title' do %>
Jay Mitchell

27

Aqui está uma alternativa com base em respostas anteriores.

Crie sua parcial em shared/_modal.html.erb:

<div class="ui modal form">
  <i class="close icon"></i>
  <div class="header">
    <%= heading %>
  </div>
  <div class="content">
    <%= capture(&block) %>
  </div>
  <div class="actions">
    <div class="ui negative button">Cancel</div>
    <div class="ui positive button">Ok</div>
  </div>
</div>

Defina seu método em application_helper.rb:

def modal_for(heading, &block)
  render(
    partial: 'shared/modal',
    locals: { heading: heading, block: block }
  )
end

Chame de qualquer visão:

<%= modal_for('My Title') do |t| %>
  <p>Here is some content to be rendered inside the partial</p>
<% end %>

11

Você pode usar o auxiliar de captura e até mesmo embutido na chamada de renderização:

<%= render 'my_partial',
           :locals => { :title => "Some Title" },
           :captured => capture { %>
    <p>Here is some content to be rendered inside the partial</p>
<% } %>

e em compartilhado / painel:

<h3><%= title %></h3>
<div class="my-outer-wrapper">
  <%= captured %>
</div>

que irá produzir:

<h3>Some Title</h3>
<div class="my-outer-wrapper">
  <p>Here is some content to be rendered inside the partial</p>
</div>

Consulte http://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html


1
O padrão embutido é bom para uma parte que precisa de vários blocos.
mahemoff de

4

Com base na resposta aceita, isso é o que funcionou bem para mim usando Rails 4.

Podemos renderizar um painel como tal:

= render_panel('Non Compliance Reports', type: 'primary') do
  %p your content goes here!

insira a descrição da imagem aqui

Isso requer um método auxiliar e uma visão compartilhada:

método auxiliar (ui_helper.rb)

def render_panel(heading, options = {}, &block)
  options.reverse_merge!(type: 'default')
  options[:panel_classes] = ["panel-#{options[:type]}"]

  render layout: '/ui/panel', locals: { heading: heading, options: options } do
    capture(&block)
  end
end

Ver (/ui/panel.html.haml)

.panel{ class: options[:panel_classes] }
  .panel-heading= heading
  .panel-body
    = yield

Isso funciona no Rails 5.x também, eu só precisava mudar panel.html.hamlpara_panel.html.haml
Kris

1

Acho que funcionará (acabei de fazer um teste rápido) se você atribuí-la a uma variável primeiro e depois gerá-la.

<% foo = render :partial => '/shared/panel', :locals =>{:title => "Some Title"} do %>
<p>Here is some content to be rendered inside the panel</p>
<% end %>
<%= foo %>
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.