source: Dev/trunk/src/client/dojox/mobile/Switch.js @ 532

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

Added Dojo 1.9.3 release.

File size: 9.7 KB
Line 
1define([
2        "dojo/_base/array",
3        "dojo/_base/connect",
4        "dojo/_base/declare",
5        "dojo/_base/event",
6        "dojo/_base/window",
7        "dojo/dom-class",
8        "dojo/dom-construct",
9        "dojo/dom-style",
10        "dojo/dom-attr",
11        "dojo/touch",
12        "dijit/_Contained",
13        "dijit/_WidgetBase",
14        "./sniff",
15        "./_maskUtils",
16        "./common",
17        "dojo/has!dojo-bidi?dojox/mobile/bidi/Switch"
18], function(array, connect, declare, event, win, domClass, domConstruct, domStyle, domAttr, touch, Contained, WidgetBase, has, maskUtils, dm, BidiSwitch){
19
20        // module:
21        //              dojox/mobile/Switch
22
23        var Switch = declare(has("dojo-bidi") ? "dojox.mobile.NonBidiSwitch" : "dojox.mobile.Switch", [WidgetBase, Contained],{
24                // summary:
25                //              A toggle switch with a sliding knob.
26                // description:
27                //              Switch is a toggle switch with a sliding knob. You can either
28                //              tap or slide the knob to toggle the switch. The onStateChanged
29                //              handler is called when the switch is manipulated.
30
31                // value: String
32                //              The initial state of the switch: "on" or "off". The default
33                //              value is "on".
34                value: "on",
35
36                // name: String
37                //              A name for a hidden input field, which holds the current value.
38                name: "",
39
40                // leftLabel: String
41                //              The left-side label of the switch.
42                leftLabel: "ON",
43
44                // rightLabel: String
45                //              The right-side label of the switch.
46                rightLabel: "OFF",
47
48                // shape: String
49                //              The shape of the switch.
50                //              "mblSwDefaultShape", "mblSwSquareShape", "mblSwRoundShape1",
51                //              "mblSwRoundShape2", "mblSwArcShape1" or "mblSwArcShape2".
52                //              The default value is "mblSwDefaultShape".
53                shape: "mblSwDefaultShape",
54
55                // tabIndex: String
56                //              Tabindex setting for this widget so users can hit the tab key to
57                //              focus on it.
58                tabIndex: "0",
59                _setTabIndexAttr: "", // sets tabIndex to domNode
60
61                /* internal properties */
62                baseClass: "mblSwitch",
63                // role: [private] String
64                //              The accessibility role.
65                role: "", // a11y
66
67                buildRendering: function(){
68                        if(!this.templateString){ // true if this widget is not templated
69                                this.domNode = (this.srcNodeRef && this.srcNodeRef.tagName === "SPAN") ?
70                                        this.srcNodeRef : domConstruct.create("span");
71                        }
72                        // prevent browser scrolling on IE10 (evt.preventDefault() is not enough)
73                        if(typeof this.domNode.style.msTouchAction != "undefined"){
74                                this.domNode.style.msTouchAction = "none";
75                        }
76                        this.inherited(arguments);
77                        if(!this.templateString){ // true if this widget is not templated
78                                var c = (this.srcNodeRef && this.srcNodeRef.className) || this.className || this["class"];
79                                if((c = c.match(/mblSw.*Shape\d*/))){ this.shape = c; }
80                                domClass.add(this.domNode, this.shape);
81                                var nameAttr = this.name ? " name=\"" + this.name + "\"" : "";
82                                this.domNode.innerHTML =
83                                          '<div class="mblSwitchInner">'
84                                        +       '<div class="mblSwitchBg mblSwitchBgLeft">'
85                                        +               '<div class="mblSwitchText mblSwitchTextLeft"></div>'
86                                        +       '</div>'
87                                        +       '<div class="mblSwitchBg mblSwitchBgRight">'
88                                        +               '<div class="mblSwitchText mblSwitchTextRight"></div>'
89                                        +       '</div>'
90                                        +       '<div class="mblSwitchKnob"></div>'
91                                        +       '<input type="hidden"'+nameAttr+'></div>'
92                                        + '</div>';
93                                var n = this.inner = this.domNode.firstChild;
94                                this.left = n.childNodes[0];
95                                this.right = n.childNodes[1];
96                                this.knob = n.childNodes[2];
97                                this.input = n.childNodes[3];
98                        }
99                        domAttr.set(this.domNode, "role", "checkbox"); //a11y
100                        domAttr.set(this.domNode, "aria-checked", (this.value === "on") ? "true" : "false"); //a11y
101
102                        this.switchNode = this.domNode;
103
104                        if(has("windows-theme")) {
105                                var rootNode = domConstruct.create("div", {className: "mblSwitchContainer"});
106                                this.labelNode = domConstruct.create("label", {"class": "mblSwitchLabel", "for": this.id}, rootNode);
107                                rootNode.appendChild(this.domNode.cloneNode(true));
108                                this.domNode = rootNode;
109                                this.focusNode = rootNode.childNodes[1];
110                                this.labelNode.innerHTML = (this.value=="off") ? this.rightLabel : this.leftLabel;
111                                this.switchNode = this.domNode.childNodes[1];
112                                var inner = this.inner = this.domNode.childNodes[1].firstChild;
113                                this.left = inner.childNodes[0];
114                                this.right = inner.childNodes[1];
115                                this.knob = inner.childNodes[2];
116                                this.input = inner.childNodes[3];
117                        }
118                },
119
120                postCreate: function(){
121                        this.connect(this.switchNode, "onclick", "_onClick");
122                        this.connect(this.switchNode, "onkeydown", "_onClick"); // for desktop browsers
123                        this._startHandle = this.connect(this.switchNode, touch.press, "onTouchStart");
124                        this._initialValue = this.value; // for reset()
125                },
126
127                _changeState: function(/*String*/state, /*Boolean*/anim){
128                        var on = (state === "on");
129                        this.left.style.display = "";
130                        this.right.style.display = "";
131                        this.inner.style.left = "";
132                        if(anim){
133                                domClass.add(this.switchNode, "mblSwitchAnimation");
134                        }
135                        domClass.remove(this.switchNode, on ? "mblSwitchOff" : "mblSwitchOn");
136                        domClass.add(this.switchNode, on ? "mblSwitchOn" : "mblSwitchOff");
137                        domAttr.set(this.switchNode, "aria-checked", on ? "true" : "false"); //a11y
138
139                        var _this = this;
140                        _this.defer(function(){
141                                _this.left.style.display = on ? "" : "none";
142                                _this.right.style.display = !on ? "" : "none";
143                                domClass.remove(_this.switchNode, "mblSwitchAnimation");
144                        }, anim ? 300 : 0);
145                },
146
147                _createMaskImage: function(){
148                        if(this._timer){
149                                 this._timer.remove();
150                                 delete this._timer;
151                        }
152                        if(this._hasMaskImage){ return; }
153                        this._width = this.switchNode.offsetWidth - this.knob.offsetWidth;
154                        this._hasMaskImage = true;
155                        if(!(has("webkit")||has("svg"))){ return; }
156                        var rDef = domStyle.get(this.left, "borderTopLeftRadius");
157                        if(rDef == "0px"){ return; }
158                        var rDefs = rDef.split(" ");
159                        var rx = parseFloat(rDefs[0]), ry = (rDefs.length == 1) ? rx : parseFloat(rDefs[1]);
160                        var w = this.switchNode.offsetWidth, h = this.switchNode.offsetHeight;
161                        var id = (this.shape+"Mask"+w+h+rx+ry).replace(/\./,"_");
162                       
163                        maskUtils.createRoundMask(this.switchNode, 0, 0, 0, 0, w, h, rx, ry, 1);
164                },
165               
166                _onClick: function(e){
167                        // summary:
168                        //              Internal handler for click events.
169                        // tags:
170                        //              private
171                        if(e && e.type === "keydown" && e.keyCode !== 13){ return; }
172                        if(this.onClick(e) === false){ return; } // user's click action
173                        if(this._moved){ return; }
174                        this._set("value", this.input.value = (this.value == "on") ? "off" : "on");
175                        this._changeState(this.value, true);
176                        this.onStateChanged(this.value);
177                },
178
179                onClick: function(/*Event*/ /*===== e =====*/){
180                        // summary:
181                        //              User defined function to handle clicks
182                        // tags:
183                        //              callback
184                },
185
186                onTouchStart: function(/*Event*/e){
187                        // summary:
188                        //              Internal function to handle touchStart events.
189                        this._moved = false;
190                        this.innerStartX = this.inner.offsetLeft;
191                        if(!this._conn){
192                                this._conn = [
193                                        this.connect(this.inner, touch.move, "onTouchMove"),
194                                        this.connect(win.doc, touch.release, "onTouchEnd")
195                                ];
196
197                                /* While moving the slider knob sometimes IE fires MSPointerCancel event. That prevents firing
198                                MSPointerUP event (http://msdn.microsoft.com/ru-ru/library/ie/hh846776%28v=vs.85%29.aspx) so the
199                                knob can be stuck in the middle of the switch. As a fix we handle MSPointerCancel event with the
200                                same lintener as for MSPointerUp event.
201                                */
202                                if(has("windows-theme")){
203                                        this._conn.push(this.connect(win.doc, "MSPointerCancel", "onTouchEnd"));
204                                }
205                        }
206                        this.touchStartX = e.touches ? e.touches[0].pageX : e.clientX;
207                        this.left.style.display = "";
208                        this.right.style.display = "";
209                        event.stop(e);
210                        this._createMaskImage();
211                },
212
213                onTouchMove: function(/*Event*/e){
214                        // summary:
215                        //              Internal function to handle touchMove events.
216                        e.preventDefault();
217                        var dx;
218                        if(e.targetTouches){
219                                if(e.targetTouches.length != 1){ return; }
220                                dx = e.targetTouches[0].clientX - this.touchStartX;
221                        }else{
222                                dx = e.clientX - this.touchStartX;
223                        }
224                        var pos = this.innerStartX + dx;
225                        var d = 10;
226                        if(pos <= -(this._width-d)){ pos = -this._width; }
227                        if(pos >= -d){ pos = 0; }
228                        this.inner.style.left = pos + "px";
229                        if(Math.abs(dx) > d){
230                                this._moved = true;
231                        }
232                },
233
234                onTouchEnd: function(/*Event*/e){
235                        // summary:
236                        //              Internal function to handle touchEnd events.
237                        array.forEach(this._conn, connect.disconnect);
238                        this._conn = null;
239                        if(this.innerStartX == this.inner.offsetLeft){
240                                // need to send a synthetic click?
241                                if(has("touch") && has("clicks-prevented")){
242                                        dm._sendClick(this.inner, e);
243                                }
244                                return;
245                        }
246                        var newState = (this.inner.offsetLeft < -(this._width/2)) ? "off" : "on";
247                        newState = this._newState(newState);
248                        this._changeState(newState, true);
249                        if(newState != this.value){
250                                this._set("value", this.input.value = newState);
251                                this.onStateChanged(newState);
252                        }
253                },
254                _newState: function(newState){
255                        return newState;
256                },
257                onStateChanged: function(/*String*/newState){
258                        // summary:
259                        //              Stub function to connect to from your application.
260                        // description:
261                        //              Called when the state has been changed.
262                        if (this.labelNode) {
263                                this.labelNode.innerHTML = newState=='off' ? this.rightLabel : this.leftLabel;
264                        }
265                },
266
267                _setValueAttr: function(/*String*/value){
268                        this._changeState(value, false);
269                        if(this.value != value){
270                                this._set("value", this.input.value = value);
271                                this.onStateChanged(value);
272                        }
273                },
274
275                _setLeftLabelAttr: function(/*String*/label){
276                        this.leftLabel = label;
277                        this.left.firstChild.innerHTML = this._cv ? this._cv(label) : label;
278                },
279
280                _setRightLabelAttr: function(/*String*/label){
281                        this.rightLabel = label;
282                        this.right.firstChild.innerHTML = this._cv ? this._cv(label) : label;
283                },
284
285                reset: function(){
286                        // summary:
287                        //              Reset the widget's value to what it was at initialization time
288                        this.set("value", this._initialValue);
289                }
290        });
291
292        return has("dojo-bidi") ? declare("dojox.mobile.Switch", [Switch, BidiSwitch]) : Switch;               
293});
Note: See TracBrowser for help on using the repository browser.