ActionCable - Part 2 - More Complex Example

Episode #38 by David Kimura

Summary

Extending the previous episode, we look into making a realtime poll application where users can get live feedback on voting.
view websockets render javascript rails 5:10

Resources

Summary

# vote.html.erb
<legend>Vote</legend>

<% powers = [
  ['Telekenisis', :telekenisis],
  ['Mind Control', :mind_control],
  ['Flying', :flying],
  ['Invisibility', :invisibility],
  ['Super Strength', :super_strength]] %>

<%= simple_form_for :vote do |f| %>
  <div class='form-group'>
    <%= f.label :power, label: 'Which super power would you choose?', class: 'col-xs-4' %>
    <div class='col-xs-8'>
      <%= f.input_field :power, as: :radio_buttons, collection: powers %>
    </div>
  </div>
<% end %>

<legend>Results</legend>
<div class='results'>
  <% powers.each do |label, symbol| %>
    <%= content_tag :div, class: symbol do %>
      <span><%= label %></span>
      <span class='count'>
        <div class="progress">
          <div class="progress-bar progress-bar-success" role="progressbar">0</div>
        </div>
      </span>
    <% end %>
  <% end %>
</div>

# vote.js
App.vote = App.cable.subscriptions.create("VoteChannel", {
  connected: function() {
  },

  disconnected: function() {
  },

  received: function(data) {
    var power =  data['power'];
    var count = parseInt($('.' + power + ' .count').text());  
    if (data['method'] == 'add') {
      $('.results ' + '.' + power + ' .count .progress-bar').html(count + 1);
    } else if (data['method'] == 'subtract') {
      $('.results ' + '.' + power + ' .count .progress-bar').html(count - 1);
    }

    var total_count = 0;
    $('.count .progress-bar').each( function(){
      total_count = total_count + parseInt($(this).text());
    })

    $('.count .progress-bar').each( function(){
      $(this).css('width',parseInt($(this).text()) / total_count * 100 + '%');
    })
  },

  voted: function(power) {
    return this.perform('voted', { power: power });
  },

  reduce: function(power) {
    return this.perform('reduce', { power: power });
  }
});

# vote_channel.rb
class VoteChannel < ApplicationCable::Channel
  def subscribed
    stream_from "vote"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  def voted(data)
    ActionCable.server.broadcast 'vote', power: data['power'], method: 'add'
  end

  def reduce(data)
    ActionCable.server.broadcast 'vote', power: data['power'], method: 'subtract'
  end
end

# visitors.coffee
vote = ->
  clicks = new Array
  $('input[type=radio]').change ->
    clicks.push @value
    App.vote.voted @value
    App.vote.reduce clicks[clicks.length - 2]

$(document).on 'turbolinks:load', vote

# visitors.js
// Javascript version of visitors.coffee
var vote = function(){
  var clicks = new Array();
  $('input[type=radio]').change(function() {
    clicks.push(this.value);
    App.vote.voted(this.value);
    App.vote.reduce(clicks[clicks.length - 2]);
  });
}
$(document).on('turbolinks:load', vote)