devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
678 lines (562 loc) • 21.7 kB
JavaScript
"use strict";
var $ = require("../../core/renderer"),
eventsEngine = require("../../events/core/events_engine"),
domUtils = require("../../core/utils/dom"),
numberLocalization = require("../../localization/number"),
devices = require("../../core/devices"),
extend = require("../../core/utils/extend").extend,
applyServerDecimalSeparator = require("../../core/utils/common").applyServerDecimalSeparator,
registerComponent = require("../../core/component_registrator"),
TrackBar = require("../track_bar"),
eventUtils = require("../../events/utils"),
pointerEvents = require("../../events/pointer"),
feedbackEvents = require("../../events/core/emitter.feedback"),
SliderHandle = require("./ui.slider_handle"),
inkRipple = require("../widget/utils.ink_ripple"),
clickEvent = require("../../events/click"),
Swipeable = require("../../events/gesture/swipeable"),
themes = require("../themes"),
Deferred = require("../../core/utils/deferred").Deferred;
var SLIDER_CLASS = "dx-slider",
SLIDER_WRAPPER_CLASS = "dx-slider-wrapper",
SLIDER_HANDLE_SELECTOR = ".dx-slider-handle",
SLIDER_BAR_CLASS = "dx-slider-bar",
SLIDER_RANGE_CLASS = "dx-slider-range",
SLIDER_RANGE_VISIBLE_CLASS = "dx-slider-range-visible",
SLIDER_LABEL_CLASS = "dx-slider-label",
SLIDER_LABEL_POSITION_CLASS_PREFIX = "dx-slider-label-position-",
SLIDER_TOOLTIP_POSITION_CLASS_PREFIX = "dx-slider-tooltip-position-";
/**
* @name dxSliderBase
* @publicName dxSliderBase
* @inherits dxTrackBar
* @hidden
*/
var Slider = TrackBar.inherit({
_activeStateUnit: SLIDER_HANDLE_SELECTOR,
_supportedKeys: function _supportedKeys() {
var isRTL = this.option("rtlEnabled");
var that = this;
var roundedValue = function roundedValue(offset, isLeftDirection) {
offset = that._valueStep(offset);
var step = that.option("step");
var value = that.option("value");
var division = (value - that.option("min")) % step;
var result = isLeftDirection ? value - offset + (division ? step - division : 0) : value + offset - division;
var min = that.option("min"),
max = that.option("max");
if (result < min) {
result = min;
} else if (result > max) {
result = max;
}
return result;
};
var moveHandleRight = function moveHandleRight(offset) {
that.option("value", roundedValue(offset, isRTL));
};
var moveHandleLeft = function moveHandleLeft(offset) {
that.option("value", roundedValue(offset, !isRTL));
};
return extend(this.callBase(), {
leftArrow: function leftArrow(e) {
e.preventDefault();
e.stopPropagation();
moveHandleLeft(this.option("step"));
},
rightArrow: function rightArrow(e) {
e.preventDefault();
e.stopPropagation();
moveHandleRight(this.option("step"));
},
pageUp: function pageUp(e) {
e.preventDefault();
e.stopPropagation();
moveHandleRight(this.option("step") * this.option("keyStep"));
},
pageDown: function pageDown(e) {
e.preventDefault();
e.stopPropagation();
moveHandleLeft(this.option("step") * this.option("keyStep"));
},
home: function home(e) {
e.preventDefault();
e.stopPropagation();
var min = this.option("min");
this.option("value", min);
},
end: function end(e) {
e.preventDefault();
e.stopPropagation();
var max = this.option("max");
this.option("value", max);
}
});
},
_getDefaultOptions: function _getDefaultOptions() {
return extend(this.callBase(), {
/**
* @name dxSliderOptions.value
* @publicName value
* @type number
* @default 50
*/
value: 50,
/**
* @name dxSliderBaseOptions.hoverStateEnabled
* @publicName hoverStateEnabled
* @type boolean
* @default true
* @inheritdoc
*/
hoverStateEnabled: true,
/**
* @name dxSliderBaseOptions.activeStateEnabled
* @publicName activeStateEnabled
* @type boolean
* @default true
* @inheritdoc
*/
activeStateEnabled: true,
/**
* @name dxSliderBaseOptions.step
* @publicName step
* @type number
* @default 1
*/
step: 1,
/**
* @name dxSliderBaseOptions.showRange
* @publicName showRange
* @type boolean
* @default true
*/
showRange: true,
/**
* @name dxSliderBaseOptions.tooltip
* @publicName tooltip
* @type object
*/
tooltip: {
/**
* @name dxSliderBaseOptions.tooltip.enabled
* @publicName enabled
* @type boolean
* @default false
*/
enabled: false,
/**
* @name dxSliderBaseOptions.tooltip.format
* @publicName format
* @type format
* @default function(value) { return value }
*/
format: function format(value) {
return value;
},
/**
* @name dxSliderBaseOptions.tooltip.position
* @publicName position
* @type Enums.VerticalEdge
* @default 'top'
*/
position: "top",
/**
* @name dxSliderBaseOptions.tooltip.showMode
* @publicName showMode
* @type Enums.SliderTooltipShowMode
* @default 'onHover'
*/
showMode: "onHover"
},
/**
* @name dxSliderBaseOptions.label
* @publicName label
* @type object
*/
label: {
/**
* @name dxSliderBaseOptions.label.visible
* @publicName visible
* @type boolean
* @default false
*/
visible: false,
/**
* @name dxSliderBaseOptions.label.position
* @publicName position
* @type Enums.VerticalEdge
* @default 'bottom'
*/
position: "bottom",
/**
* @name dxSliderBaseOptions.label.format
* @publicName format
* @type format
* @default function(value) { return value }
*/
format: function format(value) {
return value;
}
},
/**
* @name dxSliderBaseOptions.keyStep
* @publicName keyStep
* @type number
* @default 1
*/
keyStep: 1,
useInkRipple: false
/**
* @name dxSliderBaseOptions.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 dxSliderBaseOptions.focusStateEnabled
* @publicName focusStateEnabled
* @default true @for desktop
* @inheritdoc
*/
focusStateEnabled: true
}
}, {
device: function device() {
return (/android5|material/.test(themes.current())
);
},
options: {
useInkRipple: true
}
}]);
},
_initMarkup: function _initMarkup() {
this.$element().addClass(SLIDER_CLASS);
this._renderSubmitElement();
this.option("useInkRipple") && this._renderInkRipple();
this.callBase();
this._renderLabels();
this._renderStartHandler();
this._renderAriaMinAndMax();
},
_render: function _render() {
this.callBase();
this._repaintHandle();
},
_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: 0.7,
isCentered: true,
wavesNumber: 2,
useHoldAnimation: false
});
},
_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);
}
},
_visibilityChanged: function _visibilityChanged() {
this.repaint();
},
_renderWrapper: function _renderWrapper() {
this.callBase();
this._$wrapper.addClass(SLIDER_WRAPPER_CLASS);
this._createComponent(this._$wrapper, Swipeable, {
elastic: false,
immediate: true,
onStart: this._swipeStartHandler.bind(this),
onUpdated: this._swipeUpdateHandler.bind(this),
onEnd: this._swipeEndHandler.bind(this),
itemSizeFunc: this._itemWidthFunc.bind(this)
});
},
_renderContainer: function _renderContainer() {
this.callBase();
this._$bar.addClass(SLIDER_BAR_CLASS);
},
_renderRange: function _renderRange() {
this.callBase();
this._$range.addClass(SLIDER_RANGE_CLASS);
this._renderHandle();
this._renderRangeVisibility();
},
_renderRangeVisibility: function _renderRangeVisibility() {
this._$range.toggleClass(SLIDER_RANGE_VISIBLE_CLASS, Boolean(this.option("showRange")));
},
_renderHandle: function _renderHandle() {
this._$handle = this._renderHandleImpl(this.option("value"), this._$handle);
},
_renderHandleImpl: function _renderHandleImpl(value, $element) {
var $handle = $element || $("<div>").appendTo(this._$range),
format = this.option("tooltip.format"),
tooltipEnabled = this.option("tooltip.enabled"),
tooltipPosition = this.option("tooltip.position");
this.$element().toggleClass(SLIDER_TOOLTIP_POSITION_CLASS_PREFIX + "bottom", tooltipEnabled && tooltipPosition === "bottom").toggleClass(SLIDER_TOOLTIP_POSITION_CLASS_PREFIX + "top", tooltipEnabled && tooltipPosition === "top");
this._createComponent($handle, SliderHandle, {
value: value,
tooltipEnabled: tooltipEnabled,
tooltipPosition: tooltipPosition,
tooltipFormat: format,
tooltipShowMode: this.option("tooltip.showMode"),
tooltipFitIn: this.$element()
});
return $handle;
},
_renderAriaMinAndMax: function _renderAriaMinAndMax() {
this.setAria({
"valuemin": this.option("min"),
"valuemax": this.option("max")
}, this._$handle);
},
_hoverStartHandler: function _hoverStartHandler(e) {
SliderHandle.getInstance($(e.currentTarget)).updateTooltip();
},
_toggleActiveState: function _toggleActiveState($element, value) {
this.callBase($element, value);
if (value) {
SliderHandle.getInstance($element).updateTooltip();
}
this._renderInkWave($element, null, !!value, 1);
},
_toggleFocusClass: function _toggleFocusClass(isFocused, $element) {
this.callBase(isFocused, $element);
if (this._disposed) {
return;
}
var $focusTarget = $($element || this._focusTarget());
this._renderInkWave($focusTarget, null, isFocused, 0);
},
_renderLabels: function _renderLabels() {
this.$element().removeClass(SLIDER_LABEL_POSITION_CLASS_PREFIX + "bottom").removeClass(SLIDER_LABEL_POSITION_CLASS_PREFIX + "top");
if (this.option("label.visible")) {
var min = this.option("min"),
max = this.option("max"),
position = this.option("label.position"),
labelFormat = this.option("label.format");
if (!this._$minLabel) {
this._$minLabel = $("<div>").addClass(SLIDER_LABEL_CLASS).appendTo(this._$wrapper);
}
this._$minLabel.html(numberLocalization.format(min, labelFormat));
if (!this._$maxLabel) {
this._$maxLabel = $("<div>").addClass(SLIDER_LABEL_CLASS).appendTo(this._$wrapper);
}
this._$maxLabel.html(numberLocalization.format(max, labelFormat));
this.$element().addClass(SLIDER_LABEL_POSITION_CLASS_PREFIX + position);
} else {
if (this._$minLabel) {
this._$minLabel.remove();
delete this._$minLabel;
}
if (this._$maxLabel) {
this._$maxLabel.remove();
delete this._$maxLabel;
}
}
},
_renderStartHandler: function _renderStartHandler() {
var pointerDownEventName = eventUtils.addNamespace(pointerEvents.down, this.NAME);
var clickEventName = eventUtils.addNamespace(clickEvent.name, this.NAME);
var startAction = this._createAction(this._startHandler.bind(this));
var $element = this.$element();
eventsEngine.off($element, pointerDownEventName);
eventsEngine.on($element, pointerDownEventName, function (e) {
if (eventUtils.isMouseEvent(e)) {
startAction({ event: e });
}
});
eventsEngine.off($element, clickEventName);
eventsEngine.on($element, clickEventName, function (e) {
var $handle = this._activeHandle();
if ($handle) {
eventsEngine.trigger($handle, "focusin");
eventsEngine.trigger($handle, "focus");
}
startAction({ event: e });
}.bind(this));
},
_itemWidthFunc: function _itemWidthFunc() {
return this._itemWidthRatio;
},
_swipeStartHandler: function _swipeStartHandler(e) {
var rtlEnabled = this.option("rtlEnabled"),
startOffset,
endOffset;
if (eventUtils.isTouchEvent(e.event)) {
this._createAction(this._startHandler.bind(this))({ event: e.event });
}
this._feedbackDeferred = new Deferred();
feedbackEvents.lock(this._feedbackDeferred);
this._toggleActiveState(this._activeHandle(), this.option("activeStateEnabled"));
this._startOffset = this._currentRatio;
startOffset = this._startOffset * this._swipePixelRatio();
endOffset = (1 - this._startOffset) * this._swipePixelRatio();
e.event.maxLeftOffset = rtlEnabled ? endOffset : startOffset;
e.event.maxRightOffset = rtlEnabled ? startOffset : endOffset;
this._itemWidthRatio = this.$element().width() / this._swipePixelRatio();
this._needPreventAnimation = true;
},
_swipeEndHandler: function _swipeEndHandler(e) {
this._feedbackDeferred.resolve();
this._toggleActiveState(this._activeHandle(), false);
var offsetDirection = this.option("rtlEnabled") ? -1 : 1;
delete this._needPreventAnimation;
this._changeValueOnSwipe(this._startOffset + offsetDirection * e.event.targetOffset / this._swipePixelRatio());
delete this._startOffset;
this._renderValue();
},
_activeHandle: function _activeHandle() {
return this._$handle;
},
_swipeUpdateHandler: function _swipeUpdateHandler(e) {
this._saveValueChangeEvent(e);
this._updateHandlePosition(e);
},
_updateHandlePosition: function _updateHandlePosition(e) {
var offsetDirection = this.option("rtlEnabled") ? -1 : 1;
var newRatio = this._startOffset + offsetDirection * e.event.offset / this._swipePixelRatio();
this._$range.width(newRatio * 100 + "%");
SliderHandle.getInstance(this._activeHandle())["fitTooltipPosition"];
this._changeValueOnSwipe(newRatio);
},
_swipePixelRatio: function _swipePixelRatio() {
var min = this.option("min"),
max = this.option("max"),
step = this._valueStep(this.option("step"));
return (max - min) / step;
},
_valueStep: function _valueStep(step) {
if (!step || isNaN(step)) {
step = 1;
}
step = parseFloat(step.toFixed(5));
// TODO or exception?
if (step === 0) {
step = 0.00001;
}
return step;
},
_changeValueOnSwipe: function _changeValueOnSwipe(ratio) {
var min = this.option("min"),
max = this.option("max"),
step = this._valueStep(this.option("step")),
newChange = ratio * (max - min),
newValue = min + newChange;
if (step < 0) {
return;
}
if (newValue === max || newValue === min) {
this._setValueOnSwipe(newValue);
} else {
var stepExponent = (step + "").split(".")[1];
var minExponent = (min + "").split(".")[1];
var exponentLength = Math.max(stepExponent && stepExponent.length || 0, minExponent && minExponent.length || 0);
var stepCount = Math.round((newValue - min) / step);
newValue = Number((stepCount * step + min).toFixed(exponentLength));
this._setValueOnSwipe(Math.max(Math.min(newValue, max), min));
}
},
_setValueOnSwipe: function _setValueOnSwipe(value) {
this.option("value", value);
},
_startHandler: function _startHandler(args) {
var e = args.event;
this._currentRatio = (eventUtils.eventData(e).x - this._$bar.offset().left) / this._$bar.width();
if (this.option("rtlEnabled")) {
this._currentRatio = 1 - this._currentRatio;
}
this._saveValueChangeEvent(e);
this._changeValueOnSwipe(this._currentRatio);
},
_renderValue: function _renderValue() {
this.callBase();
this._setRangeStyles(this._rangeStylesConfig());
var value = this.option("value");
this._$submitElement.val(applyServerDecimalSeparator(value));
SliderHandle.getInstance(this._activeHandle()).option("value", value);
},
_setRangeStyles: function _setRangeStyles(options) {
options && this._$range.css(options);
},
_callHandlerMethod: function _callHandlerMethod(name, args) {
SliderHandle.getInstance(this._$handle)[name](args);
},
_repaintHandle: function _repaintHandle() {
this._callHandlerMethod("repaint");
},
_fitTooltip: function _fitTooltip() {
this._callHandlerMethod("fitTooltipPosition");
},
_optionChanged: function _optionChanged(args) {
switch (args.name) {
case "visible":
this.callBase(args);
this._renderHandle();
this._repaintHandle();
domUtils.triggerShownEvent(this.$element()); // TODO: move firing dxshown event to Widget
break;
case "min":
case "max":
this._renderValue();
this.callBase(args);
this._renderLabels();
this._renderAriaMinAndMax();
this._fitTooltip();
break;
case "step":
this._renderValue();
break;
case "keyStep":
break;
case "showRange":
this._renderRangeVisibility();
break;
case "tooltip":
this._renderHandle();
break;
case "label":
this._renderLabels();
break;
case "useInkRipple":
this._invalidate();
break;
default:
this.callBase(args);
}
},
_refresh: function _refresh() {
this._toggleRTLDirection(this.option("rtlEnabled"));
this._renderDimensions();
this._renderValue();
this._renderHandle();
this._repaintHandle();
}
});
registerComponent("dxSlider", Slider);
module.exports = Slider;