Daniel Viglione
Daniel Viglione

Reputation: 9457

hook into Rails built-in remote: true 'ajax:success' event

For a few weeks I have been avoiding the Rails UJS "Ajax helpers" provided by rails.js and remote: true. As a result, I was doing something like this:

// erb
<li>
  <button type="button" class="btn btn-default btn-md">
    <span class="glyphicon glyphicon-remove" aria-hidden="true"></span><%= tagging.tag.name %>
    <%= hidden_field_tag :id, tagging.id %>
  </button>
</li>

// js
    remove_tag: function(){
        $view.on('click','.cloud .glyphicon-remove', function(){  
            var id = $(this).next().val();              
            $.ajax({
                type: "POST",
                data: {id: id},
                url: 'taggings/destroy',
                beforeSend: function() {
                    $('.loading').show();
                },
                complete: function(){
                    $('.loading').hide();
                },
                success: function(resp){
                   alert( resp );
                }
            });
        })
    },

But it was obviously a lot of work to do this over and over. So I decided using remote: true and rails.js built-in unobtrusive features was more elegant:

 <li>
  <button type="button" class="btn btn-default btn-md">
    <%= link_to tagging, remote: true, method: :delete, class: 'remove-tag' do %>
      <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
    <% end %>
    <%= tagging.tag.name %>
  </button>
</li>

Which translates into this nice code:

<li>
  <button type="button" class="btn btn-default btn-md">
    <a class= "remove-tag" data-method="delete" data-remote="true" href="/taggings/1" rel="nofollow">
      <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
    </a>
    orange
  </button>
</li>   

Now I can invoke the beforeSend and complete but the success ajax event is not triggered. I followed the suggestion of this link. However, the 'ajax:success' is not being called:

 remove_tag: function(){
        $view.on('ajax:beforeSend', '.cloud .remove-tag', function(event, xhr, settings) {
            $('.loading').show();
        })

        $view.on('ajax:complete', '.cloud .remove-tag', function(event, xhr, settings) {
            $('.loading').hide();
        })

        $view.on('ajax:success', '.cloud .remove-tag', function(event, xhr, settings) {
            alert("It was a success!")
        })
    },

How can I invoke 'ajax:success' event?

Upvotes: 2

Views: 3574

Answers (2)

Daniel Viglione
Daniel Viglione

Reputation: 9457

I figured out the problem. My goal was to remove the unnecessary use of something like this:

$.ajax({
     type: "POST",
     data: $form.serialize(),
     url: $form.attr('action'),
     beforeSend: function() {
       $('.loading').show();
     },
     complete: function(){
       $('.loading').hide();
       $form.closest('.modal').modal('hide')
     },
     success: function(resp){
       console.log(resp);
     }
});

And by using the remote: true on the link, the Rails link_to helper generates a data-remote attribute, which the rails.js file will add an event listener to for ajax requests, therefore removing the need for the jquery $.ajax() method.

What I did not realize was that, the ajax call BY DEFAULT sets the dataType to 'script' and therefore the HTTP Request Headers Accept header is set with 'application/javascript'. Thus, the content type in the response will be :js (application/javascript). This requires you to either define a :action.js.erb file or pass a block. But if you pass a block, it will be a block evaluated as JavaScript, not JSON or TEXT! Hence, when I was sending JSON back to the browser, it got confused. It was expecting to evaluate javascript, not parse JSON and thus a parse error would occur.

$view.on('ajax:beforeSend', '.cloud .remove-tag', function(xhr, settings) {
    $('.loading').show();
})

$view.on('ajax:complete', '.cloud .remove-tag', function(xhr, status) {
    $('.loading').hide();
})

// this only triggered if you actually destroy record
$view.on('ajax:success', '.cloud .remove-tag', function(data, status, xhr) {
    $('.loading').hide();

    // JSON.parse not needed; status already parsed as json
    var data_id = JSON.parse(status['data-id']);
    $('.cloud').find("li[data-id='"+data_id['data-id']+"']").remove();
            })
$view.on('ajax:error', '.cloud .remove-tag', function(xhr, status, error) {
   console.log( error );
})

Consequently, ajax:error was being triggered and not ajax:success. When I changed the link to specify I wanted json as a response, then ajax:success was triggered.

<%= link_to tagging, remote: true, method: :delete, class: 'remove-tag', :'data-type' => 'json' do %>
  <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
<% end %>

So why did I choose to return JSON and not just use format.js and add a delete.js.erb file? Because that means I would have to reference a DOM id or class in that js.erb file because I would have lost context to the module I created. By passing the data back to the event listener, I was able to continue to work with the closure created by the javascript functions.

Upvotes: 3

7urkm3n
7urkm3n

Reputation: 6321

I do not know, whats your $view is, but example in below should be handle it.

$(document).ready(function() {
    //form id
    $('.remove-tag')
    .bind('ajax:success', function(evt, data, status, xhr) {
      //function called on status: 200 (for ex.)
      console.log('success');
      alert("It was a success!");
    })
    .bind("ajax:error", function(evt, xhr, status, error) {
      //function called on status: 401 or 500 (for ex.)
      console.log(xhr.responseText);
    });
  });

You have also 2nd option to handle it, just creating destroy.js.erb file.

console.log("Coupon | destroy.js.erb file");

$('nav').after("<div class='alert alert-danger'> Successfully Destroyed </div>");
$(".coupon_" + <%= @coupon.id %>).fadeOut(250, function(){
    $(this).remove();
});

Upvotes: 1

Related Questions