Skip to content

Commit

Permalink
Table cell bool rendering (#1825)
Browse files Browse the repository at this point in the history
* Table cell bool rendering
commanded by light_bool_render
resolves #662

* change in edit mode too

* use_checkbox

* support lov for boolean

* send lov when necessary only

* useCheckbox

* protect colDesc.type

---------

Co-authored-by: Fred Lefévère-Laoide <[email protected]>
  • Loading branch information
FredLL-Avaiga and Fred Lefévère-Laoide authored Sep 23, 2024
1 parent 9122f41 commit ba3505a
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 59 deletions.
8 changes: 7 additions & 1 deletion frontend/taipy-gui/src/components/Taipy/AutoLoadingTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ interface RowData {
lineStyle?: string;
nanValue?: string;
compRows?: RowType[];
useCheckbox?: boolean;
}

const Row = ({
Expand All @@ -116,6 +117,7 @@ const Row = ({
lineStyle,
nanValue,
compRows,
useCheckbox,
},
}: {
index: number;
Expand Down Expand Up @@ -150,6 +152,7 @@ const Row = ({
tableCellProps={cellProps[cIdx]}
tooltip={getTooltip(rows[index], columns[col].tooltip, col)}
comp={compRows && compRows[index] && compRows[index][col]}
useCheckbox={useCheckbox}
/>
))}
</TableRow>
Expand Down Expand Up @@ -193,6 +196,7 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
downloadable = false,
compare = false,
onCompare = "",
useCheckbox = false,
} = props;
const [rows, setRows] = useState<RowType[]>([]);
const [compRows, setCompRows] = useState<RowType[]>([]);
Expand Down Expand Up @@ -555,10 +559,12 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
lineStyle: props.lineStyle,
nanValue: props.nanValue,
compRows: compRows,
}),
useCheckbox: useCheckbox,
} as RowData),
[
rows,
compRows,
useCheckbox,
isItemLoaded,
active,
colsOrder,
Expand Down
2 changes: 2 additions & 0 deletions frontend/taipy-gui/src/components/Taipy/PaginatedTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
downloadable = false,
compare = false,
onCompare = "",
useCheckbox = false,
} = props;
const pageSize = props.pageSize === undefined || props.pageSize < 1 ? 100 : Math.round(props.pageSize);
const [value, setValue] = useState<Record<string, unknown>>({});
Expand Down Expand Up @@ -607,6 +608,7 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
nanValue={columns[col].nanValue || props.nanValue}
tooltip={getTooltip(row, columns[col].tooltip, col)}
comp={compRows && compRows[index] && compRows[index][col]}
useCheckbox={useCheckbox}
/>
))}
</TableRow>
Expand Down
61 changes: 51 additions & 10 deletions frontend/taipy-gui/src/components/Taipy/tableUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export interface TaipyTableProps extends TaipyActiveProps, TaipyMultiSelectProps
downloadable?: boolean;
onCompare?: string;
compare?: boolean;
useCheckbox?: boolean;
}

export const DownloadAction = "__Taipy__download_csv";
Expand Down Expand Up @@ -193,6 +194,7 @@ interface EditableCellProps {
tooltip?: string;
tableCellProps?: Partial<TableCellProps>;
comp?: RowValue;
useCheckbox?: boolean;
}

export const defaultColumns = {} as Record<string, ColumnDesc>;
Expand Down Expand Up @@ -293,6 +295,7 @@ export const EditableCell = (props: EditableCellProps) => {
tooltip,
tableCellProps = emptyObject,
comp,
useCheckbox = false,
} = props;
const [val, setVal] = useState<RowValue | Date>(value);
const [edit, setEdit] = useState(false);
Expand All @@ -306,14 +309,16 @@ export const EditableCell = (props: EditableCellProps) => {
const onBoolChange = useCallback((e: ChangeEvent<HTMLInputElement>) => setVal(e.target.checked), []);
const onDateChange = useCallback((date: Date | null) => setVal(date), []);

const boolVal = colDesc.type?.startsWith("bool") && val as boolean;

const withTime = useMemo(() => !!colDesc.format && colDesc.format.toLowerCase().includes("h"), [colDesc.format]);

const buttonImg = useMemo(() => {
let m;
if (typeof value == "string" && (m = imgButtonRe.exec(value)) !== null) {
return {
text: !!m[1] ? m[3]: m[2],
value: !!m[1] ? m[2]: m[3],
text: !!m[1] ? m[3] : m[2],
value: !!m[1] ? m[2] : m[3],
img: !!m[1],
action: !!onSelection,
};
Expand Down Expand Up @@ -450,6 +455,14 @@ export const EditableCell = (props: EditableCellProps) => {
[colDesc.freeLov]
);

const boolTitle = useMemo(() => {
if (!colDesc.type?.startsWith("bool") || !colDesc.lov || colDesc.lov.length == 0) {
return boolVal ? "True": "False";
}
return colDesc.lov[boolVal ? 1: 0];
}, [colDesc.type, boolVal, colDesc.lov]);


useEffect(() => {
!onValidation && setEdit(false);
}, [onValidation]);
Expand All @@ -472,14 +485,27 @@ export const EditableCell = (props: EditableCellProps) => {
{edit ? (
colDesc.type?.startsWith("bool") ? (
<Box sx={cellBoxSx}>
lightBool ? (
<input
type="checkbox"
checked={val as boolean}
title={boolTitle}
style={iconInRowSx}
className={getSuffixedClassNames(tableClassName, "-bool")}
ref={setInputFocus}
onChange={onBoolChange}
/>
) : (
<Switch
checked={val as boolean}
size="small"
title={val ? "True" : "False"}
title={boolTitle}
sx={iconInRowSx}
onChange={onBoolChange}
inputRef={setInputFocus}
className={getSuffixedClassNames(tableClassName, "-bool")}
/>
)
<Box sx={iconsWrapperSx}>
<IconButton onClick={onCheckClick} size="small" sx={iconInRowSx}>
<CheckIcon fontSize="inherit" />
Expand All @@ -498,6 +524,7 @@ export const EditableCell = (props: EditableCellProps) => {
slotProps={textFieldProps}
inputRef={setInputFocus}
sx={tableFontSx}
className={getSuffixedClassNames(tableClassName, "-date")}
/>
) : (
<DatePicker
Expand All @@ -506,6 +533,7 @@ export const EditableCell = (props: EditableCellProps) => {
slotProps={textFieldProps}
inputRef={setInputFocus}
sx={tableFontSx}
className={getSuffixedClassNames(tableClassName, "-date")}
/>
)}
<Box sx={iconsWrapperSx}>
Expand Down Expand Up @@ -542,6 +570,7 @@ export const EditableCell = (props: EditableCellProps) => {
margin="dense"
variant="standard"
sx={tableFontSx}
className={getSuffixedClassNames(tableClassName, "-input")}
/>
)}
disableClearable={!colDesc.freeLov}
Expand All @@ -563,6 +592,7 @@ export const EditableCell = (props: EditableCellProps) => {
inputRef={setInputFocus}
margin="dense"
sx={tableFontSx}
className={getSuffixedClassNames(tableClassName, "-input")}
endAdornment={
<Box sx={iconsWrapperSx}>
<IconButton onClick={onCheckClick} size="small" sx={iconInRowSx}>
Expand All @@ -582,6 +612,7 @@ export const EditableCell = (props: EditableCellProps) => {
onKeyDown={onDeleteKeyDown}
inputRef={setInputFocus}
sx={tableFontSx}
className={getSuffixedClassNames(tableClassName, "-delete")}
endAdornment={
<Box sx={iconsWrapperSx}>
<IconButton onClick={onDeleteCheckClick} size="small" sx={iconInRowSx}>
Expand Down Expand Up @@ -624,13 +655,23 @@ export const EditableCell = (props: EditableCellProps) => {
</Button>
)
) : value !== null && value !== undefined && colDesc.type && colDesc.type.startsWith("bool") ? (
<Switch
checked={value as boolean}
size="small"
title={value ? "True" : "False"}
sx={defaultCursorIcon}
className={getSuffixedClassNames(tableClassName, "-bool")}
/>
useCheckbox ? (
<input
type="checkbox"
checked={value as boolean}
title={boolTitle}
style={defaultCursor}
className={getSuffixedClassNames(tableClassName, "-bool")}
/>
) : (
<Switch
checked={value as boolean}
size="small"
title={boolTitle}
sx={defaultCursorIcon}
className={getSuffixedClassNames(tableClassName, "-bool")}
/>
)
) : (
<span style={defaultCursor}>
{formatValue(value as RowValue, colDesc, formatConfig, nanValue)}
Expand Down
64 changes: 32 additions & 32 deletions taipy/gui/_renderers/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,22 +154,22 @@ def _get_variable_hash_names(
# Bind potential function and expressions in self.attributes
for k, v in attributes.items():
val = v
hashname = hash_names.get(k)
if hashname is None:
hash_name = hash_names.get(k)
if hash_name is None:
if callable(v):
if v.__name__ == "<lambda>":
hashname = f"__lambda_{id(v)}"
gui._bind_var_val(hashname, v)
hash_name = f"__lambda_{id(v)}"
gui._bind_var_val(hash_name, v)
else:
hashname = _get_expr_var_name(v.__name__)
hash_name = _get_expr_var_name(v.__name__)
elif isinstance(v, str):
# need to unescape the double quotes that were escaped during preprocessing
(val, hashname) = _Builder.__parse_attribute_value(gui, v.replace('\\"', '"'))
(val, hash_name) = _Builder.__parse_attribute_value(gui, v.replace('\\"', '"'))

if val is not None or hashname:
if val is not None or hash_name:
attributes[k] = val
if hashname:
hashes[k] = hashname
if hash_name:
hashes[k] = hash_name
return hashes

@staticmethod
Expand Down Expand Up @@ -209,8 +209,8 @@ def get_name_indexed_property(self, name: str) -> t.Dict[str, t.Any]:
return _get_name_indexed_property(self.__attributes, name)

def __get_boolean_attribute(self, name: str, default_value=False):
boolattr = self.__attributes.get(name, default_value)
return _is_true(boolattr) if isinstance(boolattr, str) else bool(boolattr)
bool_attr = self.__attributes.get(name, default_value)
return _is_true(bool_attr) if isinstance(bool_attr, str) else bool(bool_attr)

def set_boolean_attribute(self, name: str, value: bool):
"""
Expand Down Expand Up @@ -309,12 +309,12 @@ def set_number_attribute(self, name: str, default_value: t.Optional[str] = None,
def __set_string_attribute(
self, name: str, default_value: t.Optional[str] = None, optional: t.Optional[bool] = True
):
strattr = self.__attributes.get(name, default_value)
if strattr is None:
str_attr = self.__attributes.get(name, default_value)
if str_attr is None:
if not optional:
_warn(f"Property {name} is required for control {self.__control_type}.")
return self
return self.set_attribute(_to_camel_case(name), str(strattr))
return self.set_attribute(_to_camel_case(name), str(str_attr))

def __set_dynamic_date_attribute(self, var_name: str, default_value: t.Optional[str] = None):
date_attr = self.__attributes.get(var_name, default_value)
Expand Down Expand Up @@ -347,23 +347,23 @@ def __set_dynamic_string_attribute(
def __set_function_attribute(
self, name: str, default_value: t.Optional[str] = None, optional: t.Optional[bool] = True
):
strattr = self.__attributes.get(name, default_value)
if strattr is None:
str_attr = self.__attributes.get(name, default_value)
if str_attr is None:
if not optional:
_warn(f"Property {name} is required for control {self.__control_type}.")
return self
elif callable(strattr):
strattr = self.__hashes.get(name)
if strattr is None:
elif callable(str_attr):
str_attr = self.__hashes.get(name)
if str_attr is None:
return self
elif _is_boolean(strattr) and not _is_true(strattr):
elif _is_boolean(str_attr) and not _is_true(str_attr):
return self.__set_react_attribute(_to_camel_case(name), False)
elif strattr:
strattr = str(strattr)
func = self.__gui._get_user_function(strattr)
if func == strattr:
_warn(f"{self.__control_type}.{name}: {strattr} is not a function.")
return self.set_attribute(_to_camel_case(name), strattr) if strattr else self
elif str_attr:
str_attr = str(str_attr)
func = self.__gui._get_user_function(str_attr)
if func == str_attr:
_warn(f"{self.__control_type}.{name}: {str_attr} is not a function.")
return self.set_attribute(_to_camel_case(name), str_attr) if str_attr else self

def __set_string_or_number_attribute(self, name: str, default_value: t.Optional[t.Any] = None):
attr = self.__attributes.get(name, default_value)
Expand Down Expand Up @@ -506,7 +506,7 @@ def __build_rebuild_fn(self, fn_name: str, attribute_names: t.Iterable[str]):
self.__gui._set_building(True)
return self.__gui._evaluate_expr(
"{"
+ f'{fn_name}({rebuild}, {rebuild_name}, "{quote(json.dumps(attributes))}", "{quote(json.dumps(hashes))}", {", ".join([f"{k}={v2}" for k, v2 in {v: self.__gui._get_real_var_name(v)[0] for v in hashes.values()}.items()])})' # noqa: E501
+ f'{fn_name}({rebuild}, {rebuild_name}, "{quote(json.dumps(attributes))}", "{quote(json.dumps(hashes))}", {", ".join([f"{k}={v2}" for k, v2 in {v: self.__gui._get_real_var_name(t.cast(str, v))[0] for v in hashes.values()}.items()])})' # noqa: E501
+ "}"
)
finally:
Expand Down Expand Up @@ -882,7 +882,7 @@ def _set_partial(self):
return self

def _set_propagate(self):
val = self.__get_boolean_attribute("propagate", self.__gui._config.config.get("propagate"))
val = self.__get_boolean_attribute("propagate", t.cast(bool, self.__gui._config.config.get("propagate")))
return self if val else self.set_boolean_attribute("propagate", False)

def __set_refresh_on_update(self):
Expand Down Expand Up @@ -918,7 +918,7 @@ def _set_kind(self):
def __get_typed_hash_name(self, hash_name: str, var_type: t.Optional[PropertyType]) -> str:
if taipy_type := _get_taipy_type(var_type):
expr = self.__gui._get_expr_from_hash(hash_name)
hash_name = self.__gui._evaluate_bind_holder(taipy_type, expr)
hash_name = self.__gui._evaluate_bind_holder(t.cast(t.Type[_TaipyBase], taipy_type), expr)
return hash_name

def __set_dynamic_bool_attribute(self, name: str, def_val: t.Any, with_update: bool, update_main=True):
Expand Down Expand Up @@ -1045,7 +1045,7 @@ def set_attributes(self, attributes: t.List[tuple]): # noqa: C901
elif var_type == PropertyType.dynamic_date:
self.__set_dynamic_date_attribute(attr[0], _get_tuple_val(attr, 2, None))
elif var_type == PropertyType.data:
self.__set_dynamic_property_without_default(attr[0], var_type)
self.__set_dynamic_property_without_default(attr[0], t.cast(PropertyType, var_type))
elif (
var_type == PropertyType.lov
or var_type == PropertyType.single_lov
Expand All @@ -1058,10 +1058,10 @@ def set_attributes(self, attributes: t.List[tuple]): # noqa: C901
)
elif var_type == PropertyType.lov_value:
self.__set_dynamic_property_without_default(
attr[0], var_type, _get_tuple_val(attr, 2, None) == "optional"
attr[0], t.cast(PropertyType, var_type), _get_tuple_val(attr, 2, None) == "optional"
)
elif var_type == PropertyType.toHtmlContent:
self.__set_html_content(attr[0], "page", var_type)
self.__set_html_content(attr[0], "page", t.cast(PropertyType, var_type))
elif isclass(var_type) and issubclass(var_type, _TaipyBase):
prop_name = _to_camel_case(attr[0])
if hash_name := self.__hashes.get(attr[0]):
Expand Down
1 change: 1 addition & 0 deletions taipy/gui/_renderers/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ class _Factory:
("hover_text", PropertyType.dynamic_string),
("size",),
("downloadable", PropertyType.boolean),
("use_checkbox", PropertyType.boolean),
]
)
._set_propagate()
Expand Down
Loading

0 comments on commit ba3505a

Please sign in to comment.