Skip to content

Commit bc06839

Browse files
[DURACOM-304] improve loading logic for cc-licenses section and dynamic-list
1 parent a3ebfc0 commit bc06839

7 files changed

Lines changed: 181 additions & 120 deletions

File tree

src/app/shared/form/builder/ds-dynamic-form-ui/models/list/dynamic-list.component.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@
6262
</div>
6363
<br>
6464
</div>
65+
</div>
6566

67+
<div class="d-flex justify-content-center" *ngIf="(showLoadMore$ | async)">
68+
<div *ngIf="(showLoadMore$ | async)" class="btn btn-link py-3" (click)="loadEntries()">{{'dynamic-list.load-more' | translate}}</div>
6669
</div>
70+
6771
</div>

src/app/shared/form/builder/ds-dynamic-form-ui/models/list/dynamic-list.component.ts

Lines changed: 73 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
4444
@Output() focus: EventEmitter<any> = new EventEmitter<any>();
4545

4646
public items: ListItem[][] = [];
47-
protected optionsList: VocabularyEntry[];
47+
public showLoadMore$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
48+
protected optionsList: VocabularyEntry[] = [];
49+
private nextPageInfo: PageInfo;
4850

4951
constructor(private vocabularyService: VocabularyService,
5052
private cdr: ChangeDetectorRef,
@@ -60,7 +62,7 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
6062
*/
6163
ngOnInit() {
6264
if (this.model.vocabularyOptions && hasValue(this.model.vocabularyOptions.name)) {
63-
this.setOptionsFromVocabulary();
65+
this.initOptionsFromVocabulary();
6466
}
6567
}
6668

@@ -107,70 +109,18 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
107109
/**
108110
* Setting up the field options from vocabulary
109111
*/
110-
protected setOptionsFromVocabulary() {
112+
protected initOptionsFromVocabulary() {
111113
if (this.model.vocabularyOptions.name && this.model.vocabularyOptions.name.length > 0) {
112114
const listGroup = this.group.controls[this.model.id] as UntypedFormGroup;
113115
if (this.model.repeatable && this.model.required) {
114116
listGroup.addValidators(this.hasAtLeastOneVocabularyEntry());
115117
}
116118

117-
const initialPageInfo: PageInfo = new PageInfo({
119+
this.nextPageInfo = new PageInfo({
118120
elementsPerPage: 20, currentPage: 1,
119121
} as PageInfo);
120122

121-
this.vocabularyService.getVocabularyEntries(this.model.vocabularyOptions, initialPageInfo).pipe(
122-
getFirstSucceededRemoteDataPayload(),
123-
expand((entries: PaginatedList<VocabularyEntry>) => {
124-
if (entries.pageInfo.currentPage < entries.pageInfo.totalPages) {
125-
const nextPageInfo: PageInfo = new PageInfo({
126-
elementsPerPage: 20, currentPage: entries.pageInfo.currentPage + 1,
127-
} as PageInfo);
128-
return this.vocabularyService.getVocabularyEntries(this.model.vocabularyOptions, nextPageInfo).pipe(
129-
getFirstSucceededRemoteDataPayload()
130-
);
131-
} else {
132-
return EMPTY;
133-
}
134-
}),
135-
reduce((acc: VocabularyEntry[], entries: PaginatedList<VocabularyEntry>) => acc.concat(entries.page), [])
136-
).subscribe((allEntries: VocabularyEntry[]) => {
137-
let groupCounter = 0;
138-
let itemsPerGroup = 0;
139-
let tempList: ListItem[] = [];
140-
this.optionsList = allEntries;
141-
// Make a list of available options (checkbox/radio) and split in groups of 'model.groupLength'
142-
allEntries.forEach((option: VocabularyEntry, key: number) => {
143-
const value = option.authority || option.value;
144-
const checked: boolean = isNotEmpty(findKey(
145-
this.model.value,
146-
(v) => v.value === option.value));
147-
148-
const item: ListItem = {
149-
id: `${this.model.id}_${value}`,
150-
label: option.display,
151-
value: checked,
152-
index: key
153-
};
154-
if (this.model.repeatable) {
155-
this.formBuilderService.addFormGroupControl(listGroup, (this.model as DynamicListCheckboxGroupModel), new DynamicCheckboxModel(item));
156-
} else {
157-
(this.model as DynamicListRadioGroupModel).options.push({
158-
label: item.label,
159-
value: option
160-
});
161-
}
162-
tempList.push(item);
163-
itemsPerGroup++;
164-
this.items[groupCounter] = tempList;
165-
if (itemsPerGroup === this.model.groupLength) {
166-
groupCounter++;
167-
itemsPerGroup = 0;
168-
tempList = [];
169-
}
170-
});
171-
this.cdr.markForCheck();
172-
});
173-
123+
this.loadEntries(listGroup);
174124
}
175125
}
176126

@@ -183,4 +133,70 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
183133
};
184134
}
185135

136+
/**
137+
* Update current page state to keep track of which one to load next
138+
* @param response
139+
*/
140+
setPaginationInfo(response: PaginatedList<VocabularyEntry>) {
141+
if (response.pageInfo.currentPage < response.pageInfo.totalPages) {
142+
this.nextPageInfo = Object.assign(new PageInfo(), response.pageInfo, { currentPage: response.currentPage + 1 });
143+
this.showLoadMore$.next(true);
144+
} else {
145+
this.showLoadMore$.next(false);
146+
}
147+
}
148+
149+
/**
150+
* Load entries page
151+
*
152+
* @param listGroup
153+
*/
154+
loadEntries(listGroup?: UntypedFormGroup) {
155+
if (!hasValue(listGroup)) {
156+
listGroup = this.group.controls[this.model.id] as UntypedFormGroup;
157+
}
158+
159+
this.vocabularyService.getVocabularyEntries(this.model.vocabularyOptions, this.nextPageInfo).pipe(
160+
getFirstSucceededRemoteDataPayload(),
161+
tap((response) => this.setPaginationInfo(response)),
162+
map(entries => entries.page),
163+
).subscribe((allEntries: VocabularyEntry[]) => {
164+
this.optionsList = [...this.optionsList, ...allEntries];
165+
let groupCounter = (this.items.length > 0) ? (this.items.length - 1) : 0;
166+
let itemsPerGroup = 0;
167+
let tempList: ListItem[] = [];
168+
169+
// Make a list of available options (checkbox/radio) and split in groups of 'model.groupLength'
170+
allEntries.forEach((option: VocabularyEntry, key: number) => {
171+
const value = option.authority || option.value;
172+
const checked: boolean = isNotEmpty(findKey(
173+
this.model.value,
174+
(v) => v?.value === option.value));
175+
176+
const item: ListItem = {
177+
id: `${this.model.id}_${value}`,
178+
label: option.display,
179+
value: checked,
180+
index: key,
181+
};
182+
if (this.model.repeatable) {
183+
this.formBuilderService.addFormGroupControl(listGroup, (this.model as DynamicListCheckboxGroupModel), new DynamicCheckboxModel(item));
184+
} else {
185+
(this.model as DynamicListRadioGroupModel).options.push({
186+
label: item.label,
187+
value: option,
188+
});
189+
}
190+
tempList.push(item);
191+
itemsPerGroup++;
192+
this.items[groupCounter] = tempList;
193+
if (itemsPerGroup === this.model.groupLength) {
194+
groupCounter++;
195+
itemsPerGroup = 0;
196+
tempList = [];
197+
}
198+
});
199+
this.cdr.markForCheck();
200+
});
201+
}
186202
}

src/app/submission/sections/cc-license/submission-section-cc-licenses.component.html

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,39 @@
1-
<div class="mb-4 ccLicense-select">
2-
<ds-select
3-
[disabled]="!submissionCcLicenses">
4-
5-
<ng-container class="selection">
6-
<span *ngIf="!submissionCcLicenses">
7-
<ds-themed-loading></ds-themed-loading>
8-
</span>
9-
<span *ngIf="getSelectedCcLicense()">
10-
{{ getSelectedCcLicense().name }}
11-
</span>
12-
<span *ngIf="submissionCcLicenses && !getSelectedCcLicense()">
13-
<ng-container *ngIf="storedCcLicenseLink">
14-
{{ 'submission.sections.ccLicense.change' | translate }}
15-
</ng-container>
16-
<ng-container *ngIf="!storedCcLicenseLink">
17-
{{ 'submission.sections.ccLicense.select' | translate }}
18-
</ng-container>
19-
</span>
20-
</ng-container>
21-
22-
<ng-container class="menu">
23-
<button *ngIf="submissionCcLicenses?.length == 0"
24-
class="dropdown-item disabled">
25-
{{ 'submission.sections.ccLicense.none' | translate }}
26-
</button>
27-
<button *ngFor="let license of submissionCcLicenses"
28-
class="dropdown-item"
29-
(click)="selectCcLicense(license)">
30-
{{ license.name }}
31-
</button>
32-
</ng-container>
33-
34-
</ds-select>
35-
</div>
1+
@if (submissionCcLicenses) {
2+
<div class="mb-4 ccLicense-select">
3+
<div ngbDropdown>
4+
<input id="cc-license-dropdown"
5+
class="form-control"
6+
[(ngModel)]="selectedCcLicense.name"
7+
placeholder="{{ !storedCcLicenseLink ? ('submission.sections.ccLicense.select' | translate) : ('submission.sections.ccLicense.change' | translate)}}"
8+
[ngModelOptions]="{standalone: true}"
9+
ngbDropdownToggle
10+
role="combobox"
11+
#script="ngModel">
12+
<div ngbDropdownMenu aria-labelledby="cc-license-dropdown" class="w-100 scrollable-menu"
13+
role="menu"
14+
infiniteScroll
15+
(scroll)="onScroll($event)"
16+
[infiniteScrollDistance]="5"
17+
[infiniteScrollThrottle]="300"
18+
[infiniteScrollUpDistance]="1.5"
19+
[fromRoot]="true"
20+
[scrollWindow]="false">
21+
22+
@if(submissionCcLicenses?.length === 0) {
23+
<button class="dropdown-item disabled">
24+
{{ 'submission.sections.ccLicense.none' | translate }}
25+
</button>
26+
} @else {
27+
@for(license of submissionCcLicenses; track license.id) {
28+
<button class="dropdown-item" (click)="selectCcLicense(license)">
29+
{{ license.name }}
30+
</button>
31+
}
32+
}
33+
</div>
34+
</div>
35+
</div>
36+
}
3637

3738
<ng-container *ngIf="getSelectedCcLicense()">
3839

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
11
.options-select-menu {
22
max-height: 25vh;
33
}
4+
5+
.ccLicense-select {
6+
width: fit-content;
7+
}
8+
9+
.scrollable-menu {
10+
height: auto;
11+
max-height: var(--ds-dropdown-menu-max-height);
12+
overflow-x: hidden;
13+
}

src/app/submission/sections/cc-license/submission-section-cc-licenses.component.spec.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,7 @@ describe('SubmissionSectionCcLicensesComponent', () => {
215215
});
216216

217217
it('should display the selected cc license', () => {
218-
expect(
219-
de.query(By.css('.ccLicense-select ds-select button.selection')).nativeElement.innerText
220-
).toContain('test license name 2');
218+
expect(component.selectedCcLicense.name).toContain('test license name 2');
221219
});
222220

223221
it('should display all field labels of the selected cc license only', () => {

0 commit comments

Comments
 (0)