@fto-consult/expo-ui 2.13.2 → 2.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fto-consult/expo-ui",
3
- "version": "2.13.2",
3
+ "version": "2.14.1",
4
4
  "description": "Bibliothèque de composants UI Expo,react-native",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -19,6 +19,7 @@ export default function CountryFlagComponent({code,label,withName,withCode,text,
19
19
  <Image
20
20
  accessibilityIgnoresInvertColors
21
21
  tesID ={tesID+"Image"}
22
+ editable = {false}
22
23
  {...props}
23
24
  size = {25}
24
25
  style={[props.style]}
@@ -27,7 +27,7 @@ export const getCountryFieldProps = (props)=>{
27
27
  },
28
28
  renderItem : ({item})=>{
29
29
  return <View style={[styles.renderedImage]}>
30
- {(isNonNullString(item.image) || isNumber(item.image)) && <Image accessibilityIgnoresInvertColors {...imageProps} style={[styles.flagImage,{marginRight:10},imageProps.style]} source={isNumber(item.image)?item.image:{uri:item.image}}/>}
30
+ {(isNonNullString(item.image) || isNumber(item.image)) && <Image editable = {false} accessibilityIgnoresInvertColors {...imageProps} style={[styles.flagImage,{marginRight:10},imageProps.style]} source={isNumber(item.image)?item.image:{uri:item.image}}/>}
31
31
  <Label>{item.label}</Label>
32
32
  </View>
33
33
  },
@@ -308,6 +308,7 @@ const DatagridFactory = (Factory)=>{
308
308
  filter,
309
309
  actions,
310
310
  sortable,
311
+ autoSort,
311
312
  exportable,
312
313
  filterOrOperator,
313
314
  filterAndOperator,
@@ -325,6 +326,7 @@ const DatagridFactory = (Factory)=>{
325
326
  backToTopProps,
326
327
  testID,
327
328
  renderEmpty,
329
+ ...rest
328
330
  } = this.props
329
331
  const hasData = this.state.data.length ? true : false;
330
332
  testID = defaultStr(testID,"RN_DatagridAccordion");
@@ -433,6 +435,7 @@ const DatagridFactory = (Factory)=>{
433
435
  <View testID={testID+"_HeaderQueryLimit"} style={[styles.paginationItem]}>
434
436
  {this.renderQueryLimit(this.state.data.length.formatNumber())}
435
437
  </View>
438
+ {this.renderCustomPagination()}
436
439
  {sortedColumnsLength ? <View testID={testID+"_HeaderSortedColumns"} style={[styles.sortableItems,styles.paginationItem,{paddingRight:10}]}>
437
440
  <Icon
438
441
  testID={testID+"_HeaderSortIcon"}
@@ -503,6 +506,7 @@ const DatagridFactory = (Factory)=>{
503
506
  ,icon : showFooters?'view-column':'view-module'
504
507
  ,text : (showFooters?'Masquer/Ligne des totaux':'Afficher/Ligne des totaux')
505
508
  }:null,
509
+ ...this.renderCustomMenu(),
506
510
  ...restItems,
507
511
  this.canScrollTo() && {
508
512
  text : 'Retour en haut',
@@ -579,6 +583,7 @@ const DatagridFactory = (Factory)=>{
579
583
  {hasData ? <FlashList
580
584
  estimatedItemSize = {150}
581
585
  prepareItems = {false}
586
+ {...rest}
582
587
  {...accordionProps}
583
588
  testID = {testID}
584
589
  extraData = {this.state.refresh}
@@ -78,7 +78,6 @@ export default class CommonDatagridComponent extends AppComponent {
78
78
  sData.fixedTable = defaultBool(sData.fixedTable,false);
79
79
  extendObj(this.state, {
80
80
  data,
81
- sort :defaultObj(props.sort),
82
81
  showFilters : defaultBool(props.showFilters,(sData.showFilter? true : this.isPivotDatagrid())),
83
82
  showFooters : defaultBool(props.showFooters,(sData.showFooters? true : false)),
84
83
  fixedTable : sData.fixedTable
@@ -87,6 +86,9 @@ export default class CommonDatagridComponent extends AppComponent {
87
86
  layoutRef : {
88
87
  value : React.createRef(null),
89
88
  },
89
+ sortRef : {
90
+ value : {current : defaultObj(props.sort)}
91
+ },
90
92
  preparedColumns : {
91
93
  value : {},override:false, writable:false,
92
94
  },
@@ -109,17 +111,15 @@ export default class CommonDatagridComponent extends AppComponent {
109
111
  })
110
112
  this.isLoading = this.isLoading.bind(this);
111
113
  this.getProgressBar = this.getProgressBar.bind(this);
112
- this.state.sort.dir = defaultStr(this.state.sort.dir,this.state.sort.column == "date"?"desc":'asc')
114
+ this.sortRef.current.dir = defaultStr(this.sortRef.current.dir,this.sortRef.current.column == "date"?"desc":'asc')
113
115
  this.hasColumnsHalreadyInitialized = false;
114
116
  this.initColumns(props.columns);
115
- if(!isNonNullString(this.state.sort.column) && "date" in this.state.columns){
116
- this.state.sort.column = "date";
117
+ if(!isNonNullString(this.sortRef.current.column) && "date" in this.state.columns){
118
+ this.sortRef.current.column = "date";
117
119
  }
118
120
  this.INITIAL_STATE = {
119
121
  data,
120
- sort : this.state.sort
121
122
  }
122
- this._sort = this.state.sort;
123
123
  this._datagridId = isNonNullString(this.props.id)? this.props.id : uniqid("datagrid-id")
124
124
  this.canDoFilter = true;
125
125
  this.filters = {}
@@ -308,7 +308,8 @@ export default class CommonDatagridComponent extends AppComponent {
308
308
  return ;
309
309
  }
310
310
  canPaginateData(){
311
- return false;
311
+ if(this.props.handlePagination === false) return false;
312
+ return this.isDatagrid();
312
313
  }
313
314
  getFiltersProps(){
314
315
  return this.props.filters;
@@ -688,11 +689,11 @@ export default class CommonDatagridComponent extends AppComponent {
688
689
  }
689
690
  */
690
691
  sort (column,dir){
691
- let sort = extendObj({},{
692
+ const sort = extendObj({},{
692
693
  dir: '',
693
694
  column : '',
694
695
  ignoreCase : true
695
- }, this.state.sort);
696
+ }, this.sortRef.current);
696
697
  if(isNonNullString(column)){
697
698
  sort.column = column;
698
699
  }
@@ -702,8 +703,8 @@ export default class CommonDatagridComponent extends AppComponent {
702
703
  if(isNonNullString(dir)){
703
704
  sort.dir = dir;
704
705
  }
705
- if(this.state.sort.column === sort.column){
706
- switch(this.state.sort.dir){
706
+ if(this.sortRef.current.column === sort.column){
707
+ switch(this.sortRef.current.dir){
707
708
  case 'asc':
708
709
  sort.dir = 'desc';
709
710
  break;
@@ -719,9 +720,12 @@ export default class CommonDatagridComponent extends AppComponent {
719
720
  sort.dir = sort.dir.trim().toLowerCase();
720
721
  }
721
722
  if(sort.dir !== "asc" && sort.dir !== "desc") sort.dir = 'asc';
722
- this._previousSortObj = this._sort;
723
- this._sort = sort;
724
- this.prepareColumns({sortedColumn : this._sort});
723
+ this._previousSortObj = Object.clone(this.sortRef.current);
724
+ this.sortRef.current = sort;
725
+ this.prepareColumns({sortedColumn : this.sortRef.current});
726
+ if(typeof this.props.onSort =='function' && this.props.onSort({...sort,context:this,sort,data:this.INITIAL_STATE.data,fields:this.state.columns,columns:this.state.columns}) === false){
727
+ return;
728
+ }
725
729
  this.prepareData({data:this.state.data,updateFooters:false},(state)=>{
726
730
  this.setState(state);
727
731
  });
@@ -752,7 +756,41 @@ export default class CommonDatagridComponent extends AppComponent {
752
756
  }
753
757
  this.handlePagination(newStart, limit, nextPage);
754
758
  }
755
-
759
+ /*** pour le rendu personalisé de la pagination */
760
+ renderCustomPagination(){
761
+ if(typeof this.props.renderCustomPagination ==='function'){
762
+ const r = this.props.renderCustomPagination({
763
+ context:this,refresh:this.refresh.bind(this)
764
+ });
765
+ return React.isValidElement(r)? r : null;
766
+ }
767
+ return null;
768
+ }
769
+ /*** permet de faire le rendu de certaines entête personalisés
770
+ * utile lorsque l'on veut par exemple afficher d'autres information au niveau de l'entête du tableau
771
+ */
772
+ renderCustomMenu(){
773
+ const customMenu = []
774
+ Object.map(this.props.customMenu,(menu,i)=>{
775
+ if(isObj(menu)){
776
+ const {onPress,menu,label,text,children,...rest} = menu;
777
+ const args = {context:this,refresh:this.refresh.bind(this)};
778
+ const lCB = defaultVal(children,label,text);
779
+ const labelText = typeof lCB ==='function'? lCB(args) : lCB;
780
+ if(labelText === false || !React.isValidElement(labelText,true)) return;
781
+ customMenu.push({
782
+ ...rest,
783
+ label : labelText,
784
+ onPress : (event)=>{
785
+ if(typeof onPress =='function'){
786
+ return onPress({...React.getOnPressArgs(event),...args})
787
+ }
788
+ }
789
+ })
790
+ }
791
+ });
792
+ return customMenu;
793
+ }
756
794
  /*** aller à la dernière page */
757
795
  _goToLastPage(){
758
796
  let { start,limit} = this._pagination;
@@ -913,7 +951,7 @@ export default class CommonDatagridComponent extends AppComponent {
913
951
  args = defaultObj(args);
914
952
  const filteredColumns = isObjOrArray(args.filteredColumns)?args.filteredColumns : isObjOrArray(this.state.filteredColumns) ? this.state.filteredColumns : {};
915
953
  const columns = args.columns || this.state.columns;
916
- const currentSortedColumn = isObj(args.sortedColumn) && args.sortedColumn.column? args.sortedColumn : defaultObj(this.state.sort);
954
+ const currentSortedColumn = isObj(args.sortedColumn) && args.sortedColumn.column? args.sortedColumn : defaultObj(this.sortRef.current);
917
955
  const visibleColumns = [],headerFilters = [],visibleColumnsNames={};
918
956
  const sortable = defaultBool(this.props.sortable,true);
919
957
  const sortedColumns = {};
@@ -1072,7 +1110,7 @@ export default class CommonDatagridComponent extends AppComponent {
1072
1110
  }
1073
1111
  getPaginatedSelectedRows(data){
1074
1112
  data = isArray(data)? data : this.INITIAL_STATE.data;
1075
- if(JSON.stringify(this._previousSortObj) !== JSON.stringify(this._sort || JSON.stringify(this._previousPagination) !== JSON.stringify(this._pagination)) && this.getPaginatedData().length !== data.length){
1113
+ if(JSON.stringify(this._previousSortObj) !== JSON.stringify(this.sortRef.current) || JSON.stringify(this._previousPagination) !== JSON.stringify(this._pagination) && this.getPaginatedData().length !== data.length){
1076
1114
  return {};
1077
1115
  }
1078
1116
  return this.selectedRows;
@@ -1107,10 +1145,10 @@ export default class CommonDatagridComponent extends AppComponent {
1107
1145
  });
1108
1146
  data = newData;
1109
1147
  }
1110
- if(this.isDatagrid() && isNonNullString(this._sort.column)){
1111
- if(isObj(this.state.columns) && this.state.columns[this._sort.column]){
1112
- let field = this.state.columns[this._sort.column];
1113
- const cfg = Object.assign({},this._sort);
1148
+ if(this.canAutoSort() && isNonNullString(this.sortRef.current.column)){
1149
+ if(isObj(this.state.columns) && this.state.columns[this.sortRef.current.column]){
1150
+ let field = this.state.columns[this.sortRef.current.column];
1151
+ const cfg = Object.assign({},this.sortRef.current);
1114
1152
  cfg.getItem = (item,columnName,{getItem})=>{
1115
1153
  if(isObj(item) && (field.type =='decimal' || field.type =="number")){
1116
1154
  const v = item[columnName];
@@ -1131,7 +1169,7 @@ export default class CommonDatagridComponent extends AppComponent {
1131
1169
  } else if(force){
1132
1170
  this.setSelectedRows();
1133
1171
  }
1134
- const state = {data,sort : this._sort};
1172
+ const state = {data};
1135
1173
  if((cb)){
1136
1174
  cb(state);
1137
1175
  }
@@ -1320,7 +1358,7 @@ export default class CommonDatagridComponent extends AppComponent {
1320
1358
  this._pagination.start = 0;
1321
1359
  }
1322
1360
  this.filtersSelectors = {selector:this.getFilters()};
1323
- return this.fetchData({force:true,fetchOptions:this.filtersSelectors});
1361
+ return this.fetchData({force:true,isFiltering : true,fetchOptions:this.filtersSelectors});
1324
1362
  }
1325
1363
  onSetQueryLimit(){
1326
1364
  if(!this.canSetQueryLimit()) return;
@@ -1334,7 +1372,7 @@ export default class CommonDatagridComponent extends AppComponent {
1334
1372
  this.currentDatagridQueryLimit = this.getSessionData("dataSourceQueryLimit");
1335
1373
  setQueryLimit(this.currentDatagridQueryLimit,(limit)=>{
1336
1374
  this.setSessionData("dataSourceQueryLimit",limit)
1337
- notify.success("Le nombre maximal d'élément a été définit à la valeur "+(limit==0?" infinit ":limit.formatNumber())+". Cette valeur sera prise en compte à la prochaine réactualisation du tableau")
1375
+ notify.success("Le nombre maximal d'élément à récuperer par page a été définit à la valeur "+(limit==0?" infinit ":limit.formatNumber())+". Cette valeur sera prise en compte à la prochaine réactualisation du tableau")
1338
1376
  });
1339
1377
  }
1340
1378
 
@@ -1348,6 +1386,9 @@ export default class CommonDatagridComponent extends AppComponent {
1348
1386
  return 0;
1349
1387
  }
1350
1388
  canHandleQueryLimit(){
1389
+ if(typeof this.props.handleQueryLimit ==='boolean'){
1390
+ return this.props.handleQueryLimit;
1391
+ }
1351
1392
  return true;
1352
1393
  }
1353
1394
  renderQueryLimit(content){
@@ -1357,7 +1398,7 @@ export default class CommonDatagridComponent extends AppComponent {
1357
1398
  let s = "";
1358
1399
  let canSetQ = this.canSetQueryLimit();
1359
1400
  if(canSetQ){
1360
- s = ".\nPressez pendent quelques secondes pour modifier cette valeur du nombre limite d'éléments de la liste";
1401
+ s = ".\nPressez pendent quelques secondes pour modifier cette valeur du nombre limite d'éléments de la liste par page";
1361
1402
  }
1362
1403
  else if(isDecimal(cLImit)){
1363
1404
  cLImit = (" de "+cLImit.formatNumber())
@@ -1533,9 +1574,19 @@ export default class CommonDatagridComponent extends AppComponent {
1533
1574
  getLinesProgressBar(){
1534
1575
  return CommonDatagridComponent.LinesProgressBar(this.props);
1535
1576
  }
1577
+ /*** si le datagrid sera sortable */
1578
+ isSortable(){
1579
+ return this.isDatagrid() && this.props.sortable !== false? true : false;
1580
+ }
1581
+ canAutoSort(){
1582
+ return this.isSortable() && this.props.autoSort !==false ? true : false;
1583
+ }
1536
1584
  isSelectableMultiple(){
1537
1585
  return defaultBool(this.props.selectableMultiple,true)
1538
1586
  }
1587
+ getSort(){
1588
+ return defaultObj(this.sortRef.current);
1589
+ }
1539
1590
  renderHeaderCell({columnDef,columnField}){
1540
1591
  if(this.isSelectableColumn(columnDef,columnField)){
1541
1592
  return <Checkbox
@@ -1666,16 +1717,16 @@ export default class CommonDatagridComponent extends AppComponent {
1666
1717
  else if(_type =='select_country' || _type =='selectcountry'){
1667
1718
  _render = <Flag withCode {...columnDef} length={undefined} width={undefined} height={undefined} code={defaultValue}/>
1668
1719
  }
1669
- ///le lien vers le table data se fait via une colonne de type selecttabledata ou select_tabledata ou potant l'une des propriétés foreignKeyTable ou linkToTable de type chaine de caractère non nulle
1670
- else if(arrayValueExists(['piece','selecttabledata','id'],_type) || isNonNullString(columnDef.linkToTable) || isNonNullString(columnDef.foreignKeyTable)){
1671
- let tableName = defaultStr(columnDef.linkToTable && columnDef.linkToTable,columnDef.foreignKeyTable && columnDef.foreignKeyTable,columnDef.tableName,columnDef.table).toUpperCase();
1720
+ ///le lien vers le table data se fait via la colonne ayant la propriété foreignKeyTable de type chaine de caractère non nulle
1721
+ else if(isNonNullString(columnDef.foreignKeyTable) || columnDef.primaryKey === true || arrayValueExists(['id','piece'],_type)){
1672
1722
  const id = rowData[columnField]?.toString();
1673
1723
  if(isNonNullString(id)){
1674
1724
  _render = <TableLink
1675
- tableName = {tableName}
1676
1725
  id = {id}
1726
+ foreignKeyTable = {defaultStr(columnDef.foreignKeyTable,columnDef.table,columnDef.tableName)}
1727
+ foreignKeyColumn = {defaultStr(columnDef.foreignKeyColumn,columnDef.field)}
1728
+ {...columnDef}
1677
1729
  data = {rowData}
1678
- columnDef = {columnDef}
1679
1730
  columnField = {columnField}
1680
1731
  >
1681
1732
  {rowData[columnField]}
@@ -1770,9 +1821,9 @@ export default class CommonDatagridComponent extends AppComponent {
1770
1821
  let fmat = defaultStr(columnDef.format).toLowerCase();
1771
1822
  if(fmat == "money"){
1772
1823
  _render = _render.formatMoney();
1773
- } else if(fmat =="number") {
1824
+ } else //if(fmat =="number") {
1774
1825
  _render = _render.formatNumber();
1775
- }
1826
+ //}
1776
1827
  }
1777
1828
  if(_render && isObj(renderProps)){
1778
1829
  let Component = defaultVal(renderProps.Component,Label);
@@ -1782,7 +1833,7 @@ export default class CommonDatagridComponent extends AppComponent {
1782
1833
  if(typeof _render =='boolean'){
1783
1834
  _render = _render ? "Oui" : "Non";
1784
1835
  }
1785
- if(typeof _render ==='string' || typeof _render =='decimal'){
1836
+ if(typeof _render ==='string' || typeof _render =='number'){
1786
1837
  _render = <Label selectable>{_render}</Label>
1787
1838
  }
1788
1839
  _render = React.isValidElement(_render)|| Array.isArray(_render)?_render:null;
@@ -1950,6 +2001,14 @@ CommonDatagridComponent.propTypes = {
1950
2001
  /*** pour le rendu du footer, pied de page en affichage accordion */
1951
2002
  sessionName : PropTypes.string,
1952
2003
  onFetchData : PropTypes.func,
2004
+ handleQueryLimit : PropTypes.bool, ///si le datagrid devra gérer les queryLimit
2005
+ /**** les menus customisés à ajouter au composant Datagrid */
2006
+ customMenu : PropTypes.oneOfType([
2007
+ PropTypes.arrayOf(PropTypes.object),
2008
+ PropTypes.objectOf(PropTypes.object),
2009
+ ]),
2010
+ /**** la fonction permettant de faire le rendu dun contenu paginé, personalisé */
2011
+ renderCustomPagination : PropTypes.func,
1953
2012
  getActionsArgs : PropTypes.func,//fonction permettant de récupérer les props supplémentaires à passer aux actions du datagrid
1954
2013
  }
1955
2014
 
@@ -60,7 +60,7 @@ export default class CommonTableDatagrid extends CommonDatagrid{
60
60
  la table dans la base common.
61
61
  Elle pourra éventuellement passer directement la limite et les filtres à la fonction fetchdata
62
62
  */
63
- fetchData ({cb,callback,force,fetchOptions}){
63
+ fetchData ({cb,callback,force,fetchOptions,...rest}){
64
64
  if(!this._isMounted()) return Promise.resolve(this.state.data);
65
65
  if(this.isFetchingData) {
66
66
  if(!isPromise(this.fetchingPromiseData)){
@@ -82,7 +82,7 @@ export default class CommonTableDatagrid extends CommonDatagrid{
82
82
  fetchOptions.dataSources = this.currentDataSources;
83
83
  fetchOptions = extendObj(true,true,{},fetchOptions,{selector : fetchFilters});
84
84
  fetchOptions.dataSources = this.currentDataSources;
85
- fetchOptions.sort = this.state.sort;
85
+ fetchOptions.sort = this.getSort();
86
86
  let limit = this.getQueryLimit();
87
87
  if(limit > 0 && !this.isPivotDatagrid()){
88
88
  fetchOptions.limit = limit;
@@ -92,7 +92,7 @@ export default class CommonTableDatagrid extends CommonDatagrid{
92
92
  }
93
93
  }
94
94
  this.beforeFetchData(fetchOptions);
95
- if(typeof this.props.beforeFetchData =='function' && this.props.beforeFetchData({context:this,force,fetchOptions,options:fetchOptions}) === false){
95
+ if(typeof this.props.beforeFetchData =='function' && this.props.beforeFetchData({...rest,context:this,force,fetchOptions,options:fetchOptions}) === false){
96
96
  this.isFetchingData = false;
97
97
  return resolve(this.state.data);
98
98
  }
@@ -7,6 +7,8 @@
7
7
  */
8
8
  import Datagrid from "./IndexComponent";
9
9
  import {defaultStr,defaultObj,defaultVal,isObjOrArray,isObj,extendObj} from "$utils";
10
+ import {Pressable} from "react-native";
11
+ import SimpleSelect from "$ecomponents/SimpleSelect";
10
12
  import React from "$react";
11
13
  import Auth from "$cauth";
12
14
  import DateLib from "$lib/date";
@@ -22,6 +24,9 @@ import appConfig from "$capp/config";
22
24
  import APP from "$capp/instance";
23
25
  import cAction from "$cactions";
24
26
  import PropTypes from "prop-types";
27
+ import {isDesktopMedia} from "$dimensions";
28
+ import ActivityIndicator from "$ecomponents/ActivityIndicator";
29
+ import {Menu} from "$ecomponents/BottomSheet";
25
30
 
26
31
  const timeout = 5000*60*60;
27
32
  export const swrOptions = {
@@ -31,7 +36,9 @@ export const swrOptions = {
31
36
  errorRetryInterval : timeout*2,
32
37
  errorRetryCount : 5,
33
38
  }
34
-
39
+ const getDefaultPaginationRowsPerPageItems = ()=>{
40
+ return [5,10,15,20,25,30,40,50,60,80,100,500,1000,...(isDesktopMedia() ? [1500,2000,2500,3000,3500,4000,4500,5000,10000]:[])];
41
+ }
35
42
  /****la fonction fetcher doit toujours retourner :
36
43
  * 1. la liste des éléments fetchés dans la props data
37
44
  * 2. le nombre total d'éléments de la liste obtenue en escluant les clause limit et offset correspondant à la même requête
@@ -53,10 +60,14 @@ const SWRDatagridComponent = React.forwardRef((props,ref)=>{
53
60
  fetchData,
54
61
  fetchPath,
55
62
  fetcher,
63
+ ListFooterComponent,
64
+ testID,
65
+ autoSort,
56
66
  ...rest
57
67
  } = props;
58
68
  rest = defaultObj(rest);
59
69
  rest.exportTableProps = defaultObj(rest.exportTableProps)
70
+ const firstPage = 1;
60
71
  const tableName = defaultStr(table.tableName,table.table).trim().toUpperCase();
61
72
  canMakePhoneCall = defaultBool(canMakePhoneCall,table.canMakePhoneCall);
62
73
  makePhoneCallProps = defaultObj(makePhoneCallProps,rest.makePhoneCallProps,table.makePhoneCallProps);
@@ -98,13 +109,33 @@ const SWRDatagridComponent = React.forwardRef((props,ref)=>{
98
109
  const hasResultRef = React.useRef(false);
99
110
  const totalRef = React.useRef(0);
100
111
  const isFetchingRef = React.useRef(false);
112
+ const pageRef = React.useRef(1);
113
+ const limitRef = React.useRef(500);
114
+ const isInitializedRef = React.useRef(false);
115
+ testID = defaultStr(testID,"RNSWRDatagridComponent")
101
116
  const {error, isValidating,isLoading,refresh} = useSWR(fetchPath,{
102
117
  fetcher : (url,opts)=>{
118
+ if(!isInitializedRef.current) {
119
+ isFetchingRef.current = false;
120
+ return;
121
+ }
103
122
  opts = extendObj({},opts,fetchOptionsRef.current);
104
123
  opts.queryParams = defaultObj(opts.queryParams);
105
124
  opts.queryParams.withTotal = true;
125
+ opts.queryParams.limit = limitRef.current;
126
+ opts.queryParams.page = pageRef.current -1;
127
+ if(isObj(opts.sort)){
128
+ opts.queryParams.sort = opts.sort;
129
+ }
106
130
  const fetchCB = ({data,total})=>{
107
131
  totalRef.current = total;
132
+ /***
133
+ * if(pageRef.current ===firstPage){
134
+ dataRef.current = data;
135
+ } else {
136
+ dataRef.current = prevPage != pageRef.current ? (isObj(data)?{...dataRef.current,...data}:[...dataRef.current,...data]) : data;
137
+ }
138
+ */
108
139
  dataRef.current = data;
109
140
  hasResultRef.current = true;
110
141
  return data;
@@ -146,6 +177,41 @@ const SWRDatagridComponent = React.forwardRef((props,ref)=>{
146
177
  };
147
178
  refresh();
148
179
  }
180
+ const canPagninate = ()=>{
181
+ if(typeof totalRef.current !=='number' || typeof pageRef.current !='number' || typeof limitRef.current !='number') return false;
182
+ if(limitRef.current <= 0) return false;
183
+ return true;
184
+ }
185
+ const getTotalPages = ()=>{
186
+ if(!canPagninate()) return false;
187
+ return Math.ceil(totalRef.current / limitRef.current);;
188
+ };
189
+ const getNextPage = ()=>{
190
+ if(!canPagninate()) return false;
191
+ const totalPages = getTotalPages();
192
+ let nPage = pageRef.current+1;
193
+ if(nPage > totalPages){
194
+ nPage = totalPages;
195
+ }
196
+ if(nPage === pageRef.current){
197
+ return false;
198
+ }
199
+ return nPage;
200
+ },getPrevPage = ()=>{
201
+ if(!canPagninate()) return false;
202
+ let pPage = pageRef.current - 1;
203
+ if(pPage < firstPage){
204
+ pPage = firstPage;
205
+ }
206
+ if(pPage === pageRef.current){
207
+ return false;
208
+ }
209
+ return pPage;
210
+ }, canSortRemotely = ()=>{
211
+ if(!canPagninate() || autoSort === true) return false;
212
+ ///si le nombre total d'élements est inférieur au nombre limite alors le trie peut être fait localement
213
+ return totalRef.current > limitRef.current && true || false;
214
+ }
149
215
  React.useEffect(()=>{
150
216
  const upsert = cAction.upsert(tableName);
151
217
  const remove = cAction.remove(tableName);
@@ -159,15 +225,146 @@ const SWRDatagridComponent = React.forwardRef((props,ref)=>{
159
225
  APP.off(remove,onUpsert);
160
226
  }
161
227
  },[]);
228
+ const loading = (isLoading|| isValidating);
229
+ const pointerEvents = loading ?"node" : "auto";
162
230
  return (
163
231
  <Datagrid
232
+ testID = {testID}
164
233
  {...rest}
165
234
  {...defaultObj(table.datagrid)}
166
- isLoading = {(isLoading|| isValidating) && !error && showProgressRef.current && true || false}
235
+ onSort = {({sort})=>{
236
+ if(!canSortRemotely()) return;
237
+ fetchOptionsRef.current.sort = sort;
238
+ pageRef.current = firstPage;
239
+ doRefresh(true);
240
+ return false;
241
+ }}
242
+ renderCustomPagination = {({context})=>{
243
+ if(!canPagninate()) return null;
244
+ const page = pageRef.current, totalPages = getTotalPages(), prevPage = getPrevPage(),nextPage = getNextPage();
245
+ const iconProp = {
246
+ size : 25,
247
+ style : [theme.styles.noMargin,theme.styles.noPadding],
248
+ }
249
+ const sStyle = [styles.limitStyle1,theme.styles.noPadding,theme.styles.noMargin];
250
+ return <View testID={testID+"_PaginationContainer"} pointerEvents={pointerEvents}>
251
+ <View style={[theme.styles.row,theme.styles.w100]} pointerEvents={pointerEvents} testID={testID+"_PaginationContentContainer"}>
252
+ <Menu
253
+ testID={testID+"_SimpleSelect"}
254
+ style = {sStyle}
255
+ anchor = {(p)=>{
256
+ return <Pressable style={[theme.styles.noMargin,theme.styles.noPadding]} {...p} testID={testID+"MenuSelectLimit"}>
257
+ <Label primary testID={testID+"_Label"} style={[{fontSize:15}]}>
258
+ {limitRef.current.formatNumber()}
259
+ </Label>
260
+ </Pressable>
261
+ }}
262
+ title = {'Limite du nombre d\'éléments par page'}
263
+ items = {getDefaultPaginationRowsPerPageItems().map((item)=>{
264
+ return {
265
+ text : item.formatNumber(),
266
+ icon : limitRef.current == item ? 'check' : null,
267
+ primary : limitRef.current === item ? true : false,
268
+ onPress : ()=>{
269
+ if(item == limitRef.current) return;
270
+ limitRef.current = item;
271
+ pageRef.current = firstPage;
272
+ setTimeout(() => {
273
+ doRefresh(true);
274
+ }, (500));
275
+ }
276
+ }
277
+ })}
278
+ />
279
+ <Icon
280
+ ///firstPage
281
+ {...iconProp}
282
+ title = {"Aller à la première page"}
283
+ name="material-first-page"
284
+ disabled = {pageRef.current <= 1 && true || false}
285
+ onPress = {()=>{
286
+ if(pageRef.current <= firstPage) return;
287
+ pageRef.current = firstPage;
288
+ doRefresh(true);
289
+ }}
290
+ />
291
+ <Icon
292
+ //decrement
293
+ {...iconProp}
294
+ title = {"Aller à la page précédente {0}".sprintf(prevPage && prevPage.formatNumber()||undefined)}
295
+ name="material-keyboard-arrow-left"
296
+ disabled = {page === prevPage || getPrevPage() === false ? true : false}
297
+ onPress = {()=>{
298
+ const page = getPrevPage();
299
+ if(page === false) return;
300
+ if(pageRef.current === page) return;
301
+ pageRef.current = page;
302
+ doRefresh(true);
303
+ }}
304
+
305
+ />
306
+ <View testID={testID+"_PaginationLabel"}>
307
+ <Label style={{fontSize:15}}>
308
+ {page.formatNumber()}-{totalPages.formatNumber()}{" / "}{totalRef.current.formatNumber()}
309
+ </Label>
310
+ </View>
311
+ <Icon
312
+ //increment
313
+ {...iconProp}
314
+ title = {"Aller à la page suivante {0}".sprintf(nextPage && nextPage.formatNumber()||undefined)}
315
+ name="material-keyboard-arrow-right"
316
+ disabled = {nextPage >= totalPages || getNextPage() === false ? true : false}
317
+ onPress = {()=>{
318
+ const page = getNextPage();
319
+ if(page === false) return;
320
+ if(pageRef.current === page) return;
321
+ pageRef.current = page;
322
+ doRefresh(true);
323
+ }}
324
+ />
325
+ <Icon
326
+ //lastPage
327
+ {...iconProp}
328
+ name="material-last-page"
329
+ title = {"Aller à la dernière page {0}".sprintf(totalPages && totalPages.formatNumber()||undefined)}
330
+ disabled = {page >= totalPages ? true : false}
331
+ onPress = {()=>{
332
+ const page = getTotalPages();
333
+ if(pageRef.current >= page) return;
334
+ pageRef.current = page;
335
+ doRefresh(true);
336
+ }}
337
+
338
+ />
339
+ </View>
340
+ </View>
341
+ }}
342
+ ListFooterComponent = {(props)=>{
343
+ const r = typeof ListFooterComponent =='function'? ListFooterComponent(props) : null;
344
+ if(!loading) return r;
345
+ const aContent = <View testID={testID+"_ListHeaderActivityIndicator"} style={[theme.styles.w100,theme.styles.justifyContentCenter]}>
346
+ <ActivityIndicator color={theme.colors.primary}/>
347
+ </View>;
348
+ if(r){
349
+ return <View testID={testID+"_ListHeaderContainer"} style={[theme.styles.w100]}>
350
+ {r}
351
+ {aContent}
352
+ </View>
353
+ }
354
+ return aContent;
355
+ }}
356
+ handleQueryLimit = {false}
357
+ handlePagination = {false}
358
+ autoSort = {canSortRemotely()? false : true}
359
+ isLoading = {loading && !error && showProgressRef.current && true || false}
167
360
  beforeFetchData = {({fetchOptions:opts,force})=>{
168
361
  opts.fields = fetchFields;
169
362
  opts = getFetchOptions({showError:showProgressRef.current,...opts});
363
+ isInitializedRef.current = true;
170
364
  fetchOptionsRef.current = opts;
365
+ if(force){
366
+ pageRef.current = firstPage;
367
+ }
171
368
  doRefresh(force);
172
369
  return false;
173
370
  }}
@@ -218,6 +415,11 @@ const styles = StyleSheet.create({
218
415
  labelTitle: {
219
416
  fontSize : 18,
220
417
  },
418
+ limitStyle : {
419
+ backgroundColor:'transparent',
420
+ width:50,
421
+ height : 35,
422
+ },
221
423
  emptyText : {
222
424
  fontSize : 16,
223
425
  fontWeight : 'bold',
@@ -111,7 +111,10 @@ const DatagridFactory = (Factory)=>{
111
111
  }
112
112
  }
113
113
  render(){
114
- let {title,testID,actions,selectableMultiple,sortable,exportable,
114
+ let {title,testID,actions,selectableMultiple,
115
+ sortable,
116
+ autoSort,
117
+ exportable,
115
118
  selectable,pagin,showPagination,
116
119
  sessionName,onMount,onUnmount,onFetchData,dataSourceSelector,dataSourceSelectorProps,queryLimit,
117
120
  filters,
@@ -138,7 +141,6 @@ const DatagridFactory = (Factory)=>{
138
141
  _dataSourceSelector = null;
139
142
  }
140
143
  exportable = defaultBool(exportable,true);
141
- sortable = defaultVal(sortable,true);
142
144
  let isMobile = isMobileOrTabletMedia();
143
145
  selectable = defaultVal(selectable,true);
144
146
  selectableMultiple = defaultBool(selectableMultiple,true);
@@ -170,20 +172,21 @@ const DatagridFactory = (Factory)=>{
170
172
  if(selectableMultiple && max && defaultBool(this.props.selectableMultiple,true)){
171
173
  max = max.formatNumber();
172
174
  restItems = [
173
- {
174
- text : "Sélect "+max,
175
+ ...this.renderCustomMenu(),
176
+ ...(selectableMultiple ? [{
177
+ label : "Sélect "+max,
175
178
  icon : "select-all",
176
179
  onPress : (x,event)=>{
177
180
  this.handleAllRowsToggle(true);
178
181
  }
179
182
  },
180
183
  {
181
- text : "Tout désélec",
184
+ label : "Tout désélec",
182
185
  onPress : (x,event)=>{
183
186
  this.handleAllRowsToggle(false);
184
187
  },
185
188
  icon : "select"
186
- }
189
+ }] : [])
187
190
  ]
188
191
  }
189
192
  const rPagination = showPagination ? <View style={[styles.paginationContainer]}>
@@ -192,6 +195,7 @@ const DatagridFactory = (Factory)=>{
192
195
  <View testID={testID+"_HeaderQueryLimit"}>
193
196
  {this.renderQueryLimit(this.state.data.length.formatNumber())}
194
197
  </View>
198
+ {this.renderCustomPagination()}
195
199
  {!isMobile && <>
196
200
  <Button normal style={[styles.paginationItem]} icon = {"refresh"} onPress = {this.refresh.bind(this)}>
197
201
  Rafraichir
@@ -214,19 +218,15 @@ const DatagridFactory = (Factory)=>{
214
218
  >
215
219
  {showFooters?'Masquer/Ligne des totaux':'Afficher/Ligne des totaux'}
216
220
  </Button>:null}
217
- {selectableMultiple && (<>
218
- {restItems.map((item,index)=>{
219
- return <Button
220
- normal
221
- style={styles.paginationItem}
222
- key = {index}
223
- icon = {item.icon}
224
- onPress = {item.onPress}
225
- >
226
- {item.text}
227
- </Button>
228
- })}
229
- </>)}
221
+ {restItems.map((item,index)=>{
222
+ return <Button
223
+ normal
224
+ key = {index}
225
+ {...item}
226
+ style={[styles.paginationItem,item.style]}
227
+ children = {item.children|| item.label}
228
+ />
229
+ })}
230
230
  </>}
231
231
  {exportable && (
232
232
  <>{/**
@@ -320,6 +320,7 @@ const DatagridFactory = (Factory)=>{
320
320
  </View>
321
321
  <Table
322
322
  ref = {this.listRef}
323
+ {...rest}
323
324
  hasFooters = {hasFooterFields}
324
325
  showFilters = {showFilters}
325
326
  showFooters = {showFooters}
@@ -11,34 +11,55 @@ import {defaultStr,isPromise,defaultObj} from "$utils";
11
11
  import {open as openPreloader,close as closePreloader} from "$preloader";
12
12
  import {styles as _styles} from "$theme";
13
13
  import Tooltip from "$ecomponents/Tooltip";
14
+ import {navigateToTableData} from "$enavigation/utils";
15
+ import Auth from "$cauth";
16
+ import fetch from "$capi/fetch";
14
17
 
15
18
  const TableLinKComponent = React.forwardRef((props,ref)=>{
16
- let {disabled,labelProps,server,containerProps,id,columnDef,tableName,table:customTable,data,testID,Component,routeName,routeParams,component,primary,triggerProps,onPress,children, ...rest} = props;
19
+ let {disabled,readOnly,labelProps,server,containerProps,perm,id,fetchForeignData,foreignKeyTable,foreignKeyColumn,data,testID,Component,routeName,routeParams,component,primary,triggerProps,onPress,children, ...rest} = props;
17
20
  testID = defaultStr(testID,"RN_TableDataLinkContainer")
18
- tableName = defaultStr(tableName,customTable).trim();
21
+ foreignKeyTable = defaultStr(foreignKeyTable).trim();
22
+ foreignKeyColumn = defaultStr(foreignKeyColumn).trim();
19
23
  rest = defaultObj(rest);
20
24
  containerProps = defaultObj(containerProps)
21
25
  labelProps = defaultObj(labelProps);
22
- columnDef = defaultObj(columnDef);
23
26
  data = defaultObj(data);
24
27
  id = defaultStr(id);
25
- if(!id){
26
- disabled = true;
28
+ if(!id || !foreignKeyTable){
29
+ readOnly = true;
27
30
  }
28
- const pointerEvents = disabled || !id? 'none' : 'auto';
31
+ const pointerEvents = disabled || readOnly || !id? 'none' : 'auto';
29
32
  const onPressLink = (event)=>{
30
33
  React.stopEventPropagation(event);
31
- const r = typeof onPress =='function'? onPress({...React.getOnPressArgs(event),...columnDef,tableName,table:tableName,data,id,value:id,}) : undefined;
34
+ if((isNonNullString(perm) && !Auth.isAllowedFromString(perm)) || !Auth.isTableDataAllowed({table:foreignKeyTable,action:'read'})){
35
+ return;
36
+ }
37
+ const args = {...React.getOnPressArgs(event),...rest,fetch,foreignKeyTable,foreignKeyColumn,data,id,value:id};
38
+ let r = typeof onPress =='function'? onPress(args) : undefined;
39
+ if(r === false) return;
40
+ const cb = (a)=>{
41
+ const r2 = typeof fetchForeignData === 'function'? fetchForeignData({...args,...defaultObj(a)}) : undefined;
42
+ if(isPromise(r2)){
43
+ return r2.then((data)=>{
44
+ if(isObj(data) && data[foreignKeyColumn] !== undefined){
45
+ navigateToTableData({tableName:foreignKeyTable,data});
46
+ }
47
+ });
48
+ }
49
+ return Promise.reject({msg:'type de données retournée par la fonction fetchForeignKeyData invalide'});
50
+ }
51
+ openPreloader("traitement de la requête...");
32
52
  if(isPromise(r)){
33
- openPreloader("traitement de la requête...");
34
- r.finally(closePreloader);
53
+ r.then(cb).finally(closePreloader);
54
+ } else {
55
+ cb().finally(closePreloader);
35
56
  }
36
57
  }
37
58
  rest.style = [rest.style,_styles.cursorPointer];
38
- const CP = disabled ? View : TouchableOpacity;
59
+ const CP = disabled || readOnly ? View : TouchableOpacity;
39
60
  return <CP testID={testID} onLongPres={(e)=>React.stopEventPropagation(e)} {...containerProps} onPress={disabled? undefined : onPressLink} style={[styles.container,containerProps.style]}>
40
- <Tooltip testID={testID+"_Tooltip"} {...rest} Component={Component} onPress={disabled?undefined:onPressLink} ref={ref} pointerEvents={pointerEvents} disabled = {disabled}>
41
- <Label testID={testID+"_Label"} underlined primary {...labelProps} style={[_styles.lh15,labelProps.style]} disabled={disabled}>{children}</Label>
61
+ <Tooltip testID={testID+"_Tooltip"} {...rest} Component={Component} onPress={disabled || readOnly?undefined:onPressLink} ref={ref} pointerEvents={pointerEvents} readOnly={readOnly} disabled = {disabled}>
62
+ <Label testID={testID+"_Label"} underlined primary {...labelProps} style={[_styles.lh15,labelProps.style]} disabled={disabled} readOnly={readOnly}>{children}</Label>
42
63
  </Tooltip>
43
64
  </CP>
44
65
  });
@@ -47,6 +68,9 @@ export default TableLinKComponent;
47
68
 
48
69
 
49
70
  TableLinKComponent.propTypes = {
71
+ foreignKeyColumn : PropTypes.string.isRequired,//le nom de la colonne de la clé étrangère
72
+ foreignKeyTable : PropTypes.string.isRequired, //le nom de la table référencée
73
+ fetchForeignData : PropTypes.func, // la fonction permettant de chercher la données à distance
50
74
  server : PropTypes.string,//le serveur sur lequel rechercher les données
51
75
  primary : PropTypes.bool,//si le composant sera stylé en theme primary de react
52
76
  ///les props à utiliser pour afficher la table de données en cas de click sur le lien
@@ -13,10 +13,8 @@ import cActions from "$cactions";
13
13
  import {View} from "react-native";
14
14
 
15
15
  export default function DatabaseStatisticContainer (props){
16
- const [state,setState] = React.useState({
17
- isLoading : true,
18
- count : 0,
19
- });
16
+ const [count,setCount] = React.useState(0);
17
+ const [isLoading,setIsLoading] = React.useState(true);
20
18
  let {table,fetchCount,index,testID,title,icon,onPress} = props;
21
19
  title = defaultStr(title)
22
20
  table = defaultObj(table);
@@ -28,14 +26,14 @@ export default function DatabaseStatisticContainer (props){
28
26
  const refresh = ()=>{
29
27
  if(refreshingRef.current || !isMounted()) return;
30
28
  refreshingRef.current = true;
29
+ setIsLoading(true);
31
30
  setTimeout(()=>{
32
31
  fetchCount().then((count)=>{
33
- setState({...state,isLoading:false,count});
32
+ setCount(count);
33
+ setIsLoading(false);
34
34
  refreshingRef.current = false;
35
35
  }).catch((e)=>{
36
- setState({
37
- isLoading : false, count : 0,
38
- });
36
+ setIsLoading(false);
39
37
  refreshingRef.current = false;
40
38
  });
41
39
  },100);
@@ -50,10 +48,6 @@ export default function DatabaseStatisticContainer (props){
50
48
  APP.off(cActions.remove(tableName),refresh);
51
49
  }
52
50
  },[]);
53
- React.useEffect(()=>{
54
- //refresh();
55
- },[props])
56
- const {isLoading,count} = state;
57
51
  return <Item
58
52
  testID = {defaultStr(testID,"RN_DatabaseStatistic_"+table)}
59
53
  onPress = {(args)=>{
@@ -65,7 +59,6 @@ export default function DatabaseStatisticContainer (props){
65
59
  left = {(aProps)=>{
66
60
  return <Avatar suffix={index} {...aProps} icon= {icon} size={40} label={title}/>
67
61
  }}
68
- //right = {(rP)=><Icon {...rP} name='refresh' onPress={refresh}/>}
69
62
  title = {<Label splitText numberOfLines={1} primary style={[{fontSize:15}]}>{title}</Label>}
70
63
  titleProps = {{primary : true}}
71
64
  description = {isLoading?<View style={[theme.styles.justifyContentFlexStart,theme.styles.alignItemsFlexStart]}>
@@ -29,6 +29,43 @@ const DEFAULT_TABS_KEYS = "main-tabs";
29
29
 
30
30
  const TIMEOUT = 50;
31
31
 
32
+ const checkPrimary = (data,f)=>{
33
+ return !(!(f in data) || (data[f] == null) || (!data[f] && typeof data !=='number'));
34
+ }
35
+ /*** vérifie si le document passé en paramètre est éditable
36
+ * @param {object} data la données à vérifier
37
+ * @param {object| array} les champs sur lesquels se baser pour vérifier si la donénes est une mise à jour
38
+ * @param {func} checkPrimaryKey la foncition permettant de vérifier s'il s'agit d'une clé primaire pour la données courante
39
+ */
40
+ export const isDocEditing = (data,fields,checkPrimaryKey)=>{
41
+ if(!isObj(data) || !isObjOrArray(fields)) return false;
42
+
43
+ let hasPrimaryFields = false;
44
+ let hasValidated = true;
45
+ for(let i in fields){
46
+ const field = fields[i];
47
+ if(typeof checkPrimaryKey =='function') {
48
+ hasPrimaryFields = true;
49
+ if(checkPrimaryKey({field,i,index:i,data}) === false){
50
+ return false;
51
+ }
52
+ continue;
53
+ }
54
+ if(!isObj(field)) continue;
55
+ hasPrimaryFields = true;
56
+ const f = defaultStr(field.field,i);
57
+ if(field.primaryKey === true){
58
+ if(!checkPrimary(data,f)){
59
+ hasValidated = false;
60
+ }
61
+ }
62
+ }
63
+ if(hasPrimaryFields){
64
+ return hasValidated;
65
+ }
66
+ return false;
67
+ }
68
+
32
69
  export default class TableDataScreenComponent extends FormDataScreen{
33
70
  constructor(props){
34
71
  super(props);
@@ -49,7 +86,7 @@ export default class TableDataScreenComponent extends FormDataScreen{
49
86
  Object.map(table.fields,(field,i)=>{
50
87
  if(isObj(field) && field.form !== false){
51
88
  fields[i] = Object.clone(field);
52
- if(field.primary === true){
89
+ if(field.primaryKey === true){
53
90
  primaryKeyFields[field.field || i] = true;
54
91
  }
55
92
  } else {
@@ -420,17 +457,9 @@ export default class TableDataScreenComponent extends FormDataScreen{
420
457
  isDocEditing(data){
421
458
  data = defaultObj(data);
422
459
  if(!this.isDocEditingProp){
423
- let hasPrimaryFields = false;
424
- let hasValidated = true;
425
- Object.map(this.primaryKeyFields,(v,f)=>{
426
- hasPrimaryFields = true;
427
- if(!(f in data) || (data[f] == null) || (!data[f] && typeof data !=='number')){
428
- hasValidated = false;
429
- }
430
- });
431
- if(hasPrimaryFields){
432
- return hasValidated;
433
- }
460
+ if(isDocEditing(data,this.primaryKeyFields,({index:field,data})=>{
461
+ return checkPrimary(data,field);
462
+ })) return true;
434
463
  }
435
464
  return super.isDocEditing(data);
436
465
  }
@@ -448,7 +477,7 @@ export default class TableDataScreenComponent extends FormDataScreen{
448
477
  if(this.cloneProp && this.cloneProp(data,this) === false) return data;
449
478
  this.showPreloader();
450
479
  delete data.approved;
451
- Object.map(['_rev',...generatedColumnsProperties,'_id','code','updateBy','updatedDate','createBy','updatedHour','createdHour','createdDate'],(idx)=>{
480
+ Object.map(['_rev',...generatedColumnsProperties,...Object.keys(this.primaryKeyFields),'_id','code','updateBy','updatedDate','createBy','updatedHour','createdHour','createdDate'],(idx)=>{
452
481
  data[idx] = undefined;
453
482
  delete data[idx];
454
483
  });