define("dojox/mobile/Switch", [
"dojo/_base/array",
"dojo/_base/connect",
"dojo/_base/declare",
"dojo/_base/event",
"dojo/_base/window",
"dojo/dom-class",
"dojo/dom-construct",
"dojo/dom-style",
"dojo/dom-attr",
"dojo/touch",
"dijit/_Contained",
"dijit/_WidgetBase",
"./sniff",
"./_maskUtils",
"./common",
"dojo/has!dojo-bidi?dojox/mobile/bidi/Switch"
], function(array, connect, declare, event, win, domClass, domConstruct, domStyle, domAttr, touch, Contained, WidgetBase, has, maskUtils, dm, BidiSwitch){
// module:
// dojox/mobile/Switch
var Switch = declare(has("dojo-bidi") ? "dojox.mobile.NonBidiSwitch" : "dojox.mobile.Switch", [WidgetBase, Contained],{
// summary:
// A toggle switch with a sliding knob.
// description:
// Switch is a toggle switch with a sliding knob. You can either
// tap or slide the knob to toggle the switch. The onStateChanged
// handler is called when the switch is manipulated.
// value: String
// The initial state of the switch: "on" or "off". The default
// value is "on".
value: "on",
// name: String
// A name for a hidden input field, which holds the current value.
name: "",
// leftLabel: String
// The left-side label of the switch.
leftLabel: "ON",
// rightLabel: String
// The right-side label of the switch.
rightLabel: "OFF",
// shape: String
// The shape of the switch.
// "mblSwDefaultShape", "mblSwSquareShape", "mblSwRoundShape1",
// "mblSwRoundShape2", "mblSwArcShape1" or "mblSwArcShape2".
// The default value is "mblSwDefaultShape".
shape: "mblSwDefaultShape",
// tabIndex: String
// Tabindex setting for this widget so users can hit the tab key to
// focus on it.
tabIndex: "0",
_setTabIndexAttr: "", // sets tabIndex to domNode
/* internal properties */
baseClass: "mblSwitch",
// role: [private] String
// The accessibility role.
role: "", // a11y
buildRendering: function(){
if(!this.templateString){ // true if this widget is not templated
this.domNode = (this.srcNodeRef && this.srcNodeRef.tagName === "SPAN") ?
this.srcNodeRef : domConstruct.create("span");
}
// prevent browser scrolling on IE10 (evt.preventDefault() is not enough)
dm._setTouchAction(this.domNode, "none");
this.inherited(arguments);
if(!this.templateString){ // true if this widget is not templated
var c = (this.srcNodeRef && this.srcNodeRef.className) || this.className || this["class"];
if((c = c.match(/mblSw.*Shape\d*/))){ this.shape = c; }
domClass.add(this.domNode, this.shape);
var nameAttr = this.name ? " name=\"" + this.name + "\"" : "";
this.domNode.innerHTML =
'
'
+ '';
var n = this.inner = this.domNode.firstChild;
this.left = n.childNodes[0];
this.right = n.childNodes[1];
this.knob = n.childNodes[2];
this.input = n.childNodes[3];
}
domAttr.set(this.domNode, "role", "checkbox"); //a11y
domAttr.set(this.domNode, "aria-checked", (this.value === "on") ? "true" : "false"); //a11y
this.switchNode = this.domNode;
if(has("windows-theme")) {
var rootNode = domConstruct.create("div", {className: "mblSwitchContainer"});
this.labelNode = domConstruct.create("label", {"class": "mblSwitchLabel", "for": this.id}, rootNode);
rootNode.appendChild(this.domNode.cloneNode(true));
this.domNode = rootNode;
this.focusNode = rootNode.childNodes[1];
this.labelNode.innerHTML = (this.value=="off") ? this.rightLabel : this.leftLabel;
this.switchNode = this.domNode.childNodes[1];
var inner = this.inner = this.domNode.childNodes[1].firstChild;
this.left = inner.childNodes[0];
this.right = inner.childNodes[1];
this.knob = inner.childNodes[2];
this.input = inner.childNodes[3];
}
},
postCreate: function(){
this.connect(this.switchNode, "onclick", "_onClick");
this.connect(this.switchNode, "onkeydown", "_onClick"); // for desktop browsers
this._startHandle = this.connect(this.switchNode, touch.press, "onTouchStart");
this._initialValue = this.value; // for reset()
},
startup: function(){
var started = this._started;
this.inherited(arguments);
if(!started){
this.resize();
}
},
resize: function(){
if(has("windows-theme")){
// Override the custom CSS width (if any) to avoid misplacement.
// Per design, the windows theme does not allow resizing the controls.
// The label of the switch is placed next to the switch, and a custom
// width would only have the effect to increase the distance between the
// label and the switch, which is undesired. Hence, on windows theme,
// ensure the width of root DOM node is 100%.
domStyle.set(this.domNode, "width", "100%");
}else{
var value = domStyle.get(this.domNode,"width");
var outWidth = value + "px";
var innWidth = (value - domStyle.get(this.knob,"width")) + "px";
domStyle.set(this.left, "width", outWidth);
domStyle.set(this.right,this.isLeftToRight()?{width: outWidth, left: innWidth}:{width: outWidth});
domStyle.set(this.left.firstChild, "width", innWidth);
domStyle.set(this.right.firstChild, "width", innWidth);
domStyle.set(this.knob, "left", innWidth);
if(this.value == "off"){
domStyle.set(this.inner, "left", this.isLeftToRight()?("-" + innWidth):0);
}
this._hasMaskImage = false;
this._createMaskImage();
}
},
_changeState: function(/*String*/state, /*Boolean*/anim){
var on = (state === "on");
this.left.style.display = "";
this.right.style.display = "";
this.inner.style.left = "";
if(anim){
domClass.add(this.switchNode, "mblSwitchAnimation");
}
domClass.remove(this.switchNode, on ? "mblSwitchOff" : "mblSwitchOn");
domClass.add(this.switchNode, on ? "mblSwitchOn" : "mblSwitchOff");
domAttr.set(this.switchNode, "aria-checked", on ? "true" : "false"); //a11y
if(!on && !has("windows-theme")){
this.inner.style.left = (this.isLeftToRight()?(-(domStyle.get(this.domNode,"width") - domStyle.get(this.knob,"width"))):0) + "px";
}
var _this = this;
_this.defer(function(){
_this.left.style.display = on ? "" : "none";
_this.right.style.display = !on ? "" : "none";
domClass.remove(_this.switchNode, "mblSwitchAnimation");
}, anim ? 300 : 0);
},
_createMaskImage: function(){
if(this._timer){
this._timer.remove();
delete this._timer;
}
if(this._hasMaskImage){ return; }
var w = domStyle.get(this.domNode,"width"), h = domStyle.get(this.domNode,"height");
this._width = (w - domStyle.get(this.knob,"width"));
this._hasMaskImage = true;
if(!(has("mask-image"))){ return; }
var rDef = domStyle.get(this.left, "borderTopLeftRadius");
if(!rDef || rDef == "0px"){ return; }
var rDefs = rDef.split(" ");
var rx = parseFloat(rDefs[0]), ry = (rDefs.length == 1) ? rx : parseFloat(rDefs[1]);
if(rx && ry){
maskUtils.createRoundMask(this.switchNode, 0, 0, 0, 0, w, h, rx, ry, 1);
}
},
_onClick: function(e){
// summary:
// Internal handler for click events.
// tags:
// private
if(e && e.type === "keydown" && e.keyCode !== 13){ return; }
if(this.onClick(e) === false){ return; } // user's click action
if(this._moved){ return; }
this._set("value", this.input.value = (this.value == "on") ? "off" : "on");
this._changeState(this.value, true);
this.onStateChanged(this.value);
},
onClick: function(/*Event*/ /*===== e =====*/){
// summary:
// User defined function to handle clicks
// tags:
// callback
},
onTouchStart: function(/*Event*/e){
// summary:
// Internal function to handle touchStart events.
this._moved = false;
this.innerStartX = this.inner.offsetLeft;
if(!this._conn){
this._conn = [
this.connect(this.inner, touch.move, "onTouchMove"),
this.connect(win.doc, touch.release, "onTouchEnd")
];
/* While moving the slider knob sometimes IE fires MSPointerCancel event. That prevents firing
MSPointerUp event (http://msdn.microsoft.com/ru-ru/library/ie/hh846776%28v=vs.85%29.aspx) so the
knob can be stuck in the middle of the switch. As a fix we handle MSPointerCancel event with the
same lintener as for MSPointerUp event.
*/
if(has("windows-theme")){
this._conn.push(this.connect(win.doc, "MSPointerCancel", "onTouchEnd"));
}
}
this.touchStartX = e.touches ? e.touches[0].pageX : e.clientX;
this.left.style.display = "";
this.right.style.display = "";
event.stop(e);
this._createMaskImage();
},
onTouchMove: function(/*Event*/e){
// summary:
// Internal function to handle touchMove events.
e.preventDefault();
var dx;
if(e.targetTouches){
if(e.targetTouches.length != 1){ return; }
dx = e.targetTouches[0].clientX - this.touchStartX;
}else{
dx = e.clientX - this.touchStartX;
}
var pos = this.innerStartX + dx;
var d = 10;
if(pos <= -(this._width-d)){ pos = -this._width; }
if(pos >= -d){ pos = 0; }
this.inner.style.left = pos + "px";
if(Math.abs(dx) > d){
this._moved = true;
}
},
onTouchEnd: function(/*Event*/e){
// summary:
// Internal function to handle touchEnd events.
array.forEach(this._conn, connect.disconnect);
this._conn = null;
if(this.innerStartX == this.inner.offsetLeft){
// need to send a synthetic click?
if(has("touch") && has("clicks-prevented")){
dm._sendClick(this.inner, e);
}
return;
}
var newState = (this.inner.offsetLeft < -(this._width/2)) ? "off" : "on";
newState = this._newState(newState);
this._changeState(newState, true);
if(newState != this.value){
this._set("value", this.input.value = newState);
this.onStateChanged(newState);
}
},
_newState: function(newState){
return newState;
},
onStateChanged: function(/*String*/newState){
// summary:
// Stub function to connect to from your application.
// description:
// Called when the state has been changed.
if (this.labelNode) {
this.labelNode.innerHTML = newState=='off' ? this.rightLabel : this.leftLabel;
}
},
_setValueAttr: function(/*String*/value){
this._changeState(value, false);
if(this.value != value){
this._set("value", this.input.value = value);
this.onStateChanged(value);
}
},
_setLeftLabelAttr: function(/*String*/label){
this.leftLabel = label;
this.left.firstChild.innerHTML = this._cv ? this._cv(label) : label;
},
_setRightLabelAttr: function(/*String*/label){
this.rightLabel = label;
this.right.firstChild.innerHTML = this._cv ? this._cv(label) : label;
},
reset: function(){
// summary:
// Reset the widget's value to what it was at initialization time
this.set("value", this._initialValue);
}
});
return has("dojo-bidi") ? declare("dojox.mobile.Switch", [Switch, BidiSwitch]) : Switch;
});