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