devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
443 lines (366 loc) • 13.9 kB
JavaScript
"use strict";
var $ = require("../core/renderer"),
windowUtils = require("../core/utils/window"),
window = windowUtils.getWindow(),
eventsEngine = require("../events/core/events_engine"),
devices = require("../core/devices"),
extend = require("../core/utils/extend").extend,
inkRipple = require("./widget/utils.ink_ripple"),
registerComponent = require("../core/component_registrator"),
Editor = require("./editor/editor"),
eventUtils = require("../events/utils"),
feedbackEvents = require("../events/core/emitter.feedback"),
themes = require("./themes"),
fx = require("../animation/fx"),
messageLocalization = require("../localization/message"),
clickEvent = require("../events/click"),
Swipeable = require("../events/gesture/swipeable"),
Deferred = require("../core/utils/deferred").Deferred;
var SWITCH_CLASS = "dx-switch",
SWITCH_WRAPPER_CLASS = SWITCH_CLASS + "-wrapper",
SWITCH_CONTAINER_CLASS = SWITCH_CLASS + "-container",
SWITCH_INNER_CLASS = SWITCH_CLASS + "-inner",
SWITCH_HANDLE_CLASS = SWITCH_CLASS + "-handle",
SWITCH_ON_VALUE_CLASS = SWITCH_CLASS + "-on-value",
SWITCH_ON_CLASS = SWITCH_CLASS + "-on",
SWITCH_OFF_CLASS = SWITCH_CLASS + "-off",
SWITCH_ANIMATION_DURATION = 100;
/**
* @name dxSwitch
* @isEditor
* @publicName dxSwitch
* @inherits Editor
* @module ui/switch
* @export default
*/
var Switch = Editor.inherit({
_supportedKeys: function _supportedKeys() {
var isRTL = this.option("rtlEnabled");
var click = function click(e) {
e.preventDefault();
this._clickAction({ event: e });
},
move = function move(value, e) {
e.preventDefault();
e.stopPropagation();
this._animateValue(value);
};
return extend(this.callBase(), {
space: click,
enter: click,
leftArrow: move.bind(this, isRTL ? true : false),
rightArrow: move.bind(this, isRTL ? false : true)
});
},
_getDefaultOptions: function _getDefaultOptions() {
return extend(this.callBase(), {
/**
* @name dxSwitchOptions.hoverStateEnabled
* @publicName hoverStateEnabled
* @type boolean
* @default true
* @inheritdoc
*/
hoverStateEnabled: true,
/**
* @name dxSwitchOptions.activeStateEnabled
* @publicName activeStateEnabled
* @type boolean
* @default true
* @inheritdoc
*/
activeStateEnabled: true,
/**
* @name dxSwitchOptions.onText
* @publicName onText
* @type string
* @default "ON"
*/
onText: messageLocalization.format("dxSwitch-onText"),
/**
* @name dxSwitchOptions.offText
* @publicName offText
* @type string
* @default "OFF"
*/
offText: messageLocalization.format("dxSwitch-offText"),
/**
* @name dxSwitchOptions.value
* @publicName value
* @type boolean
* @default false
*/
value: false,
useInkRipple: false
/**
* @name dxSwitchOptions.name
* @publicName name
* @type string
* @hidden false
* @inheritdoc
*/
});
},
_defaultOptionsRules: function _defaultOptionsRules() {
return this.callBase().concat([{
device: function device() {
return devices.real().deviceType === "desktop" && !devices.isSimulator();
},
options: {
/**
* @name dxSwitchOptions.focusStateEnabled
* @publicName focusStateEnabled
* @type boolean
* @default true @for desktop
* @inheritdoc
*/
focusStateEnabled: true
}
}, {
device: function device() {
return (/android5/.test(themes.current())
);
},
options: {
useInkRipple: true
}
}]);
},
_feedbackHideTimeout: 0,
_animating: false,
_initMarkup: function _initMarkup() {
this._renderContainers();
this.option("useInkRipple") && this._renderInkRipple();
this.$element().addClass(SWITCH_CLASS).append(this._$switchWrapper);
this._renderSubmitElement();
this._renderClick();
this.setAria("role", "button");
this._renderSwipeable();
this.callBase();
},
_render: function _render() {
this._renderSwitchInner();
this._renderLabels();
this._renderHandleWidth();
this._renderValue();
this.callBase();
},
_renderHandleWidth: function _renderHandleWidth() {
this._handleWidth = parseFloat(window.getComputedStyle(this._$handle.get(0)).width);
},
_getCalcOffset: function _getCalcOffset(value, offset) {
var ratio = offset - Number(!value);
return "calc(" + 100 * ratio + "% + " + -this._getHandleWidth() * ratio + "px)";
},
_getHandleWidth: function _getHandleWidth() {
!this._handleWidth && this._renderHandleWidth();
return this._handleWidth;
},
_getPixelOffset: function _getPixelOffset(value, offset) {
return this._getMarginBound() * (offset - Number(!value));
},
_renderSwitchInner: function _renderSwitchInner() {
this._$switchInner = $("<div>").addClass(SWITCH_INNER_CLASS).appendTo(this._$switchContainer);
this._$handle = $("<div>").addClass(SWITCH_HANDLE_CLASS).appendTo(this._$switchInner);
},
_renderLabels: function _renderLabels() {
this._$labelOn = $("<div>").addClass(SWITCH_ON_CLASS).prependTo(this._$switchInner);
this._$labelOff = $("<div>").addClass(SWITCH_OFF_CLASS).appendTo(this._$switchInner);
this._setLabelsText();
},
_renderContainers: function _renderContainers() {
this._$switchContainer = $("<div>").addClass(SWITCH_CONTAINER_CLASS);
this._$switchWrapper = $("<div>").addClass(SWITCH_WRAPPER_CLASS).append(this._$switchContainer);
},
_renderSwipeable: function _renderSwipeable() {
this._createComponent(this.$element(), Swipeable, {
elastic: false,
immediate: true,
onStart: this._swipeStartHandler.bind(this),
onUpdated: this._swipeUpdateHandler.bind(this),
onEnd: this._swipeEndHandler.bind(this),
itemSizeFunc: this._getMarginBound.bind(this)
});
},
_renderSubmitElement: function _renderSubmitElement() {
this._$submitElement = $("<input>").attr("type", "hidden").appendTo(this.$element());
},
_getSubmitElement: function _getSubmitElement() {
return this._$submitElement;
},
_renderInkRipple: function _renderInkRipple() {
this._inkRipple = inkRipple.render({
waveSizeCoefficient: 1.7,
isCentered: true,
useHoldAnimation: false,
wavesNumber: 2
});
},
_renderInkWave: function _renderInkWave(element, dxEvent, doRender, waveIndex) {
if (!this._inkRipple) {
return;
}
var config = {
element: element,
event: dxEvent,
wave: waveIndex
};
if (doRender) {
this._inkRipple.showWave(config);
} else {
this._inkRipple.hideWave(config);
}
},
_updateFocusState: function _updateFocusState(e, value) {
this.callBase.apply(this, arguments);
this._renderInkWave(this._$handle, e, value, 0);
},
_toggleActiveState: function _toggleActiveState($element, value, e) {
this.callBase.apply(this, arguments);
this._renderInkWave(this._$handle, e, value, 1);
},
_getMarginBound: function _getMarginBound() {
if (!this._marginBound) {
this._marginBound = this._$switchContainer.outerWidth(true) - this._getHandleWidth();
}
return this._marginBound;
},
_marginDirection: function _marginDirection() {
return this.option("rtlEnabled") ? "Right" : "Left";
},
_offsetDirection: function _offsetDirection() {
return this.option("rtlEnabled") ? -1 : 1;
},
_renderPosition: function _renderPosition(state, swipeOffset) {
if (!windowUtils.hasWindow()) {
return;
}
var marginDirection = this._marginDirection(),
resetMarginDirection = marginDirection === "Left" ? "Right" : "Left";
this._$switchInner.css("margin" + marginDirection, this._getCalcOffset(state, swipeOffset));
this._$switchInner.css("margin" + resetMarginDirection, 0);
},
_validateValue: function _validateValue() {
var check = this.option("value");
if (typeof check !== "boolean") {
this._options["value"] = !!check;
}
},
_renderClick: function _renderClick() {
var eventName = eventUtils.addNamespace(clickEvent.name, this.NAME);
var $element = this.$element();
this._clickAction = this._createAction(this._clickHandler.bind(this));
eventsEngine.off($element, eventName);
eventsEngine.on($element, eventName, function (e) {
this._clickAction({ event: e });
}.bind(this));
},
_clickHandler: function _clickHandler(args) {
var e = args.event;
this._saveValueChangeEvent(e);
if (this._animating || this._swiping) {
return;
}
this._animateValue(!this.option("value"));
},
_animateValue: function _animateValue(value) {
var startValue = this.option("value"),
endValue = value;
if (startValue === endValue) {
return;
}
this._animating = true;
var that = this,
marginDirection = this._marginDirection(),
resetMarginDirection = marginDirection === "Left" ? "Right" : "Left",
fromConfig = {},
toConfig = {};
this._$switchInner.css("margin" + resetMarginDirection, 0);
fromConfig["margin" + marginDirection] = this._getCalcOffset(startValue, 0);
toConfig["margin" + marginDirection] = this._getCalcOffset(endValue, 0);
fx.animate(this._$switchInner, {
from: fromConfig,
to: toConfig,
duration: SWITCH_ANIMATION_DURATION,
complete: function complete() {
that._animating = false;
that.option("value", endValue);
}
});
},
_swipeStartHandler: function _swipeStartHandler(e) {
var state = this.option("value"),
rtlEnabled = this.option("rtlEnabled"),
maxOffOffset = rtlEnabled ? 0 : 1,
maxOnOffset = rtlEnabled ? 1 : 0;
e.event.maxLeftOffset = state ? maxOffOffset : maxOnOffset;
e.event.maxRightOffset = state ? maxOnOffset : maxOffOffset;
this._swiping = true;
this._feedbackDeferred = new Deferred();
feedbackEvents.lock(this._feedbackDeferred);
this._toggleActiveState(this.$element(), this.option("activeStateEnabled"));
},
_swipeUpdateHandler: function _swipeUpdateHandler(e) {
this._renderPosition(this.option("value"), this._offsetDirection() * e.event.offset);
},
_swipeEndHandler: function _swipeEndHandler(e) {
var that = this,
offsetDirection = this._offsetDirection(),
toConfig = {};
toConfig["margin" + this._marginDirection()] = this._getCalcOffset(that.option("value"), offsetDirection * e.event.targetOffset);
fx.animate(this._$switchInner, {
to: toConfig,
duration: SWITCH_ANIMATION_DURATION,
complete: function complete() {
that._swiping = false;
var pos = that.option("value") + offsetDirection * e.event.targetOffset;
that.option("value", Boolean(pos));
that._feedbackDeferred.resolve();
that._toggleActiveState(that.$element(), false);
}
});
},
_renderValue: function _renderValue() {
this._validateValue();
var val = this.option("value");
this._renderPosition(val, 0);
this.$element().toggleClass(SWITCH_ON_VALUE_CLASS, val);
this._$submitElement.val(val);
this.setAria({
"pressed": val,
"label": val ? this.option("onText") : this.option("offText")
});
},
_setLabelsText: function _setLabelsText() {
this._$labelOn && this._$labelOn.text(this.option("onText"));
this._$labelOff && this._$labelOff.text(this.option("offText"));
},
_visibilityChanged: function _visibilityChanged(visible) {
if (visible) {
this.repaint();
}
},
_optionChanged: function _optionChanged(args) {
switch (args.name) {
case "useInkRipple":
this._invalidate();
break;
case "width":
delete this._marginBound;
this._refresh();
break;
case "onText":
case "offText":
this._setLabelsText();
break;
case "value":
this._renderValue();
this.callBase(args);
break;
default:
this.callBase(args);
}
}
});
registerComponent("dxSwitch", Switch);
module.exports = Switch;