devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
775 lines (646 loc) • 24.5 kB
JavaScript
"use strict";
var $ = require("../../core/renderer"),
domAdapter = require("../../core/dom_adapter"),
eventsEngine = require("../../events/core/events_engine"),
domUtils = require("../../core/utils/dom"),
focused = require("../widget/selectors").focused,
isDefined = require("../../core/utils/type").isDefined,
extend = require("../../core/utils/extend").extend,
inArray = require("../../core/utils/array").inArray,
each = require("../../core/utils/iterator").each,
themes = require("../themes"),
Editor = require("../editor/editor"),
eventUtils = require("../../events/utils"),
pointerEvents = require("../../events/pointer"),
clickEvent = require("../../events/click");
var TEXTEDITOR_CLASS = "dx-texteditor",
TEXTEDITOR_INPUT_CLASS = "dx-texteditor-input",
TEXTEDITOR_INPUT_SELECTOR = "." + TEXTEDITOR_INPUT_CLASS,
TEXTEDITOR_CONTAINER_CLASS = "dx-texteditor-container",
TEXTEDITOR_BUTTONS_CONTAINER_CLASS = "dx-texteditor-buttons-container",
TEXTEDITOR_PLACEHOLDER_CLASS = "dx-placeholder",
TEXTEDITOR_SHOW_CLEAR_BUTTON_CLASS = "dx-show-clear-button",
TEXTEDITOR_ICON_CLASS = "dx-icon",
TEXTEDITOR_CLEAR_ICON_CLASS = "dx-icon-clear",
TEXTEDITOR_CLEAR_BUTTON_CLASS = "dx-clear-button-area",
TEXTEDITOR_EMPTY_INPUT_CLASS = "dx-texteditor-empty",
STATE_INVISIBLE_CLASS = "dx-state-invisible";
var EVENTS_LIST = ["KeyDown", "KeyPress", "KeyUp", "Change", "Cut", "Copy", "Paste", "Input"];
var CONTROL_KEYS = ["Tab", "Enter", "Shift", "Control", "Alt", "Escape", "PageUp", "PageDown", "End", "Home", "ArrowLeft", "ArrowUp", "ArrowRight", "ArrowDown",
// IE9 fallback:
"Esc", "Left", "Up", "Right", "Down"];
/**
* @name dxTextEditor
* @publicName dxTextEditor
* @inherits Editor
* @hidden
*/
var TextEditorBase = Editor.inherit({
_supportedKeys: function _supportedKeys() {
var stop = function stop(e) {
e.stopPropagation();
};
return {
space: stop,
enter: stop,
leftArrow: stop,
rightArrow: stop
};
},
_getDefaultOptions: function _getDefaultOptions() {
return extend(this.callBase(), {
/**
* @name dxTextEditorOptions.value
* @publicName value
* @type any
* @default ""
*/
value: "",
/**
* @name dxTextEditorOptions.spellcheck
* @publicName spellcheck
* @type boolean
* @default false
*/
spellcheck: false,
/**
* @name dxTextEditorOptions.showClearButton
* @publicName showClearButton
* @type boolean
* @default false
*/
showClearButton: false,
/**
* @name dxTextEditorOptions.valueChangeEvent
* @publicName valueChangeEvent
* @type string
* @default "change"
*/
valueChangeEvent: "change",
/**
* @name dxTextEditorOptions.placeholder
* @publicName placeholder
* @type string
* @default ""
*/
placeholder: "",
/**
* @name dxTextEditorOptions.inputAttr
* @publicName inputAttr
* @type object
* @default {}
*/
inputAttr: {},
/**
* @name dxTextEditorOptions.onFocusIn
* @publicName onFocusIn
* @extends Action
* @type function(e)
* @type_function_param1 e:object
* @type_function_param1_field4 jQueryEvent:jQuery.Event:deprecated(event)
* @type_function_param1_field5 event:event
* @action
*/
onFocusIn: null,
/**
* @name dxTextEditorOptions.onFocusOut
* @publicName onFocusOut
* @extends Action
* @type function(e)
* @type_function_param1 e:object
* @type_function_param1_field4 jQueryEvent:jQuery.Event:deprecated(event)
* @type_function_param1_field5 event:event
* @action
*/
onFocusOut: null,
/**
* @name dxTextEditorOptions.onKeyDown
* @publicName onKeyDown
* @extends Action
* @type function(e)
* @type_function_param1 e:object
* @type_function_param1_field4 jQueryEvent:jQuery.Event:deprecated(event)
* @type_function_param1_field5 event:event
* @default null
* @action
*/
onKeyDown: null,
/**
* @name dxTextEditorOptions.onKeyPress
* @publicName onKeyPress
* @extends Action
* @type function(e)
* @type_function_param1 e:object
* @type_function_param1_field4 jQueryEvent:jQuery.Event:deprecated(event)
* @type_function_param1_field5 event:event
* @action
*/
onKeyPress: null,
/**
* @name dxTextEditorOptions.onKeyUp
* @publicName onKeyUp
* @extends Action
* @type function(e)
* @type_function_param1 e:object
* @type_function_param1_field4 jQueryEvent:jQuery.Event:deprecated(event)
* @type_function_param1_field5 event:event
* @action
*/
onKeyUp: null,
/**
* @name dxTextEditorOptions.onChange
* @publicName onChange
* @extends Action
* @type function(e)
* @type_function_param1 e:object
* @type_function_param1_field4 jQueryEvent:jQuery.Event:deprecated(event)
* @type_function_param1_field5 event:event
* @action
*/
onChange: null,
/**
* @name dxTextEditorOptions.onInput
* @publicName onInput
* @extends Action
* @type function(e)
* @type_function_param1 e:object
* @type_function_param1_field4 jQueryEvent:jQuery.Event:deprecated(event)
* @type_function_param1_field5 event:event
* @action
*/
onInput: null,
/**
* @name dxTextEditorOptions.onCut
* @publicName onCut
* @extends Action
* @type function(e)
* @type_function_param1 e:object
* @type_function_param1_field4 jQueryEvent:jQuery.Event:deprecated(event)
* @type_function_param1_field5 event:event
* @action
*/
onCut: null,
/**
* @name dxTextEditorOptions.onCopy
* @publicName onCopy
* @extends Action
* @type function(e)
* @type_function_param1 e:object
* @type_function_param1_field4 jQueryEvent:jQuery.Event:deprecated(event)
* @type_function_param1_field5 event:event
* @action
*/
onCopy: null,
/**
* @name dxTextEditorOptions.onPaste
* @publicName onPaste
* @extends Action
* @type function(e)
* @type_function_param1 e:object
* @type_function_param1_field4 jQueryEvent:jQuery.Event:deprecated(event)
* @type_function_param1_field5 event:event
* @action
*/
onPaste: null,
/**
* @name dxTextEditorOptions.onEnterKey
* @publicName onEnterKey
* @extends Action
* @type function(e)
* @type_function_param1 e:object
* @type_function_param1_field4 jQueryEvent:jQuery.Event:deprecated(event)
* @type_function_param1_field5 event:event
* @action
*/
onEnterKey: null,
mode: "text",
/**
* @name dxTextEditorOptions.hoverStateEnabled
* @publicName hoverStateEnabled
* @type boolean
* @default true
* @inheritdoc
*/
hoverStateEnabled: true,
/**
* @name dxTextEditorOptions.focusStateEnabled
* @publicName focusStateEnabled
* @type boolean
* @default true
* @inheritdoc
*/
focusStateEnabled: true,
/**
* @name dxTextEditorOptions.text
* @publicName text
* @type string
* @readonly
*/
text: undefined,
valueFormat: function valueFormat(value) {
return value;
}
/**
* @name dxTextEditorOptions.name
* @publicName name
* @type string
* @hidden false
* @inheritdoc
*/
});
},
_defaultOptionsRules: function _defaultOptionsRules() {
return this.callBase().concat([{
device: function device() {
var currentTheme = (themes.current() || "").split(".")[0];
return currentTheme === "android5";
},
options: {
validationMessageOffset: { v: -8 }
}
}]);
},
_input: function _input() {
return this.$element().find(TEXTEDITOR_INPUT_SELECTOR).first();
},
_inputWrapper: function _inputWrapper() {
return this.$element();
},
_buttonsContainer: function _buttonsContainer() {
return this._inputWrapper().find("." + TEXTEDITOR_BUTTONS_CONTAINER_CLASS).eq(0);
},
_isControlKey: function _isControlKey(key) {
return CONTROL_KEYS.indexOf(key) !== -1;
},
_initMarkup: function _initMarkup() {
this.$element().addClass(TEXTEDITOR_CLASS);
this._renderInput();
this._renderInputType();
this._renderPlaceholderMarkup();
this._renderProps();
this.callBase();
this._renderValue();
},
_render: function _render() {
this._renderPlaceholder();
this._refreshValueChangeEvent();
this._renderEvents();
this._renderEnterKeyAction();
this._renderEmptinessEvent();
this.callBase();
},
_renderInput: function _renderInput() {
$("<div>").addClass(TEXTEDITOR_CONTAINER_CLASS).append(this._createInput()).append($("<div>").addClass(TEXTEDITOR_BUTTONS_CONTAINER_CLASS)).appendTo(this.$element());
},
_createInput: function _createInput() {
var $input = $("<input>");
this._applyInputAttributes($input, this.option("inputAttr"));
return $input;
},
_setSubmitElementName: function _setSubmitElementName(name) {
var inputAttrName = this.option("inputAttr.name");
return this.callBase(name || inputAttrName || "");
},
_applyInputAttributes: function _applyInputAttributes($input, customAttributes) {
$input.attr("autocomplete", "off").attr(customAttributes).addClass(TEXTEDITOR_INPUT_CLASS).css("minHeight", this.option("height") ? "0" : "");
},
_renderValue: function _renderValue() {
this._renderInputValue();
this._renderInputAddons();
},
_renderInputValue: function _renderInputValue(value) {
value = value || this.option("value");
var text = this.option("text"),
displayValue = this.option("displayValue"),
valueFormat = this.option("valueFormat");
if (displayValue !== undefined && value !== null) {
text = valueFormat(displayValue);
} else if (!isDefined(text)) {
text = valueFormat(value);
}
this.option("text", text);
// fallback to empty string is required to support WebKit native date picker in some basic scenarios
// can not be covered by QUnit
if (this._input().val() !== (isDefined(text) ? text : "")) {
this._renderDisplayText(text);
} else {
this._toggleEmptinessEventHandler();
}
},
_renderDisplayText: function _renderDisplayText(text) {
this._input().val(text);
this._toggleEmptinessEventHandler();
},
_isValueValid: function _isValueValid() {
if (this._input().length) {
var validity = this._input().get(0).validity;
if (validity) {
return validity.valid;
}
}
return true;
},
_toggleEmptiness: function _toggleEmptiness(isEmpty) {
this.$element().toggleClass(TEXTEDITOR_EMPTY_INPUT_CLASS, isEmpty);
this._togglePlaceholder(isEmpty);
},
_togglePlaceholder: function _togglePlaceholder(isEmpty) {
if (!this._$placeholder) {
return;
}
this._$placeholder.toggleClass(STATE_INVISIBLE_CLASS, !isEmpty);
},
_renderProps: function _renderProps() {
this._toggleReadOnlyState();
this._toggleSpellcheckState();
this._toggleTabIndex();
},
_toggleDisabledState: function _toggleDisabledState(value) {
this.callBase.apply(this, arguments);
var $input = this._input();
if (value) {
$input.attr("disabled", true);
} else {
$input.removeAttr("disabled");
}
},
_toggleTabIndex: function _toggleTabIndex() {
var $input = this._input(),
disabled = this.option("disabled"),
focusStateEnabled = this.option("focusStateEnabled");
if (disabled || !focusStateEnabled) {
$input.attr("tabIndex", -1);
} else {
$input.removeAttr("tabIndex");
}
},
_toggleReadOnlyState: function _toggleReadOnlyState() {
this._input().prop("readOnly", this._readOnlyPropValue());
this.callBase();
},
_readOnlyPropValue: function _readOnlyPropValue() {
return this.option("readOnly");
},
_toggleSpellcheckState: function _toggleSpellcheckState() {
this._input().prop("spellcheck", this.option("spellcheck"));
},
_renderPlaceholder: function _renderPlaceholder() {
this._renderPlaceholderMarkup();
this._attachPlaceholderEvents();
},
_renderPlaceholderMarkup: function _renderPlaceholderMarkup() {
if (this._$placeholder) {
this._$placeholder.remove();
this._$placeholder = null;
}
var $input = this._input(),
placeholderText = this.option("placeholder"),
$placeholder = this._$placeholder = $('<div>').attr("data-dx_placeholder", placeholderText);
$placeholder.insertAfter($input);
$placeholder.addClass(TEXTEDITOR_PLACEHOLDER_CLASS);
},
_attachPlaceholderEvents: function _attachPlaceholderEvents() {
var that = this,
startEvent = eventUtils.addNamespace(pointerEvents.up, that.NAME);
eventsEngine.on(that._$placeholder, startEvent, function () {
eventsEngine.trigger(that._input(), "focus");
});
that._toggleEmptinessEventHandler();
},
_placeholder: function _placeholder() {
return this._$placeholder || $();
},
_renderInputAddons: function _renderInputAddons() {
this._renderClearButton();
},
_renderClearButton: function _renderClearButton() {
var clearButtonVisibility = this._clearButtonVisibility();
this.$element().toggleClass(TEXTEDITOR_SHOW_CLEAR_BUTTON_CLASS, clearButtonVisibility);
if (clearButtonVisibility) {
if (!this._$clearButton || this._$clearButton && !this._$clearButton.closest(this.$element()).length) {
this._$clearButton = this._createClearButton();
}
this._$clearButton.prependTo(this._buttonsContainer());
}
if (this._$clearButton) {
this._$clearButton.toggleClass(STATE_INVISIBLE_CLASS, !clearButtonVisibility);
}
},
_clearButtonVisibility: function _clearButtonVisibility() {
return this.option("showClearButton") && !this.option("readOnly");
},
_createClearButton: function _createClearButton() {
var $clearButton = $("<span>").addClass(TEXTEDITOR_CLEAR_BUTTON_CLASS).append($("<span>").addClass(TEXTEDITOR_ICON_CLASS).addClass(TEXTEDITOR_CLEAR_ICON_CLASS));
eventsEngine.on($clearButton, eventUtils.addNamespace(pointerEvents.down, this.NAME), function (e) {
if (e.pointerType === "mouse") {
e.preventDefault();
}
});
eventsEngine.on($clearButton, eventUtils.addNamespace(clickEvent.name, this.NAME), this._clearValueHandler.bind(this));
return $clearButton;
},
_clearValueHandler: function _clearValueHandler(e) {
var $input = this._input();
e.stopPropagation();
this._valueChangeEventHandler(e);
this.reset();
!focused($input) && eventsEngine.trigger($input, "focus");
eventsEngine.trigger($input, "input");
},
_renderEvents: function _renderEvents() {
var that = this,
$input = that._input();
each(EVENTS_LIST, function (_, event) {
if (that.hasActionSubscription("on" + event)) {
var action = that._createActionByOption("on" + event, { excludeValidators: ["readOnly"] });
eventsEngine.on($input, eventUtils.addNamespace(event.toLowerCase(), that.NAME), function (e) {
if (that._disposed) {
return;
}
action({ event: e });
});
}
});
},
_refreshEvents: function _refreshEvents() {
var that = this,
$input = this._input();
each(EVENTS_LIST, function (_, event) {
eventsEngine.off($input, eventUtils.addNamespace(event.toLowerCase(), that.NAME));
});
this._renderEvents();
},
_keyPressHandler: function _keyPressHandler() {
this.option("text", this._input().val());
},
_renderValueChangeEvent: function _renderValueChangeEvent() {
var keyPressEvent = eventUtils.addNamespace(this._renderValueEventName(), this.NAME + "TextChange"),
valueChangeEvent = eventUtils.addNamespace(this.option("valueChangeEvent"), this.NAME + "ValueChange");
eventsEngine.on(this._input(), keyPressEvent, this._keyPressHandler.bind(this));
eventsEngine.on(this._input(), valueChangeEvent, this._valueChangeEventHandler.bind(this));
},
_cleanValueChangeEvent: function _cleanValueChangeEvent() {
var eventNamespace = this.NAME + "ValueChange",
keyPressEvent = eventUtils.addNamespace(this._renderValueEventName(), this.NAME + "TextChange");
eventsEngine.off(this._input(), "." + eventNamespace);
eventsEngine.off(this._input(), keyPressEvent);
},
_refreshValueChangeEvent: function _refreshValueChangeEvent() {
this._cleanValueChangeEvent();
this._renderValueChangeEvent();
},
_renderValueEventName: function _renderValueEventName() {
return "input change keypress";
},
_focusTarget: function _focusTarget() {
return this._input();
},
_focusClassTarget: function _focusClassTarget() {
return this.$element();
},
_toggleFocusClass: function _toggleFocusClass(isFocused, $element) {
this.callBase(isFocused, this._focusClassTarget($element));
},
_hasFocusClass: function _hasFocusClass(element) {
return this.callBase($(element || this.$element()));
},
_renderEmptinessEvent: function _renderEmptinessEvent() {
var $input = this._input();
eventsEngine.on($input, "input blur", this._toggleEmptinessEventHandler.bind(this));
},
_toggleEmptinessEventHandler: function _toggleEmptinessEventHandler() {
var text = this._input().val(),
isEmpty = (text === "" || text === null) && this._isValueValid();
this._toggleEmptiness(isEmpty);
},
_valueChangeEventHandler: function _valueChangeEventHandler(e, formattedValue) {
this._saveValueChangeEvent(e);
this.option("value", arguments.length > 1 ? formattedValue : this._input().val());
this._saveValueChangeEvent(undefined);
},
_renderEnterKeyAction: function _renderEnterKeyAction() {
this._enterKeyAction = this._createActionByOption("onEnterKey", {
excludeValidators: ["readOnly"]
});
eventsEngine.off(this._input(), "keyup.onEnterKey.dxTextEditor");
eventsEngine.on(this._input(), "keyup.onEnterKey.dxTextEditor", this._enterKeyHandlerUp.bind(this));
},
_enterKeyHandlerUp: function _enterKeyHandlerUp(e) {
if (this._disposed) {
return;
}
if (e.which === 13) {
this._enterKeyAction({
event: e
});
}
},
_updateValue: function _updateValue() {
this.option("text", undefined);
this._renderValue();
},
_dispose: function _dispose() {
this._enterKeyAction = undefined;
this.callBase();
},
_getSubmitElement: function _getSubmitElement() {
return this._input();
},
_optionChanged: function _optionChanged(args) {
var name = args.name;
if (inArray(name.replace("on", ""), EVENTS_LIST) > -1) {
this._refreshEvents();
return;
}
switch (name) {
case "valueChangeEvent":
this._refreshValueChangeEvent();
this._refreshFocusEvent();
this._refreshEvents();
break;
case "onValueChanged":
this._createValueChangeAction();
break;
case "readOnly":
this.callBase(args);
this._renderInputAddons();
break;
case "focusStateEnabled":
this.callBase(args);
this._toggleTabIndex();
break;
case "spellcheck":
this._toggleSpellcheckState();
break;
case "mode":
this._renderInputType();
break;
case "onEnterKey":
this._renderEnterKeyAction();
break;
case "placeholder":
this._renderPlaceholder();
break;
case "showClearButton":
this._renderInputAddons();
break;
case "text":
break;
case "value":
this._updateValue();
this.callBase(args);
break;
case "inputAttr":
this._applyInputAttributes(this._input(), args.value);
break;
case "valueFormat":
this._invalidate();
break;
default:
this.callBase(args);
}
},
_renderInputType: function _renderInputType() {
// B218621, B231875
this._setInputType(this.option("mode"));
},
_setInputType: function _setInputType(type) {
var input = this._input();
if (type === "search") {
type = "text";
}
try {
input.prop("type", type);
} catch (e) {
input.prop("type", "text");
}
},
/**
* @name dxTextEditorMethods.focus
* @publicName focus()
*/
focus: function focus() {
eventsEngine.trigger(this._input(), "focus");
},
/**
* @name dxTextEditorMethods.blur
* @publicName blur()
*/
blur: function blur() {
if (this._input().is(domAdapter.getActiveElement())) {
domUtils.resetActiveElement();
}
},
reset: function reset() {
this.option("value", "");
},
on: function on(eventName, eventHandler) {
var result = this.callBase(eventName, eventHandler),
event = eventName.charAt(0).toUpperCase() + eventName.substr(1);
if (EVENTS_LIST.indexOf(event) >= 0) {
this._refreshEvents();
}
return result;
}
});
module.exports = TextEditorBase;