Subversion Repositories Applications.papyrus

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2150 mathias 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("dojo.data.ItemFileReadStore");
6
dojo.require("dijit.form.ValidationTextBox");
7
dojo.requireLocalization("dijit.form", "ComboBox", null, "ko,zh,ja,zh-tw,ru,it,hu,ROOT,fr,pt,pl,es,de,cs");
8
 
9
dojo.declare(
10
	"dijit.form.ComboBoxMixin",
11
	null,
12
	{
13
		// summary:
14
		//		Auto-completing text box, and base class for FilteringSelect widget.
15
		//
16
		//		The drop down box's values are populated from an class called
17
		//		a data provider, which returns a list of values based on the characters
18
		//		that the user has typed into the input box.
19
		//
20
		//		Some of the options to the ComboBox are actually arguments to the data
21
		//		provider.
22
		//
23
		//		You can assume that all the form widgets (and thus anything that mixes
24
		//		in ComboBoxMixin) will inherit from _FormWidget and thus the "this"
25
		//		reference will also "be a" _FormWidget.
26
 
27
		// item: Object
28
		//		This is the item returned by the dojo.data.store implementation that
29
		//		provides the data for this cobobox, it's the currently selected item.
30
		item: null,
31
 
32
		// pageSize: Integer
33
		//		Argument to data provider.
34
		//		Specifies number of search results per page (before hitting "next" button)
35
		pageSize: Infinity,
36
 
37
		// store: Object
38
		//		Reference to data provider object used by this ComboBox
39
		store: null,
40
 
41
		// query: Object
42
		//		A query that can be passed to 'store' to initially filter the items,
43
		//		before doing further filtering based on searchAttr and the key.
44
		query: {},
45
 
46
		// autoComplete: Boolean
47
		//		If you type in a partial string, and then tab out of the <input> box,
48
		//		automatically copy the first entry displayed in the drop down list to
49
		//		the <input> field
50
		autoComplete: true,
51
 
52
		// searchDelay: Integer
53
		//		Delay in milliseconds between when user types something and we start
54
		//		searching based on that value
55
		searchDelay: 100,
56
 
57
		// searchAttr: String
58
		//		Searches pattern match against this field
59
		searchAttr: "name",
60
 
61
		// ignoreCase: Boolean
62
		//		Set true if the ComboBox should ignore case when matching possible items
63
		ignoreCase: true,
64
 
65
		// hasDownArrow: Boolean
66
		//		Set this textbox to have a down arrow button.
67
		//		Defaults to true.
68
		hasDownArrow:true,
69
 
70
		// _hasFocus: Boolean
71
		//		Represents focus state of the textbox
72
		// TODO: get rid of this; it's unnecessary (but currently referenced in FilteringSelect)
73
		_hasFocus:false,
74
 
75
		templateString:"<table class=\"dijit dijitReset dijitInlineTable dijitLeft\" cellspacing=\"0\" cellpadding=\"0\"\n\tid=\"widget_${id}\" name=\"${name}\" dojoAttachEvent=\"onmouseenter:_onMouse,onmouseleave:_onMouse\" waiRole=\"presentation\"\n\t><tr class=\"dijitReset\"\n\t\t><td class='dijitReset dijitStretch dijitInputField' width=\"100%\"\n\t\t\t><input type=\"text\" autocomplete=\"off\" name=\"${name}\"\n\t\t\tdojoAttachEvent=\"onkeypress, onkeyup, onfocus, compositionend\"\n\t\t\tdojoAttachPoint=\"textbox,focusNode\" waiRole=\"combobox\"\n\t\t/></td\n\t\t><td class=\"dijitReset dijitValidationIconField\" width=\"0%\"\n\t\t\t><div dojoAttachPoint='iconNode' class='dijitValidationIcon'></div\n\t\t\t><div class='dijitValidationIconText'>&Chi;</div\n\t\t></td\n\t\t><td class='dijitReset dijitRight dijitButtonNode dijitDownArrowButton' width=\"0%\"\n\t\t\tdojoAttachPoint=\"downArrowNode\"\n\t\t\tdojoAttachEvent=\"onmousedown:_onArrowMouseDown,onmouseup:_onMouse,onmouseenter:_onMouse,onmouseleave:_onMouse\"\n\t\t\t><div class=\"dijitDownArrowButtonInner\" waiRole=\"presentation\"\n\t\t\t\t><div class=\"dijitDownArrowButtonChar\">&#9660;</div\n\t\t\t></div\n\t\t></td\t\n\t></tr\n></table>\n",
76
 
77
		baseClass:"dijitComboBox",
78
 
79
		_lastDisplayedValue: "",
80
 
81
		getValue:function(){
82
			// don't get the textbox value but rather the previously set hidden value
83
			return dijit.form.TextBox.superclass.getValue.apply(this, arguments);
84
		},
85
 
86
		setDisplayedValue:function(/*String*/ value){
87
			this._lastDisplayedValue = value;
88
			this.setValue(value, true);
89
		},
90
 
91
		_getCaretPos: function(/*DomNode*/ element){
92
			// khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22
93
			if(typeof(element.selectionStart)=="number"){
94
				// FIXME: this is totally borked on Moz < 1.3. Any recourse?
95
				return element.selectionStart;
96
			}else if(dojo.isIE){
97
				// in the case of a mouse click in a popup being handled,
98
				// then the document.selection is not the textarea, but the popup
99
				// var r = document.selection.createRange();
100
				// hack to get IE 6 to play nice. What a POS browser.
101
				var tr = document.selection.createRange().duplicate();
102
				var ntr = element.createTextRange();
103
				tr.move("character",0);
104
				ntr.move("character",0);
105
				try{
106
					// If control doesnt have focus, you get an exception.
107
					// Seems to happen on reverse-tab, but can also happen on tab (seems to be a race condition - only happens sometimes).
108
					// There appears to be no workaround for this - googled for quite a while.
109
					ntr.setEndPoint("EndToEnd", tr);
110
					return String(ntr.text).replace(/\r/g,"").length;
111
				}catch(e){
112
					return 0; // If focus has shifted, 0 is fine for caret pos.
113
				}
114
			}
115
		},
116
 
117
		_setCaretPos: function(/*DomNode*/ element, /*Number*/ location){
118
			location = parseInt(location);
119
			this._setSelectedRange(element, location, location);
120
		},
121
 
122
		_setSelectedRange: function(/*DomNode*/ element, /*Number*/ start, /*Number*/ end){
123
			if(!end){
124
				end = element.value.length;
125
			}  // NOTE: Strange - should be able to put caret at start of text?
126
			// Mozilla
127
			// parts borrowed from http://www.faqts.com/knowledge_base/view.phtml/aid/13562/fid/130
128
			if(element.setSelectionRange){
129
				dijit.focus(element);
130
				element.setSelectionRange(start, end);
131
			}else if(element.createTextRange){ // IE
132
				var range = element.createTextRange();
133
				with(range){
134
					collapse(true);
135
					moveEnd('character', end);
136
					moveStart('character', start);
137
					select();
138
				}
139
			}else{ //otherwise try the event-creation hack (our own invention)
140
				// do we need these?
141
				element.value = element.value;
142
				element.blur();
143
				dijit.focus(element);
144
				// figure out how far back to go
145
				var dist = parseInt(element.value.length)-end;
146
				var tchar = String.fromCharCode(37);
147
				var tcc = tchar.charCodeAt(0);
148
				for(var x = 0; x < dist; x++){
149
					var te = document.createEvent("KeyEvents");
150
					te.initKeyEvent("keypress", true, true, null, false, false, false, false, tcc, tcc);
151
					element.dispatchEvent(te);
152
				}
153
			}
154
		},
155
 
156
		onkeypress: function(/*Event*/ evt){
157
			// summary: handles keyboard events
158
 
159
			//except for pasting case - ctrl + v(118)
160
			if(evt.altKey || (evt.ctrlKey && evt.charCode != 118)){
161
				return;
162
			}
163
			var doSearch = false;
164
			this.item = null; // #4872
165
			if(this._isShowingNow){this._popupWidget.handleKey(evt);}
166
			switch(evt.keyCode){
167
				case dojo.keys.PAGE_DOWN:
168
				case dojo.keys.DOWN_ARROW:
169
					if(!this._isShowingNow||this._prev_key_esc){
170
						this._arrowPressed();
171
						doSearch=true;
172
					}else{
173
						this._announceOption(this._popupWidget.getHighlightedOption());
174
					}
175
					dojo.stopEvent(evt);
176
					this._prev_key_backspace = false;
177
					this._prev_key_esc = false;
178
					break;
179
 
180
				case dojo.keys.PAGE_UP:
181
				case dojo.keys.UP_ARROW:
182
					if(this._isShowingNow){
183
						this._announceOption(this._popupWidget.getHighlightedOption());
184
					}
185
					dojo.stopEvent(evt);
186
					this._prev_key_backspace = false;
187
					this._prev_key_esc = false;
188
					break;
189
 
190
				case dojo.keys.ENTER:
191
					// prevent submitting form if user presses enter
192
					// also prevent accepting the value if either Next or Previous are selected
193
					var highlighted;
194
					if(this._isShowingNow&&(highlighted=this._popupWidget.getHighlightedOption())){
195
						// only stop event on prev/next
196
						if(highlighted==this._popupWidget.nextButton){
197
							this._nextSearch(1);
198
							dojo.stopEvent(evt);
199
							break;
200
						}
201
						else if(highlighted==this._popupWidget.previousButton){
202
							this._nextSearch(-1);
203
							dojo.stopEvent(evt);
204
							break;
205
						}
206
					}else{
207
						this.setDisplayedValue(this.getDisplayedValue());
208
					}
209
					// default case:
210
					// prevent submit, but allow event to bubble
211
					evt.preventDefault();
212
					// fall through
213
 
214
				case dojo.keys.TAB:
215
					var newvalue=this.getDisplayedValue();
216
					// #4617: if the user had More Choices selected fall into the _onBlur handler
217
					if(this._popupWidget &&
218
						(newvalue == this._popupWidget._messages["previousMessage"] ||
219
							newvalue == this._popupWidget._messages["nextMessage"])){
220
						break;
221
					}
222
					if(this._isShowingNow){
223
						this._prev_key_backspace = false;
224
						this._prev_key_esc = false;
225
						if(this._popupWidget.getHighlightedOption()){
226
							this._popupWidget.setValue({target:this._popupWidget.getHighlightedOption()}, true);
227
						}
228
						this._hideResultList();
229
					}
230
					break;
231
 
232
				case dojo.keys.SPACE:
233
					this._prev_key_backspace = false;
234
					this._prev_key_esc = false;
235
					if(this._isShowingNow && this._popupWidget.getHighlightedOption()){
236
						dojo.stopEvent(evt);
237
						this._selectOption();
238
						this._hideResultList();
239
					}else{
240
						doSearch = true;
241
					}
242
					break;
243
 
244
				case dojo.keys.ESCAPE:
245
					this._prev_key_backspace = false;
246
					this._prev_key_esc = true;
247
					this._hideResultList();
248
					if(this._lastDisplayedValue != this.getDisplayedValue()){
249
						this.setDisplayedValue(this._lastDisplayedValue);
250
						dojo.stopEvent(evt);
251
					}else{
252
						this.setValue(this.getValue(), false);
253
					}
254
					break;
255
 
256
				case dojo.keys.DELETE:
257
				case dojo.keys.BACKSPACE:
258
					this._prev_key_esc = false;
259
					this._prev_key_backspace = true;
260
					doSearch = true;
261
					break;
262
 
263
				case dojo.keys.RIGHT_ARROW: // fall through
264
 
265
				case dojo.keys.LEFT_ARROW: // fall through
266
					this._prev_key_backspace = false;
267
					this._prev_key_esc = false;
268
					break;
269
 
270
				default:// non char keys (F1-F12 etc..)  shouldn't open list
271
					this._prev_key_backspace = false;
272
					this._prev_key_esc = false;
273
					if(dojo.isIE || evt.charCode != 0){
274
						doSearch=true;
275
					}
276
			}
277
			if(this.searchTimer){
278
				clearTimeout(this.searchTimer);
279
			}
280
			if(doSearch){
281
				// need to wait a tad before start search so that the event bubbles through DOM and we have value visible
282
				this.searchTimer = setTimeout(dojo.hitch(this, this._startSearchFromInput), this.searchDelay);
283
			}
284
		},
285
 
286
		_autoCompleteText: function(/*String*/ text){
287
			// summary:
288
			// Fill in the textbox with the first item from the drop down list, and
289
			// highlight the characters that were auto-completed.   For example, if user
290
			// typed "CA" and the drop down list appeared, the textbox would be changed to
291
			// "California" and "ifornia" would be highlighted.
292
 
293
			// IE7: clear selection so next highlight works all the time
294
			this._setSelectedRange(this.focusNode, this.focusNode.value.length, this.focusNode.value.length);
295
			// does text autoComplete the value in the textbox?
296
			// #3744: escape regexp so the user's input isn't treated as a regular expression.
297
			// Example: If the user typed "(" then the regexp would throw "unterminated parenthetical."
298
			// Also see #2558 for the autocompletion bug this regular expression fixes.
299
			if(new RegExp("^"+escape(this.focusNode.value), this.ignoreCase ? "i" : "").test(escape(text))){
300
				var cpos = this._getCaretPos(this.focusNode);
301
				// only try to extend if we added the last character at the end of the input
302
				if((cpos+1) > this.focusNode.value.length){
303
					// only add to input node as we would overwrite Capitalisation of chars
304
					// actually, that is ok
305
					this.focusNode.value = text;//.substr(cpos);
306
					// visually highlight the autocompleted characters
307
					this._setSelectedRange(this.focusNode, cpos, this.focusNode.value.length);
308
					dijit.setWaiState(this.focusNode, "valuenow", text);
309
				}
310
			}else{
311
				// text does not autoComplete; replace the whole value and highlight
312
				this.focusNode.value = text;
313
				this._setSelectedRange(this.focusNode, 0, this.focusNode.value.length);
314
				dijit.setWaiState(this.focusNode, "valuenow", text);
315
			}
316
		},
317
 
318
		_openResultList: function(/*Object*/ results, /*Object*/ dataObject){
319
			if(this.disabled || dataObject.query[this.searchAttr] != this._lastQuery){
320
				return;
321
			}
322
			this._popupWidget.clearResultList();
323
			if(!results.length){
324
				this._hideResultList();
325
				return;
326
			}
327
 
328
			// Fill in the textbox with the first item from the drop down list, and
329
			// highlight the characters that were auto-completed.   For example, if user
330
			// typed "CA" and the drop down list appeared, the textbox would be changed to
331
			// "California" and "ifornia" would be highlighted.
332
 
333
			var zerothvalue=new String(this.store.getValue(results[0], this.searchAttr));
334
			if(zerothvalue && this.autoComplete && !this._prev_key_backspace &&
335
			// when the user clicks the arrow button to show the full list,
336
			// startSearch looks for "*".
337
			// it does not make sense to autocomplete
338
			// if they are just previewing the options available.
339
				(dataObject.query[this.searchAttr] != "*")){
340
				this._autoCompleteText(zerothvalue);
341
				// announce the autocompleted value
342
				dijit.setWaiState(this.focusNode || this.domNode, "valuenow", zerothvalue);
343
			}
344
			this._popupWidget.createOptions(results, dataObject, dojo.hitch(this, this._getMenuLabelFromItem));
345
 
346
			// show our list (only if we have content, else nothing)
347
			this._showResultList();
348
 
349
			// #4091: tell the screen reader that the paging callback finished by shouting the next choice
350
			if(dataObject.direction){
351
				if(dataObject.direction==1){
352
					this._popupWidget.highlightFirstOption();
353
				}else if(dataObject.direction==-1){
354
					this._popupWidget.highlightLastOption();
355
				}
356
				this._announceOption(this._popupWidget.getHighlightedOption());
357
			}
358
		},
359
 
360
		_showResultList: function(){
361
			this._hideResultList();
362
			var items = this._popupWidget.getItems(),
363
				visibleCount = Math.min(items.length,this.maxListLength);
364
			this._arrowPressed();
365
			// hide the tooltip
366
			this._displayMessage("");
367
 
368
			// Position the list and if it's too big to fit on the screen then
369
			// size it to the maximum possible height
370
			// Our dear friend IE doesnt take max-height so we need to calculate that on our own every time
371
			// TODO: want to redo this, see http://trac.dojotoolkit.org/ticket/3272, http://trac.dojotoolkit.org/ticket/4108
372
			with(this._popupWidget.domNode.style){
373
				// natural size of the list has changed, so erase old width/height settings,
374
				// which were hardcoded in a previous call to this function (via dojo.marginBox() call)
375
				width="";
376
				height="";
377
			}
378
			var best=this.open();
379
			// #3212: only set auto scroll bars if necessary
380
			// prevents issues with scroll bars appearing when they shouldn't when node is made wider (fractional pixels cause this)
381
			var popupbox=dojo.marginBox(this._popupWidget.domNode);
382
			this._popupWidget.domNode.style.overflow=((best.h==popupbox.h)&&(best.w==popupbox.w))?"hidden":"auto";
383
			// #4134: borrow TextArea scrollbar test so content isn't covered by scrollbar and horizontal scrollbar doesn't appear
384
			var newwidth=best.w;
385
			if(best.h<this._popupWidget.domNode.scrollHeight){newwidth+=16;}
386
			dojo.marginBox(this._popupWidget.domNode, {h:best.h,w:Math.max(newwidth,this.domNode.offsetWidth)});
387
		},
388
 
389
		_hideResultList: function(){
390
			if(this._isShowingNow){
391
				dijit.popup.close(this._popupWidget);
392
				this._arrowIdle();
393
				this._isShowingNow=false;
394
			}
395
		},
396
 
397
		_onBlur: function(){
398
			// summary: called magically when focus has shifted away from this widget and it's dropdown
399
			this._hasFocus=false;
400
			this._hasBeenBlurred = true;
401
			this._hideResultList();
402
			this._arrowIdle();
403
			// if the user clicks away from the textbox OR tabs away, set the value to the textbox value
404
			// #4617: if value is now more choices or previous choices, revert the value
405
			var newvalue=this.getDisplayedValue();
406
			if(this._popupWidget&&(newvalue==this._popupWidget._messages["previousMessage"]||newvalue==this._popupWidget._messages["nextMessage"])){
407
				this.setValue(this._lastValueReported, true);
408
			}else{
409
				this.setDisplayedValue(newvalue);
410
			}
411
		},
412
 
413
		onfocus:function(/*Event*/ evt){
414
			this._hasFocus=true;
415
 
416
			// update styling to reflect that we are focused
417
			this._onMouse(evt);
418
		},
419
 
420
		_announceOption: function(/*Node*/ node){
421
			// summary:
422
			//	a11y code that puts the highlighted option in the textbox
423
			//	This way screen readers will know what is happening in the menu
424
 
425
			if(node==null){return;}
426
			// pull the text value from the item attached to the DOM node
427
			var newValue;
428
			if(node==this._popupWidget.nextButton||node==this._popupWidget.previousButton){
429
				newValue=node.innerHTML;
430
			}else{
431
				newValue=this.store.getValue(node.item, this.searchAttr);
432
			}
433
			// get the text that the user manually entered (cut off autocompleted text)
434
			this.focusNode.value=this.focusNode.value.substring(0, this._getCaretPos(this.focusNode));
435
			// autocomplete the rest of the option to announce change
436
			this._autoCompleteText(newValue);
437
		},
438
 
439
		_selectOption: function(/*Event*/ evt){
440
			var tgt = null;
441
			if(!evt){
442
				evt ={ target: this._popupWidget.getHighlightedOption()};
443
			}
444
				// what if nothing is highlighted yet?
445
			if(!evt.target){
446
				// handle autocompletion where the the user has hit ENTER or TAB
447
				this.setDisplayedValue(this.getDisplayedValue());
448
				return;
449
			// otherwise the user has accepted the autocompleted value
450
			}else{
451
				tgt = evt.target;
452
			}
453
			if(!evt.noHide){
454
				this._hideResultList();
455
				this._setCaretPos(this.focusNode, this.store.getValue(tgt.item, this.searchAttr).length);
456
			}
457
			this._doSelect(tgt);
458
		},
459
 
460
		_doSelect: function(tgt){
461
			this.item = tgt.item;
462
			this.setValue(this.store.getValue(tgt.item, this.searchAttr), true);
463
		},
464
 
465
		_onArrowMouseDown: function(evt){
466
			// summary: callback when arrow is clicked
467
			if(this.disabled){
468
				return;
469
			}
470
			dojo.stopEvent(evt);
471
			this.focus();
472
			if(this._isShowingNow){
473
				this._hideResultList();
474
			}else{
475
				// forces full population of results, if they click
476
				// on the arrow it means they want to see more options
477
				this._startSearch("");
478
			}
479
		},
480
 
481
		_startSearchFromInput: function(){
482
			this._startSearch(this.focusNode.value);
483
		},
484
 
485
		_startSearch: function(/*String*/ key){
486
			if(!this._popupWidget){
487
				this._popupWidget = new dijit.form._ComboBoxMenu({
488
					onChange: dojo.hitch(this, this._selectOption)
489
				});
490
			}
491
			// create a new query to prevent accidentally querying for a hidden value from FilteringSelect's keyField
492
			var query=this.query;
493
			this._lastQuery=query[this.searchAttr]=key+"*";
494
			var dataObject=this.store.fetch({queryOptions:{ignoreCase:this.ignoreCase, deep:true}, query: query, onComplete:dojo.hitch(this, "_openResultList"), start:0, count:this.pageSize});
495
			function nextSearch(dataObject, direction){
496
				dataObject.start+=dataObject.count*direction;
497
				// #4091: tell callback the direction of the paging so the screen reader knows which menu option to shout
498
				dataObject.direction=direction;
499
				dataObject.store.fetch(dataObject);
500
			}
501
			this._nextSearch=this._popupWidget.onPage=dojo.hitch(this, nextSearch, dataObject);
502
		},
503
 
504
		_getValueField:function(){
505
			return this.searchAttr;
506
		},
507
 
508
		/////////////// Event handlers /////////////////////
509
 
510
		_arrowPressed: function(){
511
			if(!this.disabled&&this.hasDownArrow){
512
				dojo.addClass(this.downArrowNode, "dijitArrowButtonActive");
513
			}
514
		},
515
 
516
		_arrowIdle: function(){
517
			if(!this.disabled&&this.hasDownArrow){
518
				dojo.removeClass(this.downArrowNode, "dojoArrowButtonPushed");
519
			}
520
		},
521
 
522
		compositionend: function(/*Event*/ evt){
523
			// summary: When inputting characters using an input method, such as Asian
524
			// languages, it will generate this event instead of onKeyDown event
525
			// Note: this event is only triggered in FF (not in IE)
526
			this.onkeypress({charCode:-1});
527
		},
528
 
529
		//////////// INITIALIZATION METHODS ///////////////////////////////////////
530
		constructor: function(){
531
			this.query={};
532
		},
533
 
534
		postMixInProperties: function(){
535
			if(!this.hasDownArrow){
536
				this.baseClass = "dijitTextBox";
537
			}
538
			if(!this.store){
539
				// if user didn't specify store, then assume there are option tags
540
				var items = this.srcNodeRef ? dojo.query("> option", this.srcNodeRef).map(function(node){
541
					node.style.display="none";
542
					return { value: node.getAttribute("value"), name: String(node.innerHTML) };
543
				}) : {};
544
				this.store = new dojo.data.ItemFileReadStore({data: {identifier:this._getValueField(), items:items}});
545
 
546
				// if there is no value set and there is an option list,
547
				// set the value to the first value to be consistent with native Select
548
				if(items && items.length && !this.value){
549
					// For <select>, IE does not let you set the value attribute of the srcNodeRef (and thus dojo.mixin does not copy it).
550
					// IE does understand selectedIndex though, which is automatically set by the selected attribute of an option tag
551
					this.value = items[this.srcNodeRef.selectedIndex != -1 ? this.srcNodeRef.selectedIndex : 0]
552
						[this._getValueField()];
553
				}
554
			}
555
		},
556
 
557
		uninitialize:function(){
558
			if(this._popupWidget){
559
				this._hideResultList();
560
				this._popupWidget.destroy()
561
			};
562
		},
563
 
564
		_getMenuLabelFromItem:function(/*Item*/ item){
565
			return {html:false, label:this.store.getValue(item, this.searchAttr)};
566
		},
567
 
568
		open:function(){
569
			this._isShowingNow=true;
570
			return dijit.popup.open({
571
				popup: this._popupWidget,
572
				around: this.domNode,
573
				parent: this
574
			});
575
		}
576
	}
577
);
578
 
579
dojo.declare(
580
	"dijit.form._ComboBoxMenu",
581
	[dijit._Widget, dijit._Templated],
582
 
583
	{
584
		// summary:
585
		//	Focus-less div based menu for internal use in ComboBox
586
 
587
		templateString:"<div class='dijitMenu' dojoAttachEvent='onmousedown,onmouseup,onmouseover,onmouseout' tabIndex='-1' style='overflow:\"auto\";'>"
588
				+"<div class='dijitMenuItem dijitMenuPreviousButton' dojoAttachPoint='previousButton'></div>"
589
				+"<div class='dijitMenuItem dijitMenuNextButton' dojoAttachPoint='nextButton'></div>"
590
			+"</div>",
591
		_messages:null,
592
 
593
		postMixInProperties:function(){
594
			this._messages = dojo.i18n.getLocalization("dijit.form", "ComboBox", this.lang);
595
			this.inherited("postMixInProperties", arguments);
596
		},
597
 
598
		setValue:function(/*Object*/ value){
599
			this.value=value;
600
			this.onChange(value);
601
		},
602
 
603
		onChange:function(/*Object*/ value){},
604
		onPage:function(/*Number*/ direction){},
605
 
606
		postCreate:function(){
607
			// fill in template with i18n messages
608
			this.previousButton.innerHTML=this._messages["previousMessage"];
609
			this.nextButton.innerHTML=this._messages["nextMessage"];
610
			this.inherited("postCreate", arguments);
611
		},
612
 
613
		onClose:function(){
614
			this._blurOptionNode();
615
		},
616
 
617
		_createOption:function(/*Object*/ item, labelFunc){
618
			// summary: creates an option to appear on the popup menu
619
			// subclassed by FilteringSelect
620
 
621
			var labelObject=labelFunc(item);
622
			var menuitem = document.createElement("div");
623
			if(labelObject.html){menuitem.innerHTML=labelObject.label;}
624
			else{menuitem.appendChild(document.createTextNode(labelObject.label));}
625
			// #3250: in blank options, assign a normal height
626
			if(menuitem.innerHTML==""){
627
				menuitem.innerHTML="&nbsp;"
628
			}
629
			menuitem.item=item;
630
			return menuitem;
631
		},
632
 
633
		createOptions:function(results, dataObject, labelFunc){
634
			//this._dataObject=dataObject;
635
			//this._dataObject.onComplete=dojo.hitch(comboBox, comboBox._openResultList);
636
			// display "Previous . . ." button
637
			this.previousButton.style.display=dataObject.start==0?"none":"";
638
			// create options using _createOption function defined by parent ComboBox (or FilteringSelect) class
639
			// #2309: iterate over cache nondestructively
640
			var _this=this;
641
			dojo.forEach(results, function(item){
642
				var menuitem=_this._createOption(item, labelFunc);
643
				menuitem.className = "dijitMenuItem";
644
				_this.domNode.insertBefore(menuitem, _this.nextButton);
645
			});
646
			// display "Next . . ." button
647
			this.nextButton.style.display=dataObject.count==results.length?"":"none";
648
		},
649
 
650
		clearResultList:function(){
651
			// keep the previous and next buttons of course
652
			while(this.domNode.childNodes.length>2){
653
				this.domNode.removeChild(this.domNode.childNodes[this.domNode.childNodes.length-2]);
654
			}
655
		},
656
 
657
		// these functions are called in showResultList
658
		getItems:function(){
659
			return this.domNode.childNodes;
660
		},
661
 
662
		getListLength:function(){
663
			return this.domNode.childNodes.length-2;
664
		},
665
 
666
		onmousedown:function(/*Event*/ evt){
667
			dojo.stopEvent(evt);
668
		},
669
 
670
		onmouseup:function(/*Event*/ evt){
671
			if(evt.target === this.domNode){
672
				return;
673
			}else if(evt.target==this.previousButton){
674
				this.onPage(-1);
675
			}else if(evt.target==this.nextButton){
676
				this.onPage(1);
677
			}else{
678
				var tgt=evt.target;
679
				// while the clicked node is inside the div
680
				while(!tgt.item){
681
					// recurse to the top
682
					tgt=tgt.parentNode;
683
				}
684
				this.setValue({target:tgt}, true);
685
			}
686
		},
687
 
688
		onmouseover:function(/*Event*/ evt){
689
			if(evt.target === this.domNode){ return; }
690
			var tgt=evt.target;
691
			if(!(tgt==this.previousButton||tgt==this.nextButton)){
692
				// while the clicked node is inside the div
693
				while(!tgt.item){
694
					// recurse to the top
695
					tgt=tgt.parentNode;
696
				}
697
			}
698
			this._focusOptionNode(tgt);
699
		},
700
 
701
		onmouseout:function(/*Event*/ evt){
702
			if(evt.target === this.domNode){ return; }
703
			this._blurOptionNode();
704
		},
705
 
706
		_focusOptionNode:function(/*DomNode*/ node){
707
			// summary:
708
			//	does the actual highlight
709
			if(this._highlighted_option != node){
710
				this._blurOptionNode();
711
				this._highlighted_option = node;
712
				dojo.addClass(this._highlighted_option, "dijitMenuItemHover");
713
			}
714
		},
715
 
716
		_blurOptionNode:function(){
717
			// summary:
718
			//	removes highlight on highlighted option
719
			if(this._highlighted_option){
720
				dojo.removeClass(this._highlighted_option, "dijitMenuItemHover");
721
				this._highlighted_option = null;
722
			}
723
		},
724
 
725
		_highlightNextOption:function(){
726
			// because each press of a button clears the menu,
727
			// the highlighted option sometimes becomes detached from the menu!
728
			// test to see if the option has a parent to see if this is the case.
729
			if(!this.getHighlightedOption()){
730
				this._focusOptionNode(this.domNode.firstChild.style.display=="none"?this.domNode.firstChild.nextSibling:this.domNode.firstChild);
731
			}else if(this._highlighted_option.nextSibling&&this._highlighted_option.nextSibling.style.display!="none"){
732
				this._focusOptionNode(this._highlighted_option.nextSibling);
733
			}
734
			// scrollIntoView is called outside of _focusOptionNode because in IE putting it inside causes the menu to scroll up on mouseover
735
			dijit.scrollIntoView(this._highlighted_option);
736
		},
737
 
738
		highlightFirstOption:function(){
739
			// highlight the non-Previous choices option
740
			this._focusOptionNode(this.domNode.firstChild.nextSibling);
741
			dijit.scrollIntoView(this._highlighted_option);
742
		},
743
 
744
		highlightLastOption:function(){
745
			// highlight the noon-More choices option
746
			this._focusOptionNode(this.domNode.lastChild.previousSibling);
747
			dijit.scrollIntoView(this._highlighted_option);
748
		},
749
 
750
		_highlightPrevOption:function(){
751
			// if nothing selected, highlight last option
752
			// makes sense if you select Previous and try to keep scrolling up the list
753
			if(!this.getHighlightedOption()){
754
				this._focusOptionNode(this.domNode.lastChild.style.display=="none"?this.domNode.lastChild.previousSibling:this.domNode.lastChild);
755
			}else if(this._highlighted_option.previousSibling&&this._highlighted_option.previousSibling.style.display!="none"){
756
				this._focusOptionNode(this._highlighted_option.previousSibling);
757
			}
758
			dijit.scrollIntoView(this._highlighted_option);
759
		},
760
 
761
		_page:function(/*Boolean*/ up){
762
			var scrollamount=0;
763
			var oldscroll=this.domNode.scrollTop;
764
			var height=parseInt(dojo.getComputedStyle(this.domNode).height);
765
			// if no item is highlighted, highlight the first option
766
			if(!this.getHighlightedOption()){this._highlightNextOption();}
767
			while(scrollamount<height){
768
				if(up){
769
					// stop at option 1
770
					if(!this.getHighlightedOption().previousSibling||this._highlighted_option.previousSibling.style.display=="none"){break;}
771
					this._highlightPrevOption();
772
				}else{
773
					// stop at last option
774
					if(!this.getHighlightedOption().nextSibling||this._highlighted_option.nextSibling.style.display=="none"){break;}
775
					this._highlightNextOption();
776
				}
777
				// going backwards
778
				var newscroll=this.domNode.scrollTop;
779
				scrollamount+=(newscroll-oldscroll)*(up ? -1:1);
780
				oldscroll=newscroll;
781
			}
782
		},
783
 
784
		pageUp:function(){
785
			this._page(true);
786
		},
787
 
788
		pageDown:function(){
789
			this._page(false);
790
		},
791
 
792
		getHighlightedOption:function(){
793
			// summary:
794
			//	Returns the highlighted option.
795
			return this._highlighted_option&&this._highlighted_option.parentNode ? this._highlighted_option : null;
796
		},
797
 
798
		handleKey:function(evt){
799
			switch(evt.keyCode){
800
				case dojo.keys.DOWN_ARROW:
801
					this._highlightNextOption();
802
					break;
803
				case dojo.keys.PAGE_DOWN:
804
					this.pageDown();
805
					break;
806
				case dojo.keys.UP_ARROW:
807
					this._highlightPrevOption();
808
					break;
809
				case dojo.keys.PAGE_UP:
810
					this.pageUp();
811
					break;
812
			}
813
		}
814
	}
815
);
816
 
817
dojo.declare(
818
	"dijit.form.ComboBox",
819
	[dijit.form.ValidationTextBox, dijit.form.ComboBoxMixin],
820
	{
821
		postMixInProperties: function(){
822
			dijit.form.ComboBoxMixin.prototype.postMixInProperties.apply(this, arguments);
823
			dijit.form.ValidationTextBox.prototype.postMixInProperties.apply(this, arguments);
824
		}
825
	}
826
);
827
 
828
}