\n",
+
+ _onChangeMonitor: '',
+ // TODO: set button's title to this.containerNode.innerText
+
+ _onClick: function(/*Event*/ e){
+ // summary: internal function to handle click actions
+ if(this.disabled || this.readOnly){
+ dojo.stopEvent(e); // needed for checkbox
+ return false;
+ }
+ this._clicked(); // widget click actions
+ return this.onClick(e); // user click actions
+ },
+
+ _onButtonClick: function(/*Event*/ e){
+ // summary: callback when the user mouse clicks the button portion
+ if(this._onClick(e) === false){ // returning nothing is same as true
+ dojo.stopEvent(e);
+ }else if(this.type=="submit" && !this.focusNode.form){ // see if a nonform widget needs to be signalled
+ for(var node=this.domNode; node.parentNode/*#5935*/; node=node.parentNode){
+ var widget=dijit.byNode(node);
+ if(widget && typeof widget._onSubmit == "function"){
+ widget._onSubmit(e);
+ break;
+ }
+ }
+ }
+ },
+
+ postCreate: function(){
+ // summary:
+ // get label and set as title on button icon if necessary
+ if (this.showLabel == false){
+ var labelText = "";
+ this.label = this.containerNode.innerHTML;
+ labelText = dojo.trim(this.containerNode.innerText || this.containerNode.textContent || '');
+ // set title attrib on iconNode
+ this.titleNode.title=labelText;
+ dojo.addClass(this.containerNode,"dijitDisplayNone");
+ }
+ dojo.setSelectable(this.focusNode, false);
+ this.inherited(arguments);
+ },
+
+ onClick: function(/*Event*/ e){
+ // summary: user callback for when button is clicked
+ // if type="submit", return true to perform submit
+ return true;
+ },
+
+ _clicked: function(/*Event*/ e){
+ // summary: internal replaceable function for when the button is clicked
+ },
+
+ setLabel: function(/*String*/ content){
+ // summary: reset the label (text) of the button; takes an HTML string
+ this.containerNode.innerHTML = this.label = content;
+ this._layoutHack();
+ if (this.showLabel == false){
+ this.titleNode.title=dojo.trim(this.containerNode.innerText || this.containerNode.textContent || '');
+ }
+ }
+});
+
+
+dojo.declare("dijit.form.DropDownButton", [dijit.form.Button, dijit._Container], {
+ // summary: A button with a popup
+ //
+ // example:
+ // |
+ //
+ // example:
+ // | var button1 = new dijit.form.DropDownButton({ label: "hi", dropDown: new dijit.Menu(...) });
+ // | dojo.body().appendChild(button1);
+ //
+
+ baseClass : "dijitDropDownButton",
+
+ templateString:"
\n",
+
+ _fillContent: function(){
+ // my inner HTML contains both the button contents and a drop down widget, like
+ // push me
+ // The first node is assumed to be the button content. The widget is the popup.
+ if(this.srcNodeRef){ // programatically created buttons might not define srcNodeRef
+ //FIXME: figure out how to filter out the widget and use all remaining nodes as button
+ // content, not just nodes[0]
+ var nodes = dojo.query("*", this.srcNodeRef);
+ dijit.form.DropDownButton.superclass._fillContent.call(this, nodes[0]);
+
+ // save pointer to srcNode so we can grab the drop down widget after it's instantiated
+ this.dropDownContainer = this.srcNodeRef;
+ }
+ },
+
+ startup: function(){
+ if(this._started){ return; }
+
+ // the child widget from srcNodeRef is the dropdown widget. Insert it in the page DOM,
+ // make it invisible, and store a reference to pass to the popup code.
+ if(!this.dropDown){
+ var dropDownNode = dojo.query("[widgetId]", this.dropDownContainer)[0];
+ this.dropDown = dijit.byNode(dropDownNode);
+ delete this.dropDownContainer;
+ }
+ dijit.popup.prepare(this.dropDown.domNode);
+
+ this.inherited(arguments);
+ },
+
+ destroyDescendants: function(){
+ if(this.dropDown){
+ this.dropDown.destroyRecursive();
+ delete this.dropDown;
+ }
+ this.inherited(arguments);
+ },
+
+ _onArrowClick: function(/*Event*/ e){
+ // summary: callback when the user mouse clicks on menu popup node
+ if(this.disabled || this.readOnly){ return; }
+ this._toggleDropDown();
+ },
+
+ _onDropDownClick: function(/*Event*/ e){
+ // on Firefox 2 on the Mac it is possible to fire onclick
+ // by pressing enter down on a second element and transferring
+ // focus to the DropDownButton;
+ // we want to prevent opening our menu in this situation
+ // and only do so if we have seen a keydown on this button;
+ // e.detail != 0 means that we were fired by mouse
+ var isMacFFlessThan3 = dojo.isFF && dojo.isFF < 3
+ && navigator.appVersion.indexOf("Macintosh") != -1;
+ if(!isMacFFlessThan3 || e.detail != 0 || this._seenKeydown){
+ this._onArrowClick(e);
+ }
+ this._seenKeydown = false;
+ },
+
+ _onDropDownKeydown: function(/*Event*/ e){
+ this._seenKeydown = true;
+ },
+
+ _onDropDownBlur: function(/*Event*/ e){
+ this._seenKeydown = false;
+ },
+
+ _onKey: function(/*Event*/ e){
+ // summary: callback when the user presses a key on menu popup node
+ if(this.disabled || this.readOnly){ return; }
+ if(e.keyCode == dojo.keys.DOWN_ARROW){
+ if(!this.dropDown || this.dropDown.domNode.style.visibility=="hidden"){
+ dojo.stopEvent(e);
+ this._toggleDropDown();
+ }
+ }
+ },
+
+ _onBlur: function(){
+ // summary: called magically when focus has shifted away from this widget and it's dropdown
+ this._closeDropDown();
+ // don't focus on button. the user has explicitly focused on something else.
+ this.inherited(arguments);
+ },
+
+ _toggleDropDown: function(){
+ // summary: toggle the drop-down widget; if it is up, close it, if not, open it
+ if(this.disabled || this.readOnly){ return; }
+ dijit.focus(this.popupStateNode);
+ var dropDown = this.dropDown;
+ if(!dropDown){ return; }
+ if(!this._opened){
+ // If there's an href, then load that first, so we don't get a flicker
+ if(dropDown.href && !dropDown.isLoaded){
+ var self = this;
+ var handler = dojo.connect(dropDown, "onLoad", function(){
+ dojo.disconnect(handler);
+ self._openDropDown();
+ });
+ dropDown._loadCheck(true);
+ return;
+ }else{
+ this._openDropDown();
+ }
+ }else{
+ this._closeDropDown();
+ }
+ },
+
+ _openDropDown: function(){
+ var dropDown = this.dropDown;
+ var oldWidth=dropDown.domNode.style.width;
+ var self = this;
+
+ dijit.popup.open({
+ parent: this,
+ popup: dropDown,
+ around: this.domNode,
+ orient:
+ // TODO: add user-defined positioning option, like in Tooltip.js
+ this.isLeftToRight() ? {'BL':'TL', 'BR':'TR', 'TL':'BL', 'TR':'BR'}
+ : {'BR':'TR', 'BL':'TL', 'TR':'BR', 'TL':'BL'},
+ onExecute: function(){
+ self._closeDropDown(true);
+ },
+ onCancel: function(){
+ self._closeDropDown(true);
+ },
+ onClose: function(){
+ dropDown.domNode.style.width = oldWidth;
+ self.popupStateNode.removeAttribute("popupActive");
+ this._opened = false;
+ }
+ });
+ if(this.domNode.offsetWidth > dropDown.domNode.offsetWidth){
+ var adjustNode = null;
+ if(!this.isLeftToRight()){
+ adjustNode = dropDown.domNode.parentNode;
+ var oldRight = adjustNode.offsetLeft + adjustNode.offsetWidth;
+ }
+ // make menu at least as wide as the button
+ dojo.marginBox(dropDown.domNode, {w: this.domNode.offsetWidth});
+ if(adjustNode){
+ adjustNode.style.left = oldRight - this.domNode.offsetWidth + "px";
+ }
+ }
+ this.popupStateNode.setAttribute("popupActive", "true");
+ this._opened=true;
+ if(dropDown.focus){
+ dropDown.focus();
+ }
+ // TODO: set this.checked and call setStateClass(), to affect button look while drop down is shown
+ },
+
+ _closeDropDown: function(/*Boolean*/ focus){
+ if(this._opened){
+ dijit.popup.close(this.dropDown);
+ if(focus){ this.focus(); }
+ this._opened = false;
+ }
+ }
+});
+
+dojo.declare("dijit.form.ComboButton", dijit.form.DropDownButton, {
+ // summary: A Normal Button with a DropDown
+ //
+ // example:
+ // |
+ //
+ // example:
+ // | var button1 = new dijit.form.ComboButton({label: "hello world", onClick: foo, dropDown: "myMenu"});
+ // | dojo.body().appendChild(button1.domNode);
+ //
+
+ templateString:"
${label}
▼
\n",
+
+ attributeMap: dojo.mixin(dojo.clone(dijit.form._FormWidget.prototype.attributeMap),
+ {id:"", name:""}),
+
+ // optionsTitle: String
+ // text that describes the options menu (accessibility)
+ optionsTitle: "",
+
+ baseClass: "dijitComboButton",
+
+ _focusedNode: null,
+
+ postCreate: function(){
+ this.inherited(arguments);
+ this._focalNodes = [this.titleNode, this.popupStateNode];
+ dojo.forEach(this._focalNodes, dojo.hitch(this, function(node){
+ if(dojo.isIE){
+ this.connect(node, "onactivate", this._onNodeFocus);
+ this.connect(node, "ondeactivate", this._onNodeBlur);
+ }else{
+ this.connect(node, "onfocus", this._onNodeFocus);
+ this.connect(node, "onblur", this._onNodeBlur);
+ }
+ }));
+ },
+
+ focusFocalNode: function(node){
+ // summary: Focus the focal node node.
+ this._focusedNode = node;
+ dijit.focus(node);
+ },
+
+ hasNextFocalNode: function(){
+ // summary: Returns true if this widget has no node currently
+ // focused or if there is a node following the focused one.
+ // False is returned if the last node has focus.
+ return this._focusedNode !== this.getFocalNodes()[1];
+ },
+
+ focusNext: function(){
+ // summary: Focus the focal node following the current node with focus
+ // or the first one if no node currently has focus.
+ this._focusedNode = this.getFocalNodes()[this._focusedNode ? 1 : 0];
+ dijit.focus(this._focusedNode);
+ },
+
+ hasPrevFocalNode: function(){
+ // summary: Returns true if this widget has no node currently
+ // focused or if there is a node before the focused one.
+ // False is returned if the first node has focus.
+ return this._focusedNode !== this.getFocalNodes()[0];
+ },
+
+ focusPrev: function(){
+ // summary: Focus the focal node before the current node with focus
+ // or the last one if no node currently has focus.
+ this._focusedNode = this.getFocalNodes()[this._focusedNode ? 0 : 1];
+ dijit.focus(this._focusedNode);
+ },
+
+ getFocalNodes: function(){
+ // summary: Returns an array of focal nodes for this widget.
+ return this._focalNodes;
+ },
+
+ _onNodeFocus: function(evt){
+ this._focusedNode = evt.currentTarget;
+ var fnc = this._focusedNode == this.focusNode ? "dijitDownArrowButtonFocused" : "dijitButtonContentsFocused";
+ dojo.addClass(this._focusedNode, fnc);
+ },
+
+ _onNodeBlur: function(evt){
+ var fnc = evt.currentTarget == this.focusNode ? "dijitDownArrowButtonFocused" : "dijitButtonContentsFocused";
+ dojo.removeClass(evt.currentTarget, fnc);
+ },
+
+ _onBlur: function(){
+ this.inherited(arguments);
+ this._focusedNode = null;
+ }
+});
+
+dojo.declare("dijit.form.ToggleButton", dijit.form.Button, {
+ // summary:
+ // A button that can be in two states (checked or not).
+ // Can be base class for things like tabs or checkbox or radio buttons
+
+ baseClass: "dijitToggleButton",
+
+ // checked: Boolean
+ // Corresponds to the native HTML element's attribute.
+ // In markup, specified as "checked='checked'" or just "checked".
+ // True if the button is depressed, or the checkbox is checked,
+ // or the radio button is selected, etc.
+ checked: false,
+
+ _onChangeMonitor: 'checked',
+
+ attributeMap: dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap),
+ {checked:"focusNode"}),
+
+ _clicked: function(/*Event*/ evt){
+ this.setAttribute('checked', !this.checked);
+ },
+
+ setAttribute: function(/*String*/ attr, /*anything*/ value){
+ this.inherited(arguments);
+ switch(attr){
+ case "checked":
+ dijit.setWaiState(this.focusNode || this.domNode, "pressed", this.checked);
+ this._setStateClass();
+ this._handleOnChange(this.checked, true);
+ }
+ },
+
+
+ setChecked: function(/*Boolean*/ checked){
+ // summary:
+ // Programatically deselect the button
+ dojo.deprecated("setChecked("+checked+") is deprecated. Use setAttribute('checked',"+checked+") instead.", "", "2.0");
+ this.setAttribute('checked', checked);
+ },
+
+ postCreate: function(){
+ this.inherited(arguments);
+ this.setAttribute('checked', this.checked); //to initially set wai pressed state
+ }
+});
+
+}
diff --git a/includes/js/dijit/form/CheckBox.js b/includes/js/dijit/form/CheckBox.js
new file mode 100644
index 0000000..295a711
--- /dev/null
+++ b/includes/js/dijit/form/CheckBox.js
@@ -0,0 +1,133 @@
+if(!dojo._hasResource["dijit.form.CheckBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
+dojo._hasResource["dijit.form.CheckBox"] = true;
+dojo.provide("dijit.form.CheckBox");
+
+dojo.require("dijit.form.Button");
+
+dojo.declare(
+ "dijit.form.CheckBox",
+ dijit.form.ToggleButton,
+ {
+ // summary:
+ // Same as an HTML checkbox, but with fancy styling.
+ //
+ // description:
+ // User interacts with real html inputs.
+ // On onclick (which occurs by mouse click, space-bar, or
+ // using the arrow keys to switch the selected radio button),
+ // we update the state of the checkbox/radio.
+ //
+ // There are two modes:
+ // 1. High contrast mode
+ // 2. Normal mode
+ // In case 1, the regular html inputs are shown and used by the user.
+ // In case 2, the regular html inputs are invisible but still used by
+ // the user. They are turned quasi-invisible and overlay the background-image.
+
+ templateString:"\n",
+
+ baseClass: "dijitCheckBox",
+
+ // Value of "type" attribute for
+ type: "checkbox",
+
+ // value: Value
+ // equivalent to value field on normal checkbox (if checked, the value is passed as
+ // the value when form is submitted)
+ value: "on",
+
+ setValue: function(/*String or Boolean*/ newValue){
+ // summary:
+ // When passed a boolean, controls whether or not the CheckBox is checked.
+ // If passed a string, changes the value attribute of the CheckBox (the one
+ // specified as "value" when the CheckBox was constructed (ex: )
+ if(typeof newValue == "string"){
+ this.setAttribute('value', newValue);
+ newValue = true;
+ }
+ this.setAttribute('checked', newValue);
+ },
+
+ _getValueDeprecated: false, // remove when _FormWidget:_getValueDeprecated is removed
+ getValue: function(){
+ // summary:
+ // If the CheckBox is checked, returns the value attribute.
+ // Otherwise returns false.
+ return (this.checked ? this.value : false);
+ },
+
+ reset: function(){
+ this.inherited(arguments);
+ this.setAttribute('value', this._resetValueAttr);
+ },
+
+ postCreate: function(){
+ this.inherited(arguments);
+ this._resetValueAttr = this.value;
+ }
+ }
+);
+
+dojo.declare(
+ "dijit.form.RadioButton",
+ dijit.form.CheckBox,
+ {
+ // summary:
+ // Same as an HTML radio, but with fancy styling.
+ //
+ // description:
+ // Implementation details
+ //
+ // Specialization:
+ // We keep track of dijit radio groups so that we can update the state
+ // of all the siblings (the "context") in a group based on input
+ // events. We don't rely on browser radio grouping.
+
+ type: "radio",
+ baseClass: "dijitRadio",
+
+ // This shared object keeps track of all widgets, grouped by name
+ _groups: {},
+
+ postCreate: function(){
+ // add this widget to _groups
+ (this._groups[this.name] = this._groups[this.name] || []).push(this);
+
+ this.inherited(arguments);
+ },
+
+ uninitialize: function(){
+ // remove this widget from _groups
+ dojo.forEach(this._groups[this.name], function(widget, i, arr){
+ if(widget === this){
+ arr.splice(i, 1);
+ return;
+ }
+ }, this);
+ },
+
+ setAttribute: function(/*String*/ attr, /*anything*/ value){
+ // If I am being checked then have to deselect currently checked radio button
+ this.inherited(arguments);
+ switch(attr){
+ case "checked":
+ if(this.checked){
+ dojo.forEach(this._groups[this.name], function(widget){
+ if(widget != this && widget.checked){
+ widget.setAttribute('checked', false);
+ }
+ }, this);
+ }
+ }
+ },
+
+ _clicked: function(/*Event*/ e){
+ if(!this.checked){
+ this.setAttribute('checked', true);
+ }
+ }
+ }
+);
+
+}
diff --git a/includes/js/dijit/form/ComboBox.js b/includes/js/dijit/form/ComboBox.js
new file mode 100644
index 0000000..77a75f0
--- /dev/null
+++ b/includes/js/dijit/form/ComboBox.js
@@ -0,0 +1,1060 @@
+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("dijit.form.ValidationTextBox");
+dojo.requireLocalization("dijit.form", "ComboBox", null, "zh,ROOT,pt,da,tr,ru,de,sv,ja,he,fi,nb,el,ar,pt-pt,cs,fr,es,ko,nl,zh-tw,pl,it,hu");
+
+dojo.declare(
+ "dijit.form.ComboBoxMixin",
+ null,
+ {
+ // item: Object
+ // This is the item returned by the dojo.data.store implementation that
+ // provides the data for this cobobox, 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: 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.
+ // Any reference to the `searchAttr` is ignored.
+ 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",
+
+ // queryExpr: String
+ // 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 should ignore case when matching possible items
+ ignoreCase: true,
+
+ // hasDownArrow: Boolean
+ // Set this textbox to have a down arrow button.
+ // Defaults to true.
+ hasDownArrow:true,
+
+ templateString:"
▼
Χ
\n",
+
+ baseClass:"dijitComboBox",
+
+ _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 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);
+ 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);
+ },
+
+ _setAttribute: function(/*String*/ attr, /*anything*/ value){
+ // summary: additional code to set disablbed state of combobox node
+ if (attr == "disabled"){
+ dijit.setWaiState(this.comboNode, "disabled", value);
+ }
+ },
+
+ _onKeyPress: function(/*Event*/ evt){
+ // summary: handles keyboard events
+
+ //except for pasting case - ctrl + v(118)
+ if(evt.altKey || (evt.ctrlKey && evt.charCode != 118)){
+ return;
+ }
+ var doSearch = false;
+ var pw = this._popupWidget;
+ var dk = dojo.keys;
+ if(this._isShowingNow){
+ pw.handleKey(evt);
+ }
+ switch(evt.keyCode){
+ case dk.PAGE_DOWN:
+ case dk.DOWN_ARROW:
+ if(!this._isShowingNow||this._prev_key_esc){
+ this._arrowPressed();
+ doSearch=true;
+ }else{
+ this._announceOption(pw.getHighlightedOption());
+ }
+ dojo.stopEvent(evt);
+ this._prev_key_backspace = false;
+ this._prev_key_esc = false;
+ break;
+
+ case dk.PAGE_UP:
+ case dk.UP_ARROW:
+ if(this._isShowingNow){
+ this._announceOption(pw.getHighlightedOption());
+ }
+ dojo.stopEvent(evt);
+ this._prev_key_backspace = false;
+ this._prev_key_esc = false;
+ break;
+
+ case dk.ENTER:
+ // prevent submitting form if user presses enter. Also
+ // prevent accepting the value if either Next or Previous
+ // are selected
+ var highlighted;
+ if( this._isShowingNow &&
+ (highlighted = pw.getHighlightedOption())
+ ){
+ // 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{
+ this.setDisplayedValue(this.getDisplayedValue());
+ }
+ // default case:
+ // prevent submit, but allow event to bubble
+ evt.preventDefault();
+ // fall through
+
+ case dk.TAB:
+ var newvalue = this.getDisplayedValue();
+ // #4617:
+ // if the user had More Choices selected fall into the
+ // _onBlur handler
+ if(pw && (
+ newvalue == pw._messages["previousMessage"] ||
+ newvalue == pw._messages["nextMessage"])
+ ){
+ break;
+ }
+ if(this._isShowingNow){
+ this._prev_key_backspace = false;
+ this._prev_key_esc = false;
+ if(pw.getHighlightedOption()){
+ pw.setValue({ target: pw.getHighlightedOption() }, true);
+ }
+ this._hideResultList();
+ }
+ break;
+
+ case dk.SPACE:
+ this._prev_key_backspace = false;
+ this._prev_key_esc = false;
+ if(this._isShowingNow && pw.getHighlightedOption()){
+ dojo.stopEvent(evt);
+ this._selectOption();
+ this._hideResultList();
+ }else{
+ doSearch = true;
+ }
+ break;
+
+ case dk.ESCAPE:
+ this._prev_key_backspace = false;
+ this._prev_key_esc = true;
+ if(this._isShowingNow){
+ dojo.stopEvent(evt);
+ this._hideResultList();
+ }
+ this.inherited(arguments);
+ break;
+
+ case dk.DELETE:
+ case dk.BACKSPACE:
+ this._prev_key_esc = false;
+ this._prev_key_backspace = true;
+ doSearch = true;
+ break;
+
+ case dk.RIGHT_ARROW: // fall through
+ case dk.LEFT_ARROW:
+ 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(dojo.isIE || 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
+ 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){
+ if( this.disabled ||
+ this.readOnly ||
+ (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 &&
+ (dataObject.query[this.searchAttr] != "*")){
+ // 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.
+ this._autoCompleteText(zerothvalue);
+ }
+ this._popupWidget.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._popupWidget.highlightFirstOption();
+ }else if(-1 == dataObject.direction){
+ this._popupWidget.highlightLastOption();
+ }
+ this._announceOption(this._popupWidget.getHighlightedOption());
+ }
+ },
+
+ _showResultList: function(){
+ this._hideResultList();
+ var items = this._popupWidget.getItems(),
+ visibleCount = Math.min(items.length,this.maxListLength);
+ this._arrowPressed();
+ // hide the tooltip
+ this.displayMessage("");
+
+ // Position the list and if it's too big to fit on the screen then
+ // size it to the maximum possible height
+ // Our dear friend IE doesnt take max-height so we need to
+ // calculate that on our own every time
+
+ // TODO: want to redo this, see
+ // http://trac.dojotoolkit.org/ticket/3272
+ // and
+ // http://trac.dojotoolkit.org/ticket/4108
+
+ with(this._popupWidget.domNode.style){
+ // natural size of the list has changed, so erase old
+ // width/height settings, which were hardcoded in a previous
+ // call to this function (via dojo.marginBox() call)
+ width = "";
+ height = "";
+ }
+ var best = this.open();
+ // #3212:
+ // only set auto scroll bars if necessary prevents issues with
+ // scroll bars appearing when they shouldn't when node is made
+ // wider (fractional pixels cause this)
+ var popupbox = dojo.marginBox(this._popupWidget.domNode);
+ this._popupWidget.domNode.style.overflow =
+ ((best.h==popupbox.h)&&(best.w==popupbox.w)) ? "hidden" : "auto";
+ // #4134:
+ // borrow TextArea scrollbar test so content isn't covered by
+ // scrollbar and horizontal scrollbar doesn't appear
+ var newwidth = best.w;
+ if(best.h < this._popupWidget.domNode.scrollHeight){
+ newwidth += 16;
+ }
+ dojo.marginBox(this._popupWidget.domNode, {
+ h: best.h,
+ w: Math.max(newwidth, this.domNode.offsetWidth)
+ });
+ dijit.setWaiState(this.comboNode, "expanded", "true");
+ },
+
+ _hideResultList: function(){
+ if(this._isShowingNow){
+ dijit.popup.close(this._popupWidget);
+ this._arrowIdle();
+ this._isShowingNow=false;
+ dijit.setWaiState(this.comboNode, "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.getDisplayedValue();
+ var pw = this._popupWidget;
+ if(pw && (
+ newvalue == pw._messages["previousMessage"] ||
+ newvalue == pw._messages["nextMessage"]
+ )
+ ){
+ this.setValue(this._lastValueReported, true);
+ }else{
+ this.setDisplayedValue(newvalue);
+ }
+ },
+
+ _onBlur: function(){
+ // summary: called magically when focus has shifted away from this widget and it's dropdown
+ this._hideResultList();
+ this._arrowIdle();
+ this.inherited(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));
+ //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){
+ 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.item = tgt.item;
+ this.setValue(this.store.getValue(tgt.item, this.searchAttr), true);
+ },
+
+ _onArrowMouseDown: function(evt){
+ // summary: callback when arrow is clicked
+ if(this.disabled || this.readOnly){
+ return;
+ }
+ dojo.stopEvent(evt);
+ this.focus();
+ 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);
+ },
+
+ _getQueryString: function(/*String*/ text){
+ return dojo.string.substitute(this.queryExpr, [text]);
+ },
+
+ _startSearch: function(/*String*/ key){
+ if(!this._popupWidget){
+ var popupId = this.id + "_popup";
+ this._popupWidget = new dijit.form._ComboBoxMenu({
+ onChange: dojo.hitch(this, this._selectOption),
+ id:popupId
+ });
+ 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
+ this.item = null; // #4872
+ var query = dojo.clone(this.query); // #5970
+ 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){
+ var dataObject = this.store.fetch({
+ queryOptions: {
+ ignoreCase: this.ignoreCase,
+ deep: true
+ },
+ query: query,
+ onComplete: dojo.hitch(this, "_openResultList"),
+ onError: function(errText){
+ console.error('dijit.form.ComboBox: ' + errText);
+ dojo.hitch(_this, "_hideResultList")();
+ },
+ start:0,
+ count:this.pageSize
+ });
+
+ 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.store.fetch(dataObject);
+ }
+ this._nextSearch = this._popupWidget.onPage = dojo.hitch(this, nextSearch, dataObject);
+ }, query, this), this.searchDelay);
+ },
+
+ _getValueField:function(){
+ return this.searchAttr;
+ },
+
+ /////////////// Event handlers /////////////////////
+
+ _arrowPressed: function(){
+ if(!this.disabled && !this.readOnly && this.hasDownArrow){
+ dojo.addClass(this.downArrowNode, "dijitArrowButtonActive");
+ }
+ },
+
+ _arrowIdle: function(){
+ if(!this.disabled && !this.readOnly && this.hasDownArrow){
+ dojo.removeClass(this.downArrowNode, "dojoArrowButtonPushed");
+ }
+ },
+
+ // FIXME:
+ // this is public so we can't remove until 2.0, but the name
+ // SHOULD be "compositionEnd"
+
+ compositionend: function(/*Event*/ evt){
+ // summary:
+ // When inputting characters using an input method, such as
+ // Asian languages, it will generate this event instead of
+ // onKeyDown event Note: this event is only triggered in FF
+ // (not in IE)
+ this.onkeypress({charCode:-1});
+ },
+
+ //////////// INITIALIZATION METHODS ///////////////////////////////////////
+
+ constructor: function(){
+ this.query={};
+ },
+
+ postMixInProperties: function(){
+ if(!this.hasDownArrow){
+ this.baseClass = "dijitTextBox";
+ }
+ 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( !this.value || (
+ (typeof srcNodeRef.selectedIndex == "number") &&
+ srcNodeRef.selectedIndex.toString() === this.value)
+ ){
+ var item = this.store.fetchSelectedItem();
+ if(item){
+ this.value = this.store.getValue(item, this._getValueField());
+ }
+ }
+ }
+ },
+
+ _postCreate:function(){
+ //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");
+ var cn=this.comboNode;
+ dijit.setWaiState(cn, "labelledby", label[0].id);
+ dijit.setWaiState(cn, "disabled", this.disabled);
+
+ }
+ },
+
+ uninitialize:function(){
+ if(this._popupWidget){
+ this._hideResultList();
+ this._popupWidget.destroy()
+ }
+ },
+
+ _getMenuLabelFromItem:function(/*Item*/ item){
+ return {
+ html: false,
+ label: this.store.getValue(item, this.searchAttr)
+ };
+ },
+
+ open:function(){
+ this._isShowingNow=true;
+ return dijit.popup.open({
+ popup: this._popupWidget,
+ around: this.domNode,
+ parent: this
+ });
+ },
+
+ reset:function(){
+ // summary:
+ // Additionally reset the .item (to clean up).
+ this.item = null;
+ this.inherited(arguments);
+ }
+
+ }
+);
+
+dojo.declare(
+ "dijit.form._ComboBoxMenu",
+ [dijit._Widget, dijit._Templated],
+
+ {
+ // summary:
+ // Focus-less div based menu for internal use in ComboBox
+
+ templateString: "
"
+ +""
+ +""
+ +"
",
+ _messages: null,
+
+ postMixInProperties: function(){
+ this._messages = dojo.i18n.getLocalization("dijit.form", "ComboBox", this.lang);
+ this.inherited("postMixInProperties", arguments);
+ },
+
+ setValue: function(/*Object*/ value){
+ this.value = value;
+ this.onChange(value);
+ },
+
+ // stubs
+ onChange: function(/*Object*/ value){},
+ onPage: function(/*Number*/ direction){},
+
+ postCreate:function(){
+ // fill in template with i18n messages
+ this.previousButton.innerHTML = this._messages["previousMessage"];
+ this.nextButton.innerHTML = this._messages["nextMessage"];
+ this.inherited("postCreate", arguments);
+ },
+
+ onClose:function(){
+ this._blurOptionNode();
+ },
+
+ _createOption:function(/*Object*/ item, labelFunc){
+ // summary:
+ // creates an option to appear on the popup menu subclassed by
+ // FilteringSelect
+
+ var labelObject = labelFunc(item);
+ var menuitem = dojo.doc.createElement("li");
+ dijit.setWaiRole(menuitem, "option");
+ 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){
+ //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);
+ menuitem.className = "dijitMenuItem";
+ dojo.attr(menuitem, "id", this.id + i);
+ this.domNode.insertBefore(menuitem, this.nextButton);
+ }, this);
+ // display "Next . . ." button
+ this.nextButton.style.display = (dataObject.count == results.length) ? "" : "none";
+ dojo.attr(this.nextButton,"id", this.id + "_next")
+ },
+
+ clearResultList: function(){
+ // keep the previous and next buttons of course
+ while(this.domNode.childNodes.length>2){
+ this.domNode.removeChild(this.domNode.childNodes[this.domNode.childNodes.length-2]);
+ }
+ },
+
+ // these functions are called in showResultList
+ getItems: function(){
+ return this.domNode.childNodes;
+ },
+
+ getListLength: function(){
+ return this.domNode.childNodes.length-2;
+ },
+
+ _onMouseDown: function(/*Event*/ evt){
+ dojo.stopEvent(evt);
+ },
+
+ _onMouseUp: function(/*Event*/ evt){
+ if(evt.target === this.domNode){
+ 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 the clicked node is inside the div
+ while(!tgt.item){
+ // recurse to the top
+ tgt = tgt.parentNode;
+ }
+ this.setValue({ 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, "dijitMenuItemHover");
+ }
+ },
+
+ _blurOptionNode:function(){
+ // summary:
+ // removes highlight on highlighted option
+ if(this._highlighted_option){
+ dojo.removeClass(this._highlighted_option, "dijitMenuItemHover");
+ 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.
+ var fc = this.domNode.firstChild;
+ if(!this.getHighlightedOption()){
+ 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);
+ }
+ }
+ // scrollIntoView is called outside of _focusOptionNode because in IE putting it inside causes the menu to scroll up on mouseover
+ dijit.scrollIntoView(this._highlighted_option);
+ },
+
+ highlightFirstOption:function(){
+ // summary:
+ // Highlight the first real item in the list (not Previous Choices).
+ this._focusOptionNode(this.domNode.firstChild.nextSibling);
+ dijit.scrollIntoView(this._highlighted_option);
+ },
+
+ highlightLastOption:function(){
+ // summary:
+ // Highlight the last real item in the list (not More Choices).
+ this._focusOptionNode(this.domNode.lastChild.previousSibling);
+ dijit.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)
+ var lc = this.domNode.lastChild;
+ if(!this.getHighlightedOption()){
+ 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);
+ }
+ }
+ dijit.scrollIntoView(this._highlighted_option);
+ },
+
+ _page:function(/*Boolean*/ up){
+ 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
+ // |
+ // | ...
+ //
+ // 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 `
+ var item = dojo.query("option[value='" + args.identity + "']", this.root)[0];
+ args.onItem(item);
+ },
+
+ fetchSelectedItem: function(){
+ // summary:
+ // Get the option marked as selected, like `