1 if(!dojo._hasResource["dijit.form.ComboBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dijit.form.ComboBox"] = true;
3 dojo.provide("dijit.form.ComboBox");
5 dojo.require("dijit.form.ValidationTextBox");
6 dojo.requireLocalization("dijit.form", "ComboBox", null, "zh,ROOT,pt,da,tr,ru,de,sv,ja,he,fi,nb,el,ar,pt-pt,cs,fr,es,ko,nl,zh-tw,pl,it,hu");
9 "dijit.form.ComboBoxMixin",
13 // This is the item returned by the dojo.data.store implementation that
14 // provides the data for this cobobox, it's the currently selected item.
18 // Argument to data provider.
19 // Specifies number of search results per page (before hitting "next" button)
23 // Reference to data provider object used by this ComboBox
27 // A query that can be passed to 'store' to initially filter the items,
28 // before doing further filtering based on `searchAttr` and the key.
29 // Any reference to the `searchAttr` is ignored.
32 // autoComplete: Boolean
33 // If you type in a partial string, and then tab out of the `<input>` box,
34 // automatically copy the first entry displayed in the drop down list to
35 // the `<input>` field
38 // searchDelay: Integer
39 // Delay in milliseconds between when user types something and we start
40 // searching based on that value
44 // Searches pattern match against this field
48 // dojo.data query expression pattern.
49 // `${0}` will be substituted for the user text.
50 // `*` is used for wildcards.
51 // `${0}*` means "starts with", `*${0}*` means "contains", `${0}` means "is"
54 // ignoreCase: Boolean
55 // Set true if the ComboBox should ignore case when matching possible items
58 // hasDownArrow: Boolean
59 // Set this textbox to have a down arrow button.
63 templateString:"<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\"\n\tdojoAttachEvent=\"onmouseenter:_onMouse,onmouseleave:_onMouse,onmousedown:_onMouse\" dojoAttachPoint=\"comboNode\" waiRole=\"combobox\" tabIndex=\"-1\"\n\t><div style=\"overflow:hidden;\"\n\t\t><div class='dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton'\n\t\t\tdojoAttachPoint=\"downArrowNode\" waiRole=\"presentation\"\n\t\t\tdojoAttachEvent=\"onmousedown:_onArrowMouseDown,onmouseup:_onMouse,onmouseenter:_onMouse,onmouseleave:_onMouse\"\n\t\t\t><div class=\"dijitArrowButtonInner\"> </div\n\t\t\t><div class=\"dijitArrowButtonChar\">▼</div\n\t\t></div\n\t\t><div class=\"dijitReset dijitValidationIcon\"><br></div\n\t\t><div class=\"dijitReset dijitValidationIconText\">Χ</div\n\t\t><div class=\"dijitReset dijitInputField\"\n\t\t\t><input type=\"text\" autocomplete=\"off\" name=\"${name}\" class='dijitReset'\n\t\t\tdojoAttachEvent=\"onkeypress:_onKeyPress, onfocus:_update, compositionend,onkeyup\"\n\t\t\tdojoAttachPoint=\"textbox,focusNode\" waiRole=\"textbox\" waiState=\"haspopup-true,autocomplete-list\"\n\t\t/></div\n\t></div\n></div>\n",
65 baseClass:"dijitComboBox",
67 _getCaretPos: function(/*DomNode*/ element){
68 // khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22
70 if(typeof(element.selectionStart)=="number"){
71 // FIXME: this is totally borked on Moz < 1.3. Any recourse?
72 pos = element.selectionStart;
74 // in the case of a mouse click in a popup being handled,
75 // then the dojo.doc.selection is not the textarea, but the popup
76 // var r = dojo.doc.selection.createRange();
77 // hack to get IE 6 to play nice. What a POS browser.
78 var tr = dojo.doc.selection.createRange().duplicate();
79 var ntr = element.createTextRange();
80 tr.move("character",0);
81 ntr.move("character",0);
83 // If control doesnt have focus, you get an exception.
84 // Seems to happen on reverse-tab, but can also happen on tab (seems to be a race condition - only happens sometimes).
85 // There appears to be no workaround for this - googled for quite a while.
86 ntr.setEndPoint("EndToEnd", tr);
87 pos = String(ntr.text).replace(/\r/g,"").length;
89 // If focus has shifted, 0 is fine for caret pos.
95 _setCaretPos: function(/*DomNode*/ element, /*Number*/ location){
96 location = parseInt(location);
97 dijit.selectInputText(element, location, location);
100 _setAttribute: function(/*String*/ attr, /*anything*/ value){
101 // summary: additional code to set disablbed state of combobox node
102 if (attr == "disabled"){
103 dijit.setWaiState(this.comboNode, "disabled", value);
107 _onKeyPress: function(/*Event*/ evt){
108 // summary: handles keyboard events
110 //except for pasting case - ctrl + v(118)
111 if(evt.altKey || (evt.ctrlKey && evt.charCode != 118)){
114 var doSearch = false;
115 var pw = this._popupWidget;
117 if(this._isShowingNow){
123 if(!this._isShowingNow||this._prev_key_esc){
124 this._arrowPressed();
127 this._announceOption(pw.getHighlightedOption());
130 this._prev_key_backspace = false;
131 this._prev_key_esc = false;
136 if(this._isShowingNow){
137 this._announceOption(pw.getHighlightedOption());
140 this._prev_key_backspace = false;
141 this._prev_key_esc = false;
145 // prevent submitting form if user presses enter. Also
146 // prevent accepting the value if either Next or Previous
149 if( this._isShowingNow &&
150 (highlighted = pw.getHighlightedOption())
152 // only stop event on prev/next
153 if(highlighted == pw.nextButton){
157 }else if(highlighted == pw.previousButton){
158 this._nextSearch(-1);
163 this.setDisplayedValue(this.getDisplayedValue());
166 // prevent submit, but allow event to bubble
167 evt.preventDefault();
171 var newvalue = this.getDisplayedValue();
173 // if the user had More Choices selected fall into the
176 newvalue == pw._messages["previousMessage"] ||
177 newvalue == pw._messages["nextMessage"])
181 if(this._isShowingNow){
182 this._prev_key_backspace = false;
183 this._prev_key_esc = false;
184 if(pw.getHighlightedOption()){
185 pw.setValue({ target: pw.getHighlightedOption() }, true);
187 this._hideResultList();
192 this._prev_key_backspace = false;
193 this._prev_key_esc = false;
194 if(this._isShowingNow && pw.getHighlightedOption()){
196 this._selectOption();
197 this._hideResultList();
204 this._prev_key_backspace = false;
205 this._prev_key_esc = true;
206 if(this._isShowingNow){
208 this._hideResultList();
210 this.inherited(arguments);
215 this._prev_key_esc = false;
216 this._prev_key_backspace = true;
220 case dk.RIGHT_ARROW: // fall through
222 this._prev_key_backspace = false;
223 this._prev_key_esc = false;
226 default: // non char keys (F1-F12 etc..) shouldn't open list
227 this._prev_key_backspace = false;
228 this._prev_key_esc = false;
229 if(dojo.isIE || evt.charCode != 0){
233 if(this.searchTimer){
234 clearTimeout(this.searchTimer);
237 // need to wait a tad before start search so that the event
238 // bubbles through DOM and we have value visible
239 setTimeout(dojo.hitch(this, "_startSearchFromInput"),1);
243 _autoCompleteText: function(/*String*/ text){
245 // Fill in the textbox with the first item from the drop down
246 // list, and highlight the characters that were
247 // auto-completed. For example, if user typed "CA" and the
248 // drop down list appeared, the textbox would be changed to
249 // "California" and "ifornia" would be highlighted.
251 var fn = this.focusNode;
253 // IE7: clear selection so next highlight works all the time
254 dijit.selectInputText(fn, fn.value.length);
255 // does text autoComplete the value in the textbox?
256 var caseFilter = this.ignoreCase? 'toLowerCase' : 'substr';
257 if(text[caseFilter](0).indexOf(this.focusNode.value[caseFilter](0)) == 0){
258 var cpos = this._getCaretPos(fn);
259 // only try to extend if we added the last character at the end of the input
260 if((cpos+1) > fn.value.length){
261 // only add to input node as we would overwrite Capitalisation of chars
262 // actually, that is ok
263 fn.value = text;//.substr(cpos);
264 // visually highlight the autocompleted characters
265 dijit.selectInputText(fn, cpos);
268 // text does not autoComplete; replace the whole value and highlight
270 dijit.selectInputText(fn);
274 _openResultList: function(/*Object*/ results, /*Object*/ dataObject){
277 (dataObject.query[this.searchAttr] != this._lastQuery)
281 this._popupWidget.clearResultList();
283 this._hideResultList();
287 // Fill in the textbox with the first item from the drop down list,
288 // and highlight the characters that were auto-completed. For
289 // example, if user typed "CA" and the drop down list appeared, the
290 // textbox would be changed to "California" and "ifornia" would be
293 var zerothvalue = new String(this.store.getValue(results[0], this.searchAttr));
294 if(zerothvalue && this.autoComplete && !this._prev_key_backspace &&
295 (dataObject.query[this.searchAttr] != "*")){
296 // when the user clicks the arrow button to show the full list,
297 // startSearch looks for "*".
298 // it does not make sense to autocomplete
299 // if they are just previewing the options available.
300 this._autoCompleteText(zerothvalue);
302 this._popupWidget.createOptions(
305 dojo.hitch(this, "_getMenuLabelFromItem")
308 // show our list (only if we have content, else nothing)
309 this._showResultList();
312 // tell the screen reader that the paging callback finished by
313 // shouting the next choice
314 if(dataObject.direction){
315 if(1 == dataObject.direction){
316 this._popupWidget.highlightFirstOption();
317 }else if(-1 == dataObject.direction){
318 this._popupWidget.highlightLastOption();
320 this._announceOption(this._popupWidget.getHighlightedOption());
324 _showResultList: function(){
325 this._hideResultList();
326 var items = this._popupWidget.getItems(),
327 visibleCount = Math.min(items.length,this.maxListLength);
328 this._arrowPressed();
330 this.displayMessage("");
332 // Position the list and if it's too big to fit on the screen then
333 // size it to the maximum possible height
334 // Our dear friend IE doesnt take max-height so we need to
335 // calculate that on our own every time
337 // TODO: want to redo this, see
338 // http://trac.dojotoolkit.org/ticket/3272
340 // http://trac.dojotoolkit.org/ticket/4108
342 with(this._popupWidget.domNode.style){
343 // natural size of the list has changed, so erase old
344 // width/height settings, which were hardcoded in a previous
345 // call to this function (via dojo.marginBox() call)
349 var best = this.open();
351 // only set auto scroll bars if necessary prevents issues with
352 // scroll bars appearing when they shouldn't when node is made
353 // wider (fractional pixels cause this)
354 var popupbox = dojo.marginBox(this._popupWidget.domNode);
355 this._popupWidget.domNode.style.overflow =
356 ((best.h==popupbox.h)&&(best.w==popupbox.w)) ? "hidden" : "auto";
358 // borrow TextArea scrollbar test so content isn't covered by
359 // scrollbar and horizontal scrollbar doesn't appear
360 var newwidth = best.w;
361 if(best.h < this._popupWidget.domNode.scrollHeight){
364 dojo.marginBox(this._popupWidget.domNode, {
366 w: Math.max(newwidth, this.domNode.offsetWidth)
368 dijit.setWaiState(this.comboNode, "expanded", "true");
371 _hideResultList: function(){
372 if(this._isShowingNow){
373 dijit.popup.close(this._popupWidget);
375 this._isShowingNow=false;
376 dijit.setWaiState(this.comboNode, "expanded", "false");
377 dijit.removeWaiState(this.focusNode,"activedescendant");
381 _setBlurValue: function(){
382 // if the user clicks away from the textbox OR tabs away, set the
383 // value to the textbox value
385 // if value is now more choices or previous choices, revert
387 var newvalue=this.getDisplayedValue();
388 var pw = this._popupWidget;
390 newvalue == pw._messages["previousMessage"] ||
391 newvalue == pw._messages["nextMessage"]
394 this.setValue(this._lastValueReported, true);
396 this.setDisplayedValue(newvalue);
401 // summary: called magically when focus has shifted away from this widget and it's dropdown
402 this._hideResultList();
404 this.inherited(arguments);
407 _announceOption: function(/*Node*/ node){
409 // a11y code that puts the highlighted option in the textbox
410 // This way screen readers will know what is happening in the
416 // pull the text value from the item attached to the DOM node
418 if( node == this._popupWidget.nextButton ||
419 node == this._popupWidget.previousButton){
420 newValue = node.innerHTML;
422 newValue = this.store.getValue(node.item, this.searchAttr);
424 // get the text that the user manually entered (cut off autocompleted text)
425 this.focusNode.value = this.focusNode.value.substring(0, this._getCaretPos(this.focusNode));
426 //set up ARIA activedescendant
427 dijit.setWaiState(this.focusNode, "activedescendant", dojo.attr(node, "id"));
428 // autocomplete the rest of the option to announce change
429 this._autoCompleteText(newValue);
432 _selectOption: function(/*Event*/ evt){
435 evt ={ target: this._popupWidget.getHighlightedOption()};
437 // what if nothing is highlighted yet?
439 // handle autocompletion where the the user has hit ENTER or TAB
440 this.setDisplayedValue(this.getDisplayedValue());
442 // otherwise the user has accepted the autocompleted value
447 this._hideResultList();
448 this._setCaretPos(this.focusNode, this.store.getValue(tgt.item, this.searchAttr).length);
453 _doSelect: function(tgt){
454 this.item = tgt.item;
455 this.setValue(this.store.getValue(tgt.item, this.searchAttr), true);
458 _onArrowMouseDown: function(evt){
459 // summary: callback when arrow is clicked
460 if(this.disabled || this.readOnly){
465 if(this._isShowingNow){
466 this._hideResultList();
468 // forces full population of results, if they click
469 // on the arrow it means they want to see more options
470 this._startSearch("");
474 _startSearchFromInput: function(){
475 this._startSearch(this.focusNode.value);
478 _getQueryString: function(/*String*/ text){
479 return dojo.string.substitute(this.queryExpr, [text]);
482 _startSearch: function(/*String*/ key){
483 if(!this._popupWidget){
484 var popupId = this.id + "_popup";
485 this._popupWidget = new dijit.form._ComboBoxMenu({
486 onChange: dojo.hitch(this, this._selectOption),
489 dijit.removeWaiState(this.focusNode,"activedescendant");
490 dijit.setWaiState(this.textbox,"owns",popupId); // associate popup with textbox
492 // create a new query to prevent accidentally querying for a hidden
493 // value from FilteringSelect's keyField
494 this.item = null; // #4872
495 var query = dojo.clone(this.query); // #5970
496 this._lastQuery = query[this.searchAttr] = this._getQueryString(key);
497 // #5970: set _lastQuery, *then* start the timeout
498 // otherwise, if the user types and the last query returns before the timeout,
499 // _lastQuery won't be set and their input gets rewritten
500 this.searchTimer=setTimeout(dojo.hitch(this, function(query, _this){
501 var dataObject = this.store.fetch({
503 ignoreCase: this.ignoreCase,
507 onComplete: dojo.hitch(this, "_openResultList"),
508 onError: function(errText){
509 console.error('dijit.form.ComboBox: ' + errText);
510 dojo.hitch(_this, "_hideResultList")();
516 var nextSearch = function(dataObject, direction){
517 dataObject.start += dataObject.count*direction;
519 // tell callback the direction of the paging so the screen
520 // reader knows which menu option to shout
521 dataObject.direction = direction;
522 this.store.fetch(dataObject);
524 this._nextSearch = this._popupWidget.onPage = dojo.hitch(this, nextSearch, dataObject);
525 }, query, this), this.searchDelay);
528 _getValueField:function(){
529 return this.searchAttr;
532 /////////////// Event handlers /////////////////////
534 _arrowPressed: function(){
535 if(!this.disabled && !this.readOnly && this.hasDownArrow){
536 dojo.addClass(this.downArrowNode, "dijitArrowButtonActive");
540 _arrowIdle: function(){
541 if(!this.disabled && !this.readOnly && this.hasDownArrow){
542 dojo.removeClass(this.downArrowNode, "dojoArrowButtonPushed");
547 // this is public so we can't remove until 2.0, but the name
548 // SHOULD be "compositionEnd"
550 compositionend: function(/*Event*/ evt){
552 // When inputting characters using an input method, such as
553 // Asian languages, it will generate this event instead of
554 // onKeyDown event Note: this event is only triggered in FF
556 this.onkeypress({charCode:-1});
559 //////////// INITIALIZATION METHODS ///////////////////////////////////////
561 constructor: function(){
565 postMixInProperties: function(){
566 if(!this.hasDownArrow){
567 this.baseClass = "dijitTextBox";
570 var srcNodeRef = this.srcNodeRef;
572 // if user didn't specify store, then assume there are option tags
573 this.store = new dijit.form._ComboBoxDataStore(srcNodeRef);
575 // if there is no value set and there is an option list, set
576 // the value to the first value to be consistent with native
579 // Firefox and Safari set value
580 // IE6 and Opera set selectedIndex, which is automatically set
581 // by the selected attribute of an option tag
582 // IE6 does not set value, Opera sets value = selectedIndex
584 (typeof srcNodeRef.selectedIndex == "number") &&
585 srcNodeRef.selectedIndex.toString() === this.value)
587 var item = this.store.fetchSelectedItem();
589 this.value = this.store.getValue(item, this._getValueField());
595 _postCreate:function(){
596 //find any associated label element and add to combobox node.
597 var label=dojo.query('label[for="'+this.id+'"]');
599 label[0].id = (this.id+"_label");
600 var cn=this.comboNode;
601 dijit.setWaiState(cn, "labelledby", label[0].id);
602 dijit.setWaiState(cn, "disabled", this.disabled);
607 uninitialize:function(){
608 if(this._popupWidget){
609 this._hideResultList();
610 this._popupWidget.destroy()
614 _getMenuLabelFromItem:function(/*Item*/ item){
617 label: this.store.getValue(item, this.searchAttr)
622 this._isShowingNow=true;
623 return dijit.popup.open({
624 popup: this._popupWidget,
625 around: this.domNode,
632 // Additionally reset the .item (to clean up).
634 this.inherited(arguments);
641 "dijit.form._ComboBoxMenu",
642 [dijit._Widget, dijit._Templated],
646 // Focus-less div based menu for internal use in ComboBox
648 templateString: "<ul class='dijitMenu' dojoAttachEvent='onmousedown:_onMouseDown,onmouseup:_onMouseUp,onmouseover:_onMouseOver,onmouseout:_onMouseOut' tabIndex='-1' style='overflow:\"auto\";'>"
649 +"<li class='dijitMenuItem dijitMenuPreviousButton' dojoAttachPoint='previousButton'></li>"
650 +"<li class='dijitMenuItem dijitMenuNextButton' dojoAttachPoint='nextButton'></li>"
654 postMixInProperties: function(){
655 this._messages = dojo.i18n.getLocalization("dijit.form", "ComboBox", this.lang);
656 this.inherited("postMixInProperties", arguments);
659 setValue: function(/*Object*/ value){
661 this.onChange(value);
665 onChange: function(/*Object*/ value){},
666 onPage: function(/*Number*/ direction){},
668 postCreate:function(){
669 // fill in template with i18n messages
670 this.previousButton.innerHTML = this._messages["previousMessage"];
671 this.nextButton.innerHTML = this._messages["nextMessage"];
672 this.inherited("postCreate", arguments);
676 this._blurOptionNode();
679 _createOption:function(/*Object*/ item, labelFunc){
681 // creates an option to appear on the popup menu subclassed by
684 var labelObject = labelFunc(item);
685 var menuitem = dojo.doc.createElement("li");
686 dijit.setWaiRole(menuitem, "option");
687 if(labelObject.html){
688 menuitem.innerHTML = labelObject.label;
690 menuitem.appendChild(
691 dojo.doc.createTextNode(labelObject.label)
694 // #3250: in blank options, assign a normal height
695 if(menuitem.innerHTML == ""){
696 menuitem.innerHTML = " ";
702 createOptions: function(results, dataObject, labelFunc){
703 //this._dataObject=dataObject;
704 //this._dataObject.onComplete=dojo.hitch(comboBox, comboBox._openResultList);
705 // display "Previous . . ." button
706 this.previousButton.style.display = (dataObject.start == 0) ? "none" : "";
707 dojo.attr(this.previousButton, "id", this.id + "_prev");
708 // create options using _createOption function defined by parent
709 // ComboBox (or FilteringSelect) class
711 // iterate over cache nondestructively
712 dojo.forEach(results, function(item, i){
713 var menuitem = this._createOption(item, labelFunc);
714 menuitem.className = "dijitMenuItem";
715 dojo.attr(menuitem, "id", this.id + i);
716 this.domNode.insertBefore(menuitem, this.nextButton);
718 // display "Next . . ." button
719 this.nextButton.style.display = (dataObject.count == results.length) ? "" : "none";
720 dojo.attr(this.nextButton,"id", this.id + "_next")
723 clearResultList: function(){
724 // keep the previous and next buttons of course
725 while(this.domNode.childNodes.length>2){
726 this.domNode.removeChild(this.domNode.childNodes[this.domNode.childNodes.length-2]);
730 // these functions are called in showResultList
731 getItems: function(){
732 return this.domNode.childNodes;
735 getListLength: function(){
736 return this.domNode.childNodes.length-2;
739 _onMouseDown: function(/*Event*/ evt){
743 _onMouseUp: function(/*Event*/ evt){
744 if(evt.target === this.domNode){
746 }else if(evt.target==this.previousButton){
748 }else if(evt.target==this.nextButton){
751 var tgt = evt.target;
752 // while the clicked node is inside the div
754 // recurse to the top
755 tgt = tgt.parentNode;
757 this.setValue({ target: tgt }, true);
761 _onMouseOver: function(/*Event*/ evt){
762 if(evt.target === this.domNode){ return; }
763 var tgt = evt.target;
764 if(!(tgt == this.previousButton || tgt == this.nextButton)){
765 // while the clicked node is inside the div
767 // recurse to the top
768 tgt = tgt.parentNode;
771 this._focusOptionNode(tgt);
774 _onMouseOut:function(/*Event*/ evt){
775 if(evt.target === this.domNode){ return; }
776 this._blurOptionNode();
779 _focusOptionNode:function(/*DomNode*/ node){
781 // does the actual highlight
782 if(this._highlighted_option != node){
783 this._blurOptionNode();
784 this._highlighted_option = node;
785 dojo.addClass(this._highlighted_option, "dijitMenuItemHover");
789 _blurOptionNode:function(){
791 // removes highlight on highlighted option
792 if(this._highlighted_option){
793 dojo.removeClass(this._highlighted_option, "dijitMenuItemHover");
794 this._highlighted_option = null;
798 _highlightNextOption:function(){
800 // Highlight the item just below the current selection.
801 // If nothing selected, highlight first option
803 // because each press of a button clears the menu,
804 // the highlighted option sometimes becomes detached from the menu!
805 // test to see if the option has a parent to see if this is the case.
806 var fc = this.domNode.firstChild;
807 if(!this.getHighlightedOption()){
808 this._focusOptionNode(fc.style.display=="none" ? fc.nextSibling : fc);
810 var ns = this._highlighted_option.nextSibling;
811 if(ns && ns.style.display!="none"){
812 this._focusOptionNode(ns);
815 // scrollIntoView is called outside of _focusOptionNode because in IE putting it inside causes the menu to scroll up on mouseover
816 dijit.scrollIntoView(this._highlighted_option);
819 highlightFirstOption:function(){
821 // Highlight the first real item in the list (not Previous Choices).
822 this._focusOptionNode(this.domNode.firstChild.nextSibling);
823 dijit.scrollIntoView(this._highlighted_option);
826 highlightLastOption:function(){
828 // Highlight the last real item in the list (not More Choices).
829 this._focusOptionNode(this.domNode.lastChild.previousSibling);
830 dijit.scrollIntoView(this._highlighted_option);
833 _highlightPrevOption:function(){
835 // Highlight the item just above the current selection.
836 // If nothing selected, highlight last option (if
837 // you select Previous and try to keep scrolling up the list)
838 var lc = this.domNode.lastChild;
839 if(!this.getHighlightedOption()){
840 this._focusOptionNode(lc.style.display == "none" ? lc.previousSibling : lc);
842 var ps = this._highlighted_option.previousSibling;
843 if(ps && ps.style.display != "none"){
844 this._focusOptionNode(ps);
847 dijit.scrollIntoView(this._highlighted_option);
850 _page:function(/*Boolean*/ up){
851 var scrollamount = 0;
852 var oldscroll = this.domNode.scrollTop;
853 var height = dojo.style(this.domNode, "height");
854 // if no item is highlighted, highlight the first option
855 if(!this.getHighlightedOption()){
856 this._highlightNextOption();
858 while(scrollamount<height){
861 if(!this.getHighlightedOption().previousSibling ||
862 this._highlighted_option.previousSibling.style.display == "none"){
865 this._highlightPrevOption();
867 // stop at last option
868 if(!this.getHighlightedOption().nextSibling ||
869 this._highlighted_option.nextSibling.style.display == "none"){
872 this._highlightNextOption();
875 var newscroll=this.domNode.scrollTop;
876 scrollamount+=(newscroll-oldscroll)*(up ? -1:1);
881 pageUp: function(){ this._page(true); },
883 pageDown: function(){ this._page(false); },
885 getHighlightedOption: function(){
887 // Returns the highlighted option.
888 var ho = this._highlighted_option;
889 return (ho && ho.parentNode) ? ho : null;
892 handleKey: function(evt){
894 case dojo.keys.DOWN_ARROW:
895 this._highlightNextOption();
897 case dojo.keys.PAGE_DOWN:
900 case dojo.keys.UP_ARROW:
901 this._highlightPrevOption();
903 case dojo.keys.PAGE_UP:
912 "dijit.form.ComboBox",
913 [dijit.form.ValidationTextBox, dijit.form.ComboBoxMixin],
916 // Auto-completing text box, and base class for dijit.form.FilteringSelect.
919 // The drop down box's values are populated from an class called
920 // a data provider, which returns a list of values based on the characters
921 // that the user has typed into the input box.
923 // Some of the options to the ComboBox are actually arguments to the data
926 // You can assume that all the form widgets (and thus anything that mixes
927 // in dijit.formComboBoxMixin) will inherit from dijit.form._FormWidget and thus the `this`
928 // reference will also "be a" _FormWidget.
930 postMixInProperties: function(){
931 // this.inherited(arguments); // ??
932 dijit.form.ComboBoxMixin.prototype.postMixInProperties.apply(this, arguments);
933 dijit.form.ValidationTextBox.prototype.postMixInProperties.apply(this, arguments);
936 postCreate: function(){
937 dijit.form.ComboBoxMixin.prototype._postCreate.apply(this, arguments);
938 dijit.form.ValidationTextBox.prototype.postCreate.apply(this, arguments);
940 setAttribute: function(/*String*/ attr, /*anything*/ value){
941 dijit.form.ValidationTextBox.prototype.setAttribute.apply(this, arguments);
942 dijit.form.ComboBoxMixin.prototype._setAttribute.apply(this, arguments);
948 dojo.declare("dijit.form._ComboBoxDataStore", null, {
950 // Inefficient but small data store specialized for inlined ComboBox data
953 // Provides a store for inlined data like:
956 // | <option value="AL">Alabama</option>
959 // Actually. just implements the subset of dojo.data.Read/Notification
960 // needed for ComboBox and FilteringSelect to work.
962 // Note that an item is just a pointer to the <option> DomNode.
964 constructor: function( /*DomNode*/ root){
967 // TODO: this was added in #3858 but unclear why/if it's needed; doesn't seem to be.
968 // If it is needed then can we just hide the select itself instead?
969 dojo.query("> option", root).forEach(function(node){
970 node.style.display="none";
975 getValue: function( /* item */ item,
976 /* attribute-name-string */ attribute,
977 /* value? */ defaultValue){
978 return (attribute == "value") ? item.value : (item.innerText || item.textContent || '');
981 isItemLoaded: function(/* anything */ something) {
985 fetch: function(/* Object */ args){
987 // Given a query and set of defined options, such as a start and count of items to return,
988 // this method executes the query and makes the results available as data items.
989 // Refer to dojo.data.api.Read.fetch() more details.
992 // Given a query like
995 // | query: {name: "Cal*"},
998 // | ignoreCase: true,
999 // | onComplete: function(/* item[] */ items, /* Object */ args){...}
1002 // will call `onComplete()` with the results of the query (and the argument to this method)
1004 // convert query to regex (ex: convert "first\last*" to /^first\\last.*$/i) and get matching vals
1005 var query = "^" + args.query.name
1006 .replace(/([\\\|\(\)\[\{\^\$\+\?\.\<\>])/g, "\\$1")
1007 .replace("*", ".*") + "$",
1008 matcher = new RegExp(query, args.queryOptions.ignoreCase ? "i" : ""),
1009 items = dojo.query("> option", this.root).filter(function(option){
1010 return (option.innerText || option.textContent || '').match(matcher);
1013 var start = args.start || 0,
1014 end = ("count" in args && args.count != Infinity) ? (start + args.count) : items.length ;
1015 args.onComplete(items.slice(start, end), args);
1016 return args; // Object
1017 // TODO: I don't need to return the length?
1020 close: function(/*dojo.data.api.Request || args || null */ request){
1024 getLabel: function(/* item */ item){
1025 return item.innerHTML;
1028 getIdentity: function(/* item */ item){
1029 return dojo.attr(item, "value");
1032 fetchItemByIdentity: function(/* Object */ args){
1034 // Given the identity of an item, this method returns the item that has
1035 // that identity through the onItem callback.
1036 // Refer to dojo.data.api.Identity.fetchItemByIdentity() for more details.
1039 // Given arguments like:
1041 // | {identity: "CA", onItem: function(item){...}
1043 // Call `onItem()` with the DOM node `<option value="CA">California</option>`
1044 var item = dojo.query("option[value='" + args.identity + "']", this.root)[0];
1048 fetchSelectedItem: function(){
1050 // Get the option marked as selected, like `<option selected>`.
1051 // Not part of dojo.data API.
1052 var root = this.root,
1053 si = root.selectedIndex;
1054 return dojo.query("> option:nth-child(" +
1055 (si != -1 ? si+1 : 1) + ")",
1056 root)[0]; // dojo.data.Item