]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/form/GlobalSearchField.js
use Proxmox.Utils instead of PVE.Utils
[pve-manager.git] / www / manager6 / form / GlobalSearchField.js
CommitLineData
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 */
12Ext.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});