开发者

How do you get the Ruby on Rails generated id of a form element for reference in JavaScript?

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 :)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜