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

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

Added Dojo 1.9.3 release.

File size: 15.6 KB
Line 
1define([
2        "dojo/_base/kernel",
3        "dojo/_base/array",
4        "dojo/_base/declare",
5        "dojo/_base/window",
6        "dojo/dom-class",
7        "dojo/dom-construct",
8        "dojo/has",
9        "dojo/has!dojo-bidi?dojox/mobile/bidi/SpinWheelSlot",
10        "dojo/touch",
11        "dojo/on",
12        "dijit/_Contained",
13        "dijit/_WidgetBase",
14        "./scrollable",
15        "./common"
16], function(dojo, array, declare, win, domClass, domConstruct, has, BidiSpinWheelSlot,
17        touch, on, Contained, WidgetBase, Scrollable){
18
19        // module:
20        //              dojox/mobile/SpinWheelSlot
21
22        var SpinWheelSlot = declare(has("dojo-bidi") ? "dojox.mobile.NonBidiSpinWheelSlot" : "dojox.mobile.SpinWheelSlot", [WidgetBase, Contained, Scrollable], {
23                // summary:
24                //              A slot of a SpinWheel.
25                // description:
26                //              SpinWheelSlot is a slot that is placed in the SpinWheel widget.
27
28                // items: Array
29                //              An array of array of key-label pairs
30                //              (e.g. [[0, "Jan"], [1, "Feb"], ...]). If key values for each label
31                //              are not necessary, labels can be used instead.
32                items: [],
33
34                // labels: Array
35                //              An array of labels to be displayed on the slot
36                //              (e.g. ["Jan", "Feb", ...]). This is a simplified version of the
37                //              items property.
38                labels: [],
39
40                // labelFrom: Number
41                //              The start value of display values of the slot. This parameter is
42                //              especially useful when the slot has serial values.
43                labelFrom: 0,
44
45                // labelTo: Number
46                //              The end value of display values of the slot.
47                labelTo: 0,
48
49                // zeroPad: Number
50                //              Length of zero padding numbers.
51                //              Ex. zeroPad=2 -> "00", "01", ...
52                //              Ex. zeroPad=3 -> "000", "001", ...
53                zeroPad: 0,
54
55                // value: String
56                //              The initial value of the slot.
57                value: "",
58
59                // step: Number
60                //              The steps between labelFrom and labelTo.
61                step: 1,
62
63                // tabIndex: String
64                //              Tabindex setting for this widget so users can hit the tab key to
65                //              focus on it.
66                tabIndex: "0",
67                _setTabIndexAttr: "", // sets tabIndex to domNode
68
69                /* internal properties */       
70                baseClass: "mblSpinWheelSlot",
71                // maxSpeed: [private] Number
72                //              Maximum speed.
73                maxSpeed: 500,
74                // minItems: [private] int
75                //              Minimum number of items.
76                minItems: 15,
77                // centerPos: [private] Number
78                //              Inherited from parent.
79                centerPos: 0,
80                // scrollbar: [private] Boolean
81                //              False: no scrollbars must be shown.
82                scrollBar: false,
83                // constraint: [private] Boolean
84                //              False: no scroll constraint.
85                constraint: false,
86                // propagatable: [private] Boolean
87                //              False: stop touchstart event propagation.
88                propagatable: false, // stop touchstart event propagation to make spin wheel work inside scrollable
89                // androidWorkaroud: [private] Boolean
90                //              False.
91                androidWorkaroud: false, // disable workaround in SpinWheel TODO:remove this line later
92
93                buildRendering: function(){
94                        this.inherited(arguments);
95
96                        this.initLabels();
97                        var i, j;
98                        if(this.labels.length > 0){
99                                this.items = [];
100                                for(i = 0; i < this.labels.length; i++){
101                                        this.items.push([i, this.labels[i]]);
102                                }
103                        }
104
105                        this.containerNode = domConstruct.create("div", {className:"mblSpinWheelSlotContainer"});
106                        this.containerNode.style.height
107                                = (win.global.innerHeight||win.doc.documentElement.clientHeight) * 2 + "px"; // must bigger than the screen
108                        this.panelNodes = [];
109                        for(var k = 0; k < 3; k++){
110                                this.panelNodes[k] = domConstruct.create("div", {className:"mblSpinWheelSlotPanel"});
111                                var len = this.items.length;
112                                if(len > 0){ // if the slot is not empty
113                                        var n = Math.ceil(this.minItems / len);
114                                        for(j = 0; j < n; j++){
115                                                for(i = 0; i < len; i++){
116                                                        domConstruct.create("div", {
117                                                                className: "mblSpinWheelSlotLabel",
118                                                                name: this.items[i][0],
119                                                                "data-mobile-val": this.items[i][1],
120                                                                innerHTML: this._cv ? this._cv(this.items[i][1]) : this.items[i][1]
121                                                        }, this.panelNodes[k]);
122                                                }
123                                        }
124                                }
125                                this.containerNode.appendChild(this.panelNodes[k]);
126                        }
127                        this.domNode.appendChild(this.containerNode);
128                        this.touchNode = domConstruct.create("div", {className:"mblSpinWheelSlotTouch"}, this.domNode);
129                        this.setSelectable(this.domNode, false);
130
131                        if(this.value === "" && this.items.length > 0){
132                                this.value = this.items[0][1];
133                        }
134                        this._initialValue = this.value;
135
136                        if(has("windows-theme")){
137                                var self = this,
138                                        containerNode = this.containerNode,
139                                        threshold = 5;
140
141                                this.own(on(self.touchNode, touch.press, function(e){
142                                        var posY = e.pageY,
143                                                slots = self.getParent().getChildren();
144
145                                        for(var i = 0, ln = slots.length; i < ln; i++){
146                                                var container = slots[i].containerNode;
147
148                                                if(containerNode !== container){
149                                                        domClass.remove(container, "mblSelectedSlot");
150                                                        container.selected = false;
151                                                }else{
152                                                        domClass.add(containerNode, "mblSelectedSlot");
153                                                }
154                                        }
155
156                                        var moveHandler = on(self.touchNode, touch.move, function(e){
157                                                if(Math.abs(e.pageY - posY) < threshold){
158                                                        return;
159                                                }
160
161                                                moveHandler.remove();
162                                                releaseHandler.remove();
163                                                containerNode.selected = true;
164
165                                                var item = self.getCenterItem();
166
167                                                if(item){
168                                                        domClass.remove(item, "mblSelectedSlotItem");
169                                                }
170                                        });
171
172                                        var releaseHandler = on(self.touchNode, touch.release, function(){
173                                                releaseHandler.remove();
174                                                moveHandler.remove();
175                                                containerNode.selected ?
176                                                        domClass.remove(containerNode, "mblSelectedSlot") :
177                                                        domClass.add(containerNode, "mblSelectedSlot");
178
179                                                containerNode.selected = !containerNode.selected;
180                                        });
181                                }));
182
183                                this.on("flickAnimationEnd", function(){
184                                                var item = self.getCenterItem();
185
186                                                if(self.previousCenterItem) {
187                                                        domClass.remove(self.previousCenterItem, "mblSelectedSlotItem");
188                                                }
189
190                                                domClass.add(item, "mblSelectedSlotItem");
191                                                self.previousCenterItem = item;
192                                });
193                        }
194                },
195
196                startup: function(){
197                        if(this._started){ return; }
198                        this.inherited(arguments);
199                        this.noResize = true;
200                        if(this.items.length > 0){ // if the slot is not empty
201                                this.init();
202                                this.centerPos = this.getParent().centerPos;
203                                var items = this.panelNodes[1].childNodes;
204                                this._itemHeight = items[0].offsetHeight;
205                                this.adjust();
206                                this.connect(this.domNode, "onkeydown", "_onKeyDown"); // for desktop browsers
207                        }
208                        if(has("windows-theme")){
209                                this.previousCenterItem = this.getCenterItem();
210                                if(this.previousCenterItem){
211                                        domClass.add(this.previousCenterItem, "mblSelectedSlotItem");
212                                }
213                        }
214                },
215
216                initLabels: function(){
217                        // summary:
218                        //              Initializes the slot labels according to the labelFrom/labelTo properties.
219                        // tags:
220                        //              private
221                        if(this.labelFrom !== this.labelTo){
222                                var a = this.labels = [],
223                                        zeros = this.zeroPad && Array(this.zeroPad).join("0");
224                                for(var i = this.labelFrom; i <= this.labelTo; i += this.step){
225                                        a.push(this.zeroPad ? (zeros + i).slice(-this.zeroPad) : i + "");
226                                }
227                        }
228                },
229
230                adjust: function(){
231                        // summary:
232                        //              Adjusts the position of slot panels.
233                        var items = this.panelNodes[1].childNodes;
234                        var adjustY;
235                        for(var i = 0, len = items.length; i < len; i++){
236                                var item = items[i];
237                                if(item.offsetTop <= this.centerPos && this.centerPos < item.offsetTop + item.offsetHeight){
238                                        adjustY = this.centerPos - (item.offsetTop + Math.round(item.offsetHeight/2));
239                                        break;
240                                }
241                        }
242                        var h = this.panelNodes[0].offsetHeight;
243                        this.panelNodes[0].style.top = -h + adjustY + "px";
244                        this.panelNodes[1].style.top = adjustY + "px";
245                        this.panelNodes[2].style.top = h + adjustY + "px";
246                },
247
248                setInitialValue: function(){
249                        // summary:
250                        //              Sets the initial value using this.value or the first item.
251                        this.set("value", this._initialValue);
252                },
253
254                _onKeyDown: function(e){
255                        if(!e || e.type !== "keydown"){ return; }
256                        if(e.keyCode === 40){ // down arrow key
257                                this.spin(-1);
258                        }else if(e.keyCode === 38){ // up arrow key
259                                this.spin(1);
260                        }
261                },
262
263                _getCenterPanel: function(){
264                        // summary:
265                        //              Gets a panel that contains the currently selected item.
266                        var pos = this.getPos();
267                        for(var i = 0, len = this.panelNodes.length; i < len; i++){
268                                var top = pos.y + this.panelNodes[i].offsetTop;
269                                if(top <= this.centerPos && this.centerPos < top + this.panelNodes[i].offsetHeight){
270                                        return this.panelNodes[i];
271                                }
272                        }
273                        return null;
274                },
275
276                setColor: function(/*String*/value, /*String?*/color){
277                        // summary:
278                        //              Sets the color of the specified item as blue.
279                        array.forEach(this.panelNodes, function(panel){
280                                array.forEach(panel.childNodes, function(node, i){
281                                        domClass.toggle(node, color || "mblSpinWheelSlotLabelBlue", node.innerHTML === value);
282                                }, this);
283                        }, this);
284                },
285
286                disableValues: function(/*Number*/n){
287                        // summary:
288                        //              Grays out the items with an index higher or equal to the specified number.
289                        array.forEach(this.panelNodes, function(panel){
290                                for(var i = 0; i < panel.childNodes.length; i++){
291                                        domClass.toggle(panel.childNodes[i], "mblSpinWheelSlotLabelGray", i >= n);
292                                }
293                        });
294                },
295
296                getCenterItem: function(){
297                        // summary:
298                        //              Gets the currently selected item.
299                        var pos = this.getPos();
300                        var centerPanel = this._getCenterPanel();
301                        if(centerPanel){
302                                var top = pos.y + centerPanel.offsetTop;
303                                var items = centerPanel.childNodes;
304                                for(var i = 0, len = items.length; i < len; i++){
305                                        if(top + items[i].offsetTop <= this.centerPos && this.centerPos < top + items[i].offsetTop + items[i].offsetHeight){
306                                                return items[i];
307                                        }
308                                }
309                        }
310                        return null;
311
312                },
313
314                _getKeyAttr: function(){
315                        // summary:
316                        //              Gets the key for the currently selected value.
317                        if(!this._started){
318                                if(this.items){
319                                        var v = this.value;
320                                        for(var i = 0; i < this.items.length; i++){
321                                                if(this.items[i][1] == this.value){
322                                                        return this.items[i][0];
323                                                }
324                                        }
325                                }
326                                return null;
327                        }
328                        var item = this.getCenterItem();
329                        return (item && item.getAttribute("name"));
330                },
331
332                _getValueAttr: function(){
333                        // summary:
334                        //              Gets the currently selected value.
335                        if(!this._started){
336                                return this.value;
337                        }
338                        if(this.items.length > 0){ // if the slot is not empty
339                                var item = this.getCenterItem();
340                                return (item && item.getAttribute("data-mobile-val"));
341                        }else{
342                                return this._initialValue;
343                        }
344                },
345
346                _setValueAttr: function(value){
347                        // summary:
348                        //              Sets the value to this slot.
349                        if(this.items.length > 0){ // no-op for empty slots
350                                this._spinToValue(value, true);
351                        }
352                },
353               
354                _spinToValue: function(value, applyValue){
355                        // summary:
356                        //              Spins the slot to the specified value.
357                        // tags:
358                        //              private
359                        var idx0, idx1;
360                        var curValue = this.get("value");
361                        if(!curValue){
362                                this._pendingValue = value;
363                                return;
364                        }
365                        if(curValue == value){
366                                return; // no change; avoid notification
367                        }
368                        this._pendingValue = undefined;
369                        // to avoid unnecessary notifications, applyValue is false when
370                        // _spinToValue is called by _DatePickerMixin.
371                        if(applyValue){
372                                this._set("value", value);
373                        }
374                        var n = this.items.length;
375                        for(var i = 0; i < n; i++){
376                                if(this.items[i][1] === String(curValue)){
377                                        idx0 = i;
378                                }
379                                if(this.items[i][1] === String(value)){
380                                        idx1 = i;
381                                }
382                                if(idx0 !== undefined && idx1 !== undefined){
383                                        break;
384                                }
385                        }
386                        var d = idx1 - (idx0 || 0);
387                        var m;
388                        if(d > 0){
389                                m = (d < n - d) ? -d : n - d;
390                        }else{
391                                m = (-d < n + d) ? -d : -(n + d);
392                        }
393                        this.spin(m);
394                },
395               
396                onFlickAnimationStart: function(e){
397                        // summary:
398                        //              Overrides dojox/mobile/scrollable.onFlickAnimationStart().
399                        this._onFlickAnimationStartCalled = true;
400                        this.inherited(arguments);
401                },
402
403                onFlickAnimationEnd: function(e){
404                        // summary:
405                        //              Overrides dojox/mobile/scrollable.onFlickAnimationEnd().
406                        this._duringSlideTo = false;
407                        this._onFlickAnimationStartCalled = false;
408                        this.inherited(arguments);
409                },
410               
411                spin: function(/*Number*/steps){
412                        // summary:
413                        //              Spins the slot as specified by steps.
414                       
415                        // do nothing before startup and during slide
416                        if(!this._started || this._duringSlideTo){
417                                return;
418                        }
419                        var to = this.getPos();
420                        to.y += steps * this._itemHeight;
421                        this.slideTo(to, 1);
422                },
423
424                getSpeed: function(){
425                        // summary:
426                        //              Overrides dojox/mobile/scrollable.getSpeed().
427                        var y = 0, n = this._time.length;
428                        var delta = (new Date()).getTime() - this.startTime - this._time[n - 1];
429                        if(n >= 2 && delta < 200){
430                                var dy = this._posY[n - 1] - this._posY[(n - 6) >= 0 ? n - 6 : 0];
431                                var dt = this._time[n - 1] - this._time[(n - 6) >= 0 ? n - 6 : 0];
432                                y = this.calcSpeed(dy, dt);
433                        }
434                        return {x:0, y:y};
435                },
436
437                calcSpeed: function(/*Number*/d, /*Number*/t){
438                        // summary:
439                        //              Overrides dojox/mobile/scrollable.calcSpeed().
440                        var speed = this.inherited(arguments);
441                        if(!speed){ return 0; }
442                        var v = Math.abs(speed);
443                        var ret = speed;
444                        if(v > this.maxSpeed){
445                                ret = this.maxSpeed*(speed/v);
446                        }
447                        return ret;
448                },
449
450                adjustDestination: function(to, pos, dim){
451                        // summary:
452                        //              Overrides dojox/mobile/scrollable.adjustDestination().
453                        var h = this._itemHeight;
454                        var j = to.y + Math.round(h/2);
455                        var r = j >= 0 ? j % h : j % h + h;
456                        to.y = j - r;
457                        return true;
458                },
459
460                resize: function(e){
461                        // Correct internal variables & adjust slot panels
462                        var items = this.panelNodes[1].childNodes;
463                        // TODO investigate - the position is calculated incorrectly for
464                        // windows theme, disable this logic for now.
465                        if(items.length > 0 && !has("windows-theme")){ // empty slot?
466                                this._itemHeight = items[0].offsetHeight;
467                                this.centerPos = this.getParent().centerPos;
468                                if(!this.panelNodes[0].style.top){
469                                        // #17339: to avoid messing up the layout of the panels, call adjust()
470                                        // only if it didn't manage yet to set the style.top (this happens
471                                        // typically because the slot was initially      hidden).
472                                        this.adjust();
473                                }
474                        }
475                        if(this._pendingValue){
476                                this.set("value", this._pendingValue);
477                        }
478                },
479
480                slideTo: function(/*Object*/to, /*Number*/duration, /*String*/easing){
481                        // summary:
482                        //              Overrides dojox/mobile/scrollable.slideTo().
483                        this._duringSlideTo = true;
484                        var pos = this.getPos();
485                        var top = pos.y + this.panelNodes[1].offsetTop;
486                        var bottom = top + this.panelNodes[1].offsetHeight;
487                        var vh = this.domNode.parentNode.offsetHeight;
488                        var t;
489                        if(pos.y < to.y){ // going down
490                                if(bottom > vh){
491                                        // move up the bottom panel
492                                        t = this.panelNodes[2];
493                                        t.style.top = this.panelNodes[0].offsetTop - this.panelNodes[0].offsetHeight + "px";
494                                        this.panelNodes[2] = this.panelNodes[1];
495                                        this.panelNodes[1] = this.panelNodes[0];
496                                        this.panelNodes[0] = t;
497                                }
498                        }else if(pos.y > to.y){ // going up
499                                if(top < 0){
500                                        // move down the top panel
501                                        t = this.panelNodes[0];
502                                        t.style.top = this.panelNodes[2].offsetTop + this.panelNodes[2].offsetHeight + "px";
503                                        this.panelNodes[0] = this.panelNodes[1];
504                                        this.panelNodes[1] = this.panelNodes[2];
505                                        this.panelNodes[2] = t;
506                                }
507                        }
508                        if(this.getParent()._duringStartup){
509                                duration = 0; // to reduce flickers at start-up especially on android
510                        }else if(Math.abs(this._speed.y) < 40){
511                                duration = 0.2;
512                        }
513                        this.inherited(arguments, [to, duration, easing]); // 2nd arg is to avoid excessive optimization by closure compiler
514                        if(this.getParent()._duringStartup && !this._onFlickAnimationStartCalled){
515                                // during startup, because of duration set to 0, if onFlickAnimationStart()
516                                // has not been called (depends on scrollType value), the call of
517                                // onFlickAnimationEnd is missing, hence:
518                                this.onFlickAnimationEnd();
519                        }else if(!this._onFlickAnimationStartCalled){
520                                // if onFlickAnimationStart() wasn't called, and if slideTo() didn't call
521                                // itself onFlickAnimationEnd():
522                                this._duringSlideTo = false;
523                                // (otherwise, wait for onFlickAnimationEnd which deletes the flag)
524                        }
525                }
526        });
527
528        return has("dojo-bidi") ? declare("dojox.mobile.SpinWheelSlot", [SpinWheelSlot, BidiSpinWheelSlot]) : SpinWheelSlot;   
529});
Note: See TracBrowser for help on using the repository browser.