devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
406 lines (405 loc) • 15.1 kB
JavaScript
/**
* DevExtreme (ui/number_box/number_box.base.js)
* Version: 18.1.3
* Build date: Tue May 15 2018
*
* Copyright (c) 2012 - 2018 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
var $ = require("../../core/renderer"),
domAdapter = require("../../core/dom_adapter"),
eventsEngine = require("../../events/core/events_engine"),
commonUtils = require("../../core/utils/common"),
mathUtils = require("../../core/utils/math"),
extend = require("../../core/utils/extend").extend,
inArray = require("../../core/utils/array").inArray,
focused = require("../widget/selectors").focused,
devices = require("../../core/devices"),
TextEditor = require("../text_box/ui.text_editor"),
eventUtils = require("../../events/utils"),
pointerEvents = require("../../events/pointer"),
wheelEvent = require("../../events/core/wheel"),
SpinButton = require("./number_box.spin"),
messageLocalization = require("../../localization/message");
var math = Math;
var WIDGET_CLASS = "dx-numberbox",
SPIN_CLASS = "dx-numberbox-spin",
SPIN_CONTAINER_CLASS = "dx-numberbox-spin-container",
SPIN_TOUCH_FRIENDLY_CLASS = "dx-numberbox-spin-touch-friendly";
var FIREFOX_CONTROL_KEYS = ["Tab", "Del", "Delete", "Backspace", "Left", "ArrowLeft", "Right", "ArrowRight", "Home", "End", "Enter"];
var NumberBoxBase = TextEditor.inherit({
_supportedKeys: function() {
return extend(this.callBase(), {
upArrow: function(e) {
e.preventDefault();
e.stopPropagation();
this._spinUpChangeHandler(e)
},
downArrow: function(e) {
e.preventDefault();
e.stopPropagation();
this._spinDownChangeHandler(e)
},
enter: function() {}
})
},
_getDefaultOptions: function() {
return extend(this.callBase(), {
value: 0,
min: void 0,
max: void 0,
step: 1,
showSpinButtons: false,
useLargeSpinButtons: true,
mode: "text",
invalidValueMessage: messageLocalization.format("dxNumberBox-invalidValueMessage")
})
},
_defaultOptionsRules: function() {
return this.callBase().concat([{
device: function() {
return devices.real().generic && !devices.isSimulator()
},
options: {
useLargeSpinButtons: false
}
}, {
device: function() {
return "generic" !== devices.real().platform
},
options: {
mode: "number"
}
}])
},
_initMarkup: function() {
this._renderSubmitElement();
this.$element().addClass(WIDGET_CLASS);
this.callBase()
},
_renderContentImpl: function() {
this.option("isValid") && this._validateValue(this.option("value"));
this.setAria("role", "spinbutton");
this._renderMouseWheelHandler()
},
_renderSubmitElement: function() {
this._$submitElement = $("<input>").attr("type", "hidden").appendTo(this.$element());
this._setSubmitValue(this.option("value"))
},
_setSubmitValue: function(value) {
this._$submitElement.val(commonUtils.applyServerDecimalSeparator(value))
},
_getSubmitElement: function() {
return this._$submitElement
},
_keyPressHandler: function(e) {
this.callBase(e);
var ch = e.key || String.fromCharCode(e.which),
validCharRegExp = /[\d.,eE\-+]|Subtract/,
isInputCharValid = validCharRegExp.test(ch);
if (!isInputCharValid) {
if (e.metaKey || e.ctrlKey || e.key && inArray(e.key, FIREFOX_CONTROL_KEYS) >= 0) {
return
}
e.preventDefault();
return false
}
this._keyPressed = true
},
_renderMouseWheelHandler: function() {
var eventName = eventUtils.addNamespace(wheelEvent.name, this.NAME);
var mouseWheelAction = this._createAction(function(e) {
this._mouseWheelHandler(e.event)
}.bind(this));
eventsEngine.off(this._input(), eventName);
eventsEngine.on(this._input(), eventName, function(e) {
mouseWheelAction({
event: e
})
})
},
_mouseWheelHandler: function(dxEvent) {
if (!focused(this._input())) {
return
}
dxEvent.delta > 0 ? this._spinValueChange(1, dxEvent) : this._spinValueChange(-1, dxEvent);
dxEvent.preventDefault();
dxEvent.stopPropagation()
},
_renderValue: function() {
var inputValue = this._input().val();
if (!inputValue.length || Number(inputValue) !== this.option("value")) {
this._forceValueRender();
this._toggleEmptinessEventHandler()
}
var value = this.option("value");
this._renderInputAddons();
this.setAria("valuenow", value);
this.option("text", this._input().val())
},
_renderValueEventName: function() {
return this.callBase() + " keypress"
},
_toggleDisabledState: function(value) {
if (this._$spinUp) {
SpinButton.getInstance(this._$spinUp).option("disabled", value)
}
if (this._$spinDown) {
SpinButton.getInstance(this._$spinDown).option("disabled", value)
}
this.callBase.apply(this, arguments)
},
_forceValueRender: function() {
var value = this.option("value"),
number = Number(value),
formattedValue = isNaN(number) ? "" : this._applyValueFormat(value);
this._renderDisplayText(formattedValue)
},
_applyValueFormat: function(value) {
return this.option("valueFormat")(value)
},
_renderProps: function() {
this.callBase();
this._input().prop({
min: this.option("min"),
max: this.option("max"),
step: this.option("step")
});
this.setAria({
valuemin: this.option("min") || "undefined",
valuemax: this.option("max") || "undefined"
})
},
_renderInputAddons: function() {
this.callBase();
this._renderSpinButtons()
},
_renderSpinButtons: function() {
var spinButtonsVisible = this.option("showSpinButtons");
this.$element().toggleClass(SPIN_CLASS, spinButtonsVisible);
this._toggleTouchFriendlyClass();
if (!spinButtonsVisible) {
this._$spinContainer && this._$spinContainer.remove();
this._$spinContainer = null;
return
}
if (!this._$spinContainer) {
this._$spinContainer = this._createSpinButtons()
}
this._$spinContainer.prependTo(this._buttonsContainer())
},
_toggleTouchFriendlyClass: function() {
this.$element().toggleClass(SPIN_TOUCH_FRIENDLY_CLASS, this.option("showSpinButtons") && this.option("useLargeSpinButtons"))
},
_createSpinButtons: function() {
var eventName = eventUtils.addNamespace(pointerEvents.down, this.NAME);
var pointerDownAction = this._createAction(this._spinButtonsPointerDownHandler.bind(this));
var $spinContainer = $("<div>").addClass(SPIN_CONTAINER_CLASS);
eventsEngine.off($spinContainer, eventName);
eventsEngine.on($spinContainer, eventName, function(e) {
pointerDownAction({
event: e
})
});
this._$spinUp = $("<div>").appendTo($spinContainer);
this._createComponent(this._$spinUp, SpinButton, {
direction: "up",
onChange: this._spinUpChangeHandler.bind(this)
});
this._$spinDown = $("<div>").appendTo($spinContainer);
this._createComponent(this._$spinDown, SpinButton, {
direction: "down",
onChange: this._spinDownChangeHandler.bind(this)
});
return $spinContainer
},
_spinButtonsPointerDownHandler: function() {
var $input = this._input();
if (!this.option("useLargeSpinButtons") && domAdapter.getActiveElement() !== $input[0]) {
eventsEngine.trigger($input, "focus")
}
},
_spinUpChangeHandler: function(e) {
if (!this.option("readOnly")) {
this._spinValueChange(1, e.event || e)
}
},
_spinDownChangeHandler: function(e) {
if (!this.option("readOnly")) {
this._spinValueChange(-1, e.event || e)
}
},
_spinValueChange: function(sign, dxEvent) {
var value = parseFloat(this._normalizeInputValue()) || 0,
step = parseFloat(this.option("step"));
value = this._correctRounding(value, step * sign);
var min = this.option("min"),
max = this.option("max");
if (void 0 !== min) {
value = Math.max(min, value)
}
if (void 0 !== max) {
value = Math.min(max, value)
}
this._saveValueChangeEvent(dxEvent);
this.option("value", value)
},
_correctRounding: function(value, step) {
var regex = /[,.](.*)/;
var isFloatValue = regex.test(value),
isFloatStep = regex.test(step);
if (isFloatValue || isFloatStep) {
var valueAccuracy = isFloatValue ? regex.exec(value)[0].length : 0,
stepAccuracy = isFloatStep ? regex.exec(step)[0].length : 0,
accuracy = math.max(valueAccuracy, stepAccuracy);
value = this._round(value + step, accuracy);
return value
}
return value + step
},
_round: function(value, precision) {
precision = precision || 0;
var multiplier = Math.pow(10, precision);
value *= multiplier;
value = Math.round(value) / multiplier;
return value
},
_renderValueChangeEvent: function() {
this.callBase();
eventsEngine.on(this._input(), "focusout", this._forceRefreshInputValue.bind(this))
},
_forceRefreshInputValue: function() {
if ("number" === this.option("mode")) {
return
}
var $input = this._input(),
formattedValue = this._applyValueFormat(this.option("value"));
$input.val(null);
$input.val(formattedValue)
},
_valueChangeEventHandler: function(e) {
var $input = this._input(),
inputValue = this._normalizeText(),
value = this._parseValue(inputValue),
valueHasDigits = "." !== inputValue && "-" !== inputValue;
if (this._isValueValid() && !this._validateValue(value)) {
$input.val(this._applyValueFormat(value));
return
}
if (valueHasDigits) {
this.callBase(e, isNaN(value) ? null : value)
}
this._applyValueBoundaries(inputValue, value);
this.validationRequest.fire({
value: value,
editor: this
})
},
_applyValueBoundaries: function(inputValue, parsedValue) {
var isValueIncomplete = this._isValueIncomplete(inputValue),
isValueCorrect = this._isValueInRange(inputValue);
if (!isValueIncomplete && !isValueCorrect && null !== parsedValue) {
if (Number(inputValue) !== parsedValue) {
this._input().val(this._applyValueFormat(parsedValue))
}
}
},
_replaceCommaWithPoint: function(value) {
return value.replace(",", ".")
},
_inputIsInvalid: function() {
var isNumberMode = "number" === this.option("mode");
var validityState = this._input().get(0).validity;
return isNumberMode && validityState && validityState.badInput
},
_renderDisplayText: function(text) {
if (this._inputIsInvalid()) {
return
}
this.callBase(text)
},
_isValueIncomplete: function(value) {
var incompleteRegex = /(^-$)|(^-?\d*\.$)|(\d+e-?$)/i;
return incompleteRegex.test(value)
},
_isValueInRange: function(value) {
return mathUtils.inRange(value, this.option("min"), this.option("max"))
},
_isNumber: function(value) {
return null !== this._parseValue(value)
},
_validateValue: function(value) {
var inputValue = this._normalizeText(),
isValueValid = this._isValueValid(),
isValid = true,
isNumber = this._isNumber(inputValue);
if (isNaN(Number(value))) {
isValid = false
}
if (!value && isValueValid) {
isValid = true
} else {
if (!isNumber && !isValueValid) {
isValid = false
}
}
this.option({
isValid: isValid,
validationError: isValid ? null : {
editorSpecific: true,
message: this.option("invalidValueMessage")
}
});
return isValid
},
_normalizeInputValue: function() {
return this._parseValue(this._normalizeText())
},
_normalizeText: function() {
var value = this._input().val().trim();
return this._replaceCommaWithPoint(value)
},
_parseValue: function(value) {
var number = parseFloat(value);
if (isNaN(number)) {
return null
}
return mathUtils.fitIntoRange(number, this.option("min"), this.option("max"))
},
reset: function() {
this.option("value", null)
},
_clean: function() {
delete this._$spinContainer;
delete this._$spinUp;
delete this._$spinDown;
this.callBase()
},
_optionChanged: function(args) {
switch (args.name) {
case "value":
this._validateValue(args.value);
this._setSubmitValue(args.value);
this.callBase(args);
this._resumeValueChangeAction();
break;
case "step":
case "min":
case "max":
this._renderProps();
break;
case "showSpinButtons":
this._renderInputAddons();
break;
case "useLargeSpinButtons":
this._toggleTouchFriendlyClass();
break;
case "invalidValueMessage":
break;
default:
this.callBase(args)
}
}
});
module.exports = NumberBoxBase;