AutoLoadingTable.tsx 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. /*
  2. * Copyright 2021-2024 Avaiga Private Limited
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  5. * the License. You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  10. * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  11. * specific language governing permissions and limitations under the License.
  12. */
  13. import React, { useState, useEffect, useCallback, useRef, useMemo, CSSProperties, MouseEvent } from "react";
  14. import Box from "@mui/material/Box";
  15. import MuiTable from "@mui/material/Table";
  16. import TableCell, { TableCellProps } from "@mui/material/TableCell";
  17. import TableContainer from "@mui/material/TableContainer";
  18. import TableHead from "@mui/material/TableHead";
  19. import TableRow from "@mui/material/TableRow";
  20. import TableSortLabel from "@mui/material/TableSortLabel";
  21. import Paper from "@mui/material/Paper";
  22. import { visuallyHidden } from "@mui/utils";
  23. import AutoSizer from "react-virtualized-auto-sizer";
  24. import { FixedSizeList, ListOnItemsRenderedProps } from "react-window";
  25. import InfiniteLoader from "react-window-infinite-loader";
  26. import Skeleton from "@mui/material/Skeleton";
  27. import IconButton from "@mui/material/IconButton";
  28. import Tooltip from "@mui/material/Tooltip";
  29. import AddIcon from "@mui/icons-material/Add";
  30. import DataSaverOn from "@mui/icons-material/DataSaverOn";
  31. import DataSaverOff from "@mui/icons-material/DataSaverOff";
  32. import Download from "@mui/icons-material/Download";
  33. import {
  34. createRequestInfiniteTableUpdateAction,
  35. createSendActionNameAction,
  36. FormatConfig,
  37. } from "../../context/taipyReducers";
  38. import {
  39. ColumnDesc,
  40. getsortByIndex,
  41. Order,
  42. TaipyTableProps,
  43. baseBoxSx,
  44. paperSx,
  45. tableSx,
  46. RowType,
  47. EditableCell,
  48. OnCellValidation,
  49. RowValue,
  50. EDIT_COL,
  51. OnRowDeletion,
  52. addDeleteColumn,
  53. headBoxSx,
  54. getClassName,
  55. LINE_STYLE,
  56. iconInRowSx,
  57. DEFAULT_SIZE,
  58. OnRowSelection,
  59. getRowIndex,
  60. getTooltip,
  61. defaultColumns,
  62. OnRowClick,
  63. DownloadAction,
  64. } from "./tableUtils";
  65. import {
  66. useClassNames,
  67. useDispatch,
  68. useDispatchRequestUpdateOnFirstRender,
  69. useDynamicJsonProperty,
  70. useDynamicProperty,
  71. useFormatConfig,
  72. useModule,
  73. } from "../../utils/hooks";
  74. import TableFilter, { FilterDesc } from "./TableFilter";
  75. import { getSuffixedClassNames } from "./utils";
  76. interface RowData {
  77. colsOrder: string[];
  78. columns: Record<string, ColumnDesc>;
  79. rows: RowType[];
  80. classes: Record<string, string>;
  81. cellProps: Partial<TableCellProps>[];
  82. isItemLoaded: (index: number) => boolean;
  83. selection: number[];
  84. formatConfig: FormatConfig;
  85. onValidation?: OnCellValidation;
  86. onDeletion?: OnRowDeletion;
  87. onRowSelection?: OnRowSelection;
  88. onRowClick?: OnRowClick;
  89. lineStyle?: string;
  90. nanValue?: string;
  91. }
  92. const Row = ({
  93. index,
  94. style,
  95. data: {
  96. colsOrder,
  97. columns,
  98. rows,
  99. classes,
  100. cellProps,
  101. isItemLoaded,
  102. selection,
  103. formatConfig,
  104. onValidation,
  105. onDeletion,
  106. onRowSelection,
  107. onRowClick,
  108. lineStyle,
  109. nanValue,
  110. },
  111. }: {
  112. index: number;
  113. style: CSSProperties;
  114. data: RowData;
  115. }) =>
  116. isItemLoaded(index) ? (
  117. <TableRow
  118. hover
  119. tabIndex={-1}
  120. key={"row" + index}
  121. component="div"
  122. sx={style}
  123. className={(classes && classes.row) + " " + getClassName(rows[index], lineStyle)}
  124. data-index={index}
  125. selected={selection.indexOf(index) > -1}
  126. onClick={onRowClick}
  127. >
  128. {colsOrder.map((col, cidx) => (
  129. <EditableCell
  130. key={"val" + index + "-" + cidx}
  131. className={getClassName(rows[index], columns[col].style, col)}
  132. colDesc={columns[col]}
  133. value={rows[index][col]}
  134. formatConfig={formatConfig}
  135. rowIndex={index}
  136. onValidation={!columns[col].notEditable ? onValidation : undefined}
  137. onDeletion={onDeletion}
  138. onSelection={onRowSelection}
  139. nanValue={columns[col].nanValue || nanValue}
  140. tableCellProps={cellProps[cidx]}
  141. tooltip={getTooltip(rows[index], columns[col].tooltip, col)}
  142. />
  143. ))}
  144. </TableRow>
  145. ) : (
  146. <Skeleton sx={style} key={"Skeleton" + index} />
  147. );
  148. interface PromiseProps {
  149. resolve: () => void;
  150. reject: () => void;
  151. }
  152. interface key2Rows {
  153. key: string;
  154. promises: Record<number, PromiseProps>;
  155. }
  156. const getRowHeight = (size = DEFAULT_SIZE) => (size == DEFAULT_SIZE ? 37 : 54);
  157. const getCellSx = (width: string | number | undefined, size = DEFAULT_SIZE) => ({
  158. width: width,
  159. height: 22,
  160. padding: size == DEFAULT_SIZE ? "7px" : undefined,
  161. });
  162. const AutoLoadingTable = (props: TaipyTableProps) => {
  163. const {
  164. id,
  165. updateVarName,
  166. height = "80vh",
  167. width = "100%",
  168. updateVars,
  169. selected = [],
  170. pageSize = 100,
  171. defaultKey = "",
  172. onEdit = "",
  173. onDelete = "",
  174. onAdd = "",
  175. onAction = "",
  176. size = DEFAULT_SIZE,
  177. userData,
  178. downloadable = false,
  179. } = props;
  180. const [rows, setRows] = useState<RowType[]>([]);
  181. const [rowCount, setRowCount] = useState(1000); // need something > 0 to bootstrap the infinite loader
  182. const dispatch = useDispatch();
  183. const page = useRef<key2Rows>({ key: defaultKey, promises: {} });
  184. const [orderBy, setOrderBy] = useState("");
  185. const [order, setOrder] = useState<Order>("asc");
  186. const [appliedFilters, setAppliedFilters] = useState<FilterDesc[]>([]);
  187. const [visibleStartIndex, setVisibleStartIndex] = useState(0);
  188. const [aggregates, setAggregates] = useState<string[]>([]);
  189. const infiniteLoaderRef = useRef<InfiniteLoader>(null);
  190. const headerRow = useRef<HTMLTableRowElement>(null);
  191. const formatConfig = useFormatConfig();
  192. const module = useModule();
  193. const className = useClassNames(props.libClassName, props.dynamicClassName, props.className);
  194. const active = useDynamicProperty(props.active, props.defaultActive, true);
  195. const editable = useDynamicProperty(props.editable, props.defaultEditable, true);
  196. const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
  197. const baseColumns = useDynamicJsonProperty(props.columns, props.defaultColumns, defaultColumns);
  198. const refresh = typeof props.data === "number";
  199. useEffect(() => {
  200. if (!refresh && props.data && page.current.key && props.data[page.current.key] !== undefined) {
  201. const newValue = props.data[page.current.key];
  202. const promise = page.current.promises[newValue.start];
  203. setRowCount(newValue.rowcount);
  204. const nr = newValue.data as RowType[];
  205. if (Array.isArray(nr) && nr.length > newValue.start) {
  206. setRows(nr);
  207. promise && promise.resolve();
  208. } else {
  209. promise && promise.reject();
  210. }
  211. delete page.current.promises[newValue.start];
  212. }
  213. }, [refresh, props.data]);
  214. useDispatchRequestUpdateOnFirstRender(dispatch, id, module, updateVars);
  215. const onSort = useCallback(
  216. (e: React.MouseEvent<HTMLElement>) => {
  217. const col = e.currentTarget.getAttribute("data-dfid");
  218. if (col) {
  219. const isAsc = orderBy === col && order === "asc";
  220. setOrder(isAsc ? "desc" : "asc");
  221. setOrderBy(col);
  222. setRows([]);
  223. setTimeout(() => infiniteLoaderRef.current?.resetloadMoreItemsCache(true), 1); // So that the state can be changed
  224. }
  225. },
  226. [orderBy, order]
  227. );
  228. useEffect(() => {
  229. if (refresh) {
  230. setRows([]);
  231. setTimeout(() => infiniteLoaderRef.current?.resetloadMoreItemsCache(true), 1); // So that the state can be changed
  232. }
  233. }, [refresh]);
  234. const onAggregate = useCallback((e: MouseEvent<HTMLElement>) => {
  235. const groupBy = e.currentTarget.getAttribute("data-dfid");
  236. if (groupBy) {
  237. setAggregates((ags) => {
  238. const nags = ags.filter((ag) => ag !== groupBy);
  239. if (ags.length == nags.length) {
  240. nags.push(groupBy);
  241. }
  242. return nags;
  243. });
  244. }
  245. e.stopPropagation();
  246. }, []);
  247. const [colsOrder, columns, styles, tooltips, handleNan, filter] = useMemo(() => {
  248. let hNan = !!props.nanValue;
  249. if (baseColumns) {
  250. try {
  251. let filter = false;
  252. Object.values(baseColumns).forEach((col) => {
  253. if (typeof col.filter != "boolean") {
  254. col.filter = !!props.filter;
  255. }
  256. filter = filter || col.filter;
  257. if (typeof col.notEditable != "boolean") {
  258. col.notEditable = !editable;
  259. }
  260. if (col.tooltip === undefined) {
  261. col.tooltip = props.tooltip;
  262. }
  263. });
  264. addDeleteColumn((active && (onAdd || onDelete) ? 1 : 0) + (active && filter ? 1 : 0) + (active && downloadable ? 1 : 0), baseColumns);
  265. const colsOrder = Object.keys(baseColumns).sort(getsortByIndex(baseColumns));
  266. const styTt = colsOrder.reduce<Record<string, Record<string, string>>>((pv, col) => {
  267. if (baseColumns[col].style) {
  268. pv.styles = pv.styles || {};
  269. pv.styles[baseColumns[col].dfid] = baseColumns[col].style as string;
  270. }
  271. hNan = hNan || !!baseColumns[col].nanValue;
  272. if (baseColumns[col].tooltip) {
  273. pv.tooltips = pv.tooltips || {};
  274. pv.tooltips[baseColumns[col].dfid] = baseColumns[col].tooltip as string;
  275. }
  276. return pv;
  277. }, {});
  278. if (props.lineStyle) {
  279. styTt.styles = styTt.styles || {};
  280. styTt.styles[LINE_STYLE] = props.lineStyle;
  281. }
  282. return [colsOrder, baseColumns, styTt.styles, styTt.tooltips, hNan, filter];
  283. } catch (e) {
  284. console.info("ATable.columns: " + ((e as Error).message || e));
  285. }
  286. }
  287. return [
  288. [],
  289. {} as Record<string, ColumnDesc>,
  290. {} as Record<string, string>,
  291. {} as Record<string, string>,
  292. hNan,
  293. false,
  294. ];
  295. }, [active, editable, onAdd, onDelete, baseColumns, props.lineStyle, props.tooltip, props.nanValue, props.filter, downloadable]);
  296. const boxBodySx = useMemo(() => ({ height: height }), [height]);
  297. useEffect(() => {
  298. selected.length &&
  299. infiniteLoaderRef.current &&
  300. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  301. (infiniteLoaderRef.current as any)._listRef.scrollToItem(selected[0]);
  302. }, [selected]);
  303. useEffect(() => {
  304. if (headerRow.current) {
  305. Array.from(headerRow.current.cells).forEach((cell, idx) => {
  306. columns[colsOrder[idx]].widthHint = cell.offsetWidth;
  307. });
  308. }
  309. }, [columns, colsOrder]);
  310. const loadMoreItems = useCallback(
  311. (startIndex: number, stopIndex: number) => {
  312. if (page.current.promises[startIndex]) {
  313. page.current.promises[startIndex].reject();
  314. }
  315. return new Promise<void>((resolve, reject) => {
  316. const agg = aggregates.length
  317. ? colsOrder.reduce((pv, col, idx) => {
  318. if (aggregates.includes(columns[col].dfid)) {
  319. return pv + "-" + idx;
  320. }
  321. return pv;
  322. }, "-agg")
  323. : "";
  324. const cols = colsOrder.map((col) => columns[col].dfid).filter((c) => c != EDIT_COL);
  325. const afs = appliedFilters.filter((fd) => Object.values(columns).some((cd) => cd.dfid === fd.col));
  326. const key = `Infinite-${cols.join()}-${orderBy}-${order}${agg}${afs.map(
  327. (af) => `${af.col}${af.action}${af.value}`
  328. )}`;
  329. page.current = {
  330. key: key,
  331. promises: { ...page.current.promises, [startIndex]: { resolve: resolve, reject: reject } },
  332. };
  333. const applies = aggregates.length
  334. ? colsOrder.reduce<Record<string, unknown>>((pv, col) => {
  335. if (columns[col].apply) {
  336. pv[columns[col].dfid] = columns[col].apply;
  337. }
  338. return pv;
  339. }, {})
  340. : undefined;
  341. dispatch(
  342. createRequestInfiniteTableUpdateAction(
  343. updateVarName,
  344. id,
  345. module,
  346. cols,
  347. key,
  348. startIndex,
  349. stopIndex,
  350. orderBy,
  351. order,
  352. aggregates,
  353. applies,
  354. styles,
  355. tooltips,
  356. handleNan,
  357. afs
  358. )
  359. );
  360. });
  361. },
  362. [
  363. aggregates,
  364. styles,
  365. tooltips,
  366. updateVarName,
  367. orderBy,
  368. order,
  369. id,
  370. colsOrder,
  371. columns,
  372. handleNan,
  373. appliedFilters,
  374. dispatch,
  375. module,
  376. ]
  377. );
  378. const onAddRowClick = useCallback(
  379. () =>
  380. dispatch(
  381. createSendActionNameAction(updateVarName, module, {
  382. action: onAdd,
  383. index: visibleStartIndex,
  384. user_data: userData,
  385. })
  386. ),
  387. [visibleStartIndex, dispatch, updateVarName, onAdd, module, userData]
  388. );
  389. const onDownload = useCallback(
  390. () =>
  391. dispatch(
  392. createSendActionNameAction(updateVarName, module, {
  393. action: DownloadAction,
  394. user_data: userData,
  395. })
  396. ),
  397. [dispatch, updateVarName, module, userData]
  398. );
  399. const isItemLoaded = useCallback((index: number) => index < rows.length && !!rows[index], [rows]);
  400. const onCellValidation: OnCellValidation = useCallback(
  401. (value: RowValue, rowIndex: number, colName: string, userValue: string, tz?: string) =>
  402. dispatch(
  403. createSendActionNameAction(updateVarName, module, {
  404. action: onEdit,
  405. value: value,
  406. index: getRowIndex(rows[rowIndex], rowIndex),
  407. col: colName,
  408. user_value: userValue,
  409. tz: tz,
  410. user_data: userData,
  411. })
  412. ),
  413. [dispatch, updateVarName, onEdit, rows, module, userData]
  414. );
  415. const onRowDeletion: OnRowDeletion = useCallback(
  416. (rowIndex: number) =>
  417. dispatch(
  418. createSendActionNameAction(updateVarName, module, {
  419. action: onDelete,
  420. index: getRowIndex(rows[rowIndex], rowIndex),
  421. user_data: userData,
  422. })
  423. ),
  424. [dispatch, updateVarName, onDelete, rows, module, userData]
  425. );
  426. const onRowSelection: OnRowSelection = useCallback(
  427. (rowIndex: number, colName?: string) =>
  428. dispatch(
  429. createSendActionNameAction(updateVarName, module, {
  430. action: onAction,
  431. index: getRowIndex(rows[rowIndex], rowIndex),
  432. col: colName === undefined ? null : colName,
  433. user_data: userData,
  434. })
  435. ),
  436. [dispatch, updateVarName, onAction, rows, module, userData]
  437. );
  438. const onRowClick = useCallback(
  439. (e: MouseEvent<HTMLTableRowElement>) => {
  440. const { index } = e.currentTarget.dataset || {};
  441. const rowIndex = index === undefined ? NaN : Number(index);
  442. if (!isNaN(rowIndex)) {
  443. onRowSelection(rowIndex);
  444. }
  445. },
  446. [onRowSelection]
  447. );
  448. const onTaipyItemsRendered = useCallback(
  449. (onItemsR: (props: ListOnItemsRenderedProps) => undefined) =>
  450. ({ visibleStartIndex, visibleStopIndex }: { visibleStartIndex: number; visibleStopIndex: number }) => {
  451. setVisibleStartIndex(visibleStartIndex);
  452. onItemsR({ visibleStartIndex, visibleStopIndex } as ListOnItemsRenderedProps);
  453. },
  454. []
  455. );
  456. const rowData: RowData = useMemo(
  457. () => ({
  458. colsOrder: colsOrder,
  459. columns: columns,
  460. rows: rows,
  461. classes: {},
  462. cellProps: colsOrder.map((col) => ({
  463. sx: getCellSx(columns[col].width || columns[col].widthHint, size),
  464. component: "div",
  465. variant: "body",
  466. })),
  467. isItemLoaded: isItemLoaded,
  468. selection: selected,
  469. formatConfig: formatConfig,
  470. onValidation: active && onEdit ? onCellValidation : undefined,
  471. onDeletion: active && onDelete ? onRowDeletion : undefined,
  472. onRowSelection: active && onAction ? onRowSelection : undefined,
  473. onRowClick: active && onAction ? onRowClick : undefined,
  474. lineStyle: props.lineStyle,
  475. nanValue: props.nanValue,
  476. }),
  477. [
  478. rows,
  479. isItemLoaded,
  480. active,
  481. colsOrder,
  482. columns,
  483. selected,
  484. formatConfig,
  485. onEdit,
  486. onCellValidation,
  487. onDelete,
  488. onRowDeletion,
  489. onAction,
  490. onRowSelection,
  491. onRowClick,
  492. props.lineStyle,
  493. props.nanValue,
  494. size,
  495. ]
  496. );
  497. const boxSx = useMemo(() => ({ ...baseBoxSx, width: width }), [width]);
  498. return (
  499. <Box id={id} sx={boxSx} className={`${className} ${getSuffixedClassNames(className, "-autoloading")}`}>
  500. <Paper sx={paperSx}>
  501. <Tooltip title={hover || ""}>
  502. <TableContainer>
  503. <MuiTable sx={tableSx} aria-labelledby="tableTitle" size={size} stickyHeader={true}>
  504. <TableHead>
  505. <TableRow ref={headerRow}>
  506. {colsOrder.map((col, idx) => (
  507. <TableCell
  508. key={col + idx}
  509. sortDirection={orderBy === columns[col].dfid && order}
  510. sx={columns[col].width ? { width: columns[col].width } : {}}
  511. >
  512. {columns[col].dfid === EDIT_COL ? (
  513. [
  514. active && onAdd ? (
  515. <Tooltip title="Add a row" key="addARow">
  516. <IconButton
  517. onClick={onAddRowClick}
  518. size="small"
  519. sx={iconInRowSx}
  520. >
  521. <AddIcon fontSize="inherit" />
  522. </IconButton>
  523. </Tooltip>
  524. ) : null,
  525. active && filter ? (
  526. <TableFilter
  527. key="filter"
  528. columns={columns}
  529. colsOrder={colsOrder}
  530. onValidate={setAppliedFilters}
  531. appliedFilters={appliedFilters}
  532. className={className}
  533. />
  534. ) : null,
  535. active && downloadable ? (
  536. <Tooltip title="Download as CSV" key="downloadCsv">
  537. <IconButton
  538. onClick={onDownload}
  539. size="small"
  540. sx={iconInRowSx}
  541. >
  542. <Download fontSize="inherit" />
  543. </IconButton>
  544. </Tooltip>
  545. ) : null,
  546. ]
  547. ) : (
  548. <TableSortLabel
  549. active={orderBy === columns[col].dfid}
  550. direction={orderBy === columns[col].dfid ? order : "asc"}
  551. data-dfid={columns[col].dfid}
  552. onClick={onSort}
  553. disabled={!active}
  554. hideSortIcon={!active}
  555. >
  556. <Box sx={headBoxSx}>
  557. {columns[col].groupBy ? (
  558. <IconButton
  559. onClick={onAggregate}
  560. size="small"
  561. title="aggregate"
  562. data-dfid={columns[col].dfid}
  563. disabled={!active}
  564. sx={iconInRowSx}
  565. >
  566. {aggregates.includes(columns[col].dfid) ? (
  567. <DataSaverOff fontSize="inherit" />
  568. ) : (
  569. <DataSaverOn fontSize="inherit" />
  570. )}
  571. </IconButton>
  572. ) : null}
  573. {columns[col].title === undefined
  574. ? columns[col].dfid
  575. : columns[col].title}
  576. </Box>
  577. {orderBy === columns[col].dfid ? (
  578. <Box component="span" sx={visuallyHidden}>
  579. {order === "desc"
  580. ? "sorted descending"
  581. : "sorted ascending"}
  582. </Box>
  583. ) : null}
  584. </TableSortLabel>
  585. )}
  586. </TableCell>
  587. ))}
  588. </TableRow>
  589. </TableHead>
  590. </MuiTable>
  591. <Box sx={boxBodySx}>
  592. <AutoSizer>
  593. {({ height, width }) => (
  594. <InfiniteLoader
  595. ref={infiniteLoaderRef}
  596. isItemLoaded={isItemLoaded}
  597. itemCount={rowCount}
  598. loadMoreItems={loadMoreItems}
  599. minimumBatchSize={pageSize}
  600. >
  601. {({ onItemsRendered, ref }) => (
  602. <FixedSizeList
  603. height={height || 100}
  604. width={width || 100}
  605. itemCount={rowCount}
  606. itemSize={getRowHeight(size)}
  607. onItemsRendered={onTaipyItemsRendered(onItemsRendered)}
  608. ref={ref}
  609. itemData={rowData}
  610. >
  611. {Row}
  612. </FixedSizeList>
  613. )}
  614. </InfiniteLoader>
  615. )}
  616. </AutoSizer>
  617. </Box>
  618. </TableContainer>
  619. </Tooltip>
  620. </Paper>
  621. </Box>
  622. );
  623. };
  624. export default AutoLoadingTable;