简体   繁体   中英

Refreshing multiple partials with polling in Rails

Let's say I have a list of statuses that might look like this:

ul#list
  - @list_items.each do |item|
    li.loading Item #{item.id} - Status: #{item.status}
    li Item #{item.id} - Status: #{item.status}
    li Item #{item.id} - Status: #{item.status}
    li.loading Item #{item.id} - Status: #{item.status}

Which renders me:

Item 1 - Status: Loading
Item 2 - Status: Finished
Item 3 - Status: Finished
Item 4 - Status: Loading

What I would like to do is periodically poll for changes on individual list items and refresh them if the status has changed. So far I was able to get away with refreshing the whole list:

ul#list
  == render 'status_list', list_items: @list_items

Coffee:

if $('.loading').length > 0
  setInterval (=>
    @refreshListPartial()
  ), 5000

Where @refreshListPartial is an AJAX function that hits the Rails Controller which then goes on to refresh the whole list partial:

$("#list").html("<%= escape_javascript(render partial: 'status_list', locals: { list_items: @list_items } ) %>");

But how would one go in order to check the state of individual list items on the page and refresh only them? I know that React would probably be a much easier solution for this task, but is it even possible to accomplish with Rails without jumping over a dozen hoops? Another thing that came to mind is ActionCable (I'm using Rails 5), but opening a perma-connection for this functionality seems to be an overkill, I'd rather prefer polling.

Update

Just thinking out loud. So to refresh multiple partials instead of one I'll need to arrive to this in my .js.erb file:

<%- @items.each do |item| %>
  $("#list-item-<%= item.id %>").html("<%= escape_javascript(render partial: 'item', locals: { list_item: item } ) %>");
<% end %>

The view should now look like:

ul#list
  @list_items.each do |item|
    == render 'list_item', list_item: @list_item

So what's left is the ajax function that should get the ids' of the list items that are needed to be refreshed and send them to the controller as an array.

I ended up doing an extension of what I myself proposed in the question update.

Frontend code that checks whether some partials need to be refreshed based on their data-tags:

class JobRequestPartialReload
  constructor: ->
    checkForRunningJobs = ->
      if $('.poll_for_changes').length > 0
        @arr = []
        $('.poll_for_changes').each (index, element) =>
            @arr.push(element.closest('[data-id]').dataset.id)
        sendDataToRails()

    sendDataToRails = ->
      $.ajax
        url: "/jobs/refresh_list"
        method: "POST"
        data: {jobRequestList: @arr}

    setInterval (=>
      checkForRunningJobs()
    ), 10000

$(document).on 'turbolinks:load', new JobRequestPartialReload

Controller:

  def refresh_list
    ajax_data = params[:jobRequestList].map(&:to_i)
    @job_requests = JobRequest.includes(...).where(id: ajax_data)

    respond_to do |format|
      format.js
    end
  end

Finally, the JS.erb file:

<% @job_requests.each do |job| %>
  <% case job.state %>
  <% when 'initiated' %>
    // don't care
  <% when 'active' %>
    visibleStatus = $('#job-id-<%= job.id %> .status-list').text()
    if (visibleStatus == 'initiated') {
      $('#job-id-<%= job.id %>').html("<%= escape_javascript(render partial: 'job_requests/shared/job_request', locals: { job: job } ) %>");
  <% else %>
    // failed, completed, etc.
    $('#job-id-<%= job.id %>').html("<%= escape_javascript(render partial: 'job_requests/shared/job_request', locals: { job: job } ) %>");
  <% end %>
<% end %>

Answer update

I later added js code that checked whether certain partials were in the actual user viewport, and checked only them, at the rate of 5-10 seconds. This greatly reduced the number of queries each client was sending.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM