diff options
author | Andrew Dolgov <[email protected]> | 2011-11-08 20:40:44 +0400 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2011-11-08 20:40:44 +0400 |
commit | 81bea17aefb26859f825b9293c7c99192874806e (patch) | |
tree | fb244408ca271affa2899adb634788802c9a89d8 /lib/dijit/form/ComboBox.js | |
parent | 870a70e109ac9e80a88047044530de53d0404ec7 (diff) |
upgrade Dojo to 1.6.1
Diffstat (limited to 'lib/dijit/form/ComboBox.js')
-rw-r--r-- | lib/dijit/form/ComboBox.js | 1812 |
1 files changed, 1214 insertions, 598 deletions
diff --git a/lib/dijit/form/ComboBox.js b/lib/dijit/form/ComboBox.js index 694c43ed1..aecc5c4fc 100644 --- a/lib/dijit/form/ComboBox.js +++ b/lib/dijit/form/ComboBox.js @@ -1,12 +1,12 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.ComboBox"]){ -dojo._hasResource["dijit.form.ComboBox"]=true; +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.window"); dojo.require("dojo.regexp"); @@ -15,601 +15,1217 @@ dojo.require("dojo.data.util.filter"); dojo.require("dijit._CssStateMixin"); dojo.require("dijit.form._FormWidget"); dojo.require("dijit.form.ValidationTextBox"); -dojo.requireLocalization("dijit.form","ComboBox",null,"ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw"); -dojo.declare("dijit.form.ComboBoxMixin",null,{item:null,pageSize:Infinity,store:null,fetchProperties:{},query:{},autoComplete:true,highlightMatch:"first",searchDelay:100,searchAttr:"name",labelAttr:"",labelType:"text",queryExpr:"${0}*",ignoreCase:true,hasDownArrow:true,templateString:dojo.cache("dijit.form","templates/ComboBox.html","<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\"\n\tdojoAttachPoint=\"comboNode\" waiRole=\"combobox\"\n\t><div class='dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton dijitArrowButtonContainer'\n\t\tdojoAttachPoint=\"downArrowNode\" waiRole=\"presentation\"\n\t\tdojoAttachEvent=\"onmousedown:_onArrowMouseDown\"\n\t\t><input class=\"dijitReset dijitInputField dijitArrowButtonInner\" value=\"▼ \" type=\"text\" tabIndex=\"-1\" readOnly waiRole=\"presentation\"\n\t\t\t${_buttonInputDisabled}\n\t/></div\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readOnly waiRole=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class='dijitReset dijitInputInner' ${!nameAttrSetting} type=\"text\" autocomplete=\"off\"\n\t\t\tdojoAttachEvent=\"onkeypress:_onKeyPress,compositionend\"\n\t\t\tdojoAttachPoint=\"textbox,focusNode\" waiRole=\"textbox\" waiState=\"haspopup-true,autocomplete-list\"\n\t/></div\n></div>\n"),baseClass:"dijitTextBox dijitComboBox",cssStateNodes:{"downArrowNode":"dijitDownArrowButton"},_getCaretPos:function(_1){ -var _2=0; -if(typeof (_1.selectionStart)=="number"){ -_2=_1.selectionStart; -}else{ -if(dojo.isIE){ -var tr=dojo.doc.selection.createRange().duplicate(); -var _3=_1.createTextRange(); -tr.move("character",0); -_3.move("character",0); -try{ -_3.setEndPoint("EndToEnd",tr); -_2=String(_3.text).replace(/\r/g,"").length; -} -catch(e){ -} -} -} -return _2; -},_setCaretPos:function(_4,_5){ -_5=parseInt(_5); -dijit.selectInputText(_4,_5,_5); -},_setDisabledAttr:function(_6){ -this.inherited(arguments); -dijit.setWaiState(this.comboNode,"disabled",_6); -},_abortQuery:function(){ -if(this.searchTimer){ -clearTimeout(this.searchTimer); -this.searchTimer=null; -} -if(this._fetchHandle){ -if(this._fetchHandle.abort){ -this._fetchHandle.abort(); -} -this._fetchHandle=null; -} -},_onInput:function(_7){ -if(!this.searchTimer&&(_7.type=="paste"||_7.type=="input")&&this._lastInput!=this.textbox.value){ -this.searchTimer=setTimeout(dojo.hitch(this,function(){ -this._onKeyPress({charOrCode:229}); -}),100); -} -this.inherited(arguments); -},_onKeyPress:function(_8){ -var _9=_8.charOrCode; -if(_8.altKey||((_8.ctrlKey||_8.metaKey)&&(_9!="x"&&_9!="v"))||_9==dojo.keys.SHIFT){ -return; -} -var _a=false; -var _b="_startSearchFromInput"; -var pw=this._popupWidget; -var dk=dojo.keys; -var _c=null; -this._prev_key_backspace=false; -this._abortQuery(); -if(this._isShowingNow){ -pw.handleKey(_9); -_c=pw.getHighlightedOption(); -} -switch(_9){ -case dk.PAGE_DOWN: -case dk.DOWN_ARROW: -case dk.PAGE_UP: -case dk.UP_ARROW: -if(!this._isShowingNow){ -_a=true; -_b="_startSearchAll"; -}else{ -this._announceOption(_c); -} -dojo.stopEvent(_8); -break; -case dk.ENTER: -if(_c){ -if(_c==pw.nextButton){ -this._nextSearch(1); -dojo.stopEvent(_8); -break; -}else{ -if(_c==pw.previousButton){ -this._nextSearch(-1); -dojo.stopEvent(_8); -break; -} -} -}else{ -this._setBlurValue(); -this._setCaretPos(this.focusNode,this.focusNode.value.length); -} -_8.preventDefault(); -case dk.TAB: -var _d=this.get("displayedValue"); -if(pw&&(_d==pw._messages["previousMessage"]||_d==pw._messages["nextMessage"])){ -break; -} -if(_c){ -this._selectOption(); -} -if(this._isShowingNow){ -this._lastQuery=null; -this._hideResultList(); -} -break; -case " ": -if(_c){ -dojo.stopEvent(_8); -this._selectOption(); -this._hideResultList(); -}else{ -_a=true; -} -break; -case dk.ESCAPE: -if(this._isShowingNow){ -dojo.stopEvent(_8); -this._hideResultList(); -} -break; -case dk.DELETE: -case dk.BACKSPACE: -this._prev_key_backspace=true; -_a=true; -break; -default: -_a=typeof _9=="string"||_9==229; -} -if(_a){ -this.item=undefined; -this.searchTimer=setTimeout(dojo.hitch(this,_b),1); -} -},_autoCompleteText:function(_e){ -var fn=this.focusNode; -dijit.selectInputText(fn,fn.value.length); -var _f=this.ignoreCase?"toLowerCase":"substr"; -if(_e[_f](0).indexOf(this.focusNode.value[_f](0))==0){ -var _10=this._getCaretPos(fn); -if((_10+1)>fn.value.length){ -fn.value=_e; -dijit.selectInputText(fn,_10); -} -}else{ -fn.value=_e; -dijit.selectInputText(fn); -} -},_openResultList:function(_11,_12){ -this._fetchHandle=null; -if(this.disabled||this.readOnly||(_12.query[this.searchAttr]!=this._lastQuery)){ -return; -} -this._popupWidget.clearResultList(); -if(!_11.length&&!this._maxOptions){ -this._hideResultList(); -return; -} -_12._maxOptions=this._maxOptions; -var _13=this._popupWidget.createOptions(_11,_12,dojo.hitch(this,"_getMenuLabelFromItem")); -this._showResultList(); -if(_12.direction){ -if(1==_12.direction){ -this._popupWidget.highlightFirstOption(); -}else{ -if(-1==_12.direction){ -this._popupWidget.highlightLastOption(); -} -} -this._announceOption(this._popupWidget.getHighlightedOption()); -}else{ -if(this.autoComplete&&!this._prev_key_backspace&&!/^[*]+$/.test(_12.query[this.searchAttr])){ -this._announceOption(_13[1]); -} -} -},_showResultList:function(){ -this._hideResultList(); -this.displayMessage(""); -dojo.style(this._popupWidget.domNode,{width:"",height:""}); -var _14=this.open(); -var _15=dojo.marginBox(this._popupWidget.domNode); -this._popupWidget.domNode.style.overflow=((_14.h==_15.h)&&(_14.w==_15.w))?"hidden":"auto"; -var _16=_14.w; -if(_14.h<this._popupWidget.domNode.scrollHeight){ -_16+=16; -} -dojo.marginBox(this._popupWidget.domNode,{h:_14.h,w:Math.max(_16,this.domNode.offsetWidth)}); -if(_16<this.domNode.offsetWidth){ -this._popupWidget.domNode.parentNode.style.left=dojo.position(this.domNode,true).x+"px"; -} -dijit.setWaiState(this.comboNode,"expanded","true"); -},_hideResultList:function(){ -this._abortQuery(); -if(this._isShowingNow){ -dijit.popup.close(this._popupWidget); -this._isShowingNow=false; -dijit.setWaiState(this.comboNode,"expanded","false"); -dijit.removeWaiState(this.focusNode,"activedescendant"); -} -},_setBlurValue:function(){ -var _17=this.get("displayedValue"); -var pw=this._popupWidget; -if(pw&&(_17==pw._messages["previousMessage"]||_17==pw._messages["nextMessage"])){ -this._setValueAttr(this._lastValueReported,true); -}else{ -if(typeof this.item=="undefined"){ -this.item=null; -this.set("displayedValue",_17); -}else{ -if(this.value!=this._lastValueReported){ -dijit.form._FormValueWidget.prototype._setValueAttr.call(this,this.value,true); -} -this._refreshState(); -} -} -},_onBlur:function(){ -this._hideResultList(); -this.inherited(arguments); -},_setItemAttr:function(_18,_19,_1a){ -if(!_1a){ -_1a=this.labelFunc(_18,this.store); -} -this.value=this._getValueField()!=this.searchAttr?this.store.getIdentity(_18):_1a; -this.item=_18; -dijit.form.ComboBox.superclass._setValueAttr.call(this,this.value,_19,_1a); -},_announceOption:function(_1b){ -if(!_1b){ -return; -} -var _1c; -if(_1b==this._popupWidget.nextButton||_1b==this._popupWidget.previousButton){ -_1c=_1b.innerHTML; -this.item=undefined; -this.value=""; -}else{ -_1c=this.labelFunc(_1b.item,this.store); -this.set("item",_1b.item,false,_1c); -} -this.focusNode.value=this.focusNode.value.substring(0,this._lastInput.length); -dijit.setWaiState(this.focusNode,"activedescendant",dojo.attr(_1b,"id")); -this._autoCompleteText(_1c); -},_selectOption:function(evt){ -if(evt){ -this._announceOption(evt.target); -} -this._hideResultList(); -this._setCaretPos(this.focusNode,this.focusNode.value.length); -dijit.form._FormValueWidget.prototype._setValueAttr.call(this,this.value,true); -},_onArrowMouseDown:function(evt){ -if(this.disabled||this.readOnly){ -return; -} -dojo.stopEvent(evt); -this.focus(); -if(this._isShowingNow){ -this._hideResultList(); -}else{ -this._startSearchAll(); -} -},_startSearchAll:function(){ -this._startSearch(""); -},_startSearchFromInput:function(){ -this._startSearch(this.focusNode.value.replace(/([\\\*\?])/g,"\\$1")); -},_getQueryString:function(_1d){ -return dojo.string.substitute(this.queryExpr,[_1d]); -},_startSearch:function(key){ -if(!this._popupWidget){ -var _1e=this.id+"_popup"; -this._popupWidget=new dijit.form._ComboBoxMenu({onChange:dojo.hitch(this,this._selectOption),id:_1e,dir:this.dir}); -dijit.removeWaiState(this.focusNode,"activedescendant"); -dijit.setWaiState(this.textbox,"owns",_1e); -} -var _1f=dojo.clone(this.query); -this._lastInput=key; -this._lastQuery=_1f[this.searchAttr]=this._getQueryString(key); -this.searchTimer=setTimeout(dojo.hitch(this,function(_20,_21){ -this.searchTimer=null; -var _22={queryOptions:{ignoreCase:this.ignoreCase,deep:true},query:_20,onBegin:dojo.hitch(this,"_setMaxOptions"),onComplete:dojo.hitch(this,"_openResultList"),onError:function(_23){ -_21._fetchHandle=null; -console.error("dijit.form.ComboBox: "+_23); -dojo.hitch(_21,"_hideResultList")(); -},start:0,count:this.pageSize}; -dojo.mixin(_22,_21.fetchProperties); -this._fetchHandle=_21.store.fetch(_22); -var _24=function(_25,_26){ -_25.start+=_25.count*_26; -_25.direction=_26; -this._fetchHandle=this.store.fetch(_25); -}; -this._nextSearch=this._popupWidget.onPage=dojo.hitch(this,_24,this._fetchHandle); -},_1f,this),this.searchDelay); -},_setMaxOptions:function(_27,_28){ -this._maxOptions=_27; -},_getValueField:function(){ -return this.searchAttr; -},compositionend:function(evt){ -this._onKeyPress({charOrCode:229}); -},constructor:function(){ -this.query={}; -this.fetchProperties={}; -},postMixInProperties:function(){ -if(!this.store){ -var _29=this.srcNodeRef; -this.store=new dijit.form._ComboBoxDataStore(_29); -if(!("value" in this.params)){ -var _2a=this.store.fetchSelectedItem(); -if(_2a){ -var _2b=this._getValueField(); -this.value=_2b!=this.searchAttr?this.store.getValue(_2a,_2b):this.labelFunc(_2a,this.store); -} -} -} -this.inherited(arguments); -},postCreate:function(){ -if(!this.hasDownArrow){ -this.downArrowNode.style.display="none"; -} -var _2c=dojo.query("label[for=\""+this.id+"\"]"); -if(_2c.length){ -_2c[0].id=(this.id+"_label"); -var cn=this.comboNode; -dijit.setWaiState(cn,"labelledby",_2c[0].id); -} -this.inherited(arguments); -},uninitialize:function(){ -if(this._popupWidget&&!this._popupWidget._destroyed){ -this._hideResultList(); -this._popupWidget.destroy(); -} -this.inherited(arguments); -},_getMenuLabelFromItem:function(_2d){ -var _2e=this.labelAttr?this.store.getValue(_2d,this.labelAttr):this.labelFunc(_2d,this.store); -var _2f=this.labelType; -if(this.highlightMatch!="none"&&this.labelType=="text"&&this._lastInput){ -_2e=this.doHighlight(_2e,this._escapeHtml(this._lastInput)); -_2f="html"; -} -return {html:_2f=="html",label:_2e}; -},doHighlight:function(_30,_31){ -var _32="i"+(this.highlightMatch=="all"?"g":""); -var _33=this._escapeHtml(_30); -_31=dojo.regexp.escapeString(_31); -var ret=_33.replace(new RegExp("(^|\\s)("+_31+")",_32),"$1<span class=\"dijitComboBoxHighlightMatch\">$2</span>"); -return ret; -},_escapeHtml:function(str){ -str=String(str).replace(/&/gm,"&").replace(/</gm,"<").replace(/>/gm,">").replace(/"/gm,"""); -return str; -},open:function(){ -this._isShowingNow=true; -return dijit.popup.open({popup:this._popupWidget,around:this.domNode,parent:this}); -},reset:function(){ -this.item=null; -this.inherited(arguments); -},labelFunc:function(_34,_35){ -return _35.getValue(_34,this.searchAttr).toString(); -}}); -dojo.declare("dijit.form._ComboBoxMenu",[dijit._Widget,dijit._Templated,dijit._CssStateMixin],{templateString:"<ul class='dijitReset dijitMenu' dojoAttachEvent='onmousedown:_onMouseDown,onmouseup:_onMouseUp,onmouseover:_onMouseOver,onmouseout:_onMouseOut' tabIndex='-1' style='overflow: \"auto\"; overflow-x: \"hidden\";'>"+"<li class='dijitMenuItem dijitMenuPreviousButton' dojoAttachPoint='previousButton' waiRole='option'></li>"+"<li class='dijitMenuItem dijitMenuNextButton' dojoAttachPoint='nextButton' waiRole='option'></li>"+"</ul>",_messages:null,baseClass:"dijitComboBoxMenu",postMixInProperties:function(){ -this._messages=dojo.i18n.getLocalization("dijit.form","ComboBox",this.lang); -this.inherited(arguments); -},_setValueAttr:function(_36){ -this.value=_36; -this.onChange(_36); -},onChange:function(_37){ -},onPage:function(_38){ -},postCreate:function(){ -this.previousButton.innerHTML=this._messages["previousMessage"]; -this.nextButton.innerHTML=this._messages["nextMessage"]; -this.inherited(arguments); -},onClose:function(){ -this._blurOptionNode(); -},_createOption:function(_39,_3a){ -var _3b=_3a(_39); -var _3c=dojo.doc.createElement("li"); -dijit.setWaiRole(_3c,"option"); -if(_3b.html){ -_3c.innerHTML=_3b.label; -}else{ -_3c.appendChild(dojo.doc.createTextNode(_3b.label)); -} -if(_3c.innerHTML==""){ -_3c.innerHTML=" "; -} -_3c.item=_39; -return _3c; -},createOptions:function(_3d,_3e,_3f){ -this.previousButton.style.display=(_3e.start==0)?"none":""; -dojo.attr(this.previousButton,"id",this.id+"_prev"); -dojo.forEach(_3d,function(_40,i){ -var _41=this._createOption(_40,_3f); -_41.className="dijitReset dijitMenuItem"+(this.isLeftToRight()?"":" dijitMenuItemRtl"); -dojo.attr(_41,"id",this.id+i); -this.domNode.insertBefore(_41,this.nextButton); -},this); -var _42=false; -if(_3e._maxOptions&&_3e._maxOptions!=-1){ -if((_3e.start+_3e.count)<_3e._maxOptions){ -_42=true; -}else{ -if((_3e.start+_3e.count)>_3e._maxOptions&&_3e.count==_3d.length){ -_42=true; -} -} -}else{ -if(_3e.count==_3d.length){ -_42=true; -} -} -this.nextButton.style.display=_42?"":"none"; -dojo.attr(this.nextButton,"id",this.id+"_next"); -return this.domNode.childNodes; -},clearResultList:function(){ -while(this.domNode.childNodes.length>2){ -this.domNode.removeChild(this.domNode.childNodes[this.domNode.childNodes.length-2]); -} -},_onMouseDown:function(evt){ -dojo.stopEvent(evt); -},_onMouseUp:function(evt){ -if(evt.target===this.domNode||!this._highlighted_option){ -return; -}else{ -if(evt.target==this.previousButton){ -this.onPage(-1); -}else{ -if(evt.target==this.nextButton){ -this.onPage(1); -}else{ -var tgt=evt.target; -while(!tgt.item){ -tgt=tgt.parentNode; -} -this._setValueAttr({target:tgt},true); -} -} -} -},_onMouseOver:function(evt){ -if(evt.target===this.domNode){ -return; -} -var tgt=evt.target; -if(!(tgt==this.previousButton||tgt==this.nextButton)){ -while(!tgt.item){ -tgt=tgt.parentNode; -} -} -this._focusOptionNode(tgt); -},_onMouseOut:function(evt){ -if(evt.target===this.domNode){ -return; -} -this._blurOptionNode(); -},_focusOptionNode:function(_43){ -if(this._highlighted_option!=_43){ -this._blurOptionNode(); -this._highlighted_option=_43; -dojo.addClass(this._highlighted_option,"dijitMenuItemSelected"); -} -},_blurOptionNode:function(){ -if(this._highlighted_option){ -dojo.removeClass(this._highlighted_option,"dijitMenuItemSelected"); -this._highlighted_option=null; -} -},_highlightNextOption:function(){ -if(!this.getHighlightedOption()){ -var fc=this.domNode.firstChild; -this._focusOptionNode(fc.style.display=="none"?fc.nextSibling:fc); -}else{ -var ns=this._highlighted_option.nextSibling; -if(ns&&ns.style.display!="none"){ -this._focusOptionNode(ns); -}else{ -this.highlightFirstOption(); -} -} -dojo.window.scrollIntoView(this._highlighted_option); -},highlightFirstOption:function(){ -var _44=this.domNode.firstChild; -var _45=_44.nextSibling; -this._focusOptionNode(_45.style.display=="none"?_44:_45); -dojo.window.scrollIntoView(this._highlighted_option); -},highlightLastOption:function(){ -this._focusOptionNode(this.domNode.lastChild.previousSibling); -dojo.window.scrollIntoView(this._highlighted_option); -},_highlightPrevOption:function(){ -if(!this.getHighlightedOption()){ -var lc=this.domNode.lastChild; -this._focusOptionNode(lc.style.display=="none"?lc.previousSibling:lc); -}else{ -var ps=this._highlighted_option.previousSibling; -if(ps&&ps.style.display!="none"){ -this._focusOptionNode(ps); -}else{ -this.highlightLastOption(); -} -} -dojo.window.scrollIntoView(this._highlighted_option); -},_page:function(up){ -var _46=0; -var _47=this.domNode.scrollTop; -var _48=dojo.style(this.domNode,"height"); -if(!this.getHighlightedOption()){ -this._highlightNextOption(); -} -while(_46<_48){ -if(up){ -if(!this.getHighlightedOption().previousSibling||this._highlighted_option.previousSibling.style.display=="none"){ -break; -} -this._highlightPrevOption(); -}else{ -if(!this.getHighlightedOption().nextSibling||this._highlighted_option.nextSibling.style.display=="none"){ -break; -} -this._highlightNextOption(); -} -var _49=this.domNode.scrollTop; -_46+=(_49-_47)*(up?-1:1); -_47=_49; -} -},pageUp:function(){ -this._page(true); -},pageDown:function(){ -this._page(false); -},getHighlightedOption:function(){ -var ho=this._highlighted_option; -return (ho&&ho.parentNode)?ho:null; -},handleKey:function(key){ -switch(key){ -case dojo.keys.DOWN_ARROW: -this._highlightNextOption(); -break; -case dojo.keys.PAGE_DOWN: -this.pageDown(); -break; -case dojo.keys.UP_ARROW: -this._highlightPrevOption(); -break; -case dojo.keys.PAGE_UP: -this.pageUp(); -break; -} -}}); -dojo.declare("dijit.form.ComboBox",[dijit.form.ValidationTextBox,dijit.form.ComboBoxMixin],{_setValueAttr:function(_4a,_4b,_4c){ -this.item=null; -if(!_4a){ -_4a=""; -} -dijit.form.ValidationTextBox.prototype._setValueAttr.call(this,_4a,_4b,_4c); -}}); -dojo.declare("dijit.form._ComboBoxDataStore",null,{constructor:function(_4d){ -this.root=_4d; -if(_4d.tagName!="SELECT"&&_4d.firstChild){ -_4d=dojo.query("select",_4d); -if(_4d.length>0){ -_4d=_4d[0]; -}else{ -this.root.innerHTML="<SELECT>"+this.root.innerHTML+"</SELECT>"; -_4d=this.root.firstChild; -} -this.root=_4d; -} -dojo.query("> option",_4d).forEach(function(_4e){ -_4e.innerHTML=dojo.trim(_4e.innerHTML); -}); -},getValue:function(_4f,_50,_51){ -return (_50=="value")?_4f.value:(_4f.innerText||_4f.textContent||""); -},isItemLoaded:function(_52){ -return true; -},getFeatures:function(){ -return {"dojo.data.api.Read":true,"dojo.data.api.Identity":true}; -},_fetchItems:function(_53,_54,_55){ -if(!_53.query){ -_53.query={}; -} -if(!_53.query.name){ -_53.query.name=""; -} -if(!_53.queryOptions){ -_53.queryOptions={}; -} -var _56=dojo.data.util.filter.patternToRegExp(_53.query.name,_53.queryOptions.ignoreCase),_57=dojo.query("> option",this.root).filter(function(_58){ -return (_58.innerText||_58.textContent||"").match(_56); +dojo.require("dijit._HasDropDown"); +dojo.requireLocalization("dijit.form", "ComboBox", null, "ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,kk,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw"); + + +dojo.declare( + "dijit.form.ComboBoxMixin", + dijit._HasDropDown, + { + // summary: + // Implements the base functionality for `dijit.form.ComboBox`/`dijit.form.FilteringSelect` + // description: + // All widgets that mix in dijit.form.ComboBoxMixin must extend `dijit.form._FormValueWidget`. + // tags: + // protected + + // item: Object + // This is the item returned by the dojo.data.store implementation that + // provides the data for this ComboBox, it's the currently selected item. + item: null, + + // pageSize: Integer + // Argument to data provider. + // Specifies number of search results per page (before hitting "next" button) + pageSize: Infinity, + + // store: [const] Object + // Reference to data provider object used by this ComboBox + store: null, + + // fetchProperties: Object + // Mixin to the dojo.data store's fetch. + // For example, to set the sort order of the ComboBox menu, pass: + // | { sort: [{attribute:"name",descending: true}] } + // To override the default queryOptions so that deep=false, do: + // | { queryOptions: {ignoreCase: true, deep: false} } + fetchProperties:{}, + + // 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. + // Any reference to the `searchAttr` is ignored. + query: {}, + + // autoComplete: Boolean + // If user types in a partial string, and then tab out of the `<input>` box, + // automatically copy the first entry displayed in the drop down list to + // the `<input>` field + autoComplete: true, + + // highlightMatch: String + // One of: "first", "all" or "none". + // + // If the ComboBox/FilteringSelect opens with the search results and the searched + // string can be found, it will be highlighted. If set to "all" + // then will probably want to change `queryExpr` parameter to '*${0}*' + // + // Highlighting is only performed when `labelType` is "text", so as to not + // interfere with any HTML markup an HTML label might contain. + highlightMatch: "first", + + // searchDelay: Integer + // Delay in milliseconds between when user types something and we start + // searching based on that value + searchDelay: 100, + + // searchAttr: String + // Search for items in the data store where this attribute (in the item) + // matches what the user typed + searchAttr: "name", + + // labelAttr: String? + // The entries in the drop down list come from this attribute in the + // dojo.data items. + // If not specified, the searchAttr attribute is used instead. + labelAttr: "", + + // labelType: String + // Specifies how to interpret the labelAttr in the data store items. + // Can be "html" or "text". + labelType: "text", + + // queryExpr: String + // This specifies what query ComboBox/FilteringSelect sends to the data store, + // based on what the user has typed. Changing this expression will modify + // whether the drop down shows only exact matches, a "starting with" match, + // etc. Use it in conjunction with highlightMatch. + // dojo.data query expression pattern. + // `${0}` will be substituted for the user text. + // `*` is used for wildcards. + // `${0}*` means "starts with", `*${0}*` means "contains", `${0}` means "is" + queryExpr: "${0}*", + + // ignoreCase: Boolean + // Set true if the ComboBox/FilteringSelect should ignore case when matching possible items + ignoreCase: true, + + // hasDownArrow: Boolean + // Set this textbox to have a down arrow button, to display the drop down list. + // Defaults to true. + hasDownArrow: true, + + templateString: dojo.cache("dijit.form", "templates/DropDownBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\"\n\trole=\"combobox\"\n\t><div class='dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton dijitArrowButtonContainer'\n\t\tdojoAttachPoint=\"_buttonNode, _popupStateNode\" role=\"presentation\"\n\t\t><input class=\"dijitReset dijitInputField dijitArrowButtonInner\" value=\"▼ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t\t${_buttonInputDisabled}\n\t/></div\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class='dijitReset dijitInputInner' ${!nameAttrSetting} type=\"text\" autocomplete=\"off\"\n\t\t\tdojoAttachPoint=\"textbox,focusNode\" role=\"textbox\" aria-haspopup=\"true\"\n\t/></div\n></div>\n"), + + baseClass: "dijitTextBox dijitComboBox", + + // dropDownClass: [protected extension] String + // Name of the dropdown widget class used to select a date/time. + // Subclasses should specify this. + dropDownClass: "dijit.form._ComboBoxMenu", + + // Set classes like dijitDownArrowButtonHover depending on + // mouse action over button node + cssStateNodes: { + "_buttonNode": "dijitDownArrowButton" + }, + + // Flags to _HasDropDown to limit height of drop down to make it fit in viewport + maxHeight: -1, + + // For backwards compatibility let onClick events propagate, even clicks on the down arrow button + _stopClickEvents: false, + + _getCaretPos: function(/*DomNode*/ element){ + // khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22 + var pos = 0; + if(typeof(element.selectionStart) == "number"){ + // FIXME: this is totally borked on Moz < 1.3. Any recourse? + pos = element.selectionStart; + }else if(dojo.isIE){ + // in the case of a mouse click in a popup being handled, + // then the dojo.doc.selection is not the textarea, but the popup + // var r = dojo.doc.selection.createRange(); + // hack to get IE 6 to play nice. What a POS browser. + var tr = dojo.doc.selection.createRange().duplicate(); + var ntr = element.createTextRange(); + tr.move("character",0); + ntr.move("character",0); + try{ + // If control doesn't 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); + pos = String(ntr.text).replace(/\r/g,"").length; + }catch(e){ + // If focus has shifted, 0 is fine for caret pos. + } + } + return pos; + }, + + _setCaretPos: function(/*DomNode*/ element, /*Number*/ location){ + location = parseInt(location); + dijit.selectInputText(element, location, location); + }, + + _setDisabledAttr: function(/*Boolean*/ value){ + // Additional code to set disabled state of ComboBox node. + // Overrides _FormValueWidget._setDisabledAttr() or ValidationTextBox._setDisabledAttr(). + this.inherited(arguments); + dijit.setWaiState(this.domNode, "disabled", value); + }, + + _abortQuery: function(){ + // stop in-progress query + if(this.searchTimer){ + clearTimeout(this.searchTimer); + this.searchTimer = null; + } + if(this._fetchHandle){ + if(this._fetchHandle.abort){ this._fetchHandle.abort(); } + this._fetchHandle = null; + } + }, + + _onInput: function(/*Event*/ evt){ + // summary: + // Handles paste events + if(!this.searchTimer && (evt.type == 'paste'/*IE|WebKit*/ || evt.type == 'input'/*Firefox*/) && this._lastInput != this.textbox.value){ + this.searchTimer = setTimeout(dojo.hitch(this, function(){ + this._onKey({charOrCode: 229}); // fake IME key to cause a search + }), 100); // long delay that will probably be preempted by keyboard input + } + this.inherited(arguments); + }, + + _onKey: function(/*Event*/ evt){ + // summary: + // Handles keyboard events + + var key = evt.charOrCode; + + // except for cutting/pasting case - ctrl + x/v + if(evt.altKey || ((evt.ctrlKey || evt.metaKey) && (key != 'x' && key != 'v')) || key == dojo.keys.SHIFT){ + return; // throw out weird key combinations and spurious events + } + + var doSearch = false; + var pw = this.dropDown; + var dk = dojo.keys; + var highlighted = null; + this._prev_key_backspace = false; + this._abortQuery(); + + // _HasDropDown will do some of the work: + // 1. when drop down is not yet shown: + // - if user presses the down arrow key, call loadDropDown() + // 2. when drop down is already displayed: + // - on ESC key, call closeDropDown() + // - otherwise, call dropDown.handleKey() to process the keystroke + this.inherited(arguments); + + if(this._opened){ + highlighted = pw.getHighlightedOption(); + } + switch(key){ + case dk.PAGE_DOWN: + case dk.DOWN_ARROW: + case dk.PAGE_UP: + case dk.UP_ARROW: + // Keystroke caused ComboBox_menu to move to a different item. + // Copy new item to <input> box. + if(this._opened){ + this._announceOption(highlighted); + } + dojo.stopEvent(evt); + break; + + case dk.ENTER: + // prevent submitting form if user presses enter. Also + // prevent accepting the value if either Next or Previous + // are selected + if(highlighted){ + // only stop event on prev/next + if(highlighted == pw.nextButton){ + this._nextSearch(1); + dojo.stopEvent(evt); + break; + }else if(highlighted == pw.previousButton){ + this._nextSearch(-1); + dojo.stopEvent(evt); + break; + } + }else{ + // Update 'value' (ex: KY) according to currently displayed text + this._setBlurValue(); // set value if needed + this._setCaretPos(this.focusNode, this.focusNode.value.length); // move cursor to end and cancel highlighting + } + // default case: + // if enter pressed while drop down is open, or for FilteringSelect, + // if we are in the middle of a query to convert a directly typed in value to an item, + // prevent submit, but allow event to bubble + if(this._opened || this._fetchHandle){ + evt.preventDefault(); + } + // fall through + + case dk.TAB: + var newvalue = this.get('displayedValue'); + // if the user had More Choices selected fall into the + // _onBlur handler + if(pw && ( + newvalue == pw._messages["previousMessage"] || + newvalue == pw._messages["nextMessage"]) + ){ + break; + } + if(highlighted){ + this._selectOption(); + } + if(this._opened){ + this._lastQuery = null; // in case results come back later + this.closeDropDown(); + } + break; + + case ' ': + if(highlighted){ + // user is effectively clicking a choice in the drop down menu + dojo.stopEvent(evt); + this._selectOption(); + this.closeDropDown(); + }else{ + // user typed a space into the input box, treat as normal character + doSearch = true; + } + break; + + case dk.DELETE: + case dk.BACKSPACE: + this._prev_key_backspace = true; + doSearch = true; + break; + + default: + // Non char keys (F1-F12 etc..) shouldn't open list. + // Ascii characters and IME input (Chinese, Japanese etc.) should. + //IME input produces keycode == 229. + doSearch = typeof key == 'string' || key == 229; + } + if(doSearch){ + // need to wait a tad before start search so that the event + // bubbles through DOM and we have value visible + this.item = undefined; // undefined means item needs to be set + this.searchTimer = setTimeout(dojo.hitch(this, "_startSearchFromInput"),1); + } + }, + + _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. + + var fn = this.focusNode; + + // IE7: clear selection so next highlight works all the time + dijit.selectInputText(fn, fn.value.length); + // does text autoComplete the value in the textbox? + var caseFilter = this.ignoreCase? 'toLowerCase' : 'substr'; + if(text[caseFilter](0).indexOf(this.focusNode.value[caseFilter](0)) == 0){ + var cpos = this._getCaretPos(fn); + // only try to extend if we added the last character at the end of the input + if((cpos+1) > fn.value.length){ + // only add to input node as we would overwrite Capitalisation of chars + // actually, that is ok + fn.value = text;//.substr(cpos); + // visually highlight the autocompleted characters + dijit.selectInputText(fn, cpos); + } + }else{ + // text does not autoComplete; replace the whole value and highlight + fn.value = text; + dijit.selectInputText(fn); + } + }, + + _openResultList: function(/*Object*/ results, /*Object*/ dataObject){ + // summary: + // Callback when a search completes. + // description: + // 1. generates drop-down list and calls _showResultList() to display it + // 2. if this result list is from user pressing "more choices"/"previous choices" + // then tell screen reader to announce new option + this._fetchHandle = null; + if( this.disabled || + this.readOnly || + (dataObject.query[this.searchAttr] != this._lastQuery) + ){ + return; + } + var wasSelected = this.dropDown._highlighted_option && dojo.hasClass(this.dropDown._highlighted_option, "dijitMenuItemSelected"); + this.dropDown.clearResultList(); + if(!results.length && !this._maxOptions){ // if no results and not just the previous choices button + this.closeDropDown(); + 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. + + dataObject._maxOptions = this._maxOptions; + var nodes = this.dropDown.createOptions( + results, + dataObject, + dojo.hitch(this, "_getMenuLabelFromItem") + ); + + // show our list (only if we have content, else nothing) + this._showResultList(); + + // #4091: + // tell the screen reader that the paging callback finished by + // shouting the next choice + if(dataObject.direction){ + if(1 == dataObject.direction){ + this.dropDown.highlightFirstOption(); + }else if(-1 == dataObject.direction){ + this.dropDown.highlightLastOption(); + } + if(wasSelected){ + this._announceOption(this.dropDown.getHighlightedOption()); + } + }else if(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. + && !/^[*]+$/.test(dataObject.query[this.searchAttr])){ + this._announceOption(nodes[1]); // 1st real item + } + }, + + _showResultList: function(){ + // summary: + // Display the drop down if not already displayed, or if it is displayed, then + // reposition it if necessary (reposition may be necessary if drop down's height changed). + + this.closeDropDown(true); + + // hide the tooltip + this.displayMessage(""); + + this.openDropDown(); + + dijit.setWaiState(this.domNode, "expanded", "true"); + }, + + loadDropDown: function(/*Function*/ callback){ + // Overrides _HasDropDown.loadDropDown(). + // This is called when user has pressed button icon or pressed the down arrow key + // to open the drop down. + + this._startSearchAll(); + }, + + isLoaded: function(){ + // signal to _HasDropDown that it needs to call loadDropDown() to load the + // drop down asynchronously before displaying it + return false; + }, + + closeDropDown: function(){ + // Overrides _HasDropDown.closeDropDown(). Closes the drop down (assuming that it's open). + // This method is the callback when the user types ESC or clicking + // the button icon while the drop down is open. It's also called by other code. + this._abortQuery(); + if(this._opened){ + this.inherited(arguments); + dijit.setWaiState(this.domNode, "expanded", "false"); + dijit.removeWaiState(this.focusNode,"activedescendant"); + } + }, + + _setBlurValue: function(){ + // if the user clicks away from the textbox OR tabs away, set the + // value to the textbox value + // #4617: + // if value is now more choices or previous choices, revert + // the value + var newvalue = this.get('displayedValue'); + var pw = this.dropDown; + if(pw && ( + newvalue == pw._messages["previousMessage"] || + newvalue == pw._messages["nextMessage"] + ) + ){ + this._setValueAttr(this._lastValueReported, true); + }else if(typeof this.item == "undefined"){ + // Update 'value' (ex: KY) according to currently displayed text + this.item = null; + this.set('displayedValue', newvalue); + }else{ + if(this.value != this._lastValueReported){ + dijit.form._FormValueWidget.prototype._setValueAttr.call(this, this.value, true); + } + this._refreshState(); + } + }, + + _onBlur: function(){ + // summary: + // Called magically when focus has shifted away from this widget and it's drop down + this.closeDropDown(); + this.inherited(arguments); + }, + + _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){ + // summary: + // Set the displayed valued in the input box, and the hidden value + // that gets submitted, based on a dojo.data store item. + // description: + // Users shouldn't call this function; they should be calling + // set('item', value) + // tags: + // private + if(!displayedValue){ + displayedValue = this.store.getValue(item, this.searchAttr); + } + var value = this._getValueField() != this.searchAttr? this.store.getIdentity(item) : displayedValue; + this._set("item", item); + dijit.form.ComboBox.superclass._setValueAttr.call(this, value, priorityChange, displayedValue); + }, + + _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){ + return; + } + // pull the text value from the item attached to the DOM node + var newValue; + if(node == this.dropDown.nextButton || + node == this.dropDown.previousButton){ + newValue = node.innerHTML; + this.item = undefined; + this.value = ''; + }else{ + newValue = this.store.getValue(node.item, this.searchAttr).toString(); + this.set('item', node.item, false, newValue); + } + // get the text that the user manually entered (cut off autocompleted text) + this.focusNode.value = this.focusNode.value.substring(0, this._lastInput.length); + // set up ARIA activedescendant + dijit.setWaiState(this.focusNode, "activedescendant", dojo.attr(node, "id")); + // autocomplete the rest of the option to announce change + this._autoCompleteText(newValue); + }, + + _selectOption: function(/*Event*/ evt){ + // summary: + // Menu callback function, called when an item in the menu is selected. + if(evt){ + this._announceOption(evt.target); + } + this.closeDropDown(); + this._setCaretPos(this.focusNode, this.focusNode.value.length); + dijit.form._FormValueWidget.prototype._setValueAttr.call(this, this.value, true); // set this.value and fire onChange + }, + + _startSearchAll: function(){ + this._startSearch(''); + }, + + _startSearchFromInput: function(){ + this._startSearch(this.focusNode.value.replace(/([\\\*\?])/g, "\\$1")); + }, + + _getQueryString: function(/*String*/ text){ + return dojo.string.substitute(this.queryExpr, [text]); + }, + + _startSearch: function(/*String*/ key){ + // summary: + // Starts a search for elements matching key (key=="" means to return all items), + // and calls _openResultList() when the search completes, to display the results. + if(!this.dropDown){ + var popupId = this.id + "_popup", + dropDownConstructor = dojo.getObject(this.dropDownClass, false); + this.dropDown = new dropDownConstructor({ + onChange: dojo.hitch(this, this._selectOption), + id: popupId, + dir: this.dir + }); + dijit.removeWaiState(this.focusNode,"activedescendant"); + dijit.setWaiState(this.textbox,"owns",popupId); // associate popup with textbox + } + // create a new query to prevent accidentally querying for a hidden + // value from FilteringSelect's keyField + var query = dojo.clone(this.query); // #5970 + this._lastInput = key; // Store exactly what was entered by the user. + this._lastQuery = query[this.searchAttr] = this._getQueryString(key); + // #5970: set _lastQuery, *then* start the timeout + // otherwise, if the user types and the last query returns before the timeout, + // _lastQuery won't be set and their input gets rewritten + this.searchTimer=setTimeout(dojo.hitch(this, function(query, _this){ + this.searchTimer = null; + var fetch = { + queryOptions: { + ignoreCase: this.ignoreCase, + deep: true + }, + query: query, + onBegin: dojo.hitch(this, "_setMaxOptions"), + onComplete: dojo.hitch(this, "_openResultList"), + onError: function(errText){ + _this._fetchHandle = null; + console.error('dijit.form.ComboBox: ' + errText); + _this.closeDropDown(); + }, + start: 0, + count: this.pageSize + }; + dojo.mixin(fetch, _this.fetchProperties); + this._fetchHandle = _this.store.fetch(fetch); + + var nextSearch = function(dataObject, direction){ + dataObject.start += dataObject.count*direction; + // #4091: + // tell callback the direction of the paging so the screen + // reader knows which menu option to shout + dataObject.direction = direction; + this._fetchHandle = this.store.fetch(dataObject); + this.focus(); + }; + this._nextSearch = this.dropDown.onPage = dojo.hitch(this, nextSearch, this._fetchHandle); + }, query, this), this.searchDelay); + }, + + _setMaxOptions: function(size, request){ + this._maxOptions = size; + }, + + _getValueField: function(){ + // summary: + // Helper for postMixInProperties() to set this.value based on data inlined into the markup. + // Returns the attribute name in the item (in dijit.form._ComboBoxDataStore) to use as the value. + return this.searchAttr; + }, + + //////////// INITIALIZATION METHODS /////////////////////////////////////// + + constructor: function(){ + this.query={}; + this.fetchProperties={}; + }, + + postMixInProperties: function(){ + if(!this.store){ + var srcNodeRef = this.srcNodeRef; + + // if user didn't specify store, then assume there are option tags + this.store = new dijit.form._ComboBoxDataStore(srcNodeRef); + + // 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 + + // Firefox and Safari set value + // IE6 and Opera set selectedIndex, which is automatically set + // by the selected attribute of an option tag + // IE6 does not set value, Opera sets value = selectedIndex + if(!("value" in this.params)){ + var item = (this.item = this.store.fetchSelectedItem()); + if(item){ + var valueField = this._getValueField(); + this.value = this.store.getValue(item, valueField); + } + } + } + + this.inherited(arguments); + }, + + postCreate: function(){ + // summary: + // Subclasses must call this method from their postCreate() methods + // tags: + // protected + + // find any associated label element and add to ComboBox node. + var label=dojo.query('label[for="'+this.id+'"]'); + if(label.length){ + label[0].id = (this.id+"_label"); + dijit.setWaiState(this.domNode, "labelledby", label[0].id); + + } + this.inherited(arguments); + }, + + _setHasDownArrowAttr: function(val){ + this.hasDownArrow = val; + this._buttonNode.style.display = val ? "" : "none"; + }, + + _getMenuLabelFromItem: function(/*Item*/ item){ + var label = this.labelFunc(item, this.store), + labelType = this.labelType; + // If labelType is not "text" we don't want to screw any markup ot whatever. + if(this.highlightMatch != "none" && this.labelType == "text" && this._lastInput){ + label = this.doHighlight(label, this._escapeHtml(this._lastInput)); + labelType = "html"; + } + return {html: labelType == "html", label: label}; + }, + + doHighlight: function(/*String*/ label, /*String*/ find){ + // summary: + // Highlights the string entered by the user in the menu. By default this + // highlights the first occurrence found. Override this method + // to implement your custom highlighting. + // tags: + // protected + + var + // Add (g)lobal modifier when this.highlightMatch == "all" and (i)gnorecase when this.ignoreCase == true + modifiers = (this.ignoreCase ? "i" : "") + (this.highlightMatch == "all" ? "g" : ""), + i = this.queryExpr.indexOf("${0}"); + find = dojo.regexp.escapeString(find); // escape regexp special chars + return this._escapeHtml(label).replace( + // prepend ^ when this.queryExpr == "${0}*" and append $ when this.queryExpr == "*${0}" + new RegExp((i == 0 ? "^" : "") + "("+ find +")" + (i == (this.queryExpr.length - 4) ? "$" : ""), modifiers), + '<span class="dijitComboBoxHighlightMatch">$1</span>' + ); // returns String, (almost) valid HTML (entities encoded) + }, + + _escapeHtml: function(/*String*/ str){ + // TODO Should become dojo.html.entities(), when exists use instead + // summary: + // Adds escape sequences for special characters in XML: &<>"' + str = String(str).replace(/&/gm, "&").replace(/</gm, "<") + .replace(/>/gm, ">").replace(/"/gm, """); + return str; // string + }, + + reset: function(){ + // Overrides the _FormWidget.reset(). + // Additionally reset the .item (to clean up). + this.item = null; + this.inherited(arguments); + }, + + labelFunc: function(/*item*/ item, /*dojo.data.store*/ store){ + // summary: + // Computes the label to display based on the dojo.data store item. + // returns: + // The label that the ComboBox should display + // tags: + // private + + // Use toString() because XMLStore returns an XMLItem whereas this + // method is expected to return a String (#9354) + return store.getValue(item, this.labelAttr || this.searchAttr).toString(); // String + } + } +); + +dojo.declare( + "dijit.form._ComboBoxMenu", + [dijit._Widget, dijit._Templated, dijit._CssStateMixin], + { + // summary: + // Focus-less menu for internal use in `dijit.form.ComboBox` + // tags: + // private + + templateString: "<ul class='dijitReset dijitMenu' dojoAttachEvent='onmousedown:_onMouseDown,onmouseup:_onMouseUp,onmouseover:_onMouseOver,onmouseout:_onMouseOut' style='overflow: \"auto\"; overflow-x: \"hidden\";'>" + +"<li class='dijitMenuItem dijitMenuPreviousButton' dojoAttachPoint='previousButton' role='option'></li>" + +"<li class='dijitMenuItem dijitMenuNextButton' dojoAttachPoint='nextButton' role='option'></li>" + +"</ul>", + + // _messages: Object + // Holds "next" and "previous" text for paging buttons on drop down + _messages: null, + + baseClass: "dijitComboBoxMenu", + + postMixInProperties: function(){ + this.inherited(arguments); + this._messages = dojo.i18n.getLocalization("dijit.form", "ComboBox", this.lang); + }, + + buildRendering: function(){ + this.inherited(arguments); + + // fill in template with i18n messages + this.previousButton.innerHTML = this._messages["previousMessage"]; + this.nextButton.innerHTML = this._messages["nextMessage"]; + }, + + _setValueAttr: function(/*Object*/ value){ + this.value = value; + this.onChange(value); + }, + + // stubs + onChange: function(/*Object*/ value){ + // summary: + // Notifies ComboBox/FilteringSelect that user clicked an option in the drop down menu. + // Probably should be called onSelect. + // tags: + // callback + }, + onPage: function(/*Number*/ direction){ + // summary: + // Notifies ComboBox/FilteringSelect that user clicked to advance to next/previous page. + // tags: + // callback + }, + + onClose: function(){ + // summary: + // Callback from dijit.popup code to this widget, notifying it that it closed + // tags: + // private + this._blurOptionNode(); + }, + + _createOption: function(/*Object*/ item, labelFunc){ + // summary: + // Creates an option to appear on the popup menu subclassed by + // `dijit.form.FilteringSelect`. + + var menuitem = dojo.create("li", { + "class": "dijitReset dijitMenuItem" +(this.isLeftToRight() ? "" : " dijitMenuItemRtl"), + role: "option" + }); + var labelObject = labelFunc(item); + if(labelObject.html){ + menuitem.innerHTML = labelObject.label; + }else{ + menuitem.appendChild( + dojo.doc.createTextNode(labelObject.label) + ); + } + // #3250: in blank options, assign a normal height + if(menuitem.innerHTML == ""){ + menuitem.innerHTML = " "; + } + menuitem.item=item; + return menuitem; + }, + + createOptions: function(results, dataObject, labelFunc){ + // summary: + // Fills in the items in the drop down list + // results: + // Array of dojo.data items + // dataObject: + // dojo.data store + // labelFunc: + // Function to produce a label in the drop down list from a dojo.data item + + //this._dataObject=dataObject; + //this._dataObject.onComplete=dojo.hitch(comboBox, comboBox._openResultList); + // display "Previous . . ." button + this.previousButton.style.display = (dataObject.start == 0) ? "none" : ""; + dojo.attr(this.previousButton, "id", this.id + "_prev"); + // create options using _createOption function defined by parent + // ComboBox (or FilteringSelect) class + // #2309: + // iterate over cache nondestructively + dojo.forEach(results, function(item, i){ + var menuitem = this._createOption(item, labelFunc); + dojo.attr(menuitem, "id", this.id + i); + this.domNode.insertBefore(menuitem, this.nextButton); + }, this); + // display "Next . . ." button + var displayMore = false; + //Try to determine if we should show 'more'... + if(dataObject._maxOptions && dataObject._maxOptions != -1){ + if((dataObject.start + dataObject.count) < dataObject._maxOptions){ + displayMore = true; + }else if((dataObject.start + dataObject.count) > dataObject._maxOptions && dataObject.count == results.length){ + //Weird return from a datastore, where a start + count > maxOptions + // implies maxOptions isn't really valid and we have to go into faking it. + //And more or less assume more if count == results.length + displayMore = true; + } + }else if(dataObject.count == results.length){ + //Don't know the size, so we do the best we can based off count alone. + //So, if we have an exact match to count, assume more. + displayMore = true; + } + + this.nextButton.style.display = displayMore ? "" : "none"; + dojo.attr(this.nextButton,"id", this.id + "_next"); + return this.domNode.childNodes; + }, + + clearResultList: function(){ + // summary: + // Clears the entries in the drop down list, but of course keeps the previous and next buttons. + while(this.domNode.childNodes.length>2){ + this.domNode.removeChild(this.domNode.childNodes[this.domNode.childNodes.length-2]); + } + this._blurOptionNode(); + }, + + _onMouseDown: function(/*Event*/ evt){ + dojo.stopEvent(evt); + }, + + _onMouseUp: function(/*Event*/ evt){ + if(evt.target === this.domNode || !this._highlighted_option){ + // !this._highlighted_option check to prevent immediate selection when menu appears on top + // of <input>, see #9898. Note that _HasDropDown also has code to prevent this. + return; + }else if(evt.target == this.previousButton){ + this._blurOptionNode(); + this.onPage(-1); + }else if(evt.target == this.nextButton){ + this._blurOptionNode(); + this.onPage(1); + }else{ + var tgt = evt.target; + // while the clicked node is inside the div + while(!tgt.item){ + // recurse to the top + tgt = tgt.parentNode; + } + this._setValueAttr({ target: tgt }, true); + } + }, + + _onMouseOver: function(/*Event*/ evt){ + if(evt.target === this.domNode){ return; } + var tgt = evt.target; + if(!(tgt == this.previousButton || tgt == this.nextButton)){ + // while the clicked node is inside the div + while(!tgt.item){ + // recurse to the top + tgt = tgt.parentNode; + } + } + this._focusOptionNode(tgt); + }, + + _onMouseOut: function(/*Event*/ evt){ + if(evt.target === this.domNode){ return; } + this._blurOptionNode(); + }, + + _focusOptionNode: function(/*DomNode*/ node){ + // summary: + // Does the actual highlight. + if(this._highlighted_option != node){ + this._blurOptionNode(); + this._highlighted_option = node; + dojo.addClass(this._highlighted_option, "dijitMenuItemSelected"); + } + }, + + _blurOptionNode: function(){ + // summary: + // Removes highlight on highlighted option. + if(this._highlighted_option){ + dojo.removeClass(this._highlighted_option, "dijitMenuItemSelected"); + this._highlighted_option = null; + } + }, + + _highlightNextOption: function(){ + // summary: + // Highlight the item just below the current selection. + // If nothing selected, highlight first option. + + // because each press of a button clears the menu, + // the highlighted option sometimes becomes detached from the menu! + // test to see if the option has a parent to see if this is the case. + if(!this.getHighlightedOption()){ + var fc = this.domNode.firstChild; + this._focusOptionNode(fc.style.display == "none" ? fc.nextSibling : fc); + }else{ + var ns = this._highlighted_option.nextSibling; + if(ns && ns.style.display != "none"){ + this._focusOptionNode(ns); + }else{ + this.highlightFirstOption(); + } + } + // scrollIntoView is called outside of _focusOptionNode because in IE putting it inside causes the menu to scroll up on mouseover + dojo.window.scrollIntoView(this._highlighted_option); + }, + + highlightFirstOption: function(){ + // summary: + // Highlight the first real item in the list (not Previous Choices). + var first = this.domNode.firstChild; + var second = first.nextSibling; + this._focusOptionNode(second.style.display == "none" ? first : second); // remotely possible that Previous Choices is the only thing in the list + dojo.window.scrollIntoView(this._highlighted_option); + }, + + highlightLastOption: function(){ + // summary: + // Highlight the last real item in the list (not More Choices). + this._focusOptionNode(this.domNode.lastChild.previousSibling); + dojo.window.scrollIntoView(this._highlighted_option); + }, + + _highlightPrevOption: function(){ + // summary: + // Highlight the item just above the current selection. + // If nothing selected, highlight last option (if + // you select Previous and try to keep scrolling up the list). + if(!this.getHighlightedOption()){ + var lc = this.domNode.lastChild; + this._focusOptionNode(lc.style.display == "none" ? lc.previousSibling : lc); + }else{ + var ps = this._highlighted_option.previousSibling; + if(ps && ps.style.display != "none"){ + this._focusOptionNode(ps); + }else{ + this.highlightLastOption(); + } + } + dojo.window.scrollIntoView(this._highlighted_option); + }, + + _page: function(/*Boolean*/ up){ + // summary: + // Handles page-up and page-down keypresses + + var scrollamount = 0; + var oldscroll = this.domNode.scrollTop; + var height = dojo.style(this.domNode, "height"); + // if no item is highlighted, highlight the first option + if(!this.getHighlightedOption()){ + this._highlightNextOption(); + } + while(scrollamount<height){ + if(up){ + // stop at option 1 + if(!this.getHighlightedOption().previousSibling || + this._highlighted_option.previousSibling.style.display == "none"){ + break; + } + this._highlightPrevOption(); + }else{ + // stop at last option + if(!this.getHighlightedOption().nextSibling || + this._highlighted_option.nextSibling.style.display == "none"){ + break; + } + this._highlightNextOption(); + } + // going backwards + var newscroll=this.domNode.scrollTop; + scrollamount+=(newscroll-oldscroll)*(up ? -1:1); + oldscroll=newscroll; + } + }, + + pageUp: function(){ + // summary: + // Handles pageup keypress. + // TODO: just call _page directly from handleKey(). + // tags: + // private + this._page(true); + }, + + pageDown: function(){ + // summary: + // Handles pagedown keypress. + // TODO: just call _page directly from handleKey(). + // tags: + // private + this._page(false); + }, + + getHighlightedOption: function(){ + // summary: + // Returns the highlighted option. + var ho = this._highlighted_option; + return (ho && ho.parentNode) ? ho : null; + }, + + handleKey: function(evt){ + // summary: + // Handle keystroke event forwarded from ComboBox, returning false if it's + // a keystroke I recognize and process, true otherwise. + switch(evt.charOrCode){ + case dojo.keys.DOWN_ARROW: + this._highlightNextOption(); + return false; + case dojo.keys.PAGE_DOWN: + this.pageDown(); + return false; + case dojo.keys.UP_ARROW: + this._highlightPrevOption(); + return false; + case dojo.keys.PAGE_UP: + this.pageUp(); + return false; + default: + return true; + } + } + } +); + +dojo.declare( + "dijit.form.ComboBox", + [dijit.form.ValidationTextBox, dijit.form.ComboBoxMixin], + { + // summary: + // Auto-completing text box, and base class for dijit.form.FilteringSelect. + // + // description: + // 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. + // If OPTION tags are used as the data provider via markup, + // then the OPTION tag's child text node is used as the widget value + // when selected. The OPTION tag's value attribute is ignored. + // To set the default value when using OPTION tags, specify the selected + // attribute on 1 of the child OPTION tags. + // + // Some of the options to the ComboBox are actually arguments to the data + // provider. + + _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){ + // summary: + // Hook so set('value', value) works. + // description: + // Sets the value of the select. + this._set("item", null); // value not looked up in store + if(!value){ value = ''; } // null translates to blank + dijit.form.ValidationTextBox.prototype._setValueAttr.call(this, value, priorityChange, displayedValue); + } + } +); + +dojo.declare("dijit.form._ComboBoxDataStore", null, { + // summary: + // Inefficient but small data store specialized for inlined `dijit.form.ComboBox` data + // + // description: + // Provides a store for inlined data like: + // + // | <select> + // | <option value="AL">Alabama</option> + // | ... + // + // Actually. just implements the subset of dojo.data.Read/Notification + // needed for ComboBox and FilteringSelect to work. + // + // Note that an item is just a pointer to the <option> DomNode. + + constructor: function( /*DomNode*/ root){ + this.root = root; + if(root.tagName != "SELECT" && root.firstChild){ + root = dojo.query("select", root); + if(root.length > 0){ // SELECT is a child of srcNodeRef + root = root[0]; + }else{ // no select, so create 1 to parent the option tags to define selectedIndex + this.root.innerHTML = "<SELECT>"+this.root.innerHTML+"</SELECT>"; + root = this.root.firstChild; + } + this.root = root; + } + dojo.query("> option", root).forEach(function(node){ + // TODO: this was added in #3858 but unclear why/if it's needed; doesn't seem to be. + // If it is needed then can we just hide the select itself instead? + //node.style.display="none"; + node.innerHTML = dojo.trim(node.innerHTML); + }); + + }, + + getValue: function( /*item*/ item, + /*attribute-name-string*/ attribute, + /*value?*/ defaultValue){ + return (attribute == "value") ? item.value : (item.innerText || item.textContent || ''); + }, + + isItemLoaded: function(/*anything*/ something){ + return true; + }, + + getFeatures: function(){ + return {"dojo.data.api.Read": true, "dojo.data.api.Identity": true}; + }, + + _fetchItems: function( /*Object*/ args, + /*Function*/ findCallback, + /*Function*/ errorCallback){ + // summary: + // See dojo.data.util.simpleFetch.fetch() + if(!args.query){ args.query = {}; } + if(!args.query.name){ args.query.name = ""; } + if(!args.queryOptions){ args.queryOptions = {}; } + var matcher = dojo.data.util.filter.patternToRegExp(args.query.name, args.queryOptions.ignoreCase), + items = dojo.query("> option", this.root).filter(function(option){ + return (option.innerText || option.textContent || '').match(matcher); + } ); + if(args.sort){ + items.sort(dojo.data.util.sorter.createSortFunction(args.sort, this)); + } + findCallback(items, args); + }, + + close: function(/*dojo.data.api.Request || args || null*/ request){ + return; + }, + + getLabel: function(/*item*/ item){ + return item.innerHTML; + }, + + getIdentity: function(/*item*/ item){ + return dojo.attr(item, "value"); + }, + + fetchItemByIdentity: function(/*Object*/ args){ + // summary: + // Given the identity of an item, this method returns the item that has + // that identity through the onItem callback. + // Refer to dojo.data.api.Identity.fetchItemByIdentity() for more details. + // + // description: + // Given arguments like: + // + // | {identity: "CA", onItem: function(item){...} + // + // Call `onItem()` with the DOM node `<option value="CA">California</option>` + var item = dojo.query("> option[value='" + args.identity + "']", this.root)[0]; + args.onItem(item); + }, + + fetchSelectedItem: function(){ + // summary: + // Get the option marked as selected, like `<option selected>`. + // Not part of dojo.data API. + var root = this.root, + si = root.selectedIndex; + return typeof si == "number" + ? dojo.query("> option:nth-child(" + (si != -1 ? si+1 : 1) + ")", root)[0] + : null; // dojo.data.Item + } }); -if(_53.sort){ -_57.sort(dojo.data.util.sorter.createSortFunction(_53.sort,this)); -} -_54(_57,_53); -},close:function(_59){ -return; -},getLabel:function(_5a){ -return _5a.innerHTML; -},getIdentity:function(_5b){ -return dojo.attr(_5b,"value"); -},fetchItemByIdentity:function(_5c){ -var _5d=dojo.query("> option[value='"+_5c.identity+"']",this.root)[0]; -_5c.onItem(_5d); -},fetchSelectedItem:function(){ -var _5e=this.root,si=_5e.selectedIndex; -return typeof si=="number"?dojo.query("> option:nth-child("+(si!=-1?si+1:1)+")",_5e)[0]:null; -}}); +//Mix in the simple fetch implementation to this class. dojo.extend(dijit.form._ComboBoxDataStore,dojo.data.util.simpleFetch); + } |