devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
550 lines (549 loc) • 20.8 kB
JavaScript
/**
* DevExtreme (ui/widget/ui.widget.js)
* Version: 19.2.6
* Build date: Thu Jan 30 2020
*
* Copyright (c) 2012 - 2020 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
var $ = require("../../core/renderer");
var eventsEngine = require("../../events/core/events_engine");
var Action = require("../../core/action");
var extend = require("../../core/utils/extend").extend;
var inArray = require("../../core/utils/array").inArray;
var each = require("../../core/utils/iterator").each;
var commonUtils = require("../../core/utils/common");
var typeUtils = require("../../core/utils/type");
var domAdapter = require("../../core/dom_adapter");
var DOMComponentWithTemplate = require("../../core/dom_component_with_template");
var KeyboardProcessor = require("./ui.keyboard_processor");
var selectors = require("./selectors");
var eventUtils = require("../../events/utils");
var hoverEvents = require("../../events/hover");
var feedbackEvents = require("../../events/core/emitter.feedback");
var clickEvent = require("../../events/click");
var UI_FEEDBACK = "UIFeedback";
var WIDGET_CLASS = "dx-widget";
var ACTIVE_STATE_CLASS = "dx-state-active";
var DISABLED_STATE_CLASS = "dx-state-disabled";
var INVISIBLE_STATE_CLASS = "dx-state-invisible";
var HOVER_STATE_CLASS = "dx-state-hover";
var FOCUSED_STATE_CLASS = "dx-state-focused";
var FEEDBACK_SHOW_TIMEOUT = 30;
var FEEDBACK_HIDE_TIMEOUT = 400;
var FOCUS_NAMESPACE = "Focus";
var Widget = DOMComponentWithTemplate.inherit({
_supportedKeys: function() {
return {}
},
_getDefaultOptions: function() {
return extend(this.callBase(), {
disabled: false,
visible: true,
hint: void 0,
activeStateEnabled: false,
onContentReady: null,
hoverStateEnabled: false,
focusStateEnabled: false,
tabIndex: 0,
accessKey: null,
onFocusIn: null,
onFocusOut: null,
_keyboardProcessor: void 0
})
},
_feedbackShowTimeout: FEEDBACK_SHOW_TIMEOUT,
_feedbackHideTimeout: FEEDBACK_HIDE_TIMEOUT,
_init: function() {
this.callBase();
this._initContentReadyAction()
},
_clearInnerOptionCache: function(optionContainer) {
this[optionContainer + "Cache"] = {}
},
_cacheInnerOptions: function(optionContainer, optionValue) {
var cacheName = optionContainer + "Cache";
this[cacheName] = extend(this[cacheName], optionValue)
},
_getOptionsFromContainer: function(_ref) {
var name = _ref.name,
fullName = _ref.fullName,
value = _ref.value;
var options = {};
if (name === fullName) {
options = value
} else {
var option = fullName.split(".").pop();
options[option] = value
}
return options
},
_innerOptionChanged: function(innerWidget, args) {
var options = this._getOptionsFromContainer(args);
innerWidget && innerWidget.option(options);
this._cacheInnerOptions(args.name, options)
},
_getInnerOptionsCache: function(optionContainer) {
return this[optionContainer + "Cache"]
},
_initInnerOptionCache: function(optionContainer) {
this._clearInnerOptionCache(optionContainer);
this._cacheInnerOptions(optionContainer, this.option(optionContainer))
},
_bindInnerWidgetOptions: function(innerWidget, optionsContainer) {
this._options[optionsContainer] = extend({}, innerWidget.option());
innerWidget.on("optionChanged", function(e) {
this._options[optionsContainer] = extend({}, e.component.option())
}.bind(this))
},
_getAriaTarget: function() {
return this._focusTarget()
},
_initContentReadyAction: function() {
this._contentReadyAction = this._createActionByOption("onContentReady", {
excludeValidators: ["disabled", "readOnly"]
})
},
_initMarkup: function() {
this.$element().addClass(WIDGET_CLASS);
this._toggleDisabledState(this.option("disabled"));
this._toggleVisibility(this.option("visible"));
this._renderHint();
if (this._isFocusable()) {
this._renderFocusTarget()
}
this.callBase()
},
_render: function() {
this.callBase();
this._renderContent();
this._renderFocusState();
this._attachFeedbackEvents();
this._attachHoverEvents()
},
_renderHint: function() {
var hint = this.option("hint");
this.$element().attr("title", hint ? hint : null)
},
_renderContent: function() {
var _this = this;
commonUtils.deferRender(function() {
if (_this._disposed) {
return
}
return _this._renderContentImpl()
}).done(function() {
if (_this._disposed) {
return
}
_this._fireContentReadyAction()
})
},
_renderContentImpl: commonUtils.noop,
_fireContentReadyAction: commonUtils.deferRenderer(function() {
this._contentReadyAction()
}),
_dispose: function() {
this._contentReadyAction = null;
this.callBase()
},
_resetActiveState: function() {
this._toggleActiveState(this._eventBindingTarget(), false)
},
_clean: function() {
this._cleanFocusState();
this._resetActiveState();
this.callBase();
this.$element().empty()
},
_toggleVisibility: function(visible) {
this.$element().toggleClass(INVISIBLE_STATE_CLASS, !visible);
this.setAria("hidden", !visible || void 0)
},
_renderFocusState: function() {
this._attachKeyboardEvents();
if (!this._isFocusable()) {
return
}
this._renderFocusTarget();
this._attachFocusEvents();
this._renderAccessKey()
},
_renderAccessKey: function() {
var focusTarget = this._focusTarget();
focusTarget.attr("accesskey", this.option("accessKey"));
var clickNamespace = eventUtils.addNamespace(clickEvent.name, UI_FEEDBACK);
eventsEngine.off(focusTarget, clickNamespace);
this.option("accessKey") && eventsEngine.on(focusTarget, clickNamespace, function(e) {
if (eventUtils.isFakeClickEvent(e)) {
e.stopImmediatePropagation();
this.focus()
}
}.bind(this))
},
_isFocusable: function() {
return this.option("focusStateEnabled") && !this.option("disabled")
},
_eventBindingTarget: function() {
return this.$element()
},
_focusTarget: function() {
return this._getActiveElement()
},
_getActiveElement: function() {
var activeElement = this._eventBindingTarget();
if (this._activeStateUnit) {
activeElement = activeElement.find(this._activeStateUnit).not("." + DISABLED_STATE_CLASS)
}
return activeElement
},
_renderFocusTarget: function() {
this._focusTarget().attr("tabIndex", this.option("tabIndex"))
},
_keyboardEventBindingTarget: function() {
return this._eventBindingTarget()
},
_detachFocusEvents: function() {
var $element = this._focusEventTarget();
var namespace = this.NAME + FOCUS_NAMESPACE;
var focusEvents = eventUtils.addNamespace("focusin", namespace);
focusEvents = focusEvents + " " + eventUtils.addNamespace("focusout", namespace);
if (domAdapter.hasDocumentProperty("onbeforeactivate")) {
focusEvents = focusEvents + " " + eventUtils.addNamespace("beforeactivate", namespace)
}
eventsEngine.off($element, focusEvents)
},
_attachFocusEvents: function() {
var namespace = this.NAME + FOCUS_NAMESPACE;
var focusInEvent = eventUtils.addNamespace("focusin", namespace);
var focusOutEvent = eventUtils.addNamespace("focusout", namespace);
var $focusTarget = this._focusEventTarget();
eventsEngine.on($focusTarget, focusInEvent, this._focusInHandler.bind(this));
eventsEngine.on($focusTarget, focusOutEvent, this._focusOutHandler.bind(this));
if (domAdapter.hasDocumentProperty("onbeforeactivate")) {
var beforeActivateEvent = eventUtils.addNamespace("beforeactivate", namespace);
eventsEngine.on(this._focusEventTarget(), beforeActivateEvent, function(e) {
if (!$(e.target).is(selectors.focusable)) {
e.preventDefault()
}
})
}
},
_refreshFocusEvent: function() {
this._detachFocusEvents();
this._attachFocusEvents()
},
_focusEventTarget: function() {
return this._focusTarget()
},
_focusInHandler: function(e) {
if (e.isDefaultPrevented()) {
return
}
var that = this;
that._createActionByOption("onFocusIn", {
beforeExecute: function() {
that._updateFocusState(e, true)
},
excludeValidators: ["readOnly"]
})({
event: e
})
},
_focusOutHandler: function(e) {
if (e.isDefaultPrevented()) {
return
}
var that = this;
that._createActionByOption("onFocusOut", {
beforeExecute: function() {
that._updateFocusState(e, false)
},
excludeValidators: ["readOnly", "disabled"]
})({
event: e
})
},
_updateFocusState: function(e, isFocused) {
var target = e.target;
if (inArray(target, this._focusTarget()) !== -1) {
this._toggleFocusClass(isFocused, $(target))
}
},
_toggleFocusClass: function(isFocused, $element) {
var $focusTarget = $element && $element.length ? $element : this._focusTarget();
$focusTarget.toggleClass(FOCUSED_STATE_CLASS, isFocused)
},
_hasFocusClass: function(element) {
var $focusTarget = $(element || this._focusTarget());
return $focusTarget.hasClass(FOCUSED_STATE_CLASS)
},
_isFocused: function() {
return this._hasFocusClass()
},
_attachKeyboardEvents: function() {
var processor = this.option("_keyboardProcessor");
if (processor) {
this._keyboardProcessor = processor.reinitialize(this._keyboardHandler, this)
} else {
if (this.option("focusStateEnabled")) {
this._disposeKeyboardProcessor();
this._keyboardProcessor = new KeyboardProcessor({
element: this._keyboardEventBindingTarget(),
handler: this._keyboardHandler,
focusTarget: this._focusTarget(),
context: this
})
}
}
},
_keyboardHandler: function(options) {
var e = options.originalEvent;
var keyName = options.keyName;
var keyCode = options.which;
var keys = this._supportedKeys(e);
var func = keys[keyName] || keys[keyCode];
if (void 0 !== func) {
var handler = func.bind(this);
return handler(e) || false
} else {
return true
}
},
_refreshFocusState: function() {
this._cleanFocusState();
this._renderFocusState()
},
_cleanFocusState: function() {
var $element = this._focusTarget();
this._detachFocusEvents();
this._toggleFocusClass(false);
$element.removeAttr("tabIndex");
this._disposeKeyboardProcessor()
},
_disposeKeyboardProcessor: function() {
if (this._keyboardProcessor) {
this._keyboardProcessor.dispose();
delete this._keyboardProcessor
}
},
_attachHoverEvents: function() {
var that = this;
var hoverableSelector = that._activeStateUnit;
var nameStart = eventUtils.addNamespace(hoverEvents.start, UI_FEEDBACK);
var nameEnd = eventUtils.addNamespace(hoverEvents.end, UI_FEEDBACK);
eventsEngine.off(that._eventBindingTarget(), nameStart, hoverableSelector);
eventsEngine.off(that._eventBindingTarget(), nameEnd, hoverableSelector);
if (that.option("hoverStateEnabled")) {
var startAction = new Action(function(args) {
that._hoverStartHandler(args.event);
that._refreshHoveredElement($(args.element))
}, {
excludeValidators: ["readOnly"]
});
var $eventBindingTarget = that._eventBindingTarget();
eventsEngine.on($eventBindingTarget, nameStart, hoverableSelector, function(e) {
startAction.execute({
element: $(e.target),
event: e
})
});
eventsEngine.on($eventBindingTarget, nameEnd, hoverableSelector, function(e) {
that._hoverEndHandler(e);
that._forgetHoveredElement()
})
} else {
that._toggleHoverClass(false)
}
},
_hoverStartHandler: commonUtils.noop,
_hoverEndHandler: commonUtils.noop,
_attachFeedbackEvents: function() {
var that = this;
var feedbackSelector = that._activeStateUnit;
var activeEventName = eventUtils.addNamespace(feedbackEvents.active, UI_FEEDBACK);
var inactiveEventName = eventUtils.addNamespace(feedbackEvents.inactive, UI_FEEDBACK);
var feedbackAction;
var feedbackActionDisabled;
eventsEngine.off(that._eventBindingTarget(), activeEventName, feedbackSelector);
eventsEngine.off(that._eventBindingTarget(), inactiveEventName, feedbackSelector);
if (that.option("activeStateEnabled")) {
var feedbackActionHandler = function(args) {
var $element = $(args.element);
var value = args.value;
var dxEvent = args.event;
that._toggleActiveState($element, value, dxEvent)
};
eventsEngine.on(that._eventBindingTarget(), activeEventName, feedbackSelector, {
timeout: that._feedbackShowTimeout
}, function(e) {
feedbackAction = feedbackAction || new Action(feedbackActionHandler);
feedbackAction.execute({
element: $(e.currentTarget),
value: true,
event: e
})
});
eventsEngine.on(that._eventBindingTarget(), inactiveEventName, feedbackSelector, {
timeout: that._feedbackHideTimeout
}, function(e) {
feedbackActionDisabled = feedbackActionDisabled || new Action(feedbackActionHandler, {
excludeValidators: ["disabled", "readOnly"]
});
feedbackActionDisabled.execute({
element: $(e.currentTarget),
value: false,
event: e
})
})
}
},
_toggleActiveState: function($element, value) {
this._toggleHoverClass(!value);
$element.toggleClass(ACTIVE_STATE_CLASS, value)
},
_refreshHoveredElement: function(hoveredElement) {
var selector = this._activeStateUnit || this._eventBindingTarget();
this._forgetHoveredElement();
this._hoveredElement = hoveredElement.closest(selector);
this._toggleHoverClass(true)
},
_forgetHoveredElement: function() {
this._toggleHoverClass(false);
delete this._hoveredElement
},
_toggleHoverClass: function(value) {
if (this._hoveredElement) {
this._hoveredElement.toggleClass(HOVER_STATE_CLASS, value && this.option("hoverStateEnabled"))
}
},
_toggleDisabledState: function(value) {
this.$element().toggleClass(DISABLED_STATE_CLASS, Boolean(value));
this._toggleHoverClass(!value);
this.setAria("disabled", value || void 0)
},
_setWidgetOption: function(widgetName, args) {
if (!this[widgetName]) {
return
}
if (typeUtils.isPlainObject(args[0])) {
each(args[0], function(option, value) {
this._setWidgetOption(widgetName, [option, value])
}.bind(this));
return
}
var optionName = args[0];
var value = args[1];
if (1 === args.length) {
value = this.option(optionName)
}
var widgetOptionMap = this[widgetName + "OptionMap"];
this[widgetName].option(widgetOptionMap ? widgetOptionMap(optionName) : optionName, value)
},
_optionChanged: function(args) {
switch (args.name) {
case "disabled":
this._toggleDisabledState(args.value);
this._refreshFocusState();
break;
case "hint":
this._renderHint();
break;
case "activeStateEnabled":
this._attachFeedbackEvents();
break;
case "hoverStateEnabled":
this._attachHoverEvents();
break;
case "tabIndex":
case "_keyboardProcessor":
case "focusStateEnabled":
this._refreshFocusState();
break;
case "onFocusIn":
case "onFocusOut":
break;
case "accessKey":
this._renderAccessKey();
break;
case "visible":
var visible = args.value;
this._toggleVisibility(visible);
if (this._isVisibilityChangeSupported()) {
this._checkVisibilityChanged(args.value ? "shown" : "hiding")
}
break;
case "onContentReady":
this._initContentReadyAction();
break;
default:
this.callBase(args)
}
},
_isVisible: function() {
return this.callBase() && this.option("visible")
},
beginUpdate: function() {
this._ready(false);
this.callBase()
},
endUpdate: function() {
this.callBase();
if (this._initialized) {
this._ready(true)
}
},
_ready: function(value) {
if (0 === arguments.length) {
return this._isReady
}
this._isReady = value
},
setAria: function() {
var setAttribute = function(option) {
var attrName = "role" === option.name || "id" === option.name ? option.name : "aria-" + option.name;
var attrValue = option.value;
if (typeUtils.isDefined(attrValue)) {
attrValue = attrValue.toString()
} else {
attrValue = null
}
option.target.attr(attrName, attrValue)
};
if (!typeUtils.isPlainObject(arguments[0])) {
setAttribute({
name: arguments[0],
value: arguments[1],
target: arguments[2] || this._getAriaTarget()
})
} else {
var $target = arguments[1] || this._getAriaTarget();
each(arguments[0], function(key, value) {
setAttribute({
name: key,
value: value,
target: $target
})
})
}
},
isReady: function() {
return this._ready()
},
repaint: function() {
this._refresh()
},
focus: function() {
eventsEngine.trigger(this._focusTarget(), "focus")
},
registerKeyHandler: function(key, handler) {
var currentKeys = this._supportedKeys();
var addingKeys = {};
addingKeys[key] = handler;
this._supportedKeys = function() {
return extend(currentKeys, addingKeys)
}
}
});
module.exports = Widget;