Skip to content
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

Table cell slot without string template #3261

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
8 changes: 8 additions & 0 deletions nicegui/elements/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from ..events import GenericEventArguments, TableSelectionEventArguments, ValueChangeEventArguments, handle_event
from ..helpers import warn_once
from .mixins.filter_element import FilterElement
from .table_cell_slot import TableCellSlot

if importlib.util.find_spec('pandas'):
optional_features.register('pandas')
Expand Down Expand Up @@ -309,6 +310,12 @@ def update_rows(self, rows: List[Dict], *, clear_selection: bool = True) -> None
self.selected.clear()
self.update()


def cell_slot(self, field:str ):
def decorator(func:Callable[...,None]):
return TableCellSlot(self,field,func)
return decorator

async def get_filtered_sorted_rows(self, *, timeout: float = 1) -> List[Dict]:
"""Asynchronously return the filtered and sorted rows of the table."""
return await self.get_computed_prop('filteredSortedRows', timeout=timeout)
Expand All @@ -321,6 +328,7 @@ async def get_computed_rows_number(self, *, timeout: float = 1) -> int:
"""Asynchronously return the number of computed rows of the table."""
return await self.get_computed_prop('computedRowsNumber', timeout=timeout)


class row(Element):

def __init__(self) -> None:
Expand Down
27 changes: 27 additions & 0 deletions nicegui/elements/table_cell_slot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export default {
template: `<slot></slot>`,
data() {
this.rowProps = [];
this.loaded = false;
},

methods: {
collectProps(props) {
if (!this.loaded) {
this.rowProps.push(props);
} else {
this.$emit("placeholder_updated", props);
}
},
emitUpdated() {
this.rowProps.forEach(props => {
this.$emit("placeholder_updated", props);
})
this.rowProps.length = 0;
},
setLoaded() {
this.loaded = true;
this.emitUpdated();
},
}
};
69 changes: 69 additions & 0 deletions nicegui/elements/table_cell_slot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Callable, Dict

from ..context import context
from ..element import Element
from ..elements.mixins.value_element import ValueElement
from .table_slot_placeholder import TableSlotPlaceholder
from .teleport import Teleport

if TYPE_CHECKING:
from .table import Table

class TableCellSlot(Element, component="table_cell_slot.js"):
def __init__(self, table: Table, slot_name: str ,func: Callable[[int,str], None]):
super().__init__()
def slot_build_fn(cell_props: Dict):
class_name = f"--table-slot-{slot_name}-{cell_props['rowIndex']}"
with Teleport(f"#c{table.id} .{class_name}") as tp:
func(cell_props['rowIndex'],slot_name)

# I'm not sure why. But setting LOOPBACK to True makes the slot work.
for element in tp:
if isinstance(element, ValueElement):
element.LOOPBACK=True
return tp

self._slot_build_fn=slot_build_fn
self.func = func
self._teleport_slots_cache: Dict[int, Teleport] = {}
# note: If the component is not instantiated, it cannot be used in table slots.
TableSlotPlaceholder()
template = rf"""
<q-td key="name" :props="props" >
<nicegui-table_slot_placeholder :tableCellSlotId="{self.id}" :dataProps="props" :class="`--table-slot-{slot_name}-${{props.rowIndex}}`"></nicegui-table_slot_placeholder>
</q-td>
"""

def placeholder_updated(e):
row_props = e.args
self._create_cell_slot(row_props)

self.on("placeholder_updated", placeholder_updated)
table.add_slot(f"body-cell-{slot_name}", template)

# make sure container is initialized before emitting the init event
client = context.client
if client.has_socket_connection:
self.run_method("setLoaded")
else:
def on_connect():
self.run_method("setLoaded")

client.on_connect(on_connect)

def _create_cell_slot(self, row_props:Dict):
key = row_props['rowIndex']
tp = self._teleport_slots_cache.get(key, None)
if tp:
tp.update()
return

with self:
tp = self._slot_build_fn(row_props)
self._teleport_slots_cache[key] = tp

def _handle_delete(self) -> None:
self._teleport_slots_cache.clear()
return super()._handle_delete()
15 changes: 15 additions & 0 deletions nicegui/elements/table_slot_placeholder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default {
template: `<div><slot></slot></div>`,
props: {
tableCellSlotId: String,
dataProps: Object,
},
async mounted() {
if (this.tableCellSlotId === null) {
return;
}
await this.$nextTick()
const target = getElement(this.tableCellSlotId);
runMethod(target, 'collectProps', [this.dataProps])
},
};
11 changes: 11 additions & 0 deletions nicegui/elements/table_slot_placeholder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from nicegui.element import Element


class TableSlotPlaceholder(Element, component="table_slot_placeholder.js"):
def __init__(self) -> None:
super().__init__()
self._props["tableCellSlotId"] = None
self._props["dataProps"] = None
self.style("display:none")


15 changes: 15 additions & 0 deletions tests/test_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,20 @@ def test_problematic_datatypes(screen: Screen):
screen.should_contain('2021-01')



def test_cell_slot(screen: Screen):
t = ui.table(columns=columns(), rows=rows(), pagination=2)

@t.cell_slot("name")
def _(row_index: int, name: str):
rows = t.rows

with ui.column():
ui.input(value=rows[row_index][name])


screen.open('/')

def test_table_computed_props(screen: Screen):
all_rows = rows()
filtered_rows = [row for row in all_rows if 'e' in row['name']]
Expand Down Expand Up @@ -318,3 +332,4 @@ def test_columns_from_df(screen: Screen):
screen.click('Update cars with columns') # updated columns via parameter
screen.should_contain('Hyundai')
screen.should_contain('i30')