flet
flet copied to clipboard
Updating `SearchBar.controls` doesn't trigger it's rebuild
The example of SearchBar provided in the documentation opens a static dropdown. As it apparent from the controls that ListTile titles are obtained using a loop.
ft.ListTile(title=ft.Text(f"Color {i}"), on_click=close_anchor, data=i)
for i in range(10)
]
I am not very clear about the use of for loop inside controls. Can't find any syntax in the documentation.
I want the dropdown list to change based on text entered in the text filed. My approach was to update the ListTile controls during on_change
event. However, not sure how to access the items in a loop and update the controls. My code is attached below. Please help.
from flet import *
import asyncio
class SearchApp(UserControl):
def __init__(self):
self.get_data = [
"design 1",
"design 2",
"design 3",
"design 4",
"design 5"
]
self.control_list = {}
self.filtered_list = []
super().__init__()
async def filter_table(self, e):
self.filtered_list = []
entered_text = self.control_list["search"].content.controls[0].value
for i in self.get_data:
if entered_text.lower() in i.lower() and i != "":
self.filtered_list.append(i)
# Update the TextButton here. Not sure how to do.
self.control_list["search"].content.controls[0].update()
print(self.filtered_list)
def search_ui(self):
_obj = Container(
bgcolor="white10",
border_radius=10,
height=50,
padding=10,
content=Row(
controls=[
SearchBar(
height=50,
on_change=lambda e: asyncio.run(self.filter_table(e)),
controls=[
TextButton(
text=item,
on_click=lambda e: print(item),
data=item
)
for item in self.filtered_list
]
)
]
)
)
self.control_list["search"] = _obj
return _obj
def build(self):
return Column(
controls=[
self.search_ui()
]
)
def main(page: Page):
page.window_width=500
page.horizontal_alignment = "center"
page.vertical_alignment = "top"
page.add(
SearchApp(),
)
page.update()
if __name__ == "__main__":
app(target=main)
You will notice that, my filtered list is being generated as expected. However, I am not able to update the SearchBar controls.
@ndonkoHenri,
Please check once if it is within your scope.
While trying to solve this i noticed two issues:
- All the suggestions go invisible when I type 2-3 chars (although they present when I inspect the search bar controls).
For those trying to repro:
- click on the bar, to open the search view
- type in "desi"
- while typing you will notice that the suggestions all dissapear at some point
- when modifying the suggestions/controls list, the changes are not reflected in the UI - Issue + Solution
Below is the updated code:
from flet import *
import asyncio
class SearchApp(UserControl):
def __init__(self):
super().__init__()
self.get_data = [
"design 1",
"design 2",
"design 3",
"design 4",
"design 5"
]
self.control_list = {}
self.filtered_list = []
self.searchbar_ref = Ref[SearchBar]()
async def filter_table(self, e):
self.filtered_list = []
entered_text = self.searchbar_ref.current.value
for i in self.get_data:
if entered_text.lower() in i.lower() and i != "":
self.filtered_list.append(i)
self.searchbar_ref.current.controls=[
TextButton(
text=item,
on_click=lambda e: print(item),
data=item
)
for item in self.filtered_list
]
self.update()
print(self.searchbar_ref.current.controls)
def search_ui(self):
_obj = Container(
bgcolor="white10",
border_radius=10,
height=50,
padding=10,
content=Row(
controls=[
SearchBar(
ref=self.searchbar_ref,
height=50,
on_change=lambda e: asyncio.run(self.filter_table(e)),
controls=[
TextButton(
text=item,
on_click=lambda e, item=item: print(item),
data=item
)
for item in self.get_data
]
)
]
)
)
self.control_list["search"] = _obj
return _obj
def build(self):
return Column(
controls=[
self.search_ui()
]
)
def main(page: Page):
page.window_width=500
page.horizontal_alignment = "center"
page.vertical_alignment = "top"
page.add(
SearchApp(),
)
page.update()
app(main)
Also at on_click event it prints only "design 5". No matter what is clicked.
I updated the code to fix that.
I updated the code to fix that.
Thanks, stupid me 🤦♂️.
controls can be dynamic updated if u do close_view() and open_view() on every single change. like this:
def filter_heroes(e):
query = e.control.value.lower()
search_bar.controls.clear()
filtered_heroes = [(hero["localized_name"], f"dir/img/{hero['localized_name']}.png") for hero in heroes_data if query in hero["localized_name"].lower()]
for hero_name, icon_path in filtered_heroes:
search_bar.controls.append(ft.ListTile(
leading=ft.Image(src=icon_path, width=40, height=40),
title=ft.Text(hero_name),
on_click=select_hero,
# data=hero_name
))
current_value = search_bar.value
search_bar.close_view(current_value)
search_bar.open_view()
page.update()
but it works only for 1 letter, because it loops=) and u lose an option to choose=/ (close_view is default option for choosing?)
sorry for my beautiful English
temporary workaround
import flet as ft
def main(page):
def close_anchor(e):
text = f"Color {e.control.data}"
print(f"closing view from {text}")
anchor.close_view(text)
def handle_change(e):
print(f"handle_change e.data: {e.data}")
print(f"handle_change e.data: {e.data}")
lv.controls.clear()
for i in range(6):
lv.controls.append(ft.ListTile(title=ft.Text(f"{e.data} {i}"), on_click=close_anchor, data=i))
lv.update()
def handle_submit(e):
print(f"handle_submit e.data: {e.data}")
def handle_tap(e):
print(f"handle_tap")
lv = ft.ListView()
anchor = ft.SearchBar(
view_elevation=4,
divider_color=ft.colors.AMBER,
bar_hint_text="Search colors...",
view_hint_text="Choose a color from the suggestions...",
on_change=handle_change,
on_submit=handle_submit,
on_tap=handle_tap,
controls=[
lv
# ft.ListTile(title=ft.Text(f"Color {i}"), on_click=close_anchor, data=i)
# for i in range(10)
],
)
page.add(
ft.Row(
alignment=ft.MainAxisAlignment.CENTER,
controls=[
ft.OutlinedButton(
"Open Search View",
on_click=lambda _: anchor.open_view(),
),
],
),
anchor,
)
ft.app(target=main)
Nice workaround, @bhushanrathod32 - thanks for sharing!
Using the ListView
(or any other container control, ex: Column
) as base control of the suggestions list, solves the both issues I mentioned in my last comment. One could conclude by saying that the direct children of SearchBar.controls
(the list of suggestions) should not be modified, if you expect to see real-time filtering. (abnormal behaviour to be fixed)
I think we should modify the example in the docs with yours. (Feel free to open a PR if you wish to contribute)
@kXborg, I trimmed your code to the below:
from flet import *
class SearchApp(SearchBar):
def __init__(self):
super().__init__(on_change=self.filter_table)
self.get_data = ["design 1", "design 2", "design 3", "design 4", "design 5"]
self.list_view_ref = Ref[ListView]()
self.controls = [
ListView(
ref=self.list_view_ref,
controls=[
ListTile(
title=Text(item),
on_click=lambda e, item=item: print(item),
)
for item in self.get_data
],
)
]
def filter_table(self, e: ControlEvent):
entered_text = self.value
filtered_list = []
for i in self.get_data:
if entered_text.lower() in i.lower() and i != "":
filtered_list.append(i)
self.list_view_ref.current.controls = [
ListTile(
title=Text(item),
on_click=lambda e, item=item: print(item),
)
for item in filtered_list
]
self.update()
def main(page: Page):
page.add(SearchApp())
app(main)
I will keep this issue opened for sometime again to see if I can fix that. :)
if small modify function filter_table, than can filter element of every letters regardless of place in words
def filter_table(self, e: ControlEvent):
entered_text = self.value
filtered_list = []
for i in self.get_data:
add = []
for el in entered_text:
if el.lower() in i.lower() and i != "":
add.append(True)
if len(entered_text) == add.count(True):
filtered_list.append(i)
self.list_view_ref.current.controls = [
ListTile(
title=Text(item),
on_click=lambda e, item=item: print(item),
)
for item in filtered_list
]
self.update()