I'm trying to write a custom builder to use with Breadcrumbs on Rails. I am using Tailwind for my styles but it doesn't seem to play nicely with Rails-generated code.
I have the following builder:
class TailwindBreadcrumbsBuilder < BreadcrumbsOnRails::Breadcrumbs::Builder
def render
#context.content_tag(:nav, class: 'flex py-3 px-5 text-slate-700 bg-slate-50 rounded-lg border border-slate-200', aria: { label: 'Breadcrumb' }) do
#context.content_tag(:ol, class: 'inline-flex items-center space-x-1 md:space-x-3') do
#elements.collect do |element|
render_element(element)
end.join.html_safe
end
end
end
def render_element(element)
current = #context.current_page?(compute_path(element))
aria = current ? { aria: { current: 'page' } } : {}
#context.content_tag(:li, aria) do
#context.content_tag(:div, class: 'flex items-center') do
link_or_text = #context.link_to_unless_current(compute_name(element), compute_path(element), element.options.merge(class: 'ml-1 text-sm font-medium text-slate-700 hover:text-white md:ml-2'))
divider = #context.content_tag(:span, (#options[:separator] || '>').html_safe, class: 'divider') unless current
link_or_text + (divider || '')
end
end
end
end
and I initialize the breadcrumbs on the page with:
<%= render_breadcrumbs builder: ::TailwindBreadcrumbsBuilder %>
However, not all styles are being applied (for example, the white text on hover does not work).
I suspect the Tailwind server doesn't compile these classes since they are Ruby-generated. Any idea how I can get this builder to work with Tailwind?
Thanks in advance
If you are using Tailwind v3, the classes are "purged" by default.
Since this is a ruby helper, I'd assume that this particular file was not added to the content list in tailwind.config.js.
Perhaps try adding something like this your config file:
module.exports = {
content: [
"./app/views/**/*.html.erb",
"./app/helpers/**/*.rb",
"./app/javascript/**/*.js",
"path/to/your/file.rb"
],
// ... your other configs
}
Hope that helps!
Related
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 am trying to follow this Gist to display Bootstrap alerts for a crud rails app https://gist.github.com/suryart/7418454. The contents of which have the code:
application_helper.rb:
module ApplicationHelper
def bootstrap_class_for flash_type
{ success: "alert-success", error: "alert-danger", alert: "alert-warning", notice: "alert-info" }[flash_type] || flash_type.to_s
end
def flash_messages(opts = {})
flash.each do |msg_type, message|
concat(content_tag(:div, message, class: "alert #{bootstrap_class_for(msg_type)} fade in") do
concat content_tag(:button, 'x', class: "close", data: { dismiss: 'alert' })
concat message
end)
end
nil
end
application.html.erb
<body>
<div class="container">
<%= flash_messages %>
<%= yield %>
</div><!-- /container -->
</body>
Problem is I am not seeing any of the messages displayed.
I thought that maybe since the gist is showing to return nil that maybe I need to return the contents of the .each iterator so I did something like this in my helper to return the html:
def flash_messages
#flash_msg = ""
flash.each do |msg_type, message|
#flash_msg = concat(content_tag(:div, message, class: "alert #{bootstrap_class_for(msg_type)} fade in") do
concat content_tag(:button, 'x', class: "close", data: { dismiss: 'alert' })
concat message
end)
puts "#flash_msg: #{#flash_msg}"
end
#flash_msg
end
But when you look at the output of the print statement it is showing me the enter HTML for the page, the end of which is actually the html I need:
<div class="alert alert-info fade in"><button class="close" data-dismiss="alert">x</button>Signed in successfully.</div>
How do I get this to work so that it only returns that last part and not the entire page?
You should use non-output code block: <% flash_messages %> (Not <%=) along with the original code of your gist.
concat is responsible for injecting the html markup in the template context and the return value of the helper has no significance.
I use Rails 4 and have a page with one large image and 6 thumbnails.
When a user clicks on one of the thumbnails then the large image should change to the one of the clicked thumbnails.
in my app/views/products/show.html.erb:
<div class="product-teaser-column">
<%= image_tag product_image_url(#product, type: :three_quarter, color: colors.first), itemprop: "image", class: 'teaser' %>
</div>
<div class="product-thumbnail-column">
<ul class="product-thumbnails" class "thumbnails inline">
<% type_map.except(:thumb, :reversiblefront).each_with_index do |type, index| %>
<li class='thumbnail'>
<%= image_tag product_image_url(#product, type: type, color: colors.first), itemprop: "image", class: 'thumbnail' %>
</li>
<% end %>
</ul>
</div>
I used to have image files in app/assets/images and implemented this feature with jQuery.
In app/assets/javascripts/application.js:
// change teaserimage on thumbnail-click
$(function() {
var images = [
"/assets/Nile_1_16_0119.jpg", "/assets/Nile_1_16_0095.jpg",
"/assets/Nile_1_16_0131.jpg", "/assets/Nile_1_16_0144.jpg",
"/assets/thumb_midnight.jpg", "/assets/back_midnight.jpg"
];
$(".thumbnail").click(function() {
var index = $(this).parent().index();
$('img.teaser').attr('src', images[index]);
$(this).parent().siblings().removeClass('active');
$(this).parent().addClass('active');
});
});
How can I achieve that when the image Urls are dynamic?
app/helpers/products_helpers.rb:
module ProductsHelper
def product_image_url(product, type:, color:)
source = URI.escape "#{product_image_base_url}/#{collection_folder(product)}/"\
"#{product_number(product)}_#{product_type(type)}"\
"_#{format_color_name(color.name)}.jpg"
image_exist?(source, product) ? source : false
end
def product_image_base_url
ENV['PRODUCT_IMAGE_BASE_URL']
end
def product_number(product)
product.number.strip.upcase
end
def product_type(type)
type_map.fetch(type)
end
def type_map
#type_map ||= {
full: "01",
three_quarter: "02",
medium: "03",
close_up: "04",
front: :front,
back: :back,
thumb: :thumb,
reversiblefront: :reversiblefront
}
end
end
Like Hardik suggested i could just replace in my js-code the images array with the thumbnail image urls:
var productImages = $("img.thumbnail").map(function() {
return this.src;
}).get();
I had misleadingly something with precompiled assets in mind when i was asking. Thanks Hardik for guiding me to the right direction!
I'm building a Rails app. I've done all the logic (db, controllers, models, etc). Now its time to make it nice.
In order to centralize the view of the app I was thinking in creating partials for the common stuff. For example one partial called common/_text_input.html.erb that will contain
<div class="field">
<%= f.label id %><br />
<%= f.text_field id %>
</div>
This will be called from inside a form using
<%= render partial: "common/text_input", locals: { f: f, id: :name } %>
Is this approach correct? Is there any other option in rails to do this?
If this is the correct way to do this, how can I acchieve this for a form tag, for example (where content is inserted inside it)?
Thanks
1 - There is another option to do this, Helpers and content_tag:
def text_input(form_builder, attribute, options = {})
options = { div: { class: :field }, label: { class: attribute } }.merge(options) # default options
content_tag :div, options[:div] do
f.label(attribute, options[:label]) + content_tag(:br) + f.text_field(attribute, options[:input])
end
end
Usage:
= form_for #resource do |f|
= text_input(f, :first_name)
= text_input(f, :last_name, input: { style: 'color: red;' }, label: { class: :another_class })
2 - It is correct to do with partials, but it is not as flexible as the Helpers are (see the options hash and the possibility to use another method in specific cases). To handle the form_tag (i.e. no form_builder), you can implement a new method:
# usage
= form_tag root_path, method: :get do
= text_input(nil, :search, input: { value: params[:search] }, label: { content: "Search for something!" })
# helper methods
def text_input(form_builder, attribute, options = {})
options = { div: { class: :field }, label: { class: attribute } }.merge(options) # default options
return text_input_tag(attribute, options) if form_builder.blank?
content_tag :div, options[:div] do
f.label(attribute, options[:label]) + content_tag(:br) + f.text_field(attribute, options[:input])
end
end
def text_input_tag(attribute, options = {})
value = options[:input].try(:delete, :value)
label_content = options[:label].try(:delete, :content)
content_tag :div, options[:div] do
label_tag(attribute, label_content, options[:label]) + content_tag(:br) + text_field_tag(attribute, value, options[:input])
end
end
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