selectic 1.3.9 → 3.0.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/README.md +4 -5
- package/dist/selectic.common.js +653 -591
- package/dist/selectic.esm.js +611 -550
- package/doc/domProperties.md +3 -1
- package/package.json +45 -41
- package/src/ExtendedList.tsx +14 -13
- package/src/Filter.tsx +18 -19
- package/src/List.tsx +13 -13
- package/src/MainInput.tsx +26 -27
- package/src/Store.tsx +396 -289
- package/src/css/selectic.css +7 -0
- package/src/index.tsx +194 -135
- package/test/Selectic/Selectic_props.spec.js +29 -10
- package/test/Store/Store_creation.spec.js +395 -438
- package/test/Store/Store_props.spec.js +119 -159
- package/test/Store/Store_state.spec.js +9 -13
- package/test/Store/changeGroups.spec.js +4 -6
- package/test/Store/changeTexts.spec.js +28 -30
- package/test/Store/clearCache.spec.js +24 -8
- package/test/Store/commit.spec.js +222 -98
- package/test/Store/getItem.spec.js +30 -38
- package/test/Store/getItems.spec.js +33 -43
- package/test/Store/selectItem.spec.js +4 -4
- package/test/Store/toggleSelectAll.spec.js +50 -52
- package/test/tools.js +4 -2
- package/tsconfig.json +5 -0
- package/types/ExtendedList.d.ts +6 -6
- package/types/Filter.d.ts +5 -5
- package/types/List.d.ts +33 -9
- package/types/MainInput.d.ts +5 -5
- package/types/Store.d.ts +166 -41
- package/types/index.d.ts +41 -21
package/src/Store.tsx
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
/* File Purpose:
|
|
2
2
|
* It keeps and computes all states at a single place.
|
|
3
|
-
* Every inner components of Selectic should
|
|
3
|
+
* Every inner components of Selectic should communicate with this file to
|
|
4
4
|
* change or to get states.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { reactive, watch, computed, ComputedRef } from 'vue';
|
|
8
8
|
|
|
9
9
|
/* {{{ Types definitions */
|
|
10
10
|
|
|
11
|
+
type MandateProps<T extends {}> = {
|
|
12
|
+
[TK in keyof T]-?: T[TK];
|
|
13
|
+
}
|
|
14
|
+
|
|
11
15
|
type voidCaller = () => void;
|
|
12
16
|
|
|
13
17
|
export type StrictOptionId = string | number;
|
|
@@ -65,7 +69,7 @@ export type ListPosition =
|
|
|
65
69
|
'bottom'
|
|
66
70
|
/* Display the list at bottom */
|
|
67
71
|
| 'top'
|
|
68
|
-
/* Display the list at
|
|
72
|
+
/* Display the list at bottom but if there is not enough space, display it at top */
|
|
69
73
|
| 'auto';
|
|
70
74
|
|
|
71
75
|
export interface SelecticStoreStateParams {
|
|
@@ -124,31 +128,34 @@ export interface SelecticStoreStateParams {
|
|
|
124
128
|
*/
|
|
125
129
|
optionBehavior?: string;
|
|
126
130
|
|
|
131
|
+
/* Indicate where the list should be deployed */
|
|
132
|
+
listPosition?: ListPosition;
|
|
133
|
+
|
|
127
134
|
/* If true, the component is open at start */
|
|
128
135
|
isOpen?: boolean;
|
|
129
136
|
}
|
|
130
137
|
|
|
131
138
|
export interface Props {
|
|
132
139
|
/* Selected value */
|
|
133
|
-
value?: SelectedValue;
|
|
140
|
+
value?: SelectedValue | null;
|
|
134
141
|
|
|
135
142
|
/* If true, the value represents the ones we don't want to select */
|
|
136
143
|
selectionIsExcluded?: boolean;
|
|
137
144
|
|
|
138
|
-
/* Equivalent of "disabled"
|
|
145
|
+
/* Equivalent of "disabled" Select's attribute */
|
|
139
146
|
disabled?: boolean;
|
|
140
147
|
|
|
141
148
|
/* List of options to display */
|
|
142
|
-
options?: OptionProp[];
|
|
149
|
+
options?: OptionProp[] | null;
|
|
143
150
|
|
|
144
151
|
/* List of options to display from child elements */
|
|
145
|
-
childOptions?:
|
|
152
|
+
childOptions?: OptionValue[];
|
|
146
153
|
|
|
147
154
|
/* Define groups which will be used by items */
|
|
148
155
|
groups?: GroupValue[];
|
|
149
156
|
|
|
150
157
|
/* Overwrite default texts */
|
|
151
|
-
texts?: PartialMessages;
|
|
158
|
+
texts?: PartialMessages | null;
|
|
152
159
|
|
|
153
160
|
/* Keep this component open if another Selectic component opens */
|
|
154
161
|
keepOpenWithOtherSelectic?: boolean;
|
|
@@ -157,10 +164,24 @@ export interface Props {
|
|
|
157
164
|
params?: SelecticStoreStateParams;
|
|
158
165
|
|
|
159
166
|
/* Method to call to fetch extra data */
|
|
160
|
-
fetchCallback?: FetchCallback;
|
|
167
|
+
fetchCallback?: FetchCallback | null;
|
|
161
168
|
|
|
162
169
|
/* Method to call to get specific item */
|
|
163
|
-
getItemsCallback?: GetCallback;
|
|
170
|
+
getItemsCallback?: GetCallback | null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
type InternalProps = MandateProps<Props>;
|
|
174
|
+
|
|
175
|
+
export interface Data {
|
|
176
|
+
/* Number of items displayed in a page (before scrolling) */
|
|
177
|
+
itemsPerPage: number;
|
|
178
|
+
|
|
179
|
+
labels: Messages;
|
|
180
|
+
/* used to avoid checking and updating table while doing batch stuff */
|
|
181
|
+
doNotUpdate: boolean;
|
|
182
|
+
cacheItem: Map<OptionId, OptionValue>;
|
|
183
|
+
activeOrder: OptionBehaviorOrder;
|
|
184
|
+
dynOffset: number;
|
|
164
185
|
}
|
|
165
186
|
|
|
166
187
|
export interface SelecticStoreState {
|
|
@@ -191,7 +212,7 @@ export interface SelecticStoreState {
|
|
|
191
212
|
allowRevert?: boolean;
|
|
192
213
|
|
|
193
214
|
/* If true, user can clear current selection
|
|
194
|
-
* (if false, it is still possible to clear it
|
|
215
|
+
* (if false, it is still possible to clear it programmatically) */
|
|
195
216
|
allowClearSelection: boolean;
|
|
196
217
|
|
|
197
218
|
/* If false, do not select the first available option even if value is mandatory */
|
|
@@ -298,6 +319,7 @@ interface Messages {
|
|
|
298
319
|
export type PartialMessages = { [K in keyof Messages]?: Messages[K] };
|
|
299
320
|
|
|
300
321
|
/* }}} */
|
|
322
|
+
/* {{{ Helper */
|
|
301
323
|
|
|
302
324
|
/**
|
|
303
325
|
* Escape search string to consider regexp special characters as they
|
|
@@ -317,6 +339,30 @@ function convertToRegExp(name: string, flag = 'i'): RegExp {
|
|
|
317
339
|
return new RegExp(pattern, flag);
|
|
318
340
|
}
|
|
319
341
|
|
|
342
|
+
/** Does the same as Object.assign but does not replace if value is undefined */
|
|
343
|
+
function assignObject<T>(obj: Partial<T>, ...sourceObjects: Array<Partial<T>>): T {
|
|
344
|
+
const result = obj;
|
|
345
|
+
for (const source of sourceObjects) {
|
|
346
|
+
for (const key of Object.keys(source)) {
|
|
347
|
+
const value = source[key as keyof T];
|
|
348
|
+
if (value === undefined) {
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
result[key as keyof T] = value;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return result as T;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* }}} */
|
|
358
|
+
/* {{{ Static */
|
|
359
|
+
|
|
360
|
+
export function changeTexts(texts: PartialMessages) {
|
|
361
|
+
messages = Object.assign(messages, texts);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* }}} */
|
|
365
|
+
|
|
320
366
|
let messages: Messages = {
|
|
321
367
|
noFetchMethod: 'Fetch callback is missing: it is not possible to retrieve data.',
|
|
322
368
|
searchPlaceholder: 'Search',
|
|
@@ -338,58 +384,16 @@ let messages: Messages = {
|
|
|
338
384
|
|
|
339
385
|
let closePreviousSelectic: undefined | voidCaller;
|
|
340
386
|
|
|
341
|
-
/* {{{ Static */
|
|
342
|
-
|
|
343
|
-
export function changeTexts(texts: PartialMessages) {
|
|
344
|
-
messages = Object.assign(messages, texts);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
387
|
/* }}} */
|
|
348
388
|
|
|
349
|
-
|
|
350
|
-
export default class SelecticStore extends Vue<Props> {
|
|
351
|
-
/* {{{ props */
|
|
352
|
-
|
|
353
|
-
@Prop()
|
|
354
|
-
public value?: SelectedValue;
|
|
355
|
-
|
|
356
|
-
@Prop({default: false})
|
|
357
|
-
public selectionIsExcluded: boolean;
|
|
358
|
-
|
|
359
|
-
@Prop({default: false})
|
|
360
|
-
public disabled: boolean;
|
|
361
|
-
|
|
362
|
-
@Prop()
|
|
363
|
-
public options?: OptionProp[];
|
|
364
|
-
|
|
365
|
-
@Prop()
|
|
366
|
-
public childOptions?: OptionValue[];
|
|
367
|
-
|
|
368
|
-
@Prop({default: () => []})
|
|
369
|
-
public groups: GroupValue[];
|
|
370
|
-
|
|
371
|
-
@Prop()
|
|
372
|
-
public texts?: PartialMessages;
|
|
373
|
-
|
|
374
|
-
@Prop()
|
|
375
|
-
private params?: SelecticStoreStateParams;
|
|
376
|
-
|
|
377
|
-
@Prop()
|
|
378
|
-
private fetchCallback?: FetchCallback;
|
|
389
|
+
let uid = 0;
|
|
379
390
|
|
|
380
|
-
|
|
381
|
-
|
|
391
|
+
export default class SelecticStore {
|
|
392
|
+
public props: InternalProps;
|
|
382
393
|
|
|
383
|
-
@Prop({ default: false })
|
|
384
|
-
private keepOpenWithOtherSelectic: boolean;
|
|
385
|
-
|
|
386
|
-
/* }}} */
|
|
387
394
|
/* {{{ data */
|
|
388
395
|
|
|
389
|
-
|
|
390
|
-
public itemsPerPage = 10;
|
|
391
|
-
|
|
392
|
-
public state: SelecticStoreState = {
|
|
396
|
+
public state = reactive<SelecticStoreState>({
|
|
393
397
|
multiple: false,
|
|
394
398
|
disabled: false,
|
|
395
399
|
placeholder: '',
|
|
@@ -427,58 +431,216 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
427
431
|
areAllSelected: false,
|
|
428
432
|
hasChanged: false,
|
|
429
433
|
},
|
|
430
|
-
};
|
|
431
|
-
public
|
|
432
|
-
/* used to avoid checking and updating table while doing batch stuff */
|
|
433
|
-
private doNotUpdate = false;
|
|
434
|
-
private cacheItem: Map<OptionId, OptionValue> = new Map();
|
|
435
|
-
private activeOrder: OptionBehaviorOrder = 'D';
|
|
436
|
-
private dynOffset: number = 0;
|
|
434
|
+
});
|
|
435
|
+
public data: Data;
|
|
437
436
|
|
|
438
437
|
/* Do not need reactivity */
|
|
439
|
-
private requestId: number;
|
|
438
|
+
private requestId: number = 0;
|
|
440
439
|
private cacheRequest: Map<string, Promise<OptionValue[]>>;
|
|
440
|
+
private closeSelectic: () => void;
|
|
441
441
|
|
|
442
442
|
/* }}} */
|
|
443
443
|
/* {{{ computed */
|
|
444
444
|
|
|
445
445
|
/* Number of item to pre-display */
|
|
446
|
-
|
|
447
|
-
return this.state.pageSize / 2;
|
|
448
|
-
}
|
|
446
|
+
public marginSize: ComputedRef<number>;
|
|
449
447
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
448
|
+
public isPartial: ComputedRef<boolean>;
|
|
449
|
+
public hasAllItems: ComputedRef<boolean>;
|
|
450
|
+
public hasFetchedAllItems: ComputedRef<boolean>;
|
|
451
|
+
private listOptions: ComputedRef<OptionValue[]>;
|
|
452
|
+
private elementOptions: ComputedRef<OptionValue[]>;
|
|
453
|
+
|
|
454
|
+
/* }}} */
|
|
455
|
+
|
|
456
|
+
public _uid: number; /* Mainly for debugging */
|
|
457
|
+
|
|
458
|
+
constructor(props: Props = {}) {
|
|
459
|
+
this._uid = ++uid;
|
|
460
|
+
|
|
461
|
+
/* {{{ Props */
|
|
462
|
+
|
|
463
|
+
const defaultProps: InternalProps = {
|
|
464
|
+
value: null,
|
|
465
|
+
selectionIsExcluded: false,
|
|
466
|
+
disabled: false,
|
|
467
|
+
options: null,
|
|
468
|
+
childOptions: [],
|
|
469
|
+
groups: [],
|
|
470
|
+
texts: null,
|
|
471
|
+
params: {},
|
|
472
|
+
fetchCallback: null,
|
|
473
|
+
getItemsCallback: null,
|
|
474
|
+
keepOpenWithOtherSelectic: false,
|
|
475
|
+
};
|
|
476
|
+
const propsVal: InternalProps = assignObject(defaultProps, props);
|
|
477
|
+
this.props = reactive(propsVal);
|
|
478
|
+
|
|
479
|
+
/* }}} */
|
|
480
|
+
/* {{{ data */
|
|
481
|
+
|
|
482
|
+
this.data = reactive({
|
|
483
|
+
labels: Object.assign({}, messages),
|
|
484
|
+
itemsPerPage: 10,
|
|
485
|
+
doNotUpdate: false,
|
|
486
|
+
cacheItem: new Map(),
|
|
487
|
+
activeOrder: 'D',
|
|
488
|
+
dynOffset: 0,
|
|
489
|
+
});
|
|
453
490
|
|
|
454
|
-
|
|
455
|
-
|
|
491
|
+
/* }}} */
|
|
492
|
+
/* {{{ computed */
|
|
493
|
+
|
|
494
|
+
this.marginSize = computed(() => {
|
|
495
|
+
return this.state.pageSize / 2;
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
this.isPartial = computed(() => {
|
|
499
|
+
const state = this.state;
|
|
500
|
+
let isPartial = typeof this.props.fetchCallback === 'function';
|
|
501
|
+
|
|
502
|
+
if (isPartial &&
|
|
503
|
+
state.optionBehaviorOperation === 'force' &&
|
|
504
|
+
this.data.activeOrder !== 'D'
|
|
505
|
+
) {
|
|
506
|
+
isPartial = false;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return isPartial;
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
this.hasAllItems = computed(() => {
|
|
513
|
+
const state = this.state;
|
|
514
|
+
const nbItems = state.totalFilteredOptions + state.groups.size;
|
|
515
|
+
|
|
516
|
+
return this.state.filteredOptions.length >= nbItems;
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
this.hasFetchedAllItems = computed(() => {
|
|
520
|
+
const isPartial = this.isPartial.value ?? this.isPartial;
|
|
521
|
+
|
|
522
|
+
if (!isPartial) {
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
const state = this.state;
|
|
526
|
+
|
|
527
|
+
return state.dynOptions.length === state.totalDynOptions;
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
this.listOptions = computed(() => {
|
|
531
|
+
return this.getListOptions();
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
this.elementOptions = computed(() => {
|
|
535
|
+
return this.getElementOptions();
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
/* }}} */
|
|
539
|
+
/* {{{ watch */
|
|
540
|
+
|
|
541
|
+
watch(() => [this.props.options, this.props.childOptions], () => {
|
|
542
|
+
this.data.cacheItem.clear();
|
|
543
|
+
this.commit('isOpen', false);
|
|
544
|
+
this.buildAllOptions(true);
|
|
545
|
+
this.buildSelectedOptions();
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
watch(() => [this.listOptions, this.elementOptions], () => {
|
|
549
|
+
/* TODO: transform allOptions as a computed properties and this
|
|
550
|
+
* watcher become useless */
|
|
551
|
+
this.buildAllOptions(true);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
watch(() => this.props.value, () => {
|
|
555
|
+
const value = this.props.value ?? null;
|
|
556
|
+
this.commit('internalValue', value);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
watch(() => this.props.selectionIsExcluded, () => {
|
|
560
|
+
this.commit('selectionIsExcluded', this.props.selectionIsExcluded);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
watch(() => this.props.disabled, () => {
|
|
564
|
+
this.commit('disabled', this.props.disabled);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
watch(() => this.state.filteredOptions, () => {
|
|
568
|
+
let areAllSelected = false;
|
|
569
|
+
const hasAllItems = this.hasAllItems.value ?? this.hasAllItems;
|
|
570
|
+
|
|
571
|
+
if (hasAllItems) {
|
|
572
|
+
const selectionIsExcluded = +this.state.selectionIsExcluded;
|
|
573
|
+
/* eslint-disable-next-line no-bitwise */
|
|
574
|
+
areAllSelected = this.state.filteredOptions.every((item) =>
|
|
575
|
+
!!(+item.selected ^ selectionIsExcluded));
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
this.state.status.areAllSelected = areAllSelected;
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
watch(() => this.state.internalValue, () => {
|
|
582
|
+
this.buildSelectedOptions();
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
watch(() => this.state.allOptions, () => {
|
|
586
|
+
this.checkAutoSelect();
|
|
587
|
+
this.checkAutoDisabled();
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
watch(() => this.state.totalAllOptions, () => {
|
|
591
|
+
this.checkHideFilter();
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
/* }}} */
|
|
595
|
+
|
|
596
|
+
this.closeSelectic = () => {
|
|
597
|
+
this.commit('isOpen', false);
|
|
456
598
|
}
|
|
457
599
|
|
|
458
|
-
|
|
459
|
-
}
|
|
600
|
+
const value = this.props.value;
|
|
460
601
|
|
|
461
|
-
|
|
462
|
-
|
|
602
|
+
/* set initial value for non reactive attribute */
|
|
603
|
+
this.cacheRequest = new Map();
|
|
463
604
|
|
|
464
|
-
|
|
465
|
-
|
|
605
|
+
const stateParam: SelecticStoreStateParams | SelecticStoreState =
|
|
606
|
+
Object.assign({}, this.props.params);
|
|
466
607
|
|
|
467
|
-
|
|
468
|
-
|
|
608
|
+
if (stateParam.optionBehavior) {
|
|
609
|
+
this.buildOptionBehavior(
|
|
610
|
+
stateParam.optionBehavior,
|
|
611
|
+
stateParam as SelecticStoreState
|
|
612
|
+
);
|
|
613
|
+
delete stateParam.optionBehavior;
|
|
614
|
+
}
|
|
469
615
|
|
|
470
|
-
if (
|
|
471
|
-
|
|
616
|
+
if (stateParam.hideFilter === 'auto') {
|
|
617
|
+
delete stateParam.hideFilter;
|
|
472
618
|
}
|
|
473
619
|
|
|
474
|
-
|
|
475
|
-
|
|
620
|
+
/* Update state */
|
|
621
|
+
assignObject(this.state, stateParam as SelecticStoreState);
|
|
622
|
+
/* XXX: should be done in 2 lines, in order to set the multiple state
|
|
623
|
+
* and ensure convertValue run with correct state */
|
|
624
|
+
assignObject(this.state, {
|
|
625
|
+
internalValue: this.convertTypeValue(value),
|
|
626
|
+
selectionIsExcluded: props.selectionIsExcluded,
|
|
627
|
+
disabled: props.disabled,
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
this.checkHideFilter();
|
|
631
|
+
|
|
632
|
+
if (this.props.texts) {
|
|
633
|
+
this.changeTexts(this.props.texts);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
this.addGroups(this.props.groups);
|
|
637
|
+
this.assertValueType();
|
|
638
|
+
this.buildAllOptions();
|
|
476
639
|
|
|
477
|
-
|
|
478
|
-
|
|
640
|
+
this.buildSelectedOptions();
|
|
641
|
+
this.checkAutoDisabled();
|
|
479
642
|
}
|
|
480
643
|
|
|
481
|
-
/* }}} */
|
|
482
644
|
/* {{{ methods */
|
|
483
645
|
/* {{{ public methods */
|
|
484
646
|
|
|
@@ -524,7 +686,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
524
686
|
if (typeof closePreviousSelectic === 'function') {
|
|
525
687
|
closePreviousSelectic();
|
|
526
688
|
}
|
|
527
|
-
if (!this.keepOpenWithOtherSelectic) {
|
|
689
|
+
if (!this.props.keepOpenWithOtherSelectic) {
|
|
528
690
|
closePreviousSelectic = this.closeSelectic;
|
|
529
691
|
}
|
|
530
692
|
}
|
|
@@ -553,7 +715,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
553
715
|
let item: OptionValue;
|
|
554
716
|
|
|
555
717
|
if (this.hasItemInStore(id)) {
|
|
556
|
-
item = this.cacheItem.get(id) as OptionValue;
|
|
718
|
+
item = this.data.cacheItem.get(id) as OptionValue;
|
|
557
719
|
} else {
|
|
558
720
|
this.getItems([id]);
|
|
559
721
|
item = {
|
|
@@ -567,8 +729,9 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
567
729
|
|
|
568
730
|
public async getItems(ids: OptionId[]): Promise<OptionItem[]> {
|
|
569
731
|
const itemsToFetch: OptionId[] = ids.filter((id) => !this.hasItemInStore(id));
|
|
732
|
+
const getItemsCallback = this.props.getItemsCallback;
|
|
570
733
|
|
|
571
|
-
if (itemsToFetch.length && typeof
|
|
734
|
+
if (itemsToFetch.length && typeof getItemsCallback === 'function') {
|
|
572
735
|
const cacheRequest = this.cacheRequest;
|
|
573
736
|
const requestId = itemsToFetch.toString();
|
|
574
737
|
let promise: Promise<OptionValue[]>;
|
|
@@ -576,7 +739,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
576
739
|
if (cacheRequest.has(requestId)) {
|
|
577
740
|
promise = cacheRequest.get(requestId)!;
|
|
578
741
|
} else {
|
|
579
|
-
promise =
|
|
742
|
+
promise = getItemsCallback(itemsToFetch);
|
|
580
743
|
cacheRequest.set(requestId, promise);
|
|
581
744
|
promise.then(() => {
|
|
582
745
|
cacheRequest.delete(requestId);
|
|
@@ -584,10 +747,11 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
584
747
|
}
|
|
585
748
|
|
|
586
749
|
const items = await promise;
|
|
750
|
+
const cacheItem = this.data.cacheItem;
|
|
587
751
|
|
|
588
752
|
for (const item of items) {
|
|
589
753
|
if (item) {
|
|
590
|
-
|
|
754
|
+
cacheItem.set(item.id, item);
|
|
591
755
|
}
|
|
592
756
|
}
|
|
593
757
|
}
|
|
@@ -598,9 +762,10 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
598
762
|
public selectItem(id: OptionId, selected?: boolean, keepOpen = false) {
|
|
599
763
|
const state = this.state;
|
|
600
764
|
let hasChanged = false;
|
|
765
|
+
const isPartial = this.isPartial.value ?? this.isPartial;
|
|
601
766
|
|
|
602
767
|
/* Check that item is not disabled */
|
|
603
|
-
if (!
|
|
768
|
+
if (!isPartial) {
|
|
604
769
|
const item = state.allOptions.find((opt) => opt.id === id);
|
|
605
770
|
if (item && item.disabled) {
|
|
606
771
|
return;
|
|
@@ -672,15 +837,18 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
672
837
|
if (!this.state.multiple) {
|
|
673
838
|
return;
|
|
674
839
|
}
|
|
840
|
+
const hasAllItems = this.hasAllItems.value ?? this.hasAllItems;
|
|
841
|
+
|
|
842
|
+
if (!hasAllItems) {
|
|
843
|
+
const labels = this.data.labels;
|
|
675
844
|
|
|
676
|
-
if (!this.hasAllItems) {
|
|
677
845
|
if (this.state.searchText) {
|
|
678
|
-
this.state.status.errorMessage =
|
|
846
|
+
this.state.status.errorMessage = labels.cannotSelectAllSearchedItems;
|
|
679
847
|
return;
|
|
680
848
|
}
|
|
681
849
|
|
|
682
850
|
if (!this.state.allowRevert) {
|
|
683
|
-
this.state.status.errorMessage =
|
|
851
|
+
this.state.status.errorMessage = labels.cannotSelectAllRevertItems;
|
|
684
852
|
return;
|
|
685
853
|
}
|
|
686
854
|
|
|
@@ -695,9 +863,9 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
695
863
|
|
|
696
864
|
const selectAll = !this.state.status.areAllSelected;
|
|
697
865
|
this.state.status.areAllSelected = selectAll;
|
|
698
|
-
this.doNotUpdate = true;
|
|
866
|
+
this.data.doNotUpdate = true;
|
|
699
867
|
this.state.filteredOptions.forEach((item) => this.selectItem(item.id, selectAll));
|
|
700
|
-
this.doNotUpdate = false;
|
|
868
|
+
this.data.doNotUpdate = false;
|
|
701
869
|
this.updateFilteredOptions();
|
|
702
870
|
}
|
|
703
871
|
|
|
@@ -710,9 +878,10 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
710
878
|
}
|
|
711
879
|
|
|
712
880
|
public clearCache(forceReset = false) {
|
|
713
|
-
const
|
|
881
|
+
const isPartial = this.isPartial.value ?? this.isPartial;
|
|
882
|
+
const total = isPartial ? Infinity : 0;
|
|
714
883
|
|
|
715
|
-
this.cacheItem.clear();
|
|
884
|
+
this.data.cacheItem.clear();
|
|
716
885
|
|
|
717
886
|
this.state.allOptions = [];
|
|
718
887
|
this.state.totalAllOptions = total;
|
|
@@ -746,7 +915,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
746
915
|
}
|
|
747
916
|
|
|
748
917
|
public changeTexts(texts: PartialMessages) {
|
|
749
|
-
this.labels = Object.assign({}, this.labels, texts);
|
|
918
|
+
this.data.labels = Object.assign({}, this.data.labels, texts);
|
|
750
919
|
}
|
|
751
920
|
|
|
752
921
|
/* }}} */
|
|
@@ -767,25 +936,52 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
767
936
|
|
|
768
937
|
return this.state.filteredOptions.find(findId) ||
|
|
769
938
|
this.state.dynOptions.find(findId) ||
|
|
770
|
-
this.
|
|
771
|
-
this.
|
|
939
|
+
(this.listOptions.value ?? this.listOptions).find(findId) ||
|
|
940
|
+
(this.elementOptions.value ?? this.elementOptions).find(findId);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
private convertTypeValue(oldValue: OptionId | StrictOptionId[]) {
|
|
944
|
+
const state = this.state;
|
|
945
|
+
const isMultiple = state.multiple;
|
|
946
|
+
let newValue = oldValue;
|
|
947
|
+
|
|
948
|
+
if (isMultiple) {
|
|
949
|
+
if (!Array.isArray(oldValue)) {
|
|
950
|
+
newValue = oldValue === null ? [] : [oldValue];
|
|
951
|
+
}
|
|
952
|
+
} else {
|
|
953
|
+
if (Array.isArray(oldValue)) {
|
|
954
|
+
const value = oldValue[0];
|
|
955
|
+
newValue = typeof value === 'undefined' ? null : value;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
return newValue;
|
|
772
959
|
}
|
|
773
960
|
|
|
774
|
-
private
|
|
961
|
+
private assertValueType() {
|
|
775
962
|
const state = this.state;
|
|
776
963
|
const internalValue = state.internalValue;
|
|
964
|
+
const newValue = this.convertTypeValue(internalValue);
|
|
965
|
+
|
|
966
|
+
if (newValue !== internalValue) {
|
|
967
|
+
state.internalValue = newValue;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
private assertCorrectValue(applyStrict = false) {
|
|
972
|
+
const state = this.state;
|
|
973
|
+
this.assertValueType();
|
|
974
|
+
const internalValue = state.internalValue;
|
|
777
975
|
const selectionIsExcluded = state.selectionIsExcluded;
|
|
778
976
|
const isMultiple = state.multiple;
|
|
779
977
|
const checkStrict = state.strictValue;
|
|
780
978
|
let newValue = internalValue;
|
|
781
|
-
const isPartial = this.isPartial;
|
|
979
|
+
const isPartial = this.isPartial.value ?? this.isPartial;
|
|
782
980
|
|
|
783
981
|
if (isMultiple) {
|
|
784
|
-
|
|
785
|
-
newValue = internalValue === null ? [] : [internalValue];
|
|
786
|
-
}
|
|
982
|
+
const hasFetchedAllItems = this.hasFetchedAllItems.value ?? this.hasFetchedAllItems;
|
|
787
983
|
|
|
788
|
-
if (selectionIsExcluded &&
|
|
984
|
+
if (selectionIsExcluded && hasFetchedAllItems) {
|
|
789
985
|
newValue = state.allOptions.reduce((values, option) => {
|
|
790
986
|
const id = option.id as StrictOptionId;
|
|
791
987
|
|
|
@@ -798,11 +994,6 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
798
994
|
state.selectionIsExcluded = false;
|
|
799
995
|
}
|
|
800
996
|
} else {
|
|
801
|
-
if (Array.isArray(internalValue)) {
|
|
802
|
-
const value = internalValue[0];
|
|
803
|
-
newValue = typeof value === 'undefined' ? null : value;
|
|
804
|
-
}
|
|
805
|
-
|
|
806
997
|
state.selectionIsExcluded = false;
|
|
807
998
|
}
|
|
808
999
|
|
|
@@ -815,17 +1006,19 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
815
1006
|
.filter((value) => this.hasItemInStore(value));
|
|
816
1007
|
isDifferent = filteredValue.length !== (newValue as StrictOptionId[]).length;
|
|
817
1008
|
|
|
818
|
-
if (isDifferent && isPartial && !
|
|
819
|
-
this.getItems(newValue as StrictOptionId[])
|
|
1009
|
+
if (isDifferent && isPartial && !applyStrict) {
|
|
1010
|
+
this.getItems(newValue as StrictOptionId[])
|
|
1011
|
+
.then(() => this.assertCorrectValue(true));
|
|
820
1012
|
return;
|
|
821
1013
|
}
|
|
822
1014
|
} else
|
|
823
|
-
if (!this.hasItemInStore(newValue as OptionId)) {
|
|
1015
|
+
if (newValue !== null && !this.hasItemInStore(newValue as OptionId)) {
|
|
824
1016
|
filteredValue = null;
|
|
825
1017
|
isDifferent = true;
|
|
826
1018
|
|
|
827
|
-
if (isPartial && !
|
|
828
|
-
this.getItems([newValue as OptionId])
|
|
1019
|
+
if (isPartial && !applyStrict) {
|
|
1020
|
+
this.getItems([newValue as OptionId])
|
|
1021
|
+
.then(() => this.assertCorrectValue(true));
|
|
829
1022
|
return;
|
|
830
1023
|
}
|
|
831
1024
|
}
|
|
@@ -843,8 +1036,9 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
843
1036
|
}
|
|
844
1037
|
|
|
845
1038
|
private updateFilteredOptions() {
|
|
846
|
-
if (!this.doNotUpdate) {
|
|
1039
|
+
if (!this.data.doNotUpdate) {
|
|
847
1040
|
this.state.filteredOptions = this.buildItems(this.state.filteredOptions);
|
|
1041
|
+
this.buildSelectedOptions();
|
|
848
1042
|
}
|
|
849
1043
|
}
|
|
850
1044
|
|
|
@@ -854,14 +1048,15 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
854
1048
|
});
|
|
855
1049
|
}
|
|
856
1050
|
|
|
857
|
-
/*
|
|
1051
|
+
/* This method is for the computed property listOptions */
|
|
858
1052
|
private getListOptions(): OptionValue[] {
|
|
859
|
-
const options = this.options;
|
|
1053
|
+
const options = this.props.options;
|
|
860
1054
|
const listOptions: OptionValue[] = [];
|
|
861
1055
|
|
|
862
1056
|
if (!Array.isArray(options)) {
|
|
863
1057
|
return listOptions;
|
|
864
1058
|
}
|
|
1059
|
+
const state = this.state;
|
|
865
1060
|
|
|
866
1061
|
options.forEach((option) => {
|
|
867
1062
|
/* manage simple string */
|
|
@@ -877,14 +1072,14 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
877
1072
|
const subOptions = option.options;
|
|
878
1073
|
|
|
879
1074
|
/* check for groups */
|
|
880
|
-
if (group && !
|
|
881
|
-
|
|
1075
|
+
if (group && !state.groups.has(group)) {
|
|
1076
|
+
state.groups.set(group, String(group));
|
|
882
1077
|
}
|
|
883
1078
|
|
|
884
1079
|
/* check for sub options */
|
|
885
1080
|
if (subOptions) {
|
|
886
1081
|
const groupId = option.id as StrictOptionId;
|
|
887
|
-
|
|
1082
|
+
state.groups.set(groupId, option.text);
|
|
888
1083
|
|
|
889
1084
|
subOptions.forEach((subOpt) => {
|
|
890
1085
|
subOpt.group = groupId;
|
|
@@ -899,28 +1094,29 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
899
1094
|
return listOptions;
|
|
900
1095
|
}
|
|
901
1096
|
|
|
902
|
-
/*
|
|
1097
|
+
/* This method is for the computed property elementOptions */
|
|
903
1098
|
private getElementOptions(): OptionValue[] {
|
|
904
|
-
const options = this.childOptions;
|
|
1099
|
+
const options = this.props.childOptions;
|
|
905
1100
|
const childOptions: OptionValue[] = [];
|
|
906
1101
|
|
|
907
|
-
if (!Array.isArray(options)) {
|
|
1102
|
+
if (!Array.isArray(options) || options.length === 0) {
|
|
908
1103
|
return childOptions;
|
|
909
1104
|
}
|
|
1105
|
+
const state = this.state;
|
|
910
1106
|
|
|
911
1107
|
options.forEach((option) => {
|
|
912
1108
|
const group = option.group;
|
|
913
1109
|
const subOptions = option.options;
|
|
914
1110
|
|
|
915
1111
|
/* check for groups */
|
|
916
|
-
if (group && !
|
|
917
|
-
|
|
1112
|
+
if (group && !state.groups.has(group)) {
|
|
1113
|
+
state.groups.set(group, String(group));
|
|
918
1114
|
}
|
|
919
1115
|
|
|
920
1116
|
/* check for sub options */
|
|
921
1117
|
if (subOptions) {
|
|
922
1118
|
const groupId = option.id as StrictOptionId;
|
|
923
|
-
|
|
1119
|
+
state.groups.set(groupId, option.text);
|
|
924
1120
|
|
|
925
1121
|
const sOpts: OptionValue[] = subOptions.map((subOpt) => {
|
|
926
1122
|
return Object.assign({}, subOpt, {
|
|
@@ -943,6 +1139,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
943
1139
|
let elementOptions: OptionValue[] = [];
|
|
944
1140
|
const optionBehaviorOrder = this.state.optionBehaviorOrder;
|
|
945
1141
|
let length: number = Infinity;
|
|
1142
|
+
const isPartial = this.isPartial.value ?? this.isPartial;
|
|
946
1143
|
|
|
947
1144
|
const arrayFromOrder = (orderValue: OptionBehaviorOrder): OptionValue[] => {
|
|
948
1145
|
switch(orderValue) {
|
|
@@ -963,7 +1160,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
963
1160
|
};
|
|
964
1161
|
|
|
965
1162
|
if (!keepFetched) {
|
|
966
|
-
if (
|
|
1163
|
+
if (isPartial) {
|
|
967
1164
|
this.state.totalAllOptions = Infinity;
|
|
968
1165
|
this.state.totalDynOptions = Infinity;
|
|
969
1166
|
} else {
|
|
@@ -971,15 +1168,15 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
971
1168
|
}
|
|
972
1169
|
}
|
|
973
1170
|
|
|
974
|
-
listOptions = this.
|
|
975
|
-
elementOptions = this.
|
|
1171
|
+
listOptions = this.listOptions.value ?? this.listOptions;
|
|
1172
|
+
elementOptions = this.elementOptions.value ?? this.elementOptions;
|
|
976
1173
|
|
|
977
1174
|
if (this.state.optionBehaviorOperation === 'force') {
|
|
978
1175
|
const orderValue = optionBehaviorOrder.find((value) => lengthFromOrder(value) > 0)!;
|
|
979
1176
|
allOptions.push(...arrayFromOrder(orderValue));
|
|
980
1177
|
length = lengthFromOrder(orderValue);
|
|
981
|
-
this.activeOrder = orderValue;
|
|
982
|
-
this.dynOffset = 0;
|
|
1178
|
+
this.data.activeOrder = orderValue;
|
|
1179
|
+
this.data.dynOffset = 0;
|
|
983
1180
|
} else {
|
|
984
1181
|
/* sort */
|
|
985
1182
|
let offset = 0;
|
|
@@ -988,7 +1185,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
988
1185
|
const lngth = lengthFromOrder(orderValue);
|
|
989
1186
|
|
|
990
1187
|
if (orderValue === 'D') {
|
|
991
|
-
this.dynOffset = offset;
|
|
1188
|
+
this.data.dynOffset = offset;
|
|
992
1189
|
} else {
|
|
993
1190
|
offset += lngth;
|
|
994
1191
|
}
|
|
@@ -999,7 +1196,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
999
1196
|
break;
|
|
1000
1197
|
}
|
|
1001
1198
|
}
|
|
1002
|
-
this.activeOrder = 'D';
|
|
1199
|
+
this.data.activeOrder = 'D';
|
|
1003
1200
|
length = optionBehaviorOrder.reduce((total, orderValue) => total + lengthFromOrder(orderValue), 0);
|
|
1004
1201
|
}
|
|
1005
1202
|
|
|
@@ -1008,7 +1205,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1008
1205
|
if (keepFetched) {
|
|
1009
1206
|
this.state.totalAllOptions = length;
|
|
1010
1207
|
} else {
|
|
1011
|
-
if (!
|
|
1208
|
+
if (!isPartial) {
|
|
1012
1209
|
this.state.totalAllOptions = allOptions.length;
|
|
1013
1210
|
}
|
|
1014
1211
|
}
|
|
@@ -1016,7 +1213,10 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1016
1213
|
this.state.filteredOptions = [];
|
|
1017
1214
|
this.state.totalFilteredOptions = Infinity;
|
|
1018
1215
|
|
|
1019
|
-
this.buildFilteredOptions()
|
|
1216
|
+
this.buildFilteredOptions().then(() => {
|
|
1217
|
+
/* XXX: To recompute for strict mode and auto-select */
|
|
1218
|
+
this.assertCorrectValue();
|
|
1219
|
+
});
|
|
1020
1220
|
}
|
|
1021
1221
|
|
|
1022
1222
|
private async buildFilteredOptions() {
|
|
@@ -1030,14 +1230,16 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1030
1230
|
const totalAllOptions = this.state.totalAllOptions;
|
|
1031
1231
|
const allOptionsLength = allOptions.length;
|
|
1032
1232
|
let filteredOptionsLength = this.state.filteredOptions.length;
|
|
1233
|
+
const hasAllItems = this.hasAllItems.value ?? this.hasAllItems;
|
|
1033
1234
|
|
|
1034
|
-
if (
|
|
1235
|
+
if (hasAllItems) {
|
|
1035
1236
|
/* Everything has already been fetched and stored in filteredOptions */
|
|
1036
1237
|
return;
|
|
1037
1238
|
}
|
|
1038
1239
|
|
|
1240
|
+
const hasFetchedAllItems = this.hasFetchedAllItems.value ?? this.hasFetchedAllItems;
|
|
1039
1241
|
/* Check if all options have been fetched */
|
|
1040
|
-
if (
|
|
1242
|
+
if (hasFetchedAllItems) {
|
|
1041
1243
|
if (!search) {
|
|
1042
1244
|
this.state.filteredOptions = this.buildGroupItems(allOptions);
|
|
1043
1245
|
this.state.totalFilteredOptions = this.state.filteredOptions.length;
|
|
@@ -1053,7 +1255,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1053
1255
|
/* When we only have partial options */
|
|
1054
1256
|
|
|
1055
1257
|
const offsetItem = this.state.offsetItem;
|
|
1056
|
-
const marginSize = this.marginSize;
|
|
1258
|
+
const marginSize = this.marginSize.value ?? this.marginSize;
|
|
1057
1259
|
const endIndex = offsetItem + marginSize;
|
|
1058
1260
|
|
|
1059
1261
|
if (endIndex <= filteredOptionsLength) {
|
|
@@ -1063,7 +1265,8 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1063
1265
|
if (!search && endIndex <= allOptionsLength) {
|
|
1064
1266
|
this.state.filteredOptions = this.buildGroupItems(allOptions);
|
|
1065
1267
|
this.state.totalFilteredOptions = totalAllOptions + this.state.groups.size;
|
|
1066
|
-
|
|
1268
|
+
const isPartial = this.isPartial.value ?? this.isPartial;
|
|
1269
|
+
if (isPartial && this.state.totalDynOptions === Infinity) {
|
|
1067
1270
|
this.fetchData();
|
|
1068
1271
|
}
|
|
1069
1272
|
return;
|
|
@@ -1073,7 +1276,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1073
1276
|
this.addStaticFilteredOptions();
|
|
1074
1277
|
|
|
1075
1278
|
filteredOptionsLength = this.state.filteredOptions.length;
|
|
1076
|
-
this.dynOffset = filteredOptionsLength;
|
|
1279
|
+
this.data.dynOffset = filteredOptionsLength;
|
|
1077
1280
|
if (endIndex <= filteredOptionsLength) {
|
|
1078
1281
|
return;
|
|
1079
1282
|
}
|
|
@@ -1084,26 +1287,27 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1084
1287
|
|
|
1085
1288
|
private async buildSelectedOptions() {
|
|
1086
1289
|
const internalValue = this.state.internalValue;
|
|
1290
|
+
const state = this.state;
|
|
1087
1291
|
|
|
1088
|
-
if (
|
|
1292
|
+
if (state.multiple) {
|
|
1089
1293
|
/* display partial information about selected items */
|
|
1090
|
-
|
|
1294
|
+
state.selectedOptions = this.buildSelectedItems(internalValue as StrictOptionId[]);
|
|
1091
1295
|
|
|
1092
1296
|
const items: OptionItem[] = await this.getItems(internalValue as StrictOptionId[]).catch(() => []);
|
|
1093
|
-
if (internalValue !==
|
|
1297
|
+
if (internalValue !== state.internalValue) {
|
|
1094
1298
|
/* Values have been deprecated */
|
|
1095
1299
|
return;
|
|
1096
1300
|
}
|
|
1097
1301
|
|
|
1098
1302
|
if (items.length !== (internalValue as StrictOptionId[]).length) {
|
|
1099
|
-
if (!
|
|
1100
|
-
const updatedItems =
|
|
1303
|
+
if (!state.strictValue) {
|
|
1304
|
+
const updatedItems = state.selectedOptions.map((option) => {
|
|
1101
1305
|
const foundItem = items.find((item) => item.id === option.id);
|
|
1102
1306
|
|
|
1103
1307
|
return foundItem || option;
|
|
1104
1308
|
});
|
|
1105
1309
|
|
|
1106
|
-
|
|
1310
|
+
state.selectedOptions = updatedItems;
|
|
1107
1311
|
} else {
|
|
1108
1312
|
const itemIds = items.map((item) => item.id as StrictOptionId) ;
|
|
1109
1313
|
|
|
@@ -1113,37 +1317,39 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1113
1317
|
}
|
|
1114
1318
|
|
|
1115
1319
|
/* display full information about selected items */
|
|
1116
|
-
|
|
1320
|
+
state.selectedOptions = items;
|
|
1117
1321
|
} else
|
|
1118
1322
|
if (internalValue === null) {
|
|
1119
|
-
|
|
1323
|
+
state.selectedOptions = null;
|
|
1120
1324
|
} else {
|
|
1121
1325
|
/* display partial information about selected items */
|
|
1122
|
-
|
|
1326
|
+
state.selectedOptions = this.buildSelectedItems([internalValue as OptionId])[0];
|
|
1123
1327
|
|
|
1124
1328
|
const items = await this.getItems([internalValue as OptionId]).catch(() => []);
|
|
1125
|
-
if (internalValue !==
|
|
1329
|
+
if (internalValue !== state.internalValue) {
|
|
1126
1330
|
/* Values have been deprecated */
|
|
1127
1331
|
return;
|
|
1128
1332
|
}
|
|
1129
1333
|
|
|
1130
1334
|
if (!items.length) {
|
|
1131
|
-
if (
|
|
1335
|
+
if (state.strictValue) {
|
|
1132
1336
|
this.commit('internalValue', null);
|
|
1133
1337
|
}
|
|
1134
1338
|
return;
|
|
1135
1339
|
}
|
|
1136
1340
|
|
|
1137
1341
|
/* display full information about selected items */
|
|
1138
|
-
|
|
1342
|
+
state.selectedOptions = items[0];
|
|
1139
1343
|
}
|
|
1140
1344
|
}
|
|
1141
1345
|
|
|
1142
1346
|
private async fetchData() {
|
|
1143
1347
|
const state = this.state;
|
|
1348
|
+
const labels = this.data.labels;
|
|
1349
|
+
const fetchCallback = this.props.fetchCallback;
|
|
1144
1350
|
|
|
1145
|
-
if (!
|
|
1146
|
-
state.status.errorMessage =
|
|
1351
|
+
if (!fetchCallback) {
|
|
1352
|
+
state.status.errorMessage = labels.noFetchMethod;
|
|
1147
1353
|
return;
|
|
1148
1354
|
}
|
|
1149
1355
|
|
|
@@ -1151,25 +1357,26 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1151
1357
|
const filteredOptionsLength = state.filteredOptions.length;
|
|
1152
1358
|
const offsetItem = state.offsetItem;
|
|
1153
1359
|
const pageSize = state.pageSize;
|
|
1154
|
-
const marginSize = this.marginSize;
|
|
1360
|
+
const marginSize = this.marginSize.value ?? this.marginSize;
|
|
1155
1361
|
const endIndex = offsetItem + marginSize;
|
|
1362
|
+
const dynOffset = this.data.dynOffset;
|
|
1156
1363
|
|
|
1157
1364
|
/* Run the query */
|
|
1158
1365
|
this.state.status.searching = true;
|
|
1159
1366
|
|
|
1160
1367
|
/* Manage cases where offsetItem is not equal to the last item received */
|
|
1161
|
-
const offset = filteredOptionsLength - this.nbGroups(state.filteredOptions) -
|
|
1368
|
+
const offset = filteredOptionsLength - this.nbGroups(state.filteredOptions) - dynOffset;
|
|
1162
1369
|
const nbItems = endIndex - offset;
|
|
1163
1370
|
const limit = Math.ceil(nbItems / pageSize) * pageSize;
|
|
1164
1371
|
|
|
1165
1372
|
try {
|
|
1166
1373
|
const requestId = ++this.requestId;
|
|
1167
|
-
const {total: rTotal, result} = await
|
|
1374
|
+
const {total: rTotal, result} = await fetchCallback(search, offset, limit);
|
|
1168
1375
|
let total = rTotal;
|
|
1169
1376
|
|
|
1170
1377
|
/* Assert result is correctly formatted */
|
|
1171
1378
|
if (!Array.isArray(result)) {
|
|
1172
|
-
throw new Error(
|
|
1379
|
+
throw new Error(labels.wrongFormattedData);
|
|
1173
1380
|
}
|
|
1174
1381
|
|
|
1175
1382
|
/* Handle case where total is not returned */
|
|
@@ -1186,7 +1393,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1186
1393
|
/* update cache */
|
|
1187
1394
|
state.totalDynOptions = total;
|
|
1188
1395
|
state.dynOptions.splice(offset, limit, ...result);
|
|
1189
|
-
|
|
1396
|
+
setTimeout(() => this.buildAllOptions(true), 0);
|
|
1190
1397
|
}
|
|
1191
1398
|
|
|
1192
1399
|
/* Check request is not obsolete */
|
|
@@ -1201,7 +1408,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1201
1408
|
const options = this.buildGroupItems(result, previousItem);
|
|
1202
1409
|
const nbGroups1 = this.nbGroups(options);
|
|
1203
1410
|
|
|
1204
|
-
state.filteredOptions.splice(offset +
|
|
1411
|
+
state.filteredOptions.splice(offset + dynOffset, limit + nbGroups1, ...options);
|
|
1205
1412
|
}
|
|
1206
1413
|
|
|
1207
1414
|
let nbGroups = state.groups.size;
|
|
@@ -1209,7 +1416,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1209
1416
|
nbGroups = this.nbGroups(state.filteredOptions);
|
|
1210
1417
|
}
|
|
1211
1418
|
|
|
1212
|
-
state.totalFilteredOptions = total + nbGroups +
|
|
1419
|
+
state.totalFilteredOptions = total + nbGroups + dynOffset;
|
|
1213
1420
|
|
|
1214
1421
|
if (search && state.totalFilteredOptions <= state.filteredOptions.length) {
|
|
1215
1422
|
this.addStaticFilteredOptions(true);
|
|
@@ -1217,7 +1424,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1217
1424
|
|
|
1218
1425
|
state.status.errorMessage = '';
|
|
1219
1426
|
} catch (e) {
|
|
1220
|
-
state.status.errorMessage = e.message;
|
|
1427
|
+
state.status.errorMessage = (e as Error).message;
|
|
1221
1428
|
if (!search) {
|
|
1222
1429
|
state.totalDynOptions = 0;
|
|
1223
1430
|
this.buildAllOptions(true);
|
|
@@ -1261,10 +1468,10 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1261
1468
|
|
|
1262
1469
|
switch (order) {
|
|
1263
1470
|
case 'O':
|
|
1264
|
-
options = this.filterOptions(this.
|
|
1471
|
+
options = this.filterOptions(this.listOptions.value ?? this.listOptions, search);
|
|
1265
1472
|
break;
|
|
1266
1473
|
case 'E':
|
|
1267
|
-
options = this.filterOptions(this.
|
|
1474
|
+
options = this.filterOptions(this.elementOptions.value ?? this.elementOptions, search);
|
|
1268
1475
|
break;
|
|
1269
1476
|
}
|
|
1270
1477
|
this.state.filteredOptions.push(...options);
|
|
@@ -1274,7 +1481,8 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1274
1481
|
|
|
1275
1482
|
private buildSelectedItems(ids: OptionId[]): OptionItem[] {
|
|
1276
1483
|
return this.buildItems(ids.map((id) => {
|
|
1277
|
-
const
|
|
1484
|
+
const cacheItem = this.data.cacheItem;
|
|
1485
|
+
const item = cacheItem.get(id);
|
|
1278
1486
|
|
|
1279
1487
|
return item || {
|
|
1280
1488
|
id,
|
|
@@ -1284,13 +1492,14 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1284
1492
|
}
|
|
1285
1493
|
|
|
1286
1494
|
private hasItemInStore(id: OptionId): boolean {
|
|
1287
|
-
|
|
1495
|
+
const cacheItem = this.data.cacheItem;
|
|
1496
|
+
let item: OptionValue | undefined = cacheItem.get(id);
|
|
1288
1497
|
|
|
1289
1498
|
if (!item) {
|
|
1290
1499
|
item = this.getValue(id);
|
|
1291
1500
|
|
|
1292
1501
|
if (item) {
|
|
1293
|
-
|
|
1502
|
+
cacheItem.set(item.id, item);
|
|
1294
1503
|
}
|
|
1295
1504
|
}
|
|
1296
1505
|
|
|
@@ -1311,7 +1520,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1311
1520
|
disabled: false,
|
|
1312
1521
|
isGroup: false,
|
|
1313
1522
|
}, option, {
|
|
1314
|
-
|
|
1523
|
+
/* eslint-disable-next-line no-bitwise */
|
|
1315
1524
|
selected: !!(+selected.includes(id) ^ selectionIsExcluded),
|
|
1316
1525
|
});
|
|
1317
1526
|
});
|
|
@@ -1355,7 +1564,8 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1355
1564
|
isValid = isValid && /^[ODE]+$/.test(order);
|
|
1356
1565
|
|
|
1357
1566
|
if (!isValid) {
|
|
1358
|
-
|
|
1567
|
+
const labels = this.data.labels;
|
|
1568
|
+
this.state.status.errorMessage = labels.unknownPropertyValue.replace(/%s/, 'optionBehavior');
|
|
1359
1569
|
operation = 'sort';
|
|
1360
1570
|
orderArray = ['O', 'D', 'E'];
|
|
1361
1571
|
} else {
|
|
@@ -1387,6 +1597,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1387
1597
|
for (const option of options) {
|
|
1388
1598
|
if (!option.disabled) {
|
|
1389
1599
|
this.selectItem(option.id, true, true);
|
|
1600
|
+
this.checkAutoDisabled();
|
|
1390
1601
|
return;
|
|
1391
1602
|
}
|
|
1392
1603
|
}
|
|
@@ -1394,9 +1605,11 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1394
1605
|
|
|
1395
1606
|
private checkAutoDisabled() {
|
|
1396
1607
|
const state = this.state;
|
|
1397
|
-
const
|
|
1608
|
+
const isPartial = this.isPartial.value ?? this.isPartial;
|
|
1609
|
+
const doNotCheck = isPartial || this.props.disabled || !state.autoDisabled;
|
|
1610
|
+
const hasFetchedAllItems = this.hasFetchedAllItems.value ?? this.hasFetchedAllItems;
|
|
1398
1611
|
|
|
1399
|
-
if (doNotCheck || !
|
|
1612
|
+
if (doNotCheck || !hasFetchedAllItems) {
|
|
1400
1613
|
return;
|
|
1401
1614
|
}
|
|
1402
1615
|
|
|
@@ -1420,128 +1633,22 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1420
1633
|
}
|
|
1421
1634
|
|
|
1422
1635
|
private checkHideFilter() {
|
|
1423
|
-
|
|
1636
|
+
const params = this.props.params;
|
|
1637
|
+
if (params && params.hideFilter !== 'auto') {
|
|
1424
1638
|
return;
|
|
1425
1639
|
}
|
|
1426
1640
|
|
|
1427
1641
|
const state = this.state;
|
|
1642
|
+
const isPartial = this.isPartial.value ?? this.isPartial;
|
|
1428
1643
|
|
|
1429
|
-
if (state.multiple ||
|
|
1644
|
+
if (state.multiple || isPartial) {
|
|
1430
1645
|
state.hideFilter = false;
|
|
1431
1646
|
} else {
|
|
1432
|
-
state.hideFilter = state.totalAllOptions <= this.itemsPerPage;
|
|
1647
|
+
state.hideFilter = state.totalAllOptions <= this.data.itemsPerPage;
|
|
1433
1648
|
}
|
|
1434
1649
|
}
|
|
1435
1650
|
|
|
1436
1651
|
/* }}} */
|
|
1437
1652
|
/* }}} */
|
|
1438
|
-
/* {{{ watch */
|
|
1439
|
-
|
|
1440
|
-
@Watch('options')
|
|
1441
|
-
protected onOptionsChange(options: OptionValue[] = [], oldOptions: OptionValue[] = []) {
|
|
1442
|
-
if (JSON.stringify(options) === JSON.stringify(oldOptions)) {
|
|
1443
|
-
/* There is no real difference, only a change of reference */
|
|
1444
|
-
return;
|
|
1445
|
-
}
|
|
1446
|
-
this.cacheItem.clear();
|
|
1447
|
-
this.commit('isOpen', false);
|
|
1448
|
-
this.buildAllOptions(true);
|
|
1449
|
-
this.assertCorrectValue();
|
|
1450
|
-
this.buildSelectedOptions();
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
@Watch('childOptions')
|
|
1454
|
-
protected onChildOptionsChange(childOptions: OptionValue[] = [], oldChildOptions: OptionValue[] = []) {
|
|
1455
|
-
if (JSON.stringify(childOptions) === JSON.stringify(oldChildOptions)) {
|
|
1456
|
-
/* There is no real difference, only a change of reference */
|
|
1457
|
-
return;
|
|
1458
|
-
}
|
|
1459
|
-
this.cacheItem.clear();
|
|
1460
|
-
this.commit('isOpen', false);
|
|
1461
|
-
this.buildAllOptions(true);
|
|
1462
|
-
this.assertCorrectValue();
|
|
1463
|
-
this.buildSelectedOptions();
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
@Watch('value')
|
|
1467
|
-
protected onValueChange() {
|
|
1468
|
-
const value = typeof this.value === 'undefined' ? null : this.value;
|
|
1469
|
-
this.commit('internalValue', value);
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
@Watch('selectionIsExcluded')
|
|
1473
|
-
protected onSelectionExcludedChange() {
|
|
1474
|
-
this.commit('selectionIsExcluded', this.selectionIsExcluded);
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
@Watch('disabled')
|
|
1478
|
-
protected onDisabledChange() {
|
|
1479
|
-
this.commit('disabled', this.disabled);
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
@Watch('state.filteredOptions')
|
|
1483
|
-
protected onFilteredChange() {
|
|
1484
|
-
let areAllSelected = false;
|
|
1485
|
-
|
|
1486
|
-
if (this.hasAllItems) {
|
|
1487
|
-
const selectionIsExcluded = +this.state.selectionIsExcluded;
|
|
1488
|
-
// tslint:disable-next-line:no-bitwise
|
|
1489
|
-
areAllSelected = this.state.filteredOptions.every((item) => !!(+item.selected ^ selectionIsExcluded));
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
|
-
this.state.status.areAllSelected = areAllSelected;
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
@Watch('state.internalValue')
|
|
1496
|
-
protected onInternalValueChange() {
|
|
1497
|
-
this.buildSelectedOptions();
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
@Watch('state.allOptions')
|
|
1501
|
-
protected onAllOptionChange() {
|
|
1502
|
-
this.checkAutoSelect();
|
|
1503
|
-
this.checkAutoDisabled();
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
@Watch('state.totalAllOptions')
|
|
1507
|
-
protected onTotalAllOptionsChange() {
|
|
1508
|
-
this.checkHideFilter();
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
/* }}} */
|
|
1512
|
-
/* {{{ life cycles methods */
|
|
1513
|
-
|
|
1514
|
-
protected created() {
|
|
1515
|
-
const value = typeof this.value === 'undefined' ? null : this.value;
|
|
1516
|
-
|
|
1517
|
-
/* set initial value for non reactive attribute */
|
|
1518
|
-
this.requestId = 0;
|
|
1519
|
-
this.cacheRequest = new Map();
|
|
1520
|
-
|
|
1521
|
-
const stateParam = Object.assign({}, this.params);
|
|
1522
|
-
|
|
1523
|
-
if (stateParam.optionBehavior) {
|
|
1524
|
-
this.buildOptionBehavior(stateParam.optionBehavior, stateParam as SelecticStoreState);
|
|
1525
|
-
delete stateParam.optionBehavior;
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
this.state = Object.assign(this.state, stateParam, {
|
|
1529
|
-
internalValue: value,
|
|
1530
|
-
selectionIsExcluded: this.selectionIsExcluded,
|
|
1531
|
-
disabled: this.disabled,
|
|
1532
|
-
});
|
|
1533
|
-
|
|
1534
|
-
this.checkHideFilter();
|
|
1535
1653
|
|
|
1536
|
-
|
|
1537
|
-
this.changeTexts(this.texts);
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
this.addGroups(this.groups);
|
|
1541
|
-
this.buildAllOptions();
|
|
1542
|
-
this.assertCorrectValue();
|
|
1543
|
-
this.buildSelectedOptions();
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
|
-
/* }}} */
|
|
1547
|
-
}
|
|
1654
|
+
};
|