modal-component.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. /**
  2. * Copyright © Magento, Inc. All rights reserved.
  3. * See COPYING.txt for license details.
  4. */
  5. /**
  6. * @api
  7. */
  8. define([
  9. 'Magento_Ui/js/lib/view/utils/async',
  10. 'uiCollection',
  11. 'uiRegistry',
  12. 'underscore',
  13. './modal'
  14. ], function ($, Collection, registry, _) {
  15. 'use strict';
  16. return Collection.extend({
  17. defaults: {
  18. template: 'ui/modal/modal-component',
  19. title: '',
  20. subTitle: '',
  21. options: {
  22. modalClass: '',
  23. title: '',
  24. subTitle: '',
  25. buttons: [],
  26. keyEventHandlers: {}
  27. },
  28. valid: true,
  29. links: {
  30. title: 'options.title',
  31. subTitle: 'options.subTitle'
  32. },
  33. listens: {
  34. state: 'onState',
  35. title: 'setTitle',
  36. 'options.subTitle': 'setSubTitle'
  37. },
  38. modalClass: 'modal-component',
  39. onCancel: 'closeModal'
  40. },
  41. /**
  42. * Initializes component.
  43. *
  44. * @returns {Object} Chainable.
  45. */
  46. initialize: function () {
  47. this._super();
  48. _.bindAll(this,
  49. 'initModal',
  50. 'openModal',
  51. 'closeModal',
  52. 'toggleModal',
  53. 'setPrevValues',
  54. 'validate');
  55. this.initializeContent();
  56. return this;
  57. },
  58. /**
  59. * Initializes modal configuration
  60. *
  61. * @returns {Object} Chainable.
  62. */
  63. initConfig: function () {
  64. return this._super()
  65. .initSelector()
  66. .initModalEvents();
  67. },
  68. /**
  69. * Configure modal selector
  70. *
  71. * @returns {Object} Chainable.
  72. */
  73. initSelector: function () {
  74. var modalClass = this.name.replace(/\./g, '_');
  75. this.contentSelector = '.' + this.modalClass;
  76. this.options.modalClass = this.options.modalClass + ' ' + modalClass;
  77. this.rootSelector = '.' + modalClass;
  78. return this;
  79. },
  80. /**
  81. * Configure modal keyboard handlers
  82. * and outer click
  83. *
  84. * @returns {Object} Chainable.
  85. */
  86. initModalEvents: function () {
  87. this.options.keyEventHandlers.escapeKey = this.options.outerClickHandler = this[this.onCancel].bind(this);
  88. return this;
  89. },
  90. /**
  91. * Initialize modal's content components
  92. */
  93. initializeContent: function () {
  94. $.async({
  95. component: this.name
  96. }, this.initModal);
  97. },
  98. /**
  99. * Init toolbar section so other components will be able to place something in it
  100. */
  101. initToolbarSection: function () {
  102. this.set('toolbarSection', this.modal.data('mage-modal').modal.find('header').get(0));
  103. },
  104. /**
  105. * Initializes observable properties.
  106. *
  107. * @returns {Object} Chainable.
  108. */
  109. initObservable: function () {
  110. this._super();
  111. this.observe(['state', 'focused']);
  112. return this;
  113. },
  114. /**
  115. * Wrap content in a modal of certain type
  116. *
  117. * @param {HTMLElement} element
  118. * @returns {Object} Chainable.
  119. */
  120. initModal: function (element) {
  121. if (!this.modal) {
  122. this.overrideModalButtonCallback();
  123. this.options.modalCloseBtnHandler = this[this.onCancel].bind(this);
  124. this.modal = $(element).modal(this.options);
  125. this.initToolbarSection();
  126. if (this.waitCbk) {
  127. this.waitCbk();
  128. this.waitCbk = null;
  129. }
  130. }
  131. return this;
  132. },
  133. /**
  134. * Open modal
  135. */
  136. openModal: function () {
  137. if (this.modal) {
  138. this.state(true);
  139. } else {
  140. this.waitCbk = this.openModal;
  141. }
  142. },
  143. /**
  144. * Close modal
  145. */
  146. closeModal: function () {
  147. if (this.modal) {
  148. this.state(false);
  149. } else {
  150. this.waitCbk = this.closeModal;
  151. }
  152. },
  153. /**
  154. * Toggle modal
  155. */
  156. toggleModal: function () {
  157. if (this.modal) {
  158. this.state(!this.state());
  159. } else {
  160. this.waitCbk = this.toggleModal;
  161. }
  162. },
  163. /**
  164. * Sets title for modal
  165. *
  166. * @param {String} title
  167. */
  168. setTitle: function (title) {
  169. if (this.title !== title) {
  170. this.title = title;
  171. }
  172. if (this.modal) {
  173. this.modal.modal('setTitle', title);
  174. }
  175. },
  176. /**
  177. * Sets subTitle for modal
  178. *
  179. * @param {String} subTitle
  180. */
  181. setSubTitle: function (subTitle) {
  182. if (this.subTitle !== subTitle) {
  183. this.subTitle = subTitle;
  184. }
  185. if (this.modal) {
  186. this.modal.modal('setSubTitle', subTitle);
  187. }
  188. },
  189. /**
  190. * Wrap content in a modal of certain type
  191. *
  192. * @param {Boolean} state
  193. */
  194. onState: function (state) {
  195. if (state) {
  196. this.modal.modal('openModal');
  197. this.applyData();
  198. } else {
  199. this.modal.modal('closeModal');
  200. }
  201. },
  202. /**
  203. * Validate everything validatable in modal
  204. */
  205. validate: function (elem) {
  206. if (typeof elem === 'undefined') {
  207. return;
  208. }
  209. if (typeof elem.validate === 'function') {
  210. this.valid = this.valid & elem.validate().valid;
  211. } else if (elem.elems) {
  212. elem.elems().forEach(this.validate, this);
  213. }
  214. },
  215. /**
  216. * Reset data from provider
  217. */
  218. resetData: function () {
  219. this.elems().forEach(this.resetValue, this);
  220. },
  221. /**
  222. * Update 'applied' property with data from modal content
  223. */
  224. applyData: function () {
  225. var applied = {};
  226. this.elems().forEach(this.gatherValues.bind(this, applied), this);
  227. this.applied = applied;
  228. },
  229. /**
  230. * Gather values from modal content
  231. *
  232. * @param {Array} applied
  233. * @param {HTMLElement} elem
  234. */
  235. gatherValues: function (applied, elem) {
  236. if (typeof elem.value === 'function') {
  237. applied[elem.name] = elem.value();
  238. } else if (elem.elems) {
  239. elem.elems().forEach(this.gatherValues.bind(this, applied), this);
  240. }
  241. },
  242. /**
  243. * Set to previous values from modal content
  244. *
  245. * @param {HTMLElement} elem
  246. */
  247. setPrevValues: function (elem) {
  248. if (typeof elem.value === 'function') {
  249. this.modal.focus();
  250. elem.value(this.applied[elem.name]);
  251. } else if (elem.elems) {
  252. elem.elems().forEach(this.setPrevValues, this);
  253. }
  254. },
  255. /**
  256. * Triggers some method in every modal child elem, if this method is defined
  257. *
  258. * @param {Object} action - action configuration,
  259. * must contain actionName and targetName and
  260. * can contain params
  261. */
  262. triggerAction: function (action) {
  263. var targetName = action.targetName,
  264. params = action.params || [],
  265. actionName = action.actionName,
  266. target;
  267. target = registry.async(targetName);
  268. if (target && typeof target === 'function' && actionName) {
  269. params.unshift(actionName);
  270. target.apply(target, params);
  271. }
  272. },
  273. /**
  274. * Override modal buttons callback placeholders with real callbacks
  275. */
  276. overrideModalButtonCallback: function () {
  277. var buttons = this.options.buttons;
  278. if (buttons && buttons.length) {
  279. buttons.forEach(function (button) {
  280. button.click = this.getButtonClickHandler(button.actions);
  281. }, this);
  282. }
  283. },
  284. /**
  285. * Generate button click handler based on button's 'actions' configuration
  286. */
  287. getButtonClickHandler: function (actionsConfig) {
  288. var actions = actionsConfig.map(
  289. function (actionConfig) {
  290. if (_.isObject(actionConfig)) {
  291. return this.triggerAction.bind(this, actionConfig);
  292. }
  293. return this[actionConfig] ? this[actionConfig].bind(this) : function () {};
  294. }, this);
  295. return function () {
  296. actions.forEach(
  297. function (action) {
  298. action();
  299. }
  300. );
  301. };
  302. },
  303. /**
  304. * Cancels changes in modal:
  305. * returning elems values to the previous state,
  306. * and close modal
  307. */
  308. actionCancel: function () {
  309. this.elems().forEach(this.setPrevValues, this);
  310. this.closeModal();
  311. },
  312. /**
  313. * Accept changes in modal by not preventing them.
  314. * Can be extended by exporting 'gatherValues' result somewhere
  315. */
  316. actionDone: function () {
  317. this.valid = true;
  318. this.elems().forEach(this.validate, this);
  319. if (this.valid) {
  320. this.closeModal();
  321. }
  322. }
  323. });
  324. });