From f4339a3d75475147d8850bc18202e678243857f0 Mon Sep 17 00:00:00 2001 From: Damien Flament Date: Thu, 11 Apr 2019 11:18:27 +0200 Subject: [PATCH 1/5] Add simple table widget --- flexx/ui/widgets/__init__.py | 1 + flexx/ui/widgets/_table.py | 173 +++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 flexx/ui/widgets/_table.py diff --git a/flexx/ui/widgets/__init__.py b/flexx/ui/widgets/__init__.py index be9fe1c2..7e7249e7 100644 --- a/flexx/ui/widgets/__init__.py +++ b/flexx/ui/widgets/__init__.py @@ -16,6 +16,7 @@ from ._progressbar import ProgressBar from ._slider import Slider, RangeSlider +from ._table import TableWidget, TableEntry, TableEntryAttr from ._tree import TreeWidget, TreeItem from ._dropdown import ComboBox, DropdownContainer diff --git a/flexx/ui/widgets/_table.py b/flexx/ui/widgets/_table.py new file mode 100644 index 00000000..0290da0f --- /dev/null +++ b/flexx/ui/widgets/_table.py @@ -0,0 +1,173 @@ +""" TableWidget + +A ``TableWidget`` can contain ``TableEntry`` objects, which in turn can contain +``TableEntryAttr`` objects to construct a table. + +.. UIExample:: 180 + + from flexx import flx + + releases = [ + flx.Dict(version='0.7.1', + commit='38b322c9f521270b1874db150104c094cce508e1'), + flx.Dict(version='0.7.0', + commit='74fe76f749f4b033c193a8f8e7b025d42c6f9e70'), + flx.Dict(version='0.6.2', + commit='1c3dbb0cedd47b29bae475517302408a53effb4b'), + flx.Dict(version='0.6.1', + commit='e54574b3ecd7e5c39c09da68eac33662cc276b78'), + flx.Dict(version='0.6.0', + commit='4cff67e57a7b2123bd4d660a79527e3131a494be'), + ] + + class Example(flx.Widget): + + def init(self): + with flx.TableWidget(show_header=True): + for r in releases: + with flx.TableEntry(): + flx.TableEntryAttr(title="Version", text=r.version) + flx.TableEntryAttr(title="Commit", text=r.commit) + +""" + +from ... import event +from .._widget import Widget, create_element + +# todo: automatically generate MDN links in the documentation + + +class TableWidget(Widget): + """ + A ``Widget`` that can be used to display information in a table. Its entries + are represented by its children, which must only be ``TableEntry`` objects. + Entry attributes are created by instantiating ``TableEntryAttributes`` in the + context of a ``TableEntry``. + + A ` `_ is + rendered to show the ``title`` of the table if it is not empty. + + The ``outernode`` of this widget is a + ``_ with a + ``_ to hold the + header row and a + ``_ to contain + the entries. + + """ + + CSS = """ + + /* ----- Table Widget Mechanics ----- */ + + .flx-TableWidget { + overflow: scroll; + cursor: default; + } + + .flx-TableWidget .cell { + padding: 3px 6px; + } + + """ + + title = event.StringProp(settable=True, doc=""" + The title which is shown as the caption of this table. If not specified, + not any caption is rendered. + """) + + show_header = event.BoolProp(False, doc=""" + Whether to show the header line. + """) + + def _create_dom(self): + return create_element('table') + + def _render_dom(self): + entries = [] + caption = '' + header = '' + + for child in self.children: + if child.__name__ == 'TableEntry': + entries.append(child) + + if not entries: + raise RuntimeError("A TableWidget must contain at least one TableEntry.") + + if self.title: + caption = create_element('caption', {}, self.title) + + if self.show_header: + header = create_element( + 'thead', {}, [ + create_element( + 'tr', {}, [ + create_element('th', {'class': 'cell'}, widget.title) + for widget in entries[0].children + if widget.__name__ == 'TableEntryAttr' + ] + ) + ] + ) + + return create_element( + 'table', {}, [ + caption, + header, + create_element('tbody', {}, [entry.outernode for entry in entries]) + ] + ) + + +class TableEntry(Widget): + """ An entry to put in a ``TableWidget``. This widget must only be used inside a + ``TableWidget``. + + The ``outernode`` of this widget is a + ``_. + + """ + + # todo: remove `set_parent` from documentation as its usage is internal, here + @event.action + def set_parent(self, parent, pos=None): + if not (parent is None or + isinstance(parent, TableWidget)): + raise RuntimeError("TableEntry objects can only be created in the context " + "of a TableWidget.") + super().set_parent(parent, pos) + + def _create_dom(self): + return create_element('tr') + + +class TableEntryAttr(Widget): + """ An attribute to put in a ``TableEntry``. This widget must only be used inside a + ``TableEntry``. + + If a ``title`` is specified, it is rendered as the header for this attribute. + + The ``outernode`` of this widget is a + ``_ to hold the header row and a ``_ to contain - the entries. + the body rows. """ @@ -61,11 +60,24 @@ class TableWidget(Widget): /* ----- Table Widget Mechanics ----- */ .flx-TableWidget { - overflow: scroll; + height: 100%; + display: flex; + flex-flow: column nowrap; + cursor: default; } - .flx-TableWidget .cell { + .flx-TableRow, .flx-TableWidget thead tr { + display: flex; + flex-flow: row nowrap; + } + + .flx-TableCell, .flx-TableWidget th { + display: flex; + flex-flow: row nowrap; + flex-grow: 1; + flex-basis: 0; + padding: 3px 6px; } @@ -84,16 +96,16 @@ def _create_dom(self): return create_element('table') def _render_dom(self): - entries = [] + rows = [] caption = '' header = '' for child in self.children: - if child.__name__ == 'TableEntry': - entries.append(child) + if isinstance(child, TableRow): + rows.append(child) - if not entries: - raise RuntimeError("A TableWidget must contain at least one TableEntry.") + if not rows: + raise RuntimeError("A TableWidget must contain at least one TableRow.") if self.title: caption = create_element('caption', {}, self.title) @@ -102,11 +114,9 @@ def _render_dom(self): header = create_element( 'thead', {}, [ create_element( - 'tr', {}, [ - create_element('th', {'class': 'cell'}, widget.title) - for widget in entries[0].children - if widget.__name__ == 'TableEntryAttr' - ] + 'tr', {}, [create_element('th', {}, widget.title) + for widget in rows[0].children + if isinstance(widget, TableCell)] ) ] ) @@ -115,14 +125,15 @@ def _render_dom(self): 'table', {}, [ caption, header, - create_element('tbody', {}, [entry.outernode for entry in entries]) + create_element('tbody', {}, [r.outernode for r in rows]) ] ) -class TableEntry(Widget): - """ An entry to put in a ``TableWidget``. This widget must only be used inside a - ``TableWidget``. +class TableRow(Widget): + """ A row to put in a ``TableWidget``. This widget must only be used inside a + ``TableWidget``. Its cells are represented by its children, which must only be + ``TableCell`` objects. The ``outernode`` of this widget is a ``_. @@ -134,17 +145,18 @@ class TableEntry(Widget): def set_parent(self, parent, pos=None): if not (parent is None or isinstance(parent, TableWidget)): - raise RuntimeError("TableEntry objects can only be created in the context " - "of a TableWidget.") + raise RuntimeError("TableRows can only be created in the context of a " + "TableWidget.") + super().set_parent(parent, pos) def _create_dom(self): return create_element('tr') -class TableEntryAttr(Widget): - """ An attribute to put in a ``TableEntry``. This widget must only be used inside a - ``TableEntry``. +class TableCell(Widget): + """ A cell to put in a ``TableRow``. This widget must only be used inside a + ``TableRow``. If a ``title`` is specified, it is rendered as the header for this attribute. @@ -154,20 +166,22 @@ class TableEntryAttr(Widget): """ text = event.StringProp(settable=True, doc=""" - The text shown for this attribute. + The text shown in the cell. """) title = event.StringProp(settable=True, doc=""" - The title of this attribute that is displayed in the header. + The title of the column containing the cell. It is displayed in the header + if enabled in the parent ``TableWidget``. """) @event.action def set_parent(self, parent, pos=None): if not (parent is None or - isinstance(parent, TableEntry)): - raise RuntimeError("TableEntryAttr objects can only be created in the " - "context of a TableEntry.") + isinstance(parent, TableRow)): + raise RuntimeError("TableCells can only be created in the context of a " + "TableRow.") + super().set_parent(parent, pos) def _create_dom(self): - return create_element('td', {'class': 'cell'}, [self.text]) + return create_element('td', {}, [self.text]) From aa3aba0049336640cbef61221612221ae561ccc1 Mon Sep 17 00:00:00 2001 From: Damien Flament Date: Thu, 18 Apr 2019 08:24:56 +0200 Subject: [PATCH 3/5] Add flex support --- flexx/ui/widgets/_table.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/flexx/ui/widgets/_table.py b/flexx/ui/widgets/_table.py index da28c15f..ea61c06a 100644 --- a/flexx/ui/widgets/_table.py +++ b/flexx/ui/widgets/_table.py @@ -114,9 +114,15 @@ def _render_dom(self): header = create_element( 'thead', {}, [ create_element( - 'tr', {}, [create_element('th', {}, widget.title) - for widget in rows[0].children - if isinstance(widget, TableCell)] + 'tr', {}, [ + create_element( + 'th', + {'style': 'flex-grow: {};'.format(widget.flex[0] + 1)}, + widget.title + ) + for widget in rows[0].children + if isinstance(widget, TableCell) + ] ) ] ) @@ -153,6 +159,14 @@ def set_parent(self, parent, pos=None): def _create_dom(self): return create_element('tr') + def _render_dom(self): + for widget in self.children: + widget.apply_style({ + 'flex-grow': widget.flex[0] + 1 + }) + + return super()._render_dom(self) + class TableCell(Widget): """ A cell to put in a ``TableRow``. This widget must only be used inside a From 12298a108bfd9b7fa92df16891ed8486e54602a5 Mon Sep 17 00:00:00 2001 From: Damien Flament Date: Thu, 18 Apr 2019 12:36:53 +0200 Subject: [PATCH 4/5] Move to examples --- flexx/ui/widgets/__init__.py | 1 - .../_table.py => flexxamples/demos/table.py | 110 ++++++++++-------- 2 files changed, 59 insertions(+), 52 deletions(-) rename flexx/ui/widgets/_table.py => flexxamples/demos/table.py (66%) diff --git a/flexx/ui/widgets/__init__.py b/flexx/ui/widgets/__init__.py index 1098064b..be9fe1c2 100644 --- a/flexx/ui/widgets/__init__.py +++ b/flexx/ui/widgets/__init__.py @@ -16,7 +16,6 @@ from ._progressbar import ProgressBar from ._slider import Slider, RangeSlider -from ._table import TableWidget, TableRow, TableCell from ._tree import TreeWidget, TreeItem from ._dropdown import ComboBox, DropdownContainer diff --git a/flexx/ui/widgets/_table.py b/flexxamples/demos/table.py similarity index 66% rename from flexx/ui/widgets/_table.py rename to flexxamples/demos/table.py index ea61c06a..4a15034e 100644 --- a/flexx/ui/widgets/_table.py +++ b/flexxamples/demos/table.py @@ -1,43 +1,22 @@ -""" TableWidget - -A ``TableWidget`` can contain ``TableRows``, which in turn can contain ``TableCells`` -to construct a table. - -.. UIExample:: 180 - - from flexx import flx - - releases = [ - flx.Dict(version='0.7.1', - commit='38b322c9f521270b1874db150104c094cce508e1'), - flx.Dict(version='0.7.0', - commit='74fe76f749f4b033c193a8f8e7b025d42c6f9e70'), - flx.Dict(version='0.6.2', - commit='1c3dbb0cedd47b29bae475517302408a53effb4b'), - flx.Dict(version='0.6.1', - commit='e54574b3ecd7e5c39c09da68eac33662cc276b78'), - flx.Dict(version='0.6.0', - commit='4cff67e57a7b2123bd4d660a79527e3131a494be'), - ] +# doc-export: ReleasesTable +""" +An example of a custom table widget built from stock. - class Example(flx.Widget): +Noticeable features: - def init(self): - with flx.TableWidget(show_header=True): - for r in releases: - with flx.TableRow(): - flx.TableCell(title="Version", text=r.version) - flx.TableCell(title="Commit", text=r.commit) + * widget properties, + * custom DOM rendering with: + - optional DOM elements, + - mandatory child widgets type, + - mandatory parent widget type, + - `flex` support. """ -from ... import event -from .._widget import Widget, create_element - -# todo: automatically generate MDN links in the documentation +from flexx import flx -class TableWidget(Widget): +class TableWidget(flx.Widget): """ A ``Widget`` that can be used to display information in a table. Its rows are represented by its children, which must only be ``TableRow`` objects. @@ -83,17 +62,17 @@ class TableWidget(Widget): """ - title = event.StringProp(settable=True, doc=""" + title = flx.StringProp(settable=True, doc=""" The title which is shown as the caption of this table. If not specified, not any caption is rendered. """) - show_header = event.BoolProp(False, doc=""" + show_header = flx.BoolProp(False, doc=""" Whether to show the header line. """) def _create_dom(self): - return create_element('table') + return flx.create_element('table') def _render_dom(self): rows = [] @@ -108,14 +87,14 @@ def _render_dom(self): raise RuntimeError("A TableWidget must contain at least one TableRow.") if self.title: - caption = create_element('caption', {}, self.title) + caption = flx.create_element('caption', {}, self.title) if self.show_header: - header = create_element( + header = flx.create_element( 'thead', {}, [ - create_element( + flx.create_element( 'tr', {}, [ - create_element( + flx.create_element( 'th', {'style': 'flex-grow: {};'.format(widget.flex[0] + 1)}, widget.title @@ -127,16 +106,16 @@ def _render_dom(self): ] ) - return create_element( + return flx.create_element( 'table', {}, [ caption, header, - create_element('tbody', {}, [r.outernode for r in rows]) + flx.create_element('tbody', {}, [r.outernode for r in rows]) ] ) -class TableRow(Widget): +class TableRow(flx.Widget): """ A row to put in a ``TableWidget``. This widget must only be used inside a ``TableWidget``. Its cells are represented by its children, which must only be ``TableCell`` objects. @@ -146,8 +125,7 @@ class TableRow(Widget): """ - # todo: remove `set_parent` from documentation as its usage is internal, here - @event.action + @flx.action def set_parent(self, parent, pos=None): if not (parent is None or isinstance(parent, TableWidget)): @@ -157,7 +135,7 @@ def set_parent(self, parent, pos=None): super().set_parent(parent, pos) def _create_dom(self): - return create_element('tr') + return flx.create_element('tr') def _render_dom(self): for widget in self.children: @@ -168,7 +146,7 @@ def _render_dom(self): return super()._render_dom(self) -class TableCell(Widget): +class TableCell(flx.Widget): """ A cell to put in a ``TableRow``. This widget must only be used inside a ``TableRow``. @@ -179,16 +157,16 @@ class TableCell(Widget): """ - text = event.StringProp(settable=True, doc=""" + text = flx.StringProp(settable=True, doc=""" The text shown in the cell. """) - title = event.StringProp(settable=True, doc=""" + title = flx.StringProp(settable=True, doc=""" The title of the column containing the cell. It is displayed in the header if enabled in the parent ``TableWidget``. """) - @event.action + @flx.action def set_parent(self, parent, pos=None): if not (parent is None or isinstance(parent, TableRow)): @@ -198,4 +176,34 @@ def set_parent(self, parent, pos=None): super().set_parent(parent, pos) def _create_dom(self): - return create_element('td', {}, [self.text]) + return flx.create_element('td', {}, [self.text]) + +releases = [ + flx.Dict(version='0.7.1', + commit='38b322c9f521270b1874db150104c094cce508e1'), + flx.Dict(version='0.7.0', + commit='74fe76f749f4b033c193a8f8e7b025d42c6f9e70'), + flx.Dict(version='0.6.2', + commit='1c3dbb0cedd47b29bae475517302408a53effb4b'), + flx.Dict(version='0.6.1', + commit='e54574b3ecd7e5c39c09da68eac33662cc276b78'), + flx.Dict(version='0.6.0', + commit='4cff67e57a7b2123bd4d660a79527e3131a494be'), +] + +class ReleasesTable(flx.Widget): + + def init(self): + self.apply_style({ + 'width': '100%' + }) + + with TableWidget(title="Flexx Releases", show_header=True): + for r in releases: + with TableRow(): + TableCell(title="Version", text=r.version, flex=0) + TableCell(title="Commit", text=r.commit, flex=1) + +if __name__ == '__main__': + m = flx.launch(ReleasesTable, 'app') + flx.run() From b0a7e531d1b8796f4d72ea183e61fc883ea6bbfa Mon Sep 17 00:00:00 2001 From: Damien Flament Date: Thu, 18 Apr 2019 13:55:39 +0200 Subject: [PATCH 5/5] Add CSS --- flexxamples/demos/table.py | 91 +++++++++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 25 deletions(-) diff --git a/flexxamples/demos/table.py b/flexxamples/demos/table.py index 4a15034e..30092833 100644 --- a/flexxamples/demos/table.py +++ b/flexxamples/demos/table.py @@ -9,7 +9,8 @@ - optional DOM elements, - mandatory child widgets type, - mandatory parent widget type, - - `flex` support. + - `flex` support, + * widget CSS. """ @@ -36,29 +37,49 @@ class TableWidget(flx.Widget): CSS = """ - /* ----- Table Widget Mechanics ----- */ +/* ----- Table Widget Mechanics ----- */ - .flx-TableWidget { - height: 100%; - display: flex; - flex-flow: column nowrap; +.flx-TableWidget { + height: 100%; + display: flex; + flex-flow: column nowrap; +} - cursor: default; - } +.flx-TableWidget caption { + margin-bottom: 1em; +} - .flx-TableRow, .flx-TableWidget thead tr { - display: flex; - flex-flow: row nowrap; - } +.flx-TableRow, .flx-TableWidget thead tr { + display: flex; + flex-flow: row nowrap; +} - .flx-TableCell, .flx-TableWidget th { - display: flex; - flex-flow: row nowrap; - flex-grow: 1; - flex-basis: 0; +.flx-TableCell, .flx-TableWidget th { + display: flex; + flex-flow: column nowrap; + flex-grow: 1; + flex-basis: 0; +} - padding: 3px 6px; - } +/* ----- Table Widget Style ----- */ + +.flx-TableWidget caption { + font-variant-caps: petite-caps; + font-weight: bold; +} + +.flx-TableWidget thead { + border-top: solid grey 1px; +} + +.flx-TableWidget tbody { + border-top: solid grey 1px; + border-bottom: solid grey 1px; +} + +.flx-TableCell, .flx-TableWidget th { + padding: 3px 16px; +} """ @@ -180,19 +201,38 @@ def _create_dom(self): releases = [ flx.Dict(version='0.7.1', - commit='38b322c9f521270b1874db150104c094cce508e1'), + date='2018-12-03', + commit='38b322c9f521270b1874db150104c094cce508e1'), flx.Dict(version='0.7.0', - commit='74fe76f749f4b033c193a8f8e7b025d42c6f9e70'), + date='2018-11-02', + commit='74fe76f749f4b033c193a8f8e7b025d42c6f9e70'), flx.Dict(version='0.6.2', - commit='1c3dbb0cedd47b29bae475517302408a53effb4b'), + date='2018-10-04', + commit='1c3dbb0cedd47b29bae475517302408a53effb4b'), flx.Dict(version='0.6.1', - commit='e54574b3ecd7e5c39c09da68eac33662cc276b78'), + date='2018-10-04', + commit='e54574b3ecd7e5c39c09da68eac33662cc276b78'), flx.Dict(version='0.6.0', - commit='4cff67e57a7b2123bd4d660a79527e3131a494be'), + date='2018-10-02', + commit='4cff67e57a7b2123bd4d660a79527e3131a494be'), ] class ReleasesTable(flx.Widget): + CSS = """ + +/* ----- ReleasesTable Style ----- */ + +.flx-ReleasesTable { + padding: 1em; +} + +.flx-ReleasesTable td { + text-align: center; +} + + """ + def init(self): self.apply_style({ 'width': '100%' @@ -202,7 +242,8 @@ def init(self): for r in releases: with TableRow(): TableCell(title="Version", text=r.version, flex=0) - TableCell(title="Commit", text=r.commit, flex=1) + TableCell(title="Date", text=r.date, flex=1) + TableCell(title="Commit", text=r.commit, flex=2) if __name__ == '__main__': m = flx.launch(ReleasesTable, 'app')
`_. + + """ + + text = event.StringProp(settable=True, doc=""" + The text shown for this attribute. + """) + + title = event.StringProp(settable=True, doc=""" + The title of this attribute that is displayed in the header. + """) + + @event.action + def set_parent(self, parent, pos=None): + if not (parent is None or + isinstance(parent, TableEntry)): + raise RuntimeError("TableEntryAttr objects can only be created in the " + "context of a TableEntry.") + super().set_parent(parent, pos) + + def _create_dom(self): + return create_element('td', {'class': 'cell'}, [self.text]) From a7df9810ef015044fe2be259b99835e8e6e52065 Mon Sep 17 00:00:00 2001 From: Damien Flament Date: Fri, 12 Apr 2019 16:15:17 +0200 Subject: [PATCH 2/5] Use classic terminology --- flexx/ui/widgets/__init__.py | 2 +- flexx/ui/widgets/_table.py | 88 +++++++++++++++++++++--------------- 2 files changed, 52 insertions(+), 38 deletions(-) diff --git a/flexx/ui/widgets/__init__.py b/flexx/ui/widgets/__init__.py index 7e7249e7..1098064b 100644 --- a/flexx/ui/widgets/__init__.py +++ b/flexx/ui/widgets/__init__.py @@ -16,7 +16,7 @@ from ._progressbar import ProgressBar from ._slider import Slider, RangeSlider -from ._table import TableWidget, TableEntry, TableEntryAttr +from ._table import TableWidget, TableRow, TableCell from ._tree import TreeWidget, TreeItem from ._dropdown import ComboBox, DropdownContainer diff --git a/flexx/ui/widgets/_table.py b/flexx/ui/widgets/_table.py index 0290da0f..da28c15f 100644 --- a/flexx/ui/widgets/_table.py +++ b/flexx/ui/widgets/_table.py @@ -1,7 +1,7 @@ """ TableWidget -A ``TableWidget`` can contain ``TableEntry`` objects, which in turn can contain -``TableEntryAttr`` objects to construct a table. +A ``TableWidget`` can contain ``TableRows``, which in turn can contain ``TableCells`` +to construct a table. .. UIExample:: 180 @@ -25,9 +25,9 @@ class Example(flx.Widget): def init(self): with flx.TableWidget(show_header=True): for r in releases: - with flx.TableEntry(): - flx.TableEntryAttr(title="Version", text=r.version) - flx.TableEntryAttr(title="Commit", text=r.commit) + with flx.TableRow(): + flx.TableCell(title="Version", text=r.version) + flx.TableCell(title="Commit", text=r.commit) """ @@ -39,10 +39,9 @@ def init(self): class TableWidget(Widget): """ - A ``Widget`` that can be used to display information in a table. Its entries - are represented by its children, which must only be ``TableEntry`` objects. - Entry attributes are created by instantiating ``TableEntryAttributes`` in the - context of a ``TableEntry``. + A ``Widget`` that can be used to display information in a table. Its rows are + represented by its children, which must only be ``TableRow`` objects. + Cells are created by instantiating ``TableCells`` in the context of a ``TableRow``. A `
`_ is rendered to show the ``title`` of the table if it is not empty. @@ -52,7 +51,7 @@ class TableWidget(Widget): `