0

I currently have a form nested in a do/end block, so for every object that's created, it'll have its' own dropdown menu:

<% @shows.each do |show| %>  
  <%= form_for show, :url => show_record_path(:show_id => show.id), method: :post do |f| %>
    <%= f.select :box_id, Box.all.collect { |p| [ p.box_number, p.id ] }, class: "show-radio-button", :include_blank => true %>
  <%= f.submit "Record", data: { disable_with: "Please wait.." }, :disabled => true, class: "record-button" %>
<% end %>

And here is my jQuery that enables the submit button if an option from the dropdown has been selected:

$('#show_box_id').on('keyup change', function(){
  if ($('#show_box_id').val() == '' ) {
    $('.record-button').prop('disabled', true);
    $(".record-button").css("background-color", "");
  } else {
    $('.record-button').prop('disabled', false);
    $(".record-button").css("background-color", "#008C00");
  }
});

The problem is that the jQuery only works on the first object created in the do/end block (so if I select something from a dropdown that's not in the first object, nothing happens), and if I select something from the dropdown in the first object, then the jQuery runs on all of them.

How can I make it so that the jQuery code works on each object created individually with no effect on the others?

1 Answer 1

1

I think what's happening is that you're relying on the default id generated by the form helper to use with the JQuery selector. This is confusing as all elements have the same ID... And an ID is supposed to be unique to a page. Your markup will be invalid and JQuery may behave strangely, assuming a unique ID so only hitting the first.

If I could see the generated markup I could be more sure about this.

Your handler acts on a class shared by all forms, so will act on all forms.

Something along the lines of:

<%= f.select :box_id, Box.all.collect { |p| [ p.box_number, p.id ] }, class: "show-radio-button", id: "show_box_id_#{show.id}", :include_blank => true %>

will fix the ID. Then in JS you can try something resembling this:

$('.show_radio_button').on('keyup change', function(e){
  var $this = $(this);
  var $form = $this.closest('form');
  var $button = $form.find(".record_button");
  if ($this.val() == '' ) {
    $button.prop('disabled', true);
    $button.css("background-color", "");
  } else {
    $button.prop('disabled', false);
    $button.css("background-color", "#008C00");
  }
});

It is more efficient to do a JQuery selector look-up once and store it in a local variable than it is to keep doing it. Also, if dealing with elements that share classes, you need to be specific about exactly the element you want to target. Hence, '$this' (the clicked-on element) is used to do a 'closest' look-up (looking up the tree) for the nearest form. Then this is used to find the button you're interested in.

You could avoid some look-ups and thus save processor cycles by using classes or indexes with id numbers in a clever way, or you could use a data-xxx attribute in the select tag to tell your JS exactly what to look for...

Sign up to request clarification or add additional context in comments.

5 Comments

var disabled = $this.val() == ''; $button.prop('disabled', disabled); $button.css("background-color", disabled ? "" : "#008C00"); - brought to you by the campaign to eliminate ugly ifs.
I tried using $('.show_radio_button') in the first jQuery line but for some reason that doesn't work unless I leave it as $('#show_box_id'). If I do use $('.show_radio_button'), it does prevent the others from lighting up, but the jQuery still doesn't do anything if I use the form on an object that isn't the first one.
Ok I think I nearly got it, I used your code but changed the <% select %> tag to <%= f.select :box_id, Box.all.collect { |p| [ p.box_number, p.id ] }, {}, class: "show-radio-button", id: "show_box_id_#{show.id}", :include_blank => true %> - so I added a {} before the class. Now the only thing not working is the :include_blank => true
Got it - just put the blank into the empty {} prior to the class. Thanks a lot for your help!
No problem! Glad to help. Also, you could do Box.all.collect { |p| [ p.box_number, p.id ] } in the controller once, then pass it across in an instance variable rather than do the look-up against the database multiple times within the template. :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.