-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathpaginated_dt.py
274 lines (234 loc) · 10.2 KB
/
paginated_dt.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# DOWNLOAD MY FILE paginated_dt.py
# FROM THIS VIDEO DESCRIPTION
# paginated_dt.py is FOR CREATE YOU DATATABEL
# WITH PAGE AND ROWS
import flet as ft
"""
PaginatedDataTable is based on flet.UserControl.
Feel free to modify it entirely so it could fit your likings and/or needs.
`How to use:`
After initializing the object, you can access the DataTable, DataRow, or DataColumn instances
using the following attributes:
- `datatable`
- `datarows`
- `datacolumns`
"""
class PaginatedDataTable(ft.UserControl):
# a default number of rows per page to be used in the data table
DEFAULT_ROW_PER_PAGE = 5
def __init__(
self,
datatable: ft.DataTable,
table_title: str = "Default Title",
rows_per_page: int = DEFAULT_ROW_PER_PAGE,
):
"""
A customized user control which returns a paginated data table. It offers the possibility to organize data
into pages and also define the number of rows to be shown on each page.
:parameter datatable: a DataTable object to be used
:parameter table_title: the title of the table
:parameter rows_per_page: the number of rows to be shown per page
"""
super().__init__()
self.dt = datatable
self.title = table_title
self.rows_per_page = rows_per_page
# number of rows in the table
self.num_rows = len(datatable.rows)
self.current_page = 1
# Calculating the number of pages.
p_int, p_add = divmod(self.num_rows, self.rows_per_page)
self.num_pages = p_int + (1 if p_add else 0)
# will display the current page number
self.v_current_page = ft.Text(
str(self.current_page),
tooltip="Double click to set current page.",
weight=ft.FontWeight.BOLD
)
# textfield to go to a particular page
self.current_page_changer_field = ft.TextField(
value=str(self.current_page),
dense=True,
filled=False,
width=40,
on_submit=lambda e: self.set_page(page=e.control.value),
visible=False,
keyboard_type=ft.KeyboardType.NUMBER,
content_padding=2,
text_align=ft.TextAlign.CENTER
)
# gesture detector to detect double taps of its contents
self.gd = ft.GestureDetector(
content=ft.Row(controls=[self.v_current_page, self.current_page_changer_field]),
on_double_tap=self.on_double_tap_page_changer,
)
# textfield to change the number of rows_per_page
self.v_num_of_row_changer_field = ft.TextField(
value=str(self.rows_per_page),
dense=True,
filled=False,
width=40,
on_submit=lambda e: self.set_rows_per_page(e.control.value),
keyboard_type=ft.KeyboardType.NUMBER,
content_padding=2,
text_align=ft.TextAlign.CENTER
)
# will display the number of rows in the table
self.v_count = ft.Text(weight=ft.FontWeight.BOLD)
self.pdt = ft.DataTable(
columns=self.dt.columns,
rows=self.build_rows()
)
@property
def datatable(self) -> ft.DataTable:
return self.pdt
@property
def datacolumns(self) -> list[ft.DataColumn]:
return self.pdt.columns
@property
def datarows(self) -> list[ft.DataRow]:
return self.dt.rows
def set_rows_per_page(self, new_row_per_page: str):
"""
Takes a string as an argument, tries converting it to an integer, and sets the number of rows per page to that
integer if it is between 1 and the total number of rows, otherwise it sets the number of rows per page to the
default value
:param new_row_per_page: The new number of rows per page
:type new_row_per_page: str
:raise ValueError
"""
try:
self.rows_per_page = int(new_row_per_page) \
if 1 <= int(new_row_per_page) <= self.num_rows \
else self.DEFAULT_ROW_PER_PAGE
except ValueError:
# if an error occurs set to default
self.rows_per_page = self.DEFAULT_ROW_PER_PAGE
self.v_num_of_row_changer_field.value = str(self.rows_per_page)
# Calculating the number of pages.
p_int, p_add = divmod(self.num_rows, self.rows_per_page)
self.num_pages = p_int + (1 if p_add else 0)
self.set_page(page=1)
# self.refresh_data()
def set_page(self, page: [str, int, None] = None, delta: int = 0):
"""
Sets the current page using the page parameter if provided. Else if the delta is not 0,
sets the current page to the current page plus the provided delta.
:param page: the page number to display
:param delta: The number of pages to move forward or backward, defaults to 0 (optional)
:return: The current page number.
:raise ValueError
"""
if page is not None:
try:
self.current_page = int(page) if 1 <= int(page) <= self.num_pages else 1
except ValueError:
self.current_page = 1
elif delta:
self.current_page += delta
else:
return
self.refresh_data()
def next_page(self, e: ft.ControlEvent):
"""sets the current page to the next page"""
if self.current_page < self.num_pages:
self.set_page(delta=1)
def prev_page(self, e: ft.ControlEvent):
"""set the current page to the previous page"""
if self.current_page > 1:
self.set_page(delta=-1)
def goto_first_page(self, e: ft.ControlEvent):
"""sets the current page to the first page"""
self.set_page(page=1)
def goto_last_page(self, e: ft.ControlEvent):
"""sets the current page to the last page"""
self.set_page(page=self.num_pages)
def build_rows(self) -> list:
"""
Returns a slice of indexes, using the start and end values returned by the paginate() function
:return: The rows of data that are being displayed on the page.
"""
return self.dt.rows[slice(*self.paginate())]
def paginate(self) -> tuple[int, int]:
"""
Returns a tuple of two integers, where the first is the index of the first row to be displayed
on the current page, and `the second the index of the last row to be displayed on the current page
:return: A tuple of two integers.
"""
i1_multiplier = 0 if self.current_page == 1 else self.current_page - 1
i1 = i1_multiplier * self.rows_per_page
i2 = self.current_page * self.rows_per_page
return i1, i2
def build(self):
return ft.Card(
ft.Container(
ft.Column(
[
ft.Text(self.title, style=ft.TextThemeStyle.HEADLINE_SMALL),
self.pdt,
ft.Row(
[
ft.Row(
controls=[
ft.IconButton(
ft.icons.KEYBOARD_DOUBLE_ARROW_LEFT,
on_click=self.goto_first_page,
tooltip="First Page"
),
ft.IconButton(
ft.icons.KEYBOARD_ARROW_LEFT,
on_click=self.prev_page,
tooltip="Previous Page"
),
self.gd,
ft.IconButton(
ft.icons.KEYBOARD_ARROW_RIGHT,
on_click=self.next_page,
tooltip="Next Page"
),
ft.IconButton(
ft.icons.KEYBOARD_DOUBLE_ARROW_RIGHT,
on_click=self.goto_last_page,
tooltip="Last Page"
),
]
),
ft.Row(
controls=[
self.v_num_of_row_changer_field, ft.Text("rows per page")
]
),
self.v_count,
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN
),
],
scroll=ft.ScrollMode.AUTO
),
padding=10,
),
elevation=5,
)
def on_double_tap_page_changer(self, e):
"""
Called when the content of the GestureDetector (gd) is double tapped.
Toggles the visibility of gd's content.
"""
self.current_page_changer_field.value = str(self.current_page)
self.v_current_page.visible = not self.v_current_page.visible
self.current_page_changer_field.visible = not self.current_page_changer_field.visible
self.update()
def refresh_data(self):
# Setting the rows of the paginated datatable to the rows returned by the `build_rows()` function.
self.pdt.rows = self.build_rows()
# display the total number of rows in the table.
self.v_count.value = f"Total Rows: {self.num_rows}"
# the current page number versus the total number of pages.
self.v_current_page.value = f"{self.current_page}/{self.num_pages}"
# update the visibility of controls in the gesture detector
self.current_page_changer_field.visible = False
self.v_current_page.visible = True
# update the control so the above changes are rendered in the UI
self.update()
def did_mount(self):
self.refresh_data()