step-wizard.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. /**
  2. * Copyright © Magento, Inc. All rights reserved.
  3. * See COPYING.txt for license details.
  4. */
  5. define([
  6. 'uiRegistry',
  7. 'uiComponent',
  8. 'jquery',
  9. 'underscore',
  10. 'ko',
  11. 'mage/backend/notification',
  12. 'mage/translate'
  13. ], function (uiRegistry, Component, $, _, ko) {
  14. 'use strict';
  15. var Wizard;
  16. ko.utils.domNodeDisposal.cleanExternalData = _.wrap(
  17. ko.utils.domNodeDisposal.cleanExternalData,
  18. function (func, node) {
  19. if (!$(node).closest('[data-type=skipKO]').length) {
  20. func(node);
  21. }
  22. }
  23. );
  24. /**
  25. * Wizard constructor.
  26. *
  27. * @param {Array} steps
  28. * @param {String} modalClass
  29. * @constructor
  30. */
  31. Wizard = function (steps, modalClass) {
  32. this.steps = steps;
  33. this.index = 0;
  34. this.data = {};
  35. this.nextLabelText = $.mage.__('Next');
  36. this.prevLabelText = $.mage.__('Back');
  37. this.elementSelector = '[data-role=steps-wizard-main]';
  38. this.element = modalClass ? $('.' + modalClass + this.elementSelector) : $(this.elementSelector);
  39. this.nextLabel = '[data-role="step-wizard-next"]';
  40. this.prevLabel = '[data-role="step-wizard-prev"]';
  41. this.element.notification();
  42. /**
  43. * Move to newIndex.
  44. *
  45. * @param {Number} newIndex
  46. * @return {String}
  47. */
  48. this.move = function (newIndex) {
  49. if (!this.preventSwitch(newIndex)) {
  50. if (newIndex > this.index) {
  51. this._next(newIndex);
  52. } else if (newIndex < this.index) {
  53. this._prev(newIndex);
  54. }
  55. }
  56. this.updateLabels(this.getStep());
  57. this.showNotificationMessage();
  58. return this.getStep().name;
  59. };
  60. /**
  61. * Move wizard to next step.
  62. *
  63. * @return {String}
  64. */
  65. this.next = function () {
  66. this.move(this.index + 1);
  67. return this.getStep().name;
  68. };
  69. /**
  70. * Move wizard to previous step.
  71. *
  72. * @return {String}
  73. */
  74. this.prev = function () {
  75. this.move(this.index - 1);
  76. return this.getStep().name;
  77. };
  78. /**
  79. * @return {*}
  80. */
  81. this.preventSwitch = function (newIndex) {
  82. return newIndex < 0 || (newIndex - this.index) > 1;//eslint-disable-line no-extra-parens
  83. };
  84. /**
  85. * @param {Number} newIndex
  86. * @return {Boolean}
  87. * @private
  88. */
  89. this._next = function (newIndex) {
  90. newIndex = _.isNumber(newIndex) ? newIndex : this.index + 1;
  91. try {
  92. this.getStep().force(this);
  93. if (newIndex >= steps.length) {
  94. return false;
  95. }
  96. } catch (e) {
  97. this.setNotificationMessage(e.message, true);
  98. return false;
  99. }
  100. this.cleanErrorNotificationMessage();
  101. this.index = newIndex;
  102. this.cleanNotificationMessage();
  103. this.render();
  104. };
  105. /**
  106. * @param {Number} newIndex
  107. * @private
  108. */
  109. this._prev = function (newIndex) {
  110. newIndex = _.isNumber(newIndex) ? newIndex : this.index - 1;
  111. this.getStep().back(this);
  112. this.index = newIndex;
  113. };
  114. /**
  115. * @param {Number} stepIndex
  116. * @return {Object}
  117. */
  118. this.getStep = function (stepIndex) {
  119. return this.steps[stepIndex || this.index] || {};
  120. };
  121. /**
  122. * @param {String} message
  123. * @param {String} error
  124. */
  125. this.notifyMessage = function (message, error) {
  126. $(this.element).notification('clear').notification('add', {
  127. error: error,
  128. message: message
  129. });
  130. };
  131. /**
  132. * @param {Object} step
  133. */
  134. this.updateLabels = function (step) {
  135. this.element.find(this.nextLabel).find('button').text(step.nextLabelText || this.nextLabelText);
  136. this.element.find(this.prevLabel).find('button').text(step.prevLabelText || this.prevLabelText);
  137. };
  138. /**
  139. * Show notification message.
  140. */
  141. this.showNotificationMessage = function () {
  142. if (!_.isEmpty(this.getStep())) {
  143. this.hideNotificationMessage();
  144. if (this.getStep().notificationMessage.text !== null) {
  145. this.notifyMessage(
  146. this.getStep().notificationMessage.text,
  147. this.getStep().notificationMessage.error
  148. );
  149. }
  150. }
  151. };
  152. /**
  153. * Remove notification message.
  154. */
  155. this.cleanNotificationMessage = function () {
  156. this.getStep().notificationMessage.text = null;
  157. this.hideNotificationMessage();
  158. };
  159. /**
  160. * Remove error message.
  161. */
  162. this.cleanErrorNotificationMessage = function () {
  163. if (this.getStep().notificationMessage.error === true) {
  164. this.cleanNotificationMessage();
  165. }
  166. };
  167. /**
  168. * @param {String} text
  169. * @param {String} error
  170. */
  171. this.setNotificationMessage = function (text, error) {
  172. error = error !== undefined;
  173. if (!_.isEmpty(this.getStep())) {
  174. this.getStep().notificationMessage.text = text;
  175. this.getStep().notificationMessage.error = error;
  176. this.showNotificationMessage();
  177. }
  178. };
  179. /**
  180. * Hide notification message.
  181. */
  182. this.hideNotificationMessage = function () {
  183. $(this.element).notification('clear');
  184. };
  185. /**
  186. * Render step.
  187. */
  188. this.render = function () {
  189. this.hideNotificationMessage();
  190. this.getStep().render(this);
  191. };
  192. /**
  193. * Initialize step.
  194. */
  195. this.init = function () {
  196. this.updateLabels(this.getStep());
  197. this.render();
  198. };
  199. this.init();
  200. };
  201. return Component.extend({
  202. defaults: {
  203. modalClass: '',
  204. initData: [],
  205. stepsNames: [],
  206. selectedStep: '',
  207. steps: [],
  208. disabled: true
  209. },
  210. /** @inheritdoc */
  211. initialize: function () {
  212. this._super();
  213. this.selectedStep.subscribe(this.wrapDisabledBackButton.bind(this));
  214. },
  215. /** @inheritdoc */
  216. initElement: function (step) {
  217. step.initData = this.initData;
  218. step.mode = _.all(this.initData, _.isEmpty) ? 'create' : 'edit';
  219. this.steps[this.getStepIndexByName(step.name)] = step;
  220. },
  221. /** @inheritdoc */
  222. initObservable: function () {
  223. this._super().observe([
  224. 'selectedStep',
  225. 'disabled'
  226. ]);
  227. return this;
  228. },
  229. /** @inheritdoc */
  230. destroy: function () {
  231. _.each(this.steps, function (step) {
  232. step.destroy();
  233. });
  234. this._super();
  235. },
  236. /**
  237. * Toggle disable property.
  238. *
  239. * @param {String} stepName
  240. */
  241. wrapDisabledBackButton: function (stepName) {
  242. if (_.first(this.stepsNames) === stepName) {
  243. this.disabled(true);
  244. } else {
  245. this.disabled(false);
  246. }
  247. },
  248. /**
  249. * Get step by index name.
  250. *
  251. * @param {String} stepName
  252. */
  253. getStepIndexByName: function (stepName) {
  254. return _.indexOf(this.stepsNames, stepName);
  255. },
  256. //controls, todo to another object
  257. /**
  258. * Select next step.
  259. */
  260. next: function () {
  261. this.selectedStep(this.wizard.next());
  262. },
  263. /**
  264. * Select previous step.
  265. */
  266. back: function () {
  267. this.selectedStep(this.wizard.prev());
  268. },
  269. /**
  270. * Open wizard.
  271. */
  272. open: function () {
  273. this.selectedStep(this.stepsNames.first());
  274. this.wizard = new Wizard(this.steps, this.modalClass);
  275. },
  276. /**
  277. * Close wizard.
  278. */
  279. close: function () {
  280. var modal = uiRegistry.get(this.initData.configurableModal);
  281. if (!_.isUndefined(modal)) {
  282. modal.closeModal();
  283. }
  284. },
  285. /**
  286. * @param {Object} data
  287. * @param {Object} event
  288. */
  289. showSpecificStep: function (data, event) {
  290. var index = _.indexOf(this.stepsNames, event.target.hash.substr(1)),
  291. stepName = this.wizard.move(index);
  292. this.selectedStep(stepName);
  293. }
  294. });
  295. });