I am currently on a Rails project and I want to override the attributes defined in the body tag in a view that is not part of the layout.
Like this:
app/views/layouts/application.haml
...header
%body
= render 'shared/menu'
.container
= yield
In another view. I want the body tag to have certain attributes:
app/views/stuff/index.haml
{data: {spy: 'scroll', target: '.d-sidebar', :'twttr-rendered' => 'true'}} << these attributes should apper in the body tag of the layout file. They should only be visible on the sub page
What is the easiest way to achieve this?
You could probably provide it:
in layout
%body{ yield(:body_attributes) }
in view
- provide(:body_attributes, { class: '', data: {} })
Related
I have an Entry model. I render each entry using a partial. Below each entry, I have a button. Clicking on that button shows the complete entry within a turbo frame.
When the turbo frame loads, the #toggle action is fired in the stimulus controller. Rails generates a unique id for every button using dom_id(entry, :show). (show_entry_1000529641 for example).
I want to reference the button element in the #toggle action.
I understand that the usual way of referencing targets is by first declaring them at the top static targets = [...]. But since the target ids are generated dynamically, I'm not sure what to do.
At the moment, I am setting a data-button attribute on the turbo frame. Then using that value to look up the button element.
I feel like there is probably a better way of doing this. What is the correct/better way?
#entries/_entry.html.erb
#...
<%=
button_to "Show #{entry}",
entry_path(entry),
id: dom_id(entry, :show),
method: :get,
data: { turbo_frame: dom_id(entry) } %>
#...
<%= turbo_frame_tag entry,
data: {
controller: "buttons",
action: "turbo:frame-load->buttons#toggle",
button: dom_id(entry, :show)
} %>
import { Controller } from "#hotwired/stimulus"
export default class extends Controller {
toggle() {
const button_id = this.element.getAttribute('data-button')
const button = document.getElementById(button_id)
console.log(button)
}
}
The Targets use a name as a reference, not an ID (see documentation)
The correct Stimulus approach is to have all necessary elements inside the controller and reference these using Target names. The controller <div> acts as a scope therefore you can have multiple same name controllers with same name targets in one page and it would just work, no need for element IDs.
There where you would normally use a reference by ID, use this.exampleTarget in Stimulus. An analogy for a reference by a class is this.exampleTargets. Targets must be defined in the static targets = ['example'] and in the element's data attribute data-controllerName-target="example"
I suggest you try something like this:
#entries/_entry.html.erb
#...
<div data-controller="buttons">
<%=
button_to "Show #{entry}",
entry_path(entry),
id: dom_id(entry, :show),
method: :get,
data: { turbo_frame: dom_id(entry),
buttons_target: 'button'
} %>
#...
<%= turbo_frame_tag entry,
data: {
action: "turbo:frame-load->buttons#toggle",
button: dom_id(entry, :show)
} %>
</div>
import { Controller } from "#hotwired/stimulus"
static targets = ['button']
export default class extends Controller {
toggle() {
console.log(this.buttonTarget)
}
}
My model structure is pretty solid. I have MarketingDeliverySystem has_many MarketingSections. MarketingSections has_many MarketingVideos.
I have another segment: GroupDevelopment has_many GroupSections. GroupSections has_many GroupVideos.
I'm trying to use a partial to pass the variables, thus DRYing it all up.
I have the following that I'm trying to pass to the partial:
= render partial: '/sales_presentations/sales_presentation',
locals: { marketing_delivery_system: #marketing_delivery_system,
first_video: first_marketing_video(#marketing_delivery_system),
sales_presentation: #marketing_delivery_system}
Then in the partial I have the following:
.rounded-box-header.blue-bg #{sales_presentation.title}
ul
- sales_presentation.sections.ordered.each_with_index do |section, index|
- list_class = 'section show'
- list_class = 'section hide' if index != 0
li
= link_to section.title, '#', class: 'section', data: { id: section.id }
ul class="#{list_class}" data-section-id="#{section.id}"
- section.videos.ordered.each do |video|
li.video
= link_to video.title, '#',
class: 'video video-link',
data: { video: video.youtube_link,
sales_presentation: sales_presentation.title.parameterize }
.seven.columns
.row
div id="#{sales_presentation.title.parameterize}-container"
video {
id="#{sales_presentation.title.parameterize}-video-player"
class="video-js vjs-default-skin videos"
height=400
poster=""
controls preload='none'
data-default-url="#{first_video(sales_presentation)&.youtube_link}"
I previously had issues with sales_presentation.title at the top until I updated the locals.
My question/issue is how do I pass in through the locals to use for sales_presentation.sections instead to use #marketing_delivery_system.marketing.sections?
I thought I could just put that in through locals:
sales_presentation.sections: #marketing_delivery_system.marketing_sections but I end up with a massive syntax error.
I've also tried creating a partial view for these two and then changed sales_presentation throughout the view to mod. Then changed mod.sections to mod_section and setting that in the locals to mod_section: #marketing_delivery_system.marketing_section. The problem then gets into that I end up needing to hit video later in the iteration. So then that has the same issue.
You misunderstand the meaning of locals in partials.
Says we have
<%= render partial: 'image', locals: {size: #image.size, extension: #image.extension} %>
It means that in image partial now we can use local variable size and extension (keys) as #image.size and #image.extension (values).
Put in locals: {} all local variables you want.
So you can't write in locals sales_presentation.sections: #marketing_delivery_system.marketing.sections
But you can sales_presentation_sections: #marketing_delivery_system.marketing.section
Also you have problem with this code:
locals: { marketing_delivery_system: #marketing_delivery_system,
first_video: first_marketing_video(#marketing_delivery_system),
sales_presentation: #marketing_delivery_system }
marketing_delivery_system and sales_presentation will be with the same value.
I use Gritter notifications in Rails 5 app and can't find a way to remove default title of the notification popup. I add Gritter as bellow:
<%= js add_gritter(flash[:notice], title: 'I dont need you', sticky: false) %>
Tried:
<%= js add_gritter(flash[:notice], title: false, sticky: false) %>
<%= js add_gritter(flash[:notice], title: nil, sticky: false) %>
<%= js add_gritter(flash[:notice], title: '', sticky: false) %>
<%= js add_gritter(flash[:notice], title: ' ', sticky: false) %>
And still popup appears with the default title - "Notification". Tried to search in the whole app's project "Notification" or "gritter", but nothing related was found.
How to get rid of it?
The add_gritter method in the gem sets the options[:title] as "Notification" if options[:title].blank? returns true.
The "dirty" option is to define it again, with a hash of options instead *args, and to render the title if it was passed as an option argument, like:
def add_gritter(text, options={})
if %w(success warning error notice progress).include?(options[:image].to_s)
options[:image] = image_path("#{options[:image]}#{options[:image].to_s == 'progress' ? '.gif' : '.png'}")
end
notification = Array.new
notification.push("jQuery(function(){") if options[:nodom_wrap].blank?
notification.push("jQuery.gritter.add({")
notification.push("image:'#{options[:image]}',") if options[:image].present?
notification.push("sticky:#{options[:sticky]},") if options[:sticky].present?
notification.push("time:#{options[:time]},") if options[:time].present?
notification.push("class_name:'#{options[:class_name]}',") if options[:class_name].present?
notification.push("before_open:function(e){#{options[:before_open]}},") if options[:before_open].present?
notification.push("after_open:function(e){#{options[:after_open]}},") if options[:after_open].present?
notification.push("before_close:function(e){#{options[:before_close]}},") if options[:before_close].present?
notification.push("after_close:function(e){#{options[:after_close]}},") if options[:after_close].present?
notification.push("on_click:function(e){#{options[:on_click]}},") if options[:on_click].present?
notification.push("title:'#{escape_javascript(options[:title])}',") if options[:title].blank? # Here
notification.push("text:'#{escape_javascript(text)}'")
notification.push("});")
notification.push("});") if options[:nodom_wrap].blank?
text.present? ? notification.join.html_safe : nil
end
But the gritter.js file has an if to check if the title has any content, so you should have to deal with your own and edit it, just to check for the text, like:
//We might have some issues if we don't have a title or text!
if (!params.text) {
throw 'You need to fill out the text parameter.';
}
Doesn't sound like a best way, but works. If any other solutions - please feel free :)
.gritter-title {
display: none;
}
Edit:
Since I use SCSS, this is better:
<%= js add_gritter(flash[:notice], sticky: false, :on_click => remove_gritter, :time => 100000, class_name: 'no-title') %>
.no-title {
.gritter-title {
display: none;
}
}
I have a Slim partial for a widget. The widget has common elements and then some custom content that I want to yield to. What's the correct syntax for that? Here's what I thought would work, but doesn't.
Widget Partial
.container
.title= title
.content
== yield
Page
.page
= render partial: "widget_partial", locals: { title: "Content 1" } do
div Some really awesome content.
= render partial: "widget_partial", locals: { title: "Content 2" } do
span Different but also awesome content.
According to the Action View Overview on partials (section 3.2.3), when you are only using a partial template and locals, you can get away with a minimal approach like this:
(widget partial)
.container
.title
p #{title}
= yield
(Page)
= render "widget_partial", title: "content 1" do
.div
p Some really awesome content.
= render "widget_partial", title: "content 2" do
.div
p Different but also awesome content.
Note that in the partial, itself, you cannot put text directly into a
div, so I added a <p> element.
You should add layout argument in the render. Something like this:
(widget partial)
.container
.title = title
.content
= yield
(page)
.page
= render layout: 'widget_partial', locals: { title: 'Content 1' } do
div Some really awesome content.
= render layout: 'widget_partial', locals: { title: 'Content 2' } do
span Different but also awesome content.
Doc: http://edgeguides.rubyonrails.org/action_view_overview.html#partial-layouts
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