devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
858 lines (710 loc) • 27 kB
JavaScript
"use strict";
var $ = require("../../core/renderer"),
eventsEngine = require("../../events/core/events_engine"),
Guid = require("../../core/guid"),
registerComponent = require("../../core/component_registrator"),
commonUtils = require("../../core/utils/common"),
domUtils = require("../../core/utils/dom"),
focused = require("../widget/selectors").focused,
each = require("../../core/utils/iterator").each,
isDefined = require("../../core/utils/type").isDefined,
extend = require("../../core/utils/extend").extend,
getPublicElement = require("../../core/utils/dom").getPublicElement,
errors = require("../widget/ui.errors"),
positionUtils = require("../../animation/position"),
getDefaultAlignment = require("../../core/utils/position").getDefaultAlignment,
messageLocalization = require("../../localization/message"),
Button = require("../button"),
eventUtils = require("../../events/utils"),
TextBox = require("../text_box"),
clickEvent = require("../../events/click"),
FunctionTemplate = require("../widget/function_template"),
Popup = require("../popup");
var DROP_DOWN_EDITOR_CLASS = "dx-dropdowneditor",
DROP_DOWN_EDITOR_INPUT_WRAPPER_CLASS = "dx-dropdowneditor-input-wrapper",
DROP_DOWN_EDITOR_BUTTON_CLASS = "dx-dropdowneditor-button",
DROP_DOWN_EDITOR_BUTTON_ICON = "dx-dropdowneditor-icon",
DROP_DOWN_EDITOR_OVERLAY = "dx-dropdowneditor-overlay",
DROP_DOWN_EDITOR_OVERLAY_FLIPPED = "dx-dropdowneditor-overlay-flipped",
DROP_DOWN_EDITOR_ACTIVE = "dx-dropdowneditor-active",
DROP_DOWN_EDITOR_BUTTON_VISIBLE = "dx-dropdowneditor-button-visible",
DROP_DOWN_EDITOR_FIELD_CLICKABLE = "dx-dropdowneditor-field-clickable";
/**
* @name dxDropDownEditor
* @publicName dxDropDownEditor
* @inherits dxTextBox
* @module ui/drop_down_editor/ui.drop_down_editor
* @export default
* @hidden
*/
var DropDownEditor = TextBox.inherit({
_supportedKeys: function _supportedKeys() {
var homeEndHandler = function homeEndHandler(e) {
if (this.option("opened")) {
e.preventDefault();
return true;
}
return false;
};
return extend({}, this.callBase(), {
tab: function tab(e) {
if (!this.option("opened")) {
return;
}
if (this.option("applyValueMode") === "instantly") {
this.close();
return;
}
var $focusableElement = e.shiftKey ? this._getLastPopupElement() : this._getFirstPopupElement();
$focusableElement && eventsEngine.trigger($focusableElement, "focus");
e.preventDefault();
},
escape: function escape(e) {
if (this.option("opened")) {
e.preventDefault();
}
this.close();
},
upArrow: function upArrow(e) {
e.preventDefault();
e.stopPropagation();
if (e.altKey) {
this.close();
return false;
}
return true;
},
downArrow: function downArrow(e) {
e.preventDefault();
e.stopPropagation();
if (e.altKey) {
this._validatedOpening();
return false;
}
return true;
},
enter: function enter(e) {
if (this.option("opened")) {
e.preventDefault();
this._valueChangeEventHandler(e);
}
return true;
},
home: homeEndHandler,
end: homeEndHandler
});
},
_getDefaultOptions: function _getDefaultOptions() {
return extend(this.callBase(), {
/**
* @name dxDropDownEditorOptions.value
* @publicName value
* @type any
* @default null
*/
value: null,
/**
* @name dxDropDownEditorOptions.onOpened
* @publicName onOpened
* @extends Action
* @action
*/
onOpened: null,
/**
* @name dxDropDownEditorOptions.onClosed
* @publicName onClosed
* @extends Action
* @action
*/
onClosed: null,
/**
* @name dxDropDownEditorOptions.opened
* @publicName opened
* @type boolean
* @default false
* @fires dxDropDownEditorOptions.onOpened
* @fires dxDropDownEditorOptions.onClosed
*/
opened: false,
/**
* @name dxDropDownEditorOptions.acceptCustomValue
* @publicName acceptCustomValue
* @type boolean
* @default true
*/
acceptCustomValue: true,
/**
* @name dxDropDownEditorOptions.applyValueMode
* @publicName applyValueMode
* @type Enums.EditorApplyValueMode
* @default "instantly"
*/
applyValueMode: "instantly",
/**
* @name dxDropDownEditorOptions.deferRendering
* @publicName deferRendering
* @type boolean
* @default true
*/
deferRendering: true,
/**
* @name dxDropDownEditorOptions.activeStateEnabled
* @publicName activeStateEnabled
* @type boolean
* @default true
* @inheritdoc
*/
activeStateEnabled: true,
/**
* @name dxDropDownEditorOptions.dropDownButtonTemplate
* @publicName dropDownButtonTemplate
* @type template|function
* @default "dropDownButton"
* @type_function_param1 buttonData:object
* @type_function_param1_field1 text:string
* @type_function_param1_field2 icon:string
* @type_function_param2 contentElement:dxElement
* @type_function_return string|Node|jQuery
*/
dropDownButtonTemplate: 'dropDownButton',
fieldTemplate: null,
contentTemplate: null,
openOnFieldClick: false,
showDropDownButton: true,
popupPosition: this._getDefaultPopupPosition(),
onPopupInitialized: null,
applyButtonText: messageLocalization.format("OK"),
cancelButtonText: messageLocalization.format("Cancel"),
buttonsLocation: "default",
showPopupTitle: false
/**
* @name dxDropDownEditorOptions.mask
* @publicName mask
* @hidden
* @inheritdoc
*/
/**
* @name dxDropDownEditorOptions.maskChar
* @publicName maskChar
* @hidden
* @inheritdoc
*/
/**
* @name dxDropDownEditorOptions.maskRules
* @publicName maskRules
* @hidden
* @inheritdoc
*/
/**
* @name dxDropDownEditorOptions.maskInvalidMessage
* @publicName maskInvalidMessage
* @hidden
* @inheritdoc
*/
/**
* @name dxDropDownEditorOptions.useMaskedValue
* @publicName useMaskedValue
* @hidden
* @inheritdoc
*/
/**
* @name dxDropDownEditorOptions.mode
* @publicName mode
* @hidden
* @inheritdoc
*/
/**
* @name dxDropDownEditorOptions.showMaskMode
* @publicName showMaskMode
* @hidden
* @inheritdoc
*/
});
},
_getDefaultPopupPosition: function _getDefaultPopupPosition() {
var position = getDefaultAlignment(this.option("rtlEnabled"));
return {
offset: { h: 0, v: -1 },
my: position + " top",
at: position + " bottom",
collision: "flip flip"
};
},
_defaultOptionsRules: function _defaultOptionsRules() {
return this.callBase().concat([{
device: function device(_device) {
var isGeneric = _device.platform === "generic",
isWin10 = _device.platform === "win" && _device.version && _device.version[0] === 10;
return isGeneric || isWin10;
},
options: {
popupPosition: { offset: { v: 0 } }
}
}]);
},
_inputWrapper: function _inputWrapper() {
return this.$element().find("." + DROP_DOWN_EDITOR_INPUT_WRAPPER_CLASS);
},
_init: function _init() {
this.callBase();
this._initVisibilityActions();
this._initPopupInitializedAction();
},
_initVisibilityActions: function _initVisibilityActions() {
this._openAction = this._createActionByOption("onOpened", {
excludeValidators: ["disabled", "readOnly"]
});
this._closeAction = this._createActionByOption("onClosed", {
excludeValidators: ["disabled", "readOnly"]
});
},
_initPopupInitializedAction: function _initPopupInitializedAction() {
this._popupInitializedAction = this._createActionByOption("onPopupInitialized", {
excludeValidators: ["disabled", "readOnly", "designMode"]
});
},
_initMarkup: function _initMarkup() {
this.callBase();
this.$element().addClass(DROP_DOWN_EDITOR_CLASS);
this.setAria("role", "combobox");
},
_render: function _render() {
this.callBase();
this._renderOpenHandler();
this._renderOpenedState();
},
_renderContentImpl: function _renderContentImpl() {
if (!this.option("deferRendering")) {
this._createPopup();
}
},
_renderInput: function _renderInput() {
this.callBase();
this.$element().wrapInner($("<div>").addClass(DROP_DOWN_EDITOR_INPUT_WRAPPER_CLASS));
this._$container = this.$element().children().eq(0);
this.setAria({
"haspopup": "true",
"autocomplete": "list"
});
},
_readOnlyPropValue: function _readOnlyPropValue() {
return !this.option("acceptCustomValue") || this.callBase();
},
_cleanFocusState: function _cleanFocusState() {
this.callBase();
if (this.option("fieldTemplate")) {
eventsEngine.off(this._input(), "focusin focusout beforeactivate");
}
},
_renderField: function _renderField() {
var fieldTemplate = this._getTemplateByOption("fieldTemplate");
if (!(fieldTemplate && this.option("fieldTemplate"))) {
return;
}
this._renderTemplatedField(fieldTemplate, this._fieldRenderData());
},
_renderTemplatedField: function _renderTemplatedField(fieldTemplate, data) {
var isFocused = focused(this._input());
this._resetFocus(isFocused);
var $container = this._$container;
$container.empty();
this._$dropDownButton = null;
this._$clearButton = null;
fieldTemplate.render({
model: data,
container: domUtils.getPublicElement($container)
});
if (!this._input().length) {
throw errors.Error("E1010");
}
this._refreshEvents();
this._refreshValueChangeEvent();
this._renderFocusState();
isFocused && eventsEngine.trigger(this._input(), "focus");
},
_resetFocus: function _resetFocus(isFocused) {
this._cleanFocusState();
isFocused && eventsEngine.trigger(this._input(), "focusout");
},
_fieldRenderData: function _fieldRenderData() {
return this.option("value");
},
_renderInputAddons: function _renderInputAddons() {
this._renderField();
this.callBase();
this._renderDropDownButton();
},
_renderDropDownButton: function _renderDropDownButton() {
if (this._$dropDownButton) {
this._$dropDownButton.remove();
this._$dropDownButton = null;
}
var showDropDownButton = this.option("showDropDownButton");
this.$element().toggleClass(DROP_DOWN_EDITOR_BUTTON_VISIBLE, showDropDownButton);
if (!showDropDownButton) return;
this._$dropDownButton = this._createDropDownButton();
this._attachDropDownButtonClickHandler();
},
_attachDropDownButtonClickHandler: function _attachDropDownButtonClickHandler() {
if (this.option("showDropDownButton") && !this.option("openOnFieldClick")) {
this._$dropDownButton.dxButton("option", "onClick", this._openHandler.bind(this));
}
},
_initTemplates: function _initTemplates() {
this.callBase();
this._defaultTemplates['dropDownButton'] = new FunctionTemplate(function (options) {
var $icon = $("<div>").addClass(DROP_DOWN_EDITOR_BUTTON_ICON);
$(options.container).append($icon);
}, this);
},
_createDropDownButton: function _createDropDownButton() {
var $button = $("<div>").addClass(DROP_DOWN_EDITOR_BUTTON_CLASS).prependTo(this._buttonsContainer());
this._createComponent($button, Button, {
focusStateEnabled: false,
hoverStateEnabled: false,
activeStateEnabled: false,
disabled: this.option("readOnly"),
useInkRipple: false,
template: this._getTemplateByOption("dropDownButtonTemplate")
});
$button.removeClass("dx-button");
eventsEngine.on($button, "mousedown", function (e) {
e.preventDefault();
});
return $button;
},
_renderOpenHandler: function _renderOpenHandler() {
var that = this,
$inputWrapper = that._inputWrapper(),
eventName = eventUtils.addNamespace(clickEvent.name, that.NAME),
openOnFieldClick = that.option("openOnFieldClick");
eventsEngine.off($inputWrapper, eventName);
eventsEngine.on($inputWrapper, eventName, that._getInputClickHandler(openOnFieldClick));
that.$element().toggleClass(DROP_DOWN_EDITOR_FIELD_CLICKABLE, openOnFieldClick);
if (openOnFieldClick) {
that._openOnFieldClickAction = that._createAction(that._openHandler.bind(that));
}
},
_getInputClickHandler: function _getInputClickHandler(openOnFieldClick) {
var that = this;
return openOnFieldClick ? function (e) {
that._executeOpenAction(e);
} : function (e) {
that._focusInput();
};
},
_openHandler: function _openHandler() {
this._toggleOpenState();
},
_executeOpenAction: function _executeOpenAction(e) {
this._openOnFieldClickAction({ event: e });
},
_keyboardEventBindingTarget: function _keyboardEventBindingTarget() {
return this._input();
},
_focusInput: function _focusInput() {
if (this.option("disabled")) {
return false;
}
if (!focused(this._input())) {
eventsEngine.trigger(this._input(), "focus");
}
return true;
},
_toggleOpenState: function _toggleOpenState(isVisible) {
if (!this._focusInput()) {
return;
}
if (!this.option("readOnly")) {
isVisible = arguments.length ? isVisible : !this.option("opened");
this.option("opened", isVisible);
}
},
_renderOpenedState: function _renderOpenedState() {
var opened = this.option("opened");
if (opened) {
this._createPopup();
}
this.$element().toggleClass(DROP_DOWN_EDITOR_ACTIVE, opened);
this._setPopupOption("visible", opened);
this.setAria({
"expanded": opened,
"owns": (opened || undefined) && this._popupContentId
});
},
_createPopup: function _createPopup() {
if (this._$popup) {
return;
}
this._$popup = $("<div>").addClass(DROP_DOWN_EDITOR_OVERLAY).addClass(this.option("customOverlayCssClass")).appendTo(this.$element());
this._renderPopup();
this._renderPopupContent();
},
_renderPopup: function _renderPopup() {
this._popup = this._createComponent(this._$popup, Popup, this._popupConfig());
this._popup.on({
"showing": this._popupShowingHandler.bind(this),
"shown": this._popupShownHandler.bind(this),
"hiding": this._popupHidingHandler.bind(this),
"hidden": this._popupHiddenHandler.bind(this)
});
this._popup.option("onContentReady", this._contentReadyHandler.bind(this));
this._contentReadyHandler();
this._popupContentId = "dx-" + new Guid();
this.setAria("id", this._popupContentId, this._popup.$content());
},
_contentReadyHandler: commonUtils.noop,
_popupConfig: function _popupConfig() {
return {
onInitialized: this._popupInitializedHandler(),
position: extend(this.option("popupPosition"), {
of: this.$element()
}),
showTitle: this.option("showPopupTitle"),
width: "auto",
height: "auto",
shading: false,
closeOnTargetScroll: true,
closeOnOutsideClick: this._closeOutsideDropDownHandler.bind(this),
animation: {
show: { type: "fade", duration: 0, from: 0, to: 1 },
hide: { type: "fade", duration: 400, from: 1, to: 0 }
},
deferRendering: false,
focusStateEnabled: false,
showCloseButton: false,
toolbarItems: this._getPopupToolbarItems(),
onPositioned: this._popupPositionedHandler.bind(this),
fullScreen: false
};
},
_popupInitializedHandler: function _popupInitializedHandler() {
if (!this.option("onPopupInitialized")) {
return;
}
return function (e) {
this._popupInitializedAction({ popup: e.component });
}.bind(this);
},
_popupPositionedHandler: function _popupPositionedHandler(e) {
e.position && this._popup.overlayContent().toggleClass(DROP_DOWN_EDITOR_OVERLAY_FLIPPED, e.position.v.flip);
},
_popupShowingHandler: commonUtils.noop,
_popupHidingHandler: function _popupHidingHandler() {
this.option("opened", false);
},
_popupShownHandler: function _popupShownHandler() {
this._openAction();
if (this._$validationMessage) {
this._$validationMessage.dxOverlay("option", "position", this._getValidationMessagePosition());
}
},
_popupHiddenHandler: function _popupHiddenHandler() {
this._closeAction();
if (this._$validationMessage) {
this._$validationMessage.dxOverlay("option", "position", this._getValidationMessagePosition());
}
},
_getValidationMessagePosition: function _getValidationMessagePosition() {
var positionRequest = "below";
if (this._popup && this._popup.option("visible")) {
var myTop = positionUtils.setup(this.$element()).top,
popupTop = positionUtils.setup(this._popup.$content()).top;
positionRequest = myTop + this.option("popupPosition").offset.v > popupTop ? "below" : "above";
}
return this.callBase(positionRequest);
},
_renderPopupContent: function _renderPopupContent() {
var contentTemplate = this._getTemplateByOption("contentTemplate");
if (!(contentTemplate && this.option("contentTemplate"))) {
return;
}
var $popupContent = this._popup.$content(),
templateData = {
value: this._fieldRenderData(),
component: this
};
$popupContent.empty();
contentTemplate.render({
container: domUtils.getPublicElement($popupContent),
model: templateData
});
},
_closeOutsideDropDownHandler: function _closeOutsideDropDownHandler(e) {
var $target = $(e.target);
var isInputClicked = !!$target.closest(this.$element()).length;
var isDropDownButtonClicked = !!$target.closest(this._$dropDownButton).length;
var isOutsideClick = !isInputClicked && !isDropDownButtonClicked;
return isOutsideClick;
},
_clean: function _clean() {
delete this._$dropDownButton;
delete this._openOnFieldClickAction;
if (this._$popup) {
this._$popup.remove();
delete this._$popup;
delete this._popup;
}
this.callBase();
},
_setPopupOption: function _setPopupOption(optionName, value) {
this._setWidgetOption("_popup", arguments);
},
_validatedOpening: function _validatedOpening() {
if (!this.option("readOnly")) {
this._toggleOpenState(true);
}
},
_getPopupToolbarItems: function _getPopupToolbarItems() {
return this.option("applyValueMode") === "useButtons" ? this._popupToolbarItemsConfig() : [];
},
_getFirstPopupElement: function _getFirstPopupElement() {
return this._popup._wrapper().find(".dx-popup-done.dx-button");
},
_getLastPopupElement: function _getLastPopupElement() {
return this._popup._wrapper().find(".dx-popup-cancel.dx-button");
},
_popupElementTabHandler: function _popupElementTabHandler(e) {
var $element = $(e.currentTarget);
if (e.shiftKey && $element.is(this._getFirstPopupElement()) || !e.shiftKey && $element.is(this._getLastPopupElement())) {
eventsEngine.trigger(this._input(), "focus");
e.preventDefault();
}
},
_popupElementEscHandler: function _popupElementEscHandler() {
eventsEngine.trigger(this._input(), "focus");
this.close();
},
_popupButtonInitializedHandler: function _popupButtonInitializedHandler(e) {
e.component.registerKeyHandler("tab", this._popupElementTabHandler.bind(this));
e.component.registerKeyHandler("escape", this._popupElementEscHandler.bind(this));
},
_popupToolbarItemsConfig: function _popupToolbarItemsConfig() {
var buttonsConfig = [{
shortcut: "done",
options: {
onClick: this._applyButtonHandler.bind(this),
text: this.option("applyButtonText"),
onInitialized: this._popupButtonInitializedHandler.bind(this)
}
}, {
shortcut: "cancel",
options: {
onClick: this._cancelButtonHandler.bind(this),
text: this.option("cancelButtonText"),
onInitialized: this._popupButtonInitializedHandler.bind(this)
}
}];
return this._applyButtonsLocation(buttonsConfig);
},
_applyButtonsLocation: function _applyButtonsLocation(buttonsConfig) {
var buttonsLocation = this.option("buttonsLocation"),
resultConfig = buttonsConfig;
if (buttonsLocation !== "default") {
var position = commonUtils.splitPair(buttonsLocation);
each(resultConfig, function (_, element) {
extend(element, {
toolbar: position[0],
location: position[1]
});
});
}
return resultConfig;
},
_applyButtonHandler: function _applyButtonHandler() {
this.close();
this.option("focusStateEnabled") && this.focus();
},
_cancelButtonHandler: function _cancelButtonHandler() {
this.close();
this.option("focusStateEnabled") && this.focus();
},
_toggleReadOnlyState: function _toggleReadOnlyState() {
this.callBase();
this._$dropDownButton && this._$dropDownButton.dxButton("option", "disabled", this.option("readOnly"));
},
_optionChanged: function _optionChanged(args) {
switch (args.name) {
case "opened":
this._renderOpenedState();
break;
case "onOpened":
case "onClosed":
this._initVisibilityActions();
break;
case "onPopupInitialized":
this._initPopupInitializedAction();
break;
case "fieldTemplate":
if (isDefined(args.value)) {
this._renderInputAddons();
} else {
this._invalidate();
}
break;
case "showDropDownButton":
case "contentTemplate":
case "acceptCustomValue":
case "openOnFieldClick":
this._invalidate();
break;
case "dropDownButtonTemplate":
this._renderDropDownButton();
break;
case "popupPosition":
case "deferRendering":
break;
case "applyValueMode":
case "applyButtonText":
case "cancelButtonText":
case "buttonsLocation":
this._setPopupOption("toolbarItems", this._getPopupToolbarItems());
break;
case "showPopupTitle":
this._setPopupOption("showTitle", args.value);
break;
default:
this.callBase(args);
}
},
/**
* @name dxDropDownEditorMethods.open
* @publicName open()
*/
open: function open() {
this.option("opened", true);
},
/**
* @name dxDropDownEditorMethods.close
* @publicName close()
*/
close: function close() {
this.option("opened", false);
},
/**
* @name dxDropDownEditorMethods.reset
* @publicName reset()
*/
reset: function reset() {
this.option("value", null);
this._input().val("");
},
/**
* @name dxDropDownEditorMethods.field
* @publicName field()
* @return dxElement
*/
field: function field() {
return getPublicElement(this._input());
},
/**
* @name dxDropDownEditorMethods.content
* @publicName content()
* @return dxElement
*/
content: function content() {
return this._popup ? this._popup.content() : null;
}
});
registerComponent("dxDropDownEditor", DropDownEditor);
module.exports = DropDownEditor;