2014-07-24 117 views
1

我想在我的應用程序中實現JavaScript輪詢,但我遇到了一些問題。我幾乎跟隨這railscasts。我的問題是試圖預先發現任何新的數據。它預先提供所有舊數據和新數據,如果沒有找到任何新數據,它只是預先存儲所有舊數據。我的另一個問題是,我的setTimeout只被調用一次,即使在我試圖保持輪詢,就像他們在railscast中顯示一樣。以下是我的代碼。我在這裏做錯了什麼?Rails的JavaScript投票

polling.js

var InboxPoller; 

InboxPoller = { 
    poll: function() { 
    return setTimeout(this.request, 5000); 
    }, 
    request: function() { 
    return $.getScript($('.inbox_wrap').data('url'), { 
     after: function() { 
     $('.conversation').last().data('id') 
     } 
    }); 
    } 
}; 

$(function() { 
    if ($('.inbox_wrap').length > 0) { 
    return InboxPoller.poll(); 
    } 
}); 

polling.js.erb

$(".inbox_wrap").prepend("<%= escape_javascript(render @conversations, :locals => {:conversation => @conversation}) %>"); 
InboxPoller.poll(); 

conversations_controller.rb

class ConversationsController < ApplicationController 
    before_filter :authenticate_member! 
    helper_method :mailbox, :conversation 

    def index 
     @messages_count = current_member.mailbox.inbox({:read => false}).count 
     @conversations = current_member.mailbox.inbox.order('created_at desc').page(params[:page]).per_page(15) 
    end 

    def polling 
     @conversations = current_member.mailbox.inbox.where('conversation_id > ?', params[:after].to_i) 
    end 

    def show 
     @receipts = conversation.receipts_for(current_member).order('created_at desc').page(params[:page]).per_page(20) 

     render :action => :show 
     @receipts.mark_as_read 
    end 

    def create 
     recipient_emails = conversation_params(:recipients).split(',').take(14) 
     recipients = Member.where(user_name: recipient_emails).all 

     @conversation = current_member.send_message(recipients, *conversation_params(:body, :subject)).conversation 

     respond_to do |format| 
      format.html { redirect_to conversation_path(conversation) } 
      format.js 
     end 
    end 

    def reply 
     @receipts = conversation.receipts_for(current_member).order('created_at desc').page(params[:page]).per_page(20) 
     @receipt = current_member.reply_to_conversation(conversation, *message_params(:body, :subject)) 

     respond_to do |format| 
      format.html { conversation_path(conversation) } 
      format.js 
     end 
    end 

    private 

     def mailbox 
      @mailbox ||= current_member.mailbox 
     end 

     def conversation 
      @conversation ||= mailbox.conversations.find(params[:id]) 
     end 

     def conversation_params(*keys) 
      fetch_params(:conversation, *keys) 
     end 

     def message_params(*keys) 
      fetch_params(:message, *keys) 
     end 

     def fetch_params(key, *subkeys) 
      params[key].instance_eval do 
      case subkeys.size 
       when 0 then self 
       when 1 then self[subkeys.first] 
       else subkeys.map{|k| self[k] } 
      end 
      end 
     end 

     def check_current_subject_in_conversation 
      if !conversation.is_participant?(current_member) 
      redirect_to conversations_path 
      end 
     end 

end 

index.html.erb

<%= content_tag :div, class: "inbox_wrap", data: {url: polling_conversations_url} do %> 
    <%= render partial: "conversations/conversation", :collection => @conversations, :as => :conversation %> 
<% end %> 

_conversation.html.erb

<div id="conv_<%= conversation.id %>_<%= current_member.id %>" class="conversation" data-id="<%= conversation.id %>"> 

    <div class="conv_body"> 
     <%= conversation.last_message.body %> 
    </div> 

    <div class="conv_time"> 
     <%= conversation.updated_at.localtime.strftime("%a, %m/%e %I:%M%P") %> 
    </div> 

</div> 
+0

只要找到這個要點,一個非常好的解決方案恕我直言:https://gist.github.com/barelyknown/7391243 –

回答

3

的Javascript輪詢是非常低效的 - 基本上發送請求每隔幾秒鐘到您的服務器監聽 「更新」。儘管如此,在許多情況下,更新將是整個文件/數據批次,沒有簡潔性

如果我們必須做這樣的事情,我們總是會考慮使用更高效的技術之一,特別是SSE's或Websockets

-

上證所

你有沒有使用Server Sent Events考慮?

這些是HTML5 technology,它與Javascript輪詢非常相似 - 每隔幾秒發送一次請求。所不同的是底層的方式將這些工作 - 聽自己的「通道」(MIME類型text/event-stream - 讓你與你發送的數據確實專用)

你可以這樣調用:

#app/assets/javascript/application.js 
var source = new EventSource("your/controller/endpoint"); 
source.onmessage = function(event) { 
    console.log(event.data); 
}; 

這將允許您爲「事件偵聽器」創建endpoint

#config/routes.rb 
resources :controller do 
    collection do 
     get :event_updates #-> domain.com/controller/event_updates 
    end 
end 

您可以使用ActionController::Live::SSE類更新發送

#app/controllers/your_controller.rb 
Class YourController < ApplicationController 
    include ActionController::Live 

    def event_updates 
    response.headers['Content-Type'] = 'text/event-stream' 
    sse = SSE.new(response.stream, retry: 300, event: "event-name") 
    sse.write({ name: 'John'}) 
    sse.write({ name: 'John'}, id: 10) 
    sse.write({ name: 'John'}, id: 10, event: "other-event") 
    sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500) 
    ensure 
    sse.close 
    end 
end 

-

的WebSockets

做到這一點的最佳方法是使用websockets

的WebSockets比上證所或標準JS輪詢高效得多,因爲他們保持連接打開永遠。這意味着您可以發送/接收任何所需的更新,而無需向服務器發送不斷更新。

Websockets的問題在於安裝過程 - 在您自己的應用服務器上運行WebSocket連接非常困難,因此爲什麼很多人不這樣做。

如果您對Websockets感興趣,您可以考慮使用Pusher - 第三方websocket提供商,他們的產品有Ruby integration。我們使用了很多 - 這是一種在您的應用程序中提供「實時」更新的非常有效的方式,並且我不隸屬於他們。

+0

是的我看過推進器和websockets,並想過要走這條路線,但在這個早期階段didn'真的需要或想要走這條路。並從我的理解(我可能是錯的),但代碼將與我正在嘗試用於輪詢的代碼相同,因此使此代碼正常工作,我可以使交換機相當容易。 – iamdhunt

+0

輪詢的優點是非常簡單,在許多情況下簡單勝過複雜性。 –

0

您確定輪詢只發生一次嗎?你有沒有檢查控制檯來確定?

對我來說,它看起來像它可能會發生,因爲我看到您的JavaScript中的問題,prepends內容。你有這個:

$(".inbox_wrap").prepend("<%= escape_javascript(render @conversations, :locals => {:conversation => @conversation}) %>"); 

這實際上會在inbox_wrap div之前插入新內容。我認爲你的意圖是前置到對話列表中。爲此,您應將其更改爲這個(參考jQuery的文檔:http://api.jquery.com/prepend/):

$(".conversation").prepend("<%= escape_javascript(render @conversations, :locals => {:conversation => @conversation}) %>"); 

接下來的事情是,我假設你想在前面加上就行了,這意味着兩件事情之上的新的對話。

  1. 控制器將按日期降序返回按日期排序的新對話。
  2. 假設上述方法正確,您需要將InboxPoller中的第一個對話ID傳遞給控制器​​作爲after參數。

    $('.conversation').first().data('id') 
    

一件事

另一件事是,你可以使用原生的JavaScript功能setInterval而不是setTimeoutsetInterval定期執行給定的function,而不是setTimeout,它只做一次。這樣,在每次調用後端文件.js.erb後,您都不需要啓動InboxPoller

更新

再次,看着jQuery Documentation,它看起來像$.getScript()不傳遞任何參數回服務器。相反,如下面使用$.get

InboxPoller = { 
    poll: function() { 
    return setInterval(this.request, 5000); 
    }, 
    request: function() { 
    $.get($('.inbox_wrap').data('url'), { 
     after: function() { 
     return $('.conversation').first().data('id'); 
     } 
    }); 
    } 
}; 

另外,我覺得你需要添加.order('created_at desc')polling方法在你的控制器。

+0

我試圖做出這些更改,但問題仍然存在。必須是其他事情。而且我知道setTimeout不會再次觸發,因爲即使我打電話給警報,它只會警告一次。我把它切換到setInterval,但它又開始了。 – iamdhunt

+0

嗯它仍然只是不斷更新列表中的所有數據,當我只希望它發現任何新的數據更新。因此,如果沒有新的數據,它只是在所有數據前加上列表。這就像是忽略':'後' – iamdhunt

+0

這是你第一次提到正在發生一些更新。你的問題題爲「JavaScript輪詢」,我們可以同意這個問題已經解決。我認爲你應該爲無法顯示正確的數據創建一個新的問題。我無法真正理解你的模型,比如對話和收件箱之間的關係如何。 – San