Rails 6 create custom formtastic input based on existing one - ruby-on-rails

Inside of my ActiveAdmin Rails6 app I've got below partial which replaces the standard input by editor.js (there is some JS magic behind, not relevant to this question). The partial and render look like below:
# _editor.html.erb
<%= f.input field, as: :hidden, input_html: { id: :editor_field } %>
<div style='width: 100%; background: white;' id="editorjs"></div>
# example of a parital call for field :body
<%= render 'admin/editor_js', f: f, field: :body %>
Because ActiveAdmin is based on formatic gem instead of this partial I want to create and use custom input based on :text field. I was trying to do something like below.
module CustomInputs
class EditorJsInput < Formtastic::Inputs::TextInput
def input_html_options
super.merge(input_html: { id: 'editor_field' }).merge(as: :hidden)
end
end
end
(as: :hidden) is not working and no idea how to add this empty div at the end <div style='width: 100%; background: white;' id="editorjs"></div> which is quite crucial.

as: :hidden is not an input_html_option, it's an input style/type that maps the input to the Inputs::HiddenInput type; all it really does is render the input field as a hidden list item. Additionally, the way you're overriding input_html_options is not correct:
# Let's say this is a line in your form input:
input_html: { value: 'Eat plants.' }
# Your code is changing `input_html_options` to be the same as if you'd
# included this in your form input declaration, which doesn't make sense:
input_html: {
value: 'Eat plants.',
input_html: { id: 'editor_field' },
as: :hidden
}
Please review the docu and source on the methods you are trying to override:
Formtastic::Inputs::HiddenInput
Formtastic::Inputs::TextInput
An overridden input_html_options method would be something like this if you're looking to override the existing ID:
# Don't recommend
def input_html_options
# Note: this is dangerous because the 'id' attribute should be unique
# and merging it here instead of passing it in the field's `input_html`
# hash forces every input of this type to have the same id
super.merge(id: 'editor_field')
end
Presuming that you're really looking to reliably know the ID so some JS can find the element and swap it out, you could either specify it in the input_html hash when declaring your form input or, more cleanly, simply use the autogenerated ID. Generally the ID is the the underscore-separated form object root key + the attribute name; so if your form object is coffee: { name: 'Goblin Sludge', origin_country: 'Columbia' }, I believe the default is for f.input :name to render with id='coffee_name' and f.input :origin_country to render with id='coffee_origin_country'. This is stuff that you can easily figure out by using the devtools inspector on the rendered form.
It seems like you really are looking to override to_html. You need something like this:
# app/input/editor_js_input.rb
class EditorJsInput < Formtastic::Inputs::TextInput
def to_html
input_wrapping do
builder.hidden_field(method, input_html_options)
end
end
end
# Variation that creates a div
class EditorJsInput < Formtastic::Inputs::TextInput
def to_html
input_wrapping do
builder.content_tag(
:div,
'',
style: 'width: 100%; background: white;',
id: "#{input_html_options[:id]}_editor"
) << builder.hidden_field(method, input_html_options)
end
end
end
# Example of what this would generate:
# "<div style=\"width: 100%; background: white;\" id=\"coffee_name_editor\"></div><input maxlength=\"255\" id=\"coffee_name\" value=\"\" type=\"hidden\" name=\"coffee[name]\" />"
# PARTIAL
# This will render the div you have above:
div style: 'width: 100%; background: white;', id: 'editorjs'
# This (or a similar variant of form declaration) will render the form
active_admin_form_for resource do |f|
f.inputs do
f.input :name,
input_html: { value: f.object.name },
as: :editor_js
f.input :origin_country,
input_html: { value: f.object.origin_country },
as: :editor_js
end
end
This should build the input for f.object.name as something like <input id="coffee_name" type="hidden" value="Goblin Sludge" name="coffee[name]"> (see FormBuilder#hidden_field
Additional thoughts:
Try using something like Pry to put a binding inside these methods to get a better understanding of what they look like and how they work. Note that you will need to reboot your server for it to load changes to overridden methods in custom input classes.
Read the documentation referenced above and the Formtastic README; there is a lot of really accessible info that would have helped you here. You could also have learned how to render a div from ActiveAdmin's docu here (as well as other pages at activeadmin.info that give examples of styled elements)
Evaluate whether you need to implement a Custom FormBuilder

Related

I18n translated values do not show up if changed from helper method rails

Hello not sure how to explain this but i will give it my best shot.
I hv a model like this
class Answer < ActiveRecord::Base
EMPLOYMENT_TYPE = [
['Employed', I18n.t('self_evaluation.self_evaluation_form.employment_status.employed')],
['Self-Employed', I18n.t('self_evaluation.self_evaluation_form.employment_status.self_employed')],
['Unemployed / Retired', I18n.t('self_evaluation.self_evaluation_form.employment_status.unemployed')]
]
end
I also have a helper method that calls this
module AnswerHelper
def get_employment_type_list
return Answer::EMPLOYMENT_TYPE
end
end
And a partial view that should display the values like this
_step_1.html.erb
<% employment_list = get_employment_type_list %>
<%= employment_list %>
<div class="flex_center flex_column button-wrap">
<%= collection_radio_buttons("application[answer_attributes]", :employment_type, employment_list, :first, :last, item_wrapper_tag: false, checked: #selected_employment_type) do |b|
b.label(class: 'employment_type_radio') { b.radio_button(:onchange => "return_employment_type(value)", class:'radio-label') + b.label(class:'button-label'){b.text}}
end %>
</div>
_step_2.html.erb
<% employer_list = get_employer_list(chosen_employment_type(#decoded_self_evaluation)) %>
<% employer_type = #decoded_self_evaluation.try(:loan_application).try(:kyc_answer).try(:employer_type) %>
<div class="flex_center flex_column button-wrap">
<%= collection_radio_buttons("loan_application[kyc_answer_attributes]", :employer_type, employer_list, :first, :last, item_wrapper_tag: false, checked: employer_type) do |b|
b.label(class: 'employer_type_radio') { b.radio_button(:onchange => "is_business_registered(value)", class:'radio-label') + b.label(class:'button-label'){b.text}}
end %>
</div>
identity.js
$.ajax({
url:'/self_evaluations/' + self_evaluation_id + '/employment_type',
method:"POST",
data:JSON.stringify({employment_type: employment_type, key: $('#token_key').val()}),
contentType:"application/json; charset=utf-8",
dataType:"script",
async: false,
success: function(data) {
$valid = true;
}
})
employment_list.js.erb
$('#id_employer').empty();
$('#id_employer').append("<%=j render 'self_evaluations/self_evaluation_partials/step_2' %>");
Now the problem is that i have already added all that's needed for I18n translations to happen in Rails and for texts that are translated on the html view it works correctly when i switch from the different languages but for texts that are been gotten via a helper method like this. Based on initial help, on initial load, all page work fine but if i change the language and then run an ajax call that loads the employment_list.js.erb my locale seems to still be the primary language the the values do not change accordingly
Not sure why this is happening anyone faced something like this before?
Because you declared the translations as a constant in your model, so the translations were loaded just one time when Answer model was loaded in the first time.
In production environment, the model will be loaded after starting server. In development environment, the model will be loaded when the file content is changed, ...
In order to solve your issue, you can add the translations in your helper instead:
module AnswerHelper
def get_employment_type_list
[
['Employed', I18n.t('self_evaluation.self_evaluation_form.employment_status.employed')],
['Self-Employed', I18n.t('self_evaluation.self_evaluation_form.employment_status.self_employed')],
['Unemployed / Retired', I18n.t('self_evaluation.self_evaluation_form.employment_status.unemployed')]
]
end
end
I usually use this approach, please have a look!
class Answer < ActiveRecord::Base
EMPLOYMENT_TYPE = [:employed, :self_employed, :unemployed_or_retired]
end
module AnswerHelper
def get_employment_type_list
i18n_scope = 'self_evaluation.self_evaluation_form.employment_status'
Answer::EMPLOYMENT_TYPE.map do |type|
[type, t(type, scope: i18n_scope)]
end
end
end

Formatting credit card number in a number_field_tag [duplicate]

I would like to make editing form fields as user-friendly as possible. For example, for numeric values, I would like the field to be displayed with commas (like number_with_precision).
This is easy enough on the display side, but what about editing? Is there a good way to do this?
I am using the Rails FormBuilder. Upon investigation, I found that it uses InstanceTag, which gets the values for fields by using <attribute>_value_before_type_cast which means overriding <attribute> won't get called.
The best I have come up with so far is something like this:
<%= f.text_field :my_attribute, :value => number_with_precision(f.object.my_attribute) %>
Or my_attribute could return the formatted value, like this:
def my_attribute
ApplicationController.helpers.number_with_precision(read_attribute(:my_attribute))
end
But you still have to use :value
<%= f.text_field :my_attribute, :value => f.object.my_attribute %>
This seems like a lot of work.
I prefer your first answer, with the formatting being done in the view. However, if you want to perform the formatting in the model, you can use wrapper methods for the getter and setter, and avoid having to use the :value option entirely.
You'd end up with something like this.
def my_attribute_string
foo_formatter(myattribute)
end
def my_attribute_string=(s)
# Parse "s" or do whatever you need to with it, then set your real attribute.
end
<%= f.text_field :my_attribute_string %>
Railscasts covered this with a Time object in a text_field in episode #32. The really clever part of this is how they handle validation errors. It's worth watching the episode for that alone.
This is an old question, but in case anyone comes across this you could use the number_to_X helpers. They have all of the attributes you could ever want for displaying your edit value:
<%= f.text_field :my_number, :value => number_to_human(f.object.my_number, :separator => '', :unit => '', :delimiter => '', :precision => 0) %>
There are more attributes available too: http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html
If you want a format to be created or maintained during editing, you will need to add Javascript to implement "masks." Here is a demo.
It was the first hit in these results.
You can use the number_format plugin. By specifying a number_format for an existing numeric attribute inside your model, the attribute will now appear as formatted to Rails in all forms and views. It will also be parsed back from that format (when assigned via forms) prior to insertion into the database. (The plugin also creates purely numeric unformatted_<attribute-name> accessors which can continue to be used for arithmetic, or for direct numerical assignment or retrieval by you for seamless integration.)
class MyModel < ActiveRecord::Base
# this model has the balance attribute, which we
# want to display using formatting in views,
# although it is stored as a numeric in the database
number_format :balance,
:precision => 2,
:delimiter => ',',
:strip_trailing_zeros => false
def increment_balance
unformatted_balance += 10
end
You can also combine the above with a Javascript solution, which can force the user to maintain the decimal point and thousands separators in place while editing, although this is really not necessary.
I have done something similar. We format times and lengths using a custom form builder. It makes use of the existing text_field, but wraps it so the value can be customized:
class SuperFormBuilder < ActionView::Helpers::FormBuilder
include ApplicationHelper
include FormHelper
include ActionView::Helpers::TagHelper
include ActionView::Helpers::FormTagHelper
def length_field(label,*args)
scale = 'medium'
args.each do |v|
if v.has_key?(:scale)
scale = v[:scale]
v.delete(:scale)
end
end
value = length_conversion(#object.send(label.to_sym),scale)
options = (args.length > 0) ? args.pop : {}
return has_error(label, text_field_tag(field_name(label),value,*args) + ' ' + length_unit(scale))
end
private
def field_name(label)
return #object_name + "[#{label}]"
end
def has_error(label, output)
return "<div class='fieldWithErrors'>#{output}</div>" if #object.errors[label]
return output
end
And it is used like this:
<%= form_for( #section, {:action => 'save', :id => #section.id}, :builder => SuperFormBuilder) do |sf| %>
<%= sf.length_field :feed_size_min_w, :size => 3, :scale => 'small' %>
<% end %>
The end result is a value in the appropriate unit based off their choice on system (Metric, Imperial) and scale IE small = inches or millimeters.
I basically copied the text_field method from the existing form builder, which uses the text_field_tag itself.
There are two gotchas: 1) Knowing the name of the object field and how to access the object to get the value which you want to format. 2) Getting the name right so when the form is submitted it is part of the correct params hash.
The form builder is given a class variable #object. You can get the value of the field using the .send method. In my case I send the label :feed_size_min_w to the #object and get its length back. I then convert it to my desired format, and give it to the text_field_tag.
The name of the field is key to having it end up in the params hash, in my instance the params[:sections] one. I made a little helper function called field_name that takes care of this.
Finally the has_error wraps the field in an error div if there are errors on that label.
I needed "nicer" format on some specified text fields, resolved it by adding this to my initializers. Seems to work nicely on Rails ~= 5.2 and it should be easy to customize.
class ActionView::Helpers::Tags::TextField
private
def value_before_type_cast # override method in ActionView::Helpers::Tags::Base
v = super
# format as you like, when you like
if #options.delete(:nice_decimal)
v = v.to_s.gsub('.', ',') if v.is_a?(BigDecimal)
end
v
end
end
Usage in form f
<%= f.text_field :foo, nice_decimal: true %>

how to embed raw html in active_admin formtastic

I'm trying to build a form, with formtastic, inside an active_admin model.
The problem is I need a certain script tag and other raw HTML stuff directly inside or around the form.
I'm doing it with the normal form block:
form do |f|
f.inputs :name => "User Details", :for => :user do |user_form|
user_form.input :first_name, :required => true
...
How do I embed a simple div tag right in between?
Or even a script tag?
I thought about using a render :partial, but I want to know if the above method is possible first. Thanks!
You can insert a div or javascript like this:
f.form_buffers.last << content_tag(:div, "Div content here")
f.form_buffers.last << javascript_tag("alert('hi');")
In the current ActiveAdmin the form_buffers is deprecated. Instead it's possible to do the following:
insert_tag(Arbre::HTML::Div) { content_tag(:span, "foo") }
Active admin created a DSL on top of Formtastic according to their docs
https://github.com/activeadmin/activeadmin/blob/master/docs/5-forms.md
So you can now do:
form do |f|
f.semantic_errors(*f.object.errors.keys)
import_errors = self.controller.instance_variable_get("#errors")
if import_errors.present?
ul class: 'errors' do
import_errors.each do |e|
li e
end
end
end
# ...
end

Ruby on Rails: Submitting an array in a form

I have a model that has an attribute that is an Array. What's the proper way for me to populate that attribute from a form submission?
I know having a form input with a field whose name includes brackets creates a hash from the input. Should I just be taking that and stepping through it in the controller to massage it into an array?
Example to make it less abstract:
class Article
serialize :links, Array
end
The links variable takes the form of a an array of URLs, i.e. [["http://www.google.com"], ["http://stackoverflow.com"]]
When I use something like the following in my form, it creates a hash:
<%= hidden_field_tag "article[links][#{url}]", :track, :value => nil %>
The resultant hash looks like this:
"links" => {"http://www.google.com" => "", "http://stackoverflow.com" => ""}
If I don't include the url in the name of the link, additional values clobber each other:
<%= hidden_field_tag "article[links]", :track, :value => url %>
The result looks like this: "links" => "http://stackoverflow.com"
If your html form has input fields with empty square brackets, then they will be turned into an array inside params in the controller.
# Eg multiple input fields all with the same name:
<input type="textbox" name="course[track_codes][]" ...>
# will become the Array
params["course"]["track_codes"]
# with an element for each of the input fields with the same name
Added:
Note that the rails helpers are not setup to do the array trick auto-magically. So you may have to create the name attributes manually. Also, checkboxes have their own issues if using the rails helpers since the checkbox helpers create additional hidden fields to handle the unchecked case.
= simple_form_for #article do |f|
= f.input_field :name, multiple: true
= f.input_field :name, multiple: true
= f.submit
TL;DR version of HTML [] convention:
Array:
<input type="textbox" name="course[track_codes][]", value="a">
<input type="textbox" name="course[track_codes][]", value="b">
<input type="textbox" name="course[track_codes][]", value="c">
Params received:
{ course: { track_codes: ['a', 'b', 'c'] } }
Hash
<input type="textbox" name="course[track_codes][x]", value="a">
<input type="textbox" name="course[track_codes][y]", value="b">
<input type="textbox" name="course[track_codes][z]", value="c">
Params received:
{ course: { track_codes: { x: 'a', y: 'b', z: 'c' } }
I've also found out that if pass your input helper like this you will get an array of courses each one with its own attributes.
# Eg multiple input fields all with the same name:
<input type="textbox" name="course[][track_codes]" ...>
# will become the Array
params["course"]
# where you can get the values of all your attributes like this:
params["course"].each do |course|
course["track_codes"]
end
I just set up a solution using jquery taginput:
http://xoxco.com/projects/code/tagsinput/
I wrote a custom simple_form extension
# for use with: http://xoxco.com/projects/code/tagsinput/
class TagInput < SimpleForm::Inputs::Base
def input
#builder.text_field(attribute_name, input_html_options.merge(value: object.value.join(',')))
end
end
A coffeescrpt snippet:
$('input.tag').tagsInput()
And a tweak to my controller, which sadly has to be slightly specific:
#user = User.find(params[:id])
attrs = params[:user]
if #user.some_field.is_a? Array
attrs[:some_field] = attrs[:some_field].split(',')
end
I had a similar issue, but wanted to let the user input a series of comma separated elements as the value for the array.
My migration uses rails new ability (or is it postrges' new ability?) to have an array as the column type
add_column :articles, :links, :string, array: true, default: []
the form can then take this input
<%= text_field_tag "article[links][]", #article.links %>
and it means the controller can operate pretty smoothly as follows
def create
split_links
Article.create(article_params)
end
private
def split_links
params[:article][:links] = params[:article][:links].first.split(",").map(&:strip)
end
params.require(:article).permit(links: [])
Now the user can input as many links as they like, and the form behaves properly on both create and update. And I can still use the strong params.
For those who use simple form, you may consider this solution. Basically need to set up your own input and use it as :array. Then you would need to handle input in your controller level.
#inside lib/utitilies
class ArrayInput < SimpleForm::Inputs::Base
def input
#builder.text_field(attribute_name, input_html_options.merge!({value: object.premium_keyword.join(',')}))
end
end
#inside view/_form
...
= f.input :premium_keyword, as: :array, label: 'Premium Keyword (case insensitive, comma seperated)'
#inside controller
def update
pkw = params[:restaurant][:premium_keyword]
if pkw.present?
pkw = pkw.split(", ")
params[:restaurant][:premium_keyword] = pkw
end
if #restaurant.update_attributes(params[:restaurant])
redirect_to admin_city_restaurants_path, flash: { success: "You have successfully edited a restaurant"}
else
render :edit
end
end
In your case just change :premium_keyword to the your array field
I had some trouble editing the array after implementing this for my new.html.erb, so I'll drop my solution to that problem here:
Edit a model property of type array with Rails form?

How can I format the value shown in a Rails edit field?

I would like to make editing form fields as user-friendly as possible. For example, for numeric values, I would like the field to be displayed with commas (like number_with_precision).
This is easy enough on the display side, but what about editing? Is there a good way to do this?
I am using the Rails FormBuilder. Upon investigation, I found that it uses InstanceTag, which gets the values for fields by using <attribute>_value_before_type_cast which means overriding <attribute> won't get called.
The best I have come up with so far is something like this:
<%= f.text_field :my_attribute, :value => number_with_precision(f.object.my_attribute) %>
Or my_attribute could return the formatted value, like this:
def my_attribute
ApplicationController.helpers.number_with_precision(read_attribute(:my_attribute))
end
But you still have to use :value
<%= f.text_field :my_attribute, :value => f.object.my_attribute %>
This seems like a lot of work.
I prefer your first answer, with the formatting being done in the view. However, if you want to perform the formatting in the model, you can use wrapper methods for the getter and setter, and avoid having to use the :value option entirely.
You'd end up with something like this.
def my_attribute_string
foo_formatter(myattribute)
end
def my_attribute_string=(s)
# Parse "s" or do whatever you need to with it, then set your real attribute.
end
<%= f.text_field :my_attribute_string %>
Railscasts covered this with a Time object in a text_field in episode #32. The really clever part of this is how they handle validation errors. It's worth watching the episode for that alone.
This is an old question, but in case anyone comes across this you could use the number_to_X helpers. They have all of the attributes you could ever want for displaying your edit value:
<%= f.text_field :my_number, :value => number_to_human(f.object.my_number, :separator => '', :unit => '', :delimiter => '', :precision => 0) %>
There are more attributes available too: http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html
If you want a format to be created or maintained during editing, you will need to add Javascript to implement "masks." Here is a demo.
It was the first hit in these results.
You can use the number_format plugin. By specifying a number_format for an existing numeric attribute inside your model, the attribute will now appear as formatted to Rails in all forms and views. It will also be parsed back from that format (when assigned via forms) prior to insertion into the database. (The plugin also creates purely numeric unformatted_<attribute-name> accessors which can continue to be used for arithmetic, or for direct numerical assignment or retrieval by you for seamless integration.)
class MyModel < ActiveRecord::Base
# this model has the balance attribute, which we
# want to display using formatting in views,
# although it is stored as a numeric in the database
number_format :balance,
:precision => 2,
:delimiter => ',',
:strip_trailing_zeros => false
def increment_balance
unformatted_balance += 10
end
You can also combine the above with a Javascript solution, which can force the user to maintain the decimal point and thousands separators in place while editing, although this is really not necessary.
I have done something similar. We format times and lengths using a custom form builder. It makes use of the existing text_field, but wraps it so the value can be customized:
class SuperFormBuilder < ActionView::Helpers::FormBuilder
include ApplicationHelper
include FormHelper
include ActionView::Helpers::TagHelper
include ActionView::Helpers::FormTagHelper
def length_field(label,*args)
scale = 'medium'
args.each do |v|
if v.has_key?(:scale)
scale = v[:scale]
v.delete(:scale)
end
end
value = length_conversion(#object.send(label.to_sym),scale)
options = (args.length > 0) ? args.pop : {}
return has_error(label, text_field_tag(field_name(label),value,*args) + ' ' + length_unit(scale))
end
private
def field_name(label)
return #object_name + "[#{label}]"
end
def has_error(label, output)
return "<div class='fieldWithErrors'>#{output}</div>" if #object.errors[label]
return output
end
And it is used like this:
<%= form_for( #section, {:action => 'save', :id => #section.id}, :builder => SuperFormBuilder) do |sf| %>
<%= sf.length_field :feed_size_min_w, :size => 3, :scale => 'small' %>
<% end %>
The end result is a value in the appropriate unit based off their choice on system (Metric, Imperial) and scale IE small = inches or millimeters.
I basically copied the text_field method from the existing form builder, which uses the text_field_tag itself.
There are two gotchas: 1) Knowing the name of the object field and how to access the object to get the value which you want to format. 2) Getting the name right so when the form is submitted it is part of the correct params hash.
The form builder is given a class variable #object. You can get the value of the field using the .send method. In my case I send the label :feed_size_min_w to the #object and get its length back. I then convert it to my desired format, and give it to the text_field_tag.
The name of the field is key to having it end up in the params hash, in my instance the params[:sections] one. I made a little helper function called field_name that takes care of this.
Finally the has_error wraps the field in an error div if there are errors on that label.
I needed "nicer" format on some specified text fields, resolved it by adding this to my initializers. Seems to work nicely on Rails ~= 5.2 and it should be easy to customize.
class ActionView::Helpers::Tags::TextField
private
def value_before_type_cast # override method in ActionView::Helpers::Tags::Base
v = super
# format as you like, when you like
if #options.delete(:nice_decimal)
v = v.to_s.gsub('.', ',') if v.is_a?(BigDecimal)
end
v
end
end
Usage in form f
<%= f.text_field :foo, nice_decimal: true %>

Resources