S
Solara2mo ago
Brian

Creating a form when I don't know how many user inputs there will be at development time

3 Replies
Brian
Brian2mo ago
Sorry for the bad formatting... Discord won't let me paste text without adding it as an attachment. I want to create a form that has a few questions for a user to answer. I want to store the questions in a database, which means that the application won't know ahead of time how many questions there will be. So let's say a have a table like:
| question_id | question_text |
|-------------|------------------------------|
| 1 | What is your name? |
| 2 | What is your quest? |
| 3 | What is your favorite color? |
| question_id | question_text |
|-------------|------------------------------|
| 1 | What is your name? |
| 2 | What is your quest? |
| 3 | What is your favorite color? |
On application start, I would expect to read these questions into a dictionary {question_id: question_text}. When constructing my form (using solara.lab.ConfirmationDialog), I would loop through the list of questions to create a prompt (solara.Text) and an input text box (solara.InputText). When the user submits the form, I want to capture the responses and write them to a table in my database like:
| submission_id | answer_id | question_id | answer_text |
|---------------|-----------|-------------|-----------------------|
| 1 | 1 | 1 | Sir Lancelot |
| 1 | 2 | 2 | I seek the Holy Grail |
| 1 | 3 | 3 | Blue |
| 2 | 4 | 1 | Sir Galahad |
| 2 | 5 | 2 | I seek the Grail |
| 2 | 6 | 3 | Blue... |
| submission_id | answer_id | question_id | answer_text |
|---------------|-----------|-------------|-----------------------|
| 1 | 1 | 1 | Sir Lancelot |
| 1 | 2 | 2 | I seek the Holy Grail |
| 1 | 3 | 3 | Blue |
| 2 | 4 | 1 | Sir Galahad |
| 2 | 5 | 2 | I seek the Grail |
| 2 | 6 | 3 | Blue... |
My issue/question is: how do I create the reactive variable(s) to capture the user input? I would expect that the value that I want to pass to the InputText should be a reactive variable (?), or else that I would create a reactive variable that is a dictionary and have it be keyed by question_id with the values being the text in the InputText. I've tried creating a dict of reactive variables and a reactive dict, but it doesn't seem to work. Here is a minimal example. It "works," except that when you click "Answer" nothing happens -- the print statements do not execute.
import pandas as pd
import solara

# assume that we have a table in a database, and
# we read the data into _something_ like this:
#
question_table = pd.DataFrame(
data=[
[1, "What is your name?"],
[2, "What is your quest?"],
[3, "What is your favorite color?"]
],
columns=["question_id", "question_text"]
)

# or else it can be treated like a list of dicts
#
questions = question_table.to_dict(orient="records")
# questions
# [{'question_id': 1, 'question_text': 'What is your name?'},
# {'question_id': 2, 'question_text': 'What is your quest?'},
# {'question_id': 3, 'question_text': 'What is your favorite color?'}]
import pandas as pd
import solara

# assume that we have a table in a database, and
# we read the data into _something_ like this:
#
question_table = pd.DataFrame(
data=[
[1, "What is your name?"],
[2, "What is your quest?"],
[3, "What is your favorite color?"]
],
columns=["question_id", "question_text"]
)

# or else it can be treated like a list of dicts
#
questions = question_table.to_dict(orient="records")
# questions
# [{'question_id': 1, 'question_text': 'What is your name?'},
# {'question_id': 2, 'question_text': 'What is your quest?'},
# {'question_id': 3, 'question_text': 'What is your favorite color?'}]
@solara.component
def BridgeOfDeath(is_open, on_ok, questions):
# normally, I would create some reactive variables here, but I don't know
# how many (apart from `len(questions)`). I also need to associate those
# reactive variables with to the question IDs so that I can save the data.

# Should it be a reactive dict, like:
# answers_by_question_id = solara.use_reactive({})

# Or a dict of reactive variables like:
answers_by_question_id = {
question["question_id"]: solara.use_reactive("")
for question in questions
}

def _on_ok():
print("_on_ok called")
on_ok(answers_by_question_id)

with solara.lab.ConfirmationDialog(
is_open,
ok=solara.Button(
label="Answer",
on_click=_on_ok,
# disabled= # if any question is unanswered
),
on_ok=_on_ok,
title="Answer",
max_width=1000,
):

for question in questions:
question_id = question["question_id"]
question_text = question["question_text"]

with solara.Card():
solara.Text(question_text)
solara.v.Spacer()
solara.InputText(
label=f"Question #{question_id}",
value=answers_by_question_id[question_id].value,
on_value=answers_by_question_id[question_id].set,
continuous_update=True,
)
@solara.component
def BridgeOfDeath(is_open, on_ok, questions):
# normally, I would create some reactive variables here, but I don't know
# how many (apart from `len(questions)`). I also need to associate those
# reactive variables with to the question IDs so that I can save the data.

# Should it be a reactive dict, like:
# answers_by_question_id = solara.use_reactive({})

# Or a dict of reactive variables like:
answers_by_question_id = {
question["question_id"]: solara.use_reactive("")
for question in questions
}

def _on_ok():
print("_on_ok called")
on_ok(answers_by_question_id)

with solara.lab.ConfirmationDialog(
is_open,
ok=solara.Button(
label="Answer",
on_click=_on_ok,
# disabled= # if any question is unanswered
),
on_ok=_on_ok,
title="Answer",
max_width=1000,
):

for question in questions:
question_id = question["question_id"]
question_text = question["question_text"]

with solara.Card():
solara.Text(question_text)
solara.v.Spacer()
solara.InputText(
label=f"Question #{question_id}",
value=answers_by_question_id[question_id].value,
on_value=answers_by_question_id[question_id].set,
continuous_update=True,
)
@solara.component
def Page():
def save_answers(answers):
print(f"Method to save answers to the database: {answers}")

bridge_of_death_is_open = solara.use_reactive(False)

with solara.Card(
"Who would cross the Bridge of Death must answer me "
"these questions three, ere the other side ye see:"
):
BridgeOfDeath(
bridge_of_death_is_open,
on_ok=save_answers,
questions=questions,
)

solara.Button(
label="Ask me the questions, I am not afraid.",
on_click=lambda: bridge_of_death_is_open.set(True),
)

Page()
@solara.component
def Page():
def save_answers(answers):
print(f"Method to save answers to the database: {answers}")

bridge_of_death_is_open = solara.use_reactive(False)

with solara.Card(
"Who would cross the Bridge of Death must answer me "
"these questions three, ere the other side ye see:"
):
BridgeOfDeath(
bridge_of_death_is_open,
on_ok=save_answers,
questions=questions,
)

solara.Button(
label="Ask me the questions, I am not afraid.",
on_click=lambda: bridge_of_death_is_open.set(True),
)

Page()
rob-vh
rob-vh2mo ago
Here is an example of a table driven editor, using a dataframe. https://py.cafe/rob.on.lists/solara-inventory-tracker-using_dummy_reactive To display the updates to the df, I use a reactive that I just increment. I could also have stored the whole dict in the reactive, but then updates (!) to the dict are not taken as changes, unless I first overwrite the reactive with None. https://py.cafe/rob.on.lists/solara-inventory-editor-only-works-first-time I believe is issue is here https://github.com/widgetti/solara/issues/245
PyCafe - Solara - Editable Inventory Tracker, updates are displayed...
Run, Edit and Share Python Apps in Your Browser with 1 click!
PyCafe - Solara - Updates show first time, but 2nd update does not ...
Run, Edit and Share Python Apps in Your Browser with 1 click!
GitHub
Event not firing when reactive variable is set · Issue #245 · widge...
I'm trying to create a solara=1.19.0 component in jupyterlab=4.0.5 with ipywidgets=8.1.0 (on Windows) I have from_date = sol.use_reactive(cast(Optional[str], None)) which will be set to (a stri...
Brian
Brian2mo ago
Thank you for this example -- I was able to get it working by following what you did. Very helpful!
Want results from more Discord servers?
Add your server