Search code examples
javascriptjqueryruby-on-railsautocompletecocoon-gem

Cocoon autocomplete Modal populating dynamic fields twice


I've been working on a rails app that tracks basic Events, which I hope to expand (it's a life data tracking app for myself). I have a EventTemplate class, which stores EventTemplateAttributes. When creating a new event, it uses rails-jquery-autocomplete to search for an existing template and populate the form with its attributes using a hash. Here is the code for that autocomplete function:

def autocomplete_eventtemplate_name
    respond_to do |format|
        format.json {
            @eventtemplates = Eventtemplate.where("name like ?", "%#{params[:term]}%")

            render json: json_for_autocomplete_eventtemplates(@eventtemplates)
        }
    end
end
def json_for_autocomplete_eventtemplates(eventtemplates)
    eventtemplates.collect do |eventtemplate|

        attributes = Hash.new
        unless eventtemplate.templateattributes.empty?
            eventtemplate.templateattributes.each do |templateattribute|
                attributes[templateattribute.id] = [templateattribute.name,templateattribute.value,templateattribute.unit]
            end
        end
        hash = {"id" => eventtemplate.id.to_s, "value" => eventtemplate.name,"eventattributes" => attributes}

        hash
    end
end

Here is my form:

<%= simple_form_for(@event) do |f| %>
  <% if @event.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@event.errors.count, "error") %> prohibited this event from being saved:</h2>
      <ul>
        <% @event.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>
  <div class="field">
    <% if @event.new_record? %>
      <% #,{:class=>"form-control"} %>
      <%= f.autocomplete_field :name, autocomplete_eventtemplate_name_events_path,{:class => "form-control"} %>
    <% else %>
      <%= f.label :name %><br>
      <%= f.text_field :name, class: 'form-control' %>
    <% end %>
  </div>
  <div id="#find-subj"></div>
  <table class="table-striped table table-bordered">
    <thead>
      <td>Name</td>
      <td>Value</td>
      <td>Unit</td>
      <td></td>
    </thead>
    <tbody id=eventattributes>
      <tr>
        <%= f.simple_fields_for(:eventattributes, :wrapper => false) do |tattribute| %>
          <% if modal == false%>
            <%= render 'eventattribute_fields', :f => tattribute %>
          <% end %>
        <% end %>
      </tr>
    </tbody>
  </table>
  <div class="links">
    <%= link_to_add_association f, :eventattributes, :"data-association-insertion-node" => "tbody#eventattributes", :"data-association-insertion-method" => "append", class: 'btn btn-info' do %>
      <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
    <% end %>
  </div></br>
  <div class="actions">
    <%= f.submit class: 'btn btn-primary'%>
    <%= link_to 'Cancel', events_path, class: 'btn btn-warning' %>
  </div>
<% end %>

And here is the _eventattribute_fields:

<tr class='nested-fields table'>
  <td><%= f.input_field :name ,class: 'form-control'%></td>
  <td><%= f.input_field :value, class: 'form-control' %></td>
  <td><%= f.input_field :unit, class: 'form-control' %></td>
  <td><%= link_to_remove_association f, class: 'btn btn-danger' do %>
    <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
    <% end %></td>
</tr>

If I add events from /events/new the form populates correctly From New Page however if I add them via a modal window, it duplicates the fields: Modal View

I've verified that the JSON coming to both pages isn't duplicated, and that it's showing the same view (/events/new) but somehow the fields are being duplicated.

EDIT With further debuging, I've deduced that the issue is happening in the cocoon script I have to populate the form. The two methods are used for the /events/new and the modal windows respectively. I don't need to keep the new page if the modal can be made to work correctly. Checking console output, I've seen that each cocoon:after-insert function are being fired only on their respective pages. They also only fire once per attribute.

$(function() {

    var eventtemplate_array;
    var modal = false;

     $('#event_name').bind('railsAutocomplete.select', function(event, data){
    //$('#event_name').bind('change', function(event, data){
        console.debug("Template selected...");
        attributes = data.item.eventattributes;
        console.debug("Removing existing attributes...");
        $('.event_eventattribute_destroy').click();
        console.debug("Adding attributes...");
        for(var id in attributes) {
            eventtemplate_array = attributes[id];
            $('.add_fields').click();
        }
        console.debug("Done adding all attribute...");
     });
     if(!modal){
    $('#eventattributes').bind('cocoon:after-insert', function(e, eventattribute) {
         console.debug(modal);
        eventattribute[0].getElementsByTagName('input')[0].value = eventtemplate_array[0];
        eventattribute[0].getElementsByTagName('input')[1].value = eventtemplate_array[1];
        eventattribute[0].getElementsByTagName('input')[2].value = eventtemplate_array[2];
    });}

    $('#newevent-modal').on('shown.bs.modal', function () {
        modal = true;
     $('input#event_name').bind('railsAutocomplete.select', function(event, data){
        attributes = data.item.eventattributes;
        $('.event_eventattribute_destroy').click();
        for(var id in attributes) {
            console.debug(id);
            eventtemplate_array = attributes[id];
            $('.add_fields').click();
        }
        eventtemplate_array = ["","",""];
    });
     $('#eventattributes').bind('cocoon:after-insert', function(e, eventattribute) {
         console.debug(modal);
        eventattribute[0].getElementsByTagName('input')[0].value = eventtemplate_array[0];
        eventattribute[0].getElementsByTagName('input')[1].value = eventtemplate_array[1];
        eventattribute[0].getElementsByTagName('input')[2].value = eventtemplate_array[2];
    });
});


});

Solution

  • Really complex question to understand. Just so I understand correctly: you are using an "autocomplete" gem to get the list of fields from a template, right? (just use a simple ajax call?) And then you use this json, to build the form in javascript. So in this script, somehow you create doubles, right? It seems an awfully complicated way, why no just render the form server-side?

    Anyhow, since your id's are the same for both pages (input#event_name), you actually are attaching the event twice when the modal is rendered, and that is why it is duplicated on the modal page. Better use scoped selectors like #newevent-modal input#event_name and #newevent-modal #eventattributes when handling the show event of the modal.