drop_down_menu.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. /**
  2. * DevExtreme (ui/drop_down_menu.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 window = require("../core/utils/window").getWindow();
  12. var devices = require("../core/devices");
  13. var registerComponent = require("../core/component_registrator");
  14. var extend = require("../core/utils/extend").extend;
  15. var Widget = require("./widget/ui.widget");
  16. var Button = require("./button");
  17. var Popover = require("./popover");
  18. var DataHelperMixin = require("../data_helper");
  19. var List = require("./list");
  20. var themes = require("./themes");
  21. var ChildDefaultTemplate = require("./widget/child_default_template");
  22. var DROP_DOWN_MENU_CLASS = "dx-dropdownmenu";
  23. var DROP_DOWN_MENU_POPUP_CLASS = "dx-dropdownmenu-popup";
  24. var DROP_DOWN_MENU_POPUP_WRAPPER_CLASS = "dx-dropdownmenu-popup-wrapper";
  25. var DROP_DOWN_MENU_LIST_CLASS = "dx-dropdownmenu-list";
  26. var DROP_DOWN_MENU_BUTTON_CLASS = "dx-dropdownmenu-button";
  27. var POPUP_OPTION_MAP = {
  28. popupWidth: "width",
  29. popupHeight: "height",
  30. popupMaxHeight: "maxHeight",
  31. popupAutoResizeEnabled: "autoResizeEnabled"
  32. };
  33. var BUTTON_OPTION_MAP = {
  34. buttonIcon: "icon",
  35. buttonText: "text",
  36. buttonWidth: "width",
  37. buttonHeight: "height",
  38. buttonTemplate: "template"
  39. };
  40. var DropDownMenu = Widget.inherit({
  41. _supportedKeys: function() {
  42. var extension = {};
  43. if (!this.option("opened") || !this._list.option("focusedElement")) {
  44. extension = this._button._supportedKeys()
  45. }
  46. return extend(this.callBase(), extension, {
  47. tab: function() {
  48. this._popup && this._popup.hide()
  49. }
  50. })
  51. },
  52. _getDefaultOptions: function() {
  53. return extend(this.callBase(), {
  54. items: [],
  55. onItemClick: null,
  56. dataSource: null,
  57. itemTemplate: "item",
  58. buttonText: "",
  59. buttonIcon: "overflow",
  60. buttonWidth: void 0,
  61. buttonHeight: void 0,
  62. buttonTemplate: "content",
  63. onButtonClick: null,
  64. usePopover: false,
  65. popupWidth: "auto",
  66. popupHeight: "auto",
  67. activeStateEnabled: true,
  68. hoverStateEnabled: true,
  69. opened: false,
  70. deferRendering: false,
  71. popupPosition: {
  72. my: "top center",
  73. at: "bottom center",
  74. collision: "fit flip",
  75. offset: {
  76. v: 1
  77. }
  78. },
  79. popupAnimation: void 0,
  80. onItemRendered: null,
  81. menuWidget: List,
  82. popupMaxHeight: void 0,
  83. closeOnClick: true,
  84. useInkRipple: false,
  85. container: void 0,
  86. popupAutoResizeEnabled: false
  87. })
  88. },
  89. _defaultOptionsRules: function() {
  90. return this.callBase().concat([{
  91. device: {
  92. platform: "ios"
  93. },
  94. options: {
  95. usePopover: true
  96. }
  97. }, {
  98. device: {
  99. platform: "generic"
  100. },
  101. options: {
  102. popupPosition: {
  103. offset: {
  104. v: 4
  105. }
  106. }
  107. }
  108. }, {
  109. device: function() {
  110. return "desktop" === devices.real().deviceType && !devices.isSimulator()
  111. },
  112. options: {
  113. focusStateEnabled: true
  114. }
  115. }, {
  116. device: {
  117. platform: "android"
  118. },
  119. options: {
  120. popupPosition: {
  121. my: "top " + (this.option("rtlEnabled") ? "left" : "right"),
  122. at: "top " + (this.option("rtlEnabled") ? "left" : "right"),
  123. collision: "flipfit"
  124. },
  125. popupAnimation: {
  126. show: {
  127. type: "pop",
  128. duration: 200,
  129. from: {
  130. scale: 0
  131. },
  132. to: {
  133. scale: 1
  134. }
  135. },
  136. hide: {
  137. type: "pop",
  138. duration: 200,
  139. from: {
  140. scale: 1
  141. },
  142. to: {
  143. scale: 0
  144. }
  145. }
  146. }
  147. }
  148. }, {
  149. device: function() {
  150. return themes.isMaterial()
  151. },
  152. options: {
  153. useInkRipple: true
  154. }
  155. }])
  156. },
  157. _initOptions: function(options) {
  158. if ("android" === devices.current().platform) {
  159. if (!options.popupPosition) {
  160. options.popupPosition = {
  161. at: (options.usePopover ? "bottom " : "top ") + (options.rtlEnabled ? "left" : "right")
  162. }
  163. }
  164. }
  165. this.callBase(options)
  166. },
  167. _dataSourceOptions: function() {
  168. return {
  169. paginate: false
  170. }
  171. },
  172. _init: function() {
  173. this.callBase();
  174. this.$element().addClass(DROP_DOWN_MENU_CLASS);
  175. this._initDataSource();
  176. this._initItemClickAction();
  177. this._initButtonClickAction()
  178. },
  179. _initItemClickAction: function() {
  180. this._itemClickAction = this._createActionByOption("onItemClick")
  181. },
  182. _initButtonClickAction: function() {
  183. this._buttonClickAction = this._createActionByOption("onButtonClick")
  184. },
  185. _initTemplates: function() {
  186. this.callBase();
  187. this._defaultTemplates.content = new ChildDefaultTemplate("content", this)
  188. },
  189. _initMarkup: function() {
  190. this._renderButton();
  191. this.callBase()
  192. },
  193. _render: function() {
  194. this.callBase();
  195. this.setAria({
  196. role: "menubar",
  197. haspopup: true,
  198. expanded: this.option("opened")
  199. })
  200. },
  201. _renderContentImpl: function() {
  202. if (this.option("opened")) {
  203. this._renderPopup()
  204. }
  205. },
  206. _clean: function() {
  207. this._cleanFocusState();
  208. if (this._popup) {
  209. this._popup.$element().remove();
  210. delete this._$popup
  211. }
  212. },
  213. _renderButton: function() {
  214. var $button = this.$element().addClass(DROP_DOWN_MENU_BUTTON_CLASS);
  215. var config = this._buttonOptions();
  216. this._button = this._createComponent($button, Button, config)
  217. },
  218. _toggleActiveState: function($element, value, e) {
  219. this._button._toggleActiveState($element, value, e)
  220. },
  221. _buttonOptions: function() {
  222. return {
  223. text: this.option("buttonText"),
  224. icon: this.option("buttonIcon"),
  225. width: this.option("buttonWidth"),
  226. height: this.option("buttonHeight"),
  227. useInkRipple: this.option("useInkRipple"),
  228. template: this.option("buttonTemplate"),
  229. focusStateEnabled: false,
  230. onClick: function(e) {
  231. this.option("opened", !this.option("opened"));
  232. this._buttonClickAction(e)
  233. }.bind(this)
  234. }
  235. },
  236. _toggleMenuVisibility: function(opened) {
  237. var state = void 0 === opened ? !this._popup.option("visible") : opened;
  238. if (opened) {
  239. this._renderPopup()
  240. }
  241. this._popup.toggle(state);
  242. this.setAria("expanded", state)
  243. },
  244. _renderPopup: function() {
  245. if (this._$popup) {
  246. return
  247. }
  248. var $popup = this._$popup = $("<div>").appendTo(this.$element());
  249. var config = this._popupOptions();
  250. this._popup = this._createComponent($popup, Popover, config)
  251. },
  252. _popupOptions: function() {
  253. var usePopup = !this.option("usePopover");
  254. return {
  255. onInitialized: function(args) {
  256. args.component._wrapper().addClass(DROP_DOWN_MENU_POPUP_WRAPPER_CLASS).toggleClass(DROP_DOWN_MENU_POPUP_CLASS, usePopup)
  257. },
  258. visible: this.option("opened"),
  259. deferRendering: false,
  260. contentTemplate: function(contentElement) {
  261. this._renderList(contentElement)
  262. }.bind(this),
  263. position: this.option("popupPosition"),
  264. animation: this.option("popupAnimation"),
  265. onOptionChanged: function(args) {
  266. if ("visible" === args.name) {
  267. this.option("opened", args.value)
  268. }
  269. }.bind(this),
  270. target: this.$element(),
  271. height: this.option("popupHeight"),
  272. width: this.option("popupWidth"),
  273. maxHeight: this.option("popupMaxHeight"),
  274. container: this.option("container"),
  275. autoResizeEnabled: this.option("popupAutoResizeEnabled")
  276. }
  277. },
  278. _renderList: function(contentElement) {
  279. var $content = $(contentElement);
  280. var listConfig = this._listOptions();
  281. $content.addClass(DROP_DOWN_MENU_LIST_CLASS);
  282. this._list = this._createComponent($content, this.option("menuWidget"), listConfig);
  283. this._list._getAriaTarget = function() {
  284. return this.$element()
  285. }.bind(this);
  286. this._setListDataSource();
  287. var listMaxHeight = .5 * $(window).height();
  288. if ($content.height() > listMaxHeight) {
  289. $content.height(listMaxHeight)
  290. }
  291. },
  292. _listOptions: function() {
  293. return {
  294. _keyboardProcessor: this._listProcessor,
  295. pageLoadMode: "scrollBottom",
  296. indicateLoading: false,
  297. noDataText: "",
  298. itemTemplate: this.option("itemTemplate"),
  299. onItemClick: function(e) {
  300. if (this.option("closeOnClick")) {
  301. this.option("opened", false)
  302. }
  303. this._itemClickAction(e)
  304. }.bind(this),
  305. tabIndex: -1,
  306. focusStateEnabled: this.option("focusStateEnabled"),
  307. activeStateEnabled: this.option("activeStateEnabled"),
  308. onItemRendered: this.option("onItemRendered"),
  309. _itemAttributes: {
  310. role: "menuitem"
  311. }
  312. }
  313. },
  314. _setListDataSource: function() {
  315. if (this._list) {
  316. this._list.option("dataSource", this._dataSource || this.option("items"))
  317. }
  318. delete this._deferRendering
  319. },
  320. _attachKeyboardEvents: function() {
  321. this.callBase.apply(this, arguments);
  322. this._listProcessor = this._keyboardProcessor && this._keyboardProcessor.attachChildProcessor();
  323. if (this._list) {
  324. this._list.option("_keyboardProcessor", this._listProcessor)
  325. }
  326. },
  327. _cleanFocusState: function() {
  328. this.callBase.apply(this, arguments);
  329. delete this._listProcessor
  330. },
  331. _toggleVisibility: function(visible) {
  332. this.callBase(visible);
  333. this._button.option("visible", visible)
  334. },
  335. _optionChanged: function(args) {
  336. var name = args.name;
  337. var value = args.value;
  338. switch (name) {
  339. case "items":
  340. case "dataSource":
  341. if (this.option("deferRendering") && !this.option("opened")) {
  342. this._deferRendering = true
  343. } else {
  344. this._refreshDataSource();
  345. this._setListDataSource()
  346. }
  347. break;
  348. case "itemTemplate":
  349. if (this._list) {
  350. this._list.option(name, this._getTemplate(value))
  351. }
  352. break;
  353. case "onItemClick":
  354. this._initItemClickAction();
  355. break;
  356. case "onButtonClick":
  357. this._buttonClickAction();
  358. break;
  359. case "buttonIcon":
  360. case "buttonText":
  361. case "buttonWidth":
  362. case "buttonHeight":
  363. case "buttonTemplate":
  364. this._button.option(BUTTON_OPTION_MAP[name], value);
  365. this._renderPopup();
  366. break;
  367. case "popupWidth":
  368. case "popupHeight":
  369. case "popupMaxHeight":
  370. case "popupAutoResizeEnabled":
  371. this._popup.option(POPUP_OPTION_MAP[name], value);
  372. break;
  373. case "usePopover":
  374. case "menuWidget":
  375. case "useInkRipple":
  376. this._invalidate();
  377. break;
  378. case "focusStateEnabled":
  379. case "activeStateEnabled":
  380. if (this._list) {
  381. this._list.option(name, value)
  382. }
  383. this.callBase(args);
  384. break;
  385. case "onItemRendered":
  386. if (this._list) {
  387. this._list.option(name, value)
  388. }
  389. break;
  390. case "opened":
  391. if (this._deferRendering) {
  392. this._refreshDataSource();
  393. this._setListDataSource()
  394. }
  395. this._toggleMenuVisibility(value);
  396. break;
  397. case "deferRendering":
  398. case "popupPosition":
  399. case "closeOnClick":
  400. break;
  401. case "container":
  402. this._popup && this._popup.option(args.name, args.value);
  403. break;
  404. default:
  405. this.callBase(args)
  406. }
  407. },
  408. open: function() {
  409. this.option("opened", true)
  410. },
  411. close: function() {
  412. this.option("opened", false)
  413. }
  414. }).include(DataHelperMixin);
  415. registerComponent("dxDropDownMenu", DropDownMenu);
  416. module.exports = DropDownMenu;