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.
Related
I'm currently building a ruby on rails app using HAML as the template language. I'm looking to create a condition that defines a tag dependent on whether it is met or not, otherwise it defines a different tag. I know I could write this like:
- if ordered
%ol
- else
%ul
But this isn't particularly DRY, and would require me to duplicate the bulk of the code. Is there a very straightforward way to get around this problem? Should I be looking into ruby logic to find it?
Thanks
Define a helper. We'll introduce the ordered option to pick the tag, the rest are passed on to the tag.
# app/helpers/application_helper.rb
module ApplicationHelper
def list_tag(ordered: false, **opts)
kind = ordered ? :ol : :ul
haml_tag kind, **opts do
yield
end
end
end
And then,
-# some_view.html.haml
%p
Here's a list:
- list_tag ordered: false, class: 'some_class' do
- #list.each do |item|
%li
= item
If you need to do this logic in different views I think there are two approaches you can follow:
1. Make a partial and render it where you need this. If you need to pass variables use local_assigns
_my_list.html.haml
- if ordered
%ol
- else
%ul
use it
render 'partials/my_list', ordered: ordered
2. Make your own helper
def my_list(ordered)
if ordered
content_tag(:ol, class: 'my-class') do
# more logic here
# use concat if you need to use more html blocks
end else
content_tag(:ul, class: 'my-class') do
# more logic here
# use concat if you need to use more html blocks
end
end
end
use it
= my_list(ordered)
You can keep your ordered variable outside of the view and deal with the whole logic inside the helper.
If you ask yourself what to use, well the first answer from here is pretty good.
You can use the content_tag method as follows. content_tag documentation
= content_tag(ordered ? "ol" : "ul")
If you need it in several occasions you can put that in a helper method
module Listhelper
def list(ordered, html_options = {})
= content_tag(ordered ? "ol" : "ul")
end
end
Call it from the view with = list(your_variables)
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
Is there a way that I can create a custom form helper so that instead of:
special_field_tag :object, :method
I can achieve something like:
form.special_field :method
Yes, you can add to the FormBuilder class and get access to the object passed into the form_for. I've done this for a lot of things: dates, times, measurements, etc. Heres an example:
class ActionView::Helpers::FormBuilder
include ActionView::Helpers::TagHelper
include ActionView::Helpers::FormTagHelper
include ActionView::Helpers::FormOptionsHelper
include ActionView::Helpers::CaptureHelper
include ActionView::Helpers::AssetTagHelper
# Accepts an int and displays a smiley based on >, <, or = 0
def smile_tag(method, options = {})
value = #object.nil? ? 0 : #object.send(method).to_i
options[:id] = field_id(method,options[:index])
smiley = ":-|"
if value > 0
smiley = ":-)"
elsif smiley < 0
smiley = ":-("
end
return text_field_tag(field_name(method,options[:index]),options) + smiley
end
def field_name(label,index=nil)
output = index ? "[#{index}]" : ''
return #object_name + output + "[#{label}]"
end
def field_id(label,index=nil)
output = index ? "_#{index}" : ''
return #object_name + output + "_#{label}"
end
end
Which you can use like this:
<% form_for #quiz do |f| %>
<%= f.smile_tag(:score) %>
<% end %>
There are some instance variables created by Rails that you can access in these helper methods:
#object - the model object specified by the form
#object_name - the class name of the object
#template - I think its an instance of the ActionView, you can possibly bypass all the includes I added by calling methods on the template. Haven't tried that yet.
#options - options passed to the FormBuilder when its created by the form_for call
I wrote the field_id and field_name methods to create these attributes on the HTML input elements the same way the regular helpers do, I'm sure there is a way to tie into the same methods that Rails uses, but I haven't found it yet.
The sky is the limit on what you can do with these helper methods, they simply return strings. You can create entire HTML tables or pages in one, but you better have a good reason to.
This file should be added in the app/helpers folder
#Tilendor, thanks so much for the pointers. Here is an example of an enum_select form tag helper that uses Rails 4.1 enums to automatically populate the options of a select tag:
# helpers/application_helper.rb
module ApplicationHelper
class ActionView::Helpers::FormBuilder
# http://stackoverflow.com/a/2625727/1935918
include ActionView::Helpers::FormTagHelper
include ActionView::Helpers::FormOptionsHelper
def enum_select(name, options = {})
# select_tag "company[time_zone]", options_for_select(Company.time_zones
# .map { |value| [value[0].titleize, value[0]] }, selected: company.time_zone)
select_tag #object_name + "[#{name}]", options_for_select(#object.class.send(name.to_s.pluralize)
.map { |value| [value[0].titleize, value[0]] }, selected: #object.send(name))
end
end
end
The trickiest construct is #object.class.send(name.to_s.pluralize) which produces a hash of available values (e.g., Company.time_zones). Putting it in helpers/application_helper.rb makes it automatically available. It is used like:
<%= f.label :billing_status %>:
<%= f.enum_select :billing_status %><br />
Our app was displaying phone numbers in text fields, and we wanted to omit the country code for domestic numbers. I was using form helpers. After reading this and rails source a bit, i came to this solution:
class ActionView::Helpers::FormBuilder
def phone_text_field name, options = {}
value = object.public_send(name).to_s
if value.start_with? "9" # national number
value = value[1..-1]
options["value"] = value
end
text_field name, options
end
end
I put this on app/helpers/application_helper.rb and use it like i use text_field() helper. Hope this helps someone.
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
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