0

I'm using the Cocoon gem to build a nested form with a field_for containing another field_for. The hierarchy looks like this:

  • Letter form
    • Card field_for
      • Button field_for

Each card entry is added using the link_to_add_association link. I use the after-insert callback to allow users to click on the created card to show up a popup containing the button fields (for UX purpose).

Example of my after-insert callback:

$('#carousel-stage-ul')
      .on('cocoon:after-insert', function(e, insertedItem) {

        insertedItem.find('#add-card-button, .new_button_card').click(function() {
          $('#black-background')[0].style.display = "block";
          insertedItem.find('.add-button-card-modal').css('display', 'block');
        });

        insertedItem.find('#delete_new_card_button').click(function() {
          $('#black-background')[0].style.display = "none";
          insertedItem.find('.add-button-card-modal').css("display", "none");

          $firstIn = insertedItem.find('.add-button-card-modal input[type=text]').eq(0);
          $secondIn = insertedItem.find('.add-button-card-modal input[type=text]').eq(1);

          $firstIn.val("");
          $secondIn.val("");

          $buttonForm = $.parseHTML('<div id="add-card-button" class="add-button">+ Add Button</div>')
          insertedItem.find('#new_card_button').replaceWith($buttonForm[0]);

          insertedItem.find('#add-card-button, .new_button_card').click(function() {
            $('#black-background')[0].style.display = "block";
            insertedItem.find('.add-button-card-modal').css('display', 'block');
          });
        });
            })
      .on('cocoon:after-remove', function(e, insertedItem) {
        ...

_card_fields.html.erb partial rendering the buttons:

<% f.object.buttons.build %>
  <%= f.fields_for :buttons do |button_card_fields| %>
      <%= render 'button_fields', f: button_card_fields %>
  <% end %>

_button_fields.html.erb partial:

<div class="add-button-card-modal">
  <h4>Add New Button</h4>
  <label>Button Text</label>
  <%= f.text_field :button_text, :maxlength => 20, placeholder: "Enter the text to display on the button..." %>
  <br><br>
  <label>Button URL</label>
  <%= f.text_field :button_url, placeholder: "Paste URL..." %>
  <div class="nav-popups-buttons">
    <button type="button" id="validate_new_card_button" class="small-cta2">Add Button</button>
    <p class="remove-link" id="delete_new_card_button">Remove Button</p>
  </div>
</div>

Here is the HTML rendered for one card inside the card carousel to better understand my insert-after callback:

<div class="connected-carousels" style="display: block;">
                  <div class="stage">
                      <div class="carousel carousel-stage" data-jcarousel="true">
                          <ul id="carousel-stage-ul" style="left: 0px; top: 0px;">


                          <li class="carousel-slide">
  <div id="card-messenger">
    <div class="DIV_1b">
      <div class="DIV_2b">
        <div class="DIV_3">
          <a class="A_4"></a>
          <div class="DIV_5" style="">
          </div>
        </div>
        <div class="DIV_6">
          <div class="DIV_7">
            <div class="DIV_8">
              <div class="DIV_9">
                <div class="DIV_10">
                </div>
                <div class="DIV_11">
                  <input maxlength="80" size="0" placeholder="Enter title..." type="text" name="letter[cards_attributes][1509602161650][title]" id="letter_cards_attributes_1509602161650_title">
                </div>
              </div>
              <div class="DIV_12">
                <div class="DIV_14">
                  <input maxlength="80" size="0" placeholder="Enter subtitle..." type="text" name="letter[cards_attributes][1509602161650][subtitle]" id="letter_cards_attributes_1509602161650_subtitle">
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div id="add-card-button" class="add-button" style="width: 308.203px;">+ Add Button</div>
      <div id="DIV_19" class="card-share-button" style="display:none;">
        <a id="A_20">Share</a>
      </div>
      <div id="DIV_19" class="input-card-share-button">
        <div id="add-share-card-button" class="add-button" style="padding: 0!important;">+ Add Share</div>
      </div>
      <input value="false" id="hidden-share-field" type="hidden" name="letter[cards_attributes][1509602161650][button_share]">
    </div>
  </div>

    <div class="add-button-card-modal">
      <h4>Add New Button</h4>
      <label>Button Text</label>
      <input maxlength="20" placeholder="Enter the text to display on the button..." size="20" type="text" name="letter[cards_attributes][1509602161650][buttons_attributes][0][button_text]" id="letter_cards_attributes_1509602161650_buttons_attributes_0_button_text">
      <br><br>
      <label>Button URL</label>
      <input placeholder="Paste URL..." type="text" name="letter[cards_attributes][1509602161650][buttons_attributes][0][button_url]" id="letter_cards_attributes_1509602161650_buttons_attributes_0_button_url">
      <div class="nav-popups-buttons">
        <button type="button" id="validate_new_card_button" class="small-cta2">Add Button</button>
        <p class="remove-link" id="delete_new_card_button">Remove Button</p>
      </div>
    </div>
  <div class="modal-image">
    <div id="display_image_upload">
      <label>Upload Image</label>
      <input id="image-input" class="inputBox_upload_image" type="file" name="letter[cards_attributes][1509602161650][image_url]"><div class="progress"><div class="bar"></div></div>
    </div>
    <label id="or">OR</label>
    <div id="display_image_url">
      <label>Paste Image Url</label>
      <input id="image-input" class="inputBox_image" type="text" name="letter[cards_attributes][1509602161650][remote_image_url]">
    </div>
    <div class="nav-popups-buttons">
      <button type="button" id="validate_image" class="small-cta2">Add Image</button>
      <p class="remove-link" id="delete_new_card_image">Remove Image</p>
    </div>
  </div>
  <div>
    <input type="hidden" name="letter[cards_attributes][1509602161650][_destroy]" id="letter_cards_attributes_1509602161650__destroy" value="false"><a class="remove-link remove_fields dynamic" data-wrapper-class="carousel-slide" href="#">Remove Card</a>
  </div>
</li></ul>
                        </div>
                        <a href="#" class="prev prev-stage inactive" data-jcarouselcontrol="true"><span>‹</span></a>
                        <a href="#" class="next next-stage inactive" data-jcarouselcontrol="true"><span>›</span></a>
                    </div>
              <div class="navigation">
                  <a href="#" class="prev prev-navigation inactive" data-jcarouselcontrol="true">‹</a>
                  <a href="#" class="next next-navigation inactive" data-jcarouselcontrol="true">›</a>
                  <div class="carousel carousel-navigation" data-jcarousel="true">
                    <ul id="carousel-navigation-ul" style="left: 0px; top: 0px;"><li data-jcarouselcontrol="true" class="active"><img alt="botletter" class="carousel-ico" src="/assets/icon-cards-0178dc5a1fb2811909dcd1d1fcef121baa165e46d49973dff5fdea12090631fa.png"></li></ul>
                  </div>
              </div>
              <div>
                <a id="add-card-button-bis" data-association-insertion-node="#carousel-stage-ul" data-association-insertion-method="append" class="add_fields" data-association="card" data-associations="cards" data-association-insertion-template="<li class=&quot;carousel-slide&quot;>
  <div id=&quot;card-messenger&quot;>
    <div class=&quot;DIV_1b&quot;>
      <div class=&quot;DIV_2b&quot;>
        <div class=&quot;DIV_3&quot;>
          <a class=&quot;A_4&quot;></a>
          <div class=&quot;DIV_5&quot; style=&quot;&quot;>
          </div>
        </div>
        <div class=&quot;DIV_6&quot;>
          <div class=&quot;DIV_7&quot;>
            <div class=&quot;DIV_8&quot;>
              <div class=&quot;DIV_9&quot;>
                <div class=&quot;DIV_10&quot;>
                </div>
                <div class=&quot;DIV_11&quot;>
                  <input maxlength=&quot;80&quot; size=&quot;0&quot; placeholder=&quot;Enter title...&quot; type=&quot;text&quot; name=&quot;letter[cards_attributes][new_cards][title]&quot; id=&quot;letter_cards_attributes_new_cards_title&quot; />
                </div>
              </div>
              <div class=&quot;DIV_12&quot;>
                <div class=&quot;DIV_14&quot;>
                  <input maxlength=&quot;80&quot; size=&quot;0&quot; placeholder=&quot;Enter subtitle...&quot; type=&quot;text&quot; name=&quot;letter[cards_attributes][new_cards][subtitle]&quot; id=&quot;letter_cards_attributes_new_cards_subtitle&quot; />
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div id=&quot;add-card-button&quot; class=&quot;add-button&quot; style=&quot;width: 308.203px;&quot;>+ Add Button</div>
      <div id=&quot;DIV_19&quot; class=&quot;card-share-button&quot; style=&quot;display:none;&quot;>
        <a id=&quot;A_20&quot;>Share</a>
      </div>
      <div id=&quot;DIV_19&quot; class=&quot;input-card-share-button&quot;>
        <div id=&quot;add-share-card-button&quot; class=&quot;add-button&quot; style=&quot;padding: 0!important;&quot;>+ Add Share</div>
      </div>
      <input value=&quot;false&quot; id=&quot;hidden-share-field&quot; type=&quot;hidden&quot; name=&quot;letter[cards_attributes][new_cards][button_share]&quot; />
    </div>
  </div>

    <div class=&quot;add-button-card-modal&quot;>
      <h4>Add New Button</h4>
      <label>Button Text</label>
      <input maxlength=&quot;20&quot; placeholder=&quot;Enter the text to display on the button...&quot; size=&quot;20&quot; type=&quot;text&quot; name=&quot;letter[cards_attributes][new_cards][buttons_attributes][0][button_text]&quot; id=&quot;letter_cards_attributes_new_cards_buttons_attributes_0_button_text&quot; />
      <br><br>
      <label>Button URL</label>
      <input placeholder=&quot;Paste URL...&quot; type=&quot;text&quot; name=&quot;letter[cards_attributes][new_cards][buttons_attributes][0][button_url]&quot; id=&quot;letter_cards_attributes_new_cards_buttons_attributes_0_button_url&quot; />
      <div class=&quot;nav-popups-buttons&quot;>
        <button type=&quot;button&quot; id=&quot;validate_new_card_button&quot; class=&quot;small-cta2&quot;>Add Button</button>
        <p class=&quot;remove-link&quot; id=&quot;delete_new_card_button&quot;>Remove Button</p>
      </div>
    </div>
  <div class=&quot;modal-image&quot;>
    <div id=&quot;display_image_upload&quot;>
      <label>Upload Image</label>
      <input id=&quot;image-input&quot; class=&quot;inputBox_upload_image&quot; type=&quot;file&quot; name=&quot;letter[cards_attributes][new_cards][image_url]&quot; />
    </div>
    <label id=&quot;or&quot;>OR</label>
    <div id=&quot;display_image_url&quot;>
      <label>Paste Image Url</label>
      <input id=&quot;image-input&quot; class=&quot;inputBox_image&quot; type=&quot;text&quot; name=&quot;letter[cards_attributes][new_cards][remote_image_url]&quot; />
    </div>
    <div class=&quot;nav-popups-buttons&quot;>
      <button type=&quot;button&quot; id=&quot;validate_image&quot; class=&quot;small-cta2&quot;>Add Image</button>
      <p class=&quot;remove-link&quot; id=&quot;delete_new_card_image&quot;>Remove Image</p>
    </div>
  </div>
  <div>
    <input type=&quot;hidden&quot; name=&quot;letter[cards_attributes][new_cards][_destroy]&quot; id=&quot;letter_cards_attributes_new_cards__destroy&quot; value=&quot;false&quot; /><a class=&quot;remove-link remove_fields dynamic&quot; data-wrapper-class=&quot;carousel-slide&quot; href=&quot;#&quot;>Remove Card</a>
  </div>
</li>
" href="#" style="display: block;">+ Add Card</a>
              </div>
            </div>

And it looks like this: The Card after clicking on the link_to_add_association link

The button popup after clicking on Add Button, displaying the button fields

Everything works well except when the create action raises an error or when I load the edit view. In those two cases, the Cards already created are rendered but the after-insert callback is not called. Thus, I'm not able to show up my button popup...

Is there any way to call the after-insert callback on rendered association in the edit view / error view?

nico_lrx
  • 715
  • 1
  • 19
  • 36

2 Answers2

2

If I understand correctly, you want what is in the after-insert callback also to server-rendered html. Which is weird in a sense, because imho that is actually what I would do first :)

So instead of attaching click events, just always handle them (dynamically). Please note that id-elements are only allowed once a page (to be correct html, and will cause a lot of errors otherwise), so I replaced all those with classes, since they seem repeating.

So do something like

$(document).on('click', '.add-card-button, .new-button-card'), function() {
  $('#black-background')[0].style.display = "block";
  $(this).nearest('.add-button-card-modal').css('display', 'block');
}) 

One might wonder why you do not use a normal modal library, like the normal folks :P

The second click handler, albeit a little more complicated would actually be solved the same way and thus the after-insert handler is not longer needed.

So something like

$(document).on('click', '.delete-card-button', function() {
  ...
})
nathanvda
  • 49,707
  • 13
  • 117
  • 139
1

You really shouldn't have use jquery to fix the edit code like this ... it's a symptom, not the problem ... if the relationships & the .builds are setup right the edits will just work & automatically load everything - as what rails does is generate hmtl of the forms.

Before you actually try to fix your code with jquery hacks - I would suggestion you build a page without the popups...where it all just spits out in one big mess. If the nested models work with your current relationships, then go about implementing the complicated series of popups. If it doesn't work - the issue is your relationships in the models or the way your controller uses the .build

That being said the answer you're looking for can take two forms in jquery:

  1. General - you use $( document ).ready() to detect when it's all set to trigger your function containing the call to the cocoon.js's after-insert callback.
  2. Specific - Since you likely know the name or the css element containing the button field_for, you first create a selector to go after that css around the field_for ... in your case likely the connected-carousels. You do this so the jquery doesn't wander around your dom & change other parts on accident. Then you use the jquery .on() to assign all dom elements of the type your buttons belong too the trigger that fires.

Mirv - Matt
  • 553
  • 7
  • 22
  • Thanks Mirv, I will try this. I indeed built a page without the popups and everything works well... Just my custom Jquery not working, which makes sense in the edit case. – nico_lrx Nov 02 '17 at 13:40
  • I got home & was finally able to open your links...I think I understand some of the other stuff a bit better now. The other thing you can test is often cocoon has callback propagation issues...check the jquery trouble shooting list I through together https://github.com/nathanvda/cocoon/wiki/Trouble-shooting:-Jquery-issues ... specifically stopPropagation() might be a good call to test with...as it helps with hanging (which would interfere with your afterinsert hook)...I'm still guessing you have 2 issues, since you never posted the results of your rails console investigation of the objects – Mirv - Matt Nov 02 '17 at 17:02
  • I did all the test, everything is working well. Again, I think it's normal Cocoon doesn't call the after-insert callback when it renders the field. This function is designed to work when someone clicks on a link_to_add_association link. In that case there is no click, existing records are just rendered. I think I will have to write custom jquery... – nico_lrx Nov 03 '17 at 05:02