3

I take some user input that results in a table which I can display. Now I want to allow the user to select some data and then send these data back to another function where it is further processed. For the toy example I use for demonstration purposes, it might look like this:

enter image description here

Unfortunately, I fail to do so. The critical parts are probably thisjquery part (the entire code can be found below):

$('#send_data').bind('click', function() {
  $.getJSON('/_selected_data', {
    sel_data: table.rows().data().toArray()

  }, function(data) {
    alert('This worked')
  });
  return false;
});

and this part of my Python code:

@app.route('/_selected_data')
def get_selected_data():

    sel_data = request.args.get('sel_data')

When I print sel_data it prints None, when I use requests.json it does not work either; ideally it would be possible to get the results again as a pandas dataframe with the same columns headers.

How would I do this correctly? Also, if the user does not select any data, then of course the entire table should be returned.

This is the entire code:

from flask import Flask, render_template, request, jsonify
import pandas as pd
import numpy as np
import json

# just for reproducibility
np.random.seed(0)

# Initialize the Flask application
app = Flask(__name__)


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


@app.route('/_get_table')
def get_table():
    a = request.args.get('a', type=int)
    b = request.args.get('b', type=int)

    df = pd.DataFrame(np.random.randint(0, 100, size=(a, b)), columns=['C1', 'C2'])

    return jsonify(number_elements=a * b,
                   my_table=json.loads(df.to_json(orient="split"))["data"],
                   columns=[{"title": str(col)} for col in json.loads(df.to_json(orient="split"))["columns"]])


@app.route('/_selected_data')
def get_selected_data():

    sel_data = request.args.get('sel_data')
    print(sel_data)

    return jsonify(dummy_data=1)


if __name__ == '__main__':
    app.run(debug=True)

and my index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <link href="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.min.css" rel="stylesheet">
  </head>
  <body>
    <div class="container">
      <div class="header">
        <h3 class="text-muted">Create a pretty table</h3>
      </div>

      <div>
        <p>Number of rows</p>
        <input type="text" size="5" id="a" value="10">
        <p>Number of columns</p>
        <input type="text" size="5" id="b" value="2">

        <p><a href="javascript:void();" id="calculate">get a pretty table</a></p>
        <p><a href="javascript:void();" id="send_data">send selected data</a></p>
         <p>Result</p>
        <p>Number of elements:</p>
          <span id="elements">Hallo</span><br>
          <table id="a_nice_table" class="table table-striped">Here should be a table</table>
      </div>
    </div>
    <script src="https://code.jquery.com/jquery-1.12.4.js" type="text/javascript"></script>
    <script src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js" type="text/javascript"></script>
    <script type="text/javascript">
      $(document).ready(function() {
        var table = null;
        $('#calculate').bind('click', function() {
          $.getJSON('/_get_table', {
            a: $('#a').val(),
            b: $('#b').val()
          }, function(data) {
            $("#elements").text(data.number_elements);
            if (table !== null) {
              table.destroy();
              table = null;
              $("#a_nice_table").empty();
            }
            table = $("#a_nice_table").DataTable({
              data: data.my_table,
              columns: data.columns
            });
          });
          return false;
        });
        $('#send_data').bind('click', function() {
          $.getJSON('/_selected_data', {
            sel_data: table.rows().data().toArray()

          }, function(data) {
            alert('This worked')
          });
          return false;
        });
      });
    </script>
  </body>
</html>
Cleb
  • 25,102
  • 20
  • 116
  • 151

1 Answers1

2

Here is my attempted solution to your problem. I'm making the assumption that the user can only select one row at a time.

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <link href="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.min.css" rel="stylesheet">
  </head>
  <body>
    <div class="container">
      <div class="header">
        <h3 class="text-muted">Create a pretty table</h3>
      </div>

      <div>
        <p>Number of rows</p>
        <input type="text" size="5" id="a" value="10">
        <p>Number of columns</p>
        <input type="text" size="5" id="b" value="2">

        <p><a href="javascript:void();" id="calculate">get a pretty table</a></p>
        <p><a href="javascript:void();" id="send_data">send selected data</a></p>
         <p>Result</p>
        <p>Number of elements:</p>
          <span id="elements">Hallo</span><br>
          <table id="a_nice_table" class="table table-striped">Here should be a table</table>
      </div>
    </div>
    <script src="https://code.jquery.com/jquery-1.12.4.js" type="text/javascript"></script>
    <script src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js" type="text/javascript"></script>
    <script type="text/javascript">
      $(document).ready(function() {
        var table = null;
        var selected = null;
        $('#calculate').bind('click', function() {
          $.getJSON('/_get_table', {
            a: $('#a').val(),
            b: $('#b').val()
          }, function(data) {
            $("#elements").text(data.number_elements);
            if (table !== null) {
              table.destroy();
              table = null;
              $("#a_nice_table").empty();
            }
            table = $("#a_nice_table").DataTable({
              data: data.my_table,
              columns: data.columns
            });
            var drawn_table = document.getElementById('a_nice_table'),
                selected = drawn_table.getElementsByClassName('selected');
            drawn_table.onclick = highlight;
            function highlight(e) {
              if (selected[0]) selected[0].className = '';
              e.target.parentNode.className = 'selected';
            }
          });
          return false;
        });
        $('#send_data').bind('click', function() {
          var selected = $("tr.selected").html();
          if (selected != null) {
            console.log("<thead>" + $("#a_nice_table thead").html() + "</thead><tbody><tr>" + selected + "</tr></tbody>");
            $.getJSON('/_selected_data', {
              sel_data: "<thead>" + $("#a_nice_table thead").html() + "</thead><tbody><tr>" + selected + "</tr></tbody>"
            }, function(data) {
              alert('This worked');
            });
          } else {
            console.log($("#a_nice_table").html());
            $.getJSON('/_selected_data', {
              sel_data: $("#a_nice_table").html()
            }, function(data) {
              alert('This worked');
            });
          }
          return false;
        });
      });
    </script>
  </body>
</html>

app.py

from flask import Flask, render_template, request, jsonify
import pandas as pd
import numpy as np
import json

# just for reproducibility
np.random.seed(0)

# Initialize the Flask application
app = Flask(__name__)


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


@app.route('/_get_table')
def get_table():
    a = request.args.get('a', type=int)
    b = request.args.get('b', type=int)

    df = pd.DataFrame(np.random.randint(0, 100, size=(a, b)), columns=['C1', 'C2'])

    return jsonify(number_elements=a * b,
                   my_table=json.loads(df.to_json(orient="split"))["data"],
                   columns=[{"title": str(col)} for col in json.loads(df.to_json(orient="split"))["columns"]])


@app.route('/_selected_data')
def get_selected_data():

    sel_data = request.args.get('sel_data')
    df_list = pd.read_html("<table>" + sel_data + "</table>")
    df = pd.concat(df_list)
    print(df)

    return jsonify(dummy_data=1)


if __name__ == '__main__':
    app.run(debug=True)

So basically what happens is that after a table is randomly generated, I have to create a couple of variables (drawn_table and selected) to track the table and its selected row. As of now, there's no visual change to when a row is selected but it's up to you if you want to make any CSS modifications to denote when a row is selected. When a user does click a row, the row gets a class assignment of "selected", and any other row gets deselected. This is so that JQuery will know what row is selected when sending it back to the server.

Then in the send_data link, I'll find the tr (table row) element that has the "selected" class (tr.selected) and grab the html code inside this element. If the element exists, then I will send the html along with the table header info as JSON to flask. If there is no tr.selected element, then the html code is obviously undefined, so the code will run the else part of the block and send the entire table.

In app.py, I read the JSON value, which is a string of HTML code. Since Pandas has a read_html method, I can pass the HTML string to the method to create a list of dataframes. Then I concat the list of dataframes to make my final df.

You can see the result of the dataframe in Python's console log (print(df)). You can also see the html code result in your browsers inspector because I added some console.log statements.

Scratch'N'Purr
  • 9,959
  • 2
  • 35
  • 51
  • Thank you already! I can test it only in about two hours; will then get back to you including upvotes etc, – Cleb Mar 13 '18 at 11:47
  • 1
    No problem! I just reviewed my answer and added a couple of edits. Also want to credit this [SO post](https://stackoverflow.com/questions/24750623/select-a-row-from-html-table-and-send-values-onclick-of-a-button) for the code on selecting rows in a table. :) – Scratch'N'Purr Mar 13 '18 at 12:19
  • Works as advertised, thanks so much (upvoted, acceptance will come soon)! Will still take me a bit to go through it though :) – Cleb Mar 13 '18 at 14:16
  • @Cleb Great! Feel free to message me if you need a more detailed explanation. – Scratch'N'Purr Mar 13 '18 at 14:50
  • Thanks for the offer! I would not have been able to do it myself, but think I understand the code now (but there will probably soon be another question ;) ). – Cleb Mar 13 '18 at 15:01
  • Ok, there is one issue: If my selection will return more than 10 entries, it will not return the additional ones but only the 10 which are shown in the table. Do you know an easy fix for this? – Cleb Mar 13 '18 at 15:32
  • Seems, `sel_data: JSON.stringify(table.rows( { filter : 'applied'} ).data().toArray())` works fine, however, now I don't know how to access the column titles correctly (opened a [new question](https://stackoverflow.com/questions/49274691/how-to-return-all-column-names-titles-and-avoid-typeerror-table-columns)) – Cleb Mar 14 '18 at 10:13
  • Ok, looking at your question now. Might take me a couple of hours to come up with a solution since I'm also at work. – Scratch'N'Purr Mar 14 '18 at 10:17
  • Thanks, it sounds so trivial... :) – Cleb Mar 14 '18 at 10:30