Ruby on Rails Ajax call runs controller code, retu

2019-07-27 19:21发布

问题:

I have a working search mechanism on my page; a form with a text field and 'search'-labelled submit button.

I’ve also got an animated raphael.js-based item on my page, with clickable items on it that I want to result in an executed search.

On click, I send to my controller params data intended to be search terms taken from the raphael objects.

The data is being passed, and the controller code runs, but the corresponding view fails to render so the click appears to the user to do nothing.

So on click, I make an Ajax call like this:

File searchthing.js

            // Add a search function
            circle.click(function (e) {
                var csrch = this.data('label');
                // alert('clicked ' + csrch);
                e.preventDefault();
                $.ajax({
                    type: "GET",
                    url: "/bubbleBar",
                    data: $.param({ srchterm: csrch} ),
                    success: function(){
                        // window.location.replace("/pictures");
                    },
                    error: function(XMLHttpRequest, textStatus, errorThrown) {
                        alert("--- error: " + errorThrown);
                    }
                });
            });

The ‘bubbleBar’ action in my controller fires successfully, transfers the search terms, and redirects to my working index action, which handles searches:

File myController.rb:

def bubbleBarSearch
    redirect_to pictures_path(:srchterm => params[:srchterm])
end

def index
    # Initialize the pictures array
    #
    @pictures = Picture.first
    if params[:srchterm].present?

        # If the search button was hit, search pictures for the terms
        @pictures = []
        results = Picture.search(params[:srchterm], page: params[:page])
        results.each do |x|
            @pictures << x
        end

        # Also search keywords for the search terms
        #
        keywords = Keyword.search(params[:srchterm], page: params[:page])

        # Retrieve images based on keywords
        #
        keywords.each do |kw|
            pics = Picture.where(:id => kw.picture_id)
            pics.each do |pic|
                # Only add to results if unique
                #
                if !@pictures.include? pic
                    @pictures << pic
                end
            end
        end
        respond_to do |format|
            format.html {
                puts '---------------- ding -----------------'
                # Redirect_to pictures_path
            }
            format.js {alert('dong');}
        end
    else
        @pictures = Picture.all.order("RANDOM()")
    end
end

The problem I’m having is that Ajax returns success, but the corresponding view for the ‘index’ action does not render.

If I uncomment the 'window.location.replace("/pictures”);' line in my JavaScript code, I can force the render on success, but I lose the search results that the controller code got for me. If I uncomment the 'redirect_to pictures_path' line in my controller, I of course get an infinite loop that errors out.

Research I’ve do done so far tells me I should be catching the response in the controller.

I’ve not worked with respond_to before, and it appears that format.html gets hit with the Ajax call, but ALSO fires when I manually do the search using the search field/button mechanism already set up on the page. I'm unclear as to why my Ajax call comes in as HTML rather than JavaScript.

So that means if I handle the rendering of the view manually in the controller, I’m not catching the case when the search hasn’t come through the already working mechanism and running the risk of rendering a page twice or otherwise introducing malfunction into my site.

I’m lost on how to make my click/Ajax call successfully render the desired view from the click function after the controller action happens. What am I missing here? What is the ideal methodology to achieve this, and not lose the controller data I need to successfully render?

Outputs from Ruby on Rails indicate that the server believes it is rendering the proper Haml view file, however I placed a JavaScript tag at the bottom of the file to log output to console and the console is empty after a click. If I navigate to the same view through the site, the statement is output to the console.

I've modified my Ajax success callback to have a parameter, and upon examination the variable contains the HTML code for the view. So is Ruby on Rails directing the output to Ajax instead of the browser? How can I then render that output from the Ajax success callback?

Successful calls to the controller contain a utf8 parameter that is not in my Ajax call. Is this a factor?

回答1:

format.js it is not your case. I think you should go this way:

if request.xhr?
  # render data on ajax request 
else
  # render view
end


回答2:

Redirects work by returning a status code of redirected to the browser, which causes the browser to make a subsequent request to the url returned by in the response header. So, the problem is that your bubbleBarSearch action is returning with a status of redirected to your ajax call. It is not actually running the code in your index action. Why not just make a route for your index action and call it directly?

Edit - So that was wrong, I see what you are asking now. To answer your question in your reply:

Ah, I see, it looks like you want to do what AlexeyKuznetsov suggested.

Instead of respond_to do |format| you'll want if request.xhr. When you make an ajax (xhr) request, the request.xhr condition will be true, otherwise rails knows its a standard request. I think your issue is that you are not responding correctly, this will render and return whatever html it's view renders, and return it to your ajax request. You can tell rails to render differently for different types of requests. To respond with a javascript snippet, you can use render js: (http://guides.rubyonrails.org/layouts_and_rendering.html search for Rendering Vanilla JavaScript) like

if request.xhr
  render js: "alert('dong')"  # this is an ajax request
else
  puts '-------- ding ---------` # this is not

or you can render json render json:, which will pass back a json response.

if request.xhr
  render json: {message: "ding"}
else
  puts '-------- ding ---------` # this is not

then in your axax success function:

success: function(data){
  console.log(data) // will have message: ding
  // window.location.replace("/pictures");
},


回答3:

I have a working solution that accomplishes my goal. It yields the following console warning in chrome:

Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/.

However, it does render the appropriate results to my browser. If there is a more appropriate method to do this, I'd love to learn it.

I discovered that the entire HTML of the rendered page to redirect to - including the site headers and footers in application.html.haml - was being returned to the Ajax success callback, and not recognized as HTML by rails. So I am now replacing the entirety of the page body's HTML with that returned data. This was accomplished with three lines of code:

                    $.ajax({
                    type: "GET",
                    url: "/bubbleBar,
                    data: $.param({ srchterm: csrch} ),
             >      dataType: "html",
             >      success: function(response, status, request){
             >          $('body').html(response);
                    )};

I am still unsure if this - the page being 'rendered' to the success callback - is normal and expected. Also if my solution is the ideal way to change the browser to the desired page, or if there is something I could do instead to have this page rendered to the browser outside of the callback. Likely not as this is an asynchronous request.

I can imagine that this may cause problems for my site when deployed if a user is in the middle of other tasks while awaiting a search to complete, but I don't currently have a better solution. I'll be looking into restructuring the site layout to prevent such a problem.

I have also removed the respond_to block from the controller, as I could not determine any way to have it perform the action I required solely when I required it. It's possible that now that I'm aware of the dataType: ajax property, I could make a case that only this request could ping, but I was unsuccessful in determining what the appropriate response would be in any case of that statement, I only got ajax errors in all my attempts.

Thanks everyone for your suggestions, they helped me get as far as I have.