UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

731 lines (593 loc) • 23 kB
"use strict"; var $ = require("../../core/renderer"), eventsEngine = require("../../events/core/events_engine"), caret = require("./utils.caret"), domUtils = require("../../core/utils/dom"), focused = require("../widget/selectors").focused, isDefined = require("../../core/utils/type").isDefined, stringUtils = require("../../core/utils/string"), inArray = require("../../core/utils/array").inArray, extend = require("../../core/utils/extend").extend, each = require("../../core/utils/iterator").each, messageLocalization = require("../../localization/message"), TextEditorBase = require("./ui.text_editor.base"), MaskRules = require("./ui.text_editor.mask.rule"), eventUtils = require("../../events/utils"); var stubCaret = function stubCaret() { return {}; }; var EMPTY_CHAR = " "; var EMPTY_CHAR_CODE = 32; var ESCAPED_CHAR = "\\"; var TEXTEDITOR_MASKED_CLASS = "dx-texteditor-masked"; var MASK_EVENT_NAMESPACE = "dxMask"; var FORWARD_DIRECTION = "forward"; var BACKWARD_DIRECTION = "backward"; var BLUR_EVENT = "blur beforedeactivate"; var BACKSPACE_INPUT_TYPE = "deleteContentBackward"; var buildInMaskRules = { "0": /[0-9]/, "9": /[0-9\s]/, "#": /[-+0-9\s]/, "L": function L(char) { return isLiteralChar(char); }, "l": function l(char) { return isLiteralChar(char) || isSpaceChar(char); }, "C": /\S/, "c": /./, "A": function A(char) { return isLiteralChar(char) || isNumericChar(char); }, "a": function a(char) { return isLiteralChar(char) || isNumericChar(char) || isSpaceChar(char); } }; var isNumericChar = function isNumericChar(char) { return (/[0-9]/.test(char) ); }; var isLiteralChar = function isLiteralChar(char) { var code = char.charCodeAt(); return 64 < code && code < 91 || 96 < code && code < 123 || code > 127; }; var isSpaceChar = function isSpaceChar(char) { return char === " "; }; var TextEditorMask = TextEditorBase.inherit({ _getDefaultOptions: function _getDefaultOptions() { return extend(this.callBase(), { /** * @name dxTextEditorOptions.mask * @publicName mask * @type string * @default "" */ mask: "", /** * @name dxTextEditorOptions.maskChar * @publicName maskChar * @type string * @default "_" */ maskChar: "_", /** * @name dxTextEditorOptions.maskRules * @publicName maskRules * @type Object * @default "{}" */ maskRules: {}, /** * @name dxTextEditorOptions.maskInvalidMessage * @publicName maskInvalidMessage * @type string * @default "Value is invalid" */ maskInvalidMessage: messageLocalization.format("validation-mask"), /** * @name dxTextEditorOptions.useMaskedValue * @publicName useMaskedValue * @type boolean * @default false */ useMaskedValue: false, /** * @name dxTextEditorOptions.showMaskMode * @publicName showMaskMode * @type Enums.ShowMaskMode * @default "always" */ showMaskMode: "always" }); }, _supportedKeys: function _supportedKeys() { var that = this; var keyHandlerMap = { backspace: that._maskBackspaceHandler, del: that._maskDelHandler, enter: that._changeHandler }; var result = that.callBase(); each(keyHandlerMap, function (key, callback) { var parentHandler = result[key]; result[key] = function (e) { that.option("mask") && callback.call(that, e); parentHandler && parentHandler(e); }; }); return result; }, _getSubmitElement: function _getSubmitElement() { return !this.option("mask") ? this.callBase() : this._$hiddenElement; }, _initMarkup: function _initMarkup() { this._renderHiddenElement(); this.callBase(); }, _render: function _render() { this.callBase(); this._renderMask(); }, _renderHiddenElement: function _renderHiddenElement() { if (this.option("mask")) { this._$hiddenElement = $("<input>").attr("type", "hidden").appendTo(this._inputWrapper()); } }, _removeHiddenElement: function _removeHiddenElement() { this._$hiddenElement && this._$hiddenElement.remove(); }, _renderMask: function _renderMask() { this.$element().removeClass(TEXTEDITOR_MASKED_CLASS); this._maskRulesChain = null; this._detachMaskEventHandlers(); if (!this.option("mask")) { return; } this.$element().addClass(TEXTEDITOR_MASKED_CLASS); this._attachMaskEventHandlers(); this._parseMask(); this._renderMaskedValue(); this._changedValue = this._input().val(); }, _attachMaskEventHandlers: function _attachMaskEventHandlers() { var $input = this._input(); eventsEngine.on($input, eventUtils.addNamespace("focusin", MASK_EVENT_NAMESPACE), this._maskFocusHandler.bind(this)); eventsEngine.on($input, eventUtils.addNamespace("focusout", MASK_EVENT_NAMESPACE), this._maskBlurHandler.bind(this)); eventsEngine.on($input, eventUtils.addNamespace("keydown", MASK_EVENT_NAMESPACE), this._maskKeyDownHandler.bind(this)); eventsEngine.on($input, eventUtils.addNamespace("keypress", MASK_EVENT_NAMESPACE), this._maskKeyPressHandler.bind(this)); eventsEngine.on($input, eventUtils.addNamespace("input", MASK_EVENT_NAMESPACE), this._maskInputHandler.bind(this)); eventsEngine.on($input, eventUtils.addNamespace("paste", MASK_EVENT_NAMESPACE), this._maskPasteHandler.bind(this)); eventsEngine.on($input, eventUtils.addNamespace("cut", MASK_EVENT_NAMESPACE), this._maskCutHandler.bind(this)); eventsEngine.on($input, eventUtils.addNamespace("drop", MASK_EVENT_NAMESPACE), this._maskDragHandler.bind(this)); this._attachChangeEventHandlers(); }, _detachMaskEventHandlers: function _detachMaskEventHandlers() { eventsEngine.off(this._input(), "." + MASK_EVENT_NAMESPACE); }, _attachChangeEventHandlers: function _attachChangeEventHandlers() { if (inArray("change", this.option("valueChangeEvent").split(" ")) === -1) { return; } eventsEngine.on(this._input(), eventUtils.addNamespace(BLUR_EVENT, MASK_EVENT_NAMESPACE), function (e) { // NOTE: input is focused on caret changing in IE(T304159) this._suppressCaretChanging(this._changeHandler, [e]); this._changeHandler(e); }.bind(this)); }, _suppressCaretChanging: function _suppressCaretChanging(callback, args) { var originalCaret = caret; caret = stubCaret; try { callback.apply(this, args); } finally { caret = originalCaret; } }, _changeHandler: function _changeHandler(e) { var $input = this._input(), inputValue = $input.val(); if (inputValue === this._changedValue) { return; } this._changedValue = inputValue; var changeEvent = eventUtils.createEvent(e, { type: "change" }); eventsEngine.trigger($input, changeEvent); }, _parseMask: function _parseMask() { this._maskRules = extend({}, buildInMaskRules, this.option("maskRules")); this._maskRulesChain = this._parseMaskRule(0); }, _parseMaskRule: function _parseMaskRule(index) { var mask = this.option("mask"); if (index >= mask.length) { return new MaskRules.EmptyMaskRule(); } var currentMaskChar = mask[index]; var isEscapedChar = currentMaskChar === ESCAPED_CHAR; var result = isEscapedChar ? new MaskRules.StubMaskRule({ maskChar: mask[index + 1] }) : this._getMaskRule(currentMaskChar); result.next(this._parseMaskRule(index + 1 + isEscapedChar)); return result; }, _getMaskRule: function _getMaskRule(pattern) { var ruleConfig; each(this._maskRules, function (rulePattern, allowedChars) { if (rulePattern === pattern) { ruleConfig = { pattern: rulePattern, allowedChars: allowedChars }; return false; } }); return isDefined(ruleConfig) ? new MaskRules.MaskRule(extend({ maskChar: this.option("maskChar") }, ruleConfig)) : new MaskRules.StubMaskRule({ maskChar: pattern }); }, _renderMaskedValue: function _renderMaskedValue() { if (!this._maskRulesChain) { return; } var value = this.option("value") || ""; this._maskRulesChain.clear(this._normalizeChainArguments()); var chainArgs = { length: value.length }; chainArgs[this._isMaskedValueMode() ? "text" : "value"] = value; this._handleChain(chainArgs); this._displayMask(); }, _isMaskedValueMode: function _isMaskedValueMode() { return this.option("useMaskedValue"); }, _displayMask: function _displayMask(caret) { caret = caret || this._caret(); this._renderValue(); this._caret(caret); }, _isValueEmpty: function _isValueEmpty() { return stringUtils.isEmpty(this._value); }, _shouldShowMask: function _shouldShowMask() { var showMaskMode = this.option("showMaskMode"); if (showMaskMode === "onFocus") { return focused(this._input()) || !this._isValueEmpty(); } return true; }, _showMaskPlaceholder: function _showMaskPlaceholder() { if (this._shouldShowMask()) { var text = this._maskRulesChain.text(); this.option("text", text); if (this.option("showMaskMode") === "onFocus") { this._renderDisplayText(text); } } }, _renderValue: function _renderValue() { if (this._maskRulesChain) { var text = this._maskRulesChain.text(); this._showMaskPlaceholder(); if (this._$hiddenElement) { var value = this._maskRulesChain.value(), hiddenElementValue = this._isMaskedValueMode() ? text : value; this._$hiddenElement.val(!stringUtils.isEmpty(value) ? hiddenElementValue : ""); } } this.callBase(); }, _valueChangeEventHandler: function _valueChangeEventHandler(e) { if (!this._maskRulesChain) { this.callBase.apply(this, arguments); return; } this._saveValueChangeEvent(e); this.option("value", this._convertToValue().replace(/\s+$/, "")); }, _maskFocusHandler: function _maskFocusHandler() { this._showMaskPlaceholder(); this._direction(FORWARD_DIRECTION); if (!this._isValueEmpty() && this.option("isValid")) { this._adjustCaret(); } else { var caret = this._maskRulesChain.first(); this._caretTimeout = setTimeout(function () { this._caret({ start: caret, end: caret }); }.bind(this), 0); } }, _maskBlurHandler: function _maskBlurHandler() { if (this.option("showMaskMode") === "onFocus" && this._isValueEmpty()) { this.option("text", ""); this._renderDisplayText(""); } }, _maskKeyDownHandler: function _maskKeyDownHandler() { this._keyPressHandled = false; }, _maskKeyPressHandler: function _maskKeyPressHandler(e) { if (this._keyPressHandled) { return; } this._keyPressHandled = true; if (this._isControlKeyFired(e)) { return; } this._maskKeyHandler(e, function () { this._handleKey(e.which); return true; }); }, _maskInputHandler: function _maskInputHandler(e) { if (this._backspaceInputHandled(e.originalEvent && e.originalEvent.inputType)) { this._handleBackspaceInput(e); } if (this._keyPressHandled) { return; } this._keyPressHandled = true; var inputValue = this._input().val(); var caret = this._caret(); if (!caret.end) { return; } caret.start = caret.end - 1; var oldValue = inputValue.substring(0, caret.start) + inputValue.substring(caret.end); var char = inputValue[caret.start]; this._input().val(oldValue); // NOTE: WP8 can not to handle setCaret immediately after setting value this._inputHandlerTimer = setTimeout(function () { this._caret({ start: caret.start, end: caret.start }); this._maskKeyHandler(e, function () { this._handleKey(char.charCodeAt()); return true; }); }.bind(this)); }, _backspaceInputHandled: function _backspaceInputHandled(inputType) { return inputType === BACKSPACE_INPUT_TYPE && !this._keyPressHandled; }, _handleBackspaceInput: function _handleBackspaceInput(e) { var caret = this._caret(); this._caret({ start: caret.start + 1, end: caret.end + 1 }); this._maskBackspaceHandler(e); }, _isControlKeyFired: function _isControlKeyFired(e) { return this._isControlKey(e.key) || e.ctrlKey // NOTE: FF fires control keys on keypress || e.metaKey; // NOTE: Safari fires keys with ctrl modifier on keypress }, _maskBackspaceHandler: function _maskBackspaceHandler(e) { var that = this; that._keyPressHandled = true; var afterBackspaceHandler = function afterBackspaceHandler(needAdjustCaret, callBack) { if (needAdjustCaret) { that._direction(FORWARD_DIRECTION); that._adjustCaret(); } var currentCaret = that._caret(); clearTimeout(that._backspaceHandlerTimeout); that._backspaceHandlerTimeout = setTimeout(function () { callBack(currentCaret); }); }; that._maskKeyHandler(e, function () { if (that._hasSelection()) { afterBackspaceHandler(true, function (currentCaret) { that._displayMask(currentCaret); that._maskRulesChain.reset(); }); return; } if (that._tryMoveCaretBackward()) { afterBackspaceHandler(false, function (currentCaret) { that._caret(currentCaret); }); return; } that._handleKey(EMPTY_CHAR_CODE, BACKWARD_DIRECTION); afterBackspaceHandler(true, function (currentCaret) { that._displayMask(currentCaret); that._maskRulesChain.reset(); }); }); }, _maskDelHandler: function _maskDelHandler(e) { this._keyPressHandled = true; this._maskKeyHandler(e, function () { !this._hasSelection() && this._handleKey(EMPTY_CHAR_CODE); return true; }); }, _maskPasteHandler: function _maskPasteHandler(e) { this._keyPressHandled = true; var caret = this._caret(); this._maskKeyHandler(e, function () { var pastingText = domUtils.clipboardText(e); var restText = this._maskRulesChain.text().substring(caret.end); var accepted = this._handleChain({ text: pastingText, start: caret.start, length: pastingText.length }); var newCaret = caret.start + accepted; this._handleChain({ text: restText, start: newCaret, length: restText.length }); this._caret({ start: newCaret, end: newCaret }); return true; }); }, _handleChain: function _handleChain(args) { var handledCount = this._maskRulesChain.handle(this._normalizeChainArguments(args)); this._value = this._maskRulesChain.value(); this._textValue = this._maskRulesChain.text(); return handledCount; }, _normalizeChainArguments: function _normalizeChainArguments(args) { args = args || {}; args.index = 0; args.fullText = this._maskRulesChain.text(); return args; }, _maskCutHandler: function _maskCutHandler(e) { var caret = this._caret(); var selectedText = this._input().val().substring(caret.start, caret.end); this._maskKeyHandler(e, function () { domUtils.clipboardText(e, selectedText); return true; }); }, _maskDragHandler: function _maskDragHandler() { this._clearDragTimer(); this._dragTimer = setTimeout(function () { this.option("value", this._convertToValue(this._input().val())); }.bind(this)); }, _convertToValue: function _convertToValue(text) { if (this._isMaskedValueMode()) { text = (text || this._textValue || "").replace(new RegExp(this.option("maskChar"), "g"), EMPTY_CHAR); } else { text = text || this._value || ""; } return text; }, _maskKeyHandler: function _maskKeyHandler(e, tryHandleKeyCallback) { if (this.option("readOnly")) { return; } this._direction(FORWARD_DIRECTION); e.preventDefault(); this._handleSelection(); if (!tryHandleKeyCallback.call(this)) { return; } this._direction(FORWARD_DIRECTION); this._adjustCaret(); this._displayMask(); this._maskRulesChain.reset(); }, _handleKey: function _handleKey(keyCode, direction) { var char = String.fromCharCode(keyCode); this._direction(direction || FORWARD_DIRECTION); this._adjustCaret(char); this._handleKeyChain(char); this._moveCaret(); }, _handleSelection: function _handleSelection() { if (!this._hasSelection()) { return; } var caret = this._caret(); var emptyChars = new Array(caret.end - caret.start + 1).join(EMPTY_CHAR); this._handleKeyChain(emptyChars); }, _handleKeyChain: function _handleKeyChain(chars) { var caret = this._caret(); var start = this._isForwardDirection() ? caret.start : caret.start - 1; var end = this._isForwardDirection() ? caret.end : caret.end - 1; var length = start === end ? 1 : end - start; this._handleChain({ text: chars, start: start, length: length }); }, _tryMoveCaretBackward: function _tryMoveCaretBackward() { this._direction(BACKWARD_DIRECTION); var currentCaret = this._caret().start; this._adjustCaret(); return !currentCaret || currentCaret !== this._caret().start; }, _adjustCaret: function _adjustCaret(char) { var caret = this._maskRulesChain.adjustedCaret(this._caret().start, this._isForwardDirection(), char); this._caret({ start: caret, end: caret }); }, _moveCaret: function _moveCaret() { var currentCaret = this._caret().start; var maskRuleIndex = currentCaret + (this._isForwardDirection() ? 0 : -1); var caret = this._maskRulesChain.isAccepted(maskRuleIndex) ? currentCaret + (this._isForwardDirection() ? 1 : -1) : currentCaret; this._caret({ start: caret, end: caret }); }, _caret: function _caret(position) { if (!arguments.length) { return caret(this._input()); } caret(this._input(), position); }, _hasSelection: function _hasSelection() { var caret = this._caret(); return caret.start !== caret.end; }, _direction: function _direction(direction) { if (!arguments.length) { return this._typingDirection; } this._typingDirection = direction; }, _isForwardDirection: function _isForwardDirection() { return this._direction() === FORWARD_DIRECTION; }, _clearDragTimer: function _clearDragTimer() { clearTimeout(this._dragTimer); }, _clean: function _clean() { this._clearDragTimer(); this.callBase(); }, _validateMask: function _validateMask() { if (!this._maskRulesChain) { return; } var isValid = this._maskRulesChain.isValid(this._normalizeChainArguments()); this.option({ isValid: isValid, validationError: isValid ? null : { editorSpecific: true, message: this.option("maskInvalidMessage") } }); }, _dispose: function _dispose() { clearTimeout(this._inputHandlerTimer); clearTimeout(this._backspaceHandlerTimeout); clearTimeout(this._caretTimeout); this.callBase(); }, _updateHiddenElement: function _updateHiddenElement() { this._removeHiddenElement(); if (this.option("mask")) { this._input().removeAttr("name"); this._renderHiddenElement(); } this._setSubmitElementName(this.option("name")); }, _updateMaskOption: function _updateMaskOption() { this._updateHiddenElement(); this._renderMask(); this._validateMask(); }, _processEmptyMask: function _processEmptyMask(mask) { if (mask) return; var value = this.option("value"); this.option({ text: value, isValid: true }); this.validationRequest.fire({ value: value, editor: this }); this._renderValue(); }, _optionChanged: function _optionChanged(args) { switch (args.name) { case "mask": this._updateMaskOption(); this._processEmptyMask(args.value); break; case "maskChar": case "maskRules": case "useMaskedValue": this._updateMaskOption(); break; case "value": this._renderMaskedValue(); this._validateMask(); this.callBase(args); break; case "maskInvalidMessage": break; case "showMaskMode": this.option("text", ""); this._renderValue(); break; default: this.callBase(args); } } }); module.exports = TextEditorMask;