๐Ÿ 
๐Ÿ โ€ข14mo ago

Creating a list of buttons

Hi there! I am working with a list of buttons that upon clicking the button_i, it looks up a list of strings and displays the i^th string in a text area. I cannot seem to make it work after reviewing the documentation on button and state (excellent example of the task list!) The main obstacle for me is making the on_click argument work for a list of buttons. For example,
# cell 1
num_buttons = 2
test_buttons = [mo.ui.button(value=i, label='test', on_click=lambda v:(1-v) for i in range(num_buttons)]
test_buttons

# cell 2
test_buttons[0].value

# cell 3
test_buttons[-1].value
# cell 1
num_buttons = 2
test_buttons = [mo.ui.button(value=i, label='test', on_click=lambda v:(1-v) for i in range(num_buttons)]
test_buttons

# cell 2
test_buttons[0].value

# cell 3
test_buttons[-1].value
When I try to click on either button, the output from cell 2 or cell 3 does not change, whereas if I were to create a single button, the same initialization
test_button = mo.ui.button(value=i, label='test', on_click=lambda v:(1-v)
test_button = mo.ui.button(value=i, label='test', on_click=lambda v:(1-v)
does work as expected when clicked upon. How may I modify the code or address the problem otherwise? Any guidance is much appreciated!
10 Replies
Myles Scolnick
Myles Scolnickโ€ข13mo ago
hi @๐Ÿ  , in order for ui elements to be registered in the scope of the notebook/application, they need to be bound to a variable in the global scope. at the moment (maybe in in the future), they cannot just be within a dict or list that is bound within the scope. for example: my_list = [button, button] is not sufficient. so we have built-in higher-order components to make this work. instead you will need to do:
my_list = mo.ui.array([button, button])
# or
my_dict = mo.ui.dict({"a": button, "b": button})
my_list = mo.ui.array([button, button])
# or
my_dict = mo.ui.dict({"a": button, "b": button})
here are some docs for array and dictionary
๐Ÿ 
๐Ÿ OPโ€ข13mo ago
Thanks, Myles! Dictionary worked. Thanks so much life is beautiful again ๐Ÿคฉ๐Ÿคฉ definitely missed that in the documentation and should have thought of global scope. Hi! I have a follow up question - I want to display my_dict without showing the key and dictionary view. What I can manage is mo.vstack([my_dict.elements[k] for k in my_dict.value.keys()]). But that seems to have disabled the button on_click functionality. Is there a way to do that? I wonder if itโ€™s scope issue again. Sorry typing on the phone so not writing the code view properly. Oh oddโ€ฆ it works now
Myles Scolnick
Myles Scolnickโ€ข13mo ago
There may be the odd issue with this. We are actually better support for zoom/lens/getters for a use case to add these elements, such as putting these elements into a table or nested stack like this
Akshay
Akshayโ€ข13mo ago
@๐Ÿ  , to create a higher-order element with a custom layout, you can use mo.ui.batch instead of mo.ui.dictionary: https://docs.marimo.io/api/inputs/batch.html#marimo.ui.batch Hopefully that helps! Let us know if not. Here's a code example from the linked-to docs.
user_info = mo.md(
'''
- What's your name?: {name}
- When were you born?: {birthday}
'''
).batch(name=mo.ui.text(), birthday=mo.ui.date())
user_info = mo.md(
'''
- What's your name?: {name}
- When were you born?: {birthday}
'''
).batch(name=mo.ui.text(), birthday=mo.ui.date())
user_info.value
user_info.value
๐Ÿ 
๐Ÿ OPโ€ข13mo ago
Hi @Akshay and @Myles Scolnick , thanks a lot for the followups; I haven't been able to fully dedicate time to this button issue. It turns out I had a hack it seems... and it's still not ideal. I am wondering if for the following use case, what I have is the only way. Use case: I want to create a vertical stack of N buttons, where N can vary. Each button is supposed to initiate an action according to the index of the button in the N-array. In addition, I prefer not to see "dictionary" or "array". What I have right now:
# cell 1
N=5
get_x, set_x = mo.state(-1)

# cell 2
def button_i(val):
set_x(val)
return (val-1)

# cell 3
buttons_dict = mo.ui.dictionary([
f'button_{i}': mo.ui.button(value=i, label=f'test', on_click = lambda v: button_i(v) for i in range(N)
])

# cell 4
mo.vstack([
buttons_dict.elements[k] for k in buttons_dict.value.keys()
])

# cell 5
buttons_dict
# cell 1
N=5
get_x, set_x = mo.state(-1)

# cell 2
def button_i(val):
set_x(val)
return (val-1)

# cell 3
buttons_dict = mo.ui.dictionary([
f'button_{i}': mo.ui.button(value=i, label=f'test', on_click = lambda v: button_i(v) for i in range(N)
])

# cell 4
mo.vstack([
buttons_dict.elements[k] for k in buttons_dict.value.keys()
])

# cell 5
buttons_dict
The look I'd like is in # cell 4 and not # cell 5 but unfortunately the reactivity in get_x() does not work if I only have # cell 4 without cell 5... I have tried `mo.ui.batch suggested but I could not get the dynamic change of the number of buttons according to N work. Is there something else I can try, or modify? Thank you!
Akshay
Akshayโ€ข13mo ago
Hi! I will take a look today, and will write back later.
Akshay
Akshayโ€ข13mo ago
Hi @๐Ÿ  ! I've attached an implementation using mo.ui.batch that works with dynamic N. We recently fixed a bug with batch so please make sure you're using the latest version of marimo. Let me know if you have any questions!
๐Ÿ 
๐Ÿ OPโ€ข13mo ago
Thanks! @Akshay . Out now and will take a look soon. Thank you for looking into this so promptly! It worked! Hallelujah thank you so much! I donโ€™t fully understand the code thoughโ€ฆ is this making the dictionary of buttons twice?
Akshay
Akshayโ€ข13mo ago
It's making just one dictionary of buttons. batch is similar conceptually to mo.ui.dictionary, except it lets you display the UI elements in arbitrary HTML. You can learn more at the documentation: https://docs.marimo.io/api/inputs/batch.html#marimo.ui.batch Here's a breakdown of the code I provided. This creates HTML with placeholders for the buttons:
mo.vstack(
[f"{{button_{i}}}" for i in range(N)],
)
mo.vstack(
[f"{{button_{i}}}" for i in range(N)],
)
If you expand the list comprehension, this looks like mo.vstack(["{button_0}", "{button_1"}, ...]): the braces are placeholders for UI elements {} This substitutes the buttons for the placeholders:
.batch(
**{
f"button_{i}": mo.ui.button(
value=i, label="test", on_click=lambda v: button_i(v)
)
for i in range(N)
}
)
.batch(
**{
f"button_{i}": mo.ui.button(
value=i, label="test", on_click=lambda v: button_i(v)
)
for i in range(N)
}
)
The keys are the placeholders and the values are the UI elements to substitute for those placeholders. The example in the docs might be easier to understand
๐Ÿ 
๐Ÿ OPโ€ข13mo ago
Thank you!! (I didn't get a notification so didn't see the response till now!!)