superboxselect.js 63 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889
  1. Ext.namespace('Ext.ux.form');
  2. /**
  3. * <p>SuperBoxSelect is an extension of the ComboBox component that displays selected items as labelled boxes within the form field. As seen on facebook, hotmail and other sites.</p>
  4. *
  5. * @author <a href="mailto:dan.humphrey@technomedia.co.uk">Dan Humphrey</a>
  6. * @class Ext.ux.form.SuperBoxSelect
  7. * @extends Ext.form.ComboBox
  8. * @constructor
  9. * @component
  10. * @version 1.0
  11. * @license TBA (To be announced)
  12. *
  13. */
  14. Ext.ux.form.SuperBoxSelect = function(config) {
  15. Ext.ux.form.SuperBoxSelect.superclass.constructor.call(this,config);
  16. this.addEvents(
  17. /**
  18. * Fires before an item is added to the component via user interaction. Return false from the callback function to prevent the item from being added.
  19. * @event beforeadditem
  20. * @memberOf Ext.ux.form.SuperBoxSelect
  21. * @param {SuperBoxSelect} this
  22. * @param {Mixed} value The value of the item to be added
  23. * @param {Record} rec The record being added
  24. * @param {Mixed} filtered Any filtered query data (if using queryFilterRe)
  25. */
  26. 'beforeadditem',
  27. /**
  28. * Fires after a new item is added to the component.
  29. * @event additem
  30. * @memberOf Ext.ux.form.SuperBoxSelect
  31. * @param {SuperBoxSelect} this
  32. * @param {Mixed} value The value of the item which was added
  33. * @param {Record} record The store record which was added
  34. */
  35. 'additem',
  36. /**
  37. * Fires when the allowAddNewData config is set to true, and a user attempts to add an item that is not in the data store.
  38. * @event newitem
  39. * @memberOf Ext.ux.form.SuperBoxSelect
  40. * @param {SuperBoxSelect} this
  41. * @param {Mixed} value The new item's value
  42. * @param {Mixed} filtered Any filtered query data (if using queryFilterRe)
  43. */
  44. 'newitem',
  45. /**
  46. * Fires when an item's remove button is clicked. Return false from the callback function to prevent the item from being removed.
  47. * @event beforeremoveitem
  48. * @memberOf Ext.ux.form.SuperBoxSelect
  49. * @param {SuperBoxSelect} this
  50. * @param {Mixed} value The value of the item to be removed
  51. */
  52. 'beforeremoveitem',
  53. /**
  54. * Fires after an item has been removed.
  55. * @event removeitem
  56. * @memberOf Ext.ux.form.SuperBoxSelect
  57. * @param {SuperBoxSelect} this
  58. * @param {Mixed} value The value of the item which was removed
  59. * @param {Record} record The store record which was removed
  60. */
  61. 'removeitem',
  62. /**
  63. * Fires after the component values have been cleared.
  64. * @event clear
  65. * @memberOf Ext.ux.form.SuperBoxSelect
  66. * @param {SuperBoxSelect} this
  67. */
  68. 'clear'
  69. );
  70. };
  71. /**
  72. * @private hide from doc gen
  73. */
  74. Ext.ux.form.SuperBoxSelect = Ext.extend(Ext.ux.form.SuperBoxSelect,Ext.form.ComboBox,{
  75. /**
  76. * @cfg {Boolean} addNewDataOnBlur Allows adding new items when the user tabs from the input element.
  77. */
  78. addNewDataOnBlur : false,
  79. /**
  80. * @cfg {Boolean} allowAddNewData When set to true, allows items to be added (via the setValueEx and addItem methods) that do not already exist in the data store. Defaults to false.
  81. */
  82. allowAddNewData: false,
  83. /**
  84. * @cfg {Boolean} allowQueryAll When set to false, prevents the trigger arrow from rendering, and the DOWN key from triggering a query all. Defaults to true.
  85. */
  86. allowQueryAll : true,
  87. /**
  88. * @cfg {Boolean} backspaceDeletesLastItem When set to false, the BACKSPACE key will focus the last selected item. When set to true, the last item will be immediately deleted. Defaults to true.
  89. */
  90. backspaceDeletesLastItem: true,
  91. /**
  92. * @cfg {String} classField The underlying data field that will be used to supply an additional class to each item.
  93. */
  94. classField: null,
  95. /**
  96. * @cfg {String} clearBtnCls An additional class to add to the in-field clear button.
  97. */
  98. clearBtnCls: '',
  99. /**
  100. * @cfg {Boolean} clearLastQueryOnEscape When set to true, the escape key will clear the lastQuery, enabling the previous query to be repeated.
  101. */
  102. clearLastQueryOnEscape : false,
  103. /**
  104. * @cfg {Boolean} clearOnEscape When set to true, the escape key will clear the input text when the component is not expanded.
  105. */
  106. clearOnEscape : false,
  107. /**
  108. * @cfg {String/XTemplate} displayFieldTpl A template for rendering the displayField in each selected item. Defaults to null.
  109. */
  110. displayFieldTpl: null,
  111. /**
  112. * @cfg {String} extraItemCls An additional css class to apply to each item.
  113. */
  114. extraItemCls: '',
  115. /**
  116. * @cfg {String/Object/Function} extraItemStyle Additional css style(s) to apply to each item. Should be a valid argument to Ext.Element.applyStyles.
  117. */
  118. extraItemStyle: '',
  119. /**
  120. * @cfg {String} expandBtnCls An additional class to add to the in-field expand button.
  121. */
  122. expandBtnCls: '',
  123. /**
  124. * @cfg {Boolean} fixFocusOnTabSelect When set to true, the component will not lose focus when a list item is selected with the TAB key. Defaults to true.
  125. */
  126. fixFocusOnTabSelect: true,
  127. /**
  128. * @cfg {Boolean} forceFormValue When set to true, the component will always return a value to the parent form getValues method, and when the parent form is submitted manually. Defaults to false, meaning the component will only be included in the parent form submission (or getValues) if at least 1 item has been selected.
  129. */
  130. forceFormValue: true,
  131. /**
  132. * @cfg {Boolean} forceSameValueQuery When set to true, the component will always query the server even when the last query was the same. Defaults to false.
  133. */
  134. forceSameValueQuery : false,
  135. /**
  136. * @cfg {Number} itemDelimiterKey A key code which terminates keying in of individual items, and adds the current
  137. * item to the list. Defaults to the ENTER key.
  138. */
  139. itemDelimiterKey: Ext.EventObject.ENTER,
  140. /**
  141. * @cfg {Boolean} navigateItemsWithTab When set to true the tab key will navigate between selected items. Defaults to true.
  142. */
  143. navigateItemsWithTab: true,
  144. /**
  145. * @cfg {Boolean} pinList When set to true and the list is opened via the arrow button, the select list will be pinned to allow for multiple selections. Defaults to true.
  146. */
  147. pinList: true,
  148. /**
  149. * @cfg {Boolean} preventDuplicates When set to true unique item values will be enforced. Defaults to true.
  150. */
  151. preventDuplicates: true,
  152. /**
  153. * @cfg {String|Regex} queryFilterRe Used to filter input values before querying the server, specifically useful when allowAddNewData is true as the filtered portion of the query will be passed to the newItem callback.
  154. */
  155. queryFilterRe: '',
  156. /**
  157. * @cfg {String} queryValuesDelimiter Used to delimit multiple values queried from the server when mode is remote.
  158. */
  159. queryValuesDelimiter: '|',
  160. /**
  161. * @cfg {String} queryValuesIndicator A request variable that is sent to the server (as true) to indicate that we are querying values rather than display data (as used in autocomplete) when mode is remote.
  162. */
  163. queryValuesIndicator: 'valuesqry',
  164. /**
  165. * @cfg {Boolean} removeValuesFromStore When set to true, selected records will be removed from the store. Defaults to true.
  166. */
  167. removeValuesFromStore: true,
  168. /**
  169. * @cfg {String} renderFieldBtns When set to true, will render in-field buttons for clearing the component, and displaying the list for selection. Defaults to true.
  170. */
  171. renderFieldBtns: true,
  172. /**
  173. * @cfg {Boolean} stackItems When set to true, the items will be stacked 1 per line. Defaults to false which displays the items inline.
  174. */
  175. stackItems: false,
  176. /**
  177. * @cfg {String} styleField The underlying data field that will be used to supply additional css styles to each item.
  178. */
  179. styleField : null,
  180. /**
  181. * @cfg {Boolean} supressClearValueRemoveEvents When true, the removeitem event will not be fired for each item when the clearValue method is called, or when the clear button is used. Defaults to false.
  182. */
  183. supressClearValueRemoveEvents : false,
  184. /**
  185. * @cfg {String/Boolean} validationEvent The event that should initiate field validation. Set to false to disable automatic validation (defaults to 'blur').
  186. */
  187. validationEvent : 'blur',
  188. /**
  189. * @cfg {String} valueDelimiter The delimiter to use when joining and splitting value arrays and strings.
  190. */
  191. valueDelimiter: ',',
  192. initComponent:function() {
  193. Ext.apply(this, {
  194. items : new Ext.util.MixedCollection(false),
  195. usedRecords : new Ext.util.MixedCollection(false),
  196. addedRecords : [],
  197. remoteLookup : [],
  198. hideTrigger : true,
  199. grow : false,
  200. resizable : false,
  201. multiSelectMode : false,
  202. preRenderValue : null,
  203. filteredQueryData: ''
  204. });
  205. if(this.queryFilterRe){
  206. if(Ext.isString(this.queryFilterRe)){
  207. this.queryFilterRe = new RegExp(this.queryFilterRe);
  208. }
  209. }
  210. if(this.transform){
  211. this.doTransform();
  212. }
  213. if(this.forceFormValue){
  214. this.items.on({
  215. add: this.manageNameAttribute,
  216. remove: this.manageNameAttribute,
  217. clear: this.manageNameAttribute,
  218. scope: this
  219. });
  220. }
  221. Ext.ux.form.SuperBoxSelect.superclass.initComponent.call(this);
  222. if(this.mode === 'remote' && this.store){
  223. this.store.on('load', this.onStoreLoad, this);
  224. }
  225. },
  226. onRender:function(ct, position) {
  227. var h = this.hiddenName;
  228. this.hiddenName = null;
  229. Ext.ux.form.SuperBoxSelect.superclass.onRender.call(this, ct, position);
  230. this.hiddenName = h;
  231. this.manageNameAttribute();
  232. var extraClass = (this.stackItems === true) ? 'x-superboxselect-stacked' : '';
  233. if(this.renderFieldBtns){
  234. extraClass += ' x-superboxselect-display-btns';
  235. }
  236. this.el.removeClass('x-form-text').addClass('x-superboxselect-input-field');
  237. this.wrapEl = this.el.wrap({
  238. tag : 'ul'
  239. });
  240. this.outerWrapEl = this.wrapEl.wrap({
  241. tag : 'div',
  242. cls: 'x-form-text x-superboxselect ' + extraClass
  243. });
  244. this.inputEl = this.el.wrap({
  245. tag : 'li',
  246. cls : 'x-superboxselect-input'
  247. });
  248. if(this.renderFieldBtns){
  249. this.setupFieldButtons().manageClearBtn();
  250. }
  251. this.setupFormInterception();
  252. },
  253. doTransform : function() {
  254. var s = Ext.getDom(this.transform), transformValues = [];
  255. if(!this.store){
  256. this.mode = 'local';
  257. var d = [], opts = s.options;
  258. for(var i = 0, len = opts.length;i < len; i++){
  259. var o = opts[i], oe = Ext.get(o),
  260. value = oe.getAttributeNS(null,'value') || '',
  261. cls = oe.getAttributeNS(null,'className') || '',
  262. style = oe.getAttributeNS(null,'style') || '';
  263. if(o.selected) {
  264. transformValues.push(value);
  265. }
  266. d.push([value, o.text, cls, typeof(style) === "string" ? style : style.cssText]);
  267. }
  268. this.store = new Ext.data.SimpleStore({
  269. 'id': 0,
  270. fields: ['value', 'text', 'cls', 'style'],
  271. data : d
  272. });
  273. Ext.apply(this,{
  274. valueField: 'value',
  275. displayField: 'text',
  276. classField: 'cls',
  277. styleField: 'style'
  278. });
  279. }
  280. if(transformValues.length){
  281. this.value = transformValues.join(',');
  282. }
  283. },
  284. setupFieldButtons : function(){
  285. this.buttonWrap = this.outerWrapEl.createChild({
  286. cls: 'x-superboxselect-btns'
  287. });
  288. this.buttonClear = this.buttonWrap.createChild({
  289. tag:'div',
  290. cls: 'x-superboxselect-btn-clear ' + this.clearBtnCls
  291. });
  292. if(this.allowQueryAll){
  293. this.buttonExpand = this.buttonWrap.createChild({
  294. tag:'div',
  295. cls: 'x-superboxselect-btn-expand ' + this.expandBtnCls
  296. });
  297. }
  298. this.initButtonEvents();
  299. return this;
  300. },
  301. initButtonEvents : function() {
  302. this.buttonClear.addClassOnOver('x-superboxselect-btn-over').on('click', function(e) {
  303. e.stopEvent();
  304. if (this.disabled) {
  305. return;
  306. }
  307. this.clearValue();
  308. this.el.focus();
  309. }, this);
  310. if(this.allowQueryAll){
  311. this.buttonExpand.addClassOnOver('x-superboxselect-btn-over').on('click', function(e) {
  312. e.stopEvent();
  313. if (this.disabled) {
  314. return;
  315. }
  316. if (this.isExpanded()) {
  317. this.multiSelectMode = false;
  318. } else if (this.pinList) {
  319. this.multiSelectMode = true;
  320. }
  321. this.onTriggerClick();
  322. }, this);
  323. }
  324. },
  325. removeButtonEvents : function() {
  326. this.buttonClear.removeAllListeners();
  327. if(this.allowQueryAll){
  328. this.buttonExpand.removeAllListeners();
  329. }
  330. return this;
  331. },
  332. clearCurrentFocus : function(){
  333. if(this.currentFocus){
  334. this.currentFocus.onLnkBlur();
  335. this.currentFocus = null;
  336. }
  337. return this;
  338. },
  339. initEvents : function() {
  340. var el = this.el;
  341. el.on({
  342. click : this.onClick,
  343. focus : this.clearCurrentFocus,
  344. blur : this.onBlur,
  345. keydown : this.onKeyDownHandler,
  346. keyup : this.onKeyUpBuffered,
  347. scope : this
  348. });
  349. this.on({
  350. collapse: this.onCollapse,
  351. expand: this.clearCurrentFocus,
  352. scope: this
  353. });
  354. this.wrapEl.on('click', this.onWrapClick, this);
  355. this.outerWrapEl.on('click', this.onWrapClick, this);
  356. this.inputEl.focus = function() {
  357. el.focus();
  358. };
  359. Ext.ux.form.SuperBoxSelect.superclass.initEvents.call(this);
  360. Ext.apply(this.keyNav, {
  361. tab: function(e) {
  362. if (this.fixFocusOnTabSelect && this.isExpanded()) {
  363. e.stopEvent();
  364. el.blur();
  365. this.onViewClick(false);
  366. this.focus(false, 10);
  367. return true;
  368. }
  369. this.onViewClick(false);
  370. if (el.dom.value !== '') {
  371. this.setRawValue('');
  372. }
  373. return true;
  374. },
  375. down: function(e) {
  376. if (!this.isExpanded() && !this.currentFocus) {
  377. if(this.allowQueryAll){
  378. this.onTriggerClick();
  379. }
  380. } else {
  381. this.inKeyMode = true;
  382. this.selectNext();
  383. }
  384. },
  385. enter: function(){}
  386. });
  387. },
  388. onClick: function() {
  389. this.clearCurrentFocus();
  390. this.collapse();
  391. this.autoSize();
  392. },
  393. beforeBlur: function(){
  394. if(this.allowAddNewData && this.addNewDataOnBlur){
  395. var v = this.el.dom.value;
  396. if(v !== ''){
  397. this.fireNewItemEvent(v);
  398. }
  399. }
  400. Ext.form.ComboBox.superclass.beforeBlur.call(this);
  401. },
  402. onFocus: function() {
  403. this.outerWrapEl.addClass(this.focusClass);
  404. Ext.ux.form.SuperBoxSelect.superclass.onFocus.call(this);
  405. },
  406. onBlur: function() {
  407. this.outerWrapEl.removeClass(this.focusClass);
  408. this.clearCurrentFocus();
  409. if (this.el.dom.value !== '') {
  410. this.applyEmptyText();
  411. this.autoSize();
  412. }
  413. Ext.ux.form.SuperBoxSelect.superclass.onBlur.call(this);
  414. },
  415. onCollapse: function() {
  416. this.view.clearSelections();
  417. this.multiSelectMode = false;
  418. },
  419. onWrapClick: function(e) {
  420. e.stopEvent();
  421. this.collapse();
  422. this.el.focus();
  423. this.clearCurrentFocus();
  424. },
  425. markInvalid : function(msg) {
  426. var elp, t;
  427. if (!this.rendered || this.preventMark ) {
  428. return;
  429. }
  430. this.outerWrapEl.addClass(this.invalidClass);
  431. msg = msg || this.invalidText;
  432. switch (this.msgTarget) {
  433. case 'qtip':
  434. Ext.apply(this.el.dom, {
  435. qtip : msg,
  436. qclass : 'x-form-invalid-tip'
  437. });
  438. Ext.apply(this.wrapEl.dom, {
  439. qtip : msg,
  440. qclass : 'x-form-invalid-tip'
  441. });
  442. if (Ext.QuickTips) { // fix for floating editors interacting with DND
  443. Ext.QuickTips.enable();
  444. }
  445. break;
  446. case 'title':
  447. this.el.dom.title = msg;
  448. this.wrapEl.dom.title = msg;
  449. this.outerWrapEl.dom.title = msg;
  450. break;
  451. case 'under':
  452. if (!this.errorEl) {
  453. elp = this.getErrorCt();
  454. if (!elp) { // field has no container el
  455. this.el.dom.title = msg;
  456. break;
  457. }
  458. this.errorEl = elp.createChild({cls:'x-form-invalid-msg'});
  459. this.errorEl.setWidth(elp.getWidth(true) - 20);
  460. }
  461. this.errorEl.update(msg);
  462. Ext.form.Field.msgFx[this.msgFx].show(this.errorEl, this);
  463. break;
  464. case 'side':
  465. if (!this.errorIcon) {
  466. elp = this.getErrorCt();
  467. if (!elp) { // field has no container el
  468. this.el.dom.title = msg;
  469. break;
  470. }
  471. this.errorIcon = elp.createChild({cls:'x-form-invalid-icon'});
  472. }
  473. this.alignErrorIcon();
  474. Ext.apply(this.errorIcon.dom, {
  475. qtip : msg,
  476. qclass : 'x-form-invalid-tip'
  477. });
  478. this.errorIcon.show();
  479. this.on('resize', this.alignErrorIcon, this);
  480. break;
  481. default:
  482. t = Ext.getDom(this.msgTarget);
  483. t.innerHTML = msg;
  484. t.style.display = this.msgDisplay;
  485. break;
  486. }
  487. this.fireEvent('invalid', this, msg);
  488. },
  489. clearInvalid : function(){
  490. if(!this.rendered || this.preventMark){ // not rendered
  491. return;
  492. }
  493. this.outerWrapEl.removeClass(this.invalidClass);
  494. switch(this.msgTarget){
  495. case 'qtip':
  496. this.el.dom.qtip = '';
  497. this.wrapEl.dom.qtip ='';
  498. break;
  499. case 'title':
  500. this.el.dom.title = '';
  501. this.wrapEl.dom.title = '';
  502. this.outerWrapEl.dom.title = '';
  503. break;
  504. case 'under':
  505. if(this.errorEl){
  506. Ext.form.Field.msgFx[this.msgFx].hide(this.errorEl, this);
  507. }
  508. break;
  509. case 'side':
  510. if(this.errorIcon){
  511. this.errorIcon.dom.qtip = '';
  512. this.errorIcon.hide();
  513. this.un('resize', this.alignErrorIcon, this);
  514. }
  515. break;
  516. default:
  517. var t = Ext.getDom(this.msgTarget);
  518. t.innerHTML = '';
  519. t.style.display = 'none';
  520. break;
  521. }
  522. this.fireEvent('valid', this);
  523. },
  524. alignErrorIcon : function(){
  525. if(this.wrap){
  526. this.errorIcon.alignTo(this.wrap, 'tl-tr', [Ext.isIE ? 5 : 2, 3]);
  527. }
  528. },
  529. expand : function(){
  530. if (this.isExpanded() || !this.hasFocus) {
  531. return;
  532. }
  533. if(this.bufferSize){
  534. this.doResize(this.bufferSize);
  535. delete this.bufferSize;
  536. }
  537. this.list.alignTo(this.outerWrapEl, this.listAlign).show();
  538. this.innerList.setOverflow('auto'); // necessary for FF 2.0/Mac
  539. this.mon(Ext.getDoc(), {
  540. scope: this,
  541. mousewheel: this.collapseIf,
  542. mousedown: this.collapseIf
  543. });
  544. this.fireEvent('expand', this);
  545. },
  546. restrictHeight : function(){
  547. var inner = this.innerList.dom,
  548. st = inner.scrollTop,
  549. list = this.list;
  550. inner.style.height = '';
  551. var pad = list.getFrameWidth('tb')+(this.resizable?this.handleHeight:0)+this.assetHeight,
  552. h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight),
  553. ha = this.getPosition()[1]-Ext.getBody().getScroll().top,
  554. hb = Ext.lib.Dom.getViewHeight()-ha-this.getSize().height,
  555. space = Math.max(ha, hb, this.minHeight || 0)-list.shadowOffset-pad-5;
  556. h = Math.min(h, space, this.maxHeight);
  557. this.innerList.setHeight(h);
  558. list.beginUpdate();
  559. list.setHeight(h+pad);
  560. list.alignTo(this.outerWrapEl, this.listAlign);
  561. list.endUpdate();
  562. if(this.multiSelectMode){
  563. inner.scrollTop = st;
  564. }
  565. },
  566. validateValue: function(val){
  567. if(this.items.getCount() === 0){
  568. if(this.allowBlank){
  569. this.clearInvalid();
  570. return true;
  571. }else{
  572. this.markInvalid(this.blankText);
  573. return false;
  574. }
  575. }
  576. this.clearInvalid();
  577. return true;
  578. },
  579. manageNameAttribute : function(){
  580. if(this.items.getCount() === 0 && this.forceFormValue){
  581. this.el.dom.setAttribute('name', this.hiddenName || this.name);
  582. }else{
  583. this.el.dom.removeAttribute('name');
  584. }
  585. },
  586. setupFormInterception : function(){
  587. var form;
  588. this.findParentBy(function(p){
  589. if(p.getForm){
  590. form = p.getForm();
  591. }
  592. });
  593. if(form){
  594. var formGet = form.getValues;
  595. form.getValues = function(asString){
  596. this.el.dom.disabled = true;
  597. var oldVal = this.el.dom.value;
  598. this.setRawValue('');
  599. var vals = formGet.call(form);
  600. this.el.dom.disabled = false;
  601. this.setRawValue(oldVal);
  602. if(this.forceFormValue && this.items.getCount() === 0){
  603. vals[this.name] = '';
  604. }
  605. return asString ? Ext.urlEncode(vals) : vals ;
  606. }.createDelegate(this);
  607. }
  608. },
  609. onResize : function(w, h, rw, rh) {
  610. var reduce = Ext.isIE6 ? 4 : Ext.isIE7 ? 1 : Ext.isIE8 ? 1 : 0;
  611. if(this.wrapEl){
  612. this._width = w;
  613. this.outerWrapEl.setWidth(w - reduce);
  614. if (this.renderFieldBtns) {
  615. reduce += (this.buttonWrap.getWidth() + 20);
  616. this.wrapEl.setWidth(w - reduce);
  617. }
  618. }
  619. Ext.ux.form.SuperBoxSelect.superclass.onResize.call(this, w, h, rw, rh);
  620. this.autoSize();
  621. },
  622. onEnable: function(){
  623. Ext.ux.form.SuperBoxSelect.superclass.onEnable.call(this);
  624. this.items.each(function(item){
  625. item.enable();
  626. });
  627. if (this.renderFieldBtns) {
  628. this.initButtonEvents();
  629. }
  630. },
  631. onDisable: function(){
  632. Ext.ux.form.SuperBoxSelect.superclass.onDisable.call(this);
  633. this.items.each(function(item){
  634. item.disable();
  635. });
  636. if(this.renderFieldBtns){
  637. this.removeButtonEvents();
  638. }
  639. },
  640. /**
  641. * Clears all values from the component.
  642. * @methodOf Ext.ux.form.SuperBoxSelect
  643. * @name clearValue
  644. * @param {Boolean} supressRemoveEvent [Optional] When true, the 'removeitem' event will not fire for each item that is removed.
  645. */
  646. clearValue : function(supressRemoveEvent){
  647. Ext.ux.form.SuperBoxSelect.superclass.clearValue.call(this);
  648. this.preventMultipleRemoveEvents = supressRemoveEvent || this.supressClearValueRemoveEvents || false;
  649. this.removeAllItems();
  650. this.preventMultipleRemoveEvents = false;
  651. this.fireEvent('clear',this);
  652. return this;
  653. },
  654. fireNewItemEvent : function(val){
  655. this.view.clearSelections();
  656. this.collapse();
  657. this.setRawValue('');
  658. if(this.queryFilterRe){
  659. val = val.replace(this.queryFilterRe, '');
  660. if(!val){
  661. return;
  662. }
  663. }
  664. this.fireEvent('newitem', this, val, this.filteredQueryData);
  665. },
  666. onKeyUp : function(e) {
  667. if (this.editable !== false && (!e.isSpecialKey() || e.getKey() === e.BACKSPACE) && this.itemDelimiterKey.indexOf !== e.getKey() && (!e.hasModifier() || e.shiftKey)) {
  668. this.lastKey = e.getKey();
  669. this.dqTask.delay(this.queryDelay);
  670. }
  671. },
  672. onKeyDownHandler : function(e,t) {
  673. var toDestroy,nextFocus,idx;
  674. if(e.getKey() === e.ESC){
  675. if(!this.isExpanded()){
  676. if(this.el.dom.value != '' && (this.clearOnEscape || this.clearLastQueryOnEscape)){
  677. if(this.clearOnEscape){
  678. this.el.dom.value = '';
  679. }
  680. if(this.clearLastQueryOnEscape){
  681. this.lastQuery = '';
  682. }
  683. e.stopEvent();
  684. }
  685. }
  686. }
  687. if ((e.getKey() === e.DELETE || e.getKey() === e.SPACE) && this.currentFocus){
  688. e.stopEvent();
  689. toDestroy = this.currentFocus;
  690. this.on('expand',function(){this.collapse();},this,{single: true});
  691. idx = this.items.indexOfKey(this.currentFocus.key);
  692. this.clearCurrentFocus();
  693. if(idx < (this.items.getCount() -1)){
  694. nextFocus = this.items.itemAt(idx+1);
  695. }
  696. toDestroy.preDestroy(true);
  697. if(nextFocus){
  698. (function(){
  699. nextFocus.onLnkFocus();
  700. this.currentFocus = nextFocus;
  701. }).defer(200,this);
  702. }
  703. return true;
  704. }
  705. var val = this.el.dom.value, it, ctrl = e.ctrlKey;
  706. if(this.itemDelimiterKey === e.getKey()){
  707. e.stopEvent();
  708. if (val !== "") {
  709. if (ctrl || !this.isExpanded()) { //ctrl+enter for new items
  710. this.fireNewItemEvent(val);
  711. } else {
  712. this.onViewClick();
  713. //removed from 3.0.1
  714. if(this.unsetDelayCheck){
  715. this.delayedCheck = true;
  716. this.unsetDelayCheck.defer(10, this);
  717. }
  718. }
  719. }else{
  720. if(!this.isExpanded()){
  721. return;
  722. }
  723. this.onViewClick();
  724. //removed from 3.0.1
  725. if(this.unsetDelayCheck){
  726. this.delayedCheck = true;
  727. this.unsetDelayCheck.defer(10, this);
  728. }
  729. }
  730. return true;
  731. }
  732. if(val !== '') {
  733. this.autoSize();
  734. return;
  735. }
  736. //select first item
  737. if(e.getKey() === e.HOME){
  738. e.stopEvent();
  739. if(this.items.getCount() > 0){
  740. this.collapse();
  741. it = this.items.get(0);
  742. it.el.focus();
  743. }
  744. return true;
  745. }
  746. //backspace remove
  747. if(e.getKey() === e.BACKSPACE){
  748. e.stopEvent();
  749. if(this.currentFocus) {
  750. toDestroy = this.currentFocus;
  751. this.on('expand',function(){
  752. this.collapse();
  753. },this,{single: true});
  754. idx = this.items.indexOfKey(toDestroy.key);
  755. this.clearCurrentFocus();
  756. if(idx < (this.items.getCount() -1)){
  757. nextFocus = this.items.itemAt(idx+1);
  758. }
  759. toDestroy.preDestroy(true);
  760. if(nextFocus){
  761. (function(){
  762. nextFocus.onLnkFocus();
  763. this.currentFocus = nextFocus;
  764. }).defer(200,this);
  765. }
  766. return;
  767. }else{
  768. it = this.items.get(this.items.getCount() -1);
  769. if(it){
  770. if(this.backspaceDeletesLastItem){
  771. this.on('expand',function(){this.collapse();},this,{single: true});
  772. it.preDestroy(true);
  773. }else{
  774. if(this.navigateItemsWithTab){
  775. it.onElClick();
  776. }else{
  777. this.on('expand',function(){
  778. this.collapse();
  779. this.currentFocus = it;
  780. this.currentFocus.onLnkFocus.defer(20,this.currentFocus);
  781. },this,{single: true});
  782. }
  783. }
  784. }
  785. return true;
  786. }
  787. }
  788. if(!e.isNavKeyPress()){
  789. this.multiSelectMode = false;
  790. this.clearCurrentFocus();
  791. return;
  792. }
  793. //arrow nav
  794. if(e.getKey() === e.LEFT || (e.getKey() === e.UP && !this.isExpanded())){
  795. e.stopEvent();
  796. this.collapse();
  797. //get last item
  798. it = this.items.get(this.items.getCount()-1);
  799. if(this.navigateItemsWithTab){
  800. //focus last el
  801. if(it){
  802. it.focus();
  803. }
  804. }else{
  805. //focus prev item
  806. if(this.currentFocus){
  807. idx = this.items.indexOfKey(this.currentFocus.key);
  808. this.clearCurrentFocus();
  809. if(idx !== 0){
  810. this.currentFocus = this.items.itemAt(idx-1);
  811. this.currentFocus.onLnkFocus();
  812. }
  813. }else{
  814. this.currentFocus = it;
  815. if(it){
  816. it.onLnkFocus();
  817. }
  818. }
  819. }
  820. return true;
  821. }
  822. if(e.getKey() === e.DOWN){
  823. if(this.currentFocus){
  824. this.collapse();
  825. e.stopEvent();
  826. idx = this.items.indexOfKey(this.currentFocus.key);
  827. if(idx == (this.items.getCount() -1)){
  828. this.clearCurrentFocus.defer(10,this);
  829. }else{
  830. this.clearCurrentFocus();
  831. this.currentFocus = this.items.itemAt(idx+1);
  832. if(this.currentFocus){
  833. this.currentFocus.onLnkFocus();
  834. }
  835. }
  836. return true;
  837. }
  838. }
  839. if(e.getKey() === e.RIGHT){
  840. this.collapse();
  841. it = this.items.itemAt(0);
  842. if(this.navigateItemsWithTab){
  843. //focus first el
  844. if(it){
  845. it.focus();
  846. }
  847. }else{
  848. if(this.currentFocus){
  849. idx = this.items.indexOfKey(this.currentFocus.key);
  850. this.clearCurrentFocus();
  851. if(idx < (this.items.getCount() -1)){
  852. this.currentFocus = this.items.itemAt(idx+1);
  853. if(this.currentFocus){
  854. this.currentFocus.onLnkFocus();
  855. }
  856. }
  857. }else{
  858. this.currentFocus = it;
  859. if(it){
  860. it.onLnkFocus();
  861. }
  862. }
  863. }
  864. }
  865. },
  866. onKeyUpBuffered : function(e){
  867. if(!e.isNavKeyPress()){
  868. this.autoSize();
  869. }
  870. },
  871. reset : function(){
  872. this.killItems();
  873. Ext.ux.form.SuperBoxSelect.superclass.reset.call(this);
  874. this.addedRecords = [];
  875. this.autoSize().setRawValue('');
  876. },
  877. applyEmptyText : function(){
  878. this.setRawValue('');
  879. if(this.items.getCount() > 0){
  880. this.el.removeClass(this.emptyClass);
  881. this.setRawValue('');
  882. return this;
  883. }
  884. if(this.rendered && this.emptyText && this.getRawValue().length < 1){
  885. this.setRawValue(this.emptyText);
  886. this.el.addClass(this.emptyClass);
  887. }
  888. return this;
  889. },
  890. /**
  891. * @private
  892. *
  893. * Use clearValue instead
  894. */
  895. removeAllItems: function(){
  896. this.items.each(function(item){
  897. item.preDestroy(true);
  898. },this);
  899. this.manageClearBtn();
  900. return this;
  901. },
  902. killItems : function(){
  903. this.items.each(function(item){
  904. item.kill();
  905. },this);
  906. this.resetStore();
  907. this.items.clear();
  908. this.manageClearBtn();
  909. return this;
  910. },
  911. resetStore: function(){
  912. this.store.clearFilter();
  913. if(!this.removeValuesFromStore){
  914. return this;
  915. }
  916. this.usedRecords.each(function(rec){
  917. this.store.add(rec);
  918. },this);
  919. this.usedRecords.clear();
  920. if(!this.store.remoteSort){
  921. this.store.sort(this.displayField, 'ASC');
  922. }
  923. return this;
  924. },
  925. sortStore: function(){
  926. var ss = this.store.getSortState();
  927. if(ss && ss.field){
  928. this.store.sort(ss.field, ss.direction);
  929. }
  930. return this;
  931. },
  932. getCaption: function(dataObject){
  933. if(typeof this.displayFieldTpl === 'string') {
  934. this.displayFieldTpl = new Ext.XTemplate(this.displayFieldTpl);
  935. }
  936. var caption, recordData = dataObject instanceof Ext.data.Record ? dataObject.data : dataObject;
  937. if(this.displayFieldTpl) {
  938. caption = this.displayFieldTpl.apply(recordData);
  939. } else if(this.displayField) {
  940. caption = recordData[this.displayField];
  941. }
  942. return caption;
  943. },
  944. addRecord : function(record) {
  945. var display = record.data[this.displayField],
  946. caption = this.getCaption(record),
  947. val = record.data[this.valueField],
  948. cls = this.classField ? record.data[this.classField] : '',
  949. style = this.styleField ? record.data[this.styleField] : '';
  950. if (this.removeValuesFromStore) {
  951. this.usedRecords.add(val, record);
  952. this.store.remove(record);
  953. }
  954. this.addItemBox(val, display, caption, cls, style);
  955. this.fireEvent('additem', this, val, record);
  956. },
  957. createRecord : function(recordData){
  958. if(!this.recordConstructor){
  959. var recordFields = [
  960. {name: this.valueField},
  961. {name: this.displayField}
  962. ];
  963. if(this.classField){
  964. recordFields.push({name: this.classField});
  965. }
  966. if(this.styleField){
  967. recordFields.push({name: this.styleField});
  968. }
  969. this.recordConstructor = Ext.data.Record.create(recordFields);
  970. }
  971. return new this.recordConstructor(recordData);
  972. },
  973. /**
  974. * Adds an array of items to the SuperBoxSelect component if the {@link #Ext.ux.form.SuperBoxSelect-allowAddNewData} config is set to true.
  975. * @methodOf Ext.ux.form.SuperBoxSelect
  976. * @name addItem
  977. * @param {Array} newItemObjects An Array of object literals containing the property names and values for an item. The property names must match those specified in {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField} and {@link #Ext.ux.form.SuperBoxSelect-classField}
  978. */
  979. addItems : function(newItemObjects){
  980. if (Ext.isArray(newItemObjects)) {
  981. Ext.each(newItemObjects, function(item) {
  982. this.addItem(item);
  983. }, this);
  984. } else {
  985. this.addItem(newItemObjects);
  986. }
  987. },
  988. /**
  989. * Adds a new non-existing item to the SuperBoxSelect component if the {@link #Ext.ux.form.SuperBoxSelect-allowAddNewData} config is set to true.
  990. * This method should be used in place of addItem from within the newitem event handler.
  991. * @methodOf Ext.ux.form.SuperBoxSelect
  992. * @name addNewItem
  993. * @param {Object} newItemObject An object literal containing the property names and values for an item. The property names must match those specified in {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField} and {@link #Ext.ux.form.SuperBoxSelect-classField}
  994. */
  995. addNewItem : function(newItemObject){
  996. this.addItem(newItemObject,true);
  997. },
  998. /**
  999. * Adds an item to the SuperBoxSelect component if the {@link #Ext.ux.form.SuperBoxSelect-allowAddNewData} config is set to true.
  1000. * @methodOf Ext.ux.form.SuperBoxSelect
  1001. * @name addItem
  1002. * @param {Object} newItemObject An object literal containing the property names and values for an item. The property names must match those specified in {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField} and {@link #Ext.ux.form.SuperBoxSelect-classField}
  1003. */
  1004. addItem : function(newItemObject, /*hidden param*/ forcedAdd){
  1005. var val = newItemObject[this.valueField];
  1006. if(this.disabled) {
  1007. return false;
  1008. }
  1009. if(this.preventDuplicates && this.hasValue(val)){
  1010. return;
  1011. }
  1012. //use existing record if found
  1013. var record = this.findRecord(this.valueField, val);
  1014. if (record) {
  1015. this.addRecord(record);
  1016. return;
  1017. } else if (!this.allowAddNewData) { // else it's a new item
  1018. return;
  1019. }
  1020. if(this.mode === 'remote'){
  1021. this.remoteLookup.push(newItemObject);
  1022. this.doQuery(val,false,false,forcedAdd);
  1023. return;
  1024. }
  1025. var rec = this.createRecord(newItemObject);
  1026. this.store.add(rec);
  1027. this.addRecord(rec);
  1028. return true;
  1029. },
  1030. addItemBox : function(itemVal,itemDisplay,itemCaption, itemClass, itemStyle) {
  1031. var hConfig, parseStyle = function(s){
  1032. var ret = '';
  1033. switch(typeof s){
  1034. case 'function' :
  1035. ret = s.call();
  1036. break;
  1037. case 'object' :
  1038. for(var p in s){
  1039. ret+= p +':'+s[p]+';';
  1040. }
  1041. break;
  1042. case 'string' :
  1043. ret = s + ';';
  1044. }
  1045. return ret;
  1046. }, itemKey = Ext.id(null,'sbx-item'), box = new Ext.ux.form.SuperBoxSelectItem({
  1047. owner: this,
  1048. disabled: this.disabled,
  1049. renderTo: this.wrapEl,
  1050. cls: this.extraItemCls + ' ' + itemClass,
  1051. style: parseStyle(this.extraItemStyle) + ' ' + itemStyle,
  1052. caption: itemCaption,
  1053. display: itemDisplay,
  1054. value: itemVal,
  1055. key: itemKey,
  1056. listeners: {
  1057. 'remove': function(item){
  1058. if(this.fireEvent('beforeremoveitem',this,item.value) === false){
  1059. return false;
  1060. }
  1061. this.items.removeKey(item.key);
  1062. if(this.removeValuesFromStore){
  1063. if(this.usedRecords.containsKey(item.value)){
  1064. this.store.add(this.usedRecords.get(item.value));
  1065. this.usedRecords.removeKey(item.value);
  1066. this.sortStore();
  1067. if(this.view){
  1068. this.view.render();
  1069. }
  1070. }
  1071. }
  1072. if(!this.preventMultipleRemoveEvents){
  1073. this.fireEvent.defer(250,this,['removeitem',this,item.value, this.findInStore(item.value)]);
  1074. }
  1075. },
  1076. destroy: function(){
  1077. this.collapse();
  1078. this.autoSize().manageClearBtn().validateValue();
  1079. },
  1080. scope: this
  1081. }
  1082. });
  1083. box.render();
  1084. hConfig = {
  1085. tag :'input',
  1086. type :'hidden',
  1087. value : itemVal,
  1088. name : (this.hiddenName || this.name)
  1089. };
  1090. if(this.disabled){
  1091. Ext.apply(hConfig,{
  1092. disabled : 'disabled'
  1093. })
  1094. }
  1095. box.hidden = this.el.insertSibling(hConfig,'before');
  1096. this.items.add(itemKey,box);
  1097. this.applyEmptyText().autoSize().manageClearBtn().validateValue();
  1098. },
  1099. manageClearBtn : function() {
  1100. if (!this.renderFieldBtns || !this.rendered) {
  1101. return this;
  1102. }
  1103. var cls = 'x-superboxselect-btn-hide';
  1104. if (this.items.getCount() === 0) {
  1105. this.buttonClear.addClass(cls);
  1106. } else {
  1107. this.buttonClear.removeClass(cls);
  1108. }
  1109. return this;
  1110. },
  1111. findInStore : function(val){
  1112. var index = this.store.find(this.valueField, val);
  1113. if(index > -1){
  1114. return this.store.getAt(index);
  1115. }
  1116. return false;
  1117. },
  1118. /**
  1119. * Returns an array of records associated with the selected items.
  1120. * @methodOf Ext.ux.form.SuperBoxSelect
  1121. * @name getSelectedRecords
  1122. * @return {Array} An array of records associated with the selected items.
  1123. */
  1124. getSelectedRecords : function(){
  1125. var ret =[];
  1126. if(this.removeValuesFromStore){
  1127. ret = this.usedRecords.getRange();
  1128. }else{
  1129. var vals = [];
  1130. this.items.each(function(item){
  1131. vals.push(item.value);
  1132. });
  1133. Ext.each(vals,function(val){
  1134. ret.push(this.findInStore(val));
  1135. },this);
  1136. }
  1137. return ret;
  1138. },
  1139. /**
  1140. * Returns an item which contains the passed HTML Element.
  1141. * @methodOf Ext.ux.form.SuperBoxSelect
  1142. * @name findSelectedItem
  1143. * @param {HTMLElement} el The LI HTMLElement of a selected item in the list
  1144. */
  1145. findSelectedItem : function(el){
  1146. var ret;
  1147. this.items.each(function(item){
  1148. if(item.el.dom === el){
  1149. ret = item;
  1150. return false;
  1151. }
  1152. });
  1153. return ret;
  1154. },
  1155. /**
  1156. * Returns a record associated with the item which contains the passed HTML Element.
  1157. * @methodOf Ext.ux.form.SuperBoxSelect
  1158. * @name findSelectedRecord
  1159. * @param {HTMLElement} el The LI HTMLElement of a selected item in the list
  1160. */
  1161. findSelectedRecord : function(el){
  1162. var ret, item = this.findSelectedItem(el);
  1163. if(item){
  1164. ret = this.findSelectedRecordByValue(item.value)
  1165. }
  1166. return ret;
  1167. },
  1168. /**
  1169. * Returns a selected record associated with the passed value.
  1170. * @methodOf Ext.ux.form.SuperBoxSelect
  1171. * @name findSelectedRecordByValue
  1172. * @param {Mixed} val The value to lookup
  1173. * @return {Record} The matching Record.
  1174. */
  1175. findSelectedRecordByValue : function(val){
  1176. var ret;
  1177. if(this.removeValuesFromStore){
  1178. this.usedRecords.each(function(rec){
  1179. if(rec.get(this.valueField) == val){
  1180. ret = rec;
  1181. return false;
  1182. }
  1183. },this);
  1184. }else{
  1185. ret = this.findInStore(val);
  1186. }
  1187. return ret;
  1188. },
  1189. /**
  1190. * Returns a String value containing a concatenated list of item values. The list is concatenated with the {@link #Ext.ux.form.SuperBoxSelect-valueDelimiter}.
  1191. * @methodOf Ext.ux.form.SuperBoxSelect
  1192. * @name getValue
  1193. * @return {String} a String value containing a concatenated list of item values.
  1194. */
  1195. getValue : function() {
  1196. var ret = [];
  1197. this.items.each(function(item){
  1198. ret.push(item.value);
  1199. });
  1200. return ret.join(this.valueDelimiter);
  1201. },
  1202. /**
  1203. * Returns the count of the selected items.
  1204. * @methodOf Ext.ux.form.SuperBoxSelect
  1205. * @name getCount
  1206. * @return {Number} the number of selected items.
  1207. */
  1208. getCount : function() {
  1209. return this.items.getCount();
  1210. },
  1211. /**
  1212. * Returns an Array of item objects containing the {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField}, {@link #Ext.ux.form.SuperBoxSelect-classField} and {@link #Ext.ux.form.SuperBoxSelect-styleField} properties.
  1213. * @methodOf Ext.ux.form.SuperBoxSelect
  1214. * @name getValueEx
  1215. * @return {Array} an array of item objects.
  1216. */
  1217. getValueEx : function() {
  1218. var ret = [];
  1219. this.items.each(function(item){
  1220. var newItem = {};
  1221. newItem[this.valueField] = item.value;
  1222. newItem[this.displayField] = item.display;
  1223. if(this.classField){
  1224. newItem[this.classField] = item.cls || '';
  1225. }
  1226. if(this.styleField){
  1227. newItem[this.styleField] = item.style || '';
  1228. }
  1229. ret.push(newItem);
  1230. },this);
  1231. return ret;
  1232. },
  1233. // private
  1234. initValue : function(){
  1235. if(Ext.isObject(this.value) || Ext.isArray(this.value)){
  1236. this.setValueEx(this.value);
  1237. this.originalValue = this.getValue();
  1238. }else{
  1239. Ext.ux.form.SuperBoxSelect.superclass.initValue.call(this);
  1240. }
  1241. if(this.mode === 'remote') {
  1242. this.setOriginal = true;
  1243. }
  1244. },
  1245. /**
  1246. * Adds an existing value to the SuperBoxSelect component.
  1247. * @methodOf Ext.ux.form.SuperBoxSelect
  1248. * @name setValue
  1249. * @param {String|Array} value An array of item values, or a String value containing a delimited list of item values. (The list should be delimited with the {@link #Ext.ux.form.SuperBoxSelect-valueDelimiter)
  1250. */
  1251. addValue : function(value){
  1252. if(Ext.isEmpty(value)){
  1253. return;
  1254. }
  1255. var values = value;
  1256. if(!Ext.isArray(value)){
  1257. value = '' + value;
  1258. values = value.split(this.valueDelimiter);
  1259. }
  1260. Ext.each(values,function(val){
  1261. var record = this.findRecord(this.valueField, val);
  1262. if(record){
  1263. this.addRecord(record);
  1264. }else if(this.mode === 'remote'){
  1265. this.remoteLookup.push(val);
  1266. }
  1267. },this);
  1268. if(this.mode === 'remote'){
  1269. var q = this.remoteLookup.join(this.queryValuesDelimiter);
  1270. this.doQuery(q,false, true); //3rd param to specify a values query
  1271. }
  1272. },
  1273. /**
  1274. * Sets the value of the SuperBoxSelect component.
  1275. * @methodOf Ext.ux.form.SuperBoxSelect
  1276. * @name setValue
  1277. * @param {String|Array} value An array of item values, or a String value containing a delimited list of item values. (The list should be delimited with the {@link #Ext.ux.form.SuperBoxSelect-valueDelimiter)
  1278. */
  1279. setValue : function(value){
  1280. if(!this.rendered){
  1281. this.value = value;
  1282. return;
  1283. }
  1284. this.removeAllItems().resetStore();
  1285. this.remoteLookup = [];
  1286. this.addValue(value);
  1287. },
  1288. /**
  1289. * Sets the value of the SuperBoxSelect component, adding new items that don't exist in the data store if the {@link #Ext.ux.form.SuperBoxSelect-allowAddNewData} config is set to true.
  1290. * @methodOf Ext.ux.form.SuperBoxSelect
  1291. * @name setValue
  1292. * @param {Array} data An Array of item objects containing the {@link #Ext.ux.form.SuperBoxSelect-displayField}, {@link #Ext.ux.form.SuperBoxSelect-valueField} and {@link #Ext.ux.form.SuperBoxSelect-classField} properties.
  1293. */
  1294. setValueEx : function(data){
  1295. if(!this.rendered){
  1296. this.value = data;
  1297. return;
  1298. }
  1299. this.removeAllItems().resetStore();
  1300. if(!Ext.isArray(data)){
  1301. data = [data];
  1302. }
  1303. this.remoteLookup = [];
  1304. if(this.allowAddNewData && this.mode === 'remote'){ // no need to query
  1305. Ext.each(data, function(d){
  1306. var r = this.findRecord(this.valueField, d[this.valueField]) || this.createRecord(d);
  1307. this.addRecord(r);
  1308. },this);
  1309. return;
  1310. }
  1311. Ext.each(data,function(item){
  1312. this.addItem(item);
  1313. },this);
  1314. },
  1315. /**
  1316. * Returns true if the SuperBoxSelect component has a selected item with a value matching the 'val' parameter.
  1317. * @methodOf Ext.ux.form.SuperBoxSelect
  1318. * @name hasValue
  1319. * @param {Mixed} val The value to test.
  1320. * @return {Boolean} true if the component has the selected value, false otherwise.
  1321. */
  1322. hasValue: function(val){
  1323. var has = false;
  1324. this.items.each(function(item){
  1325. if(item.value == val){
  1326. has = true;
  1327. return false;
  1328. }
  1329. },this);
  1330. return has;
  1331. },
  1332. onSelect : function(record, index) {
  1333. if (this.fireEvent('beforeselect', this, record, index) !== false){
  1334. var val = record.data[this.valueField];
  1335. if(this.preventDuplicates && this.hasValue(val)){
  1336. return;
  1337. }
  1338. this.setRawValue('');
  1339. this.lastSelectionText = '';
  1340. if(this.fireEvent('beforeadditem',this,val,record,this.filteredQueryData) !== false){
  1341. this.addRecord(record);
  1342. }
  1343. if(this.store.getCount() === 0 || !this.multiSelectMode){
  1344. this.collapse();
  1345. }else{
  1346. this.restrictHeight();
  1347. }
  1348. }
  1349. },
  1350. onDestroy : function() {
  1351. this.items.purgeListeners();
  1352. this.killItems();
  1353. if(this.allowQueryAll){
  1354. Ext.destroy(this.buttonExpand);
  1355. }
  1356. if (this.renderFieldBtns) {
  1357. Ext.destroy(
  1358. this.buttonClear,
  1359. this.buttonWrap
  1360. );
  1361. }
  1362. Ext.destroy(
  1363. this.inputEl,
  1364. this.wrapEl,
  1365. this.outerWrapEl
  1366. );
  1367. Ext.ux.form.SuperBoxSelect.superclass.onDestroy.call(this);
  1368. },
  1369. autoSize : function(){
  1370. if(!this.rendered){
  1371. return this;
  1372. }
  1373. if(!this.metrics){
  1374. this.metrics = Ext.util.TextMetrics.createInstance(this.el);
  1375. }
  1376. var el = this.el,
  1377. v = el.dom.value,
  1378. d = document.createElement('div');
  1379. if(v === "" && this.emptyText && this.items.getCount() < 1){
  1380. v = this.emptyText;
  1381. }
  1382. d.appendChild(document.createTextNode(v));
  1383. v = d.innerHTML;
  1384. d = null;
  1385. v += "&#160;";
  1386. var w = Math.max(this.metrics.getWidth(v) + 24, 24);
  1387. if(typeof this._width != 'undefined'){
  1388. w = Math.min(this._width, w);
  1389. }
  1390. this.el.setWidth(w);
  1391. if(Ext.isIE){
  1392. this.el.dom.style.top='0';
  1393. }
  1394. this.fireEvent('autosize', this, w);
  1395. return this;
  1396. },
  1397. shouldQuery : function(q){
  1398. if(this.lastQuery){
  1399. var m = q.match("^"+this.lastQuery);
  1400. if(!m || this.store.getCount()){
  1401. return true;
  1402. }else{
  1403. return (m[0] !== this.lastQuery);
  1404. }
  1405. }
  1406. return true;
  1407. },
  1408. doQuery : function(q, forceAll,valuesQuery, forcedAdd){
  1409. q = Ext.isEmpty(q) ? '' : q;
  1410. if(this.queryFilterRe){
  1411. this.filteredQueryData = '';
  1412. var m = q.match(this.queryFilterRe);
  1413. if(m && m.length){
  1414. this.filteredQueryData = m[0];
  1415. }
  1416. q = q.replace(this.queryFilterRe, '');
  1417. if(!q && m){
  1418. return;
  1419. }
  1420. }
  1421. var qe = {
  1422. query: q,
  1423. forceAll: forceAll,
  1424. combo: this,
  1425. cancel:false
  1426. };
  1427. if(this.fireEvent('beforequery', qe)===false || qe.cancel){
  1428. return false;
  1429. }
  1430. q = qe.query;
  1431. forceAll = qe.forceAll;
  1432. if(forceAll === true || (q.length >= this.minChars) || valuesQuery && !Ext.isEmpty(q)){
  1433. if(forcedAdd || this.forceSameValueQuery || this.shouldQuery(q) ){
  1434. this.lastQuery = q;
  1435. if(this.mode == 'local'){
  1436. this.selectedIndex = -1;
  1437. if(forceAll){
  1438. this.store.clearFilter();
  1439. }else{
  1440. this.store.filter(this.displayField, q);
  1441. }
  1442. this.onLoad();
  1443. }else{
  1444. this.store.baseParams[this.queryParam] = q;
  1445. this.store.baseParams[this.queryValuesIndicator] = valuesQuery;
  1446. this.store.load({
  1447. params: this.getParams(q)
  1448. });
  1449. if(!forcedAdd){
  1450. this.expand();
  1451. }
  1452. }
  1453. }else{
  1454. this.selectedIndex = -1;
  1455. this.onLoad();
  1456. }
  1457. }
  1458. },
  1459. onStoreLoad : function(store, records, options){
  1460. //accomodating for bug in Ext 3.0.0 where options.params are empty
  1461. var q = options.params[this.queryParam] || store.baseParams[this.queryParam] || "",
  1462. isValuesQuery = options.params[this.queryValuesIndicator] || store.baseParams[this.queryValuesIndicator];
  1463. if(this.removeValuesFromStore){
  1464. this.store.each(function(record) {
  1465. if(this.usedRecords.containsKey(record.get(this.valueField))){
  1466. this.store.remove(record);
  1467. }
  1468. }, this);
  1469. }
  1470. //queried values
  1471. if(isValuesQuery){
  1472. var params = q.split(this.queryValuesDelimiter);
  1473. Ext.each(params,function(p){
  1474. this.remoteLookup.remove(p);
  1475. var rec = this.findRecord(this.valueField,p);
  1476. if(rec){
  1477. this.addRecord(rec);
  1478. }
  1479. },this);
  1480. if(this.setOriginal){
  1481. this.setOriginal = false;
  1482. this.originalValue = this.getValue();
  1483. }
  1484. }
  1485. //queried display (autocomplete) & addItem
  1486. if(q !== '' && this.allowAddNewData){
  1487. Ext.each(this.remoteLookup,function(r){
  1488. if(typeof r === "object" && r[this.valueField] === q){
  1489. this.remoteLookup.remove(r);
  1490. if(records.length && records[0].get(this.valueField) === q) {
  1491. this.addRecord(records[0]);
  1492. return;
  1493. }
  1494. var rec = this.createRecord(r);
  1495. this.store.add(rec);
  1496. this.addRecord(rec);
  1497. this.addedRecords.push(rec); //keep track of records added to store
  1498. (function(){
  1499. if(this.isExpanded()){
  1500. this.collapse();
  1501. }
  1502. }).defer(10,this);
  1503. return;
  1504. }
  1505. },this);
  1506. }
  1507. var toAdd = [];
  1508. if(q === ''){
  1509. Ext.each(this.addedRecords,function(rec){
  1510. if(this.preventDuplicates && this.usedRecords.containsKey(rec.get(this.valueField))){
  1511. return;
  1512. }
  1513. toAdd.push(rec);
  1514. },this);
  1515. }else{
  1516. var re = new RegExp(Ext.escapeRe(q) + '.*','i');
  1517. Ext.each(this.addedRecords,function(rec){
  1518. if(this.preventDuplicates && this.usedRecords.containsKey(rec.get(this.valueField))){
  1519. return;
  1520. }
  1521. if(re.test(rec.get(this.displayField))){
  1522. toAdd.push(rec);
  1523. }
  1524. },this);
  1525. }
  1526. this.store.add(toAdd);
  1527. this.sortStore();
  1528. if(this.store.getCount() === 0 && this.isExpanded()){
  1529. this.collapse();
  1530. }
  1531. }
  1532. });
  1533. Ext.reg('superboxselect', Ext.ux.form.SuperBoxSelect);
  1534. /*
  1535. * @private
  1536. */
  1537. Ext.ux.form.SuperBoxSelectItem = function(config){
  1538. Ext.apply(this,config);
  1539. Ext.ux.form.SuperBoxSelectItem.superclass.constructor.call(this);
  1540. };
  1541. /*
  1542. * @private
  1543. */
  1544. Ext.ux.form.SuperBoxSelectItem = Ext.extend(Ext.ux.form.SuperBoxSelectItem,Ext.Component, {
  1545. initComponent : function(){
  1546. Ext.ux.form.SuperBoxSelectItem.superclass.initComponent.call(this);
  1547. },
  1548. onElClick : function(e){
  1549. var o = this.owner;
  1550. o.clearCurrentFocus().collapse();
  1551. if(o.navigateItemsWithTab){
  1552. this.focus();
  1553. }else{
  1554. o.el.dom.focus();
  1555. var that = this;
  1556. (function(){
  1557. this.onLnkFocus();
  1558. o.currentFocus = this;
  1559. }).defer(10,this);
  1560. }
  1561. },
  1562. onLnkClick : function(e){
  1563. if(e) {
  1564. e.stopEvent();
  1565. }
  1566. this.preDestroy();
  1567. if(!this.owner.navigateItemsWithTab){
  1568. this.owner.el.focus();
  1569. }
  1570. },
  1571. onLnkFocus : function(){
  1572. this.el.addClass("x-superboxselect-item-focus");
  1573. this.owner.outerWrapEl.addClass("x-form-focus");
  1574. },
  1575. onLnkBlur : function(){
  1576. this.el.removeClass("x-superboxselect-item-focus");
  1577. this.owner.outerWrapEl.removeClass("x-form-focus");
  1578. },
  1579. enableElListeners : function() {
  1580. this.el.on('click', this.onElClick, this, {stopEvent:true});
  1581. this.el.addClassOnOver('x-superboxselect-item x-superboxselect-item-hover');
  1582. },
  1583. enableLnkListeners : function() {
  1584. this.lnk.on({
  1585. click: this.onLnkClick,
  1586. focus: this.onLnkFocus,
  1587. blur: this.onLnkBlur,
  1588. scope: this
  1589. });
  1590. },
  1591. enableAllListeners : function() {
  1592. this.enableElListeners();
  1593. this.enableLnkListeners();
  1594. },
  1595. disableAllListeners : function() {
  1596. this.el.removeAllListeners();
  1597. this.lnk.un('click', this.onLnkClick, this);
  1598. this.lnk.un('focus', this.onLnkFocus, this);
  1599. this.lnk.un('blur', this.onLnkBlur, this);
  1600. },
  1601. onRender : function(ct, position){
  1602. Ext.ux.form.SuperBoxSelectItem.superclass.onRender.call(this, ct, position);
  1603. var el = this.el;
  1604. if(el){
  1605. el.remove();
  1606. }
  1607. this.el = el = ct.createChild({ tag: 'li' }, ct.last());
  1608. el.addClass('x-superboxselect-item');
  1609. var btnEl = this.owner.navigateItemsWithTab ? 'a' : 'span';
  1610. var itemKey = this.key;
  1611. Ext.apply(el, {
  1612. focus: function(){
  1613. var c = this.down(btnEl +'.x-superboxselect-item-close');
  1614. if(c){
  1615. c.focus();
  1616. }
  1617. },
  1618. preDestroy: function(){
  1619. this.preDestroy();
  1620. }.createDelegate(this)
  1621. });
  1622. this.enableElListeners();
  1623. el.update(this.caption);
  1624. var cfg = {
  1625. tag: btnEl,
  1626. 'class': 'x-superboxselect-item-close',
  1627. tabIndex : this.owner.navigateItemsWithTab ? '0' : '-1'
  1628. };
  1629. if(btnEl === 'a'){
  1630. cfg.href = '#';
  1631. }
  1632. this.lnk = el.createChild(cfg);
  1633. if(!this.disabled) {
  1634. this.enableLnkListeners();
  1635. }else {
  1636. this.disableAllListeners();
  1637. }
  1638. this.on({
  1639. disable: this.disableAllListeners,
  1640. enable: this.enableAllListeners,
  1641. scope: this
  1642. });
  1643. this.setupKeyMap();
  1644. },
  1645. setupKeyMap : function(){
  1646. this.keyMap = new Ext.KeyMap(this.lnk, [
  1647. {
  1648. key: [
  1649. Ext.EventObject.BACKSPACE,
  1650. Ext.EventObject.DELETE,
  1651. Ext.EventObject.SPACE
  1652. ],
  1653. fn: this.preDestroy,
  1654. scope: this
  1655. }, {
  1656. key: [
  1657. Ext.EventObject.RIGHT,
  1658. Ext.EventObject.DOWN
  1659. ],
  1660. fn: function(){
  1661. this.moveFocus('right');
  1662. },
  1663. scope: this
  1664. },
  1665. {
  1666. key: [Ext.EventObject.LEFT,Ext.EventObject.UP],
  1667. fn: function(){
  1668. this.moveFocus('left');
  1669. },
  1670. scope: this
  1671. },
  1672. {
  1673. key: [Ext.EventObject.HOME],
  1674. fn: function(){
  1675. var l = this.owner.items.get(0).el.focus();
  1676. if(l){
  1677. l.el.focus();
  1678. }
  1679. },
  1680. scope: this
  1681. },
  1682. {
  1683. key: [Ext.EventObject.END],
  1684. fn: function(){
  1685. this.owner.el.focus();
  1686. },
  1687. scope: this
  1688. },
  1689. {
  1690. key: Ext.EventObject.ENTER,
  1691. fn: function(){
  1692. }
  1693. }
  1694. ]);
  1695. this.keyMap.stopEvent = true;
  1696. },
  1697. moveFocus : function(dir) {
  1698. var el = this.el[dir == 'left' ? 'prev' : 'next']() || this.owner.el;
  1699. el.focus.defer(100,el);
  1700. },
  1701. preDestroy : function(supressEffect) {
  1702. if(this.fireEvent('remove', this) === false){
  1703. return;
  1704. }
  1705. var actionDestroy = function(){
  1706. if(this.owner.navigateItemsWithTab){
  1707. this.moveFocus('right');
  1708. }
  1709. this.hidden.remove();
  1710. this.hidden = null;
  1711. this.destroy();
  1712. };
  1713. if(supressEffect){
  1714. actionDestroy.call(this);
  1715. } else {
  1716. this.el.hide({
  1717. duration: 0.2,
  1718. callback: actionDestroy,
  1719. scope: this
  1720. });
  1721. }
  1722. return this;
  1723. },
  1724. kill : function(){
  1725. this.hidden.remove();
  1726. this.hidden = null;
  1727. this.purgeListeners();
  1728. this.destroy();
  1729. },
  1730. onDisable : function() {
  1731. if(this.hidden){
  1732. this.hidden.dom.setAttribute('disabled', 'disabled');
  1733. }
  1734. this.keyMap.disable();
  1735. Ext.ux.form.SuperBoxSelectItem.superclass.onDisable.call(this);
  1736. },
  1737. onEnable : function() {
  1738. if(this.hidden){
  1739. this.hidden.dom.removeAttribute('disabled');
  1740. }
  1741. this.keyMap.enable();
  1742. Ext.ux.form.SuperBoxSelectItem.superclass.onEnable.call(this);
  1743. },
  1744. onDestroy : function() {
  1745. Ext.destroy(
  1746. this.lnk,
  1747. this.el
  1748. );
  1749. Ext.ux.form.SuperBoxSelectItem.superclass.onDestroy.call(this);
  1750. }
  1751. });