button.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. /**
  2. * DevExtreme (ui/button.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 iconUtils = require("../core/utils/icon");
  13. var domUtils = require("../core/utils/dom");
  14. var devices = require("../core/devices");
  15. var registerComponent = require("../core/component_registrator");
  16. var extend = require("../core/utils/extend").extend;
  17. var ValidationMixin = require("./validation/validation_mixin");
  18. var ValidationEngine = require("./validation_engine");
  19. var Widget = require("./widget/ui.widget");
  20. var inkRipple = require("./widget/utils.ink_ripple");
  21. var eventUtils = require("../events/utils");
  22. var themes = require("./themes");
  23. var clickEvent = require("../events/click");
  24. var FunctionTemplate = require("./widget/function_template");
  25. var BUTTON_CLASS = "dx-button";
  26. var BUTTON_CONTENT_CLASS = "dx-button-content";
  27. var BUTTON_HAS_TEXT_CLASS = "dx-button-has-text";
  28. var BUTTON_HAS_ICON_CLASS = "dx-button-has-icon";
  29. var BUTTON_ICON_RIGHT_CLASS = "dx-button-icon-right";
  30. var ICON_RIGHT_CLASS = "dx-icon-right";
  31. var BUTTON_STYLING_MODE_CLASS_PREFIX = "dx-button-mode-";
  32. var ALLOWED_STYLE_CLASSES = [BUTTON_STYLING_MODE_CLASS_PREFIX + "contained", BUTTON_STYLING_MODE_CLASS_PREFIX + "text", BUTTON_STYLING_MODE_CLASS_PREFIX + "outlined"];
  33. var TEMPLATE_WRAPPER_CLASS = "dx-template-wrapper";
  34. var BUTTON_TEXT_CLASS = "dx-button-text";
  35. var ANONYMOUS_TEMPLATE_NAME = "content";
  36. var BUTTON_LEFT_ICON_POSITION = "left";
  37. var BUTTON_FEEDBACK_HIDE_TIMEOUT = 100;
  38. var Button = Widget.inherit({
  39. _supportedKeys: function() {
  40. var that = this;
  41. var click = function(e) {
  42. e.preventDefault();
  43. that._executeClickAction(e)
  44. };
  45. return extend(this.callBase(), {
  46. space: click,
  47. enter: click
  48. })
  49. },
  50. _setDeprecatedOptions: function() {
  51. this.callBase()
  52. },
  53. _getDefaultOptions: function() {
  54. return extend(this.callBase(), {
  55. hoverStateEnabled: true,
  56. onClick: null,
  57. type: "normal",
  58. text: "",
  59. icon: "",
  60. iconPosition: BUTTON_LEFT_ICON_POSITION,
  61. validationGroup: void 0,
  62. activeStateEnabled: true,
  63. template: "content",
  64. useSubmitBehavior: false,
  65. useInkRipple: false,
  66. stylingMode: "contained"
  67. })
  68. },
  69. _defaultOptionsRules: function() {
  70. return this.callBase().concat([{
  71. device: function() {
  72. return "desktop" === devices.real().deviceType && !devices.isSimulator()
  73. },
  74. options: {
  75. focusStateEnabled: true
  76. }
  77. }, {
  78. device: function() {
  79. var themeName = themes.current();
  80. return themes.isMaterial(themeName)
  81. },
  82. options: {
  83. useInkRipple: true
  84. }
  85. }])
  86. },
  87. _getAnonymousTemplateName: function() {
  88. return ANONYMOUS_TEMPLATE_NAME
  89. },
  90. _feedbackHideTimeout: BUTTON_FEEDBACK_HIDE_TIMEOUT,
  91. _initTemplates: function() {
  92. this.callBase();
  93. var that = this;
  94. this._defaultTemplates.content = new FunctionTemplate(function(options) {
  95. var data = options.model;
  96. var $iconElement = iconUtils.getImageContainer(data && data.icon);
  97. var $textContainer = data && data.text ? $("<span>").text(data.text).addClass(BUTTON_TEXT_CLASS) : void 0;
  98. var $container = $(options.container);
  99. $container.append($textContainer);
  100. if (that.option("iconPosition") === BUTTON_LEFT_ICON_POSITION) {
  101. $container.prepend($iconElement)
  102. } else {
  103. $iconElement.addClass(ICON_RIGHT_CLASS);
  104. $container.append($iconElement)
  105. }
  106. }, this)
  107. },
  108. _initMarkup: function() {
  109. this.$element().addClass(BUTTON_CLASS);
  110. this._renderType();
  111. this._renderStylingMode();
  112. this.option("useInkRipple") && this._renderInkRipple();
  113. this._renderClick();
  114. this.setAria("role", "button");
  115. this._updateAriaLabel();
  116. this.callBase();
  117. this._updateContent()
  118. },
  119. _renderInkRipple: function() {
  120. var isOnlyIconButton = !this.option("text") && this.option("icon") || "back" === this.option("type");
  121. var config = {};
  122. if (isOnlyIconButton) {
  123. extend(config, {
  124. waveSizeCoefficient: 1,
  125. useHoldAnimation: false,
  126. isCentered: true
  127. })
  128. }
  129. this._inkRipple = inkRipple.render(config)
  130. },
  131. _toggleActiveState: function($element, value, e) {
  132. this.callBase.apply(this, arguments);
  133. if (!this._inkRipple) {
  134. return
  135. }
  136. var config = {
  137. element: this._$content,
  138. event: e
  139. };
  140. if (value) {
  141. this._inkRipple.showWave(config)
  142. } else {
  143. this._inkRipple.hideWave(config)
  144. }
  145. },
  146. _updateContent: function() {
  147. var $element = this.$element();
  148. var data = this._getContentData();
  149. if (this._$content) {
  150. this._$content.empty()
  151. } else {
  152. this._$content = $("<div>").addClass(BUTTON_CONTENT_CLASS).appendTo($element)
  153. }
  154. $element.toggleClass(BUTTON_HAS_ICON_CLASS, !!data.icon).toggleClass(BUTTON_ICON_RIGHT_CLASS, !!data.icon && this.option("iconPosition") !== BUTTON_LEFT_ICON_POSITION).toggleClass(BUTTON_HAS_TEXT_CLASS, !!data.text);
  155. var transclude = this._getAnonymousTemplateName() === this.option("template");
  156. var template = this._getTemplateByOption("template");
  157. var $result = $(template.render({
  158. model: data,
  159. container: domUtils.getPublicElement(this._$content),
  160. transclude: transclude
  161. }));
  162. if ($result.hasClass(TEMPLATE_WRAPPER_CLASS)) {
  163. this._$content.replaceWith($result);
  164. this._$content = $result;
  165. this._$content.addClass(BUTTON_CONTENT_CLASS)
  166. }
  167. if (this.option("useSubmitBehavior")) {
  168. this._renderSubmitInput()
  169. }
  170. },
  171. _renderSubmitInput: function() {
  172. var submitAction = this._createAction(function(args) {
  173. var e = args.event;
  174. var validationGroup = ValidationEngine.getGroupConfig(args.component._findGroup());
  175. if (validationGroup && !validationGroup.validate().isValid) {
  176. e.preventDefault()
  177. }
  178. e.stopPropagation()
  179. });
  180. this._$submitInput = $("<input>").attr("type", "submit").attr("tabindex", -1).addClass("dx-button-submit-input").appendTo(this._$content);
  181. eventsEngine.on(this._$submitInput, "click", function(e) {
  182. submitAction({
  183. event: e
  184. })
  185. })
  186. },
  187. _getContentData: function() {
  188. var icon = this.option("icon");
  189. var text = this.option("text");
  190. var back = "back" === this.option("type");
  191. if (back && !icon) {
  192. icon = "back"
  193. }
  194. return {
  195. icon: icon,
  196. text: text
  197. }
  198. },
  199. _renderClick: function() {
  200. var that = this;
  201. var eventName = eventUtils.addNamespace(clickEvent.name, this.NAME);
  202. var actionConfig = {
  203. excludeValidators: ["readOnly"]
  204. };
  205. if (this.option("useSubmitBehavior")) {
  206. actionConfig.afterExecute = function(e) {
  207. setTimeout(function() {
  208. e.component._$submitInput.get(0).click()
  209. })
  210. }
  211. }
  212. this._clickAction = this._createActionByOption("onClick", actionConfig);
  213. eventsEngine.off(this.$element(), eventName);
  214. eventsEngine.on(this.$element(), eventName, function(e) {
  215. that._executeClickAction(e)
  216. })
  217. },
  218. _executeClickAction: function(e) {
  219. this._clickAction({
  220. event: e,
  221. validationGroup: ValidationEngine.getGroupConfig(this._findGroup())
  222. })
  223. },
  224. _updateAriaLabel: function() {
  225. var icon = this.option("icon");
  226. var text = this.option("text");
  227. if ("image" === iconUtils.getImageSourceType(icon)) {
  228. if (icon.indexOf("base64") === -1) {
  229. icon = icon.replace(/.+\/([^.]+)\..+$/, "$1")
  230. } else {
  231. icon = "Base64"
  232. }
  233. }
  234. var ariaLabel = text || icon || "";
  235. ariaLabel = ariaLabel.toString().trim();
  236. this.setAria("label", ariaLabel.length ? ariaLabel : null)
  237. },
  238. _renderType: function() {
  239. var type = this.option("type");
  240. if (type) {
  241. this.$element().addClass("dx-button-" + type)
  242. }
  243. },
  244. _renderStylingMode: function() {
  245. var _this = this;
  246. var optionName = "stylingMode";
  247. ALLOWED_STYLE_CLASSES.forEach(function(className) {
  248. return _this.$element().removeClass(className)
  249. });
  250. var stylingModeClass = BUTTON_STYLING_MODE_CLASS_PREFIX + this.option(optionName);
  251. if (ALLOWED_STYLE_CLASSES.indexOf(stylingModeClass) === -1) {
  252. var defaultOptionValue = this._getDefaultOptions()[optionName];
  253. stylingModeClass = BUTTON_STYLING_MODE_CLASS_PREFIX + defaultOptionValue
  254. }
  255. this.$element().addClass(stylingModeClass)
  256. },
  257. _refreshType: function(prevType) {
  258. var type = this.option("type");
  259. prevType && this.$element().removeClass("dx-button-" + prevType).addClass("dx-button-" + type);
  260. if (!this.$element().hasClass(BUTTON_HAS_ICON_CLASS) && "back" === type) {
  261. this._updateContent()
  262. }
  263. },
  264. _optionChanged: function(args) {
  265. switch (args.name) {
  266. case "onClick":
  267. this._renderClick();
  268. break;
  269. case "icon":
  270. case "text":
  271. this._updateContent();
  272. this._updateAriaLabel();
  273. break;
  274. case "type":
  275. this._refreshType(args.previousValue);
  276. this._updateContent();
  277. this._updateAriaLabel();
  278. break;
  279. case "template":
  280. case "iconPosition":
  281. this._updateContent();
  282. break;
  283. case "stylingMode":
  284. this._renderStylingMode();
  285. break;
  286. case "useInkRipple":
  287. case "useSubmitBehavior":
  288. this._invalidate();
  289. break;
  290. default:
  291. this.callBase(args)
  292. }
  293. },
  294. _clean: function() {
  295. delete this._inkRipple;
  296. this.callBase();
  297. delete this._$content
  298. }
  299. }).include(ValidationMixin);
  300. registerComponent("dxButton", Button);
  301. module.exports = Button;
  302. module.exports.default = module.exports;