I have STI models like this : Photo < Medium and Video < Medium.
Somewhere in a view, i would like to show them like that :
# app/views/albums/show.html.slim
ul
li= render #album.media
The render search for a albums/_photo or albums/_video partial :
# app/views/albums/_photo.html.slim
h3= photo.title
= image_tag photo.file.url
p= photo.description
# app/views/albums/_video.html.slim
h3= video.title
= video_tag video.file.url
p= video.description
Now, i would like to make a template to use in app/views/albums/show.html.slim for not repeat commons fields between media. Something like that :
# app/views/albums/_medium.html.slim
h3= medium.title
= yield(:medium_tag)
p= medium.description
= yield(:medium_additionnals)
# app/views/albums/_photo.html.slim
- content_for(:medium_tag) do
= image_tag photo.file.url
- content_for(:medium_additionnals) do
# any additionnals fields
# app/views/albums/_video.html.slim
- content_for(:medium_tag) do
= video_tag video.file.url
- content_for(:medium_additionnals) do
# any additionnals fields
But this give me in yields concatenate contents...
Someone has a solution / alternative to do this ?
[EDIT]
With this templates :
# app/views/albums/show.html.slim
= render #album.media
# app/views/albums/_medium.html.slim
div
h3= medium.title
= yield(:medium_tag)
# app/views/albums/_photo.html.slim
- content_for(:medium_tag) do
| TEST
The resulting HTML :
<div>
<h3>Photo1</h3>
TEST
</div>
<div>
<h3>Photo2</h3>
TESTTEST
</div>
<div>
<h3>Photo3</h3>
TESTTESTTEST
</div>
<div>
<h3>Photo4</h3>
TESTTESTTESTTEST
</div>
...
The yield result is concatened as many time as media count.
Your code seems really good, although I don't get how your render call is cycling through each medium item
You could use a helper to return the relevant data, although that wouldn't be the most efficient:
#app/helpers/application_helper.rb
module ApplicationHelper
output = Slim::Engine.new "h3={item.title}"
output += Slim::Engine.new "- eval(#{item.model_name.human}_tag) item.file.url"
output += Slim::Engline.new "p=item.description"
end
end
This would allow you to run:
#app/views/albums/_photo.html.slim
= media photo
#app/views/albums/_video.html.slim
= media video
-
Alternatively, you could just put it into a _medium partial:
#app/views/albums/_photo.html.slim
= render partial: "medium", locals: { item: photo }
#app/views/albums/_video.html.slim
= render partial: "medium", locals: { item: video }
_medium would then have the following:
#app/views/albums/_medium.html.slim
h3=item.title
- eval("#{item.model_name.human}_tag") item.file.url
p=item.description
Related
Question: How do I make a partial (that has to be generic) loop through different variables?
I have a tabbed page where I want to use a partial to avoid duplicating my HTML. The tabs are "Videos" and "Articles". It's the exact same HTML but I want to iterate through #videos for videos and #articles for articles.
The idea was to make the product partial completely generic and then somehow pass in #videos or #articles that I want to iterate through.
Partial: _product.html.slim
.col-md-5
.thumbnail
.thumb.spacer-sm
- if product.image.blank?
iframe allowfullscreen="" frameborder="0" mozallowfullscreen="" src="https://player.vimeo.com/product/#{product.vimeo_id}" webkitallowfullscreen=""
- elsif product.vimeo_id.blank?
= image_tag(product.image.url,
class: 'img img-responsive img-rounded')
.caption
.content-group-sm.media
.media-body
h6.text-semibold.no-margin
= product.name
small.text-muted
| by
= product.user.name
- unless product.price.blank?
h6.text-success.media-right.no-margin-bottom.text-semibold
| $
= product.price
= product.description
.panel-footer.panel-footer-transparent
.heading-elements
ul.list-inline.list-inline-separate.heading-text
li
= link_to 'Delete', product_path(product), method: :delete
li
= link_to 'Edit', edit_product_path(product)
HTML view rendering the partial:
.page-container.spacer-minus
.page-content
.row
.col-md-4
.sidebar-default.sidebar-separate
.sidebar-content
.content-group
.panel.no-border-radius-top
ul.navigation
li.navigation-header Navigation
li.active
a data-toggle="tab" href="#videos"
i.icon-video-camera2
| Videos
li
a data-toggle="tab" href="#articles"
i.icon-graduation
| Articles
.col-md-8
.tab-content
#videos.tab-pane.fade.in.active
.row
- #videos.each do |product|
= render 'shared/product'
#articles.tab-pane.fade
.row
- #articles.each do |product|
= render 'shared/product'
I just need my loop to understand what variable I want to iterate through. I cannot include #video or #article in the partial since that will defeat the purpose of having a generic partial.
With this implementation, I get the error:
undefined local variable or method `product' for #<#<Class:0x007fc58053ae60>:0x007fc58884ea68>
Try this code:
#videos.tab-pane.fade.in.active .row -
#videos.each do |video|
= render 'shared/product', product: video
#articles.tab-pane.fade .row -
#articles.each do |article|
= render 'shared/product', product: article
Hi this is pretty stuff if you want articles to come after the videos then try this.
= render partial: "product", collection: #videos
= render partial: "product", collection: #articles
this will remove the loop from your main view but in your case you are not using locals try this
.row
- #videos.each do |video|
= render 'shared/product', locals: {product: video}
.row
- #articles.each do |article|
= render 'shared/product' , locals: {product: article}
It will work.
I want to dynamically add classes to a container based on the name of the class that called my layout using the render method. Is it possible or do I need to pass it manually from each view?
File: emails/inbox.html.haml
render layout: 'shared/v3/panel' do
// whatever
Rendered HTML:
<div id="inbox" class="panel email_container"></div>
My temp solution is to do:
render layout: 'shared/v3/panel', locals: {class_panel: 'email_container', id_panel: 'inbox'} do
// whatever
But I want to do that dynamically.
Final solution: views/shared/_panel.html.haml
- # You need to use: "render layout", not "render partial" to pass the "do" block.
.container_content{class: "#{controller_name}_container #{local_assigns[:panel_class] ? panel_class : ''}", id: action_name}
- if local_assigns[:title]
.headline= local_assigns[:title]
= yield
- if local_assigns[:footer]
.footer= local_assigns[:footer]
- # Example of use:
-#= render layout: 'shared/v3/panel', locals: {title: 'Place', panel_class: 'my_custom_class'} do
-# - if #activity.place.present?
-# %p
-# = t('place')
-# = #activity.place
I am doing this for a school project and it is not working. :(
How do I get out the selected[petowner_id] from the view and make it usable in a ruby controller?
How do I make the #selected_pet = params([petowner_id]) in the controller that comes in from the view to function? Currently it renders an error message when I try to set it. :(
I am getting very tired of it not working.
The controller from Pets controller
class PetsController < ApplicationController
# GET /pets
# GET /pets.json
def monsters
#Finds the current user
if current_user
#user = current_user
#pets_kept = [] #why?
##petowner = Petowner.find(params[:petowner][:id])
#if(params[:commit])
#end
#monster = "Eeeep"
#mypets=[]
#all_my_pets = #user.petowners
#options value = 2
#params { selected_petowner[petowner_id]}
#selectpet = params{[selected][petowner_id]}
#petowner = Petowner.find_by_id(params[:id])
#pet = Pet.find_by_id(params[:pet_id])
#Find the petowners that the user currently has
##mypets = #user.petowners
#This is my way of doing things in a C++ fashion, I don't get all ruby things
#user.petowners.each do |pet|
##selected_pet = pet.find(params[:selected])
# if pet.hp != 0
# #pets_kept << pet #Dont recall seeing the << before in ruby but for C++ statement used for cout statements
#if pet.select
# #selected_pet = pet.select
#end
end
##selected_pet = Petowner.find(params[:petowner][:selected])
#end
#selected_pet = 1 ##user.petowners.find(params[:id])
#mypets = current_user.petowners.select{|pet| pet.hp !=0}
#raise "I am here"
##selected_pet = #mypets.find(params[:id][:selected])
##mypets = #pets_kept
end
The code from the view that doesn't go back to the controller variable and set it. :(
<select id="petowner_id" name="selected[petowner_id]">
<%= #all_my_pets.each do |pet| %>
<option value="<%= pet.id %>"><%= pet.pet_name %></option>
<% end %>
</select>
Previous step from pets/monsters view that doesn't work at all from previous collection. :(
<%= form_for :petowner, :url => petowner_fights_path(#selected_pet, #pet) do |f| %>
<p>Select a pet <%#= f.collection_select(:petowner, :petowner_id, #user.petowners, :petowner_id, :pet_name) %></p>
<%= render 'monsterinfo' %>
<div class="outer"></div>
<%= f.submit "Fight Selected Monster" %>
<% end %>
You probably want params[:petowner][:petowner_id]. Definitely don't construct the select with html in a view.
By the way, it's really helpful to see all of the params passed in to a controller action. I tend to raise params.to_yaml when I need to do that.
I have a JSON that's returned from my rails API like this
[{"tipo":"1","dia":"02/10/2012","empresa_id":"17","horas":"0:15","tempo":"900"},
{"tipo":"2","dia":"02/10/2012","empresa_id":"17","horas":"0:12","tempo":"720"}]
but I need it to be something like this
`[empresa_id: 17, [{"tipo":"1","dia":"02/10/2012","horas":"0:15","tempo":"900"},
{"tipo":"2","dia":"02/10/2012","horas":"0:12","tempo":"720"}]]
I need to group the results within empresa_id...how do I do that?
In your model's view folder, create a file called modelname.json.erb. Here, you can use ruby code to edit how you want to format your json. Here's an untested code example of what it might look like:
[
<% i = 0
#modelname.each do |model| %>
{
"id": <%= model.id %>,
"name": "<%= model.name %>"
}<% if i != (#modelname.size - 1) %>,<% end %>
<% i += 1 %>
<% end %>
]
In your controller, by default you will have something like this for the output:
format.json { render json: #modelname }
Change it to this:
format.json
By doing this, it will look for the json view you just created!
Try this:
require 'json'
s = '[{"tipo":"1","dia":"02/10/2012","empresa_id":"17","horas":"0:15","tempo":"900"}, {"tipo":"2","dia":"02/10/2012","empresa_id":"17","horas":"0:12","tempo":"720"}]'
j = JSON.parse(s)
r = j.inject({}) do |f,c|
key = c.delete('empresa_id')
(f[key]||=[]) << c
f
end
p r
resulting in
{"17"=>[{"tipo"=>"1", "dia"=>"02/10/2012", "horas"=>"0:15", "tempo"=>"900"}, {"tipo"=>"2", "dia"=>"02/10/2012", "horas"=>"0:12", "tempo"=>"720"}]}
See live demo here
I want to create abstract components for my views, that don't disclose the way they will be rendered.
The example is an tabbed nav box like the tabs from bootstrap
In my view I want to write s.th. like:
= tab_section(self) do
- tab 'tab1' do
%p tab1 content
= link_to example_var, "#top"
- tab 'tab2' do
%p tab2 content
= link_to 'http://example.com', "#top"
which should then be rendered to s.th. like this:
<ul>
<li>tab1</li>
<li>tab2</li>
</ul>
<div class='content'>
<div class='tab'>
<p>tab1 content</p>
<a href='#top'>this could also be an #var from the controller</a>
</div>
<div class='tab'>
<p>tab2 content</p>
<a href='#top'>http://example.com</a>
</div>
</div>
All my attempts to defer rendering of the tab 'content' failed. I created a minimal rails app demonstrating the three approaches I took.
Take a look at the application_helper.rb and the welcome#show view.
What is the correct way to do such thing?
I got some support and found the following solution:
The outer 'component' must be passed into the block to call the inner function upon:
= tab_section2 do |section|
- section.tab 'tab1' do
%p tab1 content
= link_to example_var, "#top"
- section.tab 'tab2' do
%p tab2 content
= link_to 'http://example.com', "#top"
Since we don't need to bind the block to the tab_section instance (previously done with instance_exec), we can yield the block directly.
def tab_section2(&blk)
raise "Y U NO SUPPLY block?" unless block_given?
ts = TabSection2.new(self, self)
yield(ts)
render :partial => '/tab2', locals: {section: ts}
end
The partial renders the output of the tabs render function:
%ul
- section.tabs.each do |tab|
%li= tab.name
.content
- section.tabs.each do |tab|
.tab
= tab.render.html_safe
Which is implemented as follows:
class Tab2
attr_reader :name, :blk
def initialize(name, context, &blk)
#context = context
#name = name
#blk = blk
end
def render
#context.capture_haml(&#blk)
end
end
class TabSection2
attr_reader :tabs
def initialize(context)
#context = context
#tabs = []
end
def tab(name, &blk)
raise "Y U NO SUPPLY block?" unless block_given?
#tabs << Tab2.new(name, #context, &blk)
end
end