You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
332 lines
10 KiB
332 lines
10 KiB
define([ |
|
"dojo/_base/kernel", |
|
"dojo/_base/declare", |
|
"dojo/_base/lang", |
|
"dojo/_base/window", |
|
"dojo/dom-geometry", |
|
"dojo/dom-style", |
|
"dojo/dom-attr", |
|
"dojo/window", |
|
"dojo/touch", |
|
"dijit/form/_AutoCompleterMixin", |
|
"dijit/popup", |
|
"./_ComboBoxMenu", |
|
"./TextBox", |
|
"./sniff" |
|
], function(kernel, declare, lang, win, domGeometry, domStyle, domAttr, windowUtils, touch, AutoCompleterMixin, popup, ComboBoxMenu, TextBox, has){ |
|
kernel.experimental("dojox.mobile.ComboBox"); // should be using a more native search-type UI |
|
|
|
return declare("dojox.mobile.ComboBox", [TextBox, AutoCompleterMixin], { |
|
// summary: |
|
// A non-templated auto-completing text box widget. |
|
|
|
// dropDownClass: [protected extension] String |
|
// Name of the drop-down widget class used to select a date/time. |
|
// Should be specified by subclasses. |
|
dropDownClass: "dojox.mobile._ComboBoxMenu", |
|
|
|
// initially disable selection since iphone displays selection handles |
|
// that makes it hard to pick from the list |
|
|
|
// selectOnClick: Boolean |
|
// Flag which enables the selection on click. |
|
selectOnClick: false, |
|
|
|
// autoComplete: Boolean |
|
// Flag which enables the auto-completion. |
|
autoComplete: false, |
|
|
|
// dropDown: [protected] Widget |
|
// The widget to display as a popup. This widget *must* be |
|
// defined before the startup function is called. |
|
dropDown: null, |
|
|
|
// maxHeight: [protected] int |
|
// The maximum height for the drop-down. |
|
// Any drop-down taller than this value will have scrollbars. |
|
// Set to -1 to limit the height to the available space in the viewport. |
|
maxHeight: -1, |
|
|
|
// dropDownPosition: [const] String[] |
|
// This variable controls the position of the drop-down. |
|
// It is an array of strings with the following values: |
|
// |
|
// - before: places drop down to the left of the target node/widget, or to the right in |
|
// the case of RTL scripts like Hebrew and Arabic |
|
// - after: places drop down to the right of the target node/widget, or to the left in |
|
// the case of RTL scripts like Hebrew and Arabic |
|
// - above: drop down goes above target node |
|
// - below: drop down goes below target node |
|
// |
|
// The list is positions is tried, in order, until a position is found where the drop down fits |
|
// within the viewport. |
|
dropDownPosition: ["below","above"], |
|
|
|
_throttleOpenClose: function(){ |
|
// summary: |
|
// Prevents the open/close in rapid succession. |
|
// tags: |
|
// private |
|
if(this._throttleHandler){ |
|
this._throttleHandler.remove(); |
|
} |
|
this._throttleHandler = this.defer(function(){ this._throttleHandler = null; }, 500); |
|
}, |
|
|
|
_onFocus: function(){ |
|
// summary: |
|
// Shows drop-down if the user is selecting Next/Previous from the virtual keyboard. |
|
// tags: |
|
// private |
|
this.inherited(arguments); |
|
if(!this._opened && !this._throttleHandler){ |
|
this._startSearchAll(); |
|
} |
|
|
|
if(has("windows-theme")) { |
|
this.domNode.blur(); |
|
} |
|
}, |
|
|
|
onInput: function(e){ |
|
if(!e || e.charCode !== 0){ // #18047 |
|
this._onKey(e); |
|
this.inherited(arguments); |
|
} |
|
}, |
|
|
|
_setListAttr: function(v){ |
|
// tags: |
|
// private |
|
this._set('list', v); // needed for Firefox 4+ to prevent HTML5 mode |
|
}, |
|
|
|
closeDropDown: function(){ |
|
// summary: |
|
// Closes the drop down on this widget |
|
// tags: |
|
// protected |
|
|
|
this._throttleOpenClose(); |
|
if(this.endHandler){ |
|
this.disconnect(this.startHandler); |
|
this.disconnect(this.endHandler); |
|
this.disconnect(this.moveHandler); |
|
clearInterval(this.repositionTimer); |
|
this.repositionTimer = this.endHandler = null; |
|
} |
|
this.inherited(arguments); |
|
domAttr.remove(this.domNode, "aria-owns"); |
|
domAttr.set(this.domNode, "aria-expanded", "false"); |
|
popup.close(this.dropDown); |
|
this._opened = false; |
|
|
|
// Remove disable attribute to make input element clickable after context menu closed |
|
if(has("windows-theme") && this.domNode.disabled){ |
|
this.defer(function(){ |
|
this.domNode.removeAttribute("disabled"); |
|
}, 300); |
|
} |
|
|
|
}, |
|
|
|
openDropDown: function(){ |
|
// summary: |
|
// Opens the dropdown for this widget. To be called only when this.dropDown |
|
// has been created and is ready to display (that is, its data is loaded). |
|
// returns: |
|
// Returns the value of popup.open(). |
|
// tags: |
|
// protected |
|
|
|
var wasClosed = !this._opened; |
|
var dropDown = this.dropDown, |
|
ddNode = dropDown.domNode, |
|
aroundNode = this.domNode, |
|
self = this; |
|
|
|
domAttr.set(dropDown.domNode, "role", "listbox"); |
|
domAttr.set(this.domNode, "aria-expanded", "true"); |
|
if(dropDown.id){ |
|
domAttr.set(this.domNode, "aria-owns", dropDown.id); |
|
} |
|
|
|
if(has("touch") && (!has("ios") || has("ios") < 8)){ |
|
win.global.scrollBy(0, domGeometry.position(aroundNode, false).y); // don't call scrollIntoView since it messes up ScrollableView |
|
} |
|
|
|
// TODO: isn't maxHeight dependent on the return value from popup.open(), |
|
// i.e., dependent on how much space is available (BK) |
|
|
|
if(!this._preparedNode){ |
|
this._preparedNode = true; |
|
// Check if we have explicitly set width and height on the dropdown widget dom node |
|
if(ddNode.style.width){ |
|
this._explicitDDWidth = true; |
|
} |
|
if(ddNode.style.height){ |
|
this._explicitDDHeight = true; |
|
} |
|
} |
|
|
|
// Code for resizing dropdown (height limitation, or increasing width to match my width) |
|
var myStyle = { |
|
display: "", |
|
overflow: "hidden", |
|
visibility: "hidden" |
|
}; |
|
if(!this._explicitDDWidth){ |
|
myStyle.width = ""; |
|
} |
|
if(!this._explicitDDHeight){ |
|
myStyle.height = ""; |
|
} |
|
domStyle.set(ddNode, myStyle); |
|
|
|
// Figure out maximum height allowed (if there is a height restriction) |
|
var maxHeight = this.maxHeight; |
|
if(maxHeight == -1){ |
|
// limit height to space available in viewport either above or below my domNode |
|
// (whichever side has more room) |
|
var viewport = windowUtils.getBox(), |
|
position = domGeometry.position(aroundNode, false); |
|
maxHeight = Math.floor(Math.max(position.y, viewport.h - (position.y + position.h))); |
|
} |
|
|
|
// Attach dropDown to DOM and make make visibility:hidden rather than display:none |
|
// so we call startup() and also get the size |
|
popup.moveOffScreen(dropDown); |
|
|
|
if(dropDown.startup && !dropDown._started){ |
|
dropDown.startup(); // this has to be done after being added to the DOM |
|
} |
|
// Get size of drop down, and determine if vertical scroll bar needed |
|
var mb = domGeometry.position(this.dropDown.containerNode, false); |
|
var overHeight = (maxHeight && mb.h > maxHeight); |
|
if(overHeight){ |
|
mb.h = maxHeight; |
|
} |
|
|
|
// Adjust dropdown width to match or be larger than my width |
|
mb.w = Math.max(mb.w, aroundNode.offsetWidth); |
|
domGeometry.setMarginBox(ddNode, mb); |
|
|
|
var retVal = popup.open({ |
|
parent: this, |
|
popup: dropDown, |
|
around: aroundNode, |
|
orient: has("windows-theme") ? ["above"] : this.dropDownPosition, |
|
onExecute: function(){ |
|
self.closeDropDown(); |
|
}, |
|
onCancel: function(){ |
|
self.closeDropDown(); |
|
}, |
|
onClose: function(){ |
|
self._opened = false; |
|
} |
|
}); |
|
this._opened=true; |
|
|
|
if(wasClosed){ |
|
var isGesture = false, |
|
skipReposition = false, |
|
active = false, |
|
wrapper = dropDown.domNode.parentNode, |
|
aroundNodePos = domGeometry.position(aroundNode, false), |
|
popupPos = domGeometry.position(wrapper, false), |
|
deltaX = popupPos.x - aroundNodePos.x, |
|
deltaY = popupPos.y - aroundNodePos.y, |
|
startX = -1, startY = -1; |
|
|
|
// touchstart isn't really needed since touchmove implies touchstart, but |
|
// mousedown is needed since mousemove doesn't know if the left button is down or not |
|
this.startHandler = this.connect(win.doc.documentElement, touch.press, |
|
function(e){ |
|
skipReposition = true; |
|
active = true; |
|
isGesture = false; |
|
startX = e.clientX; |
|
startY = e.clientY; |
|
} |
|
); |
|
this.moveHandler = this.connect(win.doc.documentElement, touch.move, |
|
function(e){ |
|
skipReposition = true; |
|
if(e.touches){ |
|
active = isGesture = true; // touchmove implies touchstart |
|
}else if(active && (e.clientX != startX || e.clientY != startY)){ |
|
isGesture = true; |
|
} |
|
} |
|
); |
|
this.clickHandler = this.connect(dropDown.domNode, "onclick", |
|
function(){ |
|
skipReposition = true; |
|
active = isGesture = false; // click implies no gesture movement |
|
} |
|
); |
|
this.endHandler = this.connect(win.doc.documentElement, touch.release, |
|
function(){ |
|
this.defer(function(){ // allow onclick to go first |
|
skipReposition = true; |
|
if(!isGesture && active){ // if click without move, then close dropdown |
|
this.closeDropDown(); |
|
} |
|
active = false; |
|
}); |
|
} |
|
); |
|
this.repositionTimer = setInterval(lang.hitch(this, function(){ |
|
if(skipReposition){ // don't reposition if busy |
|
skipReposition = false; |
|
return; |
|
} |
|
var currentAroundNodePos = domGeometry.position(aroundNode, false), |
|
currentPopupPos = domGeometry.position(wrapper, false), |
|
currentDeltaX = currentPopupPos.x - currentAroundNodePos.x, |
|
currentDeltaY = currentPopupPos.y - currentAroundNodePos.y; |
|
// if the popup is no longer placed correctly, relocate it |
|
if(Math.abs(currentDeltaX - deltaX) >= 1 || Math.abs(currentDeltaY - deltaY) >= 1){ // Firefox plays with partial pixels |
|
domStyle.set(wrapper, { left: parseInt(domStyle.get(wrapper, "left")) + deltaX - currentDeltaX + 'px', top: parseInt(domStyle.get(wrapper, "top")) + deltaY - currentDeltaY + 'px' }); |
|
} |
|
}), 50); // yield a short time to allow for consolidation for better CPU throughput |
|
} |
|
|
|
// We need to disable input control in order to prevent opening the soft keyboard in IE |
|
if(has("windows-theme")){ |
|
this.domNode.setAttribute("disabled", true); |
|
} |
|
|
|
return retVal; |
|
}, |
|
|
|
postCreate: function(){ |
|
this.inherited(arguments); |
|
this.connect(this.domNode, "onclick", "_onClick"); |
|
domAttr.set(this.domNode, "role", "combobox"); |
|
domAttr.set(this.domNode, "aria-expanded", "false"); |
|
}, |
|
|
|
destroy: function(){ |
|
if(this.repositionTimer){ |
|
clearInterval(this.repositionTimer); |
|
} |
|
this.inherited(arguments); |
|
}, |
|
|
|
_onClick: function(/*Event*/ e){ |
|
// tags: |
|
// private |
|
|
|
// throttle clicks to prevent double click from doing double actions |
|
if(!this._throttleHandler){ |
|
if(this.opened){ |
|
this.closeDropDown(); |
|
}else{ |
|
this._startSearchAll(); |
|
} |
|
} |
|
} |
|
}); |
|
});
|
|
|