Skip to content

Commit f443c20

Browse files
Gordon Shotwelljcheng5
andauthored
Add todo list example (#603)
Co-authored-by: Joe Cheng <[email protected]>
1 parent a479c32 commit f443c20

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed

shiny/examples/todo_list/app.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import shinyswatch
2+
from htmltools import css
3+
4+
from shiny import App, module, reactive, render, ui
5+
6+
app_ui = ui.page_fixed(
7+
{"class": "my-5"},
8+
shinyswatch.theme.minty(),
9+
ui.panel_title("Shiny TodoMVC"),
10+
ui.layout_sidebar(
11+
ui.panel_sidebar(
12+
ui.input_text("todo_input_text", "", placeholder="Todo text"),
13+
ui.input_action_button("add", "Add to-do"),
14+
),
15+
ui.panel_main(
16+
ui.output_text("cleared_tasks"),
17+
ui.div(id="tasks", style="margin-top: 0.5em"),
18+
),
19+
),
20+
)
21+
22+
23+
def server(input, output, session):
24+
finished_tasks = reactive.Value(0)
25+
task_counter = reactive.Value(0)
26+
27+
@output
28+
@render.text
29+
def cleared_tasks():
30+
return f"Finished tasks: {finished_tasks()}"
31+
32+
@reactive.Effect
33+
@reactive.event(input.add)
34+
def add():
35+
counter = task_counter.get() + 1
36+
task_counter.set(counter)
37+
id = "task_" + str(counter)
38+
ui.insert_ui(
39+
selector="#tasks",
40+
where="beforeEnd",
41+
ui=task_ui(id),
42+
)
43+
44+
finish = task_server(id, text=input.todo_input_text())
45+
46+
# Defining a nested reactive effect like this might feel a bit funny but it's the
47+
# correct pattern in this case. We are reacting to the `finish`
48+
# event within the `add` closure, so nesting the reactive effects
49+
# means that we don't have to worry about conflicting with
50+
# finish events from other task elements.
51+
@reactive.Effect
52+
@reactive.event(finish)
53+
def iterate_counter():
54+
finished_tasks.set(finished_tasks.get() + 1)
55+
56+
ui.update_text("todo_input_text", value="")
57+
58+
59+
# Modules to define the rows
60+
61+
62+
@module.ui
63+
def task_ui():
64+
return ui.output_ui("button_row")
65+
66+
67+
@module.server
68+
def task_server(input, output, session, text):
69+
finished = reactive.Value(False)
70+
71+
@output
72+
@render.ui
73+
def button_row():
74+
button = None
75+
if finished():
76+
button = ui.input_action_button("clear", "Clear", class_="btn-warning")
77+
else:
78+
button = ui.input_action_button("finish", "Finish", class_="btn-default")
79+
80+
return ui.row(
81+
ui.column(4, button),
82+
ui.column(8, text),
83+
class_="mt-3 p-3 border align-items-center",
84+
style=css(text_decoration="line-through" if finished() else None),
85+
)
86+
87+
@reactive.Effect
88+
@reactive.event(input.finish)
89+
def finish_task():
90+
finished.set(True)
91+
92+
@reactive.Effect
93+
@reactive.event(input.clear)
94+
def clear_task():
95+
ui.remove_ui(selector=f"div#{session.ns('button_row')}")
96+
97+
# Since remove_ui only removes the HTML the reactive effects will be held
98+
# in memory unless they're explicitly destroyed. This isn't a big
99+
# deal because they're very small, but it's good to clean them up.
100+
finish_task.destroy()
101+
clear_task.destroy()
102+
103+
# Returning the input.finish button to the parent scope allows us
104+
# to react to it in the parent context to keep track of the number of
105+
# completed tasks.
106+
#
107+
# This is a good pattern because it makes the module more general.
108+
# The same module can be used by different applications which may
109+
# do different things when the task is completed.
110+
return input.finish
111+
112+
113+
app = App(app_ui, server)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
shinyswatch

0 commit comments

Comments
 (0)