ui.widget.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
  1. /**
  2. * DevExtreme (ui/widget/ui.widget.js)
  3. * Version: 19.1.16
  4. * Build date: Tue Oct 18 2022
  5. *
  6. * Copyright (c) 2012 - 2022 Developer Express Inc. ALL RIGHTS RESERVED
  7. * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
  8. */
  9. "use strict";
  10. var $ = require("../../core/renderer");
  11. var eventsEngine = require("../../events/core/events_engine");
  12. var errors = require("./ui.errors");
  13. var Action = require("../../core/action");
  14. var extend = require("../../core/utils/extend").extend;
  15. var inArray = require("../../core/utils/array").inArray;
  16. var each = require("../../core/utils/iterator").each;
  17. var commonUtils = require("../../core/utils/common");
  18. var typeUtils = require("../../core/utils/type");
  19. var domUtils = require("../../core/utils/dom");
  20. var domAdapter = require("../../core/dom_adapter");
  21. var devices = require("../../core/devices");
  22. var DOMComponent = require("../../core/dom_component");
  23. var Template = require("./template");
  24. var TemplateBase = require("./ui.template_base");
  25. var FunctionTemplate = require("./function_template");
  26. var EmptyTemplate = require("./empty_template");
  27. var ChildDefaultTemplate = require("./child_default_template");
  28. var KeyboardProcessor = require("./ui.keyboard_processor");
  29. var selectors = require("./selectors");
  30. var eventUtils = require("../../events/utils");
  31. var hoverEvents = require("../../events/hover");
  32. var feedbackEvents = require("../../events/core/emitter.feedback");
  33. var clickEvent = require("../../events/click");
  34. var inflector = require("../../core/utils/inflector");
  35. var UI_FEEDBACK = "UIFeedback";
  36. var WIDGET_CLASS = "dx-widget";
  37. var ACTIVE_STATE_CLASS = "dx-state-active";
  38. var DISABLED_STATE_CLASS = "dx-state-disabled";
  39. var INVISIBLE_STATE_CLASS = "dx-state-invisible";
  40. var HOVER_STATE_CLASS = "dx-state-hover";
  41. var FOCUSED_STATE_CLASS = "dx-state-focused";
  42. var FEEDBACK_SHOW_TIMEOUT = 30;
  43. var FEEDBACK_HIDE_TIMEOUT = 400;
  44. var FOCUS_NAMESPACE = "Focus";
  45. var ANONYMOUS_TEMPLATE_NAME = "template";
  46. var TEXT_NODE = 3;
  47. var TEMPLATE_SELECTOR = "[data-options*='dxTemplate']";
  48. var TEMPLATE_WRAPPER_CLASS = "dx-template-wrapper";
  49. var DX_POLYMORPH_WIDGET_TEMPLATE = new FunctionTemplate(function(options) {
  50. var widgetName = options.model.widget;
  51. if (widgetName) {
  52. var widgetElement = $("<div>");
  53. var widgetOptions = options.model.options || {};
  54. if ("button" === widgetName || "tabs" === widgetName || "dropDownMenu" === widgetName) {
  55. var deprecatedName = widgetName;
  56. widgetName = inflector.camelize("dx-" + widgetName);
  57. errors.log("W0001", "dxToolbar - 'widget' item field", deprecatedName, "16.1", "Use: '" + widgetName + "' instead")
  58. }
  59. if (options.parent) {
  60. options.parent._createComponent(widgetElement, widgetName, widgetOptions)
  61. } else {
  62. widgetElement[widgetName](widgetOptions)
  63. }
  64. return widgetElement
  65. }
  66. return $()
  67. });
  68. var Widget = DOMComponent.inherit({
  69. _supportedKeys: function() {
  70. return {}
  71. },
  72. _getDefaultOptions: function() {
  73. return extend(this.callBase(), {
  74. disabled: false,
  75. visible: true,
  76. hint: void 0,
  77. activeStateEnabled: false,
  78. onContentReady: null,
  79. hoverStateEnabled: false,
  80. focusStateEnabled: false,
  81. tabIndex: 0,
  82. accessKey: null,
  83. onFocusIn: null,
  84. onFocusOut: null,
  85. integrationOptions: {
  86. watchMethod: function(fn, callback, options) {
  87. options = options || {};
  88. if (!options.skipImmediate) {
  89. callback(fn())
  90. }
  91. return commonUtils.noop
  92. },
  93. templates: {
  94. "dx-polymorph-widget": DX_POLYMORPH_WIDGET_TEMPLATE
  95. },
  96. createTemplate: function(element) {
  97. return new Template(element)
  98. }
  99. },
  100. _keyboardProcessor: void 0
  101. })
  102. },
  103. _feedbackShowTimeout: FEEDBACK_SHOW_TIMEOUT,
  104. _feedbackHideTimeout: FEEDBACK_HIDE_TIMEOUT,
  105. _init: function() {
  106. this.callBase();
  107. this._tempTemplates = [];
  108. this._defaultTemplates = {};
  109. this._initTemplates();
  110. this._initContentReadyAction()
  111. },
  112. _initTemplates: function() {
  113. this._extractTemplates();
  114. this._extractAnonymousTemplate()
  115. },
  116. _clearInnerOptionCache: function(optionContainer) {
  117. this[optionContainer + "Cache"] = {}
  118. },
  119. _cacheInnerOptions: function(optionContainer, optionValue) {
  120. var cacheName = optionContainer + "Cache";
  121. this[cacheName] = extend(this[cacheName], optionValue)
  122. },
  123. _getOptionsFromContainer: function(_ref) {
  124. var name = _ref.name,
  125. fullName = _ref.fullName,
  126. value = _ref.value;
  127. var options = {};
  128. if (name === fullName) {
  129. options = value
  130. } else {
  131. var option = fullName.split(".").pop();
  132. options[option] = value
  133. }
  134. return options
  135. },
  136. _innerOptionChanged: function(innerWidget, args) {
  137. var options = this._getOptionsFromContainer(args);
  138. innerWidget && innerWidget.option(options);
  139. this._cacheInnerOptions(args.name, options)
  140. },
  141. _getInnerOptionsCache: function(optionContainer) {
  142. return this[optionContainer + "Cache"]
  143. },
  144. _initInnerOptionCache: function(optionContainer) {
  145. this._clearInnerOptionCache(optionContainer);
  146. this._cacheInnerOptions(optionContainer, this.option(optionContainer))
  147. },
  148. _bindInnerWidgetOptions: function(innerWidget, optionsContainer) {
  149. this._options[optionsContainer] = extend({}, innerWidget.option());
  150. innerWidget.on("optionChanged", function(e) {
  151. this._options[optionsContainer] = extend({}, e.component.option())
  152. }.bind(this))
  153. },
  154. _extractTemplates: function() {
  155. var templateElements = this.$element().contents().filter(TEMPLATE_SELECTOR);
  156. var templatesMap = {};
  157. templateElements.each(function(_, template) {
  158. var templateOptions = domUtils.getElementOptions(template).dxTemplate;
  159. if (!templateOptions) {
  160. return
  161. }
  162. if (!templateOptions.name) {
  163. throw errors.Error("E0023")
  164. }
  165. $(template).addClass(TEMPLATE_WRAPPER_CLASS).detach();
  166. templatesMap[templateOptions.name] = templatesMap[templateOptions.name] || [];
  167. templatesMap[templateOptions.name].push(template)
  168. });
  169. each(templatesMap, function(templateName, value) {
  170. var deviceTemplate = this._findTemplateByDevice(value);
  171. if (deviceTemplate) {
  172. this._saveTemplate(templateName, deviceTemplate)
  173. }
  174. }.bind(this))
  175. },
  176. _saveTemplate: function(name, template) {
  177. var templates = this.option("integrationOptions.templates");
  178. templates[name] = this._createTemplate(template)
  179. },
  180. _findTemplateByDevice: function(templates) {
  181. var suitableTemplate = commonUtils.findBestMatches(devices.current(), templates, function(template) {
  182. return domUtils.getElementOptions(template).dxTemplate
  183. })[0];
  184. each(templates, function(index, template) {
  185. if (template !== suitableTemplate) {
  186. $(template).remove()
  187. }
  188. });
  189. return suitableTemplate
  190. },
  191. _extractAnonymousTemplate: function() {
  192. var templates = this.option("integrationOptions.templates");
  193. var anonymousTemplateName = this._getAnonymousTemplateName();
  194. var $anonymousTemplate = this.$element().contents().detach();
  195. var $notJunkTemplateContent = $anonymousTemplate.filter(function(_, element) {
  196. var isTextNode = element.nodeType === TEXT_NODE;
  197. var isEmptyText = $(element).text().trim().length < 1;
  198. return !(isTextNode && isEmptyText)
  199. });
  200. var onlyJunkTemplateContent = $notJunkTemplateContent.length < 1;
  201. if (!templates[anonymousTemplateName] && !onlyJunkTemplateContent) {
  202. templates[anonymousTemplateName] = this._createTemplate($anonymousTemplate)
  203. }
  204. },
  205. _getAriaTarget: function() {
  206. return this._focusTarget()
  207. },
  208. _getAnonymousTemplateName: function() {
  209. return ANONYMOUS_TEMPLATE_NAME
  210. },
  211. _getTemplateByOption: function(optionName) {
  212. return this._getTemplate(this.option(optionName))
  213. },
  214. _getTemplate: function(templateSource) {
  215. if (typeUtils.isFunction(templateSource)) {
  216. return new FunctionTemplate(function(options) {
  217. var templateSourceResult = templateSource.apply(this, this._getNormalizedTemplateArgs(options));
  218. if (!typeUtils.isDefined(templateSourceResult)) {
  219. return new EmptyTemplate
  220. }
  221. var dispose = false;
  222. var template = this._acquireTemplate(templateSourceResult, function(templateSource) {
  223. if (templateSource.nodeType || typeUtils.isRenderer(templateSource) && !$(templateSource).is("script")) {
  224. return new FunctionTemplate(function() {
  225. return templateSource
  226. })
  227. }
  228. dispose = true;
  229. return this._createTemplate(templateSource)
  230. }.bind(this));
  231. var result = template.render(options);
  232. dispose && template.dispose && template.dispose();
  233. return result
  234. }.bind(this))
  235. }
  236. return this._acquireTemplate(templateSource, this._createTemplateIfNeeded.bind(this))
  237. },
  238. _acquireTemplate: function(templateSource, createTemplate) {
  239. if (null == templateSource) {
  240. return new EmptyTemplate
  241. }
  242. if (templateSource instanceof ChildDefaultTemplate) {
  243. return this._defaultTemplates[templateSource.name]
  244. }
  245. if (templateSource instanceof TemplateBase) {
  246. return templateSource
  247. }
  248. if (typeUtils.isFunction(templateSource.render) && !typeUtils.isRenderer(templateSource)) {
  249. return this._addOneRenderedCall(templateSource)
  250. }
  251. if (templateSource.nodeType || typeUtils.isRenderer(templateSource)) {
  252. return createTemplate($(templateSource))
  253. }
  254. if ("string" === typeof templateSource) {
  255. var nonIntegrationTemplates = this.option("integrationOptions.skipTemplates") || [];
  256. var integrationTemplate = null;
  257. if (nonIntegrationTemplates.indexOf(templateSource) === -1) {
  258. integrationTemplate = this._renderIntegrationTemplate(templateSource)
  259. }
  260. return integrationTemplate || this._defaultTemplates[templateSource] || createTemplate(templateSource)
  261. }
  262. return this._acquireTemplate(templateSource.toString(), createTemplate)
  263. },
  264. _addOneRenderedCall: function(template) {
  265. var _render = template.render.bind(template);
  266. return extend({}, template, {
  267. render: function(options) {
  268. var templateResult = _render(options);
  269. options && options.onRendered && options.onRendered();
  270. return templateResult
  271. }
  272. })
  273. },
  274. _renderIntegrationTemplate: function(templateSource) {
  275. var integrationTemplate = this.option("integrationOptions.templates")[templateSource];
  276. if (integrationTemplate && !(integrationTemplate instanceof TemplateBase)) {
  277. var isAsyncTemplate = this.option("templatesRenderAsynchronously");
  278. if (!isAsyncTemplate) {
  279. return this._addOneRenderedCall(integrationTemplate)
  280. }
  281. }
  282. return integrationTemplate
  283. },
  284. _createTemplateIfNeeded: function(templateSource) {
  285. var templateKey = function(templateSource) {
  286. return typeUtils.isRenderer(templateSource) && templateSource[0] || templateSource
  287. };
  288. var cachedTemplate = this._tempTemplates.filter(function(t) {
  289. templateSource = templateKey(templateSource);
  290. return t.source === templateSource
  291. })[0];
  292. if (cachedTemplate) {
  293. return cachedTemplate.template
  294. }
  295. var template = this._createTemplate(templateSource);
  296. this._tempTemplates.push({
  297. template: template,
  298. source: templateKey(templateSource)
  299. });
  300. return template
  301. },
  302. _createTemplate: function(templateSource) {
  303. templateSource = "string" === typeof templateSource ? domUtils.normalizeTemplateElement(templateSource) : templateSource;
  304. return this.option("integrationOptions.createTemplate")(templateSource)
  305. },
  306. _getNormalizedTemplateArgs: function(options) {
  307. var args = [];
  308. if ("model" in options) {
  309. args.push(options.model)
  310. }
  311. if ("index" in options) {
  312. args.push(options.index)
  313. }
  314. args.push(options.container);
  315. return args
  316. },
  317. _cleanTemplates: function() {
  318. this._tempTemplates.forEach(function(t) {
  319. t.template.dispose && t.template.dispose()
  320. });
  321. this._tempTemplates = []
  322. },
  323. _initContentReadyAction: function() {
  324. this._contentReadyAction = this._createActionByOption("onContentReady", {
  325. excludeValidators: ["disabled", "readOnly"]
  326. })
  327. },
  328. _initMarkup: function() {
  329. this.$element().addClass(WIDGET_CLASS);
  330. this._toggleDisabledState(this.option("disabled"));
  331. this._toggleVisibility(this.option("visible"));
  332. this._renderHint();
  333. if (this._isFocusable()) {
  334. this._renderFocusTarget()
  335. }
  336. this.callBase()
  337. },
  338. _render: function() {
  339. this.callBase();
  340. this._renderContent();
  341. this._renderFocusState();
  342. this._attachFeedbackEvents();
  343. this._attachHoverEvents()
  344. },
  345. _renderHint: function() {
  346. var hint = this.option("hint");
  347. this.$element().attr("title", hint ? hint : null)
  348. },
  349. _renderContent: function() {
  350. var _this = this;
  351. commonUtils.deferRender(function() {
  352. if (_this._disposed) {
  353. return
  354. }
  355. return _this._renderContentImpl()
  356. }).done(function() {
  357. if (_this._disposed) {
  358. return
  359. }
  360. _this._fireContentReadyAction()
  361. })
  362. },
  363. _renderContentImpl: commonUtils.noop,
  364. _fireContentReadyAction: commonUtils.deferRenderer(function() {
  365. this._contentReadyAction()
  366. }),
  367. _dispose: function() {
  368. this._cleanTemplates();
  369. this._contentReadyAction = null;
  370. this.callBase()
  371. },
  372. _resetActiveState: function() {
  373. this._toggleActiveState(this._eventBindingTarget(), false)
  374. },
  375. _clean: function() {
  376. this._cleanFocusState();
  377. this._resetActiveState();
  378. this.callBase();
  379. this.$element().empty()
  380. },
  381. _toggleVisibility: function(visible) {
  382. this.$element().toggleClass(INVISIBLE_STATE_CLASS, !visible);
  383. this.setAria("hidden", !visible || void 0)
  384. },
  385. _renderFocusState: function() {
  386. this._attachKeyboardEvents();
  387. if (!this._isFocusable()) {
  388. return
  389. }
  390. this._renderFocusTarget();
  391. this._attachFocusEvents();
  392. this._renderAccessKey()
  393. },
  394. _renderAccessKey: function() {
  395. var focusTarget = this._focusTarget();
  396. focusTarget.attr("accesskey", this.option("accessKey"));
  397. var clickNamespace = eventUtils.addNamespace(clickEvent.name, UI_FEEDBACK);
  398. eventsEngine.off(focusTarget, clickNamespace);
  399. this.option("accessKey") && eventsEngine.on(focusTarget, clickNamespace, function(e) {
  400. if (eventUtils.isFakeClickEvent(e)) {
  401. e.stopImmediatePropagation();
  402. this.focus()
  403. }
  404. }.bind(this))
  405. },
  406. _isFocusable: function() {
  407. return this.option("focusStateEnabled") && !this.option("disabled")
  408. },
  409. _eventBindingTarget: function() {
  410. return this.$element()
  411. },
  412. _focusTarget: function() {
  413. return this._getActiveElement()
  414. },
  415. _getActiveElement: function() {
  416. var activeElement = this._eventBindingTarget();
  417. if (this._activeStateUnit) {
  418. activeElement = activeElement.find(this._activeStateUnit).not("." + DISABLED_STATE_CLASS)
  419. }
  420. return activeElement
  421. },
  422. _renderFocusTarget: function() {
  423. this._focusTarget().attr("tabIndex", this.option("tabIndex"))
  424. },
  425. _keyboardEventBindingTarget: function() {
  426. return this._eventBindingTarget()
  427. },
  428. _detachFocusEvents: function() {
  429. var $element = this._focusEventTarget();
  430. var namespace = this.NAME + FOCUS_NAMESPACE;
  431. var focusEvents = eventUtils.addNamespace("focusin", namespace);
  432. focusEvents = focusEvents + " " + eventUtils.addNamespace("focusout", namespace);
  433. if (domAdapter.hasDocumentProperty("onbeforeactivate")) {
  434. focusEvents = focusEvents + " " + eventUtils.addNamespace("beforeactivate", namespace)
  435. }
  436. eventsEngine.off($element, focusEvents)
  437. },
  438. _attachFocusEvents: function() {
  439. var namespace = this.NAME + FOCUS_NAMESPACE;
  440. var focusInEvent = eventUtils.addNamespace("focusin", namespace);
  441. var focusOutEvent = eventUtils.addNamespace("focusout", namespace);
  442. var $focusTarget = this._focusEventTarget();
  443. eventsEngine.on($focusTarget, focusInEvent, this._focusInHandler.bind(this));
  444. eventsEngine.on($focusTarget, focusOutEvent, this._focusOutHandler.bind(this));
  445. if (domAdapter.hasDocumentProperty("onbeforeactivate")) {
  446. var beforeActivateEvent = eventUtils.addNamespace("beforeactivate", namespace);
  447. eventsEngine.on(this._focusEventTarget(), beforeActivateEvent, function(e) {
  448. if (!$(e.target).is(selectors.focusable)) {
  449. e.preventDefault()
  450. }
  451. })
  452. }
  453. },
  454. _refreshFocusEvent: function() {
  455. this._detachFocusEvents();
  456. this._attachFocusEvents()
  457. },
  458. _focusEventTarget: function() {
  459. return this._focusTarget()
  460. },
  461. _focusInHandler: function(e) {
  462. if (e.isDefaultPrevented()) {
  463. return
  464. }
  465. var that = this;
  466. that._createActionByOption("onFocusIn", {
  467. beforeExecute: function() {
  468. that._updateFocusState(e, true)
  469. },
  470. excludeValidators: ["readOnly"]
  471. })({
  472. event: e
  473. })
  474. },
  475. _focusOutHandler: function(e) {
  476. if (e.isDefaultPrevented()) {
  477. return
  478. }
  479. var that = this;
  480. that._createActionByOption("onFocusOut", {
  481. beforeExecute: function() {
  482. that._updateFocusState(e, false)
  483. },
  484. excludeValidators: ["readOnly", "disabled"]
  485. })({
  486. event: e
  487. })
  488. },
  489. _updateFocusState: function(e, isFocused) {
  490. var target = e.target;
  491. if (inArray(target, this._focusTarget()) !== -1) {
  492. this._toggleFocusClass(isFocused, $(target))
  493. }
  494. },
  495. _toggleFocusClass: function(isFocused, $element) {
  496. var $focusTarget = $element && $element.length ? $element : this._focusTarget();
  497. $focusTarget.toggleClass(FOCUSED_STATE_CLASS, isFocused)
  498. },
  499. _hasFocusClass: function(element) {
  500. var $focusTarget = $(element || this._focusTarget());
  501. return $focusTarget.hasClass(FOCUSED_STATE_CLASS)
  502. },
  503. _isFocused: function() {
  504. return this._hasFocusClass()
  505. },
  506. _attachKeyboardEvents: function() {
  507. var processor = this.option("_keyboardProcessor");
  508. if (processor) {
  509. this._keyboardProcessor = processor.reinitialize(this._keyboardHandler, this)
  510. } else {
  511. if (this.option("focusStateEnabled")) {
  512. this._disposeKeyboardProcessor();
  513. this._keyboardProcessor = new KeyboardProcessor({
  514. element: this._keyboardEventBindingTarget(),
  515. handler: this._keyboardHandler,
  516. focusTarget: this._focusTarget(),
  517. context: this
  518. })
  519. }
  520. }
  521. },
  522. _keyboardHandler: function(options) {
  523. var e = options.originalEvent;
  524. var keyName = options.keyName;
  525. var keyCode = options.which;
  526. var keys = this._supportedKeys(e);
  527. var func = keys[keyName] || keys[keyCode];
  528. if (void 0 !== func) {
  529. var handler = func.bind(this);
  530. return handler(e) || false
  531. } else {
  532. return true
  533. }
  534. },
  535. _refreshFocusState: function() {
  536. this._cleanFocusState();
  537. this._renderFocusState()
  538. },
  539. _cleanFocusState: function() {
  540. var $element = this._focusTarget();
  541. this._detachFocusEvents();
  542. this._toggleFocusClass(false);
  543. $element.removeAttr("tabIndex");
  544. this._disposeKeyboardProcessor()
  545. },
  546. _disposeKeyboardProcessor: function() {
  547. if (this._keyboardProcessor) {
  548. this._keyboardProcessor.dispose();
  549. delete this._keyboardProcessor
  550. }
  551. },
  552. _attachHoverEvents: function() {
  553. var that = this;
  554. var hoverableSelector = that._activeStateUnit;
  555. var nameStart = eventUtils.addNamespace(hoverEvents.start, UI_FEEDBACK);
  556. var nameEnd = eventUtils.addNamespace(hoverEvents.end, UI_FEEDBACK);
  557. eventsEngine.off(that._eventBindingTarget(), nameStart, hoverableSelector);
  558. eventsEngine.off(that._eventBindingTarget(), nameEnd, hoverableSelector);
  559. if (that.option("hoverStateEnabled")) {
  560. var startAction = new Action(function(args) {
  561. that._hoverStartHandler(args.event);
  562. that._refreshHoveredElement($(args.element))
  563. }, {
  564. excludeValidators: ["readOnly"]
  565. });
  566. var $eventBindingTarget = that._eventBindingTarget();
  567. eventsEngine.on($eventBindingTarget, nameStart, hoverableSelector, function(e) {
  568. startAction.execute({
  569. element: $(e.target),
  570. event: e
  571. })
  572. });
  573. eventsEngine.on($eventBindingTarget, nameEnd, hoverableSelector, function(e) {
  574. that._hoverEndHandler(e);
  575. that._forgetHoveredElement()
  576. })
  577. } else {
  578. that._toggleHoverClass(false)
  579. }
  580. },
  581. _hoverStartHandler: commonUtils.noop,
  582. _hoverEndHandler: commonUtils.noop,
  583. _attachFeedbackEvents: function() {
  584. var that = this;
  585. var feedbackSelector = that._activeStateUnit;
  586. var activeEventName = eventUtils.addNamespace(feedbackEvents.active, UI_FEEDBACK);
  587. var inactiveEventName = eventUtils.addNamespace(feedbackEvents.inactive, UI_FEEDBACK);
  588. var feedbackAction;
  589. var feedbackActionDisabled;
  590. eventsEngine.off(that._eventBindingTarget(), activeEventName, feedbackSelector);
  591. eventsEngine.off(that._eventBindingTarget(), inactiveEventName, feedbackSelector);
  592. if (that.option("activeStateEnabled")) {
  593. var feedbackActionHandler = function(args) {
  594. var $element = $(args.element);
  595. var value = args.value;
  596. var dxEvent = args.event;
  597. that._toggleActiveState($element, value, dxEvent)
  598. };
  599. eventsEngine.on(that._eventBindingTarget(), activeEventName, feedbackSelector, {
  600. timeout: that._feedbackShowTimeout
  601. }, function(e) {
  602. feedbackAction = feedbackAction || new Action(feedbackActionHandler);
  603. feedbackAction.execute({
  604. element: $(e.currentTarget),
  605. value: true,
  606. event: e
  607. })
  608. });
  609. eventsEngine.on(that._eventBindingTarget(), inactiveEventName, feedbackSelector, {
  610. timeout: that._feedbackHideTimeout
  611. }, function(e) {
  612. feedbackActionDisabled = feedbackActionDisabled || new Action(feedbackActionHandler, {
  613. excludeValidators: ["disabled", "readOnly"]
  614. });
  615. feedbackActionDisabled.execute({
  616. element: $(e.currentTarget),
  617. value: false,
  618. event: e
  619. })
  620. })
  621. }
  622. },
  623. _toggleActiveState: function($element, value) {
  624. this._toggleHoverClass(!value);
  625. $element.toggleClass(ACTIVE_STATE_CLASS, value)
  626. },
  627. _refreshHoveredElement: function(hoveredElement) {
  628. var selector = this._activeStateUnit || this._eventBindingTarget();
  629. this._forgetHoveredElement();
  630. this._hoveredElement = hoveredElement.closest(selector);
  631. this._toggleHoverClass(true)
  632. },
  633. _forgetHoveredElement: function() {
  634. this._toggleHoverClass(false);
  635. delete this._hoveredElement
  636. },
  637. _toggleHoverClass: function(value) {
  638. if (this._hoveredElement) {
  639. this._hoveredElement.toggleClass(HOVER_STATE_CLASS, value && this.option("hoverStateEnabled"))
  640. }
  641. },
  642. _toggleDisabledState: function(value) {
  643. this.$element().toggleClass(DISABLED_STATE_CLASS, Boolean(value));
  644. this._toggleHoverClass(!value);
  645. this.setAria("disabled", value || void 0)
  646. },
  647. _setWidgetOption: function(widgetName, args) {
  648. if (!this[widgetName]) {
  649. return
  650. }
  651. if (typeUtils.isPlainObject(args[0])) {
  652. each(args[0], function(option, value) {
  653. this._setWidgetOption(widgetName, [option, value])
  654. }.bind(this));
  655. return
  656. }
  657. var optionName = args[0];
  658. var value = args[1];
  659. if (1 === args.length) {
  660. value = this.option(optionName)
  661. }
  662. var widgetOptionMap = this[widgetName + "OptionMap"];
  663. this[widgetName].option(widgetOptionMap ? widgetOptionMap(optionName) : optionName, value)
  664. },
  665. _optionChanged: function(args) {
  666. switch (args.name) {
  667. case "disabled":
  668. this._toggleDisabledState(args.value);
  669. this._refreshFocusState();
  670. break;
  671. case "hint":
  672. this._renderHint();
  673. break;
  674. case "activeStateEnabled":
  675. this._attachFeedbackEvents();
  676. break;
  677. case "hoverStateEnabled":
  678. this._attachHoverEvents();
  679. break;
  680. case "tabIndex":
  681. case "_keyboardProcessor":
  682. case "focusStateEnabled":
  683. this._refreshFocusState();
  684. break;
  685. case "onFocusIn":
  686. case "onFocusOut":
  687. break;
  688. case "accessKey":
  689. this._renderAccessKey();
  690. break;
  691. case "visible":
  692. var visible = args.value;
  693. this._toggleVisibility(visible);
  694. if (this._isVisibilityChangeSupported()) {
  695. this._checkVisibilityChanged(args.value ? "shown" : "hiding")
  696. }
  697. break;
  698. case "onContentReady":
  699. this._initContentReadyAction();
  700. break;
  701. default:
  702. this.callBase(args)
  703. }
  704. },
  705. _isVisible: function() {
  706. return this.callBase() && this.option("visible")
  707. },
  708. beginUpdate: function() {
  709. this._ready(false);
  710. this.callBase()
  711. },
  712. endUpdate: function() {
  713. this.callBase();
  714. if (this._initialized) {
  715. this._ready(true)
  716. }
  717. },
  718. _ready: function(value) {
  719. if (0 === arguments.length) {
  720. return this._isReady
  721. }
  722. this._isReady = value
  723. },
  724. setAria: function() {
  725. var setAttribute = function(option) {
  726. var attrName = "role" === option.name || "id" === option.name ? option.name : "aria-" + option.name;
  727. var attrValue = option.value;
  728. if (typeUtils.isDefined(attrValue)) {
  729. attrValue = attrValue.toString()
  730. } else {
  731. attrValue = null
  732. }
  733. option.target.attr(attrName, attrValue)
  734. };
  735. if (!typeUtils.isPlainObject(arguments[0])) {
  736. setAttribute({
  737. name: arguments[0],
  738. value: arguments[1],
  739. target: arguments[2] || this._getAriaTarget()
  740. })
  741. } else {
  742. var $target = arguments[1] || this._getAriaTarget();
  743. each(arguments[0], function(key, value) {
  744. setAttribute({
  745. name: key,
  746. value: value,
  747. target: $target
  748. })
  749. })
  750. }
  751. },
  752. isReady: function() {
  753. return this._ready()
  754. },
  755. repaint: function() {
  756. this._refresh()
  757. },
  758. focus: function() {
  759. eventsEngine.trigger(this._focusTarget(), "focus")
  760. },
  761. registerKeyHandler: function(key, handler) {
  762. var currentKeys = this._supportedKeys();
  763. var addingKeys = {};
  764. addingKeys[key] = handler;
  765. this._supportedKeys = function() {
  766. return extend(currentKeys, addingKeys)
  767. }
  768. }
  769. });
  770. module.exports = Widget;