selectic 3.1.0 → 3.1.2

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.
@@ -138,12 +138,25 @@ function compareOptions(oldOptions, newOptions) {
138
138
  });
139
139
  });
140
140
  }
141
+ let displayLog = false;
142
+ function debug(fName, step, ...args) {
143
+ if (!displayLog) {
144
+ return;
145
+ }
146
+ console.log('--%s-- [%s]', fName, step, ...args);
147
+ }
148
+ /** Enable logs for debugging */
149
+ debug.enable = (display) => {
150
+ displayLog = display;
151
+ };
141
152
 
142
153
  /* File Purpose:
143
154
  * It keeps and computes all states at a single place.
144
155
  * Every inner components of Selectic should communicate with this file to
145
156
  * change or to get states.
146
157
  */
158
+ /* For debugging */
159
+ debug.enable(false);
147
160
  /* }}} */
148
161
  /* {{{ Static */
149
162
  function changeTexts$1(texts) {
@@ -178,12 +191,19 @@ let messages = {
178
191
  let defaultFamilyIcon = 'selectic';
179
192
  let icons = {};
180
193
  let closePreviousSelectic;
194
+ /**
195
+ * Time to wait before considering there is no other requests.
196
+ * This time is await only if there is already a requested request.
197
+ */
198
+ const DEBOUNCE_REQUEST = 250;
181
199
  /* }}} */
182
200
  let uid = 0;
183
201
  class SelecticStore {
184
202
  constructor(props = {}) {
185
203
  /* Do not need reactivity */
186
204
  this.requestId = 0;
205
+ this.requestSearchId = 0; /* Used for search request */
206
+ this.isRequesting = false;
187
207
  this._uid = ++uid;
188
208
  /* {{{ Props */
189
209
  const defaultProps = {
@@ -299,6 +319,7 @@ class SelecticStore {
299
319
  this.data.cacheItem.clear();
300
320
  this.setAutomaticClose();
301
321
  this.commit('isOpen', false);
322
+ this.clearDisplay();
302
323
  this.buildAllOptions(true);
303
324
  this.buildSelectedOptions();
304
325
  }, { deep: true });
@@ -389,6 +410,7 @@ class SelecticStore {
389
410
  /* {{{ public methods */
390
411
  commit(name, value) {
391
412
  const oldValue = this.state[name];
413
+ debug('commit', 'start', name, value, 'oldValue:', oldValue);
392
414
  if (oldValue === value) {
393
415
  return;
394
416
  }
@@ -397,8 +419,7 @@ class SelecticStore {
397
419
  case 'searchText':
398
420
  this.state.offsetItem = 0;
399
421
  this.state.activeItemIdx = -1;
400
- this.state.filteredOptions = [];
401
- this.state.totalFilteredOptions = Infinity;
422
+ this.clearDisplay();
402
423
  if (value) {
403
424
  this.buildFilteredOptions();
404
425
  }
@@ -446,6 +467,7 @@ class SelecticStore {
446
467
  }
447
468
  break;
448
469
  }
470
+ debug('commit', '(done)', name);
449
471
  }
450
472
  setAutomaticChange() {
451
473
  this.state.status.automaticChange = true;
@@ -631,14 +653,14 @@ class SelecticStore {
631
653
  this.state.status.errorMessage = '';
632
654
  }
633
655
  clearCache(forceReset = false) {
656
+ debug('clearCache', 'start', forceReset);
634
657
  const isPartial = vue.unref(this.isPartial);
635
658
  const total = isPartial ? Infinity : 0;
636
659
  this.data.cacheItem.clear();
637
660
  this.state.allOptions = [];
638
661
  this.state.totalAllOptions = total;
639
662
  this.state.totalDynOptions = total;
640
- this.state.filteredOptions = [];
641
- this.state.totalFilteredOptions = Infinity;
663
+ this.clearDisplay();
642
664
  this.state.status.errorMessage = '';
643
665
  this.state.status.hasChanged = false;
644
666
  if (forceReset) {
@@ -770,6 +792,13 @@ class SelecticStore {
770
792
  this.checkAutoSelect();
771
793
  }
772
794
  }
795
+ /** Reset the display cache in order to rebuild it */
796
+ clearDisplay() {
797
+ debug('clearDisplay', 'start');
798
+ this.state.filteredOptions = [];
799
+ this.state.totalFilteredOptions = Infinity;
800
+ }
801
+ /** rebuild the state filteredOptions to normalize their values */
773
802
  updateFilteredOptions() {
774
803
  if (!this.data.doNotUpdate) {
775
804
  this.state.filteredOptions = this.buildItems(this.state.filteredOptions);
@@ -819,7 +848,7 @@ class SelecticStore {
819
848
  });
820
849
  return listOptions;
821
850
  }
822
- /* This method is for the computed property elementOptions */
851
+ /** This method is for the computed property elementOptions */
823
852
  getElementOptions() {
824
853
  const options = deepClone(this.props.childOptions, ['data']);
825
854
  const childOptions = [];
@@ -850,7 +879,9 @@ class SelecticStore {
850
879
  });
851
880
  return childOptions;
852
881
  }
882
+ /** Generate the list of all options by combining the 3 option lists */
853
883
  buildAllOptions(keepFetched = false, stopFetch = false) {
884
+ debug('buildAllOptions', 'start', 'keepFetched', keepFetched, 'stopFetch', stopFetch);
854
885
  const allOptions = [];
855
886
  let listOptions = [];
856
887
  let elementOptions = [];
@@ -922,8 +953,6 @@ class SelecticStore {
922
953
  }
923
954
  }
924
955
  if (!stopFetch) {
925
- this.state.filteredOptions = [];
926
- this.state.totalFilteredOptions = Infinity;
927
956
  this.buildFilteredOptions().then(() => {
928
957
  /* XXX: To recompute for strict mode and auto-select */
929
958
  this.assertCorrectValue();
@@ -939,18 +968,21 @@ class SelecticStore {
939
968
  const options = this.filterOptions(allOptions, search);
940
969
  this.setFilteredOptions(options);
941
970
  }
971
+ debug('buildAllOptions', 'end', 'allOptions:', this.state.allOptions.length, 'totalAllOptions:', this.state.totalAllOptions);
942
972
  }
943
973
  async buildFilteredOptions() {
944
- if (!this.state.isOpen) {
974
+ const state = this.state;
975
+ if (!state.isOpen) {
945
976
  /* Do not try to fetch anything while the select is not open */
946
977
  return;
947
978
  }
948
- const allOptions = this.state.allOptions;
949
- const search = this.state.searchText;
950
- const totalAllOptions = this.state.totalAllOptions;
979
+ const allOptions = state.allOptions;
980
+ const search = state.searchText;
981
+ const totalAllOptions = state.totalAllOptions;
951
982
  const allOptionsLength = allOptions.length;
952
- let filteredOptionsLength = this.state.filteredOptions.length;
983
+ let filteredOptionsLength = state.filteredOptions.length;
953
984
  const hasAllItems = vue.unref(this.hasAllItems);
985
+ debug('buildFilteredOptions', 'start', 'hasAllItems:', hasAllItems, 'allOptions', allOptions.length, 'search:', search, 'filteredOptionsLength:', filteredOptionsLength);
954
986
  if (hasAllItems) {
955
987
  /* Everything has already been fetched and stored in filteredOptions */
956
988
  return;
@@ -967,16 +999,17 @@ class SelecticStore {
967
999
  return;
968
1000
  }
969
1001
  /* When we only have partial options */
970
- const offsetItem = this.state.offsetItem;
1002
+ const offsetItem = state.offsetItem;
971
1003
  const marginSize = vue.unref(this.marginSize);
972
1004
  const endIndex = offsetItem + marginSize;
1005
+ debug('buildFilteredOptions', 'partial options', 'offsetItem:', offsetItem, 'marginSize:', marginSize, 'filteredOptionsLength', filteredOptionsLength);
973
1006
  if (endIndex <= filteredOptionsLength) {
974
1007
  return;
975
1008
  }
976
1009
  if (!search && endIndex <= allOptionsLength) {
977
- this.setFilteredOptions(this.buildGroupItems(allOptions), false, totalAllOptions + this.state.groups.size);
1010
+ this.setFilteredOptions(this.buildGroupItems(allOptions), false, totalAllOptions + state.groups.size);
978
1011
  const isPartial = vue.unref(this.isPartial);
979
- if (isPartial && this.state.totalDynOptions === Infinity) {
1012
+ if (isPartial && state.totalDynOptions === Infinity) {
980
1013
  this.fetchData();
981
1014
  }
982
1015
  return;
@@ -989,6 +1022,7 @@ class SelecticStore {
989
1022
  return;
990
1023
  }
991
1024
  }
1025
+ debug('buildFilteredOptions', 'end', '(will call fetchData)', this.state.filteredOptions.length);
992
1026
  await this.fetchData();
993
1027
  }
994
1028
  async buildSelectedOptions() {
@@ -1042,6 +1076,39 @@ class SelecticStore {
1042
1076
  state.selectedOptions = items[0];
1043
1077
  }
1044
1078
  }
1079
+ async fetchRequest(fetchCallback, search, offset, limit) {
1080
+ const searchRqId = ++this.requestSearchId;
1081
+ if (!search) {
1082
+ ++this.requestId;
1083
+ }
1084
+ const requestId = this.requestId;
1085
+ debug('fetchRequest', 'start', 'search:', search, 'offset:', offset, 'limit:', limit, 'requestId:', requestId, 'requestSearchId:', searchRqId, 'isRequesting:', this.isRequesting);
1086
+ if (this.isRequesting) {
1087
+ debug('fetchRequest', `await ${DEBOUNCE_REQUEST}ms`);
1088
+ /* debounce the call to avoid sending too much requests */
1089
+ await new Promise((resolve) => {
1090
+ setTimeout(resolve, DEBOUNCE_REQUEST);
1091
+ });
1092
+ /* Check if there are other requested requests, in such case drop this one */
1093
+ if (requestId !== this.requestId || (search && searchRqId !== this.requestSearchId)) {
1094
+ debug('fetchRequest', '××deprecated××', requestId, searchRqId);
1095
+ return false;
1096
+ }
1097
+ }
1098
+ this.isRequesting = true;
1099
+ const response = await fetchCallback(search, offset, limit);
1100
+ /* Check if request is obsolete */
1101
+ if (requestId !== this.requestId || (search && searchRqId !== this.requestSearchId)) {
1102
+ debug('fetchRequest', '×××deprecated×××', requestId, searchRqId);
1103
+ return false;
1104
+ }
1105
+ this.isRequesting = false;
1106
+ const deprecated = searchRqId !== this.requestSearchId;
1107
+ debug('fetchRequest', 'end', response.result.length, response.total, deprecated);
1108
+ return Object.assign(Object.assign({}, response), {
1109
+ /* this is to fulfill the cache */
1110
+ deprecated: deprecated });
1111
+ }
1045
1112
  async fetchData() {
1046
1113
  const state = this.state;
1047
1114
  const labels = this.data.labels;
@@ -1063,9 +1130,14 @@ class SelecticStore {
1063
1130
  const offset = filteredOptionsLength - this.nbGroups(state.filteredOptions) - dynOffset;
1064
1131
  const nbItems = endIndex - offset;
1065
1132
  const limit = Math.ceil(nbItems / pageSize) * pageSize;
1133
+ debug('fetchData', 'start', 'search:', search, 'offset:', offset, 'limit:', limit);
1066
1134
  try {
1067
- const requestId = ++this.requestId;
1068
- const { total: rTotal, result } = await fetchCallback(search, offset, limit);
1135
+ const response = await this.fetchRequest(fetchCallback, search, offset, limit);
1136
+ if (!response) {
1137
+ debug('fetchData', '×× deprecated ××', search, offset, limit);
1138
+ return;
1139
+ }
1140
+ const { total: rTotal, result, deprecated } = response;
1069
1141
  let total = rTotal;
1070
1142
  let errorMessage = '';
1071
1143
  /* Assert result is correctly formatted */
@@ -1083,11 +1155,12 @@ class SelecticStore {
1083
1155
  if (!search) {
1084
1156
  /* update cache */
1085
1157
  state.totalDynOptions = total;
1086
- const old = state.dynOptions.splice(offset, limit, ...result);
1158
+ const old = state.dynOptions.splice(offset, result.length, ...result);
1087
1159
  if (compareOptions(old, result)) {
1088
1160
  /* Added options are the same as previous ones.
1089
1161
  * Stop fetching to avoid infinite loop
1090
1162
  */
1163
+ debug('fetchData', 'no new values');
1091
1164
  if (!vue.unref(this.hasFetchedAllItems)) {
1092
1165
  /* Display error if all items are not fetch
1093
1166
  * We can have the case where old value and result
@@ -1095,14 +1168,21 @@ class SelecticStore {
1095
1168
  * total is 0 */
1096
1169
  errorMessage = labels.wrongQueryResult;
1097
1170
  }
1098
- setTimeout(() => this.buildAllOptions(true, true), 0);
1171
+ setTimeout(() => {
1172
+ debug('fetchData', 'before buildAllOptions (stopped)', 'offsetItem:', this.state.offsetItem, 'allOptions:', this.state.allOptions.length);
1173
+ this.buildAllOptions(true, true);
1174
+ }, 0);
1099
1175
  }
1100
1176
  else {
1101
- setTimeout(() => this.buildAllOptions(true), 0);
1177
+ setTimeout(() => {
1178
+ debug('fetchData', 'before buildAllOptions', 'offsetItem:', this.state.offsetItem, 'allOptions:', this.state.allOptions.length);
1179
+ this.buildAllOptions(true);
1180
+ }, 0);
1102
1181
  }
1103
1182
  }
1104
- /* Check request is not obsolete */
1105
- if (requestId !== this.requestId) {
1183
+ /* Check request (without search) is not obsolete */
1184
+ if (deprecated) {
1185
+ debug('fetchData', '××× deprecated ×××', search, offset, limit);
1106
1186
  return;
1107
1187
  }
1108
1188
  if (!search) {
@@ -1131,14 +1211,17 @@ class SelecticStore {
1131
1211
  }
1132
1212
  catch (e) {
1133
1213
  state.status.errorMessage = e.message;
1214
+ debug('fetchData', 'error', e.message);
1134
1215
  if (!search) {
1135
1216
  state.totalDynOptions = 0;
1136
1217
  this.buildAllOptions(true, true);
1137
1218
  }
1138
1219
  }
1139
1220
  this.state.status.searching = false;
1221
+ debug('fetchData', 'end');
1140
1222
  }
1141
1223
  filterOptions(options, search) {
1224
+ debug('filterOptions', 'start', 'options:', options.length, 'search:', search);
1142
1225
  if (!search) {
1143
1226
  return this.buildGroupItems(options);
1144
1227
  }
@@ -1149,6 +1232,7 @@ class SelecticStore {
1149
1232
  addStaticFilteredOptions(fromDynamic = false) {
1150
1233
  const search = this.state.searchText;
1151
1234
  let continueLoop = fromDynamic;
1235
+ debug('addStaticFilteredOptions', 'start', 'fromDynamic:', fromDynamic, 'optionBehaviorOperation:', this.state.optionBehaviorOperation);
1152
1236
  if (this.state.optionBehaviorOperation !== 'sort') {
1153
1237
  return;
1154
1238
  }
@@ -1174,6 +1258,7 @@ class SelecticStore {
1174
1258
  }
1175
1259
  this.setFilteredOptions(options, true);
1176
1260
  }
1261
+ debug('addStaticFilteredOptions', 'end');
1177
1262
  }
1178
1263
  buildSelectedItems(ids) {
1179
1264
  return this.buildItems(ids.map((id) => {
@@ -1215,6 +1300,7 @@ class SelecticStore {
1215
1300
  }
1216
1301
  buildGroupItems(options, previousItem) {
1217
1302
  let previousGroupId = previousItem && previousItem.group;
1303
+ debug('buildGroupItems', 'start', 'options:', options.length, 'previousGroupId:', previousGroupId);
1218
1304
  const list = this.buildItems(options).reduce((items, item) => {
1219
1305
  if (item.group !== previousGroupId) {
1220
1306
  const groupId = item.group;
@@ -1231,6 +1317,7 @@ class SelecticStore {
1231
1317
  items.push(item);
1232
1318
  return items;
1233
1319
  }, []);
1320
+ debug('buildGroupItems', 'end', list.length);
1234
1321
  return list;
1235
1322
  }
1236
1323
  buildOptionBehavior(optionBehavior, state) {
@@ -1348,6 +1435,7 @@ class SelecticStore {
1348
1435
  }
1349
1436
  /** assign new value to the filteredOptions and apply change depending on it */
1350
1437
  setFilteredOptions(options, add = false, length = 0) {
1438
+ debug('setFilteredOptions', 'start', 'options:', options.length, 'add', add, 'length', length);
1351
1439
  if (!add) {
1352
1440
  this.state.filteredOptions = options;
1353
1441
  this.state.totalFilteredOptions = length || options.length;
@@ -134,12 +134,25 @@ function compareOptions(oldOptions, newOptions) {
134
134
  });
135
135
  });
136
136
  }
137
+ let displayLog = false;
138
+ function debug(fName, step, ...args) {
139
+ if (!displayLog) {
140
+ return;
141
+ }
142
+ console.log('--%s-- [%s]', fName, step, ...args);
143
+ }
144
+ /** Enable logs for debugging */
145
+ debug.enable = (display) => {
146
+ displayLog = display;
147
+ };
137
148
 
138
149
  /* File Purpose:
139
150
  * It keeps and computes all states at a single place.
140
151
  * Every inner components of Selectic should communicate with this file to
141
152
  * change or to get states.
142
153
  */
154
+ /* For debugging */
155
+ debug.enable(false);
143
156
  /* }}} */
144
157
  /* {{{ Static */
145
158
  function changeTexts$1(texts) {
@@ -174,12 +187,19 @@ let messages = {
174
187
  let defaultFamilyIcon = 'selectic';
175
188
  let icons = {};
176
189
  let closePreviousSelectic;
190
+ /**
191
+ * Time to wait before considering there is no other requests.
192
+ * This time is await only if there is already a requested request.
193
+ */
194
+ const DEBOUNCE_REQUEST = 250;
177
195
  /* }}} */
178
196
  let uid = 0;
179
197
  class SelecticStore {
180
198
  constructor(props = {}) {
181
199
  /* Do not need reactivity */
182
200
  this.requestId = 0;
201
+ this.requestSearchId = 0; /* Used for search request */
202
+ this.isRequesting = false;
183
203
  this._uid = ++uid;
184
204
  /* {{{ Props */
185
205
  const defaultProps = {
@@ -295,6 +315,7 @@ class SelecticStore {
295
315
  this.data.cacheItem.clear();
296
316
  this.setAutomaticClose();
297
317
  this.commit('isOpen', false);
318
+ this.clearDisplay();
298
319
  this.buildAllOptions(true);
299
320
  this.buildSelectedOptions();
300
321
  }, { deep: true });
@@ -385,6 +406,7 @@ class SelecticStore {
385
406
  /* {{{ public methods */
386
407
  commit(name, value) {
387
408
  const oldValue = this.state[name];
409
+ debug('commit', 'start', name, value, 'oldValue:', oldValue);
388
410
  if (oldValue === value) {
389
411
  return;
390
412
  }
@@ -393,8 +415,7 @@ class SelecticStore {
393
415
  case 'searchText':
394
416
  this.state.offsetItem = 0;
395
417
  this.state.activeItemIdx = -1;
396
- this.state.filteredOptions = [];
397
- this.state.totalFilteredOptions = Infinity;
418
+ this.clearDisplay();
398
419
  if (value) {
399
420
  this.buildFilteredOptions();
400
421
  }
@@ -442,6 +463,7 @@ class SelecticStore {
442
463
  }
443
464
  break;
444
465
  }
466
+ debug('commit', '(done)', name);
445
467
  }
446
468
  setAutomaticChange() {
447
469
  this.state.status.automaticChange = true;
@@ -627,14 +649,14 @@ class SelecticStore {
627
649
  this.state.status.errorMessage = '';
628
650
  }
629
651
  clearCache(forceReset = false) {
652
+ debug('clearCache', 'start', forceReset);
630
653
  const isPartial = unref(this.isPartial);
631
654
  const total = isPartial ? Infinity : 0;
632
655
  this.data.cacheItem.clear();
633
656
  this.state.allOptions = [];
634
657
  this.state.totalAllOptions = total;
635
658
  this.state.totalDynOptions = total;
636
- this.state.filteredOptions = [];
637
- this.state.totalFilteredOptions = Infinity;
659
+ this.clearDisplay();
638
660
  this.state.status.errorMessage = '';
639
661
  this.state.status.hasChanged = false;
640
662
  if (forceReset) {
@@ -766,6 +788,13 @@ class SelecticStore {
766
788
  this.checkAutoSelect();
767
789
  }
768
790
  }
791
+ /** Reset the display cache in order to rebuild it */
792
+ clearDisplay() {
793
+ debug('clearDisplay', 'start');
794
+ this.state.filteredOptions = [];
795
+ this.state.totalFilteredOptions = Infinity;
796
+ }
797
+ /** rebuild the state filteredOptions to normalize their values */
769
798
  updateFilteredOptions() {
770
799
  if (!this.data.doNotUpdate) {
771
800
  this.state.filteredOptions = this.buildItems(this.state.filteredOptions);
@@ -815,7 +844,7 @@ class SelecticStore {
815
844
  });
816
845
  return listOptions;
817
846
  }
818
- /* This method is for the computed property elementOptions */
847
+ /** This method is for the computed property elementOptions */
819
848
  getElementOptions() {
820
849
  const options = deepClone(this.props.childOptions, ['data']);
821
850
  const childOptions = [];
@@ -846,7 +875,9 @@ class SelecticStore {
846
875
  });
847
876
  return childOptions;
848
877
  }
878
+ /** Generate the list of all options by combining the 3 option lists */
849
879
  buildAllOptions(keepFetched = false, stopFetch = false) {
880
+ debug('buildAllOptions', 'start', 'keepFetched', keepFetched, 'stopFetch', stopFetch);
850
881
  const allOptions = [];
851
882
  let listOptions = [];
852
883
  let elementOptions = [];
@@ -918,8 +949,6 @@ class SelecticStore {
918
949
  }
919
950
  }
920
951
  if (!stopFetch) {
921
- this.state.filteredOptions = [];
922
- this.state.totalFilteredOptions = Infinity;
923
952
  this.buildFilteredOptions().then(() => {
924
953
  /* XXX: To recompute for strict mode and auto-select */
925
954
  this.assertCorrectValue();
@@ -935,18 +964,21 @@ class SelecticStore {
935
964
  const options = this.filterOptions(allOptions, search);
936
965
  this.setFilteredOptions(options);
937
966
  }
967
+ debug('buildAllOptions', 'end', 'allOptions:', this.state.allOptions.length, 'totalAllOptions:', this.state.totalAllOptions);
938
968
  }
939
969
  async buildFilteredOptions() {
940
- if (!this.state.isOpen) {
970
+ const state = this.state;
971
+ if (!state.isOpen) {
941
972
  /* Do not try to fetch anything while the select is not open */
942
973
  return;
943
974
  }
944
- const allOptions = this.state.allOptions;
945
- const search = this.state.searchText;
946
- const totalAllOptions = this.state.totalAllOptions;
975
+ const allOptions = state.allOptions;
976
+ const search = state.searchText;
977
+ const totalAllOptions = state.totalAllOptions;
947
978
  const allOptionsLength = allOptions.length;
948
- let filteredOptionsLength = this.state.filteredOptions.length;
979
+ let filteredOptionsLength = state.filteredOptions.length;
949
980
  const hasAllItems = unref(this.hasAllItems);
981
+ debug('buildFilteredOptions', 'start', 'hasAllItems:', hasAllItems, 'allOptions', allOptions.length, 'search:', search, 'filteredOptionsLength:', filteredOptionsLength);
950
982
  if (hasAllItems) {
951
983
  /* Everything has already been fetched and stored in filteredOptions */
952
984
  return;
@@ -963,16 +995,17 @@ class SelecticStore {
963
995
  return;
964
996
  }
965
997
  /* When we only have partial options */
966
- const offsetItem = this.state.offsetItem;
998
+ const offsetItem = state.offsetItem;
967
999
  const marginSize = unref(this.marginSize);
968
1000
  const endIndex = offsetItem + marginSize;
1001
+ debug('buildFilteredOptions', 'partial options', 'offsetItem:', offsetItem, 'marginSize:', marginSize, 'filteredOptionsLength', filteredOptionsLength);
969
1002
  if (endIndex <= filteredOptionsLength) {
970
1003
  return;
971
1004
  }
972
1005
  if (!search && endIndex <= allOptionsLength) {
973
- this.setFilteredOptions(this.buildGroupItems(allOptions), false, totalAllOptions + this.state.groups.size);
1006
+ this.setFilteredOptions(this.buildGroupItems(allOptions), false, totalAllOptions + state.groups.size);
974
1007
  const isPartial = unref(this.isPartial);
975
- if (isPartial && this.state.totalDynOptions === Infinity) {
1008
+ if (isPartial && state.totalDynOptions === Infinity) {
976
1009
  this.fetchData();
977
1010
  }
978
1011
  return;
@@ -985,6 +1018,7 @@ class SelecticStore {
985
1018
  return;
986
1019
  }
987
1020
  }
1021
+ debug('buildFilteredOptions', 'end', '(will call fetchData)', this.state.filteredOptions.length);
988
1022
  await this.fetchData();
989
1023
  }
990
1024
  async buildSelectedOptions() {
@@ -1038,6 +1072,39 @@ class SelecticStore {
1038
1072
  state.selectedOptions = items[0];
1039
1073
  }
1040
1074
  }
1075
+ async fetchRequest(fetchCallback, search, offset, limit) {
1076
+ const searchRqId = ++this.requestSearchId;
1077
+ if (!search) {
1078
+ ++this.requestId;
1079
+ }
1080
+ const requestId = this.requestId;
1081
+ debug('fetchRequest', 'start', 'search:', search, 'offset:', offset, 'limit:', limit, 'requestId:', requestId, 'requestSearchId:', searchRqId, 'isRequesting:', this.isRequesting);
1082
+ if (this.isRequesting) {
1083
+ debug('fetchRequest', `await ${DEBOUNCE_REQUEST}ms`);
1084
+ /* debounce the call to avoid sending too much requests */
1085
+ await new Promise((resolve) => {
1086
+ setTimeout(resolve, DEBOUNCE_REQUEST);
1087
+ });
1088
+ /* Check if there are other requested requests, in such case drop this one */
1089
+ if (requestId !== this.requestId || (search && searchRqId !== this.requestSearchId)) {
1090
+ debug('fetchRequest', '××deprecated××', requestId, searchRqId);
1091
+ return false;
1092
+ }
1093
+ }
1094
+ this.isRequesting = true;
1095
+ const response = await fetchCallback(search, offset, limit);
1096
+ /* Check if request is obsolete */
1097
+ if (requestId !== this.requestId || (search && searchRqId !== this.requestSearchId)) {
1098
+ debug('fetchRequest', '×××deprecated×××', requestId, searchRqId);
1099
+ return false;
1100
+ }
1101
+ this.isRequesting = false;
1102
+ const deprecated = searchRqId !== this.requestSearchId;
1103
+ debug('fetchRequest', 'end', response.result.length, response.total, deprecated);
1104
+ return Object.assign(Object.assign({}, response), {
1105
+ /* this is to fulfill the cache */
1106
+ deprecated: deprecated });
1107
+ }
1041
1108
  async fetchData() {
1042
1109
  const state = this.state;
1043
1110
  const labels = this.data.labels;
@@ -1059,9 +1126,14 @@ class SelecticStore {
1059
1126
  const offset = filteredOptionsLength - this.nbGroups(state.filteredOptions) - dynOffset;
1060
1127
  const nbItems = endIndex - offset;
1061
1128
  const limit = Math.ceil(nbItems / pageSize) * pageSize;
1129
+ debug('fetchData', 'start', 'search:', search, 'offset:', offset, 'limit:', limit);
1062
1130
  try {
1063
- const requestId = ++this.requestId;
1064
- const { total: rTotal, result } = await fetchCallback(search, offset, limit);
1131
+ const response = await this.fetchRequest(fetchCallback, search, offset, limit);
1132
+ if (!response) {
1133
+ debug('fetchData', '×× deprecated ××', search, offset, limit);
1134
+ return;
1135
+ }
1136
+ const { total: rTotal, result, deprecated } = response;
1065
1137
  let total = rTotal;
1066
1138
  let errorMessage = '';
1067
1139
  /* Assert result is correctly formatted */
@@ -1079,11 +1151,12 @@ class SelecticStore {
1079
1151
  if (!search) {
1080
1152
  /* update cache */
1081
1153
  state.totalDynOptions = total;
1082
- const old = state.dynOptions.splice(offset, limit, ...result);
1154
+ const old = state.dynOptions.splice(offset, result.length, ...result);
1083
1155
  if (compareOptions(old, result)) {
1084
1156
  /* Added options are the same as previous ones.
1085
1157
  * Stop fetching to avoid infinite loop
1086
1158
  */
1159
+ debug('fetchData', 'no new values');
1087
1160
  if (!unref(this.hasFetchedAllItems)) {
1088
1161
  /* Display error if all items are not fetch
1089
1162
  * We can have the case where old value and result
@@ -1091,14 +1164,21 @@ class SelecticStore {
1091
1164
  * total is 0 */
1092
1165
  errorMessage = labels.wrongQueryResult;
1093
1166
  }
1094
- setTimeout(() => this.buildAllOptions(true, true), 0);
1167
+ setTimeout(() => {
1168
+ debug('fetchData', 'before buildAllOptions (stopped)', 'offsetItem:', this.state.offsetItem, 'allOptions:', this.state.allOptions.length);
1169
+ this.buildAllOptions(true, true);
1170
+ }, 0);
1095
1171
  }
1096
1172
  else {
1097
- setTimeout(() => this.buildAllOptions(true), 0);
1173
+ setTimeout(() => {
1174
+ debug('fetchData', 'before buildAllOptions', 'offsetItem:', this.state.offsetItem, 'allOptions:', this.state.allOptions.length);
1175
+ this.buildAllOptions(true);
1176
+ }, 0);
1098
1177
  }
1099
1178
  }
1100
- /* Check request is not obsolete */
1101
- if (requestId !== this.requestId) {
1179
+ /* Check request (without search) is not obsolete */
1180
+ if (deprecated) {
1181
+ debug('fetchData', '××× deprecated ×××', search, offset, limit);
1102
1182
  return;
1103
1183
  }
1104
1184
  if (!search) {
@@ -1127,14 +1207,17 @@ class SelecticStore {
1127
1207
  }
1128
1208
  catch (e) {
1129
1209
  state.status.errorMessage = e.message;
1210
+ debug('fetchData', 'error', e.message);
1130
1211
  if (!search) {
1131
1212
  state.totalDynOptions = 0;
1132
1213
  this.buildAllOptions(true, true);
1133
1214
  }
1134
1215
  }
1135
1216
  this.state.status.searching = false;
1217
+ debug('fetchData', 'end');
1136
1218
  }
1137
1219
  filterOptions(options, search) {
1220
+ debug('filterOptions', 'start', 'options:', options.length, 'search:', search);
1138
1221
  if (!search) {
1139
1222
  return this.buildGroupItems(options);
1140
1223
  }
@@ -1145,6 +1228,7 @@ class SelecticStore {
1145
1228
  addStaticFilteredOptions(fromDynamic = false) {
1146
1229
  const search = this.state.searchText;
1147
1230
  let continueLoop = fromDynamic;
1231
+ debug('addStaticFilteredOptions', 'start', 'fromDynamic:', fromDynamic, 'optionBehaviorOperation:', this.state.optionBehaviorOperation);
1148
1232
  if (this.state.optionBehaviorOperation !== 'sort') {
1149
1233
  return;
1150
1234
  }
@@ -1170,6 +1254,7 @@ class SelecticStore {
1170
1254
  }
1171
1255
  this.setFilteredOptions(options, true);
1172
1256
  }
1257
+ debug('addStaticFilteredOptions', 'end');
1173
1258
  }
1174
1259
  buildSelectedItems(ids) {
1175
1260
  return this.buildItems(ids.map((id) => {
@@ -1211,6 +1296,7 @@ class SelecticStore {
1211
1296
  }
1212
1297
  buildGroupItems(options, previousItem) {
1213
1298
  let previousGroupId = previousItem && previousItem.group;
1299
+ debug('buildGroupItems', 'start', 'options:', options.length, 'previousGroupId:', previousGroupId);
1214
1300
  const list = this.buildItems(options).reduce((items, item) => {
1215
1301
  if (item.group !== previousGroupId) {
1216
1302
  const groupId = item.group;
@@ -1227,6 +1313,7 @@ class SelecticStore {
1227
1313
  items.push(item);
1228
1314
  return items;
1229
1315
  }, []);
1316
+ debug('buildGroupItems', 'end', list.length);
1230
1317
  return list;
1231
1318
  }
1232
1319
  buildOptionBehavior(optionBehavior, state) {
@@ -1344,6 +1431,7 @@ class SelecticStore {
1344
1431
  }
1345
1432
  /** assign new value to the filteredOptions and apply change depending on it */
1346
1433
  setFilteredOptions(options, add = false, length = 0) {
1434
+ debug('setFilteredOptions', 'start', 'options:', options.length, 'add', add, 'length', length);
1347
1435
  if (!add) {
1348
1436
  this.state.filteredOptions = options;
1349
1437
  this.state.totalFilteredOptions = length || options.length;