Search code examples
pythonhtmldjangoforms

Show more data button in a list of posts django


I'm practicing with Django making a web with data about LoL with the Riot API. There is a page where you can see the game history of a searched player. I'm trying to create a button that shows the in-depth data of a game on the same page without reloading because if I try to load the data for each game at first, it takes a long time to load the page.

I haven't done much about the front-end yet, so it currently looks this:

enter image description here

And the relevant part of the views.py looks like this:

def user_info(request, server, summoner_name, template='riot/user-profile.html'):

    account_id = user_account_info['accountId']

    games_list = api_interaction.get_past_games(server, account_id)

    champ_json = services.load_champ_json_session(request)

    game_summary_list = services.get_game_summary_list(games_list, champ_json)

    context = {
        'game_summary_list': game_summary_list,
        'user_account_info': user_account_info, 
        'summoner_stats': summoner_stats 
    }

    if ('load') in request.POST:
        gameId = (request.POST['load'])
        
        game_data = api_interaction.game_summary(server, gameId, champ_json)
        context['game_data'] = game_data
        return render(request, template, context)

    else:
        return render(request, template, context)

Basically what I have right now is that the button submits a form and in views.py, if a form is submitted, it will process some data and return an extra python dictionary. Then in my template I have a div that looks like this:

{% for game in game_summary_list %}
    <span class="anchor" id="{{forloop.counter}}"></span>
    <div class="container" style="border:1px solid rgb(64, 97, 71);">
        <br>
        <h4><a href="/{{user_account_info.server}}/{{user_account_info.name}}/{{game.champion_name}}">{{ game.champion_name }}</a> {{game.position}} {{game.date}} {{ game.game_id}}</h4>

        <form action="#{{forloop.counter}}" method="post">
        {% csrf_token %}
        <button name="load" type="submit" value="{{game.game_id}}" class="btn btn-info">Show game summary</button>
        </form>
        <br>
        
        <div id="load_more">
            {% if game_data.success %}
            {% if game_data.game_id == game.game_id %}
                <table class="table table-bordered">
                <thead>
                    <tr>
                        <th scope="col">Game ID</th>
                        <th scope="col">Server</th>
                        <th scope="col">When</th>
                        <th scope="col">Duration</th>
                        <th scope="col">Game Mode</th>
                        <th scope="col">Patch</th>
                    </tr>
                </thead>
        
                <tbody>
                    <tr>
                        <td>{{ game_data.gameId }}</td>
                        <td>{{ game_data.platformId }}</td>
                        <td>{{ game_data.gameCreation }}</td>
                        <td>{{ game_data.gameDuration }}</td>
                        <td>{{ game_data.gameMode }}</td>
                        <td>{{ game_data.gameVersion }}</td>
                    </tr>
                </tbody>
        
                <thead>
                    <tr>
                        <th scope="col">{{ game_data.teams.0.win }} (Blue Team)</th>
                        <th scope="col">Rank</th>
                        <th scope="col">KDA</th>
                        <th scope="col">Damage</th>
                        <th scope="col">CS</th>
                        <th scope="col">Vision Score</th>
                    
                    </tr>
                </thead>
                
                <tbody>
                    {% for player in game_data.participants|slice:":5" %}
                    <tr >
                        <td>{{ player.champion_name }}/<a href="/{{user_account_info.server}}/{{player.summoner_name}}"> {{ player.summoner_name }}</a></td>
                        <td>{{ player.tier }}</a></td>
                        <td>{{ player.stats.kills }}/{{ player.stats.deaths }}/{{ player.stats.assists }}</td>
                        <td>{{ player.stats.totalDamageDealtToChampions }}</td>
                        <td>{{ player.stats.totalMinionsKilled }}</td>
                        <td>{{ player.stats.visionScore }}</td>
                    </tr>
                    {% endfor %}
                </tbody>
        
                <thead>
                    <tr>
                        <th scope="col">{{ game_data.teams.1.win }} (Red Team)</th>
                        <th scope="col">Rank</th>
                        <th scope="col">KDA</th>
                        <th scope="col">Damage</th>
                        <th scope="col">CS</th>
                        <th scope="col">Vision Score</th>
                    
                    </tr>
                </thead>
                
                <tbody>
                    {% for player in game_data.participants|slice:"5:" %}
                        <tr>
                        <td>{{ player.champion_name }}/<a href="/{{user_account_info.server}}/{{player.summoner_name}}"> {{ player.summoner_name }}</a></td>
                        <td>{{ player.tier }}</a></td>
                        <td>{{ player.stats.kills }}/{{ player.stats.deaths }}/{{ player.stats.assists }}</td>
                        <td>{{ player.stats.totalDamageDealtToChampions }}</td>
                        <td>{{ player.stats.totalMinionsKilled }}</td>
                        <td>{{ player.stats.visionScore }}</td>
                        </tr>
                    {% endfor %}
                </tbody>
                </table>
                <br>
            {% endif %}
            {% endif %}
        </div>
    </div>
    <br>
{% endfor %}

So when the button is pressed, the page would look like this:

enter image description here

Although it works, the problem with this process is that the page reloads when the user clicks the "show game summary" button. I have "fixed" this by adding an id for each div for each game and having the form redirect to the div that corresponds. But I would like to load the data and display it without reloading. I don't even know if I'm on the right track making a form for this, so some help would be appreciated.


SOLUTION (Thanks @Jérémie RPK):

Make the button call a JavaScript function with:

<button onclick="loadGameData('{{game.game_id}}')">Show game summary</button>

Create table with style="display: none" to hide it by default. And make a empty table but with the <thead> and <tbody> with your id of preference. In my case:

  <table style="display: none" id="game-{{game.game_id}}" class="table table-bordered">
    <thead id='game-{{game.game_id}}-thead-general'>
    </thead>
    <tbody id='game-{{game.game_id}}-tbody-general'>
    </tbody>
    <thead id='game-{{game.game_id}}-thead-blue'>
    </thead>
    <tbody id='game-{{game.game_id}}-tbody-blue'>
    </tbody>
    <thead id='game-{{game.game_id}}-thead-red'>
    </thead>
    <tbody id='game-{{game.game_id}}-tbody-red'>
    </tbody>
  </table>

Use this javascript code to create the table and call the new views:

<script type="text/javascript">
  function loadGameData(gameId) {
      $.ajax({
          url : gameId, // the endpoint
          method: "GET",
          error: function(xhr, status, error) {
            alert("Server error");
        },
          success: function(gameJson){

              $('#game-'+ gameId + '-thead-general').append('<tr><th> GameId </th>'
                                                            + '<th> Server </th>'
                                                            + '<th> When </th>'
                                                            + '<th> Duration </th>'
                                                            + '<th> GameMode </th>'
                                                            + '<th> Patch </th></tr>')

              $('#game-'+ gameId + '-tbody-general').append('<tr><td>' + gameJson.gameId + '</td>'
                                                              + '<td>' + gameJson.platformId + '</td>'
                                                              + '<td>' + gameJson.gameCreation + '</td>'
                                                              + '<td>' + gameJson.gameDuration + '</td>'
                                                              + '<td>' + gameJson.gameMode + '</td>'
                                                              + '<td>' + gameJson.gameVersion + '</td></tr>')
                                                              
              $('#game-'+ gameId + '-thead-blue').append('<tr><th>' + gameJson.teams[0].win + ' (Blue Team) </th>'
                                                          + '<th> Rank </th>'
                                                          + '<th> KDA </th>'
                                                          + '<th> Damage </th>'
                                                          + '<th> CS </th>'
                                                          + '<th> Vision Score </th></tr>')
                                                          
              gameJson.participants.slice(0,5).forEach(participant => $('#game-'+ gameId + '-tbody-blue').append('<tr><td>' + participant.champion_name + ' ' + participant.summoner_name + '</td>'
                                                                                                              + '<td>' + participant.tier +  '</td>'
                                                                                                              + '<td>' +  participant.stats.kills + '/' +  participant.stats.deaths + '/' +  participant.stats.assists + '</td>'
                                                                                                              + '<td>' + participant.stats.totalDamageDealtToChampions + '</td>'
                                                                                                              + '<td>' + participant.stats.totalMinionsKilled + '</td>'
                                                                                                              + '<td>' + participant.stats.visionScore + '</td></tr>'))
              $('#game-'+ gameId + '-thead-red').append('<tr><th>' + gameJson.teams[1].win + ' (Blue Team) </th>'
                                                          + '<th> Rank </th>'
                                                          + '<th> KDA </th>'
                                                          + '<th> Damage </th>'
                                                          + '<th> CS </th>'
                                                          + '<th> Vision Score </th></tr>')
              gameJson.participants.slice(5,10).forEach(participant => $('#game-'+ gameId + '-tbody-red').append('<tr><td>' + participant.champion_name + ' ' + participant.summoner_name + '</td>'
                                                                                                              + '<td>' + participant.tier +  '</td>'
                                                                                                              + '<td>' +  participant.stats.kills + '/' +  participant.stats.deaths + '/' +  participant.stats.assists + '</td>'
                                                                                                              + '<td>' + participant.stats.totalDamageDealtToChampions + '</td>'
                                                                                                              + '<td>' + participant.stats.totalMinionsKilled + '</td>'
                                                                                                              + '<td>' + participant.stats.visionScore + '</td></tr>'))
              $('#game-'+gameId).show()
          }
      });
  }
</script>

Change the "url" to the one you want and add the new url to your urls.py to match the URL to your views.py function. Something like this:

path('path/URL/', views.getGameData, name="getGameData"),

getGameData should return a json with return JsonResponse(JSON) and remember to add from django.http import JsonResponse


Solution

  • You have to use Ajax. Use two views: the main view will send the data to display your first picture. The second will just send the data needed for you table. In your template, when you will click "Show summary", you will call asynchronously the second view, and then when the data will be received, you will update the template using JS; I advice JQuery with Django.

    Example with a single data:

    <div id="game-{{game.id}}">
        <button onclick="loadGameData({{game.id}})">Show game summary</button>
        <table style="display: none">
            <thead>
                <tr>
                    <th scope="col">Game Mode</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td class="game-mode"></td>
                </tr>
            </tbody>
        </table>
    </div>
    
    <script type="text/javascript">
        function loadGameData(gameId) {
            $.ajax({
                url: "{% url 'game-data' %}",
                method: "GET",
                error: function(xhr, status, error) {
                    alert("Server error");
                },
                success: function(gameData){
                    gameData.games.forEach(game => {
                        $('#game-'+gameId+' tbody').append('<tr><td>'+game.gameMode'+</td></tr>')
                    )}
                    $('#game-'+gameId).show()
                }
            });
        }
    </script>
    

    In your new view:

    def getGameData(request):
        return JsonResponse({
            'gameMode': 'battle'
        })