From 88f2735087f1d370e6d4758ff1e8854211979587 Mon Sep 17 00:00:00 2001 From: Nancy Yang Date: Fri, 3 May 2024 13:22:29 -0400 Subject: [PATCH] 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 --- netbox/project-static/dist/netbox.js | Bin 529484 -> 529723 bytes netbox/project-static/dist/netbox.js.map | Bin 450007 -> 450193 bytes .../src/select/api/apiSelect.ts | 71 ++++++++++++------ 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index b6dd11eda19dccd93a677378ba6a93e09e6ea814..d5b0ce28a4066784c132f4a98853ecb03606f227 100644 GIT binary patch delta 1609 zcmah}U1%It6lS-Zd!lI~+BQUEnq;h-89P}LMXA{g!O&Q#)z*ly&=NAc_a=Mk>}+yp zCNaiEiVsButG4EX6@Ngn5sG3S=D{d5>4S)nh)^)LjedEIy8WRJ)Yx5BQNAG97A)nCnX#C>xkMUbo?m~7 zoUQ9|nEQN%UCjt>#$xWwq-lAqCohUdMxReh-Nw*?9E4Am-ns31USMY*~{bbsPUajJ7)r|aj zl{~g(Q z<21cJFH+pjS{`MNs?5wNS~4lGuabM8$O}xVl?sf;QPEk_hfgp?P1h`stJ2`|8iG4T)Pa7HM$6VgIRb8eZ?$Xgu5s&4IDj| zty2G)6EL)UdvIHxIQ74H9UiICk)$h#pDb|N)|IBb_kbv;MPu(?EqHn8KOl}#zu`0- zN}%{-XW(#x@;+6*En%lzJOiH)%9F|V33=-SX!cK?g?D3!Yng@Pc)UCdM^V{NvoIV) zKYR{)G1{QMI0tVa&cFFSjMkxtork5M85^HLN*|n6|+B*CbzbEpz!Ac1n@~`XA)EwQHmksTY xB4z6aG$Hf84QRpjGqeHQi4oy%U+#`;x|_DUnRWRjJH+i1(PHyIya5;P{}+@QIUxW5 delta 1411 zcmZ`(U1%It6lT-SJqd}5v^Mo;iW2x{iSjJYR|kugrZ zvSz0GLcV)TXonv06|8;Km>pzRP_0YezlM5yEM7Piwk})7j?Ktx z1oKi^x8(loNVFHZZgF|-Ix774d0-Wt-(7bfBYvse=#^LReKyQwY8ArAi6&=P(Y_${ z6B_D}zu!a?2d61vcJd*S%^R6TZd8rxrb=Y&ul7B=81ZMOFcV~;!mEWM&-z%U@lE;e z8cNEQo2X|*0aYVs@ho*pOxVQJwV4^sn4Q%hmfPaZat*UySvZ%hjeVT;8f~a>iEp7x zJ?V6+$PYKJ+3a&6)h|;&qh2|18+B)lfmEHMiKf#NhzpC?6Z1Qy{b9dw5Jx zLydta6~Y`{1bGG{z1^Xe6JKyADS}VV!Txqr&_Z7rKjl(8*_RVEKghF;IOBd_#i(nf`87#a9q_fRD+{&K@aP4wFW!nmo@kd395QODObOSox$XX@KH?pbxSy* z1eNfXs%%NZ+hKI-0_0V6K?L{$ys!L%-#&)1E`_{)36{fqtzCqy9Gr&(3aie;VuzCb zi|}U1dG-nzN?p7HxsEQy;t73(8#y8W{8lwi1c|HAsf8rB?m$8&Zve>QyD+Gb+FjTa zGJLfLscoti@`YC*BNy&~9+>yw*dyv;pI(Rj_Gq$2p_YOZjxqRg9sX{!BE-ijJR+^MCjM#wYuCT4_!&uRFkBev((9dQ}t76KkQPWy<@;< zWPrNc@|dnuf0P^0Nxf!D$R?u{?c7IbdMX+J0pCYw2$jAN;!R3$Eji%=-w{ElW*Tff zK(#7`wrfKWcfG$AJLs*lgTUC6pWyH2V`LlD2a$Sic0r2D2WTh^=p~Q)nHx&qmZGar zue=b5bDDc#v3kj;j9N$U6^EMmG`A3aLb=`_(l;ACp?Ene&USa*g&Y5U=dXcYHG6k) z3&VN)X9(*BW8XK66U91JwtIaj723(!FSvd4RvgP%QO$CkT%N(j=wrR<{*;dRcbB zmI_y>t&d7Dh@hf{omL`7BR=)^q35-F}xs33RPLra&J7@tW88MGN?GRMf(pR z#$8lPX~EGPBie!?Su;_T2LE>l%MQzGB23X{qgwlIB3Pft>@0mfg4e+`JFF;YdR3-3 z3l(j@Ko6a*LZZ9CTTRt01pfwe3}ow?`tM?G = 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. @@ -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) => 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 => @@ -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. */ @@ -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 ) { + 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(); } /** @@ -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.atBottom = atBottom + + if (this.atBottom) { this.base.dispatchEvent(this.bottomEvent); - } else if (!this.atBottom && atBottom) { - this.atBottom = true; - this.base.dispatchEvent(this.bottomEvent); - } + } } /** @@ -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); }