When using the form_for
helper and a text_field
call, Ruby on Rails will generate a unique id for the <input />
element that it outputs. How can I generate the same id for later inclusion into JavaScript generated later?
<%= form_for @user do |f| %>
<%= f.text_field :username %>
<% end %>
Then later in the page:
<%= javascript_tag do %>
$('<%= id of the :username field %>').doSomethingReallyCool();
<开发者_高级运维% end %>
I ended up creating a custom form builder to expose the property directly
class FormBuilder < ActionView::Helpers::FormBuilder
def id_for(method, options={})
InstanceTag.new( object_name, method, self, object ) \
.id_for( options )
end
end
class InstanceTag < ActionView::Helpers::InstanceTag
def id_for( options )
add_default_name_and_id(options)
options['id']
end
end
Then set the default form builder
ActionView::Base.default_form_builder = FormBuilder
Look at the form builder options:
<%= form_for @user do |f| %>
<% form_css_id = "#" + f.options[:html][:id] %>
<% end %>
Options should at least include the following data: css class, id, http method and authenticity token.
In case someone has a FormBuilder object from a fields_for
block, it is possible to get its id
using this snippet:
<%= form.fields_for :something do |fields_form| %>
<%= fields_form.object_name.gsub(/[\[\]]+/, '_').chop %>id
<% end %>
FieldsForm#object_name
returns the field's ID as something like this: user[address][0]
. Next, the regex substitution changes groups of one or more brackets to underscores. This substitution leaves a trailing underscore, to which it appends the letters id
. For the example provided before, this results in user_address_0_id
.
In Rails 4.1 I was able to get the id with this:
ActionView::Base::Tags::Base.new(
f.object_name,
:username,
:this_param_is_ignored,
).send(:tag_id)
It works with nested forms (fields_for
).
You'll want to specify the id yourself. Here generating the id from the object_id of the form guarantees that it won't conflict with another text_field for username in the case of nested forms.
<%= form_for @user do |f| %>
<%- id = "username_#{f.object_id}" %>
<%= f.text_field :username, :id=>id %>
<% end %>
<%= javascript_tag do %>
$("##{id}").doSomethingReallyCool();
<% end %>
At least in Rails 7 there is a method called field_id(user, :password)
which returns user_password
which is the id the input has.
Since your Javascript code is placed in a ruby embedded file (.erb) that is being interpreted by the server before being sent to the client browser, you can generated the required JS variable with a ruby snippet like:
var id='#id_for_'+<%= @user.username %>
var id='#comment_info_'+<%= n %>
and then use it simply as $(id).doSomethingReallyCool();
.
If you need to control the value of the id generated by the form in first instance, you can do it passing the id in the html hash options:
f.text_field :username, id:"id_for_#{…}"
If you're using a unobtrusive JS approach, you would like to place the JS code into a partial (.js.erb) and have the controller use it if the user client allows JS. E.g.:
class FooController < ApplicationController
def foo_doo
… # some logic
respond_to do |format|
format.js # this will render javascript in file “foo_doo.js.erb” if allowed
format.html { … } # html fallback for unobtrusive javascript
end
end
This is the recommended approach nowadays. The problem now is that you need to pass the variables to your JS code. If these variables came from a form, place them in opportune hidden form fields. And then use the render's locals
option to have them available by your partial, as in:
format.js { render 'foo_doo', locals: {n:n} }
If the JS code is updating a template that use some variables, use the same approach:
var id='#comment_info_'+<%= n %>
$(id).replaceWith("<%= j (render partial:…, locals: {…}) %>")
I don't really like my own solution that much, but I tried not to go and patch InstanceTag.
Unfortunately that meant lifting the sanitization code from ActionView::Helpers::InstanceTagMethods
class MyCoolFormBuilder < ActionView::Helpers::FormBuilder
def sanitized_object_name
@sanitized_object_name ||= object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
end
def field_id_for(method)
"#{sanitized_object_name}_#{method.to_s.sub(/\?$/,"")}"
end
end
I assume that there are multiple form_for
in the page and each has it's own submit button. I think this is how I would do this:
Have a hidden field in the the form_for and set it's value to the id of the input field. Here I've chosen input_#{n}
as the id of the input field. Of course I'll define the id for each input.
<%= form_for @user do |f| %>
<%= f.text_field :username, id: "input_#{n}" %>
<%= hidden_field_tag 'user[input_id]', value: "input_#{n}" %>
<%= f.submit %>
<% end %>
Then on submit I can get the id the form input in my params params[:user][:input_id]
which I can pass to the js.erb
using the locals
.
Hope this helps :)
精彩评论