0

I'm trying to make a quiz application (non single page app) with 2 user types and this use case:

  1. the quiz-hoster-user and the participant-users are on a "waiting page" until the quiz starts
  2. the quiz-hoster-user clicks on a "quiz start"-button to start the quiz
  3. this immediately forces the all the participant-users to be redirected to a new page with the quiz question

I followed a tutorial and set up ActionCable in my app, but what I would like to know is how to achieve step 3. My current coffeescript file for my channel looks like this:

# \app\assets\javascripts\channels\quiz_data.coffee:
App.quiz_data = App.cable.subscriptions.create "QuizDataChannel",
  connected: ->
    # Called when the subscription is ready for use on the server

  disconnected: ->
    # Called when the subscription has been terminated by the server

  received: (data) ->
    # Called when there's incoming data on the websocket for this channel
    if current_student
      # HERE I want to render the page "quiz_question.html.erb"

      # ...and then I want to append the answer buttons:
      $('answer-buttons').append(data.partial)

  send_data: ->
    @perform 'send_data'

I'm pretty sure the answer is very simple, but I've googled a lot of Coffeescript and ActionCable tutorials, and almost all of them only render partials to a page that the user is already on. I'm a Rails beginner and know nothing of Coffeescript, so any help would be appreciated!

EDIT:

This is what my coffeescript file looks like after trying to follow Laiths answer:

App.quiz_data = App.cable.subscriptions.create "QuizDataChannel",
  connected: ->
    # Called when the subscription is ready for use on the server
    $('#btn btn-primary btn-lg').on 'click', (e) -> App.quiz_data.send_data()

  disconnected: ->
    # Called when the subscription has been terminated by the server

  received: (data) ->
    # Called when there's incoming data on the websocket for this channel
    if current_student
      pageHtml = data.page_html
      answerHtml = data.answer_html

      # This will replace your body with the page html string:
      $('body').html(pageHtml)

      # Then add your answer buttons:
      #$('#answer-buttons').html(answerHtml)
      $('answer-buttons').append(answerHtml)

  send_data: ->
    @perform 'send_data'

And this is the Job I created to render my partials ans broadcast them:

# app\assets\jobs\quiz_data_broadcast_job.rb:
class QuizDataBroadcastJob < ApplicationJob
  queue_as :default

  def perform(answers)
    ActionCable.server.broadcast('quiz_data', {
        page_html: render_page,
        answer_html: render_answer_options(answers)
    })
  end

  private
  def render_page
    ApplicationController.render(
        partial: 'pages/student/quiz_question',
        locals: {}
    )
  end

  def render_answer_options(answers)
    answers.each do |answer|
      ApplicationController.render(
          #render student/quiz_question page and render as many answer_option partials as needed
          partial: 'pages/student/answer_option',
          locals: {answer: answer}
      )
    end
  end
end

EDIT 2:

This is what my Javascript Console says:

Uncaught ReferenceError: App is not defined
    at quiz_data.self-21fd077347e9c34e83bab1a2d43a8db5b083fff7ed4eaa02e5314aa78f1dba8b.js:2
    at quiz_data.self-21fd077347e9c34e83bab1a2d43a8db5b083fff7ed4eaa02e5314aa78f1dba8b.js:23

And this is what is shows me when I click on it:

1    (function() {
2      App.quiz_data = App.cable.subscriptions.create("QuizDataChannel", {
3        connected: function() {
4          return $('#btn btn-primary btn-lg').on('click', function(e) {
5            return App.quiz_data.send_data();
6          });
7        },
8        disconnected: function() {},
9        received: function(data) {
10         var answerHtml, pageHtml;
11         if (current_student) {
12           pageHtml = data.page_html;
13           answerHtml = data.answer_html;
14           $('body').html(pageHtml);
15           return $('answer-buttons').append(answerHtml);
16         }
17       },
18       send_data: function() {
19         return this.perform('send_data');
20       }
21     });
22    
23   }).call(this);

EDIT 3: This is my cable.js:

// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the rails generate channel command.
//
//= require action_cable
//= require_self
//= require_tree ./channels

(function() {
  this.App || (this.App = {});

  App.cable = ActionCable.createConsumer();

}).call(this);
megahra
  • 295
  • 2
  • 19

1 Answers1

3

WebSockets aren't really designed to perform HTTP redirects. However, you can still give the perception that the page has changed through some jQuery.

Since you'll be creating both the question and answers after the host clicks start, you can send them both in your Broadcast.

I would first suggest changing the quiz_question.html.erb file to a partial: _quiz_question.html.erb, so that you can generate the HTML and append it to your body. Next, include the answer-buttons element inside your _quiz_question file, so that you can grab it with jquery.

So it would look something similar to this, but it will vary depending on your exact implementation:

# First create the question HTML partial (ensure this has the answer-
# buttons element inside it):
question_html = ApplicationController.render(partial: 
'quizzes/_quiz_question', locals: {question: @question})

# Next create the answer HTML partial:
answer_html = ApplicationController.render(partial: 
'answers/_answe_buttonr', locals: {answer: @answer})

# Broadcast both to your channel:
ActionCable.server.broadcast('QuizDataChannel', {
  question_html: question_html
  answer_html: answer_html
})

# Finally, handle this using jquery in your coffeescript:
received: (data) ->
  questionHtml = data.question_html
  answerHtml = data.answer_html

  # This will replace your body with the question html string:
  $('body').html(questionHtml)

  # Then add your answer buttons:
  $('#answer-buttons').html(answerHtml)
Laith Azer
  • 589
  • 2
  • 7
  • Thank you for the detailed suggestions @Laith. I've integrated the first part (customized for my project) into the `connected` method and the second part into the `received` method of my coffeescript file. But now I'm getting an "Uncaught ReferenceError: App is not defined" - I had removed any attempt to call App from my Ruby code after your help with my [previous question](https://stackoverflow.com/questions/44011075/rails-broadcasting-with-actioncable-uninitialized-constant-quizsessionapp-er/44014977#44014977), so I'm a bit confused. – megahra May 27 '17 at 22:32
  • 1
    Are you getting this in your javascript console or the server logs? – Laith Azer May 28 '17 at 04:00
  • Javascript Console @Laith – megahra May 28 '17 at 04:05
  • 1
    That is the correct place you should be calling `App`. (in your coffeescript, which gets transpiled to javascript). If you click on it does it show you where the error is? – Laith Azer May 28 '17 at 04:19
  • 1
    It seems like the `cable.js` file is not not being run for some reason. Is that file in your project and does it have the following: `(function() { this.App || (this.App = {}); App.cable = ActionCable.createConsumer(); }).call(this);`? – Laith Azer May 28 '17 at 19:00
  • Yes, cable.js is in app\assets\javascripts\ and contains exactly this code @Laith – megahra May 28 '17 at 22:48
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/145327/discussion-between-megahra-and-laith-azer). – megahra May 28 '17 at 22:51