Search code examples
javascriptjqueryruby-on-rails-4turbolinks

How do I make this JS Turbolinks friendly?


This is my main.js file:

jQuery(function($) {
  $('LI.tree-item-name').has('ul').click(function() {
    if ($(this).hasClass('opened')) {
      $(this).find('UL').slideUp();
      $(this).removeClass('opened');
    } else {
      $(this).find('UL').slideDown();
      $(this).addClass('opened');
    }
    return false;
  });

  $('LI.tree-item-name li').click(function(e) {
    e.stopPropagation();
  })
});

jQuery(window).load(function(){
    jQuery('#video').removeClass('loading');
    jQuery('#video .box').animate({'opacity' : '1'});
    var $container = jQuery('#video');
    $container.masonry({
        columnWidth: 299,
        itemSelector: '.box'
    });
    jQuery('.popupbox').click(function(){
        jQuery('.popup:visible').fadeOut();
        var id = '#'+jQuery(this).data('popup');
        jQuery('#overlay').fadeIn();
        jQuery(id).css('top', jQuery(window).height()/2 - jQuery(id).height()/2).fadeIn();
        return false;
    });
    jQuery('.profile_popupbox').click(function(){
        jQuery('.popup:visible').fadeOut();
        var id = '#'+jQuery(this).data('popup');
        jQuery('#overlay').fadeIn();
        jQuery(id).css({'top': "20px", 'left': "-200px"}).fadeIn();             
        return false;
    });
    jQuery('#overlay').click(function(){
        jQuery('.popup:visible').fadeOut();
        jQuery(this).fadeOut();
    });
});

I would love for this to be Turbolinks-friendly.

I tried doing:

var ready;
ready = jQuery(window).load(function(){
    jQuery('#video').removeClass('loading');
    jQuery('#video .box').animate({'opacity' : '1'});
    var $container = jQuery('#video');
    $container.masonry({
        columnWidth: 299,
        itemSelector: '.box'
    });
    jQuery('.popupbox').click(function(){
        jQuery('.popup:visible').fadeOut();
        var id = '#'+jQuery(this).data('popup');
        jQuery('#overlay').fadeIn();
        jQuery(id).css('top', jQuery(window).height()/2 - jQuery(id).height()/2).fadeIn();
        return false;
    });
    jQuery('.profile_popupbox').click(function(){
        jQuery('.popup:visible').fadeOut();
        var id = '#'+jQuery(this).data('popup');
        jQuery('#overlay').fadeIn();
        // jQuery(id).css('top', jQuery(window).height() - jQuery(id).height()/2).fadeIn();
        jQuery(id).css({'top': "20px", 'left': "-200px"}).fadeIn();             
        return false;
    });
    jQuery('#overlay').click(function(){
        jQuery('.popup:visible').fadeOut();
        jQuery(this).fadeOut();
    });
});

$(document).ready(ready);
$(document).on('page:load', ready);

That didn't work, and it didn't make both functions Turbolinks-friendly.

So I want to just make the entire file TL-friendly.

Edit 1

I also have an upload.js.erb which has this, that doesn't get executed after initial page load:

$("#myVCModal").html("<%= escape_javascript(render 'videos/upload_video') %>");
$("#myModal").html("<%= escape_javascript(render 'videos/upload_video') %>");

$("#add-video-step-1").html("<%= escape_javascript(render 'videos/upload_video') %>");
$("#video-comment").html("<%= escape_javascript(render 'videos/upload_video') %>");
$('myModalPL').modal(show);

Ladda.bind('button');

I would like for all of these JS bits throughout my app to go back to working.

Edit 2

So now I have the main.js stuff working - Thanks @User089247. But the other modal executing JS is not working at all....i.e. the code under Edit 1.

What is happening is I hit this upload button:

<%= link_to "<i class='fa fa-film fa-lg'></i> Upload".html_safe, "#", class: "upload popupbox", data: { popup: "add-video-step-1"} %>

This is the modal that gets fired:

<div id="overlay">&nbsp;</div>
<div class="popup" id="add-video-step-1">
  <div class="titles clearfix">
      <h2>Upload a Video</h2>
      <p><i>Step 1 of 2 - TEST</i></p>
  </div>
  <div class="content">
    <% if @family_tree %>
      <%= simple_form_for([@family_tree, @video], :remote => true) do |f| %>
        <div class="column">
              <div class="f-row">
                  <%= f.input :title, label: "Title:" %>
              </div>
              <div class="f-row">
                  <%= f.input :description,label: "Description:" %>
              </div>
              <div class="f-row">
                  <%= f.input :circa, as: :datepicker, start_year: Date.today.year - 5, label: "Circa:" %>
              </div>
              <div class="f-row">
                  <label for="family">Family in this video:</label>
                  <%= f.collection_select :user_ids, @family_tree.members.order(:first_name), :id, :first_name, {}, {multiple: true} %>
              </div>
          </div>
          <%= f.button :submit, "Add Video" %>
        <% end %>
      <% end %>
    </div> <!-- //content -->
</div> <!-- //popup -->

Then when you press "Add Video", it should take you to the 2nd modal which is this:

<div class="bootstrap-styles">
 <div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
    <h3 id="myModalLabel">Upload your Video</h3>
    <p><i>Step 2 of 2 - TEST</i></p>
  </div>
  <div class="modal-body">
    <div class="form">
      <%= form_tag @upload_info[:url], :multipart => true do %>
        <div>Step 2 of 2</div>
        <%= hidden_field_tag :token, @upload_info[:token] %>
        <%= file_field_tag :file, title: 'Choose video to upload' %>
        <p class="uploader">
          <button class="btn btn-success ladda-button" data-color="green" data-style="expand-left"><span class="ladda-label">Upload Video</span><span class="ladda-spinner"></span></button>
        </p>
      <% end %>

    </div>
  </div>
  <div class="modal-footer">
  </div>
</div>

I guess the issue is I am not seeing how this 2nd modal would be executed, based on my upload.js.erb or am I missing something?

Edit 3

Here are the relevant portions of a server log when this upload video action is done (truncated for brevity):

Started POST "/family_trees/1/videos" for 127.0.0.1 at 2014-10-28 02:16:48 -0500
Processing by VideosController#create as JS
  Parameters: {"utf8"=>"✓", "video"=>{"title"=>"Hello there", "description"=>"Why hello there lady", "circa"=>"", "user_ids"=>[""]}, "commit"=>"Add Video", "family_tree_id"=>"1"}
  User Load (3.1ms)  SELECT  "users".* FROM "users"  WHERE "users"."id" = 1  ORDER BY "users"."id" ASC LIMIT 1
  FamilyTree Load (1.2ms)  SELECT  "family_trees".* FROM "family_trees"  WHERE "family_trees"."user_id" = $1 LIMIT 1  [["user_id", 1]]
  ReadMark Load (1.0ms)  SELECT  "read_marks".* FROM "read_marks"  WHERE "read_marks"."user_id" = $1 AND "read_marks"."readable_type" = 'PublicActivity::ORM::ActiveRecord::Activity' AND "read_marks"."readable_id" IS NULL  ORDER BY "read_marks"."id" ASC LIMIT 1  [["user_id", 1]]
  FamilyTree Load (1.0ms)  SELECT  "family_trees".* FROM "family_trees"  WHERE "family_trees"."id" = $1 LIMIT 1  [["id", 1]]
   (1.2ms)  SELECT COUNT(*) FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = $1 AND (((roles.name = 'admin') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL)))  [["user_id", 1]]
  Membership Load (1.7ms)  SELECT "memberships".* FROM "memberships"  WHERE "memberships"."user_id" = 1 AND "memberships"."family_tree_id" = 1
   (3.6ms)  BEGIN
  SQL (3.4ms)  INSERT INTO "videos" ("created_at", "description", "title", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["created_at", "2014-10-28 07:16:48.340452"], ["description", "Why hello there lady"], ["title", "Hello there"], ["updated_at", "2014-10-28 07:16:48.340452"]]
   (4.5ms)  COMMIT
Redirected to http://localhost:3000/
Completed 302 Found in 40ms (ActiveRecord: 20.6ms)

Started GET "/" for 127.0.0.1 at 2014-10-28 02:16:48 -0500
  User Load (4.0ms)  SELECT  "users".* FROM "users"  WHERE "users"."id" = 1  ORDER BY "users"."id" ASC LIMIT 1
Processing by DashboardController#index as JS

For what it's worth, even after it successfully finishes processing this GET "/" request, it doesn't actually reload/redirect the browser. It just stays at the modal.

Error in the JS console says:

Failed to load resource: net::ERR_CACHE_MISS 

Here is the VideoController#Create

  def create
    authorize! :read, @family_tree
    @video = Video.new(video_params)

    respond_to do |format|
      if @video.save
        format.html { redirect_to root_path }
        format.json { render action: 'show', status: :created, location: @video }
      else
        format.html { render action: 'new' }
        format.json { render json: @video.errors, status: :unprocessable_entity }
      end
    end

Solution

  • Change your main.js to this(I prefer this way as it is very easy to debug the issues in jQuery without getting lost in the web of methods, and it works really well with turbolinks without adding jquery-turbolinks gem for it):

    $(document).on("page:change", function(){
      MainJS.init();
    });
    
    MainJS = {
      init: function() {
        MainJS.initializePage();
        $('.popupbox').on('click', MainJS.popupBox);
        $('.profile_popupbox').on('click', MainJS.profilePopupbox);
        $('#overlay').on('click', MainJS.overLay);
        $('LI.tree-item-name').has('ul').on('click', MainJS.treeItemName);
    
        $('LI.tree-item-name li').on('click', function(ev) {
          ev.stopPropagation();
        });
      },
    
      initializePage: function(){
        $('#video').removeClass('loading');
        $('#video .box').animate({'opacity' : '1'});
        var $container = $('#video');
        $container.masonry({
          columnWidth: 299,
          itemSelector: '.box'
        });
      },
    
      popupBox: function(){
        $('.popup:visible').fadeOut();
        var id = '#'+$(this).data('popup');
        $('#overlay').fadeIn();
        $(id).css('top', $(window).height()/2 - $(id).height()/2).fadeIn();
        return false;
      },
    
      profilePopupbox: function(){
        $('.popup:visible').fadeOut();
        var id = '#'+$(this).data('popup');
        $('#overlay').fadeIn();
        // $(id).css('top', $(window).height() - $(id).height()/2).fadeIn();
        $(id).css({'top': "20px", 'left': "-200px"}).fadeIn();             
        return false;
      },
    
      overLay: function(){
        $('.popup:visible').fadeOut();
        $(this).fadeOut();
      },
    
      treeItemName: function(){
        if ($(this).hasClass('opened')) {
          $(this).find('UL').slideUp();
          $(this).removeClass('opened');
        } else {
          $(this).find('UL').slideDown();
          $(this).addClass('opened');
        }
        return false;
      }
    }
    

    Now, in case you can easily debug the above code to see which part is working/not-working, for example:

    $(document).on("page:change", function(){
      MainJS.init();
      console.log('MainJS.init() was called successfully);
    });
    

    or:

    MainJS = {
      init: function() {
        MainJS.initializePage();
        $('.popupbox').on('click', MainJS.popupBox);
        $('.profile_popupbox').on('click', MainJS.profilePopupbox);
        $('#overlay').on('click', MainJS.overLay);
        console.log('MainJS initialization was successful.');
      },
    

    and so on. You get the idea, right?

    Regarding your upload.js.erb issue, I am not sure what's wrong in there as it should work(as same thing works for me), however, you can wrap your code around: $(document).on("page:change", function(){ to see if it works, but I suspect that it is not the issue though.