f68f5fdfe446eeaad82446910390d8b2b19db0aa.svn-base 28 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046
  1. /*
  2. * Ext JS Library 2.0 Copyright(c) 2006-2007, Ext JS, LLC. licensing@extjs.com
  3. *
  4. * http://extjs.com/license
  5. */
  6. /**
  7. * @class Ext.form.ComboBox
  8. * @extends Ext.form.TriggerField A combobox control with support for
  9. * autocomplete, remote-loading, paging and many other features.
  10. * @constructor Create a new ComboBox.
  11. * @param {Object}
  12. * config Configuration options
  13. */
  14. Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, {
  15. /**
  16. * @cfg {Mixed} transform The id, DOM node or element of an existing select
  17. * to convert to a ComboBox
  18. */
  19. /**
  20. * @cfg {Boolean} lazyRender True to prevent the ComboBox from rendering
  21. * until requested (should always be used when rendering into an
  22. * Ext.Editor, defaults to false)
  23. */
  24. /**
  25. * @cfg {Boolean/Object} autoCreate A DomHelper element spec, or true for a
  26. * default element spec (defaults to: {tag: "input", type: "text",
  27. * size: "24", autocomplete: "off"})
  28. */
  29. /**
  30. * @cfg {Ext.data.Store} store The data store to which this combo is bound
  31. * (defaults to undefined)
  32. */
  33. /**
  34. * @cfg {String} title If supplied, a header element is created containing
  35. * this text and added into the top of the dropdown list (defaults to
  36. * undefined, with no header element)
  37. */
  38. // private
  39. defaultAutoCreate : {
  40. tag : "input",
  41. type : "text",
  42. size : "24",
  43. autocomplete : "off"
  44. },
  45. /**
  46. * @cfg {Number} listWidth The width in pixels of the dropdown list
  47. * (defaults to the width of the ComboBox field)
  48. */
  49. /**
  50. * @cfg {String} displayField The underlying data field name to bind to this
  51. * ComboBox (defaults to undefined if mode = 'remote' or 'text' if
  52. * transforming a select)
  53. */
  54. /**
  55. * @cfg {String} valueField The underlying data value name to bind to this
  56. * ComboBox (defaults to undefined if mode = 'remote' or 'value' if
  57. * transforming a select) Note: use of a valueField requires the user
  58. * to make a selection in order for a value to be mapped.
  59. */
  60. /**
  61. * @cfg {String} hiddenName If specified, a hidden form field with this name
  62. * is dynamically generated to store the field's data value (defaults
  63. * to the underlying DOM element's name). Required for the combo's
  64. * value to automatically post during a form submission.
  65. */
  66. /**
  67. * @cfg {String} listClass CSS class to apply to the dropdown list element
  68. * (defaults to '')
  69. */
  70. listClass : '',
  71. /**
  72. * @cfg {String} selectedClass CSS class to apply to the selected item in
  73. * the dropdown list (defaults to 'x-combo-selected')
  74. */
  75. selectedClass : 'x-combo-selected',
  76. /**
  77. * @cfg {String} triggerClass An additional CSS class used to style the
  78. * trigger button. The trigger will always get the class
  79. * 'x-form-trigger' and triggerClass will be <b>appended</b> if
  80. * specified (defaults to 'x-form-arrow-trigger' which displays a
  81. * downward arrow icon).
  82. */
  83. triggerClass : 'x-form-arrow-trigger',
  84. /**
  85. * @cfg {Boolean/String} shadow True or "sides" for the default effect,
  86. * "frame" for 4-way shadow, and "drop" for bottom-right
  87. */
  88. shadow : 'sides',
  89. /**
  90. * @cfg {String} listAlign A valid anchor position value. See
  91. * {@link Ext.Element#alignTo} for details on supported anchor
  92. * positions (defaults to 'tl-bl')
  93. */
  94. listAlign : 'tl-bl?',
  95. /**
  96. * @cfg {Number} maxHeight The maximum height in pixels of the dropdown list
  97. * before scrollbars are shown (defaults to 300)
  98. */
  99. maxHeight : 300,
  100. /**
  101. * @cfg {String} triggerAction The action to execute when the trigger field
  102. * is activated. Use 'all' to run the query specified by the allQuery
  103. * config option (defaults to 'query')
  104. */
  105. triggerAction : 'query',
  106. /**
  107. * @cfg {Number} minChars The minimum number of characters the user must
  108. * type before autocomplete and typeahead activate (defaults to 4 if
  109. * remote or 0 if local, does not apply if editable = false)
  110. */
  111. minChars : 4,
  112. /**
  113. * @cfg {Boolean} typeAhead True to populate and autoselect the remainder of
  114. * the text being typed after a configurable delay (typeAheadDelay) if
  115. * it matches a known value (defaults to false)
  116. */
  117. typeAhead : false,
  118. /**
  119. * @cfg {Number} queryDelay The length of time in milliseconds to delay
  120. * between the start of typing and sending the query to filter the
  121. * dropdown list (defaults to 500 if mode = 'remote' or 10 if mode =
  122. * 'local')
  123. */
  124. queryDelay : 500,
  125. /**
  126. * @cfg {Number} pageSize If greater than 0, a paging toolbar is displayed
  127. * in the footer of the dropdown list and the filter queries will
  128. * execute with page start and limit parameters. Only applies when mode =
  129. * 'remote' (defaults to 0)
  130. */
  131. pageSize : 0,
  132. /**
  133. * @cfg {Boolean} selectOnFocus True to select any existing text in the
  134. * field immediately on focus. Only applies when editable = true
  135. * (defaults to false)
  136. */
  137. selectOnFocus : false,
  138. /**
  139. * @cfg {String} queryParam Name of the query as it will be passed on the
  140. * querystring (defaults to 'query')
  141. */
  142. queryParam : 'query',
  143. /**
  144. * @cfg {String} loadingText The text to display in the dropdown list while
  145. * data is loading. Only applies when mode = 'remote' (defaults to
  146. * 'Loading...')
  147. */
  148. loadingText : 'Loading...',
  149. /**
  150. * @cfg {Boolean} resizable True to add a resize handle to the bottom of the
  151. * dropdown list (defaults to false)
  152. */
  153. resizable : false,
  154. /**
  155. * @cfg {Number} handleHeight The height in pixels of the dropdown list
  156. * resize handle if resizable = true (defaults to 8)
  157. */
  158. handleHeight : 8,
  159. /**
  160. * @cfg {Boolean} editable False to prevent the user from typing text
  161. * directly into the field, just like a traditional select (defaults to
  162. * true)
  163. */
  164. editable : true,
  165. /**
  166. * @cfg {String} allQuery The text query to send to the server to return all
  167. * records for the list with no filtering (defaults to '')
  168. */
  169. allQuery : '',
  170. /**
  171. * @cfg {String} mode Set to 'local' if the ComboBox loads local data
  172. * (defaults to 'remote' which loads from the server)
  173. */
  174. mode : 'remote',
  175. /**
  176. * @cfg {Number} minListWidth The minimum width of the dropdown list in
  177. * pixels (defaults to 70, will be ignored if listWidth has a higher
  178. * value)
  179. */
  180. minListWidth : 70,
  181. /**
  182. * @cfg {Boolean} forceSelection True to restrict the selected value to one
  183. * of the values in the list, false to allow the user to set arbitrary
  184. * text into the field (defaults to false)
  185. */
  186. forceSelection : false,
  187. /**
  188. * @cfg {Number} typeAheadDelay The length of time in milliseconds to wait
  189. * until the typeahead text is displayed if typeAhead = true (defaults
  190. * to 250)
  191. */
  192. typeAheadDelay : 250,
  193. /**
  194. * @cfg {String} valueNotFoundText When using a name/value combo, if the
  195. * value passed to setValue is not found in the store,
  196. * valueNotFoundText will be displayed as the field text if defined
  197. * (defaults to undefined)
  198. */
  199. /**
  200. * @cfg {Boolean} lazyInit True to not initialize the list for this combo
  201. * until the field is focused. (defaults to true)
  202. */
  203. lazyInit : true,
  204. initComponent : function() {
  205. Ext.form.ComboBox.superclass.initComponent.call(this);
  206. this.addEvents(
  207. /**
  208. * @event expand Fires when the dropdown list is expanded
  209. * @param {Ext.form.ComboBox}
  210. * combo This combo box
  211. */
  212. 'expand',
  213. /**
  214. * @event collapse Fires when the dropdown list is collapsed
  215. * @param {Ext.form.ComboBox}
  216. * combo This combo box
  217. */
  218. 'collapse',
  219. /**
  220. * @event beforeselect Fires before a list item is selected.
  221. * Return false to cancel the selection.
  222. * @param {Ext.form.ComboBox}
  223. * combo This combo box
  224. * @param {Ext.data.Record}
  225. * record The data record returned from the
  226. * underlying store
  227. * @param {Number}
  228. * index The index of the selected item in the
  229. * dropdown list
  230. */
  231. 'beforeselect',
  232. /**
  233. * @event select Fires when a list item is selected
  234. * @param {Ext.form.ComboBox}
  235. * combo This combo box
  236. * @param {Ext.data.Record}
  237. * record The data record returned from the
  238. * underlying store
  239. * @param {Number}
  240. * index The index of the selected item in the
  241. * dropdown list
  242. */
  243. 'select',
  244. /**
  245. * @event beforequery Fires before all queries are processed.
  246. * Return false to cancel the query or set the
  247. * queryEvent's cancel property to true.
  248. * @param {Object}
  249. * queryEvent An object that has these properties:
  250. * <ul>
  251. * <li><code>combo</code> : Ext.form.ComboBox <div
  252. * class="sub-desc">This combo box</div></li>
  253. * <li><code>query</code> : String <div
  254. * class="sub-desc">The query</div></li>
  255. * <li><code>forceAll</code> : Boolean <div
  256. * class="sub-desc">True to force "all" query</div></li>
  257. * <li><code>cancel</code> : Boolean <div
  258. * class="sub-desc">Set to true to cancel the query</div></li>
  259. * </ul>
  260. */
  261. 'beforequery');
  262. if (this.transform) {
  263. this.allowDomMove = false;
  264. var s = Ext.getDom(this.transform);
  265. if (!this.hiddenName) {
  266. this.hiddenName = s.name;
  267. }
  268. if (!this.store) {
  269. this.mode = 'local';
  270. var d = [], opts = s.options;
  271. for (var i = 0, len = opts.length; i < len; i++) {
  272. var o = opts[i];
  273. var value = (Ext.isIE
  274. ? o.getAttributeNode('value').specified
  275. : o.hasAttribute('value')) ? o.value : o.text;
  276. if (o.selected) {
  277. this.value = value;
  278. }
  279. d.push([value, o.text]);
  280. }
  281. this.store = new Ext.data.SimpleStore({
  282. 'id' : 0,
  283. fields : ['value', 'text'],
  284. data : d
  285. });
  286. this.valueField = 'value';
  287. this.displayField = 'text';
  288. }
  289. s.name = Ext.id(); // wipe out the name in case somewhere else they
  290. // have a reference
  291. if (!this.lazyRender) {
  292. this.target = true;
  293. this.el = Ext.DomHelper.insertBefore(s, this.autoCreate
  294. || this.defaultAutoCreate);
  295. Ext.removeNode(s); // remove it
  296. this.render(this.el.parentNode);
  297. } else {
  298. Ext.removeNode(s); // remove it
  299. }
  300. }
  301. this.selectedIndex = -1;
  302. if (this.mode == 'local') {
  303. if (this.initialConfig.queryDelay === undefined) {
  304. this.queryDelay = 10;
  305. }
  306. if (this.initialConfig.minChars === undefined) {
  307. this.minChars = 0;
  308. }
  309. }
  310. },
  311. // private
  312. onRender : function(ct, position) {
  313. Ext.form.ComboBox.superclass.onRender.call(this, ct, position);
  314. if (this.hiddenName) {
  315. this.hiddenField = this.el.insertSibling({
  316. tag : 'input',
  317. type : 'hidden',
  318. name : this.hiddenName,
  319. id : (this.hiddenId || this.hiddenName)
  320. }, 'before', true);
  321. this.hiddenField.value = this.hiddenValue !== undefined
  322. ? this.hiddenValue
  323. : this.value !== undefined ? this.value : '';
  324. // prevent input submission
  325. this.el.dom.removeAttribute('name');
  326. }
  327. if (Ext.isGecko) {
  328. this.el.dom.setAttribute('autocomplete', 'off');
  329. }
  330. if (!this.lazyInit) {
  331. this.initList();
  332. } else {
  333. this.on('focus', this.initList, this, {
  334. single : true
  335. });
  336. }
  337. if (!this.editable) {
  338. this.editable = true;
  339. this.setEditable(false);
  340. }
  341. },
  342. initList : function() {
  343. if (!this.list) {
  344. var cls = 'x-combo-list';
  345. this.list = new Ext.Layer({
  346. shadow : this.shadow,
  347. cls : [cls, this.listClass].join(' '),
  348. constrain : false
  349. });
  350. var lw = this.listWidth
  351. || Math.max(this.wrap.getWidth(), this.minListWidth);
  352. this.list.setWidth(lw);
  353. this.list.swallowEvent('mousewheel');
  354. this.assetHeight = 0;
  355. if (this.title) {
  356. this.header = this.list.createChild({
  357. cls : cls + '-hd',
  358. html : this.title
  359. });
  360. this.assetHeight += this.header.getHeight();
  361. }
  362. this.innerList = this.list.createChild({
  363. cls : cls + '-inner'
  364. });
  365. this.innerList.on('mouseover', this.onViewOver, this);
  366. this.innerList.on('mousemove', this.onViewMove, this);
  367. this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
  368. if (this.pageSize) {
  369. this.footer = this.list.createChild({
  370. cls : cls + '-ft'
  371. });
  372. this.pageTb = new Ext.PagingToolbar({
  373. store : this.store,
  374. pageSize : this.pageSize,
  375. renderTo : this.footer
  376. });
  377. this.assetHeight += this.footer.getHeight();
  378. }
  379. if (!this.tpl) {
  380. /**
  381. * @cfg {String/Ext.XTemplate} tpl The template string, or
  382. * {@link Ext.XTemplate} instance to use to display each
  383. * item in the dropdown list. Use this to create custom UI
  384. * layouts for items in the list.
  385. * <p>
  386. * If you wish to preserve the default visual look of list
  387. * items, add the CSS class name
  388. *
  389. * <pre>
  390. * x - combo - list - item
  391. * </pre>
  392. *
  393. * to the template's container element.
  394. * <p>
  395. * <b>The template must contain one or more substitution
  396. * parameters using field names from the Combo's</b>
  397. * {@link #store Store}. An example of a custom template
  398. * would be adding an
  399. *
  400. * <pre>
  401. * ext : qtip
  402. * </pre>
  403. *
  404. * attribute which might display other fields from the
  405. * Store.
  406. * <p>
  407. * The dropdown list is displayed in a DataView. See
  408. * {@link Ext.DataView} for details.
  409. */
  410. this.tpl = '<tpl for="."><div class="' + cls + '-item">{'
  411. + this.displayField + '}</div></tpl>';
  412. }
  413. /**
  414. * The {@link Ext.DataView DataView} used to display the ComboBox's
  415. * options.
  416. *
  417. * @type Ext.DataView
  418. */
  419. this.view = new Ext.DataView({
  420. applyTo : this.innerList,
  421. tpl : this.tpl,
  422. singleSelect : true,
  423. selectedClass : this.selectedClass,
  424. itemSelector : this.itemSelector || '.' + cls + '-item'
  425. });
  426. this.view.on('click', this.onViewClick, this);
  427. this.bindStore(this.store, true);
  428. if (this.resizable) {
  429. this.resizer = new Ext.Resizable(this.list, {
  430. pinned : true,
  431. handles : 'se'
  432. });
  433. this.resizer.on('resize', function(r, w, h) {
  434. this.maxHeight = h - this.handleHeight
  435. - this.list.getFrameWidth('tb')
  436. - this.assetHeight;
  437. this.listWidth = w;
  438. this.innerList.setWidth(w
  439. - this.list.getFrameWidth('lr'));
  440. this.restrictHeight();
  441. }, this);
  442. this[this.pageSize ? 'footer' : 'innerList'].setStyle(
  443. 'margin-bottom', this.handleHeight + 'px');
  444. }
  445. }
  446. },
  447. // private
  448. bindStore : function(store, initial) {
  449. if (this.store && !initial) {
  450. this.store.un('beforeload', this.onBeforeLoad, this);
  451. this.store.un('load', this.onLoad, this);
  452. this.store.un('loadexception', this.collapse, this);
  453. if (!store) {
  454. this.store = null;
  455. if (this.view) {
  456. this.view.setStore(null);
  457. }
  458. }
  459. }
  460. if (store) {
  461. this.store = Ext.StoreMgr.lookup(store);
  462. this.store.on('beforeload', this.onBeforeLoad, this);
  463. this.store.on('load', this.onLoad, this);
  464. this.store.on('loadexception', this.collapse, this);
  465. if (this.view) {
  466. this.view.setStore(store);
  467. }
  468. }
  469. },
  470. // private
  471. initEvents : function() {
  472. Ext.form.ComboBox.superclass.initEvents.call(this);
  473. this.keyNav = new Ext.KeyNav(this.el, {
  474. "up" : function(e) {
  475. this.inKeyMode = true;
  476. this.selectPrev();
  477. },
  478. "down" : function(e) {
  479. if (!this.isExpanded()) {
  480. this.onTriggerClick();
  481. } else {
  482. this.inKeyMode = true;
  483. this.selectNext();
  484. }
  485. },
  486. "enter" : function(e) {
  487. this.onViewClick();
  488. // return true;
  489. },
  490. "esc" : function(e) {
  491. this.collapse();
  492. },
  493. "tab" : function(e) {
  494. this.onViewClick(false);
  495. return true;
  496. },
  497. scope : this,
  498. doRelay : function(foo, bar, hname) {
  499. if (hname == 'down' || this.scope.isExpanded()) {
  500. return Ext.KeyNav.prototype.doRelay.apply(this,
  501. arguments);
  502. }
  503. return true;
  504. },
  505. forceKeyDown : true
  506. });
  507. this.queryDelay = Math.max(this.queryDelay || 10, this.mode == 'local'
  508. ? 10
  509. : 250);
  510. this.dqTask = new Ext.util.DelayedTask(this.initQuery, this);
  511. if (this.typeAhead) {
  512. this.taTask = new Ext.util.DelayedTask(this.onTypeAhead, this);
  513. }
  514. if (this.editable !== false) {
  515. this.el.on("keyup", this.onKeyUp, this);
  516. }
  517. if (this.forceSelection) {
  518. this.on('blur', this.doForce, this);
  519. }
  520. },
  521. onDestroy : function() {
  522. if (this.view) {
  523. this.view.el.removeAllListeners();
  524. this.view.el.remove();
  525. this.view.purgeListeners();
  526. }
  527. if (this.list) {
  528. this.list.destroy();
  529. }
  530. this.bindStore(null);
  531. Ext.form.ComboBox.superclass.onDestroy.call(this);
  532. },
  533. // private
  534. fireKey : function(e) {
  535. if (e.isNavKeyPress() && !this.list.isVisible()) {
  536. this.fireEvent("specialkey", this, e);
  537. }
  538. },
  539. // private
  540. onResize : function(w, h) {
  541. Ext.form.ComboBox.superclass.onResize.apply(this, arguments);
  542. if (this.list && this.listWidth === undefined) {
  543. var lw = Math.max(w, this.minListWidth);
  544. this.list.setWidth(lw);
  545. this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
  546. }
  547. },
  548. // private
  549. onDisable : function() {
  550. Ext.form.ComboBox.superclass.onDisable.apply(this, arguments);
  551. if (this.hiddenField) {
  552. this.hiddenField.disabled = this.disabled;
  553. }
  554. },
  555. /**
  556. * Allow or prevent the user from directly editing the field text. If false
  557. * is passed, the user will only be able to select from the items defined in
  558. * the dropdown list. This method is the runtime equivalent of setting the
  559. * 'editable' config option at config time.
  560. *
  561. * @param {Boolean}
  562. * value True to allow the user to directly edit the field text
  563. */
  564. setEditable : function(value) {
  565. if (value == this.editable) {
  566. return;
  567. }
  568. this.editable = value;
  569. if (!value) {
  570. this.el.dom.setAttribute('readOnly', true);
  571. this.el.on('mousedown', this.onTriggerClick, this);
  572. this.el.addClass('x-combo-noedit');
  573. } else {
  574. this.el.dom.setAttribute('readOnly', false);
  575. this.el.un('mousedown', this.onTriggerClick, this);
  576. this.el.removeClass('x-combo-noedit');
  577. }
  578. },
  579. // private
  580. onBeforeLoad : function() {
  581. if (!this.hasFocus) {
  582. return;
  583. }
  584. this.innerList.update(this.loadingText
  585. ? '<div class="loading-indicator">' + this.loadingText
  586. + '</div>'
  587. : '');
  588. this.restrictHeight();
  589. this.selectedIndex = -1;
  590. },
  591. // private
  592. onLoad : function() {
  593. if (!this.hasFocus) {
  594. return;
  595. }
  596. if (this.store.getCount() > 0) {
  597. this.expand();
  598. this.restrictHeight();
  599. if (this.lastQuery == this.allQuery) {
  600. if (this.editable) {
  601. this.el.dom.select();
  602. }
  603. if (!this.selectByValue(this.value, true)) {
  604. this.select(0, true);
  605. }
  606. } else {
  607. this.selectNext();
  608. if (this.typeAhead && this.lastKey != Ext.EventObject.BACKSPACE
  609. && this.lastKey != Ext.EventObject.DELETE) {
  610. this.taTask.delay(this.typeAheadDelay);
  611. }
  612. }
  613. } else {
  614. this.onEmptyResults();
  615. }
  616. // this.el.focus();
  617. },
  618. // private
  619. onTypeAhead : function() {
  620. if (this.store.getCount() > 0) {
  621. var r = this.store.getAt(0);
  622. var newValue = r.data[this.displayField];
  623. var len = newValue.length;
  624. var selStart = this.getRawValue().length;
  625. if (selStart != len) {
  626. this.setRawValue(newValue);
  627. this.selectText(selStart, newValue.length);
  628. }
  629. }
  630. },
  631. // private
  632. onSelect : function(record, index) {
  633. if (this.fireEvent('beforeselect', this, record, index) !== false) {
  634. this.setValue(record.data[this.valueField || this.displayField]);
  635. this.collapse();
  636. this.fireEvent('select', this, record, index);
  637. }
  638. },
  639. /**
  640. * Returns the currently selected field value or empty string if no value is
  641. * set.
  642. *
  643. * @return {String} value The selected value
  644. */
  645. getValue : function() {
  646. if (this.valueField) {
  647. return typeof this.value != 'undefined' ? this.value : '';
  648. } else {
  649. return Ext.form.ComboBox.superclass.getValue.call(this);
  650. }
  651. },
  652. /**
  653. * Clears any text/value currently set in the field
  654. */
  655. clearValue : function() {
  656. if (this.hiddenField) {
  657. this.hiddenField.value = '';
  658. }
  659. this.setRawValue('');
  660. this.lastSelectionText = '';
  661. this.applyEmptyText();
  662. },
  663. /**
  664. * Sets the specified value into the field. If the value finds a match, the
  665. * corresponding record text will be displayed in the field. If the value
  666. * does not match the data value of an existing item, and the
  667. * valueNotFoundText config option is defined, it will be displayed as the
  668. * default field text. Otherwise the field will be blank (although the value
  669. * will still be set).
  670. *
  671. * @param {String}
  672. * value The value to match
  673. */
  674. setValue : function(v) {
  675. var text = v;
  676. if (this.valueField) {
  677. var r = this.findRecord(this.valueField, v);
  678. if (r) {
  679. text = r.data[this.displayField];
  680. } else if (this.valueNotFoundText !== undefined) {
  681. text = this.valueNotFoundText;
  682. }
  683. }
  684. this.lastSelectionText = text;
  685. if (this.hiddenField) {
  686. this.hiddenField.value = v;
  687. }
  688. Ext.form.ComboBox.superclass.setValue.call(this, text);
  689. this.value = v;
  690. },
  691. // private
  692. findRecord : function(prop, value) {
  693. var record;
  694. if (this.store.getCount() > 0) {
  695. this.store.each(function(r) {
  696. if (r.data[prop] == value) {
  697. record = r;
  698. return false;
  699. }
  700. });
  701. }
  702. return record;
  703. },
  704. // private
  705. onViewMove : function(e, t) {
  706. this.inKeyMode = false;
  707. },
  708. // private
  709. onViewOver : function(e, t) {
  710. if (this.inKeyMode) { // prevent key nav and mouse over conflicts
  711. return;
  712. }
  713. var item = this.view.findItemFromChild(t);
  714. if (item) {
  715. var index = this.view.indexOf(item);
  716. this.select(index, false);
  717. }
  718. },
  719. // private
  720. onViewClick : function(doFocus) {
  721. var index = this.view.getSelectedIndexes()[0];
  722. var r = this.store.getAt(index);
  723. if (r) {
  724. this.onSelect(r, index);
  725. }
  726. if (doFocus !== false) {
  727. this.el.focus();
  728. }
  729. },
  730. // private
  731. restrictHeight : function() {
  732. this.innerList.dom.style.height = '';
  733. var inner = this.innerList.dom;
  734. var fw = this.list.getFrameWidth('tb');
  735. var h = Math.max(inner.clientHeight, inner.offsetHeight,
  736. inner.scrollHeight);
  737. this.innerList.setHeight(h < this.maxHeight ? 'auto' : this.maxHeight);
  738. this.list.beginUpdate();
  739. this.list.setHeight(this.innerList.getHeight() + fw
  740. + (this.resizable ? this.handleHeight : 0) + this.assetHeight);
  741. this.list.alignTo(this.el, this.listAlign);
  742. this.list.endUpdate();
  743. },
  744. // private
  745. onEmptyResults : function() {
  746. this.collapse();
  747. },
  748. /**
  749. * Returns true if the dropdown list is expanded, else false.
  750. */
  751. isExpanded : function() {
  752. return this.list && this.list.isVisible();
  753. },
  754. /**
  755. * Select an item in the dropdown list by its data value. This function does
  756. * NOT cause the select event to fire. The store must be loaded and the list
  757. * expanded for this function to work, otherwise use setValue.
  758. *
  759. * @param {String}
  760. * value The data value of the item to select
  761. * @param {Boolean}
  762. * scrollIntoView False to prevent the dropdown list from
  763. * autoscrolling to display the selected item if it is not
  764. * currently in view (defaults to true)
  765. * @return {Boolean} True if the value matched an item in the list, else
  766. * false
  767. */
  768. selectByValue : function(v, scrollIntoView) {
  769. if (v !== undefined && v !== null) {
  770. var r = this.findRecord(this.valueField || this.displayField, v);
  771. if (r) {
  772. this.select(this.store.indexOf(r), scrollIntoView);
  773. return true;
  774. }
  775. }
  776. return false;
  777. },
  778. /**
  779. * Select an item in the dropdown list by its numeric index in the list.
  780. * This function does NOT cause the select event to fire. The store must be
  781. * loaded and the list expanded for this function to work, otherwise use
  782. * setValue.
  783. *
  784. * @param {Number}
  785. * index The zero-based index of the list item to select
  786. * @param {Boolean}
  787. * scrollIntoView False to prevent the dropdown list from
  788. * autoscrolling to display the selected item if it is not
  789. * currently in view (defaults to true)
  790. */
  791. select : function(index, scrollIntoView) {
  792. this.selectedIndex = index;
  793. this.view.select(index);
  794. if (scrollIntoView !== false) {
  795. var el = this.view.getNode(index);
  796. if (el) {
  797. this.innerList.scrollChildIntoView(el, false);
  798. }
  799. }
  800. },
  801. // private
  802. selectNext : function() {
  803. var ct = this.store.getCount();
  804. if (ct > 0) {
  805. if (this.selectedIndex == -1) {
  806. this.select(0);
  807. } else if (this.selectedIndex < ct - 1) {
  808. this.select(this.selectedIndex + 1);
  809. }
  810. }
  811. },
  812. // private
  813. selectPrev : function() {
  814. var ct = this.store.getCount();
  815. if (ct > 0) {
  816. if (this.selectedIndex == -1) {
  817. this.select(0);
  818. } else if (this.selectedIndex != 0) {
  819. this.select(this.selectedIndex - 1);
  820. }
  821. }
  822. },
  823. // private
  824. onKeyUp : function(e) {
  825. if (this.editable !== false && !e.isSpecialKey()) {
  826. this.lastKey = e.getKey();
  827. this.dqTask.delay(this.queryDelay);
  828. }
  829. },
  830. // private
  831. validateBlur : function() {
  832. return !this.list || !this.list.isVisible();
  833. },
  834. // private
  835. initQuery : function() {
  836. this.doQuery(this.getRawValue());
  837. },
  838. // private
  839. doForce : function() {
  840. if (this.el.dom.value.length > 0) {
  841. this.el.dom.value = this.lastSelectionText === undefined
  842. ? ''
  843. : this.lastSelectionText;
  844. this.applyEmptyText();
  845. }
  846. },
  847. /**
  848. * Execute a query to filter the dropdown list. Fires the beforequery event
  849. * prior to performing the query allowing the query action to be canceled if
  850. * needed.
  851. *
  852. * @param {String}
  853. * query The SQL query to execute
  854. * @param {Boolean}
  855. * forceAll True to force the query to execute even if there are
  856. * currently fewer characters in the field than the minimum
  857. * specified by the minChars config option. It also clears any
  858. * filter previously saved in the current store (defaults to
  859. * false)
  860. */
  861. doQuery : function(q, forceAll) {
  862. if (q === undefined || q === null) {
  863. q = '';
  864. }
  865. var qe = {
  866. query : q,
  867. forceAll : forceAll,
  868. combo : this,
  869. cancel : false
  870. };
  871. if (this.fireEvent('beforequery', qe) === false || qe.cancel) {
  872. return false;
  873. }
  874. q = qe.query;
  875. forceAll = qe.forceAll;
  876. if (forceAll === true || (q.length >= this.minChars)) {
  877. if (this.lastQuery !== q) {
  878. this.lastQuery = q;
  879. if (this.mode == 'local') {
  880. this.selectedIndex = -1;
  881. if (forceAll) {
  882. this.store.clearFilter();
  883. } else {
  884. this.store.filter(this.displayField, q);
  885. }
  886. this.onLoad();
  887. } else {
  888. this.store.baseParams[this.queryParam] = q;
  889. this.store.load({
  890. params : this.getParams(q)
  891. });
  892. this.expand();
  893. }
  894. } else {
  895. this.selectedIndex = -1;
  896. this.onLoad();
  897. }
  898. }
  899. },
  900. // private
  901. getParams : function(q) {
  902. var p = {};
  903. // p[this.queryParam] = q;
  904. if (this.pageSize) {
  905. p.start = 0;
  906. p.limit = this.pageSize;
  907. }
  908. return p;
  909. },
  910. /**
  911. * Hides the dropdown list if it is currently expanded. Fires the 'collapse'
  912. * event on completion.
  913. */
  914. collapse : function() {
  915. if (!this.isExpanded()) {
  916. return;
  917. }
  918. this.list.hide();
  919. Ext.getDoc().un('mousewheel', this.collapseIf, this);
  920. Ext.getDoc().un('mousedown', this.collapseIf, this);
  921. this.fireEvent('collapse', this);
  922. },
  923. // private
  924. collapseIf : function(e) {
  925. if (!e.within(this.wrap) && !e.within(this.list)) {
  926. this.collapse();
  927. }
  928. },
  929. /**
  930. * Expands the dropdown list if it is currently hidden. Fires the 'expand'
  931. * event on completion.
  932. */
  933. expand : function() {
  934. if (this.isExpanded() || !this.hasFocus) {
  935. return;
  936. }
  937. this.list.alignTo(this.wrap, this.listAlign);
  938. this.list.show();
  939. Ext.getDoc().on('mousewheel', this.collapseIf, this);
  940. Ext.getDoc().on('mousedown', this.collapseIf, this);
  941. this.fireEvent('expand', this);
  942. },
  943. // private
  944. // Implements the default empty TriggerField.onTriggerClick function
  945. onTriggerClick : function() {
  946. if (this.disabled) {
  947. return;
  948. }
  949. if (this.isExpanded()) {
  950. this.collapse();
  951. this.el.focus();
  952. } else {
  953. this.onFocus({});
  954. if (this.triggerAction == 'all') {
  955. this.doQuery(this.allQuery, true);
  956. } else {
  957. this.doQuery(this.getRawValue());
  958. }
  959. this.el.focus();
  960. }
  961. }
  962. /**
  963. * @hide
  964. * @method autoSize
  965. */
  966. /**
  967. * @cfg {Boolean} grow
  968. * @hide
  969. */
  970. /**
  971. * @cfg {Number} growMin
  972. * @hide
  973. */
  974. /**
  975. * @cfg {Number} growMax
  976. * @hide
  977. */
  978. });
  979. Ext.reg('combo', Ext.form.ComboBox);