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

Emit event on table group open/collapse #2133 #2402

Merged
merged 2 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions py/examples/table_events_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Table / Events / Group
# Register the `group_change` #event to emit Wave event when group collapses or opens.
# #table #events #groups
# ---
from h2o_wave import main, app, Q, ui

bobrows = [
{"name":"row1", "cell":"Issue1"},
{"name":"row2", "cell":"Issue2"},
]
johnrows = [
{"name":"row3", "cell":"Issue3"},
{"name":"row4", "cell":"Issue4"},
{"name":"row5", "cell":"Issue5"},
]

collapsed_states = {
'Bob': True,
'John': False
}

@app('/demo')
async def serve(q: Q):
mturoci marked this conversation as resolved.
Show resolved Hide resolved
if q.events.issues_table and q.events.issues_table.group_change:
# toggle the collapse states
for group in q.events.issues_table.group_change:
collapsed_states[group] = not collapsed_states[group]
q.page['collapse'].content = f'{q.events.issues_table.group_change}'
else:
q.page['form'] = ui.form_card(box='1 1 4 5', items=[
ui.table(
name='issues_table',
columns=[ui.table_column(name='text', label='Issues assigned to')],
groups=[
ui.table_group("Bob",
rows=[ui.table_row(
name=row["name"],
cells=[row["cell"]])
for row in bobrows],
collapsed=collapsed_states["Bob"]
),
ui.table_group("John",
rows=[ui.table_row(
name=row["name"],
cells=[row["cell"]])
for row in johnrows],
collapsed=collapsed_states["John"]
),],
height='400px',
events=['group_change']
)
])
q.page['collapse'] = ui.markdown_card(box='5 1 2 1', title='Group change info', content='')

q.client.initialized = True

await q.page.save()
1 change: 1 addition & 0 deletions py/examples/tour.conf
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ table_filter_backend.py
table_download.py
table_groupby.py
table_groups.py
table_events_group.py
table_select_single.py
table_select_multiple.py
table_events_select.py
Expand Down
2 changes: 1 addition & 1 deletion py/h2o_lightwave/h2o_lightwave/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3922,7 +3922,7 @@ def __init__(
self.pagination = pagination
"""Display a pagination control at the bottom of the table. Set this value using `ui.table_pagination()`."""
self.events = events
"""The events to capture on this table when pagination is set. One of 'search' | 'sort' | 'filter' | 'download' | 'page_change' | 'reset' | 'select'."""
"""The events to capture on this table. When pagination is set, one of 'search' | 'sort' | 'filter' | 'download' | 'page_change' | 'reset'. These events are available regardless of pagination: 'select' | 'group_change'."""
self.single = single
"""True to allow only one row to be selected at time. Mutually exclusive with `multiple` attr."""
self.value = value
Expand Down
2 changes: 1 addition & 1 deletion py/h2o_lightwave/h2o_lightwave/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -1478,7 +1478,7 @@ def table(
tooltip: An optional tooltip message displayed when a user clicks the help icon to the right of the component.
groups: Creates collapsible / expandable groups of data rows. Mutually exclusive with `rows` attr.
pagination: Display a pagination control at the bottom of the table. Set this value using `ui.table_pagination()`.
events: The events to capture on this table when pagination is set. One of 'search' | 'sort' | 'filter' | 'download' | 'page_change' | 'reset' | 'select'.
events: The events to capture on this table. When pagination is set, one of 'search' | 'sort' | 'filter' | 'download' | 'page_change' | 'reset'. These events are available regardless of pagination: 'select' | 'group_change'.
single: True to allow only one row to be selected at time. Mutually exclusive with `multiple` attr.
value: The name of the selected row. If this parameter is set, single selection will be allowed (`single` is assumed to be `True`).
Returns:
Expand Down
2 changes: 1 addition & 1 deletion py/h2o_wave/h2o_wave/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3922,7 +3922,7 @@ def __init__(
self.pagination = pagination
"""Display a pagination control at the bottom of the table. Set this value using `ui.table_pagination()`."""
self.events = events
"""The events to capture on this table when pagination is set. One of 'search' | 'sort' | 'filter' | 'download' | 'page_change' | 'reset' | 'select'."""
"""The events to capture on this table. When pagination is set, one of 'search' | 'sort' | 'filter' | 'download' | 'page_change' | 'reset'. These events are available regardless of pagination: 'select' | 'group_change'."""
self.single = single
"""True to allow only one row to be selected at time. Mutually exclusive with `multiple` attr."""
self.value = value
Expand Down
2 changes: 1 addition & 1 deletion py/h2o_wave/h2o_wave/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -1478,7 +1478,7 @@ def table(
tooltip: An optional tooltip message displayed when a user clicks the help icon to the right of the component.
groups: Creates collapsible / expandable groups of data rows. Mutually exclusive with `rows` attr.
pagination: Display a pagination control at the bottom of the table. Set this value using `ui.table_pagination()`.
events: The events to capture on this table when pagination is set. One of 'search' | 'sort' | 'filter' | 'download' | 'page_change' | 'reset' | 'select'.
events: The events to capture on this table. When pagination is set, one of 'search' | 'sort' | 'filter' | 'download' | 'page_change' | 'reset'. These events are available regardless of pagination: 'select' | 'group_change'.
single: True to allow only one row to be selected at time. Mutually exclusive with `multiple` attr.
value: The name of the selected row. If this parameter is set, single selection will be allowed (`single` is assumed to be `True`).
Returns:
Expand Down
2 changes: 1 addition & 1 deletion r/R/ui.R
Original file line number Diff line number Diff line change
Expand Up @@ -1703,7 +1703,7 @@ ui_table_pagination <- function(
#' @param tooltip An optional tooltip message displayed when a user clicks the help icon to the right of the component.
#' @param groups Creates collapsible / expandable groups of data rows. Mutually exclusive with `rows` attr.
#' @param pagination Display a pagination control at the bottom of the table. Set this value using `ui.table_pagination()`.
#' @param events The events to capture on this table when pagination is set. One of 'search' | 'sort' | 'filter' | 'download' | 'page_change' | 'reset' | 'select'.
#' @param events The events to capture on this table. When pagination is set, one of 'search' | 'sort' | 'filter' | 'download' | 'page_change' | 'reset'. These events are available regardless of pagination: 'select' | 'group_change'.
#' @param single True to allow only one row to be selected at time. Mutually exclusive with `multiple` attr.
#' @param value The name of the selected row. If this parameter is set, single selection will be allowed (`single` is assumed to be `True`).
#' @return A Table instance.
Expand Down
128 changes: 128 additions & 0 deletions ui/src/table.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1676,6 +1676,58 @@ describe('Table.tsx', () => {
expect(container.querySelectorAll('.ms-GroupHeader-title')[0]).toHaveTextContent('1/20/1970, 4:58:47 AM(0)')
expect(container.querySelectorAll('.ms-GroupHeader-title')[1]).toHaveTextContent('6/22/2022, 8:47:51 PM(1)')
})

it('Collapses all group by list - fire event', () => {
mturoci marked this conversation as resolved.
Show resolved Hide resolved
const { container, getAllByText, getByTestId } = render(<XTable model={{ ...tableProps, events: ['group_change'] }} />)

fireEvent.click(getByTestId('groupby'))
fireEvent.click(getAllByText('Col1')[1]!)

//open all groups
fireEvent.click(container.querySelector('.ms-DetailsHeader-collapseButton')!)
emitMock.mockClear()

//collapse all groups
fireEvent.click(container.querySelector('.ms-DetailsHeader-collapseButton')!)
expect(emitMock).toHaveBeenCalledWith(tableProps.name, 'group_change', [cell21, cell11, cell31])
})

it('Expands all group by list - fire event', () => {
const { container, getAllByText, getByTestId } = render(<XTable model={{ ...tableProps, events: ['group_change'] }} />)

fireEvent.click(getByTestId('groupby'))
fireEvent.click(getAllByText('Col1')[1]!)

//open all groups
fireEvent.click(container.querySelector('.ms-DetailsHeader-collapseButton')!)
expect(emitMock).toHaveBeenCalledWith(tableProps.name, 'group_change', [cell21, cell11, cell31])
})

it('Collapses group by list - fire event', () => {
const { container, getAllByText, getByTestId } = render(<XTable model={{ ...tableProps, events: ['group_change'] }} />)

fireEvent.click(getByTestId('groupby'))
fireEvent.click(getAllByText('Col1')[1]!)

//open all groups
fireEvent.click(container.querySelector('.ms-DetailsHeader-collapseButton')!)
emitMock.mockClear()

//collapse 1st group
fireEvent.click(container.querySelector('.ms-GroupHeader-expand')!)
expect(emitMock).toHaveBeenCalledWith(tableProps.name, 'group_change', [cell21])
})

it('Expands group by list - fire event', () => {
const { container, getAllByText, getByTestId } = render(<XTable model={{ ...tableProps, events: ['group_change'] }} />)

fireEvent.click(getByTestId('groupby'))
fireEvent.click(getAllByText('Col1')[1]!)

//open 1st group
fireEvent.click(container.querySelector('.ms-GroupHeader-expand')!)
expect(emitMock).toHaveBeenCalledWith(tableProps.name, 'group_change', [cell21])
})
})

describe('Groups', () => {
Expand Down Expand Up @@ -1778,6 +1830,82 @@ describe('Table.tsx', () => {
expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + items - filteredItem)
})

it('Collapses all groups - fire event', () => {
const { container, getAllByRole } = render(<XTable model={{ ...tableProps, events: ['group_change'] }} />)

//collapse all groups
fireEvent.click(container.querySelector('.ms-DetailsHeader-collapseButton')!)
expect(emitMock).toHaveBeenCalledWith(tableProps.name, 'group_change', ['GroupA', 'GroupB'])
expect(emitMock).toHaveBeenCalledTimes(1)
expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount)
})

it('Expands all groups - fire event', () => {
const { container, getAllByRole } = render(<XTable model={{ ...tableProps, events: ['group_change'] }} />)

//collapse all groups
fireEvent.click(container.querySelector('.ms-DetailsHeader-collapseButton')!)
emitMock.mockClear()

//open all groups
fireEvent.click(container.querySelector('.ms-DetailsHeader-collapseButton')!)
expect(emitMock).toHaveBeenCalledWith(tableProps.name, 'group_change', ['GroupA', 'GroupB'])
expect(emitMock).toHaveBeenCalledTimes(1)
expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + items)
})

it('Collapses group - fire event', () => {
const { container, getAllByRole } = render(<XTable model={{ ...tableProps, events: ['group_change'] }} />)

fireEvent.click(container.querySelector('.ms-GroupHeader-expand')!)
expect(emitMock).toHaveBeenCalledWith(tableProps.name, 'group_change', ['GroupA'])
expect(emitMock).toHaveBeenCalledTimes(1)
expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + filteredItem)
})

it('Expands group - fire event', () => {
const { container, getAllByRole } = render(<XTable model={{ ...tableProps, events: ['group_change'] }} />)

fireEvent.click(container.querySelector('.ms-DetailsHeader-collapseButton')!)
emitMock.mockClear()

fireEvent.click(container.querySelector('.ms-GroupHeader-expand')!)
expect(emitMock).toHaveBeenCalledWith(tableProps.name, 'group_change', ['GroupA'])
expect(emitMock).toHaveBeenCalledTimes(1)
expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + items - filteredItem)
})

it('Collapses all groups when some already collapsed - fire event', () => {
const { container, getAllByRole } = render(<XTable model={{ ...tableProps, events: ['group_change'] }} />)

//collapse all groups
fireEvent.click(container.querySelector('.ms-DetailsHeader-collapseButton')!)
expect(emitMock).toHaveBeenCalledWith(tableProps.name, 'group_change', ['GroupA', 'GroupB'])
expect(emitMock).toHaveBeenCalledTimes(1)
expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount)
emitMock.mockClear()

//open all groups
fireEvent.click(container.querySelector('.ms-DetailsHeader-collapseButton')!)
expect(emitMock).toHaveBeenCalledWith(tableProps.name, 'group_change', ['GroupA', 'GroupB'])
expect(emitMock).toHaveBeenCalledTimes(1)
expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + items)
emitMock.mockClear()

//collapse GroupA
fireEvent.click(container.querySelector('.ms-GroupHeader-expand')!)
expect(emitMock).toHaveBeenCalledWith(tableProps.name, 'group_change', ['GroupA'])
expect(emitMock).toHaveBeenCalledTimes(1)
expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount + filteredItem)
emitMock.mockClear()

//collapse all groups
fireEvent.click(container.querySelector('.ms-DetailsHeader-collapseButton')!)
expect(emitMock).toHaveBeenCalledWith(tableProps.name, 'group_change', ['GroupB'])
expect(emitMock).toHaveBeenCalledTimes(1)
expect(getAllByRole('row')).toHaveLength(headerRow + groupHeaderRowsCount)
})

it('Checks if expanded state is preserved after sort', () => {
const { container, getAllByRole } = render(<XTable model={tableProps} />)

Expand Down
25 changes: 22 additions & 3 deletions ui/src/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ export interface Table {
groups?: TableGroup[]
/** Display a pagination control at the bottom of the table. Set this value using `ui.table_pagination()`. */
pagination?: TablePagination
/** The events to capture on this table when pagination is set. One of 'search' | 'sort' | 'filter' | 'download' | 'page_change' | 'reset' | 'select'. */
/** The events to capture on this table. When pagination is set, one of 'search' | 'sort' | 'filter' | 'download' | 'page_change' | 'reset'. These events are available regardless of pagination: 'select' | 'group_change'. */
events?: S[]
/** True to allow only one row to be selected at time. Mutually exclusive with `multiple` attr. */
single?: B
Expand Down Expand Up @@ -491,14 +491,33 @@ const
} />
)
}, []),
onToggleCollapseAll = (isAllCollapsed: B) => expandedRefs.current = isAllCollapsed ? {} : null,
onToggleCollapseAll = (isAllCollapsed: B) => {
if (m.events?.includes('group_change')) {
const changedGroups =
isAllCollapsed && expandedRefs.current && Object.keys(expandedRefs.current).length > 0
? Object.keys(expandedRefs.current)
: groups?.map(group => group.name)
wave.emit(m.name, 'group_change', changedGroups)
}
expandedRefs.current = isAllCollapsed ? {} : null
},
onToggleCollapse = ({ key, isCollapsed }: Fluent.IGroup) => {
if (m.events?.includes('group_change')) {
wave.emit(m.name, 'group_change', [key])
}
if (expandedRefs.current) {
isCollapsed
? expandedRefs.current[key] = false
: delete expandedRefs.current[key]
} else {
expandedRefs.current = { [key]: false }
if (groups){
expandedRefs.current = groups?.reduce((acc, { name }) => {
if (name != key){
acc[name] = false
}
return acc
}, {} as { [key: S]: B })
}
}
},
onRenderRow = (props?: Fluent.IDetailsRowProps) => props
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions website/widgets/form/table.md
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ q.page['example'] = ui.form_card(box='1 1 3 4', items=[

### With collapsed groups

Groups are shown in a collapsed state by default. With the `collapsed` attribute you can change this behavior.
Groups are shown in a collapsed state by default. With the `collapsed` attribute you can change this behavior. You can also keep track of the collapsed states by registering a `'group_change'` [event](/docs/examples/table-events-group) (populated in `q.events`). This is useful when needing to refresh the table and persist collapsed states.

```py
q.page['example'] = ui.form_card(box='1 1 3 4', items=[
Expand All @@ -421,7 +421,8 @@ q.page['example'] = ui.form_card(box='1 1 3 4', items=[
ui.table_row(name='row4', cells=['Task4', 'Low']),
ui.table_row(name='row5', cells=['Task5', 'Very High'])
])
])
],
events=['group_change'])
])
```

Expand Down