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

feat: new section for tabular data #327

Merged
merged 5 commits into from
Nov 18, 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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/userman/gui/extension/dynamic_element/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,4 +355,5 @@ Here are the sections that address the different use cases that your custom
element may need:

- [Scalar properties](scalar_props.md)
- [Tabular data properties](tabular_data_props.md)

219 changes: 219 additions & 0 deletions docs/userman/gui/extension/dynamic_element/tabular_data_props.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
The previous section on [Scalar properties](scalar_props.md) shows how to create a dynamic element that holds a scalar
value. However, when dealing with collections of data, such as tabular data, we need a more complex approach to support
arrays or tables that can be dynamically updated.

In Taipy GUI, tabular data can also be bound to Python variables or expressions, allowing the user interface to instantly
reflect any changes in the underlying data. Custom elements that manage tabular data must declare their properties to
specify the type of data they support, similar to scalar properties, but now using a structure suitable for collections.
This is handled by the PropertyType class, where you can define the type as an array or table format, enabling the
binding of multidimensional data.

For example, a table element can bind a two-dimensional array or a list of objects to its properties. Each time the data
in the array changes, the table element automatically refreshes to display the updated content in the user interface.
This approach leverages TypeScript and JavaScript code in the backend, working with React to dynamically generate HTML
for tabular displays. By using Taipy GUI’s variable binding capabilities, developers can seamlessly update and manage
tabular data within the UI.

Even if a custom element does not need to update its tabular data dynamically, it can still be implemented as a dynamic
element to take advantage of the expressivity and flexibility offered by this approach.

# Using tabular data

In this section, we will expand the dynamic element library, initially created in the [Scalar properties](../scalar_props.md)
section, by adding a new dynamic custom element.

This dynamic element will accept a property containing tabular data and display it within a table. When a Python
variable is bound to this property, updates to the variable will immediately reflect in the table content shown on
the page, ensuring real-time synchronization.

## Declaring a dynamic element {data-source="gui:doc/extension/example_library/example_library.py#L36"}
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
Starting from the code mentioned above, here is how you would declare this new element:
```py title="example_library.py"
from taipy.gui.extension import Element, ElementLibrary, ElementProperty, PropertyType

class ExampleLibrary(ElementLibrary):
def __init__(self) -> None:
# Initialize the set of visual elements for this extension library
self.elements = {
"game_table": Element(
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
"data",
{
"data": ElementProperty(PropertyType.data),
},
# The name of the React component (GameTable) that implements this custom
# element, exported as GameTable in front-end/src/index.ts
# react_component="GameTable",
),
}
```
The declaration of this dynamic element is very similar to what we created in the [Scalar properties](scalar_props.md).
The detailed explanation of the code is as follows:

- The *game_table* element includes a single property: *data*.
- The *data* property has the type *PropertyType.data*, meaning it holds a data value and is dynamic.
- The *get_name()* method in the *ExampleLibrary* class returns the name of the library as a string. This name is used
to identify the library within the Taipy GUI framework.
- The *get_elements()* method in the *ExampleLibrary* class returns a dictionary of all elements that are part of this
library. Each element is defined with its properties and associated React component.

## Creating the React component {data-source="gui:doc/extension/example_library/front-end/src/GameTable.tsx"}

The React component for the *game_table* element is defined as follows:

```jsx title="GameTable.tsx" linenums="1"
import React, { useEffect, useMemo, useState } from "react";
import {
createRequestDataUpdateAction,
useDispatch,
useDispatchRequestUpdateOnFirstRender,
useModule,
TaipyDynamicProps,
TableValueType,
RowType,
RowValue,
} from "taipy-gui";

interface GameTableProps extends TaipyDynamicProps {
data: TableValueType;
}

const pageKey = "no-page-key";

const GameTable = (props: GameTableProps) => {
const { data, updateVarName = "", updateVars = "", id } = props;
const [value, setValue] = useState<Record<string, Array<RowValue>>>({});
const dispatch = useDispatch();
const module = useModule();
const refresh = data?.__taipy_refresh !== undefined;
useDispatchRequestUpdateOnFirstRender(dispatch, id, module, updateVars);

const colsOrder = useMemo(() => {
return Object.keys(value);
}, [value]);

const rows = useMemo(() => {
const rows: RowType[] = [];
if (value) {
Object.entries(value).forEach(([col, colValues]) => {
colValues.forEach((val, idx) => {
rows[idx] = rows[idx] || {};
rows[idx][col] = val;
});
});
}
return rows;
}, [value]);

useEffect(() => {
if (refresh || !data || data[pageKey] === undefined) {
dispatch(
createRequestDataUpdateAction(
updateVarName,
id,
module,
colsOrder,
pageKey,
{},
true,
"ExampleLibrary",
),
);
} else {
setValue(data[pageKey]);
}
}, [refresh, data, colsOrder, updateVarName, id, dispatch, module]);

return (
<div>
<table border={1} cellPadding={10} cellSpacing={0}>
<tbody>
{rows.map((row, index) => (
<tr key={"row" + index}>
{colsOrder.map((col, cidx) => (
<td key={"val" + index + "-" + cidx}>{row[col]}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
};

export default GameTable;
```

The detailed explanation of the code is as follows:

- We use the [*useDispatch()*](../../../../refmans/reference_guiext/functions/useDispatch.md) hook to
dispatch actions to the store and initiate backend communications.
- Additionally, the [*useModule()*](../../../../refmans/reference_guiext/functions/useModule.md) hook
retrieves the page module, enabling correct execution of backend functions.
- To request an update for every dynamic property of an element on initial render, we use the
[*useDispatchRequestUpdateOnFirstRender()*](../../../../refmans/reference_guiext/functions/useDispatchRequestUpdateOnFirstRender.md)
hook provided by the Taipy GUI Extension API. This hook takes five parameters:
- *dispatch*: The React dispatcher associated with the context.
- *id*: The identifier of the element.
- *context*: The execution context.
- *updateVars*: The content of the *updateVars* property.
- We also dispatch the
[*createRequestDataUpdateAction()*](../../../../refmans/reference_guiext/functions/createRequestDataUpdateAction.md)
hook to create a request data update action, which updates the context by invoking the
[*get_data()*](.../refmans/reference/pkg_taipy/pkg_gui/pkg_extension/ElementLibrary/#taipy.gui.extension.ElementLibrary.get_data)
method of the backend library. This invocation triggers an update of front-end elements holding the data.

The [*createRequestDataUpdateAction()*](../../../../refmans/reference_guiext/functions/createRequestUpdateAction.md)
hook accepts eight parameters:

- *name*: The name of the variable containing the requested data, as received in the property.
- *id*: The identifier of the visual element.
- *context*: The execution context.
- *columns*: The list of column names required by the element emitting this action.
- *pageKey*: The unique identifier for the data received from this action.
- *payload*: The payload, specific to the component type (e.g., table, chart).
- *allData*: A flag indicating if all the data is requested.
- *library*: The name of the extension library.

## Exporting the React component {data-source="gui:doc/extension/example_library/front-end/src/index.ts"}

When the component is entirely defined, it must be exported by the JavaScript library.
This is done by adding the export directive in the file *<project dir>/<package dir>front-end/src/index.ts*.

```js title="index.ts"
import GameTable from "./GameTable";

export { GameTable };
```

## Using the element {data-source="gui:doc/extension/table_chess_game.py"}

In the example below, we use the *game_table* element to display a chess game board.
The data is represented as a two-dimensional list of strings, where each string represents a chess piece.
The board is displayed in a table format using the *game_table* element.
We can see how the data property of the control is bound to the Python variable *data*, using the default property syntax.

```py
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
data = [
["♖", "♘", "♗", "♕", "♔", "♗", "♘", "♖"],
["♙", "♙", "♙", "♙", "♙", "♙", "♙", "♙"],
["", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", ""],
["♟", "♟", "♟", "♟", "♟", "♟", "♟", "♟"],
["♜", "♞", "♝", "♛", "♚", "♝", "♞", "♜"]
]

page = """
## Chess Game
<|{data}|example.game_table|>
"""
```

When you run this application, the page displays the element like this:

<figure>
<img src="../chess_game-d.png" class="visible-dark" width="80%"/>
<img src="../chess_game-l.png" class="visible-light" width="80%"/>
<figcaption>Chess game</figcaption>
</figure>
Loading
Loading