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

How to use slots with props #501

Closed
dclause opened this issue Mar 11, 2023 · 7 comments
Closed

How to use slots with props #501

dclause opened this issue Mar 11, 2023 · 7 comments
Labels
enhancement New feature or request
Milestone

Comments

@dclause
Copy link
Contributor

dclause commented Mar 11, 2023

Currently writting a table component using QTable, I wish I could use slots where props is needed to be injected.

As you can check in PR https://github.com/zauberzeug/nicegui/pull/500/files,
I am able to use components slots for slots that are named but do not inject props.

I wish to use the component for use-cases like body-slot: https://quasar.dev/vue-components/table#body-slots where the slot exposes props.

How can we use props in the slots currently (ie how could I use the body slot in the PR's example above) ?
If not possible currently, could this be added as a feature in the roadmap and eventually, could you point at some directly on how to do it: I may be able to succeed to create a PR for it if guided.

Thanks for this amazing project and your help.

@Allen-Taylor
Copy link

Allen-Taylor commented Mar 12, 2023

image

This is using the example for the Quasar website:
https://github.com/quasarframework/quasar/blob/dev/docs/public/examples/QTable/SlotBody.vue

qtable.js

export default {
    props: {
      tableColumns: {
        type: Array,
        required: true,
      },
      tableRows: {
        type: Array,
        required: true,
      },
      rowKey: {
        type: String,
        required: true,
      },
      tableTitle: {
        type: String,
        default: 'Default Title',
      },
    },
    template: `
      <q-table :title="tableTitle" :rows="tableRows" :columns="tableColumns" :row-key="rowKey">
        <template v-slot:body="props">
          <q-tr :props="props" @click="onRowClick(props.row)">
            <q-td key="name" :props="props">
              {{ props.row.name }}
            </q-td>
            <q-td key="calories" :props="props">
              <q-badge color="green">
                {{ props.row.calories }}
              </q-badge>
            </q-td>
            <q-td key="fat" :props="props">
              <q-badge color="purple">
                {{ props.row.fat }}
              </q-badge>
            </q-td>
            <q-td key="carbs" :props="props">
              <q-badge color="orange">
                {{ props.row.carbs }}
              </q-badge>
            </q-td>
            <q-td key="protein" :props="props">
              <q-badge color="primary">
                {{ props.row.protein }}
              </q-badge>
            </q-td>
            <q-td key="sodium" :props="props">
              <q-badge color="teal">
                {{ props.row.sodium }}
              </q-badge>
            </q-td>
            <q-td key="calcium" :props="props">
              <q-badge color="accent">
                {{ props.row.calcium }}
              </q-badge>
            </q-td>
            <q-td key="iron" :props="props">
              <q-badge color="amber">
                {{ props.row.iron }}
              </q-badge>
            </q-td>
          </q-tr>
        </template>
      </q-table>
    `,
    setup() {
      const onRowClick = (row) => alert(`${row.name} clicked`);
      const columns = [];
      const rows = [];
      return { onRowClick, columns, rows };
    },
  };

qtable.py

from typing import List, Dict
from nicegui.dependencies import register_component
from nicegui.element import Element

register_component('qtable', __file__, 'qtable.js')

class QTable(Element):
    def __init__(
        self,
        rows: List[Dict],
        columns: List[Dict],
        row_key: str = 'id',
        title: str = 'Default Title',
    ) -> None:
        super().__init__('qtable')
        self._props['rows'] = rows
        self._props['columns'] = columns
        self._props['row-key'] = row_key
        self._props['title'] = title

test_table.py

from nicegui import ui
from qtable import QTable as qt

columns = [
    {
        "name": "name",
        "required": True,
        "label": "Dessert (100g serving)",
        "align": "left",
        "field": "name",
        "sortable": True
    },
    {
        "name": "calories",
        "align": "center",
        "label": "Calories",
        "field": "calories",
        "sortable": True
    },
    {
        "name": "fat",
        "label": "Fat (g)",
        "field": "fat",
        "sortable": True
    },
    {
        "name": "carbs",
        "label": "Carbs (g)",
        "field": "carbs"
    },
    {
        "name": "protein",
        "label": "Protein (g)",
        "field": "protein"
    },
    {
        "name": "sodium",
        "label": "Sodium (mg)",
        "field": "sodium"
    },
    {
        "name": "calcium",
        "label": "Calcium (%)",
        "field": "calcium",
        "sortable": True,
    },
    {
        "name": "iron",
        "label": "Iron (%)",
        "field": "iron",
        "sortable": True,
    }
]

rows = [
    {
        "name": "Frozen Yogurt",
        "calories": 159,
        "fat": 6.0,
        "carbs": 24,
        "protein": 4.0,
        "sodium": 87,
        "calcium": "14%",
        "iron": "1%"
    },
    {
        "name": "Ice cream sandwich",
        "calories": 237,
        "fat": 9.0,
        "carbs": 37,
        "protein": 4.3,
        "sodium": 129,
        "calcium": "8%",
        "iron": "1%"
    },
    {
        "name": "Eclair",
        "calories": 262,
        "fat": 16.0,
        "carbs": 23,
        "protein": 6.0,
        "sodium": 337,
        "calcium": "6%",
        "iron": "7%"
    },
    {
        "name": "Cupcake",
        "calories": 305,
        "fat": 3.7,
        "carbs": 67,
        "protein": 4.3,
        "sodium": 413,
        "calcium": "3%",
        "iron": "8%"
    },
    {
        "name": "Gingerbread",
        "calories": 356,
        "fat": 16.0,
        "carbs": 49,
        "protein": 3.9,
        "sodium": 327,
        "calcium": "7%",
        "iron": "16%"
    },]

qt = qt(rows=rows, columns=columns)

ui.run()

@dclause
Copy link
Contributor Author

dclause commented Mar 12, 2023

Hi @Allen-Taylor !

Thanks for your brillant demo. I had figured that out indeed, but this is a very user specific example here.
If you have a look at my PR at https://github.com/zauberzeug/nicegui/pull/500/files I am making a qtable component that is generic enough to all user. In the example file on the PR I show how to use slots from python code, hence you can embed anything a user would want in the slot. But I could not have that strategy working with slots with props and that is what I try to do.

I am not sure if I am able to explain that properly here: I am looking at a way to render a slot with props agnostically of what is inside so that this becomes an API of nicegui.

@Allen-Taylor
Copy link

Allen-Taylor commented Mar 12, 2023

Wouldn't you have to pass props you want to the Tr, Th and Td classes? The classes just set the tags as they currently stand.

image

class Tr(Element):
    def __init__(self) -> None:
        super().__init__('q-tr')

class Th(Element):
    def __init__(self) -> None:
        super().__init__('q-th')

class Td(Element):
    def __init__(self, key: str = '') -> None:
        super().__init__('q-td')
        if key:
            self._props['key'] = key

@dclause
Copy link
Contributor Author

dclause commented Mar 12, 2023

What I would like to, following the PR and it's example, is something this kind :

with ui.table(title='Foo', rows=rows, columns=columns) as table:
   with table.add_slot('body') as slot:
        with ui.tr():
            with ui.td(key='name'):
                ui.label(slot.props.row.name).classes('text-center')

Hence from the slot, retrieving the props injected by the vue component and use it within in the python code.

@falkoschindler
Copy link
Contributor

Very interesting, @dclause! But I've also been struggling to really understand the idea, since I'm not that familiar with Vue and its advanced concepts. I think this is related to #167. But QTree has "ScopedSlots" and QTable doesn't. So maybe it is not the same... I don't know.

Let me summarize: You want to control how a table row ("body" slot) or a cell ("body-cell" slot) is rendered. Because these elements are generated dynamically by Quasar, these slots don't exist in the Python world (yet). This is in contrast to slots like "top-row" which are uniquely named and can be created in Python to fill them with child elements.

Currently I really don't know how to tackle the implementation of such dynamic (scoped?) slots. Do we need to introduce something like a template element in Python that is synced to the client? But how to inject the prop values?

I think this is not an easy task and we shouldn't let the QTable component #500 depend on this.

@dclause
Copy link
Contributor Author

dclause commented Mar 12, 2023

Hi @falkoschindler !
My implementation of QTable in PR #500 do not need a new slot feature as a prerequisite indeed. You can pass data as usual, and you can use the named slots as usual (see the example provided in the PR). That is enough to create tables of data through that element as we would with the aggrid but using the q-table element.

Scoped slot would be a nice to have in order to be able to create tables where a cell is an icon, of with action buttons in for instance.
(of course you can create a custom element for that (that is what I do in my project, similarly to @Allen-Taylor answer above, but that feels more hacky and JS harcodes.)
And more generally, scoped slots in a feature in itself as it would unlock many other things: QTree as scoped slots and could be personalized through that mecanism and many Quasar elements. That would bring a whole new level of flexibility in the nicegui API and is out of scope of PR #500.

@dclause
Copy link
Contributor Author

dclause commented Mar 12, 2023

I guess this issue duplicates discussion at #167 and therefore can be closed.

@dclause dclause closed this as completed Mar 12, 2023
@falkoschindler falkoschindler added this to the v1.1.12 milestone Mar 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants