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