ComboBox.js 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dijit.form.ComboBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dijit.form.ComboBox"] = true;
  8. dojo.provide("dijit.form.ComboBox");
  9. dojo.require("dojo.window");
  10. dojo.require("dojo.regexp");
  11. dojo.require("dojo.data.util.simpleFetch");
  12. dojo.require("dojo.data.util.filter");
  13. dojo.require("dijit._CssStateMixin");
  14. dojo.require("dijit.form._FormWidget");
  15. dojo.require("dijit.form.ValidationTextBox");
  16. dojo.require("dijit._HasDropDown");
  17. dojo.requireLocalization("dijit.form", "ComboBox", null, "ROOT,ar,az,bg,ca,cs,da,de,el,es,fi,fr,he,hr,hu,it,ja,kk,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw");
  18. dojo.declare(
  19. "dijit.form.ComboBoxMixin",
  20. dijit._HasDropDown,
  21. {
  22. // summary:
  23. // Implements the base functionality for `dijit.form.ComboBox`/`dijit.form.FilteringSelect`
  24. // description:
  25. // All widgets that mix in dijit.form.ComboBoxMixin must extend `dijit.form._FormValueWidget`.
  26. // tags:
  27. // protected
  28. // item: Object
  29. // This is the item returned by the dojo.data.store implementation that
  30. // provides the data for this ComboBox, it's the currently selected item.
  31. item: null,
  32. // pageSize: Integer
  33. // Argument to data provider.
  34. // Specifies number of search results per page (before hitting "next" button)
  35. pageSize: Infinity,
  36. // store: [const] Object
  37. // Reference to data provider object used by this ComboBox
  38. store: null,
  39. // fetchProperties: Object
  40. // Mixin to the dojo.data store's fetch.
  41. // For example, to set the sort order of the ComboBox menu, pass:
  42. // | { sort: [{attribute:"name",descending: true}] }
  43. // To override the default queryOptions so that deep=false, do:
  44. // | { queryOptions: {ignoreCase: true, deep: false} }
  45. fetchProperties:{},
  46. // query: Object
  47. // A query that can be passed to 'store' to initially filter the items,
  48. // before doing further filtering based on `searchAttr` and the key.
  49. // Any reference to the `searchAttr` is ignored.
  50. query: {},
  51. // autoComplete: Boolean
  52. // If user types in a partial string, and then tab out of the `<input>` box,
  53. // automatically copy the first entry displayed in the drop down list to
  54. // the `<input>` field
  55. autoComplete: true,
  56. // highlightMatch: String
  57. // One of: "first", "all" or "none".
  58. //
  59. // If the ComboBox/FilteringSelect opens with the search results and the searched
  60. // string can be found, it will be highlighted. If set to "all"
  61. // then will probably want to change `queryExpr` parameter to '*${0}*'
  62. //
  63. // Highlighting is only performed when `labelType` is "text", so as to not
  64. // interfere with any HTML markup an HTML label might contain.
  65. highlightMatch: "first",
  66. // searchDelay: Integer
  67. // Delay in milliseconds between when user types something and we start
  68. // searching based on that value
  69. searchDelay: 100,
  70. // searchAttr: String
  71. // Search for items in the data store where this attribute (in the item)
  72. // matches what the user typed
  73. searchAttr: "name",
  74. // labelAttr: String?
  75. // The entries in the drop down list come from this attribute in the
  76. // dojo.data items.
  77. // If not specified, the searchAttr attribute is used instead.
  78. labelAttr: "",
  79. // labelType: String
  80. // Specifies how to interpret the labelAttr in the data store items.
  81. // Can be "html" or "text".
  82. labelType: "text",
  83. // queryExpr: String
  84. // This specifies what query ComboBox/FilteringSelect sends to the data store,
  85. // based on what the user has typed. Changing this expression will modify
  86. // whether the drop down shows only exact matches, a "starting with" match,
  87. // etc. Use it in conjunction with highlightMatch.
  88. // dojo.data query expression pattern.
  89. // `${0}` will be substituted for the user text.
  90. // `*` is used for wildcards.
  91. // `${0}*` means "starts with", `*${0}*` means "contains", `${0}` means "is"
  92. queryExpr: "${0}*",
  93. // ignoreCase: Boolean
  94. // Set true if the ComboBox/FilteringSelect should ignore case when matching possible items
  95. ignoreCase: true,
  96. // hasDownArrow: Boolean
  97. // Set this textbox to have a down arrow button, to display the drop down list.
  98. // Defaults to true.
  99. hasDownArrow: true,
  100. templateString: dojo.cache("dijit.form", "templates/DropDownBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\"\n\trole=\"combobox\"\n\t><div class='dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton dijitArrowButtonContainer'\n\t\tdojoAttachPoint=\"_buttonNode, _popupStateNode\" role=\"presentation\"\n\t\t><input class=\"dijitReset dijitInputField dijitArrowButtonInner\" value=\"&#9660; \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t\t${_buttonInputDisabled}\n\t/></div\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"&#935; \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class='dijitReset dijitInputInner' ${!nameAttrSetting} type=\"text\" autocomplete=\"off\"\n\t\t\tdojoAttachPoint=\"textbox,focusNode\" role=\"textbox\" aria-haspopup=\"true\"\n\t/></div\n></div>\n"),
  101. baseClass: "dijitTextBox dijitComboBox",
  102. // dropDownClass: [protected extension] String
  103. // Name of the dropdown widget class used to select a date/time.
  104. // Subclasses should specify this.
  105. dropDownClass: "dijit.form._ComboBoxMenu",
  106. // Set classes like dijitDownArrowButtonHover depending on
  107. // mouse action over button node
  108. cssStateNodes: {
  109. "_buttonNode": "dijitDownArrowButton"
  110. },
  111. // Flags to _HasDropDown to limit height of drop down to make it fit in viewport
  112. maxHeight: -1,
  113. // For backwards compatibility let onClick events propagate, even clicks on the down arrow button
  114. _stopClickEvents: false,
  115. _getCaretPos: function(/*DomNode*/ element){
  116. // khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22
  117. var pos = 0;
  118. if(typeof(element.selectionStart) == "number"){
  119. // FIXME: this is totally borked on Moz < 1.3. Any recourse?
  120. pos = element.selectionStart;
  121. }else if(dojo.isIE){
  122. // in the case of a mouse click in a popup being handled,
  123. // then the dojo.doc.selection is not the textarea, but the popup
  124. // var r = dojo.doc.selection.createRange();
  125. // hack to get IE 6 to play nice. What a POS browser.
  126. var tr = dojo.doc.selection.createRange().duplicate();
  127. var ntr = element.createTextRange();
  128. tr.move("character",0);
  129. ntr.move("character",0);
  130. try{
  131. // If control doesn't have focus, you get an exception.
  132. // Seems to happen on reverse-tab, but can also happen on tab (seems to be a race condition - only happens sometimes).
  133. // There appears to be no workaround for this - googled for quite a while.
  134. ntr.setEndPoint("EndToEnd", tr);
  135. pos = String(ntr.text).replace(/\r/g,"").length;
  136. }catch(e){
  137. // If focus has shifted, 0 is fine for caret pos.
  138. }
  139. }
  140. return pos;
  141. },
  142. _setCaretPos: function(/*DomNode*/ element, /*Number*/ location){
  143. location = parseInt(location);
  144. dijit.selectInputText(element, location, location);
  145. },
  146. _setDisabledAttr: function(/*Boolean*/ value){
  147. // Additional code to set disabled state of ComboBox node.
  148. // Overrides _FormValueWidget._setDisabledAttr() or ValidationTextBox._setDisabledAttr().
  149. this.inherited(arguments);
  150. dijit.setWaiState(this.domNode, "disabled", value);
  151. },
  152. _abortQuery: function(){
  153. // stop in-progress query
  154. if(this.searchTimer){
  155. clearTimeout(this.searchTimer);
  156. this.searchTimer = null;
  157. }
  158. if(this._fetchHandle){
  159. if(this._fetchHandle.abort){ this._fetchHandle.abort(); }
  160. this._fetchHandle = null;
  161. }
  162. },
  163. _onInput: function(/*Event*/ evt){
  164. // summary:
  165. // Handles paste events
  166. if(!this.searchTimer && (evt.type == 'paste'/*IE|WebKit*/ || evt.type == 'input'/*Firefox*/) && this._lastInput != this.textbox.value){
  167. this.searchTimer = setTimeout(dojo.hitch(this, function(){
  168. this._onKey({charOrCode: 229}); // fake IME key to cause a search
  169. }), 100); // long delay that will probably be preempted by keyboard input
  170. }
  171. this.inherited(arguments);
  172. },
  173. _onKey: function(/*Event*/ evt){
  174. // summary:
  175. // Handles keyboard events
  176. var key = evt.charOrCode;
  177. // except for cutting/pasting case - ctrl + x/v
  178. if(evt.altKey || ((evt.ctrlKey || evt.metaKey) && (key != 'x' && key != 'v')) || key == dojo.keys.SHIFT){
  179. return; // throw out weird key combinations and spurious events
  180. }
  181. var doSearch = false;
  182. var pw = this.dropDown;
  183. var dk = dojo.keys;
  184. var highlighted = null;
  185. this._prev_key_backspace = false;
  186. this._abortQuery();
  187. // _HasDropDown will do some of the work:
  188. // 1. when drop down is not yet shown:
  189. // - if user presses the down arrow key, call loadDropDown()
  190. // 2. when drop down is already displayed:
  191. // - on ESC key, call closeDropDown()
  192. // - otherwise, call dropDown.handleKey() to process the keystroke
  193. this.inherited(arguments);
  194. if(this._opened){
  195. highlighted = pw.getHighlightedOption();
  196. }
  197. switch(key){
  198. case dk.PAGE_DOWN:
  199. case dk.DOWN_ARROW:
  200. case dk.PAGE_UP:
  201. case dk.UP_ARROW:
  202. // Keystroke caused ComboBox_menu to move to a different item.
  203. // Copy new item to <input> box.
  204. if(this._opened){
  205. this._announceOption(highlighted);
  206. }
  207. dojo.stopEvent(evt);
  208. break;
  209. case dk.ENTER:
  210. // prevent submitting form if user presses enter. Also
  211. // prevent accepting the value if either Next or Previous
  212. // are selected
  213. if(highlighted){
  214. // only stop event on prev/next
  215. if(highlighted == pw.nextButton){
  216. this._nextSearch(1);
  217. dojo.stopEvent(evt);
  218. break;
  219. }else if(highlighted == pw.previousButton){
  220. this._nextSearch(-1);
  221. dojo.stopEvent(evt);
  222. break;
  223. }
  224. }else{
  225. // Update 'value' (ex: KY) according to currently displayed text
  226. this._setBlurValue(); // set value if needed
  227. this._setCaretPos(this.focusNode, this.focusNode.value.length); // move cursor to end and cancel highlighting
  228. }
  229. // default case:
  230. // if enter pressed while drop down is open, or for FilteringSelect,
  231. // if we are in the middle of a query to convert a directly typed in value to an item,
  232. // prevent submit
  233. if(this._opened || this._fetchHandle){
  234. dojo.stopEvent(evt);
  235. }
  236. // fall through
  237. case dk.TAB:
  238. var newvalue = this.get('displayedValue');
  239. // if the user had More Choices selected fall into the
  240. // _onBlur handler
  241. if(pw && (
  242. newvalue == pw._messages["previousMessage"] ||
  243. newvalue == pw._messages["nextMessage"])
  244. ){
  245. break;
  246. }
  247. if(highlighted){
  248. this._selectOption();
  249. }
  250. if(this._opened){
  251. this._lastQuery = null; // in case results come back later
  252. this.closeDropDown();
  253. }
  254. break;
  255. case ' ':
  256. if(highlighted){
  257. // user is effectively clicking a choice in the drop down menu
  258. dojo.stopEvent(evt);
  259. this._selectOption();
  260. this.closeDropDown();
  261. }else{
  262. // user typed a space into the input box, treat as normal character
  263. doSearch = true;
  264. }
  265. break;
  266. case dk.DELETE:
  267. case dk.BACKSPACE:
  268. this._prev_key_backspace = true;
  269. doSearch = true;
  270. break;
  271. default:
  272. // Non char keys (F1-F12 etc..) shouldn't open list.
  273. // Ascii characters and IME input (Chinese, Japanese etc.) should.
  274. //IME input produces keycode == 229.
  275. doSearch = typeof key == 'string' || key == 229;
  276. }
  277. if(doSearch){
  278. // need to wait a tad before start search so that the event
  279. // bubbles through DOM and we have value visible
  280. this.item = undefined; // undefined means item needs to be set
  281. this.searchTimer = setTimeout(dojo.hitch(this, "_startSearchFromInput"),1);
  282. }
  283. },
  284. _autoCompleteText: function(/*String*/ text){
  285. // summary:
  286. // Fill in the textbox with the first item from the drop down
  287. // list, and highlight the characters that were
  288. // auto-completed. For example, if user typed "CA" and the
  289. // drop down list appeared, the textbox would be changed to
  290. // "California" and "ifornia" would be highlighted.
  291. var fn = this.focusNode;
  292. // IE7: clear selection so next highlight works all the time
  293. dijit.selectInputText(fn, fn.value.length);
  294. // does text autoComplete the value in the textbox?
  295. var caseFilter = this.ignoreCase? 'toLowerCase' : 'substr';
  296. if(text[caseFilter](0).indexOf(this.focusNode.value[caseFilter](0)) == 0){
  297. var cpos = this._getCaretPos(fn);
  298. // only try to extend if we added the last character at the end of the input
  299. if((cpos+1) > fn.value.length){
  300. // only add to input node as we would overwrite Capitalisation of chars
  301. // actually, that is ok
  302. fn.value = text;//.substr(cpos);
  303. // visually highlight the autocompleted characters
  304. dijit.selectInputText(fn, cpos);
  305. }
  306. }else{
  307. // text does not autoComplete; replace the whole value and highlight
  308. fn.value = text;
  309. dijit.selectInputText(fn);
  310. }
  311. },
  312. _openResultList: function(/*Object*/ results, /*Object*/ dataObject){
  313. // summary:
  314. // Callback when a search completes.
  315. // description:
  316. // 1. generates drop-down list and calls _showResultList() to display it
  317. // 2. if this result list is from user pressing "more choices"/"previous choices"
  318. // then tell screen reader to announce new option
  319. this._fetchHandle = null;
  320. if( this.disabled ||
  321. this.readOnly ||
  322. (dataObject.query[this.searchAttr] != this._lastQuery)
  323. ){
  324. return;
  325. }
  326. var wasSelected = this.dropDown._highlighted_option && dojo.hasClass(this.dropDown._highlighted_option, "dijitMenuItemSelected");
  327. this.dropDown.clearResultList();
  328. if(!results.length && !this._maxOptions){ // if no results and not just the previous choices button
  329. this.closeDropDown();
  330. return;
  331. }
  332. // Fill in the textbox with the first item from the drop down list,
  333. // and highlight the characters that were auto-completed. For
  334. // example, if user typed "CA" and the drop down list appeared, the
  335. // textbox would be changed to "California" and "ifornia" would be
  336. // highlighted.
  337. dataObject._maxOptions = this._maxOptions;
  338. var nodes = this.dropDown.createOptions(
  339. results,
  340. dataObject,
  341. dojo.hitch(this, "_getMenuLabelFromItem")
  342. );
  343. // show our list (only if we have content, else nothing)
  344. this._showResultList();
  345. // #4091:
  346. // tell the screen reader that the paging callback finished by
  347. // shouting the next choice
  348. if(dataObject.direction){
  349. if(1 == dataObject.direction){
  350. this.dropDown.highlightFirstOption();
  351. }else if(-1 == dataObject.direction){
  352. this.dropDown.highlightLastOption();
  353. }
  354. if(wasSelected){
  355. this._announceOption(this.dropDown.getHighlightedOption());
  356. }
  357. }else if(this.autoComplete && !this._prev_key_backspace
  358. // when the user clicks the arrow button to show the full list,
  359. // startSearch looks for "*".
  360. // it does not make sense to autocomplete
  361. // if they are just previewing the options available.
  362. && !/^[*]+$/.test(dataObject.query[this.searchAttr])){
  363. this._announceOption(nodes[1]); // 1st real item
  364. }
  365. },
  366. _showResultList: function(){
  367. // summary:
  368. // Display the drop down if not already displayed, or if it is displayed, then
  369. // reposition it if necessary (reposition may be necessary if drop down's height changed).
  370. this.closeDropDown(true);
  371. // hide the tooltip
  372. this.displayMessage("");
  373. this.openDropDown();
  374. dijit.setWaiState(this.domNode, "expanded", "true");
  375. },
  376. loadDropDown: function(/*Function*/ callback){
  377. // Overrides _HasDropDown.loadDropDown().
  378. // This is called when user has pressed button icon or pressed the down arrow key
  379. // to open the drop down.
  380. this._startSearchAll();
  381. },
  382. isLoaded: function(){
  383. // signal to _HasDropDown that it needs to call loadDropDown() to load the
  384. // drop down asynchronously before displaying it
  385. return false;
  386. },
  387. closeDropDown: function(){
  388. // Overrides _HasDropDown.closeDropDown(). Closes the drop down (assuming that it's open).
  389. // This method is the callback when the user types ESC or clicking
  390. // the button icon while the drop down is open. It's also called by other code.
  391. this._abortQuery();
  392. if(this._opened){
  393. this.inherited(arguments);
  394. dijit.setWaiState(this.domNode, "expanded", "false");
  395. dijit.removeWaiState(this.focusNode,"activedescendant");
  396. }
  397. },
  398. _setBlurValue: function(){
  399. // if the user clicks away from the textbox OR tabs away, set the
  400. // value to the textbox value
  401. // #4617:
  402. // if value is now more choices or previous choices, revert
  403. // the value
  404. var newvalue = this.get('displayedValue');
  405. var pw = this.dropDown;
  406. if(pw && (
  407. newvalue == pw._messages["previousMessage"] ||
  408. newvalue == pw._messages["nextMessage"]
  409. )
  410. ){
  411. this._setValueAttr(this._lastValueReported, true);
  412. }else if(typeof this.item == "undefined"){
  413. // Update 'value' (ex: KY) according to currently displayed text
  414. this.item = null;
  415. this.set('displayedValue', newvalue);
  416. }else{
  417. if(this.value != this._lastValueReported){
  418. dijit.form._FormValueWidget.prototype._setValueAttr.call(this, this.value, true);
  419. }
  420. this._refreshState();
  421. }
  422. },
  423. _onBlur: function(){
  424. // summary:
  425. // Called magically when focus has shifted away from this widget and it's drop down
  426. this.closeDropDown();
  427. this.inherited(arguments);
  428. },
  429. _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){
  430. // summary:
  431. // Set the displayed valued in the input box, and the hidden value
  432. // that gets submitted, based on a dojo.data store item.
  433. // description:
  434. // Users shouldn't call this function; they should be calling
  435. // set('item', value)
  436. // tags:
  437. // private
  438. if(!displayedValue){
  439. displayedValue = this.store.getValue(item, this.searchAttr);
  440. }
  441. var value = this._getValueField() != this.searchAttr? this.store.getIdentity(item) : displayedValue;
  442. this._set("item", item);
  443. dijit.form.ComboBox.superclass._setValueAttr.call(this, value, priorityChange, displayedValue);
  444. },
  445. _announceOption: function(/*Node*/ node){
  446. // summary:
  447. // a11y code that puts the highlighted option in the textbox.
  448. // This way screen readers will know what is happening in the
  449. // menu.
  450. if(!node){
  451. return;
  452. }
  453. // pull the text value from the item attached to the DOM node
  454. var newValue;
  455. if(node == this.dropDown.nextButton ||
  456. node == this.dropDown.previousButton){
  457. newValue = node.innerHTML;
  458. this.item = undefined;
  459. this.value = '';
  460. }else{
  461. newValue = this.store.getValue(node.item, this.searchAttr).toString();
  462. this.set('item', node.item, false, newValue);
  463. }
  464. // get the text that the user manually entered (cut off autocompleted text)
  465. this.focusNode.value = this.focusNode.value.substring(0, this._lastInput.length);
  466. // set up ARIA activedescendant
  467. dijit.setWaiState(this.focusNode, "activedescendant", dojo.attr(node, "id"));
  468. // autocomplete the rest of the option to announce change
  469. this._autoCompleteText(newValue);
  470. },
  471. _selectOption: function(/*Event*/ evt){
  472. // summary:
  473. // Menu callback function, called when an item in the menu is selected.
  474. if(evt){
  475. this._announceOption(evt.target);
  476. }
  477. this.closeDropDown();
  478. this._setCaretPos(this.focusNode, this.focusNode.value.length);
  479. dijit.form._FormValueWidget.prototype._setValueAttr.call(this, this.value, true); // set this.value and fire onChange
  480. },
  481. _startSearchAll: function(){
  482. this._startSearch('');
  483. },
  484. _startSearchFromInput: function(){
  485. this._startSearch(this.focusNode.value.replace(/([\\\*\?])/g, "\\$1"));
  486. },
  487. _getQueryString: function(/*String*/ text){
  488. return dojo.string.substitute(this.queryExpr, [text]);
  489. },
  490. _startSearch: function(/*String*/ key){
  491. // summary:
  492. // Starts a search for elements matching key (key=="" means to return all items),
  493. // and calls _openResultList() when the search completes, to display the results.
  494. if(!this.dropDown){
  495. var popupId = this.id + "_popup",
  496. dropDownConstructor = dojo.getObject(this.dropDownClass, false);
  497. this.dropDown = new dropDownConstructor({
  498. onChange: dojo.hitch(this, this._selectOption),
  499. id: popupId,
  500. dir: this.dir
  501. });
  502. dijit.removeWaiState(this.focusNode,"activedescendant");
  503. dijit.setWaiState(this.textbox,"owns",popupId); // associate popup with textbox
  504. }
  505. // create a new query to prevent accidentally querying for a hidden
  506. // value from FilteringSelect's keyField
  507. var query = dojo.clone(this.query); // #5970
  508. this._lastInput = key; // Store exactly what was entered by the user.
  509. this._lastQuery = query[this.searchAttr] = this._getQueryString(key);
  510. // #5970: set _lastQuery, *then* start the timeout
  511. // otherwise, if the user types and the last query returns before the timeout,
  512. // _lastQuery won't be set and their input gets rewritten
  513. this.searchTimer=setTimeout(dojo.hitch(this, function(query, _this){
  514. this.searchTimer = null;
  515. var fetch = {
  516. queryOptions: {
  517. ignoreCase: this.ignoreCase,
  518. deep: true
  519. },
  520. query: query,
  521. onBegin: dojo.hitch(this, "_setMaxOptions"),
  522. onComplete: dojo.hitch(this, "_openResultList"),
  523. onError: function(errText){
  524. _this._fetchHandle = null;
  525. console.error('dijit.form.ComboBox: ' + errText);
  526. _this.closeDropDown();
  527. },
  528. start: 0,
  529. count: this.pageSize
  530. };
  531. dojo.mixin(fetch, _this.fetchProperties);
  532. this._fetchHandle = _this.store.fetch(fetch);
  533. var nextSearch = function(dataObject, direction){
  534. dataObject.start += dataObject.count*direction;
  535. // #4091:
  536. // tell callback the direction of the paging so the screen
  537. // reader knows which menu option to shout
  538. dataObject.direction = direction;
  539. this._fetchHandle = this.store.fetch(dataObject);
  540. this.focus();
  541. };
  542. this._nextSearch = this.dropDown.onPage = dojo.hitch(this, nextSearch, this._fetchHandle);
  543. }, query, this), this.searchDelay);
  544. },
  545. _setMaxOptions: function(size, request){
  546. this._maxOptions = size;
  547. },
  548. _getValueField: function(){
  549. // summary:
  550. // Helper for postMixInProperties() to set this.value based on data inlined into the markup.
  551. // Returns the attribute name in the item (in dijit.form._ComboBoxDataStore) to use as the value.
  552. return this.searchAttr;
  553. },
  554. //////////// INITIALIZATION METHODS ///////////////////////////////////////
  555. constructor: function(){
  556. this.query={};
  557. this.fetchProperties={};
  558. },
  559. postMixInProperties: function(){
  560. if(!this.store){
  561. var srcNodeRef = this.srcNodeRef;
  562. // if user didn't specify store, then assume there are option tags
  563. this.store = new dijit.form._ComboBoxDataStore(srcNodeRef);
  564. // if there is no value set and there is an option list, set
  565. // the value to the first value to be consistent with native
  566. // Select
  567. // Firefox and Safari set value
  568. // IE6 and Opera set selectedIndex, which is automatically set
  569. // by the selected attribute of an option tag
  570. // IE6 does not set value, Opera sets value = selectedIndex
  571. if(!("value" in this.params)){
  572. var item = (this.item = this.store.fetchSelectedItem());
  573. if(item){
  574. var valueField = this._getValueField();
  575. this.value = this.store.getValue(item, valueField);
  576. }
  577. }
  578. }
  579. this.inherited(arguments);
  580. },
  581. postCreate: function(){
  582. // summary:
  583. // Subclasses must call this method from their postCreate() methods
  584. // tags:
  585. // protected
  586. // find any associated label element and add to ComboBox node.
  587. var label=dojo.query('label[for="'+this.id+'"]');
  588. if(label.length){
  589. label[0].id = (this.id+"_label");
  590. dijit.setWaiState(this.domNode, "labelledby", label[0].id);
  591. }
  592. this.inherited(arguments);
  593. },
  594. _setHasDownArrowAttr: function(val){
  595. this.hasDownArrow = val;
  596. this._buttonNode.style.display = val ? "" : "none";
  597. },
  598. _getMenuLabelFromItem: function(/*Item*/ item){
  599. var label = this.labelFunc(item, this.store),
  600. labelType = this.labelType;
  601. // If labelType is not "text" we don't want to screw any markup ot whatever.
  602. if(this.highlightMatch != "none" && this.labelType == "text" && this._lastInput){
  603. label = this.doHighlight(label, this._escapeHtml(this._lastInput));
  604. labelType = "html";
  605. }
  606. return {html: labelType == "html", label: label};
  607. },
  608. doHighlight: function(/*String*/ label, /*String*/ find){
  609. // summary:
  610. // Highlights the string entered by the user in the menu. By default this
  611. // highlights the first occurrence found. Override this method
  612. // to implement your custom highlighting.
  613. // tags:
  614. // protected
  615. var
  616. // Add (g)lobal modifier when this.highlightMatch == "all" and (i)gnorecase when this.ignoreCase == true
  617. modifiers = (this.ignoreCase ? "i" : "") + (this.highlightMatch == "all" ? "g" : ""),
  618. i = this.queryExpr.indexOf("${0}");
  619. find = dojo.regexp.escapeString(find); // escape regexp special chars
  620. return this._escapeHtml(label).replace(
  621. // prepend ^ when this.queryExpr == "${0}*" and append $ when this.queryExpr == "*${0}"
  622. new RegExp((i == 0 ? "^" : "") + "("+ find +")" + (i == (this.queryExpr.length - 4) ? "$" : ""), modifiers),
  623. '<span class="dijitComboBoxHighlightMatch">$1</span>'
  624. ); // returns String, (almost) valid HTML (entities encoded)
  625. },
  626. _escapeHtml: function(/*String*/ str){
  627. // TODO Should become dojo.html.entities(), when exists use instead
  628. // summary:
  629. // Adds escape sequences for special characters in XML: &<>"'
  630. str = String(str).replace(/&/gm, "&amp;").replace(/</gm, "&lt;")
  631. .replace(/>/gm, "&gt;").replace(/"/gm, "&quot;");
  632. return str; // string
  633. },
  634. reset: function(){
  635. // Overrides the _FormWidget.reset().
  636. // Additionally reset the .item (to clean up).
  637. this.item = null;
  638. this.inherited(arguments);
  639. },
  640. labelFunc: function(/*item*/ item, /*dojo.data.store*/ store){
  641. // summary:
  642. // Computes the label to display based on the dojo.data store item.
  643. // returns:
  644. // The label that the ComboBox should display
  645. // tags:
  646. // private
  647. // Use toString() because XMLStore returns an XMLItem whereas this
  648. // method is expected to return a String (#9354)
  649. return store.getValue(item, this.labelAttr || this.searchAttr).toString(); // String
  650. }
  651. }
  652. );
  653. dojo.declare(
  654. "dijit.form._ComboBoxMenu",
  655. [dijit._Widget, dijit._Templated, dijit._CssStateMixin],
  656. {
  657. // summary:
  658. // Focus-less menu for internal use in `dijit.form.ComboBox`
  659. // tags:
  660. // private
  661. templateString: "<ul class='dijitReset dijitMenu' dojoAttachEvent='onmousedown:_onMouseDown,onmouseup:_onMouseUp,onmouseover:_onMouseOver,onmouseout:_onMouseOut' style='overflow: \"auto\"; overflow-x: \"hidden\";'>"
  662. +"<li class='dijitMenuItem dijitMenuPreviousButton' dojoAttachPoint='previousButton' role='option'></li>"
  663. +"<li class='dijitMenuItem dijitMenuNextButton' dojoAttachPoint='nextButton' role='option'></li>"
  664. +"</ul>",
  665. // _messages: Object
  666. // Holds "next" and "previous" text for paging buttons on drop down
  667. _messages: null,
  668. baseClass: "dijitComboBoxMenu",
  669. postMixInProperties: function(){
  670. this.inherited(arguments);
  671. this._messages = dojo.i18n.getLocalization("dijit.form", "ComboBox", this.lang);
  672. },
  673. buildRendering: function(){
  674. this.inherited(arguments);
  675. // fill in template with i18n messages
  676. this.previousButton.innerHTML = this._messages["previousMessage"];
  677. this.nextButton.innerHTML = this._messages["nextMessage"];
  678. },
  679. _setValueAttr: function(/*Object*/ value){
  680. this.value = value;
  681. this.onChange(value);
  682. },
  683. // stubs
  684. onChange: function(/*Object*/ value){
  685. // summary:
  686. // Notifies ComboBox/FilteringSelect that user clicked an option in the drop down menu.
  687. // Probably should be called onSelect.
  688. // tags:
  689. // callback
  690. },
  691. onPage: function(/*Number*/ direction){
  692. // summary:
  693. // Notifies ComboBox/FilteringSelect that user clicked to advance to next/previous page.
  694. // tags:
  695. // callback
  696. },
  697. onClose: function(){
  698. // summary:
  699. // Callback from dijit.popup code to this widget, notifying it that it closed
  700. // tags:
  701. // private
  702. this._blurOptionNode();
  703. },
  704. _createOption: function(/*Object*/ item, labelFunc){
  705. // summary:
  706. // Creates an option to appear on the popup menu subclassed by
  707. // `dijit.form.FilteringSelect`.
  708. var menuitem = dojo.create("li", {
  709. "class": "dijitReset dijitMenuItem" +(this.isLeftToRight() ? "" : " dijitMenuItemRtl"),
  710. role: "option"
  711. });
  712. var labelObject = labelFunc(item);
  713. if(labelObject.html){
  714. menuitem.innerHTML = labelObject.label;
  715. }else{
  716. menuitem.appendChild(
  717. dojo.doc.createTextNode(labelObject.label)
  718. );
  719. }
  720. // #3250: in blank options, assign a normal height
  721. if(menuitem.innerHTML == ""){
  722. menuitem.innerHTML = "&nbsp;";
  723. }
  724. menuitem.item=item;
  725. return menuitem;
  726. },
  727. createOptions: function(results, dataObject, labelFunc){
  728. // summary:
  729. // Fills in the items in the drop down list
  730. // results:
  731. // Array of dojo.data items
  732. // dataObject:
  733. // dojo.data store
  734. // labelFunc:
  735. // Function to produce a label in the drop down list from a dojo.data item
  736. //this._dataObject=dataObject;
  737. //this._dataObject.onComplete=dojo.hitch(comboBox, comboBox._openResultList);
  738. // display "Previous . . ." button
  739. this.previousButton.style.display = (dataObject.start == 0) ? "none" : "";
  740. dojo.attr(this.previousButton, "id", this.id + "_prev");
  741. // create options using _createOption function defined by parent
  742. // ComboBox (or FilteringSelect) class
  743. // #2309:
  744. // iterate over cache nondestructively
  745. dojo.forEach(results, function(item, i){
  746. var menuitem = this._createOption(item, labelFunc);
  747. dojo.attr(menuitem, "id", this.id + i);
  748. this.domNode.insertBefore(menuitem, this.nextButton);
  749. }, this);
  750. // display "Next . . ." button
  751. var displayMore = false;
  752. //Try to determine if we should show 'more'...
  753. if(dataObject._maxOptions && dataObject._maxOptions != -1){
  754. if((dataObject.start + dataObject.count) < dataObject._maxOptions){
  755. displayMore = true;
  756. }else if((dataObject.start + dataObject.count) > dataObject._maxOptions && dataObject.count == results.length){
  757. //Weird return from a datastore, where a start + count > maxOptions
  758. // implies maxOptions isn't really valid and we have to go into faking it.
  759. //And more or less assume more if count == results.length
  760. displayMore = true;
  761. }
  762. }else if(dataObject.count == results.length){
  763. //Don't know the size, so we do the best we can based off count alone.
  764. //So, if we have an exact match to count, assume more.
  765. displayMore = true;
  766. }
  767. this.nextButton.style.display = displayMore ? "" : "none";
  768. dojo.attr(this.nextButton,"id", this.id + "_next");
  769. return this.domNode.childNodes;
  770. },
  771. clearResultList: function(){
  772. // summary:
  773. // Clears the entries in the drop down list, but of course keeps the previous and next buttons.
  774. while(this.domNode.childNodes.length>2){
  775. this.domNode.removeChild(this.domNode.childNodes[this.domNode.childNodes.length-2]);
  776. }
  777. this._blurOptionNode();
  778. },
  779. _onMouseDown: function(/*Event*/ evt){
  780. dojo.stopEvent(evt);
  781. },
  782. _onMouseUp: function(/*Event*/ evt){
  783. if(evt.target === this.domNode || !this._highlighted_option){
  784. // !this._highlighted_option check to prevent immediate selection when menu appears on top
  785. // of <input>, see #9898. Note that _HasDropDown also has code to prevent this.
  786. return;
  787. }else if(evt.target == this.previousButton){
  788. this._blurOptionNode();
  789. this.onPage(-1);
  790. }else if(evt.target == this.nextButton){
  791. this._blurOptionNode();
  792. this.onPage(1);
  793. }else{
  794. var tgt = evt.target;
  795. // while the clicked node is inside the div
  796. while(!tgt.item){
  797. // recurse to the top
  798. tgt = tgt.parentNode;
  799. }
  800. this._setValueAttr({ target: tgt }, true);
  801. }
  802. },
  803. _onMouseOver: function(/*Event*/ evt){
  804. if(evt.target === this.domNode){ return; }
  805. var tgt = evt.target;
  806. if(!(tgt == this.previousButton || tgt == this.nextButton)){
  807. // while the clicked node is inside the div
  808. while(!tgt.item){
  809. // recurse to the top
  810. tgt = tgt.parentNode;
  811. }
  812. }
  813. this._focusOptionNode(tgt);
  814. },
  815. _onMouseOut: function(/*Event*/ evt){
  816. if(evt.target === this.domNode){ return; }
  817. this._blurOptionNode();
  818. },
  819. _focusOptionNode: function(/*DomNode*/ node){
  820. // summary:
  821. // Does the actual highlight.
  822. if(this._highlighted_option != node){
  823. this._blurOptionNode();
  824. this._highlighted_option = node;
  825. dojo.addClass(this._highlighted_option, "dijitMenuItemSelected");
  826. }
  827. },
  828. _blurOptionNode: function(){
  829. // summary:
  830. // Removes highlight on highlighted option.
  831. if(this._highlighted_option){
  832. dojo.removeClass(this._highlighted_option, "dijitMenuItemSelected");
  833. this._highlighted_option = null;
  834. }
  835. },
  836. _highlightNextOption: function(){
  837. // summary:
  838. // Highlight the item just below the current selection.
  839. // If nothing selected, highlight first option.
  840. // because each press of a button clears the menu,
  841. // the highlighted option sometimes becomes detached from the menu!
  842. // test to see if the option has a parent to see if this is the case.
  843. if(!this.getHighlightedOption()){
  844. var fc = this.domNode.firstChild;
  845. this._focusOptionNode(fc.style.display == "none" ? fc.nextSibling : fc);
  846. }else{
  847. var ns = this._highlighted_option.nextSibling;
  848. if(ns && ns.style.display != "none"){
  849. this._focusOptionNode(ns);
  850. }else{
  851. this.highlightFirstOption();
  852. }
  853. }
  854. // scrollIntoView is called outside of _focusOptionNode because in IE putting it inside causes the menu to scroll up on mouseover
  855. dojo.window.scrollIntoView(this._highlighted_option);
  856. },
  857. highlightFirstOption: function(){
  858. // summary:
  859. // Highlight the first real item in the list (not Previous Choices).
  860. var first = this.domNode.firstChild;
  861. var second = first.nextSibling;
  862. this._focusOptionNode(second.style.display == "none" ? first : second); // remotely possible that Previous Choices is the only thing in the list
  863. dojo.window.scrollIntoView(this._highlighted_option);
  864. },
  865. highlightLastOption: function(){
  866. // summary:
  867. // Highlight the last real item in the list (not More Choices).
  868. this._focusOptionNode(this.domNode.lastChild.previousSibling);
  869. dojo.window.scrollIntoView(this._highlighted_option);
  870. },
  871. _highlightPrevOption: function(){
  872. // summary:
  873. // Highlight the item just above the current selection.
  874. // If nothing selected, highlight last option (if
  875. // you select Previous and try to keep scrolling up the list).
  876. if(!this.getHighlightedOption()){
  877. var lc = this.domNode.lastChild;
  878. this._focusOptionNode(lc.style.display == "none" ? lc.previousSibling : lc);
  879. }else{
  880. var ps = this._highlighted_option.previousSibling;
  881. if(ps && ps.style.display != "none"){
  882. this._focusOptionNode(ps);
  883. }else{
  884. this.highlightLastOption();
  885. }
  886. }
  887. dojo.window.scrollIntoView(this._highlighted_option);
  888. },
  889. _page: function(/*Boolean*/ up){
  890. // summary:
  891. // Handles page-up and page-down keypresses
  892. var scrollamount = 0;
  893. var oldscroll = this.domNode.scrollTop;
  894. var height = dojo.style(this.domNode, "height");
  895. // if no item is highlighted, highlight the first option
  896. if(!this.getHighlightedOption()){
  897. this._highlightNextOption();
  898. }
  899. while(scrollamount<height){
  900. if(up){
  901. // stop at option 1
  902. if(!this.getHighlightedOption().previousSibling ||
  903. this._highlighted_option.previousSibling.style.display == "none"){
  904. break;
  905. }
  906. this._highlightPrevOption();
  907. }else{
  908. // stop at last option
  909. if(!this.getHighlightedOption().nextSibling ||
  910. this._highlighted_option.nextSibling.style.display == "none"){
  911. break;
  912. }
  913. this._highlightNextOption();
  914. }
  915. // going backwards
  916. var newscroll=this.domNode.scrollTop;
  917. scrollamount+=(newscroll-oldscroll)*(up ? -1:1);
  918. oldscroll=newscroll;
  919. }
  920. },
  921. pageUp: function(){
  922. // summary:
  923. // Handles pageup keypress.
  924. // TODO: just call _page directly from handleKey().
  925. // tags:
  926. // private
  927. this._page(true);
  928. },
  929. pageDown: function(){
  930. // summary:
  931. // Handles pagedown keypress.
  932. // TODO: just call _page directly from handleKey().
  933. // tags:
  934. // private
  935. this._page(false);
  936. },
  937. getHighlightedOption: function(){
  938. // summary:
  939. // Returns the highlighted option.
  940. var ho = this._highlighted_option;
  941. return (ho && ho.parentNode) ? ho : null;
  942. },
  943. handleKey: function(evt){
  944. // summary:
  945. // Handle keystroke event forwarded from ComboBox, returning false if it's
  946. // a keystroke I recognize and process, true otherwise.
  947. switch(evt.charOrCode){
  948. case dojo.keys.DOWN_ARROW:
  949. this._highlightNextOption();
  950. return false;
  951. case dojo.keys.PAGE_DOWN:
  952. this.pageDown();
  953. return false;
  954. case dojo.keys.UP_ARROW:
  955. this._highlightPrevOption();
  956. return false;
  957. case dojo.keys.PAGE_UP:
  958. this.pageUp();
  959. return false;
  960. default:
  961. return true;
  962. }
  963. }
  964. }
  965. );
  966. dojo.declare(
  967. "dijit.form.ComboBox",
  968. [dijit.form.ValidationTextBox, dijit.form.ComboBoxMixin],
  969. {
  970. // summary:
  971. // Auto-completing text box, and base class for dijit.form.FilteringSelect.
  972. //
  973. // description:
  974. // The drop down box's values are populated from an class called
  975. // a data provider, which returns a list of values based on the characters
  976. // that the user has typed into the input box.
  977. // If OPTION tags are used as the data provider via markup,
  978. // then the OPTION tag's child text node is used as the widget value
  979. // when selected. The OPTION tag's value attribute is ignored.
  980. // To set the default value when using OPTION tags, specify the selected
  981. // attribute on 1 of the child OPTION tags.
  982. //
  983. // Some of the options to the ComboBox are actually arguments to the data
  984. // provider.
  985. _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){
  986. // summary:
  987. // Hook so set('value', value) works.
  988. // description:
  989. // Sets the value of the select.
  990. this._set("item", null); // value not looked up in store
  991. if(!value){ value = ''; } // null translates to blank
  992. dijit.form.ValidationTextBox.prototype._setValueAttr.call(this, value, priorityChange, displayedValue);
  993. }
  994. }
  995. );
  996. dojo.declare("dijit.form._ComboBoxDataStore", null, {
  997. // summary:
  998. // Inefficient but small data store specialized for inlined `dijit.form.ComboBox` data
  999. //
  1000. // description:
  1001. // Provides a store for inlined data like:
  1002. //
  1003. // | <select>
  1004. // | <option value="AL">Alabama</option>
  1005. // | ...
  1006. //
  1007. // Actually. just implements the subset of dojo.data.Read/Notification
  1008. // needed for ComboBox and FilteringSelect to work.
  1009. //
  1010. // Note that an item is just a pointer to the <option> DomNode.
  1011. constructor: function( /*DomNode*/ root){
  1012. this.root = root;
  1013. if(root.tagName != "SELECT" && root.firstChild){
  1014. root = dojo.query("select", root);
  1015. if(root.length > 0){ // SELECT is a child of srcNodeRef
  1016. root = root[0];
  1017. }else{ // no select, so create 1 to parent the option tags to define selectedIndex
  1018. this.root.innerHTML = "<SELECT>"+this.root.innerHTML+"</SELECT>";
  1019. root = this.root.firstChild;
  1020. }
  1021. this.root = root;
  1022. }
  1023. dojo.query("> option", root).forEach(function(node){
  1024. // TODO: this was added in #3858 but unclear why/if it's needed; doesn't seem to be.
  1025. // If it is needed then can we just hide the select itself instead?
  1026. //node.style.display="none";
  1027. node.innerHTML = dojo.trim(node.innerHTML);
  1028. });
  1029. },
  1030. getValue: function( /*item*/ item,
  1031. /*attribute-name-string*/ attribute,
  1032. /*value?*/ defaultValue){
  1033. return (attribute == "value") ? item.value : (item.innerText || item.textContent || '');
  1034. },
  1035. isItemLoaded: function(/*anything*/ something){
  1036. return true;
  1037. },
  1038. getFeatures: function(){
  1039. return {"dojo.data.api.Read": true, "dojo.data.api.Identity": true};
  1040. },
  1041. _fetchItems: function( /*Object*/ args,
  1042. /*Function*/ findCallback,
  1043. /*Function*/ errorCallback){
  1044. // summary:
  1045. // See dojo.data.util.simpleFetch.fetch()
  1046. if(!args.query){ args.query = {}; }
  1047. if(!args.query.name){ args.query.name = ""; }
  1048. if(!args.queryOptions){ args.queryOptions = {}; }
  1049. var matcher = dojo.data.util.filter.patternToRegExp(args.query.name, args.queryOptions.ignoreCase),
  1050. items = dojo.query("> option", this.root).filter(function(option){
  1051. return (option.innerText || option.textContent || '').match(matcher);
  1052. } );
  1053. if(args.sort){
  1054. items.sort(dojo.data.util.sorter.createSortFunction(args.sort, this));
  1055. }
  1056. findCallback(items, args);
  1057. },
  1058. close: function(/*dojo.data.api.Request || args || null*/ request){
  1059. return;
  1060. },
  1061. getLabel: function(/*item*/ item){
  1062. return item.innerHTML;
  1063. },
  1064. getIdentity: function(/*item*/ item){
  1065. return dojo.attr(item, "value");
  1066. },
  1067. fetchItemByIdentity: function(/*Object*/ args){
  1068. // summary:
  1069. // Given the identity of an item, this method returns the item that has
  1070. // that identity through the onItem callback.
  1071. // Refer to dojo.data.api.Identity.fetchItemByIdentity() for more details.
  1072. //
  1073. // description:
  1074. // Given arguments like:
  1075. //
  1076. // | {identity: "CA", onItem: function(item){...}
  1077. //
  1078. // Call `onItem()` with the DOM node `<option value="CA">California</option>`
  1079. var item = dojo.query("> option[value='" + args.identity + "']", this.root)[0];
  1080. args.onItem(item);
  1081. },
  1082. fetchSelectedItem: function(){
  1083. // summary:
  1084. // Get the option marked as selected, like `<option selected>`.
  1085. // Not part of dojo.data API.
  1086. var root = this.root,
  1087. si = root.selectedIndex;
  1088. return typeof si == "number"
  1089. ? dojo.query("> option:nth-child(" + (si != -1 ? si+1 : 1) + ")", root)[0]
  1090. : null; // dojo.data.Item
  1091. }
  1092. });
  1093. //Mix in the simple fetch implementation to this class.
  1094. dojo.extend(dijit.form._ComboBoxDataStore,dojo.data.util.simpleFetch);
  1095. }