[483] | 1 | define([ |
---|
| 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 | }); |
---|