if(!dojo._hasResource["dijit.form.ComboBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit.form.ComboBox"] = true;
dojo.provide("dijit.form.ComboBox");
dojo.require("dojo.data.ItemFileReadStore");
dojo.require("dijit.form._DropDownTextBox");
dojo.require("dijit.form.ValidationTextBox");
dojo.requireLocalization("dijit.form", "ComboBox", null, "ROOT");
dojo.declare(
"dijit.form.ComboBoxMixin",
dijit.form._DropDownTextBox,
{
// summary:
// Auto-completing text box, and base class for FilteringSelect widget.
//
// The drop down box's values are populated from an class called
// a data provider, which returns a list of values based on the characters
// that the user has typed into the input box.
//
// Some of the options to the ComboBox are actually arguments to the data
// provider.
// pageSize: Integer
// Argument to data provider.
// Specifies number of search results per page (before hitting "next" button)
pageSize: 30,
// store: Object
// Reference to data provider object used by this ComboBox
store: null,
// query: Object
// A query that can be passed to 'store' to initially filter the items,
// before doing further filtering based on searchAttr and the key.
query: {},
// autoComplete: Boolean
// If you type in a partial string, and then tab out of the box,
// automatically copy the first entry displayed in the drop down list to
// the field
autoComplete: true,
// searchDelay: Integer
// Delay in milliseconds between when user types something and we start
// searching based on that value
searchDelay: 100,
// searchAttr: String
// Searches pattern match against this field
searchAttr: "name",
// ignoreCase: Boolean
// Does the ComboBox menu ignore case?
ignoreCase: true,
_hasMasterPopup:true,
_popupClass:"dijit.form._ComboBoxMenu",
getValue:function(){
// don't get the textbox value but rather the previously set hidden value
return dijit.form.TextBox.superclass.getValue.apply(this, arguments);
},
setDisplayedValue:function(/*String*/ value){
this.setValue(value, true);
},
_getCaretPos: function(/*DomNode*/ element){
// khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22
if(typeof(element.selectionStart)=="number"){
// FIXME: this is totally borked on Moz < 1.3. Any recourse?
return element.selectionStart;
}else if(dojo.isIE){
// in the case of a mouse click in a popup being handled,
// then the document.selection is not the textarea, but the popup
// var r = document.selection.createRange();
// hack to get IE 6 to play nice. What a POS browser.
var tr = document.selection.createRange().duplicate();
var ntr = element.createTextRange();
tr.move("character",0);
ntr.move("character",0);
try{
// If control doesnt have focus, you get an exception.
// Seems to happen on reverse-tab, but can also happen on tab (seems to be a race condition - only happens sometimes).
// There appears to be no workaround for this - googled for quite a while.
ntr.setEndPoint("EndToEnd", tr);
return String(ntr.text).replace(/\r/g,"").length;
}
catch(e){
return 0; // If focus has shifted, 0 is fine for caret pos.
}
}
},
_setCaretPos: function(/*DomNode*/ element, /*Number*/ location){
location = parseInt(location);
this._setSelectedRange(element, location, location);
},
_setSelectedRange: function(/*DomNode*/ element, /*Number*/ start, /*Number*/ end){
if(!end){
end = element.value.length;
} // NOTE: Strange - should be able to put caret at start of text?
// Mozilla
// parts borrowed from http://www.faqts.com/knowledge_base/view.phtml/aid/13562/fid/130
if(element.setSelectionRange){
dijit.focus(element);
element.setSelectionRange(start, end);
}else if(element.createTextRange){ // IE
var range = element.createTextRange();
with(range){
collapse(true);
moveEnd('character', end);
moveStart('character', start);
select();
}
}else{ //otherwise try the event-creation hack (our own invention)
// do we need these?
element.value = element.value;
element.blur();
dijit.focus(element);
// figure out how far back to go
var dist = parseInt(element.value.length)-end;
var tchar = String.fromCharCode(37);
var tcc = tchar.charCodeAt(0);
for(var x = 0; x < dist; x++){
var te = document.createEvent("KeyEvents");
te.initKeyEvent("keypress", true, true, null, false, false, false, false, tcc, tcc);
element.dispatchEvent(te);
}
}
},
onkeypress: function(/*Event*/ evt){
// summary: handles keyboard events
if(evt.ctrlKey || evt.altKey){
return;
}
var doSearch = false;
if(this._isShowingNow){this._popupWidget.handleKey(evt);}
switch(evt.keyCode){
case dojo.keys.PAGE_DOWN:
case dojo.keys.DOWN_ARROW:
if(!this._isShowingNow||this._prev_key_esc){
this._arrowPressed();
doSearch=true;
}else{
this._announceOption(this._popupWidget.getHighlightedOption());
}
dojo.stopEvent(evt);
this._prev_key_backspace = false;
this._prev_key_esc = false;
break;
case dojo.keys.PAGE_UP:
case dojo.keys.UP_ARROW:
if(this._isShowingNow){
this._announceOption(this._popupWidget.getHighlightedOption());
}
dojo.stopEvent(evt);
this._prev_key_backspace = false;
this._prev_key_esc = false;
break;
case dojo.keys.ENTER:
// prevent submitting form if user presses enter
// also prevent accepting the value if either Next or Previous are selected
if(this._isShowingNow){
// only stop event on prev/next
var highlighted=this._popupWidget.getHighlightedOption();
if(highlighted==this._popupWidget.nextButton){
this._nextSearch(1);
dojo.stopEvent(evt);
break;
}
else if(highlighted==this._popupWidget.previousButton){
this._nextSearch(-1);
dojo.stopEvent(evt);
break;
}
}
// default case:
// prevent submit, but allow event to bubble
evt.preventDefault();
// fall through
case dojo.keys.TAB:
if(this._isShowingNow){
this._prev_key_backspace = false;
this._prev_key_esc = false;
if(this._isShowingNow&&this._popupWidget.getHighlightedOption()){
this._popupWidget.setValue({target:this._popupWidget.getHighlightedOption()}, true);
}else{
this.setDisplayedValue(this.getDisplayedValue());
}
this._hideResultList();
}else{
// also allow arbitrary user input
this.setDisplayedValue(this.getDisplayedValue());
}
break;
case dojo.keys.SPACE:
this._prev_key_backspace = false;
this._prev_key_esc = false;
if(this._isShowingNow && this._popupWidget.getHighlightedOption()){
dojo.stopEvent(evt);
this._selectOption();
this._hideResultList();
}
else{doSearch=true;}
break;
case dojo.keys.ESCAPE:
this._prev_key_backspace = false;
this._prev_key_esc = true;
this._hideResultList();
this.setValue(this.getValue());
break;
case dojo.keys.DELETE:
case dojo.keys.BACKSPACE:
this._prev_key_esc = false;
this._prev_key_backspace = true;
doSearch=true;
break;
case dojo.keys.RIGHT_ARROW: // fall through
case dojo.keys.LEFT_ARROW: // fall through
this._prev_key_backspace = false;
this._prev_key_esc = false;
break;
default:// non char keys (F1-F12 etc..) shouldn't open list
this._prev_key_backspace = false;
this._prev_key_esc = false;
if(evt.charCode!=0){
doSearch=true;
}
}
if(this.searchTimer){
clearTimeout(this.searchTimer);
}
if(doSearch){
// need to wait a tad before start search so that the event bubbles through DOM and we have value visible
this.searchTimer = setTimeout(dojo.hitch(this, this._startSearchFromInput), this.searchDelay);
}
},
_autoCompleteText: function(/*String*/ text){
// summary:
// Fill in the textbox with the first item from the drop down list, and
// highlight the characters that were auto-completed. For example, if user
// typed "CA" and the drop down list appeared, the textbox would be changed to
// "California" and "ifornia" would be highlighted.
// IE7: clear selection so next highlight works all the time
this._setSelectedRange(this.focusNode, this.focusNode.value.length, this.focusNode.value.length);
// does text autoComplete the value in the textbox?
// #3744: escape regexp so the user's input isn't treated as a regular expression.
// Example: If the user typed "(" then the regexp would throw "unterminated parenthetical."
// Also see #2558 for the autocompletion bug this regular expression fixes.
if(new RegExp("^"+escape(this.focusNode.value), this.ignoreCase ? "i" : "").test(escape(text))){
var cpos = this._getCaretPos(this.focusNode);
// only try to extend if we added the last character at the end of the input
if((cpos+1) > this.focusNode.value.length){
// only add to input node as we would overwrite Capitalisation of chars
// actually, that is ok
this.focusNode.value = text;//.substr(cpos);
// visually highlight the autocompleted characters
this._setSelectedRange(this.focusNode, cpos, this.focusNode.value.length);
}
}else{
// text does not autoComplete; replace the whole value and highlight
this.focusNode.value = text;
this._setSelectedRange(this.focusNode, 0, this.focusNode.value.length);
}
},
_openResultList: function(/*Object*/ results, /*Object*/ dataObject){
if(this.disabled||dataObject.query[this.searchAttr]!=this._lastQuery){
return;
}
this._popupWidget.clearResultList();
if(!results.length){
this._hideResultList();
return;
}
// Fill in the textbox with the first item from the drop down list, and
// highlight the characters that were auto-completed. For example, if user
// typed "CA" and the drop down list appeared, the textbox would be changed to
// "California" and "ifornia" would be highlighted.
var zerothvalue=new String(this.store.getValue(results[0], this.searchAttr));
if(zerothvalue&&(this.autoComplete)&&
(!this._prev_key_backspace)&&
// when the user clicks the arrow button to show the full list,
// startSearch looks for "*".
// it does not make sense to autocomplete
// if they are just previewing the options available.
(dataObject.query[this.searchAttr]!="*")){
this._autoCompleteText(zerothvalue);
// announce the autocompleted value
dijit.wai.setAttr(this.focusNode || this.domNode, "waiState", "valuenow", zerothvalue);
}
this._popupWidget.createOptions(results, dataObject, dojo.hitch(this, this._getMenuLabelFromItem));
// show our list (only if we have content, else nothing)
this._showResultList();
},
onfocus:function(){
dijit.form._DropDownTextBox.prototype.onfocus.apply(this, arguments);
this.inherited('onfocus', arguments);
},
onblur:function(){ /* not _onBlur! */
// call onblur first to avoid race conditions with _hasFocus
dijit.form._DropDownTextBox.prototype.onblur.apply(this, arguments);
if(!this._isShowingNow){
// if the user clicks away from the textbox, set the value to the textbox value
this.setDisplayedValue(this.getDisplayedValue());
}
// don't call this since the TextBox setValue is asynchronous
// if you uncomment this line, when you click away from the textbox,
// the value in the textbox reverts to match the hidden value
//this.parentClass.onblur.apply(this, arguments);
},
_announceOption: function(/*Node*/ node){
// summary:
// a11y code that puts the highlighted option in the textbox
// This way screen readers will know what is happening in the menu
if(node==null){return;}
// pull the text value from the item attached to the DOM node
var newValue;
if(node==this._popupWidget.nextButton||node==this._popupWidget.previousButton){
newValue=node.innerHTML;
}else{
newValue=this.store.getValue(node.item, this.searchAttr);
}
// get the text that the user manually entered (cut off autocompleted text)
this.focusNode.value=this.focusNode.value.substring(0, this._getCaretPos(this.focusNode));
// autocomplete the rest of the option to announce change
this._autoCompleteText(newValue);
},
_selectOption: function(/*Event*/ evt){
var tgt = null;
if(!evt){
evt ={ target: this._popupWidget.getHighlightedOption()};
}
// what if nothing is highlighted yet?
if(!evt.target){
// handle autocompletion where the the user has hit ENTER or TAB
this.setDisplayedValue(this.getDisplayedValue());
return;
// otherwise the user has accepted the autocompleted value
}else{
tgt = evt.target;
}
if(!evt.noHide){
this._hideResultList();
this._setCaretPos(this.focusNode, this.store.getValue(tgt.item, this.searchAttr).length);
}
this._doSelect(tgt);
},
_doSelect: function(tgt){
this.setValue(this.store.getValue(tgt.item, this.searchAttr), true);
},
_onArrowClick: function(){
// summary: callback when arrow is clicked
if(this.disabled){
return;
}
this.focus();
this.makePopup();
if(this._isShowingNow){
this._hideResultList();
}else{
// forces full population of results, if they click
// on the arrow it means they want to see more options
this._startSearch("");
}
},
_startSearchFromInput: function(){
this._startSearch(this.focusNode.value);
},
_startSearch: function(/*String*/ key){
this.makePopup();
// create a new query to prevent accidentally querying for a hidden value from FilteringSelect's keyField
var query=this.query;
this._lastQuery=query[this.searchAttr]=key+"*";
var dataObject=this.store.fetch({queryOptions:{ignoreCase:this.ignoreCase, deep:true}, query: query, onComplete:dojo.hitch(this, "_openResultList"), start:0, count:this.pageSize});
function nextSearch(dataObject, direction){
dataObject.start+=dataObject.count*direction;
dataObject.store.fetch(dataObject);
}
this._nextSearch=this._popupWidget.onPage=dojo.hitch(this, nextSearch, dataObject);
},
_getValueField:function(){
return this.searchAttr;
},
postMixInProperties: function(){
dijit.form._DropDownTextBox.prototype.postMixInProperties.apply(this, arguments);
if(!this.store){
// if user didn't specify store, then assume there are option tags
var items = dojo.query("> option", this.srcNodeRef).map(function(node){
node.style.display="none";
return { value: node.getAttribute("value"), name: String(node.innerHTML) };
});
this.store = new dojo.data.ItemFileReadStore({data: {identifier:this._getValueField(), items:items}});
// if there is no value set and there is an option list,
// set the value to the first value to be consistent with native Select
if(items&&items.length&&!this.value){
// For