Mel
Mel

Reputation: 2685

Rails 5 - How to use javascript in Rails - using one script breaks another

I am trying to learn how to use javascript in Rails 5.

I have found a work-around which seems to solve the specific rendering problem - but it doesnt feel like its consistent with the rules for using rails and it definitely doesnt seem logical. I'm trying to understand how to use Rails 5 with javascript. At best, I seem to be learning how to work around Rails so that I can use javascript.

I have a proposal show view. It has two partials rendered on it. Each of those partials has a javascript script tag at the end of it.

I have a status menu partial with:

<div class="btn-group">
  <button type="button" style="color: black; width:100%" class="btn btn-blue btn-line dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" >
    STATUS: <%= text_for_state(@proposal.current_state) %>
  </button>
  <ul class="dropdown-menu">
    <% if policy(@proposal).under_review? && @proposal.can_transition_to?(:under_review) %>
        <%= link_to "Request review", under_review_proposal_path(@proposal),  method: :put, :style=>"padding-left:20px; padding-right: 10px"%>
        <li role="separator" class="divider"></li>
    <% end %>

    <% if policy(@proposal).approved? && @proposal.can_transition_to?(:approved) %>
        <%= link_to "Approve", approved_proposal_path(@proposal), method: :put, :style=>"padding-left:20px; padding-right: 10px" %>
        <li role="separator" class="divider"></li>
    <% end %>

    <% if policy(@proposal).not_approved? && @proposal.can_transition_to?(:not_approved)%>
        <%= link_to "Not approved", not_approved_proposal_path(@proposal),  method: :put, :style=>"padding-left:20px; padding-right: 10px" %>
        <li role="separator" class="divider"></li>
    <% end %>

    <% if policy(@proposal).publish_openly? && @proposal.can_transition_to?(:publish_openly)%>
        <%= link_to "Publish", publish_openly_proposal_path(@proposal),  method: :put, :style=>"padding-left:20px; padding-right: 10px" %>
        <li role="separator" class="divider"></li>
    <% end %>

    <% if policy(@proposal).publish_to_invitees? && @proposal.can_transition_to?(:publish_to_invitees)%>
        <%= link_to "Publish - invitees only", publish_to_invitees_proposal_path(@proposal),  method: :put, :style=>"padding-left:20px; padding-right: 10px" %>
        <li role="separator" class="divider"></li>
    <% end %>

    <% if policy(@proposal).publish_counterparties_only? && @proposal.can_transition_to?(:publish_counterparties_only)%>
        <%= link_to "Publish - counterparties only", publish_counterparties_only_proposal_path(@proposal),  method: :put, :style=>"padding-left:20px; padding-right: 10px" %>
        <li role="separator" class="divider"></li>
    <% end %>

    <% if policy(@proposal).remove? && @proposal.can_transition_to?(:remove) %>
        <%= link_to "Remove (set as private draft)", remove_proposal_path(@proposal),  method: :put, :style=>"padding-left:20px; padding-right: 10px" %>
        <li role="separator" class="divider"></li>
    <% end %>

    <% if policy(@proposal).destroy? && @proposal.can_transition_to?(:destroy) %>
        <%= link_to "Delete", remove_proposal_path(@proposal),  method: :put, :style=>"padding-left:20px; padding-right: 10px" %>
        <li role="separator" class="divider"></li>
    <% end %>

    <!-- &nbsp;&nbsp; -->

    <% if policy(@proposal).update? %>
        &nbsp;&nbsp;
        <%= link_to "Edit", edit_proposal_path(@proposal), :style=>"padding-right: 10px"  %>
        <!-- <li role="separator" class="divider"></li> -->
    <% end %>
    </ul>
</div>




<!-- Note: this script is necessary becasue the bootstrap dropdown doesnt work if bootstrap sprockets are loaded after jquery in application.js. But if ordered in that way, the rest of my js doesnt work. -->
<script>
   $('.dropdown-toggle').dropdown()
</script>

I also have a carousel showing images with:

<%= carousel_for(@proposal.images.all.map(&:picture_url)) %>




<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>

<script>
  $(document).ready(function(){
  $('#mycarousel').carousel();
  });
</script>

My application.js has:

//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require jquery-fileupload/vendor/jquery.ui.widget
//= require jquery-fileupload/jquery.iframe-transport
//= require jquery-fileupload/jquery.fileupload
//= require bootstrap-sprockets
//= require moment
//= require bootstrap-datetimepicker
//= require pickers
//= require markerclusterer
//= require cocoon
//= require cable
//= require_tree .

If I don't use the image carousel, then I can comment out the script tag from the end of the status menu partial (and the menu works fine). If I include the carousel, then the status menu won't work if I don't add that script.

Can anyone point me in the direction of resources to learn how to use javascript in rails 5, or explain why I have to have these scripts in the view partial? I don't find the rails guides helpful with this line of enquiry because none of the asset pipeline explanations address why these scripts should be in the view files at all and the js guide talks about specific event handling - not about how to configure a rails application to use javascript.

Upvotes: 0

Views: 420

Answers (1)

Ryenski
Ryenski

Reputation: 9692

It looks like the problem that you're running into is that your dropdown initializer is running before the rest of your code libraries are evaluated.

You can tell Javascript to delay running your page scripts until the DOM is ready. You already did this for your carousel script.

To do this, create a new file under app/assets/javascripts:

// page_scripts.js

document.addEventListener("turbolinks:load", function() {
  $('.dropdown-toggle').dropdown()

  $('#mycarousel').carousel();
})

This file will automatically be picked up by your application.js manifest. This is because it includes the line //= require_tree ., which basically says "require all the other files in this directory that I have not already explicitly required above."

This script is listening for the event turbolinks:load because Turbolinks (which you have included in your application.js manifest) handles page loading differently. According to the Turbolinks docs:

You may be used to installing JavaScript behavior in response to the window.onload, DOMContentLoaded, or jQuery ready events. With Turbolinks, these events will fire only in response to the initial page load—not after any subsequent page changes.

Source: Turbolinks docs

To get to the rest of your question: jQuery and Javascript in Rails works just like it does with any HTML page - the difference is in how the asset pipeline compiles your javascript files.

Briefly, it works like this:

  1. You put all your javascript files in app/javascripts
  2. You create a "manifest" file called app/javascripts/application.js
  3. You require your jQuery library, all your supporting libraries, and your application javascript files, as you have done.
  4. You can create your own app-specific javascript files. These files will automatically be included by //= require_tree .. This just says "require all the other files in this directory that I have not already explicitly required above."
  5. You have to be aware of load order. As you've found out, order matters. To be sure that your scripts fire at the right time, enclose them in a turbolinks:load event listener as shown.

Upvotes: 1

Related Questions