]>
Commit | Line | Data |
---|---|---|
18532680 DC |
1 | /* |
2 | * This is a global search field | |
3 | * it loads the /cluster/resources on focus | |
4 | * and displays the result in a floating grid | |
5 | * | |
6 | * it filters and sorts the objects by the algorithm in | |
7 | * the customFilter function | |
8 | * | |
9 | * also it does accept key up/down and enter for input | |
10 | * and it opens to ctrl+shift+f and ctrl+space | |
11 | */ | |
12 | Ext.define('PVE.form.GlobalSearchField', { | |
13 | extend: 'Ext.form.field.Text', | |
14 | alias: 'widget.pveGlobalSearchField', | |
15 | ||
16 | emptyText: gettext('Search'), | |
17 | enableKeyEvents: true, | |
18 | selectOnFocus: true, | |
19 | padding: '0 5 0 5', | |
20 | ||
21 | grid: { | |
22 | xtype: 'gridpanel', | |
23 | focusOnToFront: false, | |
24 | floating: true, | |
e7ade592 | 25 | emptyText: Proxmox.Utils.noneText, |
18532680 | 26 | width: 600, |
b35f19a3 | 27 | height: 400, |
18532680 DC |
28 | scrollable: { |
29 | xtype: 'scroller', | |
30 | y: true, | |
31 | x:false | |
32 | }, | |
33 | store: { | |
34 | model: 'PVEResources', | |
35 | proxy:{ | |
36 | type: 'pve', | |
37 | url: '/api2/extjs/cluster/resources' | |
38 | } | |
39 | }, | |
40 | plugins: { | |
41 | ptype: 'bufferedrenderer', | |
42 | trailingBufferZone: 20, | |
43 | leadingBufferZone: 20 | |
44 | }, | |
45 | ||
46 | hideMe: function() { | |
47 | var me = this; | |
48 | me.hasFocus = false; | |
49 | if (!me.textfield.hasFocus) { | |
50 | me.hide(); | |
51 | } | |
52 | }, | |
53 | ||
54 | setFocus: function() { | |
55 | var me = this; | |
56 | me.hasFocus = true; | |
57 | }, | |
58 | ||
59 | listeners: { | |
60 | rowclick: function(grid, record) { | |
61 | var me = this; | |
62 | me.textfield.selectAndHide(record.id); | |
63 | }, | |
64 | /* because of lint */ | |
65 | focusleave: { | |
66 | fn: 'hideMe' | |
67 | }, | |
68 | focusenter: 'setFocus' | |
69 | }, | |
70 | ||
71 | columns: [ | |
72 | { | |
73 | text: gettext('Type'), | |
74 | dataIndex: 'type', | |
75 | width: 100, | |
76 | renderer: PVE.Utils.render_resource_type | |
77 | }, | |
78 | { | |
79 | text: gettext('Description'), | |
80 | flex: 1, | |
81 | dataIndex: 'text' | |
82 | }, | |
83 | { | |
84 | text: gettext('Node'), | |
85 | dataIndex: 'node' | |
86 | }, | |
87 | { | |
88 | text: gettext('Pool'), | |
89 | dataIndex: 'pool' | |
90 | } | |
91 | ] | |
92 | }, | |
93 | ||
94 | customFilter: function(item) { | |
95 | var me = this; | |
96 | var match = 0; | |
97 | var fieldArr = []; | |
98 | var i,j, fields; | |
99 | ||
100 | // different types of objects have different fields to search | |
101 | // for example, a node will never have a pool and vice versa | |
102 | switch (item.data.type) { | |
103 | case 'pool': fieldArr = ['type', 'pool', 'text']; break; | |
104 | case 'node': fieldArr = ['type', 'node', 'text']; break; | |
105 | case 'storage': fieldArr = ['type', 'pool', 'node', 'storage']; break; | |
106 | default: fieldArr = ['name', 'type', 'node', 'pool', 'vmid']; | |
107 | } | |
108 | if (me.filterVal === '') { | |
109 | item.data.relevance = 0; | |
110 | return true; | |
111 | } | |
112 | ||
113 | // all text is case insensitive and each word is | |
114 | // searched alone | |
115 | // for every partial match, the row gets | |
116 | // 1 match point, for every exact match | |
117 | // it gets 2 points | |
118 | // | |
119 | // results gets sorted by points (descending) | |
120 | fields = me.filterVal.split(/\s+/); | |
121 | for(i = 0; i < fieldArr.length; i++) { | |
122 | var v = item.data[fieldArr[i]]; | |
123 | if (v !== undefined) { | |
124 | v = v.toString().toLowerCase(); | |
125 | for(j = 0; j < fields.length; j++) { | |
126 | if (v.indexOf(fields[j]) !== -1) { | |
127 | match++; | |
128 | if(v === fields[j]) { | |
129 | match++; | |
130 | } | |
131 | } | |
132 | } | |
133 | } | |
134 | } | |
135 | // give the row the 'relevance' value | |
136 | item.data.relevance = match; | |
137 | return (match > 0); | |
138 | }, | |
139 | ||
140 | updateFilter: function(field, newValue, oldValue) { | |
141 | var me = this; | |
142 | // parse input and filter store, | |
143 | // show grid | |
144 | me.grid.store.filterVal = newValue.toLowerCase().trim(); | |
145 | me.grid.store.clearFilter(true); | |
146 | me.grid.store.filterBy(me.customFilter); | |
147 | me.grid.getSelectionModel().select(0); | |
148 | }, | |
149 | ||
150 | selectAndHide: function(id) { | |
151 | var me = this; | |
152 | me.tree.selectById(id); | |
153 | me.grid.hide(); | |
154 | me.setValue(''); | |
155 | me.blur(); | |
156 | }, | |
157 | ||
158 | onKey: function(field, e) { | |
159 | var me = this; | |
160 | var key = e.getKey(); | |
161 | ||
162 | switch(key) { | |
163 | case Ext.event.Event.ENTER: | |
164 | // go to first entry if there is one | |
165 | if (me.grid.store.getCount() > 0) { | |
166 | me.selectAndHide(me.grid.getSelection()[0].data.id); | |
167 | } | |
168 | break; | |
169 | case Ext.event.Event.UP: | |
170 | me.grid.getSelectionModel().selectPrevious(); | |
171 | break; | |
172 | case Ext.event.Event.DOWN: | |
173 | me.grid.getSelectionModel().selectNext(); | |
174 | break; | |
175 | case Ext.event.Event.ESC: | |
176 | me.grid.hide(); | |
177 | me.blur(); | |
178 | break; | |
179 | } | |
180 | }, | |
181 | ||
182 | loadValues: function(field) { | |
183 | var me = this; | |
184 | var records = []; | |
185 | ||
186 | me.hasFocus = true; | |
187 | me.grid.textfield = me; | |
188 | me.grid.store.load(); | |
189 | me.grid.showBy(me, 'tl-bl'); | |
190 | }, | |
191 | ||
192 | hideGrid: function() { | |
193 | var me = this; | |
194 | ||
195 | me.hasFocus = false; | |
196 | if (!me.grid.hasFocus) { | |
197 | me.grid.hide(); | |
198 | } | |
199 | }, | |
200 | ||
201 | listeners: { | |
202 | change: { | |
203 | fn: 'updateFilter', | |
204 | buffer: 250 | |
205 | }, | |
206 | specialkey: 'onKey', | |
207 | focusenter: 'loadValues', | |
208 | focusleave: { | |
209 | fn: 'hideGrid', | |
210 | delay: 100 | |
211 | } | |
212 | }, | |
213 | ||
214 | toggleFocus: function() { | |
215 | var me = this; | |
216 | if (!me.hasFocus) { | |
217 | me.focus(); | |
218 | } else { | |
219 | me.blur(); | |
220 | } | |
221 | }, | |
222 | ||
223 | initComponent: function() { | |
224 | var me = this; | |
225 | ||
226 | if (!me.tree) { | |
227 | throw "no tree given"; | |
228 | } | |
229 | ||
230 | me.grid = Ext.create(me.grid); | |
231 | ||
232 | me.callParent(); | |
233 | ||
234 | /*jslint confusion: true*/ | |
235 | /*because shift is also a function*/ | |
236 | // bind ctrl+shift+f and ctrl+space | |
237 | // to open/close the search | |
238 | me.keymap = new Ext.KeyMap({ | |
239 | target: Ext.get(document), | |
240 | binding: [{ | |
241 | key:'F', | |
242 | ctrl: true, | |
243 | shift: true, | |
244 | fn: me.toggleFocus, | |
245 | scope: me | |
246 | },{ | |
247 | key:' ', | |
248 | ctrl: true, | |
249 | fn: me.toggleFocus, | |
250 | scope: me | |
251 | }] | |
252 | }); | |
253 | ||
254 | // always select first item and | |
255 | // sort by relevance after load | |
256 | me.mon(me.grid.store, 'load', function() { | |
257 | me.grid.getSelectionModel().select(0); | |
258 | me.grid.store.sort({ | |
259 | property: 'relevance', | |
260 | direction: 'DESC' | |
261 | }); | |
262 | }); | |
263 | } | |
264 | ||
265 | }); |