2

I have a rails4 app. Since I'm not good at js yet I TURNED OFF TURBOLINKS. I have read a bunch of articles, still I couldn't figure it out how to organize my javascript files. At the moment I have a problem with firing js code after AJAX append.

I have a list of tasks (index page). If I load the page then I can click on any of the tasks (to update them) and the bootstrap modal shows up + the following code is working, so the time is formatted and datetimepicker is available.

If I create a new task with AJAX (will be appended by create.js.erb) and then click on that certain task the modal shows up but the following code doesn't get fired. (If I click on the rest of the tasks, those are working as they are there since page load.)

How can I make this working? I want to have the code to be available on page load and page change as well. Since this is a user triggered event hopefully doesn't get fired twice. Can sby recommend me a clear good explanation/article how I should organize my js files? As I mentioned above I read a bunch of them but I just got even more confused.

js file

var ready = function() {
   $('.updatetask').on('shown.bs.modal', function (e) {
     alert('haha');
     var modalId = $(this).attr('id');
     var deadlineField = $("#" + modalId).find($('.edit-task-deadline'));
     var deadlineValue = $(deadlineField).attr('value');
     var momentDeadline = moment(deadlineValue).format('MM/DD/YYYY hh:mm A');
     $(deadlineField).val(momentDeadline);
   });

   $(function () {
     $('.new-task-deadline').datetimepicker({
      sideBySide: true,
      format: 'MM/DD/YYYY hh:mm A',
      stepping: 15,
      widgetPositioning: { vertical: 'bottom' }
    });
  });
};

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

_task.html.erb partial

  ........
    <%= link_to edit_user_task_path(id: task.id), remote: true, type: "button" do %>
          <i class="fa fa-pencil" data-toggle="modal" data-target="#updatetask_<%= task.id %>"></i>
        <% end %>
        <!--Modal for updating task -->
        <%= form_for([@user, task], method: :patch, remote: true) do |f| %>
          <div class="modal fade updatetask" id="updatetask_<%= task.id %>" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
            <div class="modal-dialog" role="document">
              <div class="modal-content" style="text-align:left">
                <div class="modal-header">
                  <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                  <h4 class="modal-title" id="myModalLabel">Edit Task</h4>
                </div>
                <div class="modal-body">
                  <div class="alert alert-danger" style="display:none">
                    <ul class="errors" style="display:none">
                      <%= render 'layouts/error_messages', object: f.object %>
                    </ul>
                  </div>
                  <div class="field form-group">
                    <% if current_user.id == task.assigner.id %>
                      <p><strong>Executor: <%= task.executor.profile.first_name %> <%= task.executor.profile.last_name %>, <%= task.executor.profile.company %></strong></p>
                    <% else %>
                      <p><strong>Assigner: <%= task.assigner.profile.first_name %> <%= task.assigner.profile.last_name %>, <%= task.assigner.profile.company %></strong></p>
                    <% end %>
                  </div>
                  <div class="field form-group">
                    <%= f.label :content %>
                    <%= f.text_area :content, class: "form-control edit-content" %>
                  </div>
                  <div class="field form-group">
                    <%= f.label :deadline %>
                    <%= f.text_field :deadline, class: "form-control edit-task-deadline" %>
                  </div>    
                </div>
                <div class="modal-footer">
                  <button type="button" class="btn btn-default" data-dismiss="modal" id="updatetaskclose">Close</button>
                    <%= f.submit "Update Task", class: 'btn btn-primary edit-task-submit', "data-sid" => current_user.id, "data-rip" => :executor_id %>
                </div>
              </div>
            </div>
          </div>
        <% end %>
        <!--Modal end for update task --> 
      </td>

create.js.erb

$("ul.errors").html("");
<% if @task.errors.any? %>
  //modal error messages get inserted via AJAX
  $('.alert-danger').show();
  $('ul.errors').show();
  <% @task.errors.full_messages.each do |message| %>
    $("ul.errors").append($("<li />").html("<%= message.html_safe %>"));
  <% end %>
<% else %>
  //hiding modal on creation and setting values to zero for optional new modal
  $('ul.errors').hide();
  $('.alert-danger').hide();
  $("#newtask").modal('hide');
  $(".task_name_company").val('');
  $(".contentarea").val('');
  $(".new-task-deadline").val('');

  //different div class for different partials + table rows get inserted into view via AJAX
  $(".newtaskinsert").prepend('<%= j render @task %>');
  $(".newtaskinsert2").prepend('<%= j render partial: "tasks/task_between", locals: { task: @task } %>');
  $("#task_<%= @task.id %>").hide().fadeIn(1000);
<% end %>

EXTRA CODE:

Here is the sample code I'm using for new task (it's easier since there is just one modal with no ID.)

I'd like to have it for updates as well both on document ready and AJAX added code. Submit must be called on the modal id not the form id.

working code for new task:

$('.new-task-submit').on('click', function (e){
  e.preventDefault();
  var localMoment = moment($('.new-task-deadline').val());
  $('.new-task-deadline').val(localMoment.toISOString());
  $('#newtask').submit();
});

my attempt for update task:

edit_task_submit($(document.body));
.....
......
function edit_task_submit($container) {
  $container.find('.edit-task-submit').on('click', function (e){
    e.preventDefault();
    var deadlineField = $(this).find($('.edit-task-deadline'));
    var localMoment = moment((deadlineField).val());
    deadlineField.val(localMoment.toISOString());
    alert(deadlineField.val());
    $(this).submit();
  });
}

update.js.erb

<% else %>
  $('ul.errors').hide();
  $('.alert-danger').hide();
  $('#updatetask_<%= @task.id %>').modal('hide');

  $task = $('<%= j render @task %>');
  edit_task_submit($task);

  $('#task_<%= @task.id %>').fadeOut(400, function(){
      $(this).remove();
      //$(".newtaskinsert").prepend('<%= j render @task %>');
      $(".newtaskinsert").prepend('$task');
      $(".newtaskinsert2").prepend('<%= j render partial: "tasks/task_between", locals: { task: @task } %>');
  });
<% end %>

enter image description here

Sean Magyar
  • 2,360
  • 1
  • 25
  • 57
  • Can you show HTML code? – Inpego Jan 08 '16 at 02:41
  • I attached the html.erb/js.erb as well. The problem is the lack of my js knowledge. I can't find a good description how to include the different js codes like: page specific js/ code that should be always available on document ready/ user triggered js/ after AJAX js. The hardest for me is this after AJAX js. Either can't make them fire or events fire several times. Because of the AJAX calls in different models I had to turn of Turbolinks, some events got fired 4times.. But as I mentioned can't make them work w/ no turbolinks either. Good description/explanation would be much appreciated. – Sean Magyar Jan 08 '16 at 03:17

1 Answers1

2

$(document).ready executed once, when the page is loaded. create.js.erb creates new elements, for which this function is not applied.

Reorganize your code as follows:

js file:

function updatetask($container)
{
  $container.find('.updatetask').on('shown.bs.modal', function (e)
  {
    var deadlineField = $(this).find('.edit-task-deadline');
    var deadlineValue = deadlineField.val();
    var momentDeadline = moment(deadlineValue).format('MM/DD/YYYY hh:mm A');
    deadlineField.val(momentDeadline);
  });
}

function new_task_deadline($container)
{
  $container.find('.new-task-deadline').datetimepicker({
    sideBySide: true,
    format: 'MM/DD/YYYY hh:mm A',
    stepping: 15,
    widgetPositioning: { vertical: 'bottom' }
  });
}

$(function () // shortcut for $(document).ready
{
  updatetask($(document.body));
  new_task_deadline($(document.body));
});

create.js.erb:

$("ul.errors").html("");
<% if @task.errors.any? %>
  //modal error messages get inserted via AJAX
  $('.alert-danger').show();
  $('ul.errors').show();
  <% @task.errors.full_messages.each do |message| %>
    $("ul.errors").append($("<li />").html("<%= message.html_safe %>"));
  <% end %>
<% else %>
  //hiding modal on creation and setting values to zero for optional new modal
  $('ul.errors').hide();
  $('.alert-danger').hide();
  $("#newtask").modal('hide');
  $(".task_name_company").val('');
  $(".contentarea").val('');
  $(".new-task-deadline").val('');

  // time formatting and datetimepicker for new elements
  $task = $('<%= j render @task %>');
  updatetask($task);
  new_task_deadline($task);

  //different div class for different partials + table rows get inserted into view via AJAX
  $(".newtaskinsert").prepend($task);
  $(".newtaskinsert2").prepend('<%= j render partial: "tasks/task_between", locals: { task: @task } %>');
  $("#task_<%= @task.id %>").hide().fadeIn(1000);
<% end %>

For update:

edit_task_submit($(document.body));

function edit_task_submit($container)
{
  $container.find('.edit-task-submit').on('click', function (e)
  {
    e.preventDefault();
    var $this = $(this);
    var deadlineField = $this.closest('form').find('.edit-task-deadline');
    var localMoment = moment(deadlineField.val());
    deadlineField.val(localMoment.toISOString());
    $this.click();
  });
}

update.js.erb:

<% else %>
  $('ul.errors').hide();
  $('.alert-danger').hide();
  $('#updatetask_<%= @task.id %>').modal('hide');

  $task = $('<%= j render @task %>');
  edit_task_submit($task);

  $('#task_<%= @task.id %>').fadeOut(400, function()
  {
    $(this).remove();
    $(".newtaskinsert").prepend($task);
    $(".newtaskinsert2").prepend('<%= j render partial: "tasks/task_between", locals: { task: @task } %>');
  });
<% end %>
Inpego
  • 2,657
  • 13
  • 14
  • Inpego, I try it out right away and get back soon with the result. In the meantime, could you suggest me some readings on this topic? – Sean Magyar Jan 08 '16 at 04:06
  • This is my own approach; for `on` you can also use http://stackoverflow.com/questions/10604328/jquery-how-to-replace-live-with-on, but if you want use datepicker advantages immediately after task creation, you must call `.datepicker` for newly created elements; otherwise, you can call `.datepicker` in `on` function. – Inpego Jan 08 '16 at 04:22
  • Inpego, it works THX a lot! I read what you sent. Could you plz send some more pieces regarding the stuff I mentioned below the original code? I am confused when it comes to organizing js. I don't totally get when I should use document ready or 'page:load' / how to use page specific js / etc. I guess there should be a nice guide somewhere on the web (unfortunately I couldn't find it yet) since this is pretty essential for every rails app nowadays. – Sean Magyar Jan 08 '16 at 05:07
  • Official guides: http://guides.rubyonrails.org/working_with_javascript_in_rails.html, https://learn.jquery.com/ Free e-books: http://www.sitepoint.com/top-10-free-jquery-ebooks/. Hope this helps. – Inpego Jan 08 '16 at 05:15
  • Thx Inpego! Last question. I forgot to include some part of the code. I tried to pull it off based on your code, but I couldn't. I guess because I'm confused about the usage of 'this'. With the code I included the alert(deadlineField.val()); doesn't run at all. Could you help me out for last time? – Sean Magyar Jan 08 '16 at 05:47
  • From [documentation](http://api.jquery.com/on/): "When jQuery calls a handler, the `this` keyword is a reference to the element where the event is being delivered; for directly bound events this is the element where the event was attached and for delegated events this is an element matching `selector`. (Note that `this` may not be equal to `event.target` if the event has bubbled from a descendant element.) To create a jQuery object from the element so that it can be used with jQuery methods, use `$( this )`." – Inpego Jan 08 '16 at 05:55
  • Can you show me in code? I edited my question and passed some extra code. I guess it would take 1min for you with the code sample. Thx, for being so patient with me. – Sean Magyar Jan 08 '16 at 05:59
  • It can't find the proper ('.edit-task-submit) or ('.edit-task-deadline'). When I alert(localMoment); the value is wrong. I only tried the document ready version, but there are a bunch of ('.edit-task-submit). Every fa-pencil (pic attached) has one since every task has a hidden modal. Should it found based on $(this).find('.edit-task-deadline'); ? – Sean Magyar Jan 08 '16 at 06:27
  • P.S. to prev comment: alert(deadlineField.val()); is undefined. – Sean Magyar Jan 08 '16 at 06:36
  • I missed that `.edit-task-submit` is a button, not a form, see update – Inpego Jan 08 '16 at 06:50
  • 1
    Thanks a lot Inpego! Yeah I saw 'find' won't work but I tried this var deadlineField = $(this).closest($('.edit-task-deadline')); instead of your code. As I see I have to catch up with the jquery/js. – Sean Magyar Jan 08 '16 at 07:04
  • `var deadlineField = $(this).closest($('.edit-task-deadline'));` works? – Inpego Jan 08 '16 at 08:36
  • yes, 'find' seems to be working both with ($('.edit-task-deadline')); and ('.edit-task-deadline). I will refactor the code to be consistent, since both version can be found in the current code. – Sean Magyar Jan 08 '16 at 18:03