How can I render new Template with Flask Socket-IO join event?

Question:

I’m programming a litte multiplayer game with Flask Socket-IO. If the client is on the website, he connects to the websocket. On the start page the client should type a nickname in the field and press the button. Then the client tries to join a room, if its possible to join the room, I want to render a new html file or redirect to an new site if the join worked.

Here is my try to programm this join event. Just ignore my prints.

@socketio.on('join')
def handle_join(data):
    nickname = data['nickname']
    room = data['room']
    if room not in room_clients:
        room_clients[room] = 0
    if room_clients[room] >= MAX_CLIENTS_PER_ROOM:
        print("Raum ist voll:"+ str(room_clients[room]))
        emit('room_full', {'room': room})
    else:
        print("Es sind "+ str(room_clients[room]) +" Leute im Raum!")
        if room in room_states and room_states[room] == 'running':
            print("Das Spiel läuft bereits")
            emit('room_running', {'room': room})
        else:
            print("Du kannst dem Raum beitreten. Mit dir sind "+ str(room_clients[room]+1)+" Leute im Raum.")
            join_room(room)
            room_clients[room] += 1
            emit('joined', {'nickname': nickname, 'room': room}, room=room)
            return render_template('game.html')

My index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Start</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
    <script src="{{ url_for('static', filename='js/script.js')}}"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.6.1/socket.io.js" integrity="sha512-xbQU0+iHqhVt7VIXi6vBJKPh3IQBF5B84sSHdjKiSccyX/1ZI7Vnkt2/8y8uruj63/DVmCxfUNohPNruthTEQA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>
<body>
<script type="text/javascript">
  var socket = io.connect('localhost:5000');
  var sessionId = socket.id;

  document.cookie = 'session_id=' + sessionId + '; expires=' + new Date(Date.now() + 3600000);

  socket.on('connect', function() {
    console.log(socket.id)
  });
  socket.on('joined', function(data) {
    console.log(data['nickname'] + " joined the " + data['room']);
  });
  function joinroom(){
    var nickname = document.getElementById("nickname").value;
    socket.emit('join', {nickname: nickname, room : 'room1'})
  }
</script>
  <div id="home">
    <div class ="logo-big">
      <a href="./start">
        <img src="{{ url_for('static', filename='images/logo.png')}}">
      </a>
    </div>
    <div class ="panels">
      <div class="panel-left"></div>
      <div class="panel">
        <form onsubmit="joinroom()">
          <div class="container-name">
            <input class="input-name" id="nickname" type="text" placeholder="Enter your name" maxlength="21" data-translate="placeholder" required>
          </div>
            <button id="playbtn" class="button-play" type="submit">
            <span>Play!</span>
          </button>
        </form>
      </div>
      <div class="panel-right">
      </div>
    </div>
    <div class="bottom"></div>
  </div>
</body>
</html>

In the moment it does not work. After clicking the button and trigger the join event, there is now update of the View.

The full flask app:

from flask import Flask, render_template, request
from flask_socketio import SocketIO, send, emit, join_room, leave_room

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app, cors_allowed_origins="*")

MAX_CLIENTS_PER_ROOM = 4
room_clients = {}
room_states = {}

@app.route("/start")
def start():
    return render_template('index.html')

@app.route("/game")
def game():
    return render_template('game.html')
  
@socketio.on('connect')
def handle_connect():
    print('Client connected!')

@socketio.on('disconnect')
def handle_disconnect():
    print("Client disconnected!")

@socketio.on('join')
def handle_join(data):
    nickname = data['nickname']
    room = data['room']
    if room not in room_clients:
        room_clients[room] = 0
    if room_clients[room] >= MAX_CLIENTS_PER_ROOM:
        print("Raum ist voll:"+ str(room_clients[room]))
        emit('room_full', {'room': room})
    else:
        print("Es sind "+ str(room_clients[room]) +" Leute im Raum!")
        if room in room_states and room_states[room] == 'running':
            print("Das Spiel läuft bereits")
            emit('room_running', {'room': room})
        else:
            print("Du kannst dem Raum beitreten. Mit dir sind "+ str(room_clients[room]+1)+" Leute im Raum.")
            join_room(room)
            room_clients[room] += 1
            emit('joined', {'nickname': nickname, 'room': room}, room=room)
            return render_template('game.html')

@socketio.on('leave')
def handle_leave(data):
    nickname = data['nickname']
    room = data['room']
    leave_room(room)
    room_clients[room] -= 1
    if room_clients[room] == 0:
        del room_clients[room]
        del room_states[room]
    emit('left', {'nickname': nickname, 'room': room}, room=room)
    
if __name__ == '__main__':
    socketio.run(app)
Asked By: luca

||

Answers:

The Socket.IO event handlers are not Flask routes, you cannot return HTML from them. Normally Socket.IO apps are single-page apps. You should implement the page updates that you want through JavaScript code in the client.

One way you can do this is to have your main Flask template render both the initial and game views, but the game view is initially hidden. Then once the game starts you can hide the initial view and display the game view.

Answered By: Miguel Grinberg