Controls
ModelDataTable
If we want the state of our view to be encapsulated in a model, the DataTable control (as flet provides it right now) constitutes a challenge if our data is somewhat dynamic.
It is not easily possible to change the state of a DataTable as that is encapsulated in possibly thousands of DataCells that are only accessible via the DataRow they belong to. Even creating a DataTable is somewhat tedious because of this, although e.g. simpledt can mitigate that.
But even if it were way more ergonomic, our initial problem persists: We want the state of the DataTable to be external of it, so we need some kind of data structure outside the control to dictate what the DataTable displays.
Lucky for us, python has immaculate support for tabular data structures! You have probably heard of pandas DataFrames, and they would have been an acceptable choice for our model. But since performance is very important here and they are just as ergonomic, polars is the DataFrame library we will use.
It allows you to easily create DataFrames from JSON, Excel, CSV or even SQL statements (paired with a connection string of course). Read the details on how to do each here.
Usage
Config
ModelDataTable
inherits from flet.UserControl
,
as all custom controls should prefer to do.
It needs a lot of parameters in order for you to able
to customize as much as possible from the outside.
These parameters have been organized into two Config dataclasses:
DataTableConfig
for every parameter relating to flet.DataTable
and ModelDataTableConfig
for everything that comes on top and the ref,
because the latter has to be passed to flet.UserControl.__init__()
.
It also takes a polars.DataFrame
instance as a parameter
and makes that its initial model.
By default, ModelDataTable
creates a flet.DataTable
instance internally
with the given parameters from the DataTableConfig
instance
and wraps that in a flet.Row
,
which is then wrapped in a flet.Column
that is finally put inside a flet.Container
.
In-table search
If the dataset you want to display doesn't change
and you only need a dynamic table in order to query it,
you can set the search
field of the ModelDataTableConfig
instance to True.
This will automatically add a search bar
containing a flet.Dropdown
with all the columns of the table
and a flet.TextField
for your search input
with a convenient clear button next to it.
You can change the default column to search in by setting the
search_column_default_index
field inModelDataTableConfig
.
You now have a simple but effective and performant "contains" search for every column of your DataFrame.
Please note that when you set the model
property
in a ModelDataTable
,
it is saved as self._original_model
internally
(which is also the one being returned by the same property).
If ModelDataTableConfig.search
or ModelDataTableConfig.create_text_model
are set to True,
a self._text_model
is created as well,
which converts all original model columns to polars.Utf8
strings.
This makes it way easier to search the model
without knowledge of its exact schema
and may aid you when dealing with "truly" dynamic data as well.
Examples
Static dataset that needs to be searchable:
import flet as ft
import polars as pl
from fletched.controls import (
DataTableConfig,
ModelDataTable,
ModelDataTableConfig
)
def main(page: ft.Page) -> None:
model = pl.read_csv(
file="https://raw.githubusercontent.com/iron3oxide/ndcc/main/ndcc/data/charts.csv",
infer_schema_length=300,
)
dt_config = DataTableConfig(expand=True)
config = ModelDataTableConfig(search=True)
table = ModelDataTable(model=model, config=config, dt_config=dt_config)
page.scroll = ft.ScrollMode.ADAPTIVE
page.add(table)
page.update()
ft.app(target=main)
Dynamic dataset read from a database, refreshed on selection change:
import flet as ft
import polars as pl
from fletched.controls import (
DataTableConfig,
ModelDataTable,
ModelDataTableConfig
)
from my_app.settings import settings
def main(page: ft.Page) -> None:
def get_db_dataframe() -> pl.DataFrame:
return pl.read_sql(
sql="SELECT * FROM users;", connection_uri=settings.db_uri
)
def refresh_table(e: ft.ControlEvent):
table.model = get_db_dataframe()
model = get_db_dataframe()
dt_config = DataTableConfig(expand=True)
config = ModelDataTableConfig(on_select_changed_row=refresh_table)
table = ModelDataTable(model=model, config=config, dt_config=dt_config)
page.scroll = ft.ScrollMode.ADAPTIVE
page.add(table)
page.update()
ft.app(target=main)