zauberzeug / nicegui

Create web-based user interfaces with Python. The nice way.
https://nicegui.io
MIT License
9.78k stars 580 forks source link

Simple tables #441

Closed johnrtipton closed 1 year ago

johnrtipton commented 1 year ago

I see there has been some discussion about qtable and AgGrid. I would also like to have the ability to create a simple html table.

When I try this it refuses to name the tag table:

from nicegui import ui
from nicegui.element import Element

class Table(Element):

    def __init__(self) -> None:
        super().__init__('table')

Table()
ui.run()

But this just generates this:

<div id="4"></div>

I can name it tabl, but not table it seems something is preventing this.

Allen-Taylor commented 1 year ago

Did you try tag="q-table"

Diegiwg commented 1 year ago

To define an html table, you will still need to create an Element (class), and define the basic template (based on Vue templates).

Diegiwg commented 1 year ago

Explanation

As I said in another comment, you will need to create your own Nicegui element, to get the standard HTML table.

I'm enjoying creating elements for Nicegui, so I decided to create this one too.

Below is the code I sent to PR #442.

Code

html_table.py

from nicegui.element import Element
from nicegui.dependencies import register_component

register_component("html_table", __file__, "html_table.js")

def parser_style(table):
    m_style = ["<style>"]

    for key in table._style.keys():
        m_style.append(key + " {" + table._style[key] + "}")

    m_style.append("</style>")
    return "\n".join(m_style)

def parser_columns(table):
    m_thead = ["<thead><tr>"]

    for col in table._columns:
        m_thead.append(f"<th>{col}</th>")

    m_thead.append("</tr></thead>")
    return "\n".join(m_thead)

def parser_rows(table):
    m_tbody = ["<tbody>"]

    for row in table._rows:
        m_data = ["<tr>"]
        for col in row.keys():
            m_data.append(f"<td>{row[col]}</td>")
        m_data.append("</tr>")
        m_tbody.append("\n".join(m_data))

    m_tbody.append("</tbody>")
    return "\n".join(m_tbody)

class HTMLTable(Element):
    def __init__(self, columns=[], rows=[], style={}):,
        """HTML Table
        """
        super().__init__("html_table")

        self._columns = columns
        self._rows = rows
        self._style = style

        self.sync_config()

    def sync_config(self):
        data = f"{parser_columns(self)} {parser_rows(self)} {parser_style(self)}"
        self._props["table_config"] = data
        self.update()

html_table.js

export default {
    template: '<table v-html="this.table_config"></table>',
    props: {
        table_config: String,
    },
};

Example of Use

exemple.py

from nicegui.ui import run
from html_table import HTMLTable

cols = ["Name", "Age", "Sex", "Height", "Weight"]

rows = [
    {"name": "John", "age": 30, "sex": "Male", "height": 180, "weight": 80},
    {"name": "Jane", "age": 25, "sex": "Female", "height": 160, "weight": 60},
]

style = {
    "table": "border-collapse: collapse; margin: 25px 0; font-size: 0.9em; font-family: sans-serif;",
    "tr": "background-color: #5898d4; color: #ffffff; text-align: left; border-bottom: 1px solid #dddddd;",
    "th, td": "padding: 12px 15px;",
}

table = HTMLTable(columns=cols, rows=rows, style=style)

run()

Results

1 2

johnrtipton commented 1 year ago

@Diegiwg thanks very much for the quick response. This looks interesting, but I was thinking of being able to create a table and with my own rows and cells to allow more freedom to fully customize the table. Like adding my own components within the table. There are times when we need more control, instead of feeding it json to build it out dynamically.

I've been using JustPy and have build my own customized tables to meet my needs there.

Based on your example above, it looks like I was missing the: register_component("html_table", __file__, "html_table.js")

falkoschindler commented 1 year ago

In principle, the idea of deriving from Element and calling super().__init__ with the tag name "table" is correct. You shouldn't need to implement a custom component for this.

However, we have a naming issue, here: Our AG Grid component, which is currently named ui.table, registers a component we the tag name "table". So when you try to create a plain HTML table, you end up using an AG Grid accidentally.

You can try removing the register_component line in table.py. Then you can create a table as follows:

with ui.element('table'):
    with ui.element('tr'):
        with ui.element('td'):
            ui.label('Name')
        with ui.element('td'):
            ui.label('Age')

Of course, this is no permanent solution. Changing the naming from ui.table to ui.aggrid (#370) might help, but will not be completed in near future since we need to deprecate ui.table slowly. Maybe we can undo the register_component for the specific case where you need an HTML table...

But there is a simple solution. Using ui.html you can add arbitrary HTML code:

ui.html('''
    <table>
        <tr>
            <td>Name</td>
            <td>Age</td>
        </tr>
    </table>
''')

Note that the table will be wrapped into a div tag. Apart from that, it might be what you're looking for.

falkoschindler commented 1 year ago

Two more workarounds:

johnrtipton commented 1 year ago

Thanks @falkoschindler I will try this:

del js_components['table']
johnrtipton commented 1 year ago

Thanks for the help, I was able to generate a very basic table using the following:

from nicegui.dependencies import js_components
from nicegui.elements.mixins.text_element import TextElement

from nicegui import ui
from nicegui.element import Element

del js_components['table']

class Table(Element):

    def __init__(self) -> None:
        super().__init__('table')

class Thead(Element):

    def __init__(self) -> None:
        super().__init__('thead')

class Tr(Element):

    def __init__(self) -> None:
        super().__init__('tr')

class Th(TextElement):

    def __init__(self, text: str = '') -> None:
        super().__init__(tag='th', text=text)

class Tbody(Element):

    def __init__(self) -> None:
        super().__init__('tbody')

class Td(TextElement):

    def __init__(self, text: str = '') -> None:
        super().__init__(tag='td', text=text)

with Table() as table:
    with Thead():
        with Tr():
            Th('col 1')
            Th('col 2')
    with Tbody():
        with Tr():
            Td('val 1')
            Td('val 2')

ui.run()
falkoschindler commented 1 year ago

Great! I'll close this issue, since we found multiple workaround for the unfortunate naming conflict between ui.table and the HTML <table>.

In version 1.1.10 we will rename ui.table into ui.aggrid and deprecate the use of ui.table. This resolves the conflict with <table> and makes room for a new ui.table based on Quasar's QTable (#370). There won't be a conflict, since Quasar uses the tag <q-table>.