You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
330 lines
11 KiB
330 lines
11 KiB
define([ |
|
"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 = |
|
'<div class="mblSwitchInner">' |
|
+ '<div class="mblSwitchBg mblSwitchBgLeft">' |
|
+ '<div class="mblSwitchText mblSwitchTextLeft"></div>' |
|
+ '</div>' |
|
+ '<div class="mblSwitchBg mblSwitchBgRight">' |
|
+ '<div class="mblSwitchText mblSwitchTextRight"></div>' |
|
+ '</div>' |
|
+ '<div class="mblSwitchKnob"></div>' |
|
+ '<input type="hidden"'+nameAttr+' value="'+this.value+'"></div>' |
|
+ '</div>'; |
|
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; |
|
});
|
|
|