4

I have a Ruby on Rails application with Twitter Bootstrap tabs within a partial, and multiple instances of that partial can be added by the user using Simple Form and Cocoon.

In order for the tab links to work properly, I must have unique names for the tabs in each new child of the partial (so that clicking on a particular tab activates its own unique content and not content for the tab of the same name in the preceding child partial appearing on the same page).

I saw that code was added to Cocoon to accomplish this objective here: https://github.com/nathanvda/cocoon/pull/100 .

In that discussion there is an indication that the new functionality was tried out on the Cocoon Simple Form Demo project. But whatever changes were made in the process of trying out the new functionality were not committed there (at least as far as I could tell).

Looking at the commit on github, that addition seems to have been accomplished in the Cocoon javascript code by adding '[contentNode]' to the parameters on the 'insertionNode.trigger' call (https://github.com/woto/cocoon/commit/cb206d501e4749a05e0c1d868911da159c8200c1):

insertionNode.trigger('cocoon:before-insert', [contentNode]);

I am not very sophisticated with this stuff.

So, how do I implement/grab the unique identifier for each new instance of a nested child?

Any help or direction would be much appreciated.

Here is the code from my application for reference:

-# /app/controllers/parent_records_controller.rb

class ParentRecordsController < ApplicationController

  # GET /parent_records/1/edit
  def edit
    @parent_record = ParentRecord.find(params[:id])
    @parent_record.items.build
  end
end

-#/app/views/parent_records/_form.html.haml

.row-fluid
  =simple_form_for(@parent_record, :wrapper => :inline4, :html => {:class => 'form-inline', :multipart => true}) do |f|
    .row-fluid
      = f.simple_fields_for :items do |item|
        =render :partial => '/parent_records/form_partials/item', :locals => { :f => item }
      .row
        .span2.offset9
          = link_to_add_association 'Add Another Item', f, :items, :class => 'btn btn-primary', :partial =>     'parent_records/form_partials/item'
    = f.button :submit , :class => 'btn btn-success'

-#/app/views/parent_records/form_partials/_item ------ THE TAB HREF NAME IS WHERE I NEED THE UNIQUE IDENTIFIERS ------

.nested-fields
  .well.span
    .tabbable
      %ul.nav.nav-tabs
        %li.active
          %a{"data-toggle" => "tab", :href => "#tab1"} Item Info 
        %li
          %a{"data-toggle" => "tab", :href => "#tab2"} Pictures

      .tab-content
        #tab1.tab-pane.active
          = f.input :short_name,:label => 'Item Title'
          = f.input :brand,:label => 'Brand'
          = f.input :description,:label => 'Description' 

        #tab2.tab-pane
          = f.simple_fields_for :pictures do |picture|
            =render :partial => '/parent_record/form_partials/picture', :locals => { :f => picture }
          .span2.offset9
            = link_to_add_association 'Add A Picture', f, :pictures, :class => 'btn btn-primary', :partial =>     'parent_records/form_partials/picture'
      .span2
        = link_to_remove_association "Remove Item", f, :class => 'btn btn-danger'

-#/app/views/parent_records/form_partials/_picture

.nested-fields  
  .well.span
    = f.input :image,   :label => 'Seclect Image to be Uploaded'
    = f.input :rank,    :label => 'Image Rank for this Item'
    .row
      .span2
        = link_to_remove_association "Remove Picture", f, :class => 'btn btn-danger'

EDIT / FOLLOW-ON QUESTION:

Nathan's answer below worked for adding unique identifiers, but adding the unique identifiers has broken Bootstrap's tabbing functionality. That seems to make some sense given my impression that the Bootstrap javascript is probably only assessing the correspondence between tabs and links when the document initially loads.

Can anybody point me in the direction of how to get the Bootstrap javascript to reinitialize and find the new ids and links??

EDIT2

Turns out (I think) that it was the div .well.span separating .nested-fields and .tabbable that prevented Bootstrap from being able to initialize the newly named href and tab ids. Crazy journey, but satisfying in the end.

Bryan Ash
  • 4,385
  • 3
  • 41
  • 57
malikilam
  • 672
  • 8
  • 10
  • as far as i understand your problem, you have a partial that gets ajax-loaded and appended to your form evry time you click on a link. Since we can only know which tab ids exist on the page when we are client-side, i would suggest to use `after-insert` callback to tweak the ids after they have been inserted (i.e. scan all present ids and increment) + initiate the tabs. Not the best solution, but still. another option would be to use the object_id (f.object.object_id) to generate unique tab ids in the first place. – m_x Feb 01 '13 at 20:25
  • @m_x Thanks for the advice. I certainly think your 'after-insert' callback idea would work. I just don't know how to do it. – malikilam Feb 01 '13 at 21:11
  • @m_x Sorry my comment is cut into two pieces. I just tried what I could figure out about the `object_id` idea. I can get an object id to show in the form form when each new 'item' is added (using `f.object_id` and `f.object.object_id`), but every new child prints the same 'object_id' (which tells me that I am not grabbing the right object). It seems that whatever `simple_fields_for` is passing into the block is the same object for each child. I would need to figure out to get the actual `@item` object. Any hints? – malikilam Feb 01 '13 at 21:23
  • weird. The form builder object should be the nested object... my guess is that you access the parent object instead. maybe you use the wrong `f` ? i tend to name my subform builders differently to avoid such mistakes... Or maybe there's a subtlety i don't know about simple_form – m_x Feb 01 '13 at 21:43
  • @m_x Yes, I considered the possibility that I was working on the `parent_record` object by mistake. One thing leads me away from that possibility: the `item` object that I build in the controller (as opposed to the child `item` objects build by Cocoon via ajax) has a different `object_id`. I'm still trying to figure this out, but I am guessing there is a fairly simple way to get this unique id that is now built into Cocoon (by the above-mentioned addition to the source code). I just don't know how to do it (but at least I know that it exists, a great first step, lol). Thanks! – malikilam Feb 01 '13 at 22:06
  • Nope `f.object.object_id` is ruby code, you are in the browser now. The nested object is rendered once, so each new nested inserted element will have the same `f.object.object_id`. You should calculate a unique id in your javascript. – nathanvda Feb 04 '13 at 23:42

1 Answers1

4

First off, in the cocoon_simple_form_demo project, you will see the last commit adds animations, using the cocoon:before-insert or cocoon:after-insert event.

The after_insert event gives you the DOM element that is inserted, so you can do whatever you like with it, e.g. fill in a unique href.

So in your case, that would be something like the following (note: completely untested code, but I hope it gets you going) :

$('form').bind('cocoon:before-insert', function(e,item_to_be_added) {
    new_id = new Date().getTime();
    item_to_be_added.find('a[href="#tab1"]').attr('href', "#tab1_" + new_id);
    item_to_be_added.find('#tab1').attr('id', "tab1_" + new_id);
    // do the same for #tab2
});

This just standard jquery DOM-manipulation. Hope this helps.

You could either re-trigger the tabs behaviour after insert (this needs to be after-insert, other wise there is no use):

   $('form').bind('cocoon:after-insert', function(e,item_to_be_added) {
       $('nav-tabs a').click(function(e) {
         e.preventDefault();
         $(this).tab('show');
       }); 
   });

You already should have code like this? The other alternative is to use the better on, like this:

$('form').on 'click', '.nav-tabs a', function(e) {
    e.preventDefault(); 
    $(this).tab('show');
});

This should have to be done only once, when the page is loaded, and this will keep on working when tabs are added.

HTH.

allenwlee
  • 665
  • 6
  • 21
nathanvda
  • 49,707
  • 13
  • 117
  • 139
  • Thanks so much for your response Nathan! Jquery DOM-manipulation is not yet a tool I have in my arsenal, but with your example and the research I'm going to do, it's about to become one. A couple of quick refining questions: 1) you mentioned that the `after_insert` event gives the DOM element that is inserted, but your example seems to use the `cocoon:before-insert` event. Is that a typo? Or, is my ignorance leading me off the trail? – malikilam Feb 05 '13 at 01:44
  • Correction: that was only one refining question. Thanks! Malik – malikilam Feb 05 '13 at 01:51
  • Please ignore the preceding question. The answer was in your post: the change can be made either before or after insertion. Both work. Getting it done was actually pretty easy with your example and the hint about where to look in the demo app. I was able to get the unique ids I was looking for added using both methods (`cocoon:before-insert' and `cocoon:after-insert`), HOWEVER, doing so apparently breaks Bootstrap's tabbing functionality. – malikilam Feb 05 '13 at 04:41
  • I think it makes some sense that the Bootstrap tabbing functionality would break in that case because I would guess that the Bootstrap javascript probably only checks the tab `href` and `id` when the page initially loads. Can you think of any way to trigger the Bootstrap javascript to reinitialize the tabs once the names are changed?? This is a very educational rabbit hole. Thanks – malikilam Feb 05 '13 at 04:42
  • You will have to re-trigger the bootstrap tabs-behaviour, by doing, now definitely `after-insert`, something like `$("your-tab-container").tabs()` (because otherwise bootstrap will not do the necessary stuff). HTH. – nathanvda Feb 05 '13 at 07:49