1
1
import TomSelect from "tom-select" ;
2
- import { setValidity } from "solidus_admin/utils" ;
2
+ import { setValidity , parseLinkHeader } from "solidus_admin/utils" ;
3
3
4
4
class SolidusSelect extends HTMLSelectElement {
5
5
static observedAttributes = [ "synced" ] ;
@@ -21,7 +21,7 @@ class SolidusSelect extends HTMLSelectElement {
21
21
22
22
this . setAttribute ( "hidden" , "true" ) ;
23
23
this . setAttribute ( "aria-hidden" , "true" ) ;
24
-
24
+ this . fixDropdownScroll ( ) ;
25
25
setValidity ( this , this . dataset . errorMessage ) ;
26
26
}
27
27
@@ -46,7 +46,7 @@ class SolidusSelect extends HTMLSelectElement {
46
46
dropdownContentClass : "dropdown-content" ,
47
47
optionClass : "option" ,
48
48
wrapperClass : "wrapper" ,
49
- maxOptions : 500 ,
49
+ maxOptions : null ,
50
50
refreshThrottle : 0 ,
51
51
plugins : {
52
52
no_active_items : true ,
@@ -55,18 +55,6 @@ class SolidusSelect extends HTMLSelectElement {
55
55
className : "remove-button"
56
56
} ,
57
57
} ,
58
- onItemAdd : function ( ) {
59
- if ( ! originalSelect . multiple || ! this . isOpen ) return ;
60
-
61
- this . setTextboxValue ( "" ) ;
62
- this . refreshOptions ( ) ;
63
- } ,
64
- onLoad : function ( ) {
65
- originalSelect . tomselect . setValue (
66
- originalSelect . getAttribute ( "data-selected" ) ?. split ( "," ) || [ ] ,
67
- true
68
- ) ;
69
- } ,
70
58
onType : function ( ) {
71
59
if ( ! originalSelect . multiple && ! this . currentResults . items . length ) {
72
60
this . setTextboxValue ( "" ) ;
@@ -76,47 +64,82 @@ class SolidusSelect extends HTMLSelectElement {
76
64
} ;
77
65
78
66
if ( originalSelect . getAttribute ( "data-src" ) ) {
79
- settings . load = originalSelect . loadOnce . bind ( originalSelect ) ;
67
+ settings . plugins . virtual_scroll = true
68
+ settings . firstUrl = ( ) => originalSelect . getAttribute ( "data-src" ) ;
69
+ settings . load = originalSelect . loadOptions . bind ( originalSelect ) ;
70
+ settings . shouldLoad = ( query ) => query . length > 1
80
71
settings . preload = true ;
81
72
settings . valueField = originalSelect . getAttribute ( "data-option-value-field" ) || "id" ;
82
73
settings . labelField = originalSelect . getAttribute ( "data-option-label-field" ) || "name" ;
83
74
settings . searchField = [ settings . labelField ] ;
84
75
settings . render = {
85
76
loading : function ( ) {
86
77
return "<div class='loading'>Loading</div>" ;
78
+ } ,
79
+ loading_more : function ( ) {
80
+ return "<div class='loading-more disabled'>Loading...</div>" ;
87
81
}
88
- }
82
+ } ;
89
83
}
90
84
91
85
return settings ;
92
86
}
93
87
94
- // Fetch all options from remote source and remove #load callback
95
- // https://tom-select.js.org/examples/remote/
96
- async loadOnce ( query , callback ) {
97
- // Avoid queueing more load requests (e.g. searching while options are still loading) if there's one already running
98
- if ( this . tomselect . loading > 1 ) {
99
- callback ( ) ;
100
- return ;
88
+ buildUrl ( query ) {
89
+ const url = new URL ( this . tomselect . getUrl ( query ) ) ;
90
+ if ( ! query ) return url ;
91
+
92
+ url . searchParams . set ( this . getAttribute ( "data-query-param" ) , query ) ;
93
+ return url . toString ( ) ;
94
+ }
95
+
96
+ // Fetch all options from remote source and setup pagination if needed
97
+ async loadOptions ( query , callback ) {
98
+ const { options, next } = await this . fetchOptions ( query ) ;
99
+ if ( next ) {
100
+ this . tomselect . setNextUrl ( query , next ) ;
101
101
}
102
102
103
- const options = await this . fetchOptions ( ) ;
104
103
callback ( options ) ;
105
- this . tomselect . settings . load = null ;
106
104
}
107
105
108
106
// Fetch options from remote source. If options data is nested in json response, specify path to it with "data-json-path"
109
107
// E.g. https://whatcms.org/API/List data is deep nested in json response: `{ result: { list: [...] } }`, so
110
108
// in order to access it, specify attributes as follows:
111
109
// "data-src"="https://whatcms.org/API/List"
112
110
// "data-json-path"="result.list"
113
- async fetchOptions ( ) {
111
+ async fetchOptions ( query ) {
114
112
const dataPath = this . getAttribute ( "data-json-path" ) ;
115
- const response = await fetch ( this . getAttribute ( "data-src" ) ) ;
113
+ const response = await fetch ( this . buildUrl ( query ) , { headers : { "Accept" : "application/json" } } ) ;
114
+ const next = parseLinkHeader ( response . headers . get ( "Link" ) ) . next ;
116
115
const json = await response . json ( ) ;
117
- if ( ! dataPath ) return json ;
118
116
119
- return dataPath . split ( '.' ) . reduce ( ( acc , key ) => acc && acc [ key ] , json ) ;
117
+ let options ;
118
+ if ( ! dataPath ) {
119
+ options = json ;
120
+ } else {
121
+ options = dataPath . split ( '.' ) . reduce ( ( acc , key ) => acc && acc [ key ] , json ) ;
122
+ }
123
+
124
+ return { options, next } ;
125
+ }
126
+
127
+ fixDropdownScroll ( ) {
128
+ // https://github.com/orchidjs/tom-select/issues/556
129
+ // https://github.com/orchidjs/tom-select/issues/867
130
+ this . patch ( "onOptionSelect" ) ;
131
+ this . patch ( "loadCallback" ) ;
132
+ }
133
+
134
+ patch ( fnName ) {
135
+ const originalFn = this . tomselect [ fnName ] ;
136
+ this . tomselect . hook ( "instead" , fnName , function ( ) {
137
+ const originalScrollToOption = this . scrollToOption ;
138
+
139
+ this . scrollToOption = ( ) => { } ;
140
+ originalFn . apply ( this , arguments ) ;
141
+ this . scrollToOption = originalScrollToOption ;
142
+ } ) ;
120
143
}
121
144
}
122
145
0 commit comments