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'>Χ</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\">▼</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=" "
|
|
|
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 |
}
|