1 | define([ |
---|
2 | "dojo/_base/kernel", |
---|
3 | "dojo/_base/declare", |
---|
4 | "dojo/_base/lang", |
---|
5 | "dojo/_base/window", |
---|
6 | "dojo/dom-geometry", |
---|
7 | "dojo/dom-style", |
---|
8 | "dojo/window", |
---|
9 | "dijit/form/_AutoCompleterMixin", |
---|
10 | "dijit/popup", |
---|
11 | "./_ComboBoxMenu", |
---|
12 | "./TextBox", |
---|
13 | "./sniff" |
---|
14 | ], function(kernel, declare, lang, win, domGeometry, domStyle, windowUtils, AutoCompleterMixin, popup, ComboBoxMenu, TextBox, has){ |
---|
15 | kernel.experimental("dojox.mobile.ComboBox"); // should be using a more native search-type UI |
---|
16 | |
---|
17 | /*===== |
---|
18 | TextBox = dojox.mobile.TextBox; |
---|
19 | AutoCompleterMixin = dijit.form._AutoCompleterMixin; |
---|
20 | =====*/ |
---|
21 | return declare("dojox.mobile.ComboBox", [TextBox, AutoCompleterMixin], { |
---|
22 | // summary: |
---|
23 | // A non-templated auto-completing text box widget |
---|
24 | // |
---|
25 | |
---|
26 | // dropDownClass: [protected extension] String |
---|
27 | // Name of the dropdown widget class used to select a date/time. |
---|
28 | // Subclasses should specify this. |
---|
29 | dropDownClass: "dojox.mobile._ComboBoxMenu", |
---|
30 | |
---|
31 | // initially disable selection since iphone displays selection handles that makes it hard to pick from the list |
---|
32 | selectOnClick: false, |
---|
33 | autoComplete: false, |
---|
34 | |
---|
35 | // dropDown: [protected] Widget |
---|
36 | // The widget to display as a popup. This widget *must* be |
---|
37 | // defined before the startup function is called. |
---|
38 | dropDown: null, |
---|
39 | |
---|
40 | // maxHeight: [protected] Integer |
---|
41 | // The max height for our dropdown. |
---|
42 | // Any dropdown taller than this will have scrollbars. |
---|
43 | // Set to -1 to limit height to available space in viewport |
---|
44 | maxHeight: -1, |
---|
45 | |
---|
46 | // dropDownPosition: [const] String[] |
---|
47 | // This variable controls the position of the drop down. |
---|
48 | // It's an array of strings with the following values: |
---|
49 | // |
---|
50 | // * before: places drop down to the left of the target node/widget, or to the right in |
---|
51 | // the case of RTL scripts like Hebrew and Arabic |
---|
52 | // * after: places drop down to the right of the target node/widget, or to the left in |
---|
53 | // the case of RTL scripts like Hebrew and Arabic |
---|
54 | // * above: drop down goes above target node |
---|
55 | // * below: drop down goes below target node |
---|
56 | // |
---|
57 | // The list is positions is tried, in order, until a position is found where the drop down fits |
---|
58 | // within the viewport. |
---|
59 | // |
---|
60 | dropDownPosition: ["below","above"], |
---|
61 | |
---|
62 | _throttleOpenClose: function(){ |
---|
63 | // prevent open/close in rapid succession |
---|
64 | if(this._throttleHandler){ |
---|
65 | clearTimeout(this._throttleHandler); |
---|
66 | } |
---|
67 | this._throttleHandler = setTimeout(lang.hitch(this, function(){ this._throttleHandler = null; }), 500); |
---|
68 | }, |
---|
69 | |
---|
70 | _onFocus: function(){ |
---|
71 | this.inherited(arguments); |
---|
72 | if(!this._opened && !this._throttleHandler){ |
---|
73 | this._startSearchAll(); // show dropdown if user is selecting Next/Previous from virtual keyboard |
---|
74 | } |
---|
75 | }, |
---|
76 | |
---|
77 | onInput: function(e){ |
---|
78 | this._onKey(e); |
---|
79 | this.inherited(arguments); |
---|
80 | }, |
---|
81 | |
---|
82 | _setListAttr: function(v){ |
---|
83 | this._set('list', v); // needed for Firefox 4+ to prevent HTML5 mode |
---|
84 | }, |
---|
85 | |
---|
86 | closeDropDown: function(){ |
---|
87 | // summary: |
---|
88 | // Closes the drop down on this widget |
---|
89 | // tags: |
---|
90 | // protected |
---|
91 | |
---|
92 | this._throttleOpenClose(); |
---|
93 | if(this.startHandler){ |
---|
94 | this.disconnect(this.startHandler); |
---|
95 | this.startHandler = null; |
---|
96 | if(this.moveHandler){ this.disconnect(this.moveHandler); } |
---|
97 | if(this.endHandler){ this.disconnect(this.endHandler); } |
---|
98 | } |
---|
99 | this.inherited(arguments); |
---|
100 | popup.close(this.dropDown); |
---|
101 | this._opened = false; |
---|
102 | }, |
---|
103 | |
---|
104 | openDropDown: function(){ |
---|
105 | // summary: |
---|
106 | // Opens the dropdown for this widget. To be called only when this.dropDown |
---|
107 | // has been created and is ready to display (ie, it's data is loaded). |
---|
108 | // returns: |
---|
109 | // return value of popup.open() |
---|
110 | // tags: |
---|
111 | // protected |
---|
112 | |
---|
113 | var wasClosed = !this._opened; |
---|
114 | var dropDown = this.dropDown, |
---|
115 | ddNode = dropDown.domNode, |
---|
116 | aroundNode = this.domNode, |
---|
117 | self = this; |
---|
118 | |
---|
119 | |
---|
120 | // TODO: isn't maxHeight dependent on the return value from popup.open(), |
---|
121 | // ie, dependent on how much space is available (BK) |
---|
122 | |
---|
123 | if(!this._preparedNode){ |
---|
124 | this._preparedNode = true; |
---|
125 | // Check if we have explicitly set width and height on the dropdown widget dom node |
---|
126 | if(ddNode.style.width){ |
---|
127 | this._explicitDDWidth = true; |
---|
128 | } |
---|
129 | if(ddNode.style.height){ |
---|
130 | this._explicitDDHeight = true; |
---|
131 | } |
---|
132 | } |
---|
133 | |
---|
134 | // Code for resizing dropdown (height limitation, or increasing width to match my width) |
---|
135 | var myStyle = { |
---|
136 | display: "", |
---|
137 | overflow: "hidden", |
---|
138 | visibility: "hidden" |
---|
139 | }; |
---|
140 | if(!this._explicitDDWidth){ |
---|
141 | myStyle.width = ""; |
---|
142 | } |
---|
143 | if(!this._explicitDDHeight){ |
---|
144 | myStyle.height = ""; |
---|
145 | } |
---|
146 | domStyle.set(ddNode, myStyle); |
---|
147 | |
---|
148 | // Figure out maximum height allowed (if there is a height restriction) |
---|
149 | var maxHeight = this.maxHeight; |
---|
150 | if(maxHeight == -1){ |
---|
151 | // limit height to space available in viewport either above or below my domNode |
---|
152 | // (whichever side has more room) |
---|
153 | var viewport = windowUtils.getBox(), |
---|
154 | position = domGeometry.position(aroundNode, false); |
---|
155 | maxHeight = Math.floor(Math.max(position.y, viewport.h - (position.y + position.h))); |
---|
156 | } |
---|
157 | |
---|
158 | // Attach dropDown to DOM and make make visibility:hidden rather than display:none |
---|
159 | // so we call startup() and also get the size |
---|
160 | popup.moveOffScreen(dropDown); |
---|
161 | |
---|
162 | if(dropDown.startup && !dropDown._started){ |
---|
163 | dropDown.startup(); // this has to be done after being added to the DOM |
---|
164 | } |
---|
165 | // Get size of drop down, and determine if vertical scroll bar needed |
---|
166 | var mb = domGeometry.position(this.dropDown.containerNode, false); |
---|
167 | var overHeight = (maxHeight && mb.h > maxHeight); |
---|
168 | if(overHeight){ |
---|
169 | mb.h = maxHeight; |
---|
170 | } |
---|
171 | |
---|
172 | // Adjust dropdown width to match or be larger than my width |
---|
173 | mb.w = Math.max(mb.w, aroundNode.offsetWidth); |
---|
174 | domGeometry.setMarginBox(ddNode, mb); |
---|
175 | |
---|
176 | var retVal = popup.open({ |
---|
177 | parent: this, |
---|
178 | popup: dropDown, |
---|
179 | around: aroundNode, |
---|
180 | orient: this.dropDownPosition, |
---|
181 | onExecute: function(){ |
---|
182 | self.closeDropDown(); |
---|
183 | }, |
---|
184 | onCancel: function(){ |
---|
185 | self.closeDropDown(); |
---|
186 | }, |
---|
187 | onClose: function(){ |
---|
188 | self._opened = false; |
---|
189 | } |
---|
190 | }); |
---|
191 | this._opened=true; |
---|
192 | |
---|
193 | if(wasClosed){ |
---|
194 | if(retVal.aroundCorner.charAt(0) == 'B'){ // is popup below? |
---|
195 | this.domNode.scrollIntoView(true); // scroll to top |
---|
196 | } |
---|
197 | this.startHandler = this.connect(win.doc.documentElement, has('touch') ? "ontouchstart" : "onmousedown", |
---|
198 | lang.hitch(this, function(){ |
---|
199 | var isMove = false; |
---|
200 | this.moveHandler = this.connect(win.doc.documentElement, has('touch') ? "ontouchmove" : "onmousemove", function(){ isMove = true; }); |
---|
201 | this.endHandler = this.connect(win.doc.documentElement, has('touch') ? "ontouchend" : "onmouseup", function(){ if(!isMove){ this.closeDropDown(); } }); |
---|
202 | }) |
---|
203 | ); |
---|
204 | } |
---|
205 | return retVal; |
---|
206 | }, |
---|
207 | |
---|
208 | postCreate: function(){ |
---|
209 | this.inherited(arguments); |
---|
210 | this.connect(this.domNode, "onclick", "_onClick"); |
---|
211 | }, |
---|
212 | |
---|
213 | _onClick: function(/*Event*/ e){ |
---|
214 | // throttle clicks to prevent double click from doing double actions |
---|
215 | if(!this._throttleHandler){ |
---|
216 | if(this.opened){ |
---|
217 | this.closeDropDown(); |
---|
218 | }else{ |
---|
219 | this._startSearchAll(); |
---|
220 | } |
---|
221 | } |
---|
222 | } |
---|
223 | }); |
---|
224 | }); |
---|