javascriptwidgetruby-on-rails-5jsonresponse

Send JSON Response Data to a Custom Widget - Ruby Rails 5.2


I'm building a widget that a client can display on their third party website. The widget accepts input from consumers about what kind of house they are looking for. On submit, the widget makes a POST request on a search model within the endpoint API (my database). Then I want to display the results of the search on the client's website.

So far I've successfully posted the search form on a third party website, and on submit a new search object is created and the search results are ready for view on my website. But I want the search results to be sent back to the client's website.

On the client's website, they have a js script that looks like this:

<script type="text/javascript" src="http://localhost:3000/searchapis/search_form.js"></script>

My routes.rb:

get 'searchapis/show'
get '/searchapis/:template', to: "searchapis#show"
post 'search/create', to: "searches#create"

I created a searchesapi controller that looks like this:

class SearchapisController < ApplicationController
  protect_from_forgery :except => :show
  
  def show
    @search = Search.new
    
    respond_to do |format|
      format.html { render params[:template], layout: 'searchapis' }
      format.js   { render js: js_constructor }
    end
  end
  
  private
    def js_constructor
      content = render_to_string(params[:template], layout: false)
      "document.write(#{content.to_json})"
    end
end

I created a search_form.html.erb view that looks like this:

<%= bootstrap_form_for @search, :html => {}, :url => create_search_url(@search), remote: true do |form| %>

<%=form.select :beds, (select_options), {prompt: "Min # of Bedrooms", hide_label: true}, {class: "form-control form-control-sm"}%>

<%=form.select :max_price, (100000..2000000).step(10000).map{|x| number_to_currency(x, precision: 0)}, {hide_label: true, prompt: "Select Max Price"}, {class: "form-control form-control-sm", id: "sale_price"}%>

<input type="submit" name="commit" value="SEARCH" class="btn btn-danger" data-disable-with="Finding Schools...">

And finally the searches_controller.rb:

class SearchesController < ApplicationController
  skip_before_action :verify_authenticity_token
  before_action :set_search, only: [:show, :edit, :update, :destroy]
  before_action :require_admin, only: [:index, :destroy]

  def create
      @search = Search.new(search_params)

      respond_to do |format|
        if @search.save

          format.html { redirect_to @search, notice: 'Search was successfully created.'}
          results = @search.listings
          p results #this is what I want to send to the third party site
          format.json { render json: results.to_json, remote: true, notice: "Search results below"}
        end
      end
  end
end

Now the form is displayed, and on submit, a new search is created, but I'm not sure how to proceed so the search results can be sent via a JSON response to the third party website.

I'm thinking I'll need to produce an additional javascript src for the client's website =(<script type="text/javascript" src="http://localhost:3000/searchapis/api_search_results.js"></script>) linked to a new api_search_results page, and I'll need to rails g migration AddNewSttributeToSearch api:boolean so I can catch an api search in the controller and route to the new api_search_results page. Then, when a consumer hits submit on the third party website, the 2nd javascript src would display the results, but I think perhaps there is a better way. Any thoughts out there?


Solution

  • I finally figured it out. On the same view that has the form, I created a table to hold the data and some javascript to display the json response. A new action "search_results" handles accepting the form data, creating the search, and returning json.

    Updated routes:

      get 'searchapis/search_results'
      post 'searchapis/search_results'
      get 'searchapis/show'
      get '/searchapis/:template', to: "searchapis#show"
    

    Updated search_form view:

    <%= bootstrap_form_for @search, :html => {id: "search_api"}, :url => searchapis_search_results_url(@search), remote: true do |form| %>
    
    <%=form.select :beds, (select_options), {prompt: "Min # of Bedrooms", hide_label: true}, {class: "form-control form-control-sm"}%>
    
    <%=form.select :max_price, (100000..2000000).step(10000).map{|x| number_to_currency(x, precision: 0)}, {hide_label: true, prompt: "Select Max Price"}, {class: "form-control form-control-sm", id: "sale_price"}%>
    
    <input type="submit" name="commit" value="SEARCH" class="btn btn-danger" data-disable-with="Finding Schools...">
    
    <% end %>
    

    The updated controller to display the form, accept the form data, create a search, and return results via json:

    class SearchapisController < ApplicationController
      protect_from_forgery :except => [:show, :search_results]
      
      def show
          @search = Search.new
          @search.api_search = true
        
        respond_to do |format|
          format.html { render params[:template], layout: 'searchapis' }
          format.js   { render js: js_constructor }
        end
      end
      
      def search_results
        @search = Search.new(search_params)
        @search.save
        @results = @search.listings
        response = @results.map{|x| [x[0].name,x[0].address,x[0].city,x[0].performance_stats.find_by(year: "1819").rating,x[1].count]}
        render json: response.to_json
      end
    
      
      private
        def js_constructor
          content = render_to_string(params[:template], layout: false)
          "document.write(#{content.to_json})"
        end
        
        #note, I had to copy search_params from the SearchesController, so perhaps the search_results method could be added to the Searches Controller to avoid this
    
        def search_params
          params.require(:search).permit(:name, :address, :city)
        end
    end
    

    SearchesController Create action:

    respond_to do |format|
          if @search.save
            if @search.api_search == true
              return
            end
           *additional routing*
          end
    end
    

    The search_form view now looks like this:

    <style>
      #hide_table {
         display:none
      }
    </style>
    
    <%= bootstrap_form_for @search, :html => {id: "search_api"}, :url => searchapis_search_results_url(@search), remote: true do |form| %>
    
    <%=form.select :beds, (select_options), {prompt: "Min # of Bedrooms", hide_label: true}, {class: "form-control form-control-sm"}%>
    
    <%=form.select :max_price, (100000..2000000).step(10000).map{|x| number_to_currency(x, precision: 0)}, {hide_label: true, prompt: "Select Max Price"}, {class: "form-control form-control-sm", id: "sale_price"}%>
    
    <input type="submit" name="commit" value="SEARCH" class="btn btn-danger" data-disable-with="Finding Schools...">
    
    <% end %>
    
    
    
    <div class="container-fluid">
        <div class="row">
            <div class="col">
                <div id='hide_table'>
                    <table class="table table-sm" id="schools_api">
                        <thead>
                            <tr>
                                <th>Name</th>
                                <th>Address</th>
                                <th>City</th>
                                <th>Score</th>
                                <th># Homes</th>
                            </tr>
                        </thead>
                        <tbody>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
    
    <script>
    document.body.addEventListener("ajax:success", (event) => {
            var response = event.detail[0];
            $("#schools_api tbody tr").remove(); 
            $('#hide_table').css("display","block");
            $.each(response, function(i, item) {
                $('<tr>').html("<td>" + item[0] + "</td><td>" + item[1] + "</td><td>" + item[2] + "</td><td>" + item[3] + "</td><td>" + item[4] + "</td>").appendTo('#schools_api');
            });
        });
    
    </script>