Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #15917: slim-select-pagination-bug-fix : fixed several bugs related to slim select #15918

Merged
merged 2 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions netbox/project-static/dist/netbox.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion netbox/project-static/dist/netbox.js.map

Large diffs are not rendered by default.

71 changes: 49 additions & 22 deletions netbox/project-static/src/select/api/apiSelect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ export class APISelect {
*/
private queryUrl: string = '';

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

private setSearchKey(event: Event) {
const { value: q } = event.target as HTMLInputElement;
this.searchKey = q
}

/**
* Add event listeners to this element and its dependencies so that when dependencies change
* this element's options are updated.
*/
private addEventListeners(): void {
// Create a debounced function to fetch options based on the search input value.
const fetcher = debounce((event: Event) => this.handleSearch(event), 300, false);
const fetcher = debounce((action:ApplyMethod, url: Nullable<string>) => this.handleSearch(action, url), 300, false);

// Query the API when the input value changes or a value is pasted.
this.slim.slim.search.input.addEventListener('keyup', event => {
// Only search when necessary keys are pressed.
if (!event.key.match(/^(Arrow|Enter|Tab).*/)) {
return fetcher(event);
this.setSearchKey(event);
return fetcher('replace', null);
}
});
this.slim.slim.search.input.addEventListener('paste', event => fetcher(event));
this.slim.slim.search.input.addEventListener('paste', event => {
this.setSearchKey(event);
return fetcher('replace', null);;
});

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

// When the scroll position is at bottom, fetch additional options.
this.base.addEventListener(`netbox.select.atbottom.${this.name}`, () =>
this.fetchOptions(this.more, 'merge'),
);
this.base.addEventListener(`netbox.select.atbottom.${this.name}`, () => {
if (this.more!=null) {
return fetcher('merge', this.more, )
}
});

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

private getUrl() {
var url = this.queryUrl
if (this.searchKey!=null) {
url = queryString.stringifyUrl({ url: this.queryUrl, query: { q : this.searchKey } })
}
return url
}

/**
* Query the NetBox API for this element's options.
*/
Expand All @@ -559,21 +582,25 @@ export class APISelect {
this.resetOptions();
return;
}
await this.fetchOptions(this.queryUrl, action);
const url = this.getUrl()
await this.fetchOptions(url, action);
}

/**
* Query the API for a specific search pattern and add the results to the available options.
*/
private async handleSearch(event: Event) {
const { value: q } = event.target as HTMLInputElement;
const url = queryString.stringifyUrl({ url: this.queryUrl, query: { q } });
if (!url.includes(`{{`)) {
await this.fetchOptions(url, 'merge');
this.slim.data.search(q);
this.slim.render();
private async handleSearch(action: ApplyMethod = 'merge', url: Nullable<string> ) {
if (url==null) {
url = this.getUrl()
}
return;
if (url.includes(`{{`)) {
return
}
await this.fetchOptions(url, action);
if (this.searchKey!=null) {
this.slim.data.search(this.searchKey);
}
this.slim.render();
}

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

if (this.atBottom && !atBottom) {
this.atBottom = false;
this.base.dispatchEvent(this.bottomEvent);
} else if (!this.atBottom && atBottom) {
this.atBottom = true;
this.atBottom = atBottom

if (this.atBottom) {
this.base.dispatchEvent(this.bottomEvent);
}
}
}

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