UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

607 lines (489 loc) • 18.4 kB
"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 _supportedKeys() { return extend(this.callBase(), { upArrow: function upArrow(e) { e.preventDefault(); e.stopPropagation(); this._spinUpChangeHandler(e); }, downArrow: function downArrow(e) { e.preventDefault(); e.stopPropagation(); this._spinDownChangeHandler(e); }, enter: function enter() {} }); }, _getDefaultOptions: function _getDefaultOptions() { return extend(this.callBase(), { /** * @name dxNumberBoxOptions.value * @publicName value * @type number * @default 0 */ value: 0, /** * @name dxNumberBoxOptions.min * @publicName min * @type number * @default undefined */ min: undefined, /** * @name dxNumberBoxOptions.max * @publicName max * @type number * @default undefined */ max: undefined, /** * @name dxNumberBoxOptions.step * @publicName step * @type number * @default 1 */ step: 1, /** * @name dxNumberBoxOptions.showSpinButtons * @publicName showSpinButtons * @type boolean * @default false */ showSpinButtons: false, /** * @name dxNumberBoxOptions.useLargeSpinButtons * @publicName useLargeSpinButtons * @type boolean * @default true * @default false @for desktop */ useLargeSpinButtons: true, /** * @name dxNumberBoxOptions.mode * @publicName mode * @type Enums.NumberBoxMode * @default "text" */ mode: "text", /** * @name dxNumberBoxOptions.invalidValueMessage * @publicName invalidValueMessage * @type string * @default "Value must be a number" */ invalidValueMessage: messageLocalization.format("dxNumberBox-invalidValueMessage") /** * @name dxNumberBoxOptions.mask * @publicName mask * @hidden * @inheritdoc */ /** * @name dxNumberBoxOptions.maskChar * @publicName maskChar * @hidden * @inheritdoc */ /** * @name dxNumberBoxOptions.maskRules * @publicName maskRules * @hidden * @inheritdoc */ /** * @name dxNumberBoxOptions.maskInvalidMessage * @publicName maskInvalidMessage * @hidden * @inheritdoc */ /** * @name dxNumberBoxOptions.useMaskedValue * @publicName useMaskedValue * @hidden * @inheritdoc */ /** * @name dxNumberBoxOptions.showMaskMode * @publicName showMaskMode * @hidden * @inheritdoc */ /** * @name dxNumberBoxOptions.spellcheck * @publicName spellcheck * @hidden * @inheritdoc */ }); }, _defaultOptionsRules: function _defaultOptionsRules() { return this.callBase().concat([{ device: function device() { return devices.real().generic && !devices.isSimulator(); }, options: { useLargeSpinButtons: false } }, { device: function device() { return devices.real().platform !== "generic"; }, options: { /** * @name dxNumberBoxOptions.mode * @publicName mode * @default 'number' @for mobile_devices */ mode: "number" } }]); }, _initMarkup: function _initMarkup() { this._renderSubmitElement(); this.$element().addClass(WIDGET_CLASS); this.callBase(); }, _renderContentImpl: function _renderContentImpl() { this.option("isValid") && this._validateValue(this.option("value")); this.setAria("role", "spinbutton"); this._renderMouseWheelHandler(); }, _renderSubmitElement: function _renderSubmitElement() { this._$submitElement = $("<input>").attr("type", "hidden").appendTo(this.$element()); this._setSubmitValue(this.option("value")); }, _setSubmitValue: function _setSubmitValue(value) { this._$submitElement.val(commonUtils.applyServerDecimalSeparator(value)); }, _getSubmitElement: function _getSubmitElement() { return this._$submitElement; }, _keyPressHandler: function _keyPressHandler(e) { this.callBase(e); var ch = e.key || String.fromCharCode(e.which), validCharRegExp = /[\d.,eE\-+]|Subtract/, // Workaround for IE (T592690) isInputCharValid = validCharRegExp.test(ch); if (!isInputCharValid) { // NOTE: Additional check for Firefox control keys if (e.metaKey || e.ctrlKey || e.key && inArray(e.key, FIREFOX_CONTROL_KEYS) >= 0) { return; } e.preventDefault(); return false; } this._keyPressed = true; }, _renderMouseWheelHandler: function _renderMouseWheelHandler() { 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 _mouseWheelHandler(dxEvent) { if (!focused(this._input())) { return; } dxEvent.delta > 0 ? this._spinValueChange(1, dxEvent) : this._spinValueChange(-1, dxEvent); dxEvent.preventDefault(); dxEvent.stopPropagation(); }, _renderValue: function _renderValue() { 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 _renderValueEventName() { return this.callBase() + " keypress"; }, _toggleDisabledState: function _toggleDisabledState(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 _forceValueRender() { var value = this.option("value"), number = Number(value), formattedValue = isNaN(number) ? "" : this._applyValueFormat(value); this._renderDisplayText(formattedValue); }, _applyValueFormat: function _applyValueFormat(value) { return this.option("valueFormat")(value); }, _renderProps: function _renderProps() { 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 _renderInputAddons() { this.callBase(); this._renderSpinButtons(); }, _renderSpinButtons: function _renderSpinButtons() { 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 _toggleTouchFriendlyClass() { this.$element().toggleClass(SPIN_TOUCH_FRIENDLY_CLASS, this.option("showSpinButtons") && this.option("useLargeSpinButtons")); }, _createSpinButtons: function _createSpinButtons() { 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 _spinButtonsPointerDownHandler() { var $input = this._input(); if (!this.option("useLargeSpinButtons") && domAdapter.getActiveElement() !== $input[0]) { eventsEngine.trigger($input, "focus"); } }, _spinUpChangeHandler: function _spinUpChangeHandler(e) { if (!this.option("readOnly")) { this._spinValueChange(1, e.event || e); } }, _spinDownChangeHandler: function _spinDownChangeHandler(e) { if (!this.option("readOnly")) { this._spinValueChange(-1, e.event || e); } }, _spinValueChange: function _spinValueChange(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 (min !== undefined) { value = Math.max(min, value); } if (max !== undefined) { value = Math.min(max, value); } this._saveValueChangeEvent(dxEvent); this.option("value", value); }, _correctRounding: function _correctRounding(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 _round(value, precision) { precision = precision || 0; var multiplier = Math.pow(10, precision); value *= multiplier; value = Math.round(value) / multiplier; return value; }, _renderValueChangeEvent: function _renderValueChangeEvent() { this.callBase(); eventsEngine.on(this._input(), "focusout", this._forceRefreshInputValue.bind(this)); }, _forceRefreshInputValue: function _forceRefreshInputValue() { if (this.option("mode") === "number") { return; } var $input = this._input(), formattedValue = this._applyValueFormat(this.option("value")); $input.val(null); $input.val(formattedValue); }, _valueChangeEventHandler: function _valueChangeEventHandler(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 _applyValueBoundaries(inputValue, parsedValue) { var isValueIncomplete = this._isValueIncomplete(inputValue), isValueCorrect = this._isValueInRange(inputValue); if (!isValueIncomplete && !isValueCorrect && parsedValue !== null) { if (Number(inputValue) !== parsedValue) { this._input().val(this._applyValueFormat(parsedValue)); } } }, _replaceCommaWithPoint: function _replaceCommaWithPoint(value) { return value.replace(",", "."); }, _inputIsInvalid: function _inputIsInvalid() { var isNumberMode = this.option("mode") === "number"; var validityState = this._input().get(0).validity; return isNumberMode && validityState && validityState.badInput; }, _renderDisplayText: function _renderDisplayText(text) { if (this._inputIsInvalid()) { return; } this.callBase(text); }, _isValueIncomplete: function _isValueIncomplete(value) { var incompleteRegex = /(^-$)|(^-?\d*\.$)|(\d+e-?$)/i; return incompleteRegex.test(value); }, _isValueInRange: function _isValueInRange(value) { return mathUtils.inRange(value, this.option("min"), this.option("max")); }, _isNumber: function _isNumber(value) { return this._parseValue(value) !== null; }, _validateValue: function _validateValue(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 _normalizeInputValue() { return this._parseValue(this._normalizeText()); }, _normalizeText: function _normalizeText() { var value = this._input().val().trim(); return this._replaceCommaWithPoint(value); }, _parseValue: function _parseValue(value) { var number = parseFloat(value); if (isNaN(number)) { return null; } return mathUtils.fitIntoRange(number, this.option("min"), this.option("max")); }, reset: function reset() { this.option("value", null); }, _clean: function _clean() { delete this._$spinContainer; delete this._$spinUp; delete this._$spinDown; this.callBase(); }, _optionChanged: function _optionChanged(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;