widget.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. /**
  2. * Copyright © Magento, Inc. All rights reserved.
  3. * See COPYING.txt for license details.
  4. */
  5. /* global setLocation, Base64, updateElementAtCursor, varienGlobalEvents */
  6. /* eslint-disable strict */
  7. define([
  8. 'jquery',
  9. 'wysiwygAdapter',
  10. 'Magento_Ui/js/modal/alert',
  11. 'jquery/ui',
  12. 'mage/translate',
  13. 'mage/mage',
  14. 'mage/validation',
  15. 'mage/adminhtml/events',
  16. 'prototype',
  17. 'Magento_Ui/js/modal/modal'
  18. ], function (jQuery, wysiwyg, alert) {
  19. var widgetTools = {
  20. /**
  21. * Sets the widget to be active and is the scope of the slide out if the value is set
  22. */
  23. activeSelectedNode: null,
  24. editMode: false,
  25. cursorLocation: 0,
  26. /**
  27. * Set active selected node.
  28. *
  29. * @param {Object} activeSelectedNode
  30. */
  31. setActiveSelectedNode: function (activeSelectedNode) {
  32. this.activeSelectedNode = activeSelectedNode;
  33. },
  34. /**
  35. * Get active selected node.
  36. *
  37. * @returns {null}
  38. */
  39. getActiveSelectedNode: function () {
  40. return this.activeSelectedNode;
  41. },
  42. /**
  43. *
  44. * @param {Boolean} editMode
  45. */
  46. setEditMode: function (editMode) {
  47. this.editMode = editMode;
  48. },
  49. /**
  50. * @param {*} id
  51. * @param {*} html
  52. * @return {String}
  53. */
  54. getDivHtml: function (id, html) {
  55. if (!html) {
  56. html = '';
  57. }
  58. return '<div id="' + id + '">' + html + '</div>';
  59. },
  60. /**
  61. * @param {Object} transport
  62. */
  63. onAjaxSuccess: function (transport) {
  64. var response;
  65. if (transport.responseText.isJSON()) {
  66. response = transport.responseText.evalJSON();
  67. if (response.error) {
  68. throw response;
  69. } else if (response.ajaxExpired && response.ajaxRedirect) {
  70. setLocation(response.ajaxRedirect);
  71. }
  72. }
  73. },
  74. dialogOpened: false,
  75. /**
  76. * @return {Number}
  77. */
  78. getMaxZIndex: function () {
  79. var max = 0,
  80. cn = document.body.childNodes,
  81. i, el, zIndex;
  82. for (i = 0; i < cn.length; i++) {
  83. el = cn[i];
  84. zIndex = el.nodeType == 1 ? parseInt(el.style.zIndex, 10) || 0 : 0; //eslint-disable-line eqeqeq
  85. if (zIndex < 10000) {
  86. max = Math.max(max, zIndex);
  87. }
  88. }
  89. return max + 10;
  90. },
  91. /**
  92. * @param {String} widgetUrl
  93. */
  94. openDialog: function (widgetUrl) {
  95. var oThis = this,
  96. title = 'Insert Widget',
  97. mode = 'new',
  98. dialog;
  99. if (this.editMode) {
  100. title = 'Edit Widget';
  101. mode = 'edit';
  102. }
  103. if (this.dialogOpened) {
  104. return;
  105. }
  106. this.dialogWindow = jQuery('<div/>').modal({
  107. title: jQuery.mage.__(title),
  108. type: 'slide',
  109. buttons: [],
  110. /**
  111. * Opened.
  112. */
  113. opened: function () {
  114. dialog = jQuery(this).addClass('loading magento-message');
  115. widgetUrl += 'mode/' + mode;
  116. new Ajax.Updater($(this), widgetUrl, {
  117. evalScripts: true,
  118. /**
  119. * On complete.
  120. */
  121. onComplete: function () {
  122. dialog.removeClass('loading');
  123. }
  124. });
  125. },
  126. /**
  127. * @param {jQuery.Event} e
  128. * @param {Object} modal
  129. */
  130. closed: function (e, modal) {
  131. modal.modal.remove();
  132. oThis.dialogOpened = false;
  133. }
  134. });
  135. this.dialogOpened = true;
  136. this.dialogWindow.modal('openModal');
  137. }
  138. },
  139. WysiwygWidget = {};
  140. WysiwygWidget.Widget = Class.create();
  141. WysiwygWidget.Widget.prototype = {
  142. /**
  143. * @param {HTMLElement} formEl
  144. * @param {HTMLElement} widgetEl
  145. * @param {*} widgetOptionsEl
  146. * @param {*} optionsSourceUrl
  147. * @param {*} widgetTargetId
  148. */
  149. initialize: function (formEl, widgetEl, widgetOptionsEl, optionsSourceUrl, widgetTargetId) {
  150. $(formEl).insert({
  151. bottom: widgetTools.getDivHtml(widgetOptionsEl)
  152. });
  153. this.formEl = formEl;
  154. this.widgetEl = $(widgetEl);
  155. this.widgetOptionsEl = $(widgetOptionsEl);
  156. this.optionsUrl = optionsSourceUrl;
  157. this.optionValues = new Hash({});
  158. this.widgetTargetId = widgetTargetId;
  159. if (typeof wysiwyg != 'undefined' && wysiwyg.activeEditor()) { //eslint-disable-line eqeqeq
  160. this.bMark = wysiwyg.activeEditor().selection.getBookmark();
  161. }
  162. // disable -- Please Select -- option from being re-selected
  163. this.widgetEl.querySelector('option').setAttribute('disabled', 'disabled');
  164. Event.observe(this.widgetEl, 'change', this.loadOptions.bind(this));
  165. this.initOptionValues();
  166. },
  167. /**
  168. * @return {String}
  169. */
  170. getOptionsContainerId: function () {
  171. return this.widgetOptionsEl.id + '_' + this.widgetEl.value.gsub(/\//, '_');
  172. },
  173. /**
  174. * @param {*} containerId
  175. */
  176. switchOptionsContainer: function (containerId) {
  177. $$('#' + this.widgetOptionsEl.id + ' div[id^=' + this.widgetOptionsEl.id + ']').each(function (e) {
  178. this.disableOptionsContainer(e.id);
  179. }.bind(this));
  180. if (containerId != undefined) { //eslint-disable-line eqeqeq
  181. this.enableOptionsContainer(containerId);
  182. }
  183. this._showWidgetDescription();
  184. },
  185. /**
  186. * @param {*} containerId
  187. */
  188. enableOptionsContainer: function (containerId) {
  189. $$('#' + containerId + ' .widget-option').each(function (e) {
  190. e.removeClassName('skip-submit');
  191. if (e.hasClassName('obligatory')) {
  192. e.removeClassName('obligatory');
  193. e.addClassName('required-entry');
  194. }
  195. });
  196. $(containerId).removeClassName('no-display');
  197. },
  198. /**
  199. * @param {*} containerId
  200. */
  201. disableOptionsContainer: function (containerId) {
  202. if ($(containerId).hasClassName('no-display')) {
  203. return;
  204. }
  205. $$('#' + containerId + ' .widget-option').each(function (e) {
  206. // Avoid submitting fields of unactive container
  207. if (!e.hasClassName('skip-submit')) {
  208. e.addClassName('skip-submit');
  209. }
  210. // Form validation workaround for unactive container
  211. if (e.hasClassName('required-entry')) {
  212. e.removeClassName('required-entry');
  213. e.addClassName('obligatory');
  214. }
  215. });
  216. $(containerId).addClassName('no-display');
  217. },
  218. /**
  219. * Assign widget options values when existing widget selected in WYSIWYG.
  220. *
  221. * @return {Boolean}
  222. */
  223. initOptionValues: function () {
  224. var e, widgetCode;
  225. if (!this.wysiwygExists()) {
  226. return false;
  227. }
  228. e = this.getWysiwygNode();
  229. if (e.localName === 'span') {
  230. e = e.firstElementChild;
  231. }
  232. if (e != undefined && e.id) { //eslint-disable-line eqeqeq
  233. // attempt to Base64-decode id on selected node; exception is thrown if it is in fact not a widget node
  234. try {
  235. widgetCode = Base64.idDecode(e.id);
  236. } catch (ex) {
  237. return false;
  238. }
  239. if (widgetCode.indexOf('{{widget') !== -1) {
  240. this.optionValues = new Hash({});
  241. widgetCode.gsub(/([a-z0-9\_]+)\s*\=\s*[\"]{1}([^\"]+)[\"]{1}/i, function (match) {
  242. if (match[1] == 'type') { //eslint-disable-line eqeqeq
  243. this.widgetEl.value = match[2];
  244. } else {
  245. this.optionValues.set(match[1], match[2]);
  246. }
  247. }.bind(this));
  248. this.loadOptions();
  249. }
  250. }
  251. },
  252. /**
  253. * Load options.
  254. */
  255. loadOptions: function () {
  256. var optionsContainerId,
  257. params,
  258. msg,
  259. msgTmpl,
  260. $wrapper,
  261. typeName = this.optionValues.get('type_name');
  262. if (!this.widgetEl.value) {
  263. if (typeName) {
  264. msgTmpl = jQuery.mage.__('The widget %1 is no longer available. Select a different widget.');
  265. msg = jQuery.mage.__(msgTmpl).replace('%1', typeName);
  266. jQuery('body').notification('clear').notification('add', {
  267. error: true,
  268. message: msg,
  269. /**
  270. * @param {String} message
  271. */
  272. insertMethod: function (message) {
  273. $wrapper = jQuery('<div/>').html(message);
  274. $wrapper.insertAfter('.modal-slide .page-main-actions');
  275. }
  276. });
  277. }
  278. this.switchOptionsContainer();
  279. return;
  280. }
  281. optionsContainerId = this.getOptionsContainerId();
  282. if ($(optionsContainerId) != undefined) { //eslint-disable-line eqeqeq
  283. this.switchOptionsContainer(optionsContainerId);
  284. return;
  285. }
  286. this._showWidgetDescription();
  287. params = {
  288. 'widget_type': this.widgetEl.value,
  289. values: this.optionValues
  290. };
  291. new Ajax.Request(this.optionsUrl, {
  292. parameters: {
  293. widget: Object.toJSON(params)
  294. },
  295. /**
  296. * On success.
  297. */
  298. onSuccess: function (transport) {
  299. try {
  300. widgetTools.onAjaxSuccess(transport);
  301. this.switchOptionsContainer();
  302. if ($(optionsContainerId) == undefined) { //eslint-disable-line eqeqeq
  303. this.widgetOptionsEl.insert({
  304. bottom: widgetTools.getDivHtml(optionsContainerId, transport.responseText)
  305. });
  306. } else {
  307. this.switchOptionsContainer(optionsContainerId);
  308. }
  309. } catch (e) {
  310. alert({
  311. content: e.message
  312. });
  313. }
  314. }.bind(this)
  315. });
  316. },
  317. /**
  318. * @private
  319. */
  320. _showWidgetDescription: function () {
  321. var noteCnt = this.widgetEl.next().down('small'),
  322. descrCnt = $('widget-description-' + this.widgetEl.selectedIndex),
  323. description;
  324. if (noteCnt != undefined) { //eslint-disable-line eqeqeq
  325. description = descrCnt != undefined ? descrCnt.innerHTML : ''; //eslint-disable-line eqeqeq
  326. noteCnt.update(description);
  327. }
  328. },
  329. /**
  330. * Validate field.
  331. */
  332. validateField: function () {
  333. jQuery(this.widgetEl).valid();
  334. jQuery('#insert_button').removeClass('disabled');
  335. },
  336. /**
  337. * Closes the modal
  338. */
  339. closeModal: function () {
  340. widgetTools.dialogWindow.modal('closeModal');
  341. },
  342. /* eslint-disable max-depth*/
  343. /**
  344. * Insert widget.
  345. */
  346. insertWidget: function () {
  347. var validationResult,
  348. $form = jQuery('#' + this.formEl),
  349. formElements,
  350. i,
  351. params,
  352. editor,
  353. activeNode;
  354. // remove cached validator instance, which caches elements to validate
  355. jQuery.data($form[0], 'validator', null);
  356. $form.validate({
  357. /**
  358. * Ignores elements with .skip-submit, .no-display ancestor elements
  359. */
  360. ignore: function () {
  361. return jQuery(this).closest('.skip-submit, .no-display').length;
  362. },
  363. errorClass: 'mage-error'
  364. });
  365. validationResult = $form.valid();
  366. if (validationResult) {
  367. formElements = [];
  368. i = 0;
  369. Form.getElements($(this.formEl)).each(function (e) {
  370. if (!e.hasClassName('skip-submit')) {
  371. formElements[i] = e;
  372. i++;
  373. }
  374. });
  375. // Add as_is flag to parameters if wysiwyg editor doesn't exist
  376. params = Form.serializeElements(formElements);
  377. if (!this.wysiwygExists()) {
  378. params += '&as_is=1';
  379. }
  380. new Ajax.Request($(this.formEl).action, {
  381. parameters: params,
  382. onComplete: function (transport) {
  383. try {
  384. editor = wysiwyg.activeEditor();
  385. widgetTools.onAjaxSuccess(transport);
  386. widgetTools.dialogWindow.modal('closeModal');
  387. if (editor) {
  388. editor.focus();
  389. activeNode = widgetTools.getActiveSelectedNode();
  390. if (activeNode) {
  391. editor.selection.select(activeNode);
  392. editor.selection.setContent(transport.responseText);
  393. } else if (this.bMark) {
  394. wysiwyg.activeEditor().selection.moveToBookmark(this.bMark);
  395. }
  396. }
  397. if (!activeNode) {
  398. this.updateContent(transport.responseText);
  399. }
  400. } catch (e) {
  401. alert({
  402. content: e.message
  403. });
  404. }
  405. }.bind(this)
  406. });
  407. }
  408. },
  409. /**
  410. * @param {Object} content
  411. */
  412. updateContent: function (content) {
  413. var textarea;
  414. if (this.wysiwygExists()) {
  415. wysiwyg.insertContent(content, false);
  416. } else {
  417. textarea = document.getElementById(this.widgetTargetId);
  418. updateElementAtCursor(textarea, content);
  419. varienGlobalEvents.fireEvent('tinymceChange');
  420. jQuery(textarea).change();
  421. }
  422. },
  423. /**
  424. * @return {Boolean}
  425. */
  426. wysiwygExists: function () {
  427. return typeof wysiwyg != 'undefined' && wysiwyg.get(this.widgetTargetId);
  428. },
  429. /**
  430. * @return {null|wysiwyg.Editor|*}
  431. */
  432. getWysiwyg: function () {
  433. return wysiwyg.activeEditor();
  434. },
  435. /**
  436. * @return {*|Element}
  437. */
  438. getWysiwygNode: function () {
  439. return widgetTools.getActiveSelectedNode() || wysiwyg.activeEditor().selection.getNode();
  440. }
  441. };
  442. WysiwygWidget.chooser = Class.create();
  443. WysiwygWidget.chooser.prototype = {
  444. // HTML element A, on which click event fired when choose a selection
  445. chooserId: null,
  446. // Source URL for Ajax requests
  447. chooserUrl: null,
  448. // Chooser config
  449. config: null,
  450. // Chooser dialog window
  451. dialogWindow: null,
  452. // Chooser content for dialog window
  453. dialogContent: null,
  454. overlayShowEffectOptions: null,
  455. overlayHideEffectOptions: null,
  456. /**
  457. * @param {*} chooserId
  458. * @param {*} chooserUrl
  459. * @param {*} config
  460. */
  461. initialize: function (chooserId, chooserUrl, config) {
  462. this.chooserId = chooserId;
  463. this.chooserUrl = chooserUrl;
  464. this.config = config;
  465. },
  466. /**
  467. * @return {String}
  468. */
  469. getResponseContainerId: function () {
  470. return 'responseCnt' + this.chooserId;
  471. },
  472. /**
  473. * @return {jQuery|*|HTMLElement}
  474. */
  475. getChooserControl: function () {
  476. return $(this.chooserId + 'control');
  477. },
  478. /**
  479. * @return {jQuery|*|HTMLElement}
  480. */
  481. getElement: function () {
  482. return $(this.chooserId + 'value');
  483. },
  484. /**
  485. * @return {jQuery|*|HTMLElement}
  486. */
  487. getElementLabel: function () {
  488. return $(this.chooserId + 'label');
  489. },
  490. /**
  491. * Open.
  492. */
  493. open: function () {
  494. $(this.getResponseContainerId()).show();
  495. },
  496. /**
  497. * Close.
  498. */
  499. close: function () {
  500. $(this.getResponseContainerId()).hide();
  501. this.closeDialogWindow();
  502. },
  503. /**
  504. * Choose.
  505. */
  506. choose: function () {
  507. // Open dialog window with previously loaded dialog content
  508. var responseContainerId;
  509. if (this.dialogContent) {
  510. this.openDialogWindow(this.dialogContent);
  511. return;
  512. }
  513. // Show or hide chooser content if it was already loaded
  514. responseContainerId = this.getResponseContainerId();
  515. // Otherwise load content from server
  516. new Ajax.Request(this.chooserUrl, {
  517. parameters: {
  518. 'element_value': this.getElementValue(),
  519. 'element_label': this.getElementLabelText()
  520. },
  521. /**
  522. * On success.
  523. */
  524. onSuccess: function (transport) {
  525. try {
  526. widgetTools.onAjaxSuccess(transport);
  527. this.dialogContent = widgetTools.getDivHtml(responseContainerId, transport.responseText);
  528. this.openDialogWindow(this.dialogContent);
  529. } catch (e) {
  530. alert({
  531. content: e.message
  532. });
  533. }
  534. }.bind(this)
  535. });
  536. },
  537. /**
  538. * Open dialog winodw.
  539. *
  540. * @param {*} content
  541. */
  542. openDialogWindow: function (content) {
  543. this.dialogWindow = jQuery('<div/>').modal({
  544. title: this.config.buttons.open,
  545. type: 'slide',
  546. buttons: [],
  547. /**
  548. * Opened.
  549. */
  550. opened: function () {
  551. jQuery(this).addClass('magento-message');
  552. },
  553. /**
  554. * @param {jQuery.Event} e
  555. * @param {Object} modal
  556. */
  557. closed: function (e, modal) {
  558. modal.modal.remove();
  559. this.dialogWindow = null;
  560. }
  561. });
  562. this.dialogWindow.modal('openModal').append(content);
  563. },
  564. /**
  565. * Close dialog window.
  566. */
  567. closeDialogWindow: function () {
  568. this.dialogWindow.modal('closeModal').remove();
  569. },
  570. /**
  571. * @return {*|Number}
  572. */
  573. getElementValue: function () {
  574. return this.getElement().value;
  575. },
  576. /**
  577. * @return {String}
  578. */
  579. getElementLabelText: function () {
  580. return this.getElementLabel().innerHTML;
  581. },
  582. /**
  583. * @param {*} value
  584. */
  585. setElementValue: function (value) {
  586. this.getElement().value = value;
  587. },
  588. /**
  589. * @param {*} value
  590. */
  591. setElementLabel: function (value) {
  592. this.getElementLabel().innerHTML = value;
  593. }
  594. };
  595. window.WysiwygWidget = WysiwygWidget;
  596. window.widgetTools = widgetTools;
  597. });