MultiCombo.js
上传用户:shuoshiled
上传日期:2018-01-28
资源大小:10124k
文件大小:28k
源码类别:

中间件编程

开发平台:

JavaScript

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