Automatic method to set the tabindex using form helpers - ruby-on-rails

Is there an easy way to have the form helpers set the tabindex parameter automatically when using form helpers in Rails?
Basically, I don't want to have to manually set the tab index on every form element when building forms (I keep forgetting to update them when I change things). The majority of the forms I write are basically a list of fields. The tab index should be in the order they are defined. Ideally, I would set the initial index in the form_for call and everything else would be handled for me.
Does anyone know how to do this?

I usually add a method like this to ApplicationHelper
def autotab
#current_tab ||= 0
#current_tab += 1
end
Then in my views I make calls to the helper with a :tabindex => autotab like so:
<%= text_field "post", "login",:tabindex => autotab, :value => #login %>
You can also modify all the text_field, check_box, methods one at a time to add the tabindex automatically, by adding something like this to your application helper: (untested but you get the point)
def text_field_with_tabindex(*args)
options = args.last
options[:tabindex] = autotab if options.is_a?(Hash) && options[:tabindex].nil?
text_field_without_tabindex(*args)
end
def self.included(base)
base.class_eval do
alias_method_chain :text_field, :tabindex
end
end
That might be more trouble than it's worth

Related

Render a view in a model

I know views shouldn't be rendered from a model, but I cannot make sense of a better way to handle this problem.
I have a simple_form form that should show a list of object as checkboxes:
= f.association :results, collection: #search.results.all(order: 'cost'), as: :check_boxes
Like this it would only show the object next to a checkbox, but I need to display a bunch of more detailed information, so I'm defining the to_label method in my model like this:
class Result < ActiveRecord::Base
belongs_to :search
def to_label
"<div class='row'>
<div class='col-md-2'>#{supplier}</br><span class='label label-default'>#{brand}</span></div>
<div class='col-md-2'>#{desc}</div>
<div class='col-md-2'>#{model}</div>
<div class='col-md-1'>#{season}</div>
<div class='col-md-1'>#{qty}</div>
<div class='col-md-1'>#{expected_delivery}</div>
<div class='col-md-1'>#{msrp}</div>
<div class='col-md-1'>#{cost}</div>
</div>".html_safe
end
end
This shows some better formatted information, but still it's becoming very complicate to mantain and I will need at some point to add information like images etc. Of course it is also very wrong from a MVC point of view.
My idea is to create a partial in app/views, using haml as for the rest of my application and make my to_label method look like this:
def to_label
render "results/label"
end
Of course rails won't allow me to do that, but I have no better idea on how to implement it.
You should not access views from a model. Instead, create a helper with a function that receives the result object as a parameter (code not tested):
helpers/results_helper.rb
result_to_label(result)
render(:template =>"results/label", :layout => nil , :locals => { :result => result }).to_s
end
Then, from your view you call result_to_label(the_result) directly.
Update
In your simple_form, you can add the label_method with a lambda:
= f.association :results, collection: #search.results.all(order: 'cost'), label_method: lambda { |res| result_to_label(res) }
Bending rails that way will get you somewhere you don't wanna be. First of all you trying to render complex html layout by making simple_form thinking it's label for form control. Building custom form without use of simple_form makes more sense. Because rendering simple controls with automatic label generation is kinda simple form thing. And you are way beyond that.
So solution to your problem as it seems for me is defining to_partial_path on your class:
class Result < ActiveRecord::Base
def to_partial_path
'results/form'
end
end
and render your form with:
= render #results
it may be rendered inside of simple form you just need to build form controls with rails or simple form helpers.

How to get class="form-control" in input fields by default (Rails Form Helper + Bootstrap 3)

I'm converting my site to Twitter Bootstrap 3, and have run into what seems like silly problem, but I haven't been able to find an easy solution via google.
How do I get class="form-control" to be populated by default in the Rails Form Helper? I can only do it by typing it explicitly, this seems like a waste of time. (below)
It is required for bootstrap to style the input.
<%= f.label :email %>
<%= f.text_field :email, class: "form-control" %>
Am I naive to think that Rails should add this feature just because bootstrap implemented it?
Yup, this can be done without changing the way you use the Rails form helpers. You can extend the form helpers to include the class name if it is not already included in the options.
Note: You will have to override each method in FormTagHelper that you want to augment. This only augments text_field_tag.
Add something like this to your ApplicationHelper:
module ApplicationHelper
module BootstrapExtension
FORM_CONTROL_CLASS = "form-control"
# Override the 'text_field_tag' method defined in FormTagHelper[1]
#
# [1] https://github.com/rails/rails/blob/master/actionview/lib/action_view/helpers/form_tag_helper.rb
def text_field_tag(name, value = nil, options = {})
class_name = options[:class]
if class_name.nil?
# Add 'form-control' as the only class if no class was provided
options[:class] = FORM_CONTROL_CLASS
else
# Add ' form-control' to the class if it doesn't already exist
options[:class] << " #{FORM_CONTROL_CLASS}" if
" #{class_name} ".index(" #{FORM_CONTROL_CLASS} ").nil?
end
# Call the original 'text_field_tag' method to do the real work
super
end
end
# Add the modified method to ApplicationHelper
include BootstrapExtension
end
To get the class added to all form elements, even if those form elements are generated by gems like simple_form, the modification has to be done on a higher-level class than the ApplicationController. The following snippet can be placed in an initializer to do just that:
require 'action_view/helpers/tags/base'
# Most input types need the form-control class on them. This is the easiest way to get that into every form input
module BootstrapTag
FORM_CONTROL_CLASS = 'form-control'
def tag(name, options, *)
options = add_bootstrap_class_to_options options, true if name.to_s == 'input'
super
end
private
def content_tag_string(name, content, options, *)
options = add_bootstrap_class_to_options options if name.to_s.in? %w(select textarea)
super
end
def add_bootstrap_class_to_options(options, check_type = false)
options = {} if options.nil?
options.stringify_keys!
if !check_type || options['type'].to_s.in?(%w(text password number email))
options['class'] = [] unless options.has_key? 'class'
options['class'] << FORM_CONTROL_CLASS if options['class'].is_a?(Array) && !options['class'].include?(FORM_CONTROL_CLASS)
options['class'] << " #{FORM_CONTROL_CLASS}" if options['class'].is_a?(String) && options['class'] !~ /\b#{FORM_CONTROL_CLASS}\b/
end
options
end
end
ActionView::Helpers::Tags::Base.send :include, BootstrapTag
ActionView::Base.send :include, BootstrapTag
Yes, it's a waste of time.
Use simple_form gem which integrate nicely with Bootstrap. You no longer need to write these.
After bundle, just run
rails generate simple_form:install --bootstrap
Then a simple_form initailizer will be added. You can further customize it in initializers/simple_form_bootstrap, though default is good enough.
All these helper classed will be generated automatically, as well as many other good stuff.
You can use one of the Bootstrap related Gems such as this one:
https://github.com/stouset/twitter_bootstrap_form_for
or this one:
https://github.com/sethvargo/bootstrap_forms
The previous two answers will indeed work very well (simple form using your own initializer or use the bootstrap form gems). As with anything in code, there are many ways to skin a cat. Another (more manual) way is to add a form helper of your own. The steps are basically:
Create a helper file such as app/helpers/custom_form_helper.rb
Inherit from the form builder class: CustomFormBuilder < ActionView::Helpers::FormBuilder
Create the look you want.
def text_field(label, *args)
options = args.extract_options!
new_class = options[:class] || "form-control"
super("dd", label, *(args << options.merge(:class => new_class)))
end
Call your helper method in application so you don not have to include the helper each time you call a form, like:
def custom_form_for(name, *args, &block)
options = args.extract_options!
content_tag("div",
content_tag("dl", form_for(name, *(args << options.merge(:builder => CustomFormBuilder)), &block)), :class => "standard_form")
end
Use the custom form in your forms as custom_form_for

Rails - default value in text_field but only for new_record?

On a Content model have an attribute named slug. When creating a new record, I want to use a helper to populate this field, but on an existing record I want to use the value from the database.
Currently I have:
<% if #content.new_record? %>
<%= f.text_field :slug, :value => "#{generate_slug(6)}" %>
<% else %>
<%= f.text_field :slug %>
<% end %>
But that seems a bit verbose. Is this the best way, or is there no other way? (Rails newb just trying to find the "Rails way" on issues I'm unsure of)
Edit
I should note that the helper is currently in /app/helpers/application_helper.rb Moved to be a private action in the Contents controller. David's answer worked great.
In your controller
#content.slug ||= generate_slug(6)
This will assign a value to the slug attribute if none is present
Then, in your view you can simply use
<%= f.text_field :slug %>
Options
Try after_initialize callback in your model.
Try creating a method in your model where you set defaults and call it in your new action in the controller. Also call this method if your create fails and you render new. Remember to set default only when no value exists by using the ||= operator.
Example to follow. I'm typing on phone!
I happen to use jQuery in my projects, so when I want some functionality like this, I usually use something like labelify. Then, I'd use something like <%= f.text_field :slug, :title => generate_slug(6) %>. (Hot tip, you don't need to put the #generate_slug call inside of a string if it returns something that will resolve to a string by itself, in fact it's more performant if you don't.)
If you don't want to go with jQuery approach, you might want to wrap this piece of logic in your model.
def Content < ActiveRecord::Base
def slug
self.new_record? ? self.slug_for_new_record : attributes[:slug]
end
private
def slug_for_new_record
# I don't know what you're doing in generate_slug, but it sounds model-
# related, so if so, put it here and not in a helper
end
end
If it really belongs in the view, still another option is to just make your Ruby a little bit more concise (you'll have to judge if this is more readable):
<%= f.text_field :slug, :value => (generate_slug(6) if #content.new_record?) %>
Don't forget the parens surrounding (generate_slug(6) if #content.new_record?). If you do, the if will be applied to the text_field, which is not what you want.
But there are still more ways to do it. The above line of code isn't great if your logic might change and you're pasting this code all over your rails project. When I wanted to add a 'required' class to my text fields but only if they were a new record (we had some legacy data that we didn't want to make people clean up), I created my own form builder with a required_field method that just called text_field and added a 'required' class if the item was a new record. This might seem like a work, but we have around 20 different forms, each with potentially multiple required fields, and it's a lot easier to change the business logic in one place. So if you really think this logic belongs in the view but you've got a ton of these lines of code and you don't want to have to change it in a million places, then FormBuilder is the way to go. I think this is in most cases prettier and more appropriate than a helper, but again, beauty is in the eye of the beholder. Here's my code somewhat adapted for your case:
# config/environment.rb
ActionView::Base.default_form_builder = NamespacesAreFun::FormBuilder
# lib/namespaces_are_fun/form_builder.rb
module NamespacesAreFun
class FormBuilder < ActionView::Helpers::FormBuilder
def slug_field(method, options = {})
opts = options.to_options
opts.merge!(:value => generate_slug) if self.object.new_record?
text_field(method, opts)
end
end
end
# views/.../your_view.html.erb
<%= f.slug_field :slug %>
Hopefully in all of these different approaches is one that fits your project.

Is there a way to disable default formtastic layout?

For one specific case I would like to render the form as a part of (for in-place editing). Is there a way in formtastic to disable the layout generated by .inputs / .buttons? Instead a
<fieldset> <ol> <li>
i would like simply to wrap the fields in the
<td>
Is there a build-in way or any solution to this problem?
There's no built in way (yet) in Formtastic to change the mark-up. Either use CSS to tweak the ample mark-up hooks in place, or ditch Formtastic for this form and code your own way (like we used to).
It's not yet supported, however you can use forked formtastic version:
https://github.com/linoj/formtastic
More details at:
http://www.vaporbase.com/postings/Replaceable_render_engines_for_Formtastic
Read on the formtastic forum that it might be even merge to origin someday.
In rails you can overrite the functions that define the tags that are used to render elements:
config/initializers/formtastic_foundation.rb:
# change required fields advice tag (abbr -> span)
Formtastic::FormBuilder.required_string =
proc { Formtastic::Util.html_safe(%{<span title="#{Formtastic::I18n.t(:required)}">*</span>}) }
module Formtastic
module Helpers
# change field wrapper (ol -> div)
module FieldsetWrapper
protected
def field_set_and_list_wrapping(*args, &block) #:nodoc:
contents = args.last.is_a?(::Hash) ? '' : args.pop.flatten
html_options = args.extract_options!
if block_given?
contents = if template.respond_to?(:is_haml?) && template.is_haml?
template.capture_haml(&block)
else
template.capture(&block)
end
end
contents = contents.join if contents.respond_to?(:join)
legend = field_set_legend(html_options)
fieldset = template.content_tag(:fieldset,
Formtastic::Util.html_safe(legend) << template.content_tag(:div, Formtastic::Util.html_safe(contents)),
html_options.except(:builder, :parent, :name)
)
fieldset
end
end
end
module Inputs
module Base
# change input wrapper tag (li.default_clases -> div.large-12.columns inside div.row)
module Wrapping
def input_wrapping(&block)
def super_wrapper_html_options
{:class => 'row'}
end
new_class = [wrapper_html_options[:class], "large-12 columns"].compact.join(" ")
template.content_tag(:div,
template.content_tag(:div,
[template.capture(&block), error_html, hint_html].join("\n").html_safe,
wrapper_html_options.merge(:class => new_class)),
super_wrapper_html_options)
end
end
end
end
end
I use this code to integrate Formtastic 3 with Foundation 5.4.5
I wrapped my call to the formtastic bit (in my haml file) in a string and then subbed out the
= "#{f.input ...}".gsub('<li class=', '<fart class=').html_safe #remove the li to align this input with the other text in the table.
It's a might bit easier than re-writing the form without formtastic, and it worked perfectly.
Admittedly it's a not an ideal solution. For a one off though... I can live with it.

Creating customized label fields in forms

I'd like to be able to generate the following markup:
<label for="field">Something <span class="hint">Field hint</span></label>
from the following code:
form_for ... do |f|
f.label :field, :hint => "Field hint"
end
So far I've created an initializer to store the custom functionality which re-opens ActionView::Helpers::FormBuilder and changes the label method, however I'm not sure what the best way to actually get the span into the text for the label. If I try to put the text in directly then rails, rightly so, escapes the content.
I'd quite like to use the existing label infrastructure as it has all the validation error support. This rules out using content_tag and generating it all myself (which would work, but doesn't seem... right).
Instead of changing the default builder, you should create a custom builder and pass it to the form with the :builder parameter.
class HintFormBuilder < ActionView::Helpers::FormBuilder
end
form_for #resource, :builder => HintFormBuilder do |f|
# ...
end
The Hint builder inherits all FormBuilder features, including validation, error messages and so on. Now, you should change what you need to change in order to customize the behavior.
This is a really raw draft.
class HintFormBuilder < ActionView::Helpers::FormBuilder
(%w(label)).each do |selector|
src = <<-end_src
def #{selector}(method, options = {})
hint = options.delete(:hint)
returning(super) do |element|
# replace here the value of element with hint
# if hint != nil
# remember to use gsub! and not gsub
end
end
end_src
class_eval src, __FILE__, __LINE__
end
end
EDIT based on the first comment:
It's always a good idea to not hack the Rails internals because you might need to use, now or in the future, plugins or features that rely on the original behavior. If you don't want to manually append the builder in your forms, you can create an helper.
def search_form_for(record_or_name_or_array, *args, &proc)
options = { :builder => HintFormBuilder }
form_for(record_or_name_or_array,
*(args << options),
&proc)
end
If you want to reopen the original class instead, I would suggest to create a new method. This solution also applies to the custom helper and has the benefit you can customize it without the need to gsub! the response. Yes, gsub! is the common way to do so because when extending the original methods you only have access to the method/options and the result, no the value (that is injected by the #object variable).
class ActionView::Helpers::FormBuilder
def label_with_hint(method, text = nil, options = {})
hint = options.delete(:hint)
# do your own customizations...
#template.label(#object_name, method, text, objectify_options(options))
end
end
EDIT: I was mistaken, you can pass a custom text as a parameter so you don't need to gsub! the returned string. I got confused by the text_field tag.
At this point, you can use either the first (subclassing with/without custom method), second (hacking internals) or third option (hacking internals with custom method) and intercept the text value before it is sent to #template.label.
Also note that text can be nil. If nil, the value is automatically generated from method. You should be aware of this.
Here's what I would have done.
# config/initializers/[anything].rb
ActionView::Base.default_form_builder = CustomFormBuilder
# lib/custom_form_builder.rb
class CustomFormBuilder < ActionView::Helpers::FormBuilder
def label(field, text, options = {})
if options[:hint]
hint = #template.content_tag(:span, options[:hint], :class => "hint")
super(field, "#{field.to_s.humanize} #{hint}", options)
else
super
end
end
end

Resources