diff --git a/flexxamples/demos/table.py b/flexxamples/demos/table.py new file mode 100644 index 00000000..30092833 --- /dev/null +++ b/flexxamples/demos/table.py @@ -0,0 +1,250 @@ +# doc-export: ReleasesTable +""" +An example of a custom table widget built from stock. + +Noticeable features: + + * widget properties, + * custom DOM rendering with: + - optional DOM elements, + - mandatory child widgets type, + - mandatory parent widget type, + - `flex` support, + * widget CSS. + +""" + +from flexx import flx + + +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. + 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. + + The ``outernode`` of this widget is a + ``_ with a + ``_ to hold the + header row and a + ``_ to contain + the body rows. + + """ + + CSS = """ + +/* ----- Table Widget Mechanics ----- */ + +.flx-TableWidget { + height: 100%; + display: flex; + flex-flow: column nowrap; +} + +.flx-TableWidget caption { + margin-bottom: 1em; +} + +.flx-TableRow, .flx-TableWidget thead tr { + display: flex; + flex-flow: row nowrap; +} + +.flx-TableCell, .flx-TableWidget th { + display: flex; + flex-flow: column nowrap; + flex-grow: 1; + flex-basis: 0; +} + +/* ----- 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; +} + + """ + + 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 = flx.BoolProp(False, doc=""" + Whether to show the header line. + """) + + def _create_dom(self): + return flx.create_element('table') + + def _render_dom(self): + rows = [] + caption = '' + header = '' + + for child in self.children: + if isinstance(child, TableRow): + rows.append(child) + + if not rows: + raise RuntimeError("A TableWidget must contain at least one TableRow.") + + if self.title: + caption = flx.create_element('caption', {}, self.title) + + if self.show_header: + header = flx.create_element( + 'thead', {}, [ + flx.create_element( + 'tr', {}, [ + flx.create_element( + 'th', + {'style': 'flex-grow: {};'.format(widget.flex[0] + 1)}, + widget.title + ) + for widget in rows[0].children + if isinstance(widget, TableCell) + ] + ) + ] + ) + + return flx.create_element( + 'table', {}, [ + caption, + header, + flx.create_element('tbody', {}, [r.outernode for r in rows]) + ] + ) + + +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. + + The ``outernode`` of this widget is a + ``_. + + """ + + @flx.action + def set_parent(self, parent, pos=None): + if not (parent is None or + isinstance(parent, TableWidget)): + raise RuntimeError("TableRows can only be created in the context of a " + "TableWidget.") + + super().set_parent(parent, pos) + + def _create_dom(self): + return flx.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(flx.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. + + The ``outernode`` of this widget is a + `
`_. + + """ + + text = flx.StringProp(settable=True, doc=""" + The text shown in the cell. + """) + + 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``. + """) + + @flx.action + def set_parent(self, parent, pos=None): + if not (parent is None or + 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 flx.create_element('td', {}, [self.text]) + +releases = [ + flx.Dict(version='0.7.1', + date='2018-12-03', + commit='38b322c9f521270b1874db150104c094cce508e1'), + flx.Dict(version='0.7.0', + date='2018-11-02', + commit='74fe76f749f4b033c193a8f8e7b025d42c6f9e70'), + flx.Dict(version='0.6.2', + date='2018-10-04', + commit='1c3dbb0cedd47b29bae475517302408a53effb4b'), + flx.Dict(version='0.6.1', + date='2018-10-04', + commit='e54574b3ecd7e5c39c09da68eac33662cc276b78'), + flx.Dict(version='0.6.0', + 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%' + }) + + with TableWidget(title="Flexx Releases", show_header=True): + for r in releases: + with TableRow(): + TableCell(title="Version", text=r.version, flex=0) + TableCell(title="Date", text=r.date, flex=1) + TableCell(title="Commit", text=r.commit, flex=2) + +if __name__ == '__main__': + m = flx.launch(ReleasesTable, 'app') + flx.run()