1 | /*! |
---|
2 | * Ext JS Library 3.0.0 |
---|
3 | * Copyright(c) 2006-2009 Ext JS, LLC |
---|
4 | * licensing@extjs.com |
---|
5 | * http://www.extjs.com/license |
---|
6 | */ |
---|
7 | Ext.ns('Ext.ux'); |
---|
8 | |
---|
9 | /** |
---|
10 | * Ext.ux.MultiCombo |
---|
11 | */ |
---|
12 | Ext.ux.MultiCombo = Ext.extend(Ext.form.ComboBox, { |
---|
13 | |
---|
14 | /** |
---|
15 | * @cfg {String} overClass [x-grid3-row-over] |
---|
16 | */ |
---|
17 | overClass : 'x-grid3-row-over', |
---|
18 | /** |
---|
19 | * @cfg {Boolean} enableKeyEvents for typeAhead |
---|
20 | */ |
---|
21 | enableKeyEvents: true, |
---|
22 | /** |
---|
23 | * @cfg {String} selectedClass [x-grid3-row-selected] |
---|
24 | */ |
---|
25 | selectedClass: 'x-grid3-row-selected', |
---|
26 | /** |
---|
27 | * @cfg {String} highlightClass The css class applied to rows which are hovered with mouse |
---|
28 | * selected via key-nav, or highlighted when a text-query matches a single item. |
---|
29 | */ |
---|
30 | highlightClass: 'x-grid3-row-over', |
---|
31 | /** |
---|
32 | * @cfg {Number} autoSelectKey [44] COMMA Sets the key used to auto-select an auto-suggest |
---|
33 | * highlighted query. When pressed, the highlighted text-item will be selected as if the user |
---|
34 | * selected the row with a mouse click. |
---|
35 | */ |
---|
36 | autoSelectKey : 44, |
---|
37 | /** |
---|
38 | * @cfg {String} allSelectedText Text to display when all items are selected |
---|
39 | */ |
---|
40 | allSelectedText : 'All selected', |
---|
41 | /** |
---|
42 | * @cfg {Number} maxDisplayRows The maximum number of rows to show before applying vscroll |
---|
43 | */ |
---|
44 | maxDisplayRows: null, |
---|
45 | |
---|
46 | mode: 'local', |
---|
47 | triggerAction: 'all', |
---|
48 | typeAhead: true, |
---|
49 | |
---|
50 | // private |
---|
51 | highlightIndex : null, |
---|
52 | highlightIndexPrev : null, |
---|
53 | |
---|
54 | query : null, |
---|
55 | |
---|
56 | |
---|
57 | /** |
---|
58 | * @cfg {Array} value CheckboxCombo expresses its value as an array. |
---|
59 | */ |
---|
60 | value: [], |
---|
61 | |
---|
62 | /** |
---|
63 | * @cfg {Integer} minChars [0] |
---|
64 | */ |
---|
65 | minChars: 0, |
---|
66 | |
---|
67 | initComponent : function() { |
---|
68 | var cls = 'x-combo-list'; |
---|
69 | |
---|
70 | // when blurring out of field, ensure that rawValue contains ONLY items contained in Store. |
---|
71 | this.on('blur', this.validateSelections.createDelegate(this)); |
---|
72 | |
---|
73 | // create an auto-select key handler, like *nix-based console [tab] key behaviour |
---|
74 | this.on('keypress', function(field, ev) { |
---|
75 | if (ev.getKey() == this.autoSelectKey) { // COMMA |
---|
76 | this.onAutoSelect(); |
---|
77 | } |
---|
78 | },this); |
---|
79 | |
---|
80 | this.addEvents( |
---|
81 | /** |
---|
82 | * @event initview Fires when Combo#initView is called. |
---|
83 | * gives plugins a chance to interact with DataView |
---|
84 | * @author Chris Scott |
---|
85 | * @param {Combo} this |
---|
86 | * @param {DataView} dv |
---|
87 | */ |
---|
88 | 'initview', |
---|
89 | 'clearall' |
---|
90 | ); |
---|
91 | |
---|
92 | // when list expands, constrain the height with @cfg maxDisplayRows |
---|
93 | if (this.maxDisplayRows) { |
---|
94 | this.on('expand', function(){ |
---|
95 | var cnt = this.store.getCount(); |
---|
96 | if (cnt > this.maxDisplayRows) { |
---|
97 | var children = this.view.getNodes(); |
---|
98 | var h = 0; |
---|
99 | for (var n = 0; n < this.maxDisplayRows; n++) { |
---|
100 | h += Ext.fly(children[n]).getHeight(); |
---|
101 | } |
---|
102 | this.maxHeight = h; |
---|
103 | } |
---|
104 | }, this, { |
---|
105 | single: true |
---|
106 | }); |
---|
107 | } |
---|
108 | |
---|
109 | this.on('beforequery', this.onQuery, this); |
---|
110 | |
---|
111 | // Enforce that plugins is an Array. |
---|
112 | if (typeof(this.plugins) == 'undefined'){ |
---|
113 | this.plugins = []; |
---|
114 | } |
---|
115 | else if (!Ext.isArray(this.plugins)) { |
---|
116 | this.plugins = [this.plugins]; |
---|
117 | } |
---|
118 | |
---|
119 | var tmp = this.value; // for case where transform is set. |
---|
120 | Ext.ux.MultiCombo.superclass.initComponent.call(this); |
---|
121 | if (this.transform) { |
---|
122 | if (typeof(tmp) == 'undefined') { |
---|
123 | tmp = []; |
---|
124 | } |
---|
125 | this.setValue(tmp); |
---|
126 | } |
---|
127 | }, |
---|
128 | |
---|
129 | // private |
---|
130 | onViewClick : function(dv, index, node, ev){ |
---|
131 | var rec = this.store.getAt(index); |
---|
132 | this.onSelect(rec, index); |
---|
133 | this.el.focus(); |
---|
134 | /* |
---|
135 | if(doFocus !== false){ |
---|
136 | this.el.focus(); |
---|
137 | } |
---|
138 | */ |
---|
139 | }, |
---|
140 | |
---|
141 | // onTriggerClick, overrides Ext.form.ComboBox#onTriggerClick |
---|
142 | onTriggerClick: function() { |
---|
143 | if (this.highlightIndex != -1) { |
---|
144 | this.clearHighlight(); |
---|
145 | } |
---|
146 | this.highlightIndex = -1; |
---|
147 | |
---|
148 | if(this.disabled){ |
---|
149 | return; |
---|
150 | } |
---|
151 | if(this.isExpanded()){ |
---|
152 | this.collapse(); |
---|
153 | this.el.focus(); |
---|
154 | }else { |
---|
155 | this.onFocus({}); |
---|
156 | if(this.triggerAction == 'all') { |
---|
157 | this.doQuery(this.getRawValue(), true); |
---|
158 | var vlen = this.getValue().length, slen = this.view.getSelectedRecords().length; |
---|
159 | if (vlen != slen || vlen == 0) { |
---|
160 | this.selectByValue(this.value, true); |
---|
161 | } |
---|
162 | } else { |
---|
163 | this.expand(); |
---|
164 | this.doQuery(this.getRawValue()); |
---|
165 | } |
---|
166 | |
---|
167 | this.highlightIndex = -1 |
---|
168 | this.highlightIndexPrev = null; |
---|
169 | this.selectNext(); |
---|
170 | this.scrollIntoView(); |
---|
171 | this.el.focus(); |
---|
172 | } |
---|
173 | }, |
---|
174 | |
---|
175 | // onQuery, beforequery listener, @return false |
---|
176 | onQuery : function(qe) { |
---|
177 | q = qe.query; |
---|
178 | forceAll = qe.forceAll; |
---|
179 | if(forceAll === true || (q.length >= this.minChars)){ |
---|
180 | if(this.lastQuery !== q){ |
---|
181 | if (typeof(this.lastQuery) != 'undefined') { |
---|
182 | if (q.match(new RegExp('^'+this.allSelectedText))) { |
---|
183 | this.query = this.store.data; |
---|
184 | } |
---|
185 | else if (this.lastQuery.length > q.length) { |
---|
186 | var items = q.replace(/\s+/g, '').split(','); |
---|
187 | if (items[items.length-1].length == 0) { |
---|
188 | items.pop(); |
---|
189 | } |
---|
190 | this.query = this.store.data.filterBy(this.store.createFilterFn(this.displayField, new RegExp('^'+items.join('$|^')+'$', "i"), false, false)); |
---|
191 | } |
---|
192 | else { |
---|
193 | this.query = null; |
---|
194 | } |
---|
195 | } |
---|
196 | this.lastQuery = q; |
---|
197 | if(this.mode == 'local'){ |
---|
198 | var raw = this.getRawValue(); |
---|
199 | if (raw == this.allSelectedText) { |
---|
200 | |
---|
201 | } |
---|
202 | var items = raw.replace(/\s+/g, '').split(','); |
---|
203 | var last = items.pop(); |
---|
204 | this.matches = this.store.data.filterBy(this.store.createFilterFn(this.displayField, new RegExp('^'+last, "i"), false, false)).filterBy(this.createTypeAheadFilterFn(items)); |
---|
205 | if (this.matches.getCount() == 0) { |
---|
206 | this.clearHighlight(); |
---|
207 | } |
---|
208 | if (q.length == 0) { |
---|
209 | this.view.clearSelections(); |
---|
210 | this.updateValue([]); |
---|
211 | } |
---|
212 | |
---|
213 | this.onLoad(); |
---|
214 | } else { |
---|
215 | this.store.baseParams[this.queryParam] = q; |
---|
216 | this.store.load({ |
---|
217 | params: this.getParams(q) |
---|
218 | }); |
---|
219 | this.expand(); |
---|
220 | } |
---|
221 | }else{ |
---|
222 | this.selectedIndex = -1; |
---|
223 | this.onLoad(); |
---|
224 | } |
---|
225 | } |
---|
226 | |
---|
227 | return false; |
---|
228 | }, |
---|
229 | |
---|
230 | // onLoad, overrides Ext.form.ComboBox#onLoad |
---|
231 | onLoad : function(){ |
---|
232 | |
---|
233 | if(!this.hasFocus){ |
---|
234 | return; |
---|
235 | } |
---|
236 | if(this.store.getCount() > 0){ |
---|
237 | if (!this.isExpanded()) { |
---|
238 | this.expand(); |
---|
239 | this.restrictHeight(); |
---|
240 | } |
---|
241 | if(this.lastQuery == this.allQuery){ |
---|
242 | if(this.editable){ |
---|
243 | this.el.dom.select(); |
---|
244 | } |
---|
245 | }else{ |
---|
246 | if (this.query != null) { |
---|
247 | var values = [], indexes = []; |
---|
248 | this.query.each(function(r){ |
---|
249 | values.push(r.data[this.valueField]); |
---|
250 | indexes.push(this.store.indexOf(r)); |
---|
251 | }, this); |
---|
252 | this.view.clearSelections(); |
---|
253 | this.updateValue(values, this.getRawValue()); |
---|
254 | this.view.select(indexes); |
---|
255 | } |
---|
256 | if (this.matches != null) { |
---|
257 | if (this.matches.getCount() == 1) { |
---|
258 | this.highlight(this.store.indexOf(this.matches.first())); |
---|
259 | this.scrollIntoView(); |
---|
260 | } |
---|
261 | } |
---|
262 | else { |
---|
263 | // @HACK: If store was configured with a proxy, set its mode to local now that its populated with data. |
---|
264 | // Re-execute the query now. |
---|
265 | this.mode = 'local'; |
---|
266 | this.lastQuery = undefined; |
---|
267 | this.doQuery(this.getRawValue(), true); |
---|
268 | } |
---|
269 | if(this.typeAhead && this.lastKey != Ext.EventObject.DOWN && this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE){ |
---|
270 | this.taTask.delay(this.typeAheadDelay); |
---|
271 | } |
---|
272 | } |
---|
273 | }else{ |
---|
274 | this.onEmptyResults(); |
---|
275 | } |
---|
276 | }, |
---|
277 | |
---|
278 | onSelect : function(record, index) { |
---|
279 | if (index == -1) { |
---|
280 | throw new Error('MultiCombo#onSelect did not receive a valid index'); |
---|
281 | } |
---|
282 | |
---|
283 | // select only when user clicks [apply] button |
---|
284 | if (this.selectOnApply == true) { |
---|
285 | return; |
---|
286 | } |
---|
287 | |
---|
288 | if (this.fireEvent('beforeselect', this, record, index) !== false) { |
---|
289 | var text = []; |
---|
290 | var value = []; |
---|
291 | var rs = this.view.getSelectedRecords(); |
---|
292 | for (var n = 0, len = rs.length; n < len; n++) { |
---|
293 | text.push(rs[n].data[this.displayField]); |
---|
294 | value.push(rs[n].data[this.valueField]); |
---|
295 | } |
---|
296 | this.updateValue(value, (value.length != this.store.getCount()) ? text.join(', ') : this.allSelectedText); |
---|
297 | var node = this.view.getNode(index); |
---|
298 | this.innerList.scrollChildIntoView(node, false); |
---|
299 | this.fireEvent('select', this, record, index); |
---|
300 | } |
---|
301 | }, |
---|
302 | |
---|
303 | // private |
---|
304 | onViewOver : function(ev, node){ |
---|
305 | var t = ev.getTarget(this.view.itemSelector); |
---|
306 | if (t == null) { |
---|
307 | return; |
---|
308 | } |
---|
309 | this.highlightIndex = this.store.indexOf(this.view.getRecord(t)); |
---|
310 | this.clearHighlight(); |
---|
311 | this.highlight(this.highlightIndex); |
---|
312 | if(this.inKeyMode){ // prevent key nav and mouse over conflicts |
---|
313 | return;null |
---|
314 | } |
---|
315 | return; |
---|
316 | }, |
---|
317 | |
---|
318 | // private |
---|
319 | onTypeAhead : function(){ |
---|
320 | if(this.store.getCount() > 0){ |
---|
321 | this.inKeyMode = false; |
---|
322 | var raw = this.getRawValue(); |
---|
323 | var pos = this.getCaretPosition(raw); |
---|
324 | var items = []; |
---|
325 | var query = ''; |
---|
326 | if (pos !== false && pos < raw.length) { |
---|
327 | items = raw.substr(0, pos).replace(/\s+/g, '').split(','); |
---|
328 | query = items.pop(); |
---|
329 | } else { |
---|
330 | items = raw.replace(/\s+/g, '').split(','); |
---|
331 | query = items.pop(); |
---|
332 | } |
---|
333 | var rs = this.store.data.filterBy(this.store.createFilterFn(this.displayField, new RegExp(query, "i"), false, false)).filterBy(this.createTypeAheadFilterFn(items)); |
---|
334 | |
---|
335 | if (rs.getCount() == 1) { |
---|
336 | var r = rs.first(); |
---|
337 | var rindex = this.store.indexOf(r) |
---|
338 | if (!this.view.isSelected(rindex)) { |
---|
339 | this.typeAheadSelected = true; |
---|
340 | var selStart = raw.length; |
---|
341 | var len = items.join(',').length; |
---|
342 | var selEnd = null; |
---|
343 | var newValue = r.data[this.displayField]; |
---|
344 | if (pos !== false && pos < raw.length) { |
---|
345 | var insertIdx = items.length; |
---|
346 | var selStart = pos; |
---|
347 | items = raw.replace(/\s+/g, '').split(','); |
---|
348 | items.splice(insertIdx, 1, newValue); |
---|
349 | selEnd = items.slice(0, insertIdx+1).join(', ').length; |
---|
350 | this.highlight(rindex); |
---|
351 | this.scrollIntoView(); |
---|
352 | |
---|
353 | } |
---|
354 | else { |
---|
355 | items.push(newValue); |
---|
356 | } |
---|
357 | var len = items.join(',').length; |
---|
358 | if(selStart != len){ |
---|
359 | var lastWord = raw.split(',').pop(); |
---|
360 | if (items.length >1 && lastWord.match(/^\s+/) == null) { |
---|
361 | selStart++; |
---|
362 | } |
---|
363 | this.setRawValue(items.join(', ')); |
---|
364 | this.selectText(selStart, (selEnd!=null) ? selEnd : this.getRawValue().length); |
---|
365 | } |
---|
366 | } |
---|
367 | } |
---|
368 | } |
---|
369 | }, |
---|
370 | |
---|
371 | apply : function() { |
---|
372 | var selected = this.view.getSelectedRecords(); |
---|
373 | var value = []; |
---|
374 | for (var n=0,len=selected.length;n<len;n++) { |
---|
375 | value.push(selected[n].data[this.valueField]); |
---|
376 | } |
---|
377 | this.setValue(value); |
---|
378 | }, |
---|
379 | |
---|
380 | getCaretPosition : function(raw) { |
---|
381 | raw = raw || this.getRawValue(); |
---|
382 | if(document.selection) { // <-- IE, ugh: http://parentnode.org/javascript/working-with-the-cursor-position/ |
---|
383 | var range = document.selection.createRange(); |
---|
384 | //Save the current value. We will need this value later to find out, where the text has been changed |
---|
385 | var orig = obj.value.replace(/rn/g, "n"); |
---|
386 | // replace the text |
---|
387 | range.text = text; |
---|
388 | // Now get the new content and save it into a temporary variable |
---|
389 | var actual = tmp = obj.value.replace(/rn/g, "n"); |
---|
390 | /* Find the first occurance, where the original differs |
---|
391 | from the actual content. This could be the startposition |
---|
392 | of our text selection, but it has not to be. Think of the |
---|
393 | selection "ab" and replacing it with "ac". The first |
---|
394 | difference would be the "c", while the start position |
---|
395 | is the "a" |
---|
396 | */ |
---|
397 | for(var diff = 0; diff < orig.length; diff++) { |
---|
398 | if(orig.charAt(diff) != actual.charAt(diff)) break; |
---|
399 | } |
---|
400 | |
---|
401 | /* To get the real start position, we iterate through |
---|
402 | the string searching for the whole replacement |
---|
403 | text - "abc", as long as the first difference is not |
---|
404 | reached. If you do not understand that logic - no |
---|
405 | blame to you, just copy & paste it ;) |
---|
406 | */ |
---|
407 | for(var index = 0, start = 0; tmp.match(text) && (tmp = tmp.replace(text, "")) && index <= diff; index = start + text.length) { |
---|
408 | start = actual.indexOf(text, index); |
---|
409 | } |
---|
410 | } else if(this.el.dom.selectionStart) { // <-- Go the Gecko way |
---|
411 | return this.el.dom.selectionStart; |
---|
412 | } else { |
---|
413 | // Fallback for any other browser |
---|
414 | return false; |
---|
415 | } |
---|
416 | }, |
---|
417 | |
---|
418 | onAutoSelect : function() { |
---|
419 | if (!this.isExpanded()) { |
---|
420 | var vlen = this.getValue().length, slen = this.view.getSelectedRecords().length; |
---|
421 | if (vlen != slen || vlen == 0) { |
---|
422 | this.selectByValue(this.value, true); |
---|
423 | } |
---|
424 | } |
---|
425 | var raw = this.getRawValue(); |
---|
426 | this.selectText(raw.length, raw.length); |
---|
427 | |
---|
428 | var pos = this.getCaretPosition(raw); |
---|
429 | var word = ''; |
---|
430 | if (pos !== false && pos < raw.length) { |
---|
431 | word = Ext.util.Format.trim(raw.substr(0, pos).split(',').pop()); |
---|
432 | } else { |
---|
433 | word = Ext.util.Format.trim(raw.split(',').pop()); |
---|
434 | } |
---|
435 | var idx = this.store.find(this.displayField, word); |
---|
436 | if (idx > -1 && !this.view.isSelected(idx)) { |
---|
437 | var rec = this.store.getAt(idx); |
---|
438 | this.select(idx); |
---|
439 | } |
---|
440 | }, |
---|
441 | // filters-out already-selected items from type-ahead queries. |
---|
442 | // e.g.: if store contains: "betty, barney, bart" and betty is already selected, |
---|
443 | // when user types "b", only "bart" and "barney" should be returned as possible matches, |
---|
444 | // since betty is *already* selected |
---|
445 | createTypeAheadFilterFn : function(items) { |
---|
446 | var key = this.displayField; |
---|
447 | return function(rec) { |
---|
448 | var re = new RegExp(rec.data[key], "i"); |
---|
449 | var add = true; |
---|
450 | for (var n=0,len=items.length;n<len;n++) { |
---|
451 | if (re.test(items[n])) { |
---|
452 | add = false; |
---|
453 | break; |
---|
454 | } |
---|
455 | } |
---|
456 | return add; |
---|
457 | } |
---|
458 | }, |
---|
459 | |
---|
460 | updateValue : function(value, text) { |
---|
461 | this.value = value; |
---|
462 | if(this.hiddenField){ |
---|
463 | this.hiddenField.value = value.join(','); |
---|
464 | } |
---|
465 | if (typeof(text) == 'string') { |
---|
466 | this.setRawValue(text); |
---|
467 | } |
---|
468 | |
---|
469 | }, |
---|
470 | |
---|
471 | /** |
---|
472 | * setValue |
---|
473 | * Accepts a comma-separated list of ids or an array. if given a string, will conver to Array. |
---|
474 | * @param {Array, String} v |
---|
475 | */ |
---|
476 | setValue : function(v) { |
---|
477 | var text = []; |
---|
478 | var value = []; |
---|
479 | |
---|
480 | if (typeof(v) == 'string') { // <-- "1,2,3" |
---|
481 | value = v.match(/\d+/g); // <-- strip multiple spaces and split on "," |
---|
482 | if(value){ |
---|
483 | for (var n=0,len=value.length;n<len;n++) { |
---|
484 | value[n] = parseInt(value[n]); |
---|
485 | } |
---|
486 | } |
---|
487 | } |
---|
488 | else if (Ext.isArray(v)) { // <-- [1,2,3] |
---|
489 | value = v; |
---|
490 | } |
---|
491 | if (value && value.length) { |
---|
492 | if (this.mode == 'local') { |
---|
493 | this.updateValue(value); |
---|
494 | this.setRawValue(this.getTextValue()); |
---|
495 | } |
---|
496 | else { |
---|
497 | this.updateValue(value); |
---|
498 | this.store.load({ |
---|
499 | callback: function() { |
---|
500 | this.setRawValue(this.getTextValue()); |
---|
501 | }, |
---|
502 | scope: this |
---|
503 | }); |
---|
504 | this.mode = 'local'; |
---|
505 | } |
---|
506 | } |
---|
507 | }, |
---|
508 | |
---|
509 | getTextValue : function() { |
---|
510 | if (this.value.length == this.store.getCount()) { |
---|
511 | return this.allSelectedText; |
---|
512 | } |
---|
513 | else { |
---|
514 | var text = []; |
---|
515 | this.store.data.filterBy(this.store.createFilterFn(this.valueField, new RegExp(this.value.join('|'), "i"), false, false)).each(function(r){ |
---|
516 | text.push(r.data[this.displayField]); |
---|
517 | }, this); |
---|
518 | return text.join(', '); |
---|
519 | } |
---|
520 | }, |
---|
521 | |
---|
522 | /** |
---|
523 | * Select an item in the dropdown list by its numeric index in the list. This function does NOT cause the select event to fire. |
---|
524 | * The store must be loaded and the list expanded for this function to work, otherwise use setValue. |
---|
525 | * @param {Number} index The zero-based index of the list item to select |
---|
526 | * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the |
---|
527 | * selected item if it is not currently in view (defaults to true) |
---|
528 | */ |
---|
529 | select : function(index, scrollIntoView){ |
---|
530 | if (!typeof(index) == 'number') { |
---|
531 | throw new Error('MultiCombo#select expected @param {Number} index but got: ' + typeof(index)); |
---|
532 | } |
---|
533 | this.view.isSelected(index) ? this.view.deselect(index, true) : this.view.select(index, true); |
---|
534 | this.onSelect(this.store.getAt(index), index); |
---|
535 | |
---|
536 | this.matches = null; |
---|
537 | if(scrollIntoView !== false){ |
---|
538 | var el = this.view.getNode(index); |
---|
539 | if(el){ |
---|
540 | this.innerList.scrollChildIntoView(el, false); |
---|
541 | } |
---|
542 | } |
---|
543 | |
---|
544 | }, |
---|
545 | |
---|
546 | getLastValue : function() { |
---|
547 | return Ext.util.Format.trim(this.getRawValue().split(',').pop()); |
---|
548 | }, |
---|
549 | |
---|
550 | /** |
---|
551 | * Select an item in the dropdown list by its data value. This function does NOT cause the select event to fire. |
---|
552 | * The store must be loaded and the list expanded for this function to work, otherwise use setValue. |
---|
553 | * @param {String} value The data value of the item to select |
---|
554 | * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the |
---|
555 | * selected item if it is not currently in view (defaults to true) |
---|
556 | * @return {Boolean} True if the value matched an item in the list, else false |
---|
557 | */ |
---|
558 | selectByValue : function(v, scrollIntoView){ |
---|
559 | if (v.length) { |
---|
560 | var indexes = []; |
---|
561 | var rs = this.store.data.filterBy(this.store.createFilterFn(this.valueField, new RegExp(v.join('|'), "i"))).each(function(r){ |
---|
562 | indexes.push(this.store.indexOf(r)); |
---|
563 | }, this); |
---|
564 | if (indexes.length) { |
---|
565 | this.view.select(indexes); |
---|
566 | return true; |
---|
567 | } |
---|
568 | } |
---|
569 | else { |
---|
570 | this.view.clearSelections(); |
---|
571 | this.setRawValue(''); |
---|
572 | return false; |
---|
573 | } |
---|
574 | }, |
---|
575 | |
---|
576 | // private |
---|
577 | initEvents : function(){ |
---|
578 | Ext.form.ComboBox.superclass.initEvents.call(this); |
---|
579 | this.keyNav = new Ext.KeyNav(this.el, { |
---|
580 | "up" : function(e){ |
---|
581 | this.lastKey = Ext.EventObject.UP; |
---|
582 | this.inKeyMode = true; |
---|
583 | this.selectPrev(); |
---|
584 | this.scrollIntoView(); |
---|
585 | }, |
---|
586 | |
---|
587 | "down" : function(e){ |
---|
588 | this.inKeyMode = true; |
---|
589 | if(!this.isExpanded()){ |
---|
590 | this.lastKey = Ext.EventObject.DOWN; |
---|
591 | this.onTriggerClick(); |
---|
592 | }else{ |
---|
593 | this.selectNext(); |
---|
594 | this.scrollIntoView(); |
---|
595 | } |
---|
596 | |
---|
597 | }, |
---|
598 | |
---|
599 | "enter" : function(e){ |
---|
600 | var idx = this.highlightIndex; |
---|
601 | if (this.inKeyMode === true) { |
---|
602 | if (this.plugins.length && (idx <= -1)) { |
---|
603 | if (this.plugins[idx + 1]) { |
---|
604 | this.plugins[idx + 1].onEnter(this); |
---|
605 | } |
---|
606 | } |
---|
607 | else |
---|
608 | if (this.plugins.length && this.highlightIndex == 0 && this.highlightIndexPrev == -1) { |
---|
609 | if (this.plugins[idx]) { |
---|
610 | this.plugins[idx].onEnter(this); |
---|
611 | } |
---|
612 | } |
---|
613 | else { |
---|
614 | var idx = this.getHighlightedIndex() || 0; |
---|
615 | if (this.highlightIndex != null && idx != null) { |
---|
616 | this.select(idx, true); |
---|
617 | //this.delayedCheck = true; |
---|
618 | //this.unsetDelayCheck.defer(10, this); |
---|
619 | |
---|
620 | } |
---|
621 | } |
---|
622 | } |
---|
623 | else { |
---|
624 | var v = this.getLastValue(); |
---|
625 | var raw = this.getRawValue(); |
---|
626 | |
---|
627 | /** this block should be moved to method getCurrentWord |
---|
628 | * |
---|
629 | */ |
---|
630 | var pos = this.getCaretPosition(raw); |
---|
631 | var word = ''; |
---|
632 | if (pos !== false && pos < raw.length) { |
---|
633 | word = Ext.util.Format.trim(raw.substr(0, pos).split(',').pop()); |
---|
634 | } else { |
---|
635 | word = Ext.util.Format.trim(raw.split(',').pop()); |
---|
636 | } |
---|
637 | /*******************************************************/ |
---|
638 | |
---|
639 | var idx = this.store.find(this.displayField, word); |
---|
640 | if (idx != -1) { |
---|
641 | var rec = this.store.getAt(idx); |
---|
642 | this.select(idx, true); |
---|
643 | } |
---|
644 | raw = this.getRawValue(); |
---|
645 | this.selectText(raw.length, raw.length); |
---|
646 | this.collapse(); |
---|
647 | } |
---|
648 | }, |
---|
649 | |
---|
650 | "esc" : function(e){ |
---|
651 | this.collapse(); |
---|
652 | }, |
---|
653 | |
---|
654 | "tab" : function(e){ |
---|
655 | if (this.matches != null && this.matches.getCount() == 1) { |
---|
656 | var idx = this.store.indexOf(this.matches.first()); |
---|
657 | if (!this.view.isSelected(idx)) { |
---|
658 | this.select(this.store.indexOf(this.matches.first()), true); |
---|
659 | } |
---|
660 | } |
---|
661 | else if (this.value.length == 0 && this.getRawValue().length > 0) { |
---|
662 | this.setRawValue(''); |
---|
663 | } |
---|
664 | this.collapse(); |
---|
665 | return true; |
---|
666 | }, |
---|
667 | |
---|
668 | scope : this, |
---|
669 | |
---|
670 | doRelay : function(foo, bar, hname){ |
---|
671 | if(hname == 'down' || this.scope.isExpanded()){ |
---|
672 | return Ext.KeyNav.prototype.doRelay.apply(this, arguments); |
---|
673 | } |
---|
674 | return true; |
---|
675 | }, |
---|
676 | |
---|
677 | forceKeyDown : true |
---|
678 | }); |
---|
679 | this.queryDelay = Math.max(this.queryDelay || 10, |
---|
680 | this.mode == 'local' ? 10 : 250); |
---|
681 | this.dqTask = new Ext.util.DelayedTask(this.initQuery, this); |
---|
682 | if(this.typeAhead){ |
---|
683 | this.taTask = new Ext.util.DelayedTask(this.onTypeAhead, this); |
---|
684 | } |
---|
685 | if(this.editable !== false){ |
---|
686 | this.el.on("keyup", this.onKeyUp, this); |
---|
687 | } |
---|
688 | if(this.forceSelection){ |
---|
689 | this.on('blur', this.doForce, this); |
---|
690 | } |
---|
691 | }, |
---|
692 | |
---|
693 | // private, blur-handler to ensure that rawValue contains only values from selections, in the same order as selected |
---|
694 | validateSelections : function(field) { |
---|
695 | var v = this.getValue(); |
---|
696 | var text = []; |
---|
697 | for (var i=0,len=v.length;i<len;i++) { |
---|
698 | var idx = this.store.find(this.valueField, v[i]); |
---|
699 | if (idx >=0) { |
---|
700 | text.push(this.store.getAt(idx).data[this.displayField]); |
---|
701 | } |
---|
702 | } |
---|
703 | this.setRawValue(text.join(', ')); |
---|
704 | }, |
---|
705 | |
---|
706 | scrollIntoView : function() { |
---|
707 | var el = this.getHighlightedNode(); |
---|
708 | if (el) { |
---|
709 | this.innerList.scrollChildIntoView(el); |
---|
710 | } |
---|
711 | }, |
---|
712 | |
---|
713 | // private |
---|
714 | selectNext : function(){ |
---|
715 | this.clearHighlight(); |
---|
716 | if (this.highlightIndex == null) { |
---|
717 | this.highlightIndex = -1; |
---|
718 | } |
---|
719 | if (this.highlightIndex <= -1 && this.highlightIndexPrev != -1) { |
---|
720 | if (this.plugins.length > 0) { |
---|
721 | var idx = Math.abs(this.highlightIndex)-1; |
---|
722 | if (this.plugins.length >= Math.abs(this.highlightIndex)) { |
---|
723 | this.plugins[idx].selectNext(this); |
---|
724 | this.highlightIndexPrev = this.highlightIndex; |
---|
725 | this.highlightIndex++; |
---|
726 | return false; |
---|
727 | } |
---|
728 | } |
---|
729 | } |
---|
730 | if (this.highlightIndexPrev == -1 && this.highlightIndex == 0) { |
---|
731 | this.highlightIndex = -1; |
---|
732 | } |
---|
733 | var ct = this.store.getCount(); |
---|
734 | if(ct > 0){ |
---|
735 | if (this.highlightIndex == -1 || this.highlightIndex+1 < ct) { |
---|
736 | if (this.highlightIndex == -1) { |
---|
737 | this.highlightIndexPrev = 0; |
---|
738 | } |
---|
739 | else { |
---|
740 | this.highlightIndexPrev = this.highlightIndex -1; |
---|
741 | } |
---|
742 | this.highlight(++this.highlightIndex); |
---|
743 | |
---|
744 | } |
---|
745 | else { |
---|
746 | this.highlight(ct-1); |
---|
747 | } |
---|
748 | } |
---|
749 | }, |
---|
750 | |
---|
751 | // private |
---|
752 | selectPrev : function(){ |
---|
753 | this.clearHighlight(); |
---|
754 | if (this.highlightIndex <= 0) { |
---|
755 | var idx = Math.abs(this.highlightIndex); |
---|
756 | if (this.plugins.length >= idx+1 && this.highlightIndexPrev >= 0) { |
---|
757 | this.clearHighlight(); |
---|
758 | this.plugins[idx].selectPrev(this); |
---|
759 | this.highlightIndexPrev = this.highlightIndex; |
---|
760 | this.highlightIndex--; |
---|
761 | if (this.highlightIndex == -1) { |
---|
762 | this.highlightIndexPrev = -1; |
---|
763 | } |
---|
764 | return false; |
---|
765 | } |
---|
766 | else { |
---|
767 | this.highlightIndex = -1; |
---|
768 | this.highlightIndexPrev = -1; |
---|
769 | this.collapse(); |
---|
770 | return; |
---|
771 | } |
---|
772 | } |
---|
773 | |
---|
774 | this.highlightIndexPrev = this.highlightIndex; |
---|
775 | var ct = this.store.getCount(); |
---|
776 | if(ct > 0){ |
---|
777 | if (this.highlighIndex == -1) { |
---|
778 | this.highlightIndex = 0; |
---|
779 | } |
---|
780 | else if (this.highlightIndex != 0) { |
---|
781 | this.highlightIndex--; |
---|
782 | } |
---|
783 | else if (this.highlightIndex == 0) { |
---|
784 | this.collapse(); |
---|
785 | } |
---|
786 | this.highlight(this.highlightIndex); |
---|
787 | } |
---|
788 | }, |
---|
789 | |
---|
790 | collapse : function() { |
---|
791 | if (this.isExpanded()) { |
---|
792 | this.highlightIndex = null; |
---|
793 | this.highlightIndexPrev = null; |
---|
794 | } |
---|
795 | Ext.ux.MultiCombo.superclass.collapse.call(this); |
---|
796 | }, |
---|
797 | |
---|
798 | highlight : function(index) { |
---|
799 | this.view.el.select('.'+this.highlightClass).removeClass(this.highlightClass); |
---|
800 | var node = Ext.fly(this.view.getNode(index)); |
---|
801 | if (node) { |
---|
802 | node.addClass(this.highlightClass); |
---|
803 | } |
---|
804 | }, |
---|
805 | |
---|
806 | getHighlightedIndex : function() { |
---|
807 | var node = this.view.el.child('.' + this.highlightClass, true); |
---|
808 | return (node) ? this.store.indexOf(this.view.getRecord(node)) : this.highlightIndex; |
---|
809 | }, |
---|
810 | getHighlightedNode : function() { |
---|
811 | return this.view.el.child('.'+this.highlightClass, true); |
---|
812 | }, |
---|
813 | |
---|
814 | clearHighlight : function() { |
---|
815 | if (typeof(this.view) != 'object') { return false; } |
---|
816 | var el = this.view.el.select('.'+this.highlightClass); |
---|
817 | if (el) { |
---|
818 | el.removeClass(this.highlightClass); |
---|
819 | } |
---|
820 | }, |
---|
821 | |
---|
822 | // private |
---|
823 | initList : function(){ |
---|
824 | if(!this.list){ |
---|
825 | var cls = 'x-combo-list'; |
---|
826 | |
---|
827 | this.list = new Ext.Layer({ |
---|
828 | shadow: this.shadow, cls: [cls, this.listClass].join(' '), constrain:false |
---|
829 | }); |
---|
830 | |
---|
831 | var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth); |
---|
832 | this.list.setWidth(lw); |
---|
833 | this.list.swallowEvent('mousewheel'); |
---|
834 | this.assetHeight = 0; |
---|
835 | if(this.syncFont !== false){ |
---|
836 | this.list.setStyle('font-size', this.el.getStyle('font-size')); |
---|
837 | } |
---|
838 | if(this.title){ |
---|
839 | this.header = this.list.createChild({cls:cls+'-hd', html: this.title}); |
---|
840 | this.assetHeight += this.header.getHeight(); |
---|
841 | } |
---|
842 | |
---|
843 | this.innerList = this.list.createChild({cls:cls+'-inner'}); |
---|
844 | this.innerList.on('mouseover', this.onViewOver, this); |
---|
845 | this.innerList.on('mousemove', this.onViewMove, this); |
---|
846 | this.innerList.setWidth(lw - this.list.getFrameWidth('lr')); |
---|
847 | |
---|
848 | if(this.pageSize){ |
---|
849 | this.footer = this.list.createChild({cls:cls+'-ft'}); |
---|
850 | this.pageTb = new Ext.PagingToolbar({ |
---|
851 | store:this.store, |
---|
852 | pageSize: this.pageSize, |
---|
853 | renderTo:this.footer |
---|
854 | }); |
---|
855 | this.assetHeight += this.footer.getHeight(); |
---|
856 | } |
---|
857 | |
---|
858 | if(!this.tpl){ |
---|
859 | /** |
---|
860 | * @cfg {String/Ext.XTemplate} tpl The template string, or {@link Ext.XTemplate} |
---|
861 | * instance to use to display each item in the dropdown list. Use |
---|
862 | * this to create custom UI layouts for items in the list. |
---|
863 | * <p> |
---|
864 | * If you wish to preserve the default visual look of list items, add the CSS |
---|
865 | * class name <pre>x-combo-list-item</pre> to the template's container element. |
---|
866 | * <p> |
---|
867 | * <b>The template must contain one or more substitution parameters using field |
---|
868 | * names from the Combo's</b> {@link #store Store}. An example of a custom template |
---|
869 | * would be adding an <pre>ext:qtip</pre> attribute which might display other fields |
---|
870 | * from the Store. |
---|
871 | * <p> |
---|
872 | * The dropdown list is displayed in a DataView. See {@link Ext.DataView} for details. |
---|
873 | */ |
---|
874 | this.tpl = '<tpl for="."><div class="'+cls+'-item">{' + this.displayField + '}</div></tpl>'; |
---|
875 | /** |
---|
876 | * @cfg {String} itemSelector |
---|
877 | * <b>This setting is required if a custom XTemplate has been specified in {@link #tpl} |
---|
878 | * which assigns a class other than <pre>'x-combo-list-item'</pre> to dropdown list items</b>. |
---|
879 | * A simple CSS selector (e.g. div.some-class or span:first-child) that will be |
---|
880 | * used to determine what nodes the DataView which handles the dropdown display will |
---|
881 | * be working with. |
---|
882 | */ |
---|
883 | } |
---|
884 | |
---|
885 | /** |
---|
886 | * The {@link Ext.DataView DataView} used to display the ComboBox's options. |
---|
887 | * @type Ext.DataView |
---|
888 | */ |
---|
889 | this.view = new Ext.DataView({ |
---|
890 | applyTo: this.innerList, |
---|
891 | tpl: this.tpl, |
---|
892 | simpleSelect: true, |
---|
893 | multiSelect: true, |
---|
894 | overClass: this.overClass, |
---|
895 | selectedClass: this.selectedClass, |
---|
896 | itemSelector: this.itemSelector || '.' + cls + '-item' |
---|
897 | }); |
---|
898 | this.view.on('click', this.onViewClick, this); |
---|
899 | this.fireEvent('initview', this, this.view); |
---|
900 | this.bindStore(this.store, true); |
---|
901 | |
---|
902 | if(this.resizable){ |
---|
903 | this.resizer = new Ext.Resizable(this.list, { |
---|
904 | pinned:true, handles:'se' |
---|
905 | }); |
---|
906 | this.resizer.on('resize', function(r, w, h){ |
---|
907 | this.maxHeight = h-this.handleHeight-this.list.getFrameWidth('tb')-this.assetHeight; |
---|
908 | this.listWidth = w; |
---|
909 | this.innerList.setWidth(w - this.list.getFrameWidth('lr')); |
---|
910 | this.restrictHeight(); |
---|
911 | }, this); |
---|
912 | this[this.pageSize?'footer':'innerList'].setStyle('margin-bottom', this.handleHeight+'px'); |
---|
913 | } |
---|
914 | } |
---|
915 | } |
---|
916 | }); |
---|
917 | |
---|
918 | |
---|
919 | Ext.reg('multicombo', Ext.ux.MultiCombo); |
---|