Search code examples
javascriptpythonhtmltornadorangeslider

Python tornado multiple rangesliders javascript accessing last instance


I am building a tornado application in which I want to update the values of multiple independent range sliders within a form. The problem with my current code is that I only seem to access the last instance created by my loop. What happens is, when I change the value of any of the range slides by dragging it, the JavaScript part updates the last value, not the value that corresponds to the range slides I am pulling on.

Please advise on how to solve this. I understand that something is wrong with how I index my range sliders, but I am clueless to how I solve this.

The page (scoring.html):

{% extends "base.html" %}

{% block head %}
    Scoring
{% end %}

{% block body %}
    Scoring body here.
    <form method="POST">
        {% for proposal_id in range(3) %}
            {% module Proposal(proposal_id) %}
        {% end %}
        <input type="submit" id="submit_scores" name="submit_scores", value="Submit scores">
    </form>
{% end %}

The proposal module (proposal.html):

<div class="proposal">
    Here is proposal {{ proposal_id }}. We need info on all parameters here.
    <input type="range" min="1" max="100" value="100" class="slider" id="proposal_slider_{{ proposal_id }}">
    <p>Value: <span id="score_value_{{ proposal_id }}"></span></p>
    <script>
        var slider = document.getElementById("proposal_slider_{{ proposal_id }}");
        var output = document.getElementById("score_value_{{ proposal_id }}");
        output.innerHTML = slider.value;
        slider.oninput = function() {
            output.innerHTML = slider.value;
        }
    </script>
</div>

The uimodules.py file:

import tornado.web

class Proposal(tornado.web.UIModule):
    def render(self, proposal_id):
        return self.render_string(
            "proposal.html", proposal_id=proposal_id)

Edit: A picture of what happens: enter image description here


Solution

  • These variables —

    var slider = ...
    var output = ...
    

    — are being defined multiple times in the global JS scope. So every time the loop iterates, these variables overwrite the values of the previous loop.

    So when the browser sets up the JS Document, the slider variable is the one defined in the last cycle of the loop.


    The quickest (and also the dirtiest) way to fix it is to also name these variables dynamically as per the proposal_id:

    var slider_{{ proposal_id }} = document.getElementById("proposal_slider_{{ proposal_id }}");
    var output_{{ proposal_id }} = document.getElementById("score_value_{{ proposal_id }}");
    
    output_{{ proposal_id }}.innerHTML = slider_{{ proposal_id }}.value;
    
    slider_{{ proposal_id }}.oninput = function() {
        output_{{ proposal_id }}.innerHTML = slider_{{ proposal_id }}.value;
    }
    

    There are better, more elegant solutions wherein you write a single, common JS function to deal with multiple inputs. But that require keeping that code in a JS file and loading it as a static asset.

    If you want, I can show an example of that as well.