2014-07-24 90 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