-
-
Notifications
You must be signed in to change notification settings - Fork 798
Added table widget to web backend #3425
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
1e345ac
979333b
1e515a6
aaac0a7
0ca9ef0
eceef8f
15c47a7
2f784a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| The Web backend now supports Table widgets. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| import warnings | ||
|
|
||
| import toga | ||
| from toga_web.libs import create_proxy | ||
|
|
||
| from .base import Widget | ||
|
|
||
|
|
||
| # placeholder from gtk | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to describe provenance here; it's either a common utility (in which case it should be factored out into core), or it's a standalone platform-specific implementation. In this case, I'd lean to the latter. |
||
| class TogaRow: | ||
| def __init__(self, value): | ||
| super().__init__() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there's no base class, the call to super() is a no-op. |
||
| self.value = value | ||
|
|
||
| # All paths return none as Icon is not implemented in web. | ||
| def icon(self, attr): | ||
| data = getattr(self.value, attr, None) | ||
| if isinstance(data, tuple): | ||
| if data[0] is not None: | ||
| return None | ||
| return None | ||
| else: | ||
| try: | ||
| return None | ||
| except AttributeError: | ||
| return None | ||
|
|
||
| def text(self, attr, missing_value): | ||
| data = getattr(self.value, attr, None) | ||
|
|
||
| if isinstance(data, toga.Widget): | ||
| warnings.warn("Web does not support the use of widgets in cells") | ||
| text = None | ||
| elif isinstance(data, tuple): | ||
| text = data[1] | ||
| else: | ||
| text = data | ||
|
|
||
| if text is None: | ||
| return missing_value | ||
|
|
||
| return str(text) | ||
|
|
||
|
|
||
| class Table(Widget): | ||
| def create(self): | ||
|
|
||
| self.native = self._create_native_widget( | ||
| "div", classes=["toga-table-container"] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why this class? It's not a container, and |
||
| ) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this outer container required? Why can the |
||
|
|
||
| self.table = self._create_native_widget( | ||
| "table", | ||
| ) | ||
| self.native.appendChild(self.table) | ||
|
|
||
| self.table_header_group = self._create_native_widget( | ||
| "thead", classes=["table-header"] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again - why the class here? |
||
| ) | ||
| self.table.appendChild(self.table_header_group) | ||
|
|
||
| self.table_body = self._create_native_widget("tbody", classes=["table-body"]) | ||
| self.table.appendChild(self.table_body) | ||
|
|
||
| def change_source(self, source): | ||
| self.selection = {} | ||
|
|
||
| # remove old table data | ||
| for row_child in list(self.table_body.children): | ||
| for td_child in list(row_child.children): | ||
| row_child.removeChild(td_child) | ||
| self.table_body.removeChild(row_child) | ||
|
|
||
| for row_child in list(self.table_header_group.children): | ||
| for td_child in list(row_child.children): | ||
| row_child.removeChild(td_child) | ||
| self.table_header_group.removeChild(row_child) | ||
|
|
||
| if source is not None: | ||
| self._create_table_headers() | ||
|
|
||
| for i, row in enumerate(source): | ||
| self._create_table_row(row, i) | ||
|
|
||
| # set table here | ||
| self.refresh() | ||
|
|
||
| def get_selection(self): | ||
| selection = sorted(self.selection) | ||
| if self.interface.multiple_select: | ||
| return selection | ||
| elif len(selection) == 0: | ||
| return None | ||
| else: | ||
| return selection[0] | ||
|
|
||
| def add_selection(self, index, table_row): | ||
| self.selection[index] = table_row | ||
| table_row.style.backgroundColor = "lightblue" | ||
| # set colour | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be better to do this with a stylesheet - add a We should probably also get a better color match than CSS |
||
|
|
||
| def remove_selection(self, index): | ||
| table_row = self.selection.pop(index) | ||
| table_row.style.backgroundColor = "" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added bonus here if you use a style-based approach - you can delete a class and have all the style changes re-apply. |
||
|
|
||
| def clear_selection(self): | ||
| for index in list(self.selection): | ||
| self.remove_selection(index) | ||
|
|
||
| def _create_table_headers(self): | ||
| if self.interface.headings: | ||
| headings = self.interface.headings | ||
| else: | ||
| headings = self.interface.accessors | ||
| self.table_header_row = self._create_native_widget( | ||
| "tr", classes=["table-header-row"] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the class needed here? It can already be targeted with |
||
| ) | ||
|
|
||
| for heading in headings: | ||
| th = self._create_native_widget("th", content=heading) | ||
| self.table_header_row.appendChild(th) | ||
|
|
||
| self.table_header_group.appendChild(self.table_header_row) | ||
|
|
||
| def _create_table_row(self, item, index): | ||
| row = TogaRow(item) | ||
| values = [] | ||
| for accessor in self.interface.accessors: | ||
| values.extend( | ||
| [ | ||
| # Removed icon accessor for now as not sure how to handle icon | ||
| # row.icon(accessor), | ||
| row.text(accessor, self.interface.missing_value), | ||
| ] | ||
| ) | ||
| tr = self._create_native_widget( | ||
| "tr", | ||
| ) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's no need for this to be split over 3 lines. Drop the comma, and black will collapse the definition. |
||
|
|
||
| tr.addEventListener( | ||
| "click", create_proxy(lambda event: self.dom_row_click(event, index, tr)) | ||
| ) | ||
|
|
||
| for value in values: | ||
| td = self._create_native_widget("td", content=value) | ||
| tr.appendChild(td) | ||
| self.table_body.appendChild(tr) | ||
|
|
||
| def dom_row_click(self, event, index, table_row): | ||
| print("row_click listener! row:", index) | ||
| if index in self.selection: | ||
| self.remove_selection(index) | ||
| print("removing row ", index, " from selection") | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can delete these stray debug lines |
||
| else: | ||
| if not self.interface.multiple_select: | ||
| self.clear_selection() | ||
| self.add_selection(index, table_row) | ||
| print("adding row ", index, " to selection") | ||
|
|
||
| # if self.interface.on_select: | ||
| # self.interface.on_select(self.interface) | ||
|
|
||
| def insert(self, index, item): | ||
| self.change_source(self.interface.data) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This clearly works; but is it not possible to do a more selective index-based child insert? Similarly for remove and change. |
||
|
|
||
| def clear(self): | ||
| self.change_source(self.interface.data) | ||
|
|
||
| def change(self, item): | ||
| self.change_source(self.interface.data) | ||
|
|
||
| def remove(self, index, item): | ||
| self.change_source(self.interface.data) | ||
|
|
||
| def insert_column(self, index, heading, accessor): | ||
| self.change_source(self.interface.data) | ||
|
|
||
| def remove_column(self, accessor): | ||
| self.change_source(self.interface.data) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm guessing you've got this example table from the
tableapp - the screenshot should be based on thescreenshotsapp (or equivalent content). If nothing else, that will remove the weird font here :-)