source: Dev/trunk/src/client/dojox/mobile/ComboBox.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

  • Property svn:executable set to *
File size: 10.2 KB
Line 
1define([
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/dom-attr",
9        "dojo/window",
10        "dojo/touch",
11        "dijit/form/_AutoCompleterMixin",
12        "dijit/popup",
13        "./_ComboBoxMenu",
14        "./TextBox",
15        "./sniff"
16], function(kernel, declare, lang, win, domGeometry, domStyle, domAttr, windowUtils, touch, AutoCompleterMixin, popup, ComboBoxMenu, TextBox, has){
17        kernel.experimental("dojox.mobile.ComboBox"); // should be using a more native search-type UI
18
19        return declare("dojox.mobile.ComboBox", [TextBox, AutoCompleterMixin], {
20                // summary:
21                //              A non-templated auto-completing text box widget.
22
23                // dropDownClass: [protected extension] String
24                //              Name of the drop-down widget class used to select a date/time.
25                //              Should be specified by subclasses.
26                dropDownClass: "dojox.mobile._ComboBoxMenu",
27
28                // initially disable selection since iphone displays selection handles
29                // that makes it hard to pick from the list
30               
31                // selectOnClick: Boolean
32                //              Flag which enables the selection on click.
33                selectOnClick: false,
34               
35                // autoComplete: Boolean
36                //              Flag which enables the auto-completion.
37                autoComplete: false,
38
39                // dropDown: [protected] Widget
40                //              The widget to display as a popup. This widget *must* be
41                //              defined before the startup function is called.
42                dropDown: null,
43
44                // maxHeight: [protected] int
45                //              The maximum height for the drop-down.
46                //              Any drop-down taller than this value will have scrollbars.
47                //              Set to -1 to limit the height to the available space in the viewport.
48                maxHeight: -1,
49
50                // dropDownPosition: [const] String[]
51                //              This variable controls the position of the drop-down.
52                //              It is an array of strings with the following values:
53                //
54                //              - before: places drop down to the left of the target node/widget, or to the right in
55                //                the case of RTL scripts like Hebrew and Arabic
56                //              - after: places drop down to the right of the target node/widget, or to the left in
57                //                the case of RTL scripts like Hebrew and Arabic
58                //              - above: drop down goes above target node
59                //              - below: drop down goes below target node
60                //
61                //              The list is positions is tried, in order, until a position is found where the drop down fits
62                //              within the viewport.
63                dropDownPosition: ["below","above"],
64
65                _throttleOpenClose: function(){
66                        // summary:
67                        //              Prevents the open/close in rapid succession.
68                        // tags:
69                        //              private
70                        if(this._throttleHandler){
71                                this._throttleHandler.remove();
72                        }
73                        this._throttleHandler = this.defer(function(){ this._throttleHandler = null; }, 500);
74                },
75
76                _onFocus: function(){
77                        // summary:
78                        //              Shows drop-down if the user is selecting Next/Previous from the virtual keyboard.
79                        // tags:
80                        //              private
81                        this.inherited(arguments);
82                        if(!this._opened && !this._throttleHandler){
83                                this._startSearchAll();
84                        }
85
86                        if(has("windows-theme")) {
87                                this.domNode.blur();
88                        }
89                },
90
91                onInput: function(e){
92                        this._onKey(e);
93                        this.inherited(arguments);
94                },
95
96                _setListAttr: function(v){
97                        // tags:
98                        //              private
99                        this._set('list', v); // needed for Firefox 4+ to prevent HTML5 mode
100                },
101
102                closeDropDown: function(){
103                        // summary:
104                        //              Closes the drop down on this widget
105                        // tags:
106                        //              protected
107
108                        this._throttleOpenClose();
109                        if(this.endHandler){
110                                this.disconnect(this.startHandler);
111                                this.disconnect(this.endHandler);
112                                this.disconnect(this.moveHandler);
113                                clearInterval(this.repositionTimer);
114                                this.repositionTimer = this.endHandler = null;
115                        }
116                        this.inherited(arguments);
117                        domAttr.remove(this.domNode, "aria-owns");
118                        domAttr.set(this.domNode, "aria-expanded", "false");
119                        popup.close(this.dropDown);
120                        this._opened = false;
121
122                        // Remove disable attribute to make input element clickable after context menu closed
123                        if(has("windows-theme") && this.domNode.disabled){
124                                this.defer(function(){
125                                        this.domNode.removeAttribute("disabled");
126                                }, 300);
127                        }
128
129                },
130
131                openDropDown: function(){
132                        // summary:
133                        //              Opens the dropdown for this widget. To be called only when this.dropDown
134                        //              has been created and is ready to display (that is, its data is loaded).
135                        // returns:
136                        //              Returns the value of popup.open().
137                        // tags:
138                        //              protected
139
140                        var wasClosed = !this._opened;
141                        var dropDown = this.dropDown,
142                                ddNode = dropDown.domNode,
143                                aroundNode = this.domNode,
144                                self = this;
145                               
146                        domAttr.set(dropDown.domNode, "role", "listbox");
147                        domAttr.set(this.domNode, "aria-expanded", "true");
148                        if(dropDown.id){
149                                domAttr.set(this.domNode, "aria-owns", dropDown.id);
150                        }
151
152                        if(has('touch')){
153                                win.global.scrollBy(0, domGeometry.position(aroundNode, false).y); // don't call scrollIntoView since it messes up ScrollableView
154                        }
155
156                        // TODO: isn't maxHeight dependent on the return value from popup.open(),
157                        // i.e., dependent on how much space is available (BK)
158
159                        if(!this._preparedNode){
160                                this._preparedNode = true;
161                                // Check if we have explicitly set width and height on the dropdown widget dom node
162                                if(ddNode.style.width){
163                                        this._explicitDDWidth = true;
164                                }
165                                if(ddNode.style.height){
166                                        this._explicitDDHeight = true;
167                                }
168                        }
169
170                        // Code for resizing dropdown (height limitation, or increasing width to match my width)
171                        var myStyle = {
172                                display: "",
173                                overflow: "hidden",
174                                visibility: "hidden"
175                        };
176                        if(!this._explicitDDWidth){
177                                myStyle.width = "";
178                        }
179                        if(!this._explicitDDHeight){
180                                myStyle.height = "";
181                        }
182                        domStyle.set(ddNode, myStyle);
183
184                        // Figure out maximum height allowed (if there is a height restriction)
185                        var maxHeight = this.maxHeight;
186                        if(maxHeight == -1){
187                                // limit height to space available in viewport either above or below my domNode
188                                // (whichever side has more room)
189                                var viewport = windowUtils.getBox(),
190                                        position = domGeometry.position(aroundNode, false);
191                                maxHeight = Math.floor(Math.max(position.y, viewport.h - (position.y + position.h)));
192                        }
193
194                        // Attach dropDown to DOM and make make visibility:hidden rather than display:none
195                        // so we call startup() and also get the size
196                        popup.moveOffScreen(dropDown);
197
198                        if(dropDown.startup && !dropDown._started){
199                                dropDown.startup(); // this has to be done after being added to the DOM
200                        }
201                        // Get size of drop down, and determine if vertical scroll bar needed
202                        var mb = domGeometry.position(this.dropDown.containerNode, false);
203                        var overHeight = (maxHeight && mb.h > maxHeight);
204                        if(overHeight){
205                                mb.h = maxHeight;
206                        }
207
208                        // Adjust dropdown width to match or be larger than my width
209                        mb.w = Math.max(mb.w, aroundNode.offsetWidth);
210                        domGeometry.setMarginBox(ddNode, mb);
211
212                        var retVal = popup.open({
213                                parent: this,
214                                popup: dropDown,
215                                around: aroundNode,
216                                orient: has("windows-theme") ? ["above"] : this.dropDownPosition,
217                                onExecute: function(){
218                                        self.closeDropDown();
219                                },
220                                onCancel: function(){
221                                        self.closeDropDown();
222                                },
223                                onClose: function(){
224                                        self._opened = false;
225                                }
226                        });
227                        this._opened=true;
228
229                        if(wasClosed){
230                                var     isGesture = false,
231                                        skipReposition = false,
232                                        active = false,
233                                        wrapper = dropDown.domNode.parentNode,
234                                        aroundNodePos = domGeometry.position(aroundNode, false),
235                                        popupPos = domGeometry.position(wrapper, false),
236                                        deltaX = popupPos.x - aroundNodePos.x,
237                                        deltaY = popupPos.y - aroundNodePos.y,
238                                        startX = -1, startY = -1;
239
240                                // touchstart isn't really needed since touchmove implies touchstart, but
241                                // mousedown is needed since mousemove doesn't know if the left button is down or not
242                                this.startHandler = this.connect(win.doc.documentElement, touch.press,
243                                        function(e){
244                                                skipReposition = true;
245                                                active = true;
246                                                isGesture = false;
247                                                startX = e.clientX;
248                                                startY = e.clientY;
249                                        }
250                                );
251                                this.moveHandler = this.connect(win.doc.documentElement, touch.move,
252                                        function(e){
253                                                skipReposition = true;
254                                                if(e.touches){
255                                                        active = isGesture = true; // touchmove implies touchstart
256                                                }else if(active && (e.clientX != startX || e.clientY != startY)){
257                                                        isGesture = true;
258                                                }
259                                        }
260                                );
261                                this.clickHandler = this.connect(dropDown.domNode, "onclick",
262                                        function(){
263                                                skipReposition = true;
264                                                active = isGesture = false; // click implies no gesture movement
265                                        }
266                                );
267                                this.endHandler = this.connect(win.doc.documentElement, "onmouseup",//touch.release,
268                                        function(){
269                                                this.defer(function(){ // allow onclick to go first
270                                                        skipReposition = true;
271                                                        if(!isGesture && active){ // if click without move, then close dropdown
272                                                                this.closeDropDown();
273                                                        }
274                                                        active = false;
275                                                });
276                                        }
277                                );
278                                this.repositionTimer = setInterval(lang.hitch(this, function(){
279                                        if(skipReposition){ // don't reposition if busy
280                                                skipReposition = false;
281                                                return;
282                                        }
283                                        var     currentAroundNodePos = domGeometry.position(aroundNode, false),
284                                                currentPopupPos = domGeometry.position(wrapper, false),
285                                                currentDeltaX = currentPopupPos.x - currentAroundNodePos.x,
286                                                currentDeltaY = currentPopupPos.y - currentAroundNodePos.y;
287                                        // if the popup is no longer placed correctly, relocate it
288                                        if(Math.abs(currentDeltaX - deltaX) >= 1 || Math.abs(currentDeltaY - deltaY) >= 1){ // Firefox plays with partial pixels
289                                                domStyle.set(wrapper, { left: parseInt(domStyle.get(wrapper, "left")) + deltaX - currentDeltaX + 'px', top: parseInt(domStyle.get(wrapper, "top")) + deltaY - currentDeltaY + 'px' });
290                                        }
291                                }), 50); // yield a short time to allow for consolidation for better CPU throughput
292                        }
293
294                        // We need to disable input control in order to prevent opening the soft keyboard in IE
295                        if(has("windows-theme")){
296                                this.domNode.setAttribute("disabled", true);
297                        }
298
299                        return retVal;
300                },
301
302                postCreate: function(){
303                        this.inherited(arguments);
304                        this.connect(this.domNode, "onclick", "_onClick");
305                        domAttr.set(this.domNode, "role", "combobox");
306                        domAttr.set(this.domNode, "aria-expanded", "false");
307                },
308
309                destroy: function(){
310                        if(this.repositionTimer){
311                                clearInterval(this.repositionTimer);
312                        }
313                        this.inherited(arguments);
314                },
315
316                _onClick: function(/*Event*/ e){
317                        // tags:
318                        //              private
319                       
320                        // throttle clicks to prevent double click from doing double actions
321                        if(!this._throttleHandler){
322                                if(this.opened){
323                                        this.closeDropDown();
324                                }else{
325                                        this._startSearchAll();
326                                }
327                        }
328                }
329        });
330});
Note: See TracBrowser for help on using the repository browser.