Skip to content

Commit a544b55

Browse files
Fixes #15917: slim-select-pagination-bug-fix : fixed several bugs related to slim select (#15918)
* slim-select-pagination-bug-fix : fixed several bugs related to slim select search box gui element 1. If user enters a search text in the filter text box, the user will not be able to scroll to the next page. That is the user will only be able to see the first page of returned item with a none empty search string. 2. User will not be able to select an item returned from search query if user clicks reload after a dynami search. When the user is able to load a second page, the user will be able to select an item from the third+ page if previous bug is fixed. * Recompile static assets --------- Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
1 parent 53e1ab5 commit a544b55

File tree

3 files changed

+54
-27
lines changed

3 files changed

+54
-27
lines changed

netbox/project-static/dist/netbox.js

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

netbox/project-static/dist/netbox.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

netbox/project-static/src/select/api/apiSelect.ts

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ export class APISelect {
140140
*/
141141
private queryUrl: string = '';
142142

143+
/**
144+
* Interal state variable used to remember search key entered by user for "Filter" search box
145+
*/
146+
private searchKey: Nullable<string> = null;
143147
/**
144148
* Scroll position of options is at the bottom of the list, or not. Used to determine if
145149
* additional options should be fetched from the API.
@@ -359,30 +363,41 @@ export class APISelect {
359363
this.slim.enable();
360364
}
361365

366+
private setSearchKey(event: Event) {
367+
const { value: q } = event.target as HTMLInputElement;
368+
this.searchKey = q
369+
}
370+
362371
/**
363372
* Add event listeners to this element and its dependencies so that when dependencies change
364373
* this element's options are updated.
365374
*/
366375
private addEventListeners(): void {
367376
// Create a debounced function to fetch options based on the search input value.
368-
const fetcher = debounce((event: Event) => this.handleSearch(event), 300, false);
377+
const fetcher = debounce((action:ApplyMethod, url: Nullable<string>) => this.handleSearch(action, url), 300, false);
369378

370379
// Query the API when the input value changes or a value is pasted.
371380
this.slim.slim.search.input.addEventListener('keyup', event => {
372381
// Only search when necessary keys are pressed.
373382
if (!event.key.match(/^(Arrow|Enter|Tab).*/)) {
374-
return fetcher(event);
383+
this.setSearchKey(event);
384+
return fetcher('replace', null);
375385
}
376386
});
377-
this.slim.slim.search.input.addEventListener('paste', event => fetcher(event));
387+
this.slim.slim.search.input.addEventListener('paste', event => {
388+
this.setSearchKey(event);
389+
return fetcher('replace', null);;
390+
});
378391

379392
// Watch every scroll event to determine if the scroll position is at bottom.
380393
this.slim.slim.list.addEventListener('scroll', () => this.handleScroll());
381394

382395
// When the scroll position is at bottom, fetch additional options.
383-
this.base.addEventListener(`netbox.select.atbottom.${this.name}`, () =>
384-
this.fetchOptions(this.more, 'merge'),
385-
);
396+
this.base.addEventListener(`netbox.select.atbottom.${this.name}`, () => {
397+
if (this.more!=null) {
398+
return fetcher('merge', this.more, )
399+
}
400+
});
386401

387402
// When the base select element is disabled or enabled, properly disable/enable this instance.
388403
this.base.addEventListener(`netbox.select.disabled.${this.name}`, event =>
@@ -551,6 +566,14 @@ export class APISelect {
551566
}
552567
}
553568

569+
private getUrl() {
570+
var url = this.queryUrl
571+
if (this.searchKey!=null) {
572+
url = queryString.stringifyUrl({ url: this.queryUrl, query: { q : this.searchKey } })
573+
}
574+
return url
575+
}
576+
554577
/**
555578
* Query the NetBox API for this element's options.
556579
*/
@@ -559,21 +582,25 @@ export class APISelect {
559582
this.resetOptions();
560583
return;
561584
}
562-
await this.fetchOptions(this.queryUrl, action);
585+
const url = this.getUrl()
586+
await this.fetchOptions(url, action);
563587
}
564588

565589
/**
566590
* Query the API for a specific search pattern and add the results to the available options.
567591
*/
568-
private async handleSearch(event: Event) {
569-
const { value: q } = event.target as HTMLInputElement;
570-
const url = queryString.stringifyUrl({ url: this.queryUrl, query: { q } });
571-
if (!url.includes(`{{`)) {
572-
await this.fetchOptions(url, 'merge');
573-
this.slim.data.search(q);
574-
this.slim.render();
592+
private async handleSearch(action: ApplyMethod = 'merge', url: Nullable<string> ) {
593+
if (url==null) {
594+
url = this.getUrl()
575595
}
576-
return;
596+
if (url.includes(`{{`)) {
597+
return
598+
}
599+
await this.fetchOptions(url, action);
600+
if (this.searchKey!=null) {
601+
this.slim.data.search(this.searchKey);
602+
}
603+
this.slim.render();
577604
}
578605

579606
/**
@@ -586,13 +613,11 @@ export class APISelect {
586613
Math.floor(this.slim.slim.list.scrollTop) + this.slim.slim.list.offsetHeight ===
587614
this.slim.slim.list.scrollHeight;
588615

589-
if (this.atBottom && !atBottom) {
590-
this.atBottom = false;
591-
this.base.dispatchEvent(this.bottomEvent);
592-
} else if (!this.atBottom && atBottom) {
593-
this.atBottom = true;
616+
this.atBottom = atBottom
617+
618+
if (this.atBottom) {
594619
this.base.dispatchEvent(this.bottomEvent);
595-
}
620+
}
596621
}
597622

598623
/**
@@ -994,7 +1019,9 @@ export class APISelect {
9941019
['btn', 'btn-sm', 'btn-ghost-dark'],
9951020
[createElement('i', null, ['mdi', 'mdi-reload'])],
9961021
);
997-
refreshButton.addEventListener('click', () => this.loadData());
1022+
// calling this.loadData() will prevent first page of returned items
1023+
// with non-null search key inplace not selectable
1024+
refreshButton.addEventListener('click', () => this.handleSearch('replace', null));
9981025
refreshButton.type = 'button';
9991026
this.slim.slim.search.container.appendChild(refreshButton);
10001027
}

0 commit comments

Comments
 (0)