4

I am using ruby 1.9.3 and rails 4.1.
I very new to ruby and have no previous javascripting knowledge.

I have a couple of simple has many relationships and one has many through relationship.

class Service < ActiveRecord::Base
  has_many :websites,   :dependent => :destroy
  has_many :timetables, :dependent => :destroy
  has_many :service_places, :dependent => :destroy
  has_many :places, through: :service_places
end

class ServicePlace < ActiveRecord::Base
  belongs_to :service
  belongs_to :place
end

class Website < ActiveRecord::Base
  belongs_to :service
end

class Place < ActiveRecord::Base
  has_many :service_places    
  has_many :services, through: :service_places
end

class Timetable < ActiveRecord::Base
  belongs_to :service
end

I have create nested forms in the service form (_form.html.erb) for each of the associations which render fine, all the CRUD functions work.

<%= form_for(@service, :html => {:class => "form-horizontal", :role => "form"}) do |f| %>

  <%= render 'wcc_style/layouts/error_messages', object: @service %>

  <!-- I've skipped some of the html for the service details to just rendering the forms... -->

  <!-- Adds the Service_Places associations -->
  <%= f.fields_for :service_places do |service_places| %>
    <%= render "service_place_fields", :f => service_places %>
  <%end %>

  <!-- Adds the website associations via a partial -->
  <%= f.fields_for :websites do |website| %>
    <%= render "website_fields", :f => website %>
  <%end %>
  <%= link_to_add_fields "Add a website", f, :websites, "btn btn-default", "Add a new website" %>


  <!-- Adds the timetables associations via a partial -->
  <%=f.fields_for :timetables do |timetable| %>
    <%= render "timetable_fields", :f => timetable %>
  <%end %>

  <%= f.submit 'Save', :class => 'btn btn-primary' %>
  <%= link_to 'Cancel', services_path, :class => "btn btn-default", :role =>"button" %>

<% end %>

Example of website partial (_website_fields.html.erb)

<!-- This partial holds the websites information for main service form -->
<div class="form-group"> 
  <%= f.label :website, :class=>'col-sm-2 control-label' %>
  <div class="col-sm-4">
    <%= f.text_field :url, :class => "form-control", :placeholder => "Service Provider Website" %>
  </div>
</div>

What I am trying to do now is to add functionality to be able to add and remove items (eg websites, timetables, service_places) related to the service when adding\editing a service record. I've seen a lot of posts relating to this but nothing with a good working solution using jquery and rails 4.

Having done some research on this I found the old railcasts 196 and 197. These were out of date and didn't work in rails 4. More research indicated a depreciated command. I saw there was a newer version of the railscasts which I haven't looked at yet. I did read the comments on the revised version and it indicates that it uses coffeescript, although I can't confirm this.

I have also seen links to other potential solutions using the cocoon gem and nested_form_fields. Both seem to use HAML which I am unfamiliar with.

I am trying to do this using jquery, to help me understand the javascript process more than anything (because I'll need to support the development when it's complete and our standard is to use JQuery where possible).

I've managed to get the add sort of working in that it renders a website label and checkbox on first press but then on subsequent clicks simply renders this again for about a second before disappearing. The position isn't where I would expect it to be either, at the top of the page, but I am sure that is down to our own styling gem which deals with the CSS. I thought that javascript was not supposed to do a POST which is what it appears to do because it clears my other fields on each button click.

My first question is how can I get the add function to add a new item without clearing the other items in my services form?

My second question is how do I get a delete function to work. I am assuming that the delete has to be within the form builder in the partial (eg one delete per item).

I've included the application helper and javascript below (cobbled together from various posts).

application_helper.rb module ApplicationHelper

  #def link_to_remove_fields(name, f)
  #  f.hidden_field(:destroy) + link_to_function(name, "remove_fields(this)")
  #end

  # remove link doesn't work!!!!!!!!!
  def link_to_remove_fields(name, f)
    f.hidden_field(:_destroy) + link_to_function(name, "remove_fields(this)")
  end

  # This now sort of works but the field disappears when it is created after about a second.  It also appears at the top of the page.  
  def link_to_add_fields(name, f, association, cssClass, title)  
    new_object = f.object.class.reflect_on_association(association).klass.new  
    fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder|  
      render(association.to_s.singularize + "_fields", :f => builder)  
    end  
    link_to name, "#", :onclick => h("add_fields(this, \"#{association}\", \"#{escape_javascript(fields)}\")"), :class => cssClass, :title => title 
  end 

end

application.js

function remove_fields(link) {
    $(link).prev("input[type=hidden]").val("1");
    $(link).closest(".fields").hide();
}

function add_fields(link, association, content) {
    var new_id = new Date().getTime();
    var regexp = new RegExp("new_" + association, "g")
    $(link).parent().before(content.replace(regexp, new_id));
}

services_controller.rb

class ServicesController < ApplicationController
  # Set the service record before each action
  before_action :set_service, only: [:show, :edit, :update, :destroy]

  ################################################################################
  # Create
  ################################################################################
  def create
    @service = Service.new(service_params)

    respond_to do |format|
      if @service.save
        format.html { redirect_to @service, notice: 'Service was successfully created.' }
        format.json { render action: 'show', status: :created, location: @service }
      else
        @service.websites.build
        @service.timetables.build
        @service.service_places.build
        format.html { render action: 'new' }
        format.json { render json: @service.errors, status: :unprocessable_entity }
      end
    end

  end


  ################################################################################
  # Delete
  ################################################################################
  def destroy
    @service.destroy

    respond_to do |format|
      format.html { redirect_to services_url , notice: 'Service was successfully deleted.' }
      format.json { head :no_content }
    end
  end


  ################################################################################
  # Edit
  ################################################################################
  def edit
    @service.websites.build
    @service.timetables.build
    @service.service_places.build
  end


  ################################################################################
  # Index
  ################################################################################
  def index
    @services = Service.order(:service_number).page params[:page]
  end


  ################################################################################
  # New
  ################################################################################
  def new
    @service = Service.new
    @service.websites.build
    @service.timetables.build
    @service.service_places.build
  end


  ################################################################################
  # Search
  ################################################################################
  def search
    @search_term = params[:search_term]
    if @search_term && @search_term.strip.empty?
      redirect_to root_path
    else
      @services = Service.search(@search_term).order(:service_number).page params[:page]
    end
  end


  ################################################################################
  # Show
  ################################################################################
  def show
  end


  ################################################################################
  # Update
  ################################################################################
  def update
    respond_to do |format|
      if @service.update(service_params)
        format.html { redirect_to @service, notice: 'Service was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @service.errors, status: :unprocessable_entity }
      end
    end
  end


  ################################################################################
  # Private methods
  ################################################################################
  private

    # Use callbacks to share common setup or constraints between actions.
    def set_service
      @service = Service.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def service_params
      params.require(:service).permit(:service_number, :operator, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday, :bank_holiday, :search_term, websites_attributes: [:id, :service_id, :url, :_destroy], timetables_attributes: [:id, :service_id, :url, :_destroy], service_places_attributes: [:id, :service_id, :position, :place_id, :_destroy],  places_attributes: [:id, :place_name, :active, :_destroy])
    end

end

Having scoured the internet I can't seem to find any links on how to get this working using jquery and standard ruby\rails. Does anyone know of any links or resources that would be useful for trying to achieve this?

I am not adverse to using gems like cocoon or nested_form_fields, assuming I can convert the HAML to ERB and coffeescript to javascript. Being a newbie to both ruby\rails and javascript having builders doing stuff is great but when it goes wrong I wouldn't have a clue how to fix it.

I appreciate any help or guidance.

1 Answer 1

4

Having not got anywhere fast I followed up on suggestions from a colleague to use cocoon. I followed the steps here: cocoon gem on github and managed to get it to work. I know this doesn't answer my actual question but it did provide me with a method for doing what I wanted to do. I've noted the steps for others in case they find it useful. I also converted the HAML to normal ruby because I was more comfortable with that.

1) Add following line to your gemfile:

gem "cocoon"

2) Add the line below into your app/assets/javascripts/application.js file

//= require cocoon

3) Amended my service form (_form.html.erb) as follows

<%= form_for(@service, :html => {:class => "form-horizontal", :role => "form"}) do |f| %>

  <%= render 'wcc_style/layouts/error_messages', object: @service %>

  <div class="form-group">
    <%= f.label :service_number, :class=>'col-sm-2 control-label' %>
    <div class="col-sm-3">
      <%= f.text_field :service_number, :class => "form-control", :placeholder => "Service Number" %>
    </div>
  </div>

  <div class="form-group">
    <%= f.label :operator, :class=>'col-sm-2 control-label' %>
    <div class="col-sm-4">
      <%= f.text_field :operator, :class => "form-control", :placeholder => "Operator" %>
    </div>
  </div>

  <div class="form-group">
    <%= f.label :monday, :class=>'col-sm-2 control-label' %>
    <div class="col-sm-2">
      <%= f.check_box :monday, :class => "form-control, pull-left", :placeholder => "Monday" %>
    </div>
  </div>

  <div class="form-group">
    <%= f.label :tuesday, :class=>'col-sm-2 control-label' %>
    <div class="col-sm-2">
      <%= f.check_box :tuesday, :class => "form-control, pull-left", :placeholder => "Tuesday" %>
    </div>
  </div>

  <div class="form-group">
    <%= f.label :wednesday, :class=>'col-sm-2 control-label' %>
    <div class="col-sm-2">
      <%= f.check_box :wednesday, :class => "form-control, pull-left", :placeholder => "Wednesday" %>
    </div>
  </div>

  <div class="form-group">
    <%= f.label :thursday, :class=>'col-sm-2 control-label' %>
    <div class="col-sm-2">
      <%= f.check_box :thursday, :class => "form-control, pull-left", :placeholder => "thursday" %>
    </div>
  </div>

  <div class="form-group">
    <%= f.label :friday, :class=>'col-sm-2 control-label' %>
    <div class="col-sm-2">
      <%= f.check_box :friday, :class => "form-control, pull-left", :placeholder => "Friday" %>
    </div>
  </div>

  <div class="form-group">
    <%= f.label :saturday, :class=>'col-sm-2 control-label' %>
    <div class="col-sm-2">
      <%= f.check_box :saturday, :class => "form-control, pull-left", :placeholder => "Saturday" %>
    </div>
  </div>

  <div class="form-group">
    <%= f.label :sunday, :class=>'col-sm-2 control-label' %>
    <div class="col-sm-2">
      <%= f.check_box :sunday, :class => "form-control, pull-left", :placeholder => "Sunday" %>
    </div>
  </div>

  <div class="form-group">
    <%= f.label :bank_holiday, :class=>'col-sm-2 control-label' %>
    <div class="col-sm-2">
      <%= f.check_box :bank_holiday, :class => "form-control, pull-left", :placeholder => "Bank Holiday" %>
    </div>
  </div>

<h3>Stops</h3>

  <!-- Adds the Service_Places associations via partial (sort applied in model) -->
  <div>
    <%= f.fields_for :service_places do |service_places| %>
      <%= render 'service_place_fields', :f => service_places %>
    <% end %>
    <div class="links">
      <%= link_to_add_association 'add service places', f, :service_places, :class => "btn btn-default" %>
    </div>
  </div>


<h3>Websites</h3>

  <!-- Adds the website associations via a partial -->
  <div>
    <%= f.fields_for :websites do |website| %>
      <%= render "website_fields", :f => website %>
    <%end %>
    <div class="links">
      <%= link_to_add_association 'add websites', f, :websites, :class => "btn btn-default" %>
    </div>
  </div>

<h3>Timetables</h3>

  <!-- Adds the timetables associations via a partial -->
  <div>
    <%=f.fields_for :timetables do |timetable| %>
      <%= render "timetable_fields", :f => timetable %>
    <%end %>
    <div class="links">
      <%= link_to_add_association 'add timetables', f, :timetables, :class => "btn btn-default" %>
    </div>
  </div>

  <br>

  <%= f.submit 'Save', :class => 'btn btn-primary' %>
  <%= link_to 'Cancel', services_path, :class => "btn btn-default", :role =>"button" %>

<% end %>

4) Amended the partial to that shown below. It needs the class="nested-fields" otherwise it will not work.

<div class="nested-fields">

  <%= f.label :website, :class=>'col-sm-2 control-label' %>

  <div class="col-sm-4">
    <%= f.text_field :url, :class => "form-control", :placeholder => "Service Provider Website" %>
  </div>

  <div>
    <%= link_to_remove_association "remove website", f, :class => "btn btn-default" %>
  </div>

</div>

I haven't included all my partials but they follow the same format.

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

Comments

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.