7

I am creating a system that stores cards using Ruby/Rails/HAML - In this case there is a Card class that has many Colours (this is also a class). When creating and editing a card I am using the Cocoon gem to allow me to dynamically add colour associations.

The problem I am having is that in the Card model, a card is limited to having a maximum of 5 colours. Yet the interface allows adding of unlimited colours resulting in an error.

Is there a way in Cocoon to limit the number of associations that can be added to a form, so that this limit is not exceeded?

This is the code for a form to add/edit a Card

 = simple_form_for @card, multipart: true do |c|
  = c.input :name, label: "Name of the card"
  = c.input :cost, label: "Cost of the card"
  #colours
   = c.simple_fields_for :colours do |colour|
    = render "colour_fields", f: colour
   .links
    = link_to_add_association 'add colour', c, :colours

And this is the colour_fields form

.nested-fields
 = f.input :value, as: :select, collection: Colour::VALUES, selected: f.object.value, include_blank: false
 = link_to_remove_association "remove colour", f

Thanks in advance.

Bryan Ash
  • 4,385
  • 3
  • 41
  • 57

2 Answers2

15

I would use javascript for this. You can bind to an event that is triggered upon insert of a new item: on this event count how many items there are, and hide the link if needed.

Likewise, when loading the page do the same.

So in code that would look like:

 $(function() {
   function check_to_hide_or_show_add_link() {
     if ($('#colours .nested-fields:visible').length == 5) {
       $('#colours .links a').hide();
     } else {
       $('#colours .links a').show();
     }
   }

   $('#colours').on('cocoon:after-insert', function() {
     check_to_hide_or_show_add_link();
   });

   $('#colours').on('cocoon:after-remove', function() {
     check_to_hide_or_show_add_link();
   });

   check_to_hide_or_show_add_link();     
 });

Something like this should work. Note this code is not tested :)

Hope this helps.

nathanvda
  • 49,707
  • 13
  • 117
  • 139
  • 1
    This is probably the best solution. It does seem bad that there is no pure Ruby way of doing this though. Thanks! – Invincibloobles Oct 27 '12 at 12:07
  • You could add validations to your model, like was done here: http://stackoverflow.com/questions/10080488/rails3-cocoon-validate-nested-field-count But as I read your question: you already have failing validations. Since the adding and removing of elements takes place in the browser, if you want to stop it there, there is no other way than using javascript. So it is not "bad", it is just because we do web-programming: some stuff runs on the server, and some in the browser. – nathanvda Oct 27 '12 at 13:03
  • 4
    This works fine, however I would add ':visible' before to check the lenght, I mean: $('#colours .nested-fields:visible') because when you delete a nested div element, it is hidden not destroyed :P – joselo Oct 07 '13 at 21:49
  • Agreed with @joselo regarding the `:visible` issue, plus the fact that the .bind() command is now deprecated since jQuery 3.0, you should use .on() instead. – Fabrice Carrega Mar 28 '17 at 10:14
  • Updated my 4+ year old answer with the suggestions in comments: check on `:visible` and use `.on` instead of `.bind` (which was still very much in use at the time) :) – nathanvda Mar 28 '17 at 11:39
2

I had a similar task to do and what I ended up doing was putting an onclick event on the "Add" link. This onclick event will execute before the code to add the fields is executed.

In your case, the code would look something like the following:

= link_to_add_association 'add colour', c, :colours, class: 'add-colour-link'

Then in your JavaScript (CoffeeScript in this case):

$('.add-colour-link').on 'click', (e) ->
   if $('#colours .nested-fields:visible').size() < 5
     return true #continue the execution 
   else
     #maybe display the modal to the user that only a maximum of 5 is allowed
     return false #prevent further execution
Marek Lipka
  • 50,622
  • 7
  • 87
  • 91
Andrei
  • 1,121
  • 8
  • 19