Rails - ActionView::Base.field_error_proc moving up the DOM tree? - ruby-on-rails

Is there anyway to go up the DOM tree from the html_tag element passed in?
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
# implementation
end
Is there anyway I can implement this method to move up the DOM tree and place a class on the parent div?
For example:
<div class="email">
<label for="user_email">Email Address</label>
<input id="user_email" name="user[email]" size="30" type="text" value="">
</div>
I would like to place a class on the div.email rather than place something directly on the input/label.
Can this be done with the field_error_proc method or is there a clean alternative?
I want to avoid doing this explicitly in my views on every form field. (like the following)
.email{:class => object.errors[:email].present? 'foo' : nil}
=form.label :email
=form.text_field :email
FYI: Short answer to my question is that there is no way to gain access to additional portions of the DOM in the field_error_proc method. This is due to the fact that these methods are not actually building a DOM but instead just concatting a bunch of strings together. For info on some possible work arounds, read the solutions below.

You have two options that I can think of off the top of my head:
Rewrite ActionView::Base.field_error_proc
In one situation I rewrote ActionView::Base.field_error_proc (there is a rails cast on it). Using a little nokogiri, I changed the proc to add the error messages to the input/textarea element's data-error attribute instead of wrapping them in an error div. Then a wrote a little javascript using jquery to wrap all inputs and their labels on document ready. If there was error information associated with the input/textarea, it is transferred to the wrapper div.
I know this solution relies on javascript, and may or may not have a graceful fall back, but it works fine in my situation since it is for a web app rather than a publicly accessible site. I feel okay requiring javascript in that scenario.
# place me inside your base controller class
ActionView::Base.field_error_proc = Proc.new do |html_tag, object|
html = Nokogiri::HTML::DocumentFragment.parse(html_tag)
html = html.at_css("input") || html.at_css("textarea")
unless html.nil?
css_class = html['class'] || ""
html['class'] = css_class.split.push("error").join(' ')
html['data-error'] = object.error_message.join(". ")
html_tag = html.to_s.html_safe
end
html_tag
end
Write a your own ActionView::Helpers::FormBuilder
You can also get more or less the same effect by overriding the text_field method so that it always returns a wrapped input. Then, using the object variable, access the errors hash and add any needed error information to the wrapper. This one does not require javascript and works nicely, but in the end I prefer the first approach because I find it more flexible. If however, I was working on a publicly accessible site, I would use this approach instead.
Also, FYI, I found it handy to override the check_box and radio_button methods so they always return the input with its associated label. There are lots of fun things you can do with a custom FormBuilder.
# place me inside your projects lib folder
class PrettyFormBuilder < ActionView::Helpers::FormBuilder
def check_box(field, label_text, options = {})
checkbox = super(field, options)
checkbox += label(field, label_text)
#template.content_tag(:div, checkbox, :class => "wrapper")
end
end
The above example shows how to wrap a check_box, but it is more or less the same with the text_field. Like I said, use the object.errors to access the errors hash if needed. This is just the tip of the ice berg... there is a ton you can do with custom FormBuilders.
If you go the custom form builder route, you may find it helpful to modify the above ActionView::Base.field_error_proc as follows so you don't get double wrapped fields when there are errors.
ActionView::Base.field_error_proc = Proc.new do |html_tag, object|
html_tag # return the html tag unmodified and unwrapped
end
To use the form builder either specify it in the form_for method call or place the following in your application helper:
# application helper
module ApplicationHelper
ActionView::Base.default_form_builder = PrettyFormBuilder
end
Often my solutions end up using some combination of each to achieve the desired result.

Related

Can a `<button type="submit">` be used in place of a `f.submit` in a Rails `form_for` form?

I'm looking to standardize all the buttons and button-styled elements in our Rails app with a button component. This means rendering a styled <button> element with type "button" or type "submit". I'm making use of the view_component gem provided by github!
I am hoping to be able to replace all Rails form helper-y elements such as button_tag, submit_tag and f.submit with a button element. I'm pretty new to Rails, and I am not sure if there are things happening under the hood I'm not taking into account. Looking at the Rails documentation, it doesn't seem like there anything special with the f.submit form helper element.
Am I missing something? Are there consequences to replacing rails form helper submits with a button[type='submit']?
It would be long way to change stuff, so Better to use css and adjust css to get your requirements.
Just add following in you code to create your own my_submit_button
### app/helpers/application_helper.rb ###
module ApplicationHelper
def my_submit
# decide what the submit text should be
text = if #record.new_record?
"Create #{#record.class}"
else
"Update #{#record.class}"
end
# build and return the tag string
my_submit_tag(text)
end
end
further you can read here
The form_for helper

Sanitizing Trix input

Under Rails 5.1 I am using Trix to allow users to edit their 'legal conditions'. Then I am trying to sanitize this 'legal' parameter in my controller before the user record is updated, but end up with :
undefined method `sanitize'
Here the code :
params[:user][:legal] = sanitize params[:user][:legal], tags: %w(strong div strong br li ul)
def user_params
params.require(:user).permit(:presentation, :linktowebsite, :legal)
end
Don't see anything different than normal usage shown here : http://api.rubyonrails.org/classes/ActionView/Helpers/SanitizeHelper.html
You are not using sanitize correctly. sanitize is used in the view, not in the controller.
To use it correctly, your model should allow a field to save html input from the user, but you want to "clean" it when it's used in the view so that unsafe or non-whitelisted tags/attributes is prevented from being sent/displayed to the user.
If you are looking to remove html tags/attributes before it gets saved, you may want to look at strip_tags.
strip_tags("Strip <i>these</i> tags!")
# => Strip these tags!
strip_tags("<b>Bold</b> no more! <a href='more.html'>See more here</a>...")
# => Bold no more! See more here...
strip_tags("<div id='top-bar'>Welcome to my website!</div>")
# => Welcome to my website!

Stripping HTML markup from a translation string

I have some translations that I use in my views. These translations sometimes return very basic HTML markup in them -
t("some.translation")
#=> "This is a translation with some markup<br />"
(Side note: I'm using the fantastic it gem to easily embed markup, and specifically links, in my translations)
What if I wanted to strip the HTML tags in certain cases, like when I'm working with the translation string in my RSpec tests. Is there an HTML strp functionality that will compile and remove that markup?
t("some.translation").some_html_strip_method
#=> "This is a translation with some markup"
Thanks!
You may want to try strip_tags from ActionView::Helpers::SanitizeHelper
strip_tags("Strip <i>these</i> tags!")
# => Strip these tags!
strip_tags("<b>Bold</b> no more! <a href='more.html'>See more here</a>...")
# => Bold no more! See more here...
strip_tags("<div id='top-bar'>Welcome to my website!</div>")
# => Welcome to my website!
Depending on where you use it.
strip_tags method not functioning in controllers, models, or libs
It comes up with an error about white_list_sanitizer undefined in the class you’re using it in.
To get around this, use:
ActionController::Base.helpers.strip_tags('string')
To shorten this, add something like this in an initializer:
class String
def strip_tags
ActionController::Base.helpers.strip_tags(self)
end
end
Then call it with:
'string'.strip_tags
But if you only need to use it in VIEW, simply:
<%= strip_tags(t("some.translation")) %>

Is there a way to cache a string fragment within a helper method?

I have a helper that generates complex HTML for common components in an engine.
Helper (very simplified):
def component(name)
component = Component.find_by_name!(name)
# a whole lot of complex stuff that uses component to build some HTML
end
View:
<%= component(:my_component) %>
I want to implement fragment caching on these components but I want to do it within #component itself to keep things DRY, e.g.
def component(name)
...
cache some_unique_fragment_name do
html
end
# or, more succinctly:
cache(some_unique_fragment_name, html)
end
The problem is that Rails' cache helper expects it's going to wrap a block of HTML in Erb and therefore won't work as I've described above.
Is there a way to use #cache for a string fragment in a helper method?
I'm a big fan of the fetch block, you can read more in the Rails docs:
def component(name)
# ...
cache.fetch('some_unique_fragment_name') do
html
end
end
What this does is it will return the value of some_unique_fragment_name if it's available, otherwise it will generate it inside the block. It's a readable, clean way of showing that caching is occurring.

Rails 3: How to display properly text from "textarea"?

In my Rails 3 application I use textarea to let users to write a new message in a forum.
However, when the message is displayed, all newlines look like spaces (there is no <br />). Maybe there are other mismatch examples, I don't know yet.
I wonder what is the most appropriate way to deal with this.
I guess that the text that is stored in the database is OK (I see for example that < is converted to <), so the main problem is the presentation.
Are there build-in helper methods in Rails for this ?
(simple_format does something that looks similar to what I need, but it adds <p> tags which I don't want to appear.)
Rails got a helper method out of the box, so you dont have to write your own method.
From the documentation:
simple_format(text, html_options={}, options={})
my_text = "Here is some basic text...\n...with a line break."
simple_format(my_text)
# => "<p>Here is some basic text...\n<br />...with a line break.</p>"
more_text = "We want to put a paragraph...\n\n...right there."
simple_format(more_text)
# => "<p>We want to put a paragraph...</p>\n\n<p>...right there.</p>"
simple_format("Look ma! A class!", :class => 'description')
# => "<p class='description'>Look ma! A class!</p>"
You can use style="white-space: pre-wrap;" in the html tag surrounding the text. This respects any line breaks in the text.
Since simple_format does not do what you want, I'd make a simple helper method to convert newlines to <br>s:
def nl2br(s)
s.gsub(/\n/, '<br>')
end
Then in your view you can use it like this:
<%= nl2br(h(#forum_post.message)) %>
If someone still gets redirected here and uses Rails 4:
http://apidock.com/rails/v4.0.2/ActionView/Helpers/TextHelper/simple_format
You can now specify the tag it gets wrapped in (defaults to p) like so:
simple_format(my_text, {}, wrapper_tag: "div")
# => "<div>Here is some basic text...\n<br />...with a line break.</div>"
CSS-only option
I believe one of the easiest options is to use css white-space: pre-line;
Other answers also mentioned using white-space, but I think it needs a little more information:
In most cases you should probably choose pre-line over pre-wrap. View the difference here.
It's very important to keep in mind about white-space that you should not do something like this:
<p style="white-space: pre-line;">
<%= your.text %>
</p>
It will produce extra spaces and line-breaks in the output. Instead, go with this:
<p style="white-space: pre-line;"><%= your.text %></p>
HTML alternative
Another way is to wrap your text in <pre> tags. And last note on my CSS option is true here as well:
<p>
<pre><%= your.text %></pre>
</p>
Don't separate your text from <pre> tags with spaces or line-breaks.
Final thoughts
After googling this matter a little I have a feeling that html-approach is considered less clean than the css one and we should go css-way. However, html-way seems to be more browser-compatible (supports archaic browsers, but who cares):
pre tag
white-space
The following helper preserves new lines as line breaks, and renders any HTML or Script (e.g Javscript) as plain text.
def with_new_lines(string)
(h(string).gsub(/\n/, '<br/>')).html_safe
end
Use as so in views
<%= with_new_lines #object.some_text %>
I just used white-space: pre-line. So next line (\n) will render it.
You'll need to convert the plain text of the textarea to HTML.
At the most basic level you could run a string replacement:
message_content.gsub! /\n/, '<br />'
You could also use a special format like Markdown (Ruby library: BlueCloth) or Textile (Ruby library: RedCloth).
I was using Ace code-editor in my rails app and i had problem, that whenever i update or create the code, it adds always extra TAB on every line (except first). I couldn't solve it with gsub or javascript replace.. But it accidently solved itself when i disabled layout for that template.
So, i solved it with
render :layout => false

Resources