]> git.pond.sub.org Git - eow/blob - static/dojo-release-1.1.1/dijit/form/ComboBox.js
add Dojo 1.1.1
[eow] / static / dojo-release-1.1.1 / dijit / form / ComboBox.js
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");
4
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");
7
8 dojo.declare(
9         "dijit.form.ComboBoxMixin",
10         null,
11         {
12                 // item: Object
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.
15                 item: null,
16
17                 // pageSize: Integer
18                 //              Argument to data provider.
19                 //              Specifies number of search results per page (before hitting "next" button)
20                 pageSize: Infinity,
21
22                 // store: Object
23                 //              Reference to data provider object used by this ComboBox
24                 store: null,
25
26                 // query: Object
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.
30                 query: {},
31
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
36                 autoComplete: true,
37
38                 // searchDelay: Integer
39                 //              Delay in milliseconds between when user types something and we start
40                 //              searching based on that value
41                 searchDelay: 100,
42
43                 // searchAttr: String
44                 //              Searches pattern match against this field
45                 searchAttr: "name",
46
47                 // queryExpr: String
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"
52                 queryExpr: "${0}*",
53
54                 // ignoreCase: Boolean
55                 //              Set true if the ComboBox should ignore case when matching possible items
56                 ignoreCase: true,
57
58                 // hasDownArrow: Boolean
59                 //              Set this textbox to have a down arrow button.
60                 //              Defaults to true.
61                 hasDownArrow:true,
62
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\">&thinsp;</div\n\t\t\t><div class=\"dijitArrowButtonChar\">&#9660;</div\n\t\t></div\n\t\t><div class=\"dijitReset dijitValidationIcon\"><br></div\n\t\t><div class=\"dijitReset dijitValidationIconText\">&Chi;</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",
64
65                 baseClass:"dijitComboBox",
66
67                 _getCaretPos: function(/*DomNode*/ element){
68                         // khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22
69                         var pos = 0;
70                         if(typeof(element.selectionStart)=="number"){
71                                 // FIXME: this is totally borked on Moz < 1.3. Any recourse?
72                                 pos = element.selectionStart;
73                         }else if(dojo.isIE){
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);
82                                 try{
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;
88                                 }catch(e){
89                                         // If focus has shifted, 0 is fine for caret pos.
90                                 }
91                         }
92                         return pos;
93                 },
94
95                 _setCaretPos: function(/*DomNode*/ element, /*Number*/ location){
96                         location = parseInt(location);
97                         dijit.selectInputText(element, location, location);
98                 },
99
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);
104                         }
105                 },      
106                 
107                 _onKeyPress: function(/*Event*/ evt){
108                         // summary: handles keyboard events
109
110                         //except for pasting case - ctrl + v(118)
111                         if(evt.altKey || (evt.ctrlKey && evt.charCode != 118)){
112                                 return;
113                         }
114                         var doSearch = false;
115                         var pw = this._popupWidget;
116                         var dk = dojo.keys;
117                         if(this._isShowingNow){
118                                 pw.handleKey(evt);
119                         }
120                         switch(evt.keyCode){
121                                 case dk.PAGE_DOWN:
122                                 case dk.DOWN_ARROW:
123                                         if(!this._isShowingNow||this._prev_key_esc){
124                                                 this._arrowPressed();
125                                                 doSearch=true;
126                                         }else{
127                                                 this._announceOption(pw.getHighlightedOption());
128                                         }
129                                         dojo.stopEvent(evt);
130                                         this._prev_key_backspace = false;
131                                         this._prev_key_esc = false;
132                                         break;
133
134                                 case dk.PAGE_UP:
135                                 case dk.UP_ARROW:
136                                         if(this._isShowingNow){
137                                                 this._announceOption(pw.getHighlightedOption());
138                                         }
139                                         dojo.stopEvent(evt);
140                                         this._prev_key_backspace = false;
141                                         this._prev_key_esc = false;
142                                         break;
143
144                                 case dk.ENTER:
145                                         // prevent submitting form if user presses enter. Also
146                                         // prevent accepting the value if either Next or Previous
147                                         // are selected
148                                         var highlighted;
149                                         if(     this._isShowingNow && 
150                                                 (highlighted = pw.getHighlightedOption())
151                                         ){
152                                                 // only stop event on prev/next
153                                                 if(highlighted == pw.nextButton){
154                                                         this._nextSearch(1);
155                                                         dojo.stopEvent(evt);
156                                                         break;
157                                                 }else if(highlighted == pw.previousButton){
158                                                         this._nextSearch(-1);
159                                                         dojo.stopEvent(evt);
160                                                         break;
161                                                 }
162                                         }else{
163                                                 this.setDisplayedValue(this.getDisplayedValue());
164                                         }
165                                         // default case:
166                                         // prevent submit, but allow event to bubble
167                                         evt.preventDefault();
168                                         // fall through
169
170                                 case dk.TAB:
171                                         var newvalue = this.getDisplayedValue();
172                                         // #4617: 
173                                         //              if the user had More Choices selected fall into the
174                                         //              _onBlur handler
175                                         if(pw && (
176                                                 newvalue == pw._messages["previousMessage"] ||
177                                                 newvalue == pw._messages["nextMessage"])
178                                         ){
179                                                 break;
180                                         }
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);
186                                                 }
187                                                 this._hideResultList();
188                                         }
189                                         break;
190
191                                 case dk.SPACE:
192                                         this._prev_key_backspace = false;
193                                         this._prev_key_esc = false;
194                                         if(this._isShowingNow && pw.getHighlightedOption()){
195                                                 dojo.stopEvent(evt);
196                                                 this._selectOption();
197                                                 this._hideResultList();
198                                         }else{
199                                                 doSearch = true;
200                                         }
201                                         break;
202
203                                 case dk.ESCAPE:
204                                         this._prev_key_backspace = false;
205                                         this._prev_key_esc = true;
206                                         if(this._isShowingNow){
207                                                 dojo.stopEvent(evt);
208                                                 this._hideResultList();
209                                         }
210                                         this.inherited(arguments);
211                                         break;
212
213                                 case dk.DELETE:
214                                 case dk.BACKSPACE:
215                                         this._prev_key_esc = false;
216                                         this._prev_key_backspace = true;
217                                         doSearch = true;
218                                         break;
219
220                                 case dk.RIGHT_ARROW: // fall through
221                                 case dk.LEFT_ARROW: 
222                                         this._prev_key_backspace = false;
223                                         this._prev_key_esc = false;
224                                         break;
225
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){
230                                                 doSearch = true;
231                                         }
232                         }
233                         if(this.searchTimer){
234                                 clearTimeout(this.searchTimer);
235                         }
236                         if(doSearch){
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);
240                         }
241                 },
242
243                 _autoCompleteText: function(/*String*/ text){
244                         // summary:
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.
250
251                         var fn = this.focusNode;
252
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);
266                                 }
267                         }else{
268                                 // text does not autoComplete; replace the whole value and highlight
269                                 fn.value = text;
270                                 dijit.selectInputText(fn);
271                         }
272                 },
273
274                 _openResultList: function(/*Object*/ results, /*Object*/ dataObject){
275                         if(     this.disabled || 
276                                 this.readOnly || 
277                                 (dataObject.query[this.searchAttr] != this._lastQuery)
278                         ){
279                                 return;
280                         }
281                         this._popupWidget.clearResultList();
282                         if(!results.length){
283                                 this._hideResultList();
284                                 return;
285                         }
286
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
291                         // highlighted.
292
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);
301                         }
302                         this._popupWidget.createOptions(
303                                 results, 
304                                 dataObject, 
305                                 dojo.hitch(this, "_getMenuLabelFromItem")
306                         );
307
308                         // show our list (only if we have content, else nothing)
309                         this._showResultList();
310
311                         // #4091:
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();
319                                 }
320                                 this._announceOption(this._popupWidget.getHighlightedOption());
321                         }
322                 },
323
324                 _showResultList: function(){
325                         this._hideResultList();
326                         var items = this._popupWidget.getItems(),
327                                 visibleCount = Math.min(items.length,this.maxListLength);
328                         this._arrowPressed();
329                         // hide the tooltip
330                         this.displayMessage("");
331                         
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
336
337                         // TODO: want to redo this, see 
338                         //              http://trac.dojotoolkit.org/ticket/3272
339                         //      and
340                         //              http://trac.dojotoolkit.org/ticket/4108
341
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) 
346                                 width = "";
347                                 height = "";
348                         }
349                         var best = this.open();
350                         // #3212:
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";
357                         // #4134:
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){
362                                 newwidth += 16;
363                         }
364                         dojo.marginBox(this._popupWidget.domNode, {
365                                 h: best.h,
366                                 w: Math.max(newwidth, this.domNode.offsetWidth)
367                         });
368                         dijit.setWaiState(this.comboNode, "expanded", "true");
369                 },
370
371                 _hideResultList: function(){
372                         if(this._isShowingNow){
373                                 dijit.popup.close(this._popupWidget);
374                                 this._arrowIdle();
375                                 this._isShowingNow=false;
376                                 dijit.setWaiState(this.comboNode, "expanded", "false");
377                                 dijit.removeWaiState(this.focusNode,"activedescendant");
378                         }
379                 },
380
381                 _setBlurValue: function(){
382                         // if the user clicks away from the textbox OR tabs away, set the
383                         // value to the textbox value
384                         // #4617: 
385                         //              if value is now more choices or previous choices, revert
386                         //              the value
387                         var newvalue=this.getDisplayedValue();
388                         var pw = this._popupWidget;
389                         if(pw && (
390                                 newvalue == pw._messages["previousMessage"] ||
391                                 newvalue == pw._messages["nextMessage"]
392                                 )
393                         ){
394                                 this.setValue(this._lastValueReported, true);
395                         }else{
396                                 this.setDisplayedValue(newvalue);
397                         }
398                 },
399
400                 _onBlur: function(){
401                         // summary: called magically when focus has shifted away from this widget and it's dropdown
402                         this._hideResultList();
403                         this._arrowIdle();
404                         this.inherited(arguments);
405                 },
406
407                 _announceOption: function(/*Node*/ node){
408                         // summary:
409                         //              a11y code that puts the highlighted option in the textbox
410                         //              This way screen readers will know what is happening in the
411                         //              menu
412
413                         if(node == null){
414                                 return;
415                         }
416                         // pull the text value from the item attached to the DOM node
417                         var newValue;
418                         if( node == this._popupWidget.nextButton ||
419                                 node == this._popupWidget.previousButton){
420                                 newValue = node.innerHTML;
421                         }else{
422                                 newValue = this.store.getValue(node.item, this.searchAttr);
423                         }
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);
430                 },
431
432                 _selectOption: function(/*Event*/ evt){
433                         var tgt = null;
434                         if(!evt){
435                                 evt ={ target: this._popupWidget.getHighlightedOption()};
436                         }
437                                 // what if nothing is highlighted yet?
438                         if(!evt.target){
439                                 // handle autocompletion where the the user has hit ENTER or TAB
440                                 this.setDisplayedValue(this.getDisplayedValue());
441                                 return;
442                         // otherwise the user has accepted the autocompleted value
443                         }else{
444                                 tgt = evt.target;
445                         }
446                         if(!evt.noHide){
447                                 this._hideResultList();
448                                 this._setCaretPos(this.focusNode, this.store.getValue(tgt.item, this.searchAttr).length);
449                         }
450                         this._doSelect(tgt);
451                 },
452
453                 _doSelect: function(tgt){
454                         this.item = tgt.item;
455                         this.setValue(this.store.getValue(tgt.item, this.searchAttr), true);
456                 },
457
458                 _onArrowMouseDown: function(evt){
459                         // summary: callback when arrow is clicked
460                         if(this.disabled || this.readOnly){
461                                 return;
462                         }
463                         dojo.stopEvent(evt);
464                         this.focus();
465                         if(this._isShowingNow){
466                                 this._hideResultList();
467                         }else{
468                                 // forces full population of results, if they click
469                                 // on the arrow it means they want to see more options
470                                 this._startSearch("");
471                         }
472                 },
473
474                 _startSearchFromInput: function(){
475                         this._startSearch(this.focusNode.value);
476                 },
477
478                 _getQueryString: function(/*String*/ text){
479                         return dojo.string.substitute(this.queryExpr, [text]);
480                 },
481
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),
487                                         id:popupId
488                                 });
489                                 dijit.removeWaiState(this.focusNode,"activedescendant");
490                                 dijit.setWaiState(this.textbox,"owns",popupId); // associate popup with textbox
491                         }
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({
502                                         queryOptions: {
503                                                 ignoreCase: this.ignoreCase, 
504                                                 deep: true
505                                         },
506                                         query: query,
507                                         onComplete: dojo.hitch(this, "_openResultList"), 
508                                         onError: function(errText){
509                                                 console.error('dijit.form.ComboBox: ' + errText);
510                                                 dojo.hitch(_this, "_hideResultList")();
511                                         },
512                                         start:0,
513                                         count:this.pageSize
514                                 });
515
516                                 var nextSearch = function(dataObject, direction){
517                                         dataObject.start += dataObject.count*direction;
518                                         // #4091:
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);
523                                 }
524                                 this._nextSearch = this._popupWidget.onPage = dojo.hitch(this, nextSearch, dataObject);
525                         }, query, this), this.searchDelay);
526                 },
527
528                 _getValueField:function(){
529                         return this.searchAttr;
530                 },
531
532                 /////////////// Event handlers /////////////////////
533
534                 _arrowPressed: function(){
535                         if(!this.disabled && !this.readOnly && this.hasDownArrow){
536                                 dojo.addClass(this.downArrowNode, "dijitArrowButtonActive");
537                         }
538                 },
539
540                 _arrowIdle: function(){
541                         if(!this.disabled && !this.readOnly && this.hasDownArrow){
542                                 dojo.removeClass(this.downArrowNode, "dojoArrowButtonPushed");
543                         }
544                 },
545
546                 // FIXME: 
547                 //              this is public so we can't remove until 2.0, but the name
548                 //              SHOULD be "compositionEnd"
549
550                 compositionend: function(/*Event*/ evt){
551                         //      summary:
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
555                         //              (not in IE)
556                         this.onkeypress({charCode:-1});
557                 },
558
559                 //////////// INITIALIZATION METHODS ///////////////////////////////////////
560
561                 constructor: function(){
562                         this.query={};
563                 },
564
565                 postMixInProperties: function(){
566                         if(!this.hasDownArrow){
567                                 this.baseClass = "dijitTextBox";
568                         }
569                         if(!this.store){
570                                 var srcNodeRef = this.srcNodeRef;
571
572                                 // if user didn't specify store, then assume there are option tags
573                                 this.store = new dijit.form._ComboBoxDataStore(srcNodeRef);
574
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
577                                 // Select
578
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
583                                 if(     !this.value || (
584                                                 (typeof srcNodeRef.selectedIndex == "number") && 
585                                                 srcNodeRef.selectedIndex.toString() === this.value)
586                                 ){
587                                         var item = this.store.fetchSelectedItem();
588                                         if(item){
589                                                 this.value = this.store.getValue(item, this._getValueField());
590                                         }
591                                 }
592                         }
593                 },
594                 
595                 _postCreate:function(){
596                         //find any associated label element and add to combobox node.
597                         var label=dojo.query('label[for="'+this.id+'"]');
598                         if(label.length){
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);
603                                 
604                         }
605                 },
606
607                 uninitialize:function(){
608                         if(this._popupWidget){
609                                 this._hideResultList();
610                                 this._popupWidget.destroy()
611                         }
612                 },
613
614                 _getMenuLabelFromItem:function(/*Item*/ item){
615                         return {
616                                 html: false, 
617                                 label: this.store.getValue(item, this.searchAttr)
618                         };
619                 },
620
621                 open:function(){
622                         this._isShowingNow=true;
623                         return dijit.popup.open({
624                                 popup: this._popupWidget,
625                                 around: this.domNode,
626                                 parent: this
627                         });
628                 },
629                 
630                 reset:function(){
631                         //      summary:
632                         //              Additionally reset the .item (to clean up).
633                         this.item = null;
634                         this.inherited(arguments);
635                 }
636                 
637         }
638 );
639
640 dojo.declare(
641         "dijit.form._ComboBoxMenu",
642         [dijit._Widget, dijit._Templated],
643
644         {
645                 //      summary:
646                 //              Focus-less div based menu for internal use in ComboBox
647
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>"
651                         +"</ul>",
652                 _messages: null,
653
654                 postMixInProperties: function(){
655                         this._messages = dojo.i18n.getLocalization("dijit.form", "ComboBox", this.lang);
656                         this.inherited("postMixInProperties", arguments);
657                 },
658
659                 setValue: function(/*Object*/ value){
660                         this.value = value;
661                         this.onChange(value);
662                 },
663
664                 // stubs
665                 onChange: function(/*Object*/ value){},
666                 onPage: function(/*Number*/ direction){},
667
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);
673                 },
674
675                 onClose:function(){
676                         this._blurOptionNode();
677                 },
678
679                 _createOption:function(/*Object*/ item, labelFunc){
680                         //      summary: 
681                         //              creates an option to appear on the popup menu subclassed by
682                         //              FilteringSelect
683
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;
689                         }else{
690                                 menuitem.appendChild(
691                                         dojo.doc.createTextNode(labelObject.label)
692                                 );
693                         }
694                         // #3250: in blank options, assign a normal height
695                         if(menuitem.innerHTML == ""){
696                                 menuitem.innerHTML = "&nbsp;";
697                         }
698                         menuitem.item=item;
699                         return menuitem;
700                 },
701
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
710                         // #2309:
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);
717                         }, this);
718                         // display "Next . . ." button
719                         this.nextButton.style.display = (dataObject.count == results.length) ? "" : "none";
720                         dojo.attr(this.nextButton,"id", this.id + "_next")
721                 },
722
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]);
727                         }
728                 },
729
730                 // these functions are called in showResultList
731                 getItems: function(){
732                         return this.domNode.childNodes;
733                 },
734
735                 getListLength: function(){
736                         return this.domNode.childNodes.length-2;
737                 },
738
739                 _onMouseDown: function(/*Event*/ evt){
740                         dojo.stopEvent(evt);
741                 },
742
743                 _onMouseUp: function(/*Event*/ evt){
744                         if(evt.target === this.domNode){
745                                 return;
746                         }else if(evt.target==this.previousButton){
747                                 this.onPage(-1);
748                         }else if(evt.target==this.nextButton){
749                                 this.onPage(1);
750                         }else{
751                                 var tgt = evt.target;
752                                 // while the clicked node is inside the div
753                                 while(!tgt.item){
754                                         // recurse to the top
755                                         tgt = tgt.parentNode;
756                                 }
757                                 this.setValue({ target: tgt }, true);
758                         }
759                 },
760
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
766                                 while(!tgt.item){
767                                         // recurse to the top
768                                         tgt = tgt.parentNode;
769                                 }
770                         }
771                         this._focusOptionNode(tgt);
772                 },
773
774                 _onMouseOut:function(/*Event*/ evt){
775                         if(evt.target === this.domNode){ return; }
776                         this._blurOptionNode();
777                 },
778
779                 _focusOptionNode:function(/*DomNode*/ node){
780                         // summary:
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");
786                         }
787                 },
788
789                 _blurOptionNode:function(){
790                         // summary:
791                         //      removes highlight on highlighted option
792                         if(this._highlighted_option){
793                                 dojo.removeClass(this._highlighted_option, "dijitMenuItemHover");
794                                 this._highlighted_option = null;
795                         }
796                 },
797
798                 _highlightNextOption:function(){
799                         //      summary:
800                         //              Highlight the item just below the current selection.
801                         //              If nothing selected, highlight first option
802
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);
809                         }else{
810                                 var ns = this._highlighted_option.nextSibling;
811                                 if(ns && ns.style.display!="none"){
812                                         this._focusOptionNode(ns);
813                                 }
814                         }
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);
817                 },
818
819                 highlightFirstOption:function(){
820                         //      summary:
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);
824                 },
825
826                 highlightLastOption:function(){
827                         //      summary:
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);
831                 },
832
833                 _highlightPrevOption:function(){
834                         //      summary:
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);
841                         }else{
842                                 var ps = this._highlighted_option.previousSibling;
843                                 if(ps && ps.style.display != "none"){
844                                         this._focusOptionNode(ps);
845                                 }
846                         }
847                         dijit.scrollIntoView(this._highlighted_option);
848                 },
849
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();
857                         }
858                         while(scrollamount<height){
859                                 if(up){
860                                         // stop at option 1
861                                         if(!this.getHighlightedOption().previousSibling ||
862                                                 this._highlighted_option.previousSibling.style.display == "none"){
863                                                 break;
864                                         }
865                                         this._highlightPrevOption();
866                                 }else{
867                                         // stop at last option
868                                         if(!this.getHighlightedOption().nextSibling ||
869                                                 this._highlighted_option.nextSibling.style.display == "none"){
870                                                 break;
871                                         }
872                                         this._highlightNextOption();
873                                 }
874                                 // going backwards
875                                 var newscroll=this.domNode.scrollTop;
876                                 scrollamount+=(newscroll-oldscroll)*(up ? -1:1);
877                                 oldscroll=newscroll;
878                         }
879                 },
880
881                 pageUp: function(){ this._page(true); },
882
883                 pageDown: function(){ this._page(false); },
884
885                 getHighlightedOption: function(){
886                         //      summary:
887                         //              Returns the highlighted option.
888                         var ho = this._highlighted_option;
889                         return (ho && ho.parentNode) ? ho : null;
890                 },
891
892                 handleKey: function(evt){
893                         switch(evt.keyCode){
894                                 case dojo.keys.DOWN_ARROW:
895                                         this._highlightNextOption();
896                                         break;
897                                 case dojo.keys.PAGE_DOWN:
898                                         this.pageDown();
899                                         break;  
900                                 case dojo.keys.UP_ARROW:
901                                         this._highlightPrevOption();
902                                         break;
903                                 case dojo.keys.PAGE_UP:
904                                         this.pageUp();
905                                         break;  
906                         }
907                 }
908         }
909 );
910
911 dojo.declare(
912         "dijit.form.ComboBox",
913         [dijit.form.ValidationTextBox, dijit.form.ComboBoxMixin],
914         {
915                 //      summary:
916                 //              Auto-completing text box, and base class for dijit.form.FilteringSelect.
917                 // 
918                 //      description:
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.
922                 // 
923                 //              Some of the options to the ComboBox are actually arguments to the data
924                 //              provider.
925                 // 
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.
929
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);
934                 },
935
936                 postCreate: function(){
937                         dijit.form.ComboBoxMixin.prototype._postCreate.apply(this, arguments);
938                         dijit.form.ValidationTextBox.prototype.postCreate.apply(this, arguments);
939                 },
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);
943                 }
944                 
945         }
946 );
947
948 dojo.declare("dijit.form._ComboBoxDataStore", null, {
949         //      summary:
950         //              Inefficient but small data store specialized for inlined ComboBox data
951         //
952         //      description:
953         //              Provides a store for inlined data like:
954         //
955         //      |       <select>
956         //      |               <option value="AL">Alabama</option>
957         //      |               ...
958         //
959         //              Actually. just implements the subset of dojo.data.Read/Notification
960         //              needed for ComboBox and FilteringSelect to work.
961         //
962         //              Note that an item is just a pointer to the <option> DomNode.
963
964         constructor: function( /*DomNode*/ root){
965                 this.root = root;
966 /*
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";
971                 });
972 */
973         },
974
975         getValue: function(     /* item */ item, 
976                                                 /* attribute-name-string */ attribute, 
977                                                 /* value? */ defaultValue){
978                 return (attribute == "value") ? item.value : (item.innerText || item.textContent || '');
979         },
980
981         isItemLoaded: function(/* anything */ something) {
982                 return true;
983         },
984
985         fetch: function(/* Object */ args){
986                 //      summary:
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.
990                 //
991                 //      description:
992                 //              Given a query like
993                 //
994                 //      |       {
995                 //      |               query: {name: "Cal*"},
996                 //      |               start: 30,
997                 //      |               count: 20,
998                 //      |               ignoreCase: true,
999                 //      |               onComplete: function(/* item[] */ items, /* Object */ args){...}
1000                 //      |       }
1001                 //
1002                 //              will call `onComplete()` with the results of the query (and the argument to this method)
1003
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);
1011                         } );
1012
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?
1018         },
1019
1020         close: function(/*dojo.data.api.Request || args || null */ request){
1021                 return;
1022         },
1023
1024         getLabel: function(/* item */ item){
1025                 return item.innerHTML;
1026         },
1027
1028         getIdentity: function(/* item */ item){
1029                 return dojo.attr(item, "value");
1030         },
1031
1032         fetchItemByIdentity: function(/* Object */ args){
1033                 //      summary:
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.
1037                 //
1038                 //      description:
1039                 //              Given arguments like:
1040                 //
1041                 //      |               {identity: "CA", onItem: function(item){...}
1042                 //
1043                 //              Call `onItem()` with the DOM node `<option value="CA">California</option>`
1044                 var item = dojo.query("option[value='" + args.identity + "']", this.root)[0];
1045                 args.onItem(item);
1046         },
1047         
1048         fetchSelectedItem: function(){
1049                 //      summary:
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
1057         }
1058 });
1059
1060 }