browser.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. /**
  2. * Copyright © Magento, Inc. All rights reserved.
  3. * See COPYING.txt for license details.
  4. */
  5. /* global MediabrowserUtility, FORM_KEY, tinyMceEditors */
  6. /* eslint-disable strict */
  7. define([
  8. 'jquery',
  9. 'wysiwygAdapter',
  10. 'Magento_Ui/js/modal/prompt',
  11. 'Magento_Ui/js/modal/confirm',
  12. 'Magento_Ui/js/modal/alert',
  13. 'Magento_Ui/js/modal/modal',
  14. 'jquery/ui',
  15. 'jquery/jstree/jquery.jstree',
  16. 'mage/mage'
  17. ], function ($, wysiwyg, prompt, confirm, alert) {
  18. window.MediabrowserUtility = {
  19. windowId: 'modal_dialog_message',
  20. /**
  21. * @return {Number}
  22. */
  23. getMaxZIndex: function () {
  24. var max = 0,
  25. cn = document.body.childNodes,
  26. i, el, zIndex;
  27. for (i = 0; i < cn.length; i++) {
  28. el = cn[i];
  29. zIndex = el.nodeType == 1 ? parseInt(el.style.zIndex, 10) || 0 : 0; //eslint-disable-line eqeqeq
  30. if (zIndex < 10000) {
  31. max = Math.max(max, zIndex);
  32. }
  33. }
  34. return max + 10;
  35. },
  36. /**
  37. * @param {*} url
  38. * @param {*} width
  39. * @param {*} height
  40. * @param {*} title
  41. * @param {Object} options
  42. */
  43. openDialog: function (url, width, height, title, options) {
  44. var windowId = this.windowId,
  45. content = '<div class="popup-window" id="' + windowId + '"></div>',
  46. self = this;
  47. if (this.modal) {
  48. this.modal.html($(content).html());
  49. if (options && typeof options.closed !== 'undefined') {
  50. this.modal.modal('option', 'closed', options.closed);
  51. }
  52. } else {
  53. this.modal = $(content).modal($.extend({
  54. title: title || 'Insert File...',
  55. modalClass: 'magento',
  56. type: 'slide',
  57. buttons: []
  58. }, options));
  59. }
  60. this.modal.modal('openModal');
  61. $.ajax({
  62. url: url,
  63. type: 'get',
  64. context: $(this),
  65. showLoader: true
  66. }).done(function (data) {
  67. self.modal.html(data).trigger('contentUpdated');
  68. });
  69. },
  70. /**
  71. * Close dialog.
  72. */
  73. closeDialog: function () {
  74. this.modal.modal('closeModal');
  75. }
  76. };
  77. $.widget('mage.mediabrowser', {
  78. eventPrefix: 'mediabrowser',
  79. options: {
  80. targetElementId: null,
  81. contentsUrl: null,
  82. onInsertUrl: null,
  83. newFolderUrl: null,
  84. deleteFolderUrl: null,
  85. deleteFilesUrl: null,
  86. headerText: null,
  87. tree: null,
  88. currentNode: null,
  89. storeId: null,
  90. showBreadcrumbs: null,
  91. hidden: 'no-display'
  92. },
  93. /**
  94. * Proxy creation
  95. * @protected
  96. */
  97. _create: function () {
  98. this._on({
  99. 'click [data-row=file]': 'selectFile',
  100. 'dblclick [data-row=file]': 'insert',
  101. 'click #new_folder': 'newFolder',
  102. 'click #delete_folder': 'deleteFolder',
  103. 'click #delete_files': 'deleteFiles',
  104. 'click #insert_files': 'insertSelectedFiles',
  105. 'fileuploaddone': '_uploadDone',
  106. 'click [data-row=breadcrumb]': 'selectFolder'
  107. });
  108. this.activeNode = null;
  109. //tree dont use event bubbling
  110. this.tree = this.element.find('[data-role=tree]');
  111. this.tree.on('select_node.jstree', $.proxy(this._selectNode, this));
  112. },
  113. /**
  114. * @param {jQuery.Event} event
  115. * @param {Object} data
  116. * @private
  117. */
  118. _selectNode: function (event, data) {
  119. var node = data.rslt.obj.data('node');
  120. this.activeNode = node;
  121. this.element.find('#delete_files, #insert_files').toggleClass(this.options.hidden, true);
  122. this.element.find('#contents').toggleClass(this.options.hidden, false);
  123. this.element.find('#delete_folder')
  124. .toggleClass(this.options.hidden, node.id == 'root'); //eslint-disable-line eqeqeq
  125. this.element.find('#content_header_text')
  126. .html(node.id == 'root' ? this.headerText : node.text); //eslint-disable-line eqeqeq
  127. this.drawBreadcrumbs(data);
  128. this.loadFileList(node);
  129. },
  130. /**
  131. * @return {*}
  132. */
  133. reload: function (uploaded) {
  134. return this.loadFileList(this.activeNode, uploaded);
  135. },
  136. /**
  137. * @param {Object} element
  138. * @param {*} value
  139. */
  140. insertAtCursor: function (element, value) {
  141. var sel, startPos, endPos, scrollTop;
  142. if ('selection' in document) {
  143. //For browsers like Internet Explorer
  144. element.focus();
  145. sel = document.selection.createRange();
  146. sel.text = value;
  147. element.focus();
  148. } else if (element.selectionStart || element.selectionStart == '0') { //eslint-disable-line eqeqeq
  149. //For browsers like Firefox and Webkit based
  150. startPos = element.selectionStart;
  151. endPos = element.selectionEnd;
  152. scrollTop = element.scrollTop;
  153. element.value = element.value.substring(0, startPos) + value +
  154. element.value.substring(startPos, endPos) + element.value.substring(endPos, element.value.length);
  155. element.focus();
  156. element.selectionStart = startPos + value.length;
  157. element.selectionEnd = startPos + value.length + element.value.substring(startPos, endPos).length;
  158. element.scrollTop = scrollTop;
  159. } else {
  160. element.value += value;
  161. element.focus();
  162. }
  163. },
  164. /**
  165. * @param {Object} node
  166. */
  167. loadFileList: function (node, uploaded) {
  168. var contentBlock = this.element.find('#contents');
  169. return $.ajax({
  170. url: this.options.contentsUrl,
  171. type: 'GET',
  172. dataType: 'html',
  173. data: {
  174. 'form_key': FORM_KEY,
  175. node: node.id
  176. },
  177. context: contentBlock,
  178. showLoader: true
  179. }).done(function (data) {
  180. contentBlock.html(data).trigger('contentUpdated');
  181. if (uploaded) {
  182. contentBlock.find('.filecnt:last').click();
  183. }
  184. });
  185. },
  186. /**
  187. * @param {jQuery.Event} event
  188. */
  189. selectFolder: function (event) {
  190. this.element.find('[data-id="' + $(event.currentTarget).data('node').id + '"]>a').click();
  191. },
  192. /**
  193. * Insert selected files.
  194. */
  195. insertSelectedFiles: function () {
  196. this.element.find('[data-row=file].selected').trigger('dblclick');
  197. },
  198. /**
  199. * @param {jQuery.Event} event
  200. */
  201. selectFile: function (event) {
  202. var fileRow = $(event.currentTarget);
  203. fileRow.toggleClass('selected');
  204. this.element.find('[data-row=file]').not(fileRow).removeClass('selected');
  205. this.element.find('#delete_files, #insert_files')
  206. .toggleClass(this.options.hidden, !fileRow.is('.selected'));
  207. fileRow.trigger('selectfile');
  208. },
  209. /**
  210. * @private
  211. */
  212. _uploadDone: function () {
  213. this.element.find('.file-row').remove();
  214. this.reload(true);
  215. },
  216. /**
  217. * @param {jQuery.Event} event
  218. * @return {Boolean}
  219. */
  220. insert: function (event) {
  221. var fileRow = $(event.currentTarget),
  222. targetEl;
  223. if (!fileRow.prop('id')) {
  224. return false;
  225. }
  226. targetEl = this.getTargetElement();
  227. if (!targetEl.length) {
  228. MediabrowserUtility.closeDialog();
  229. throw 'Target element not found for content update';
  230. }
  231. return $.ajax({
  232. url: this.options.onInsertUrl,
  233. data: {
  234. filename: fileRow.attr('id'),
  235. node: this.activeNode.id,
  236. store: this.options.storeId,
  237. 'as_is': targetEl.is('textarea') ? 1 : 0,
  238. 'force_static_path': targetEl.data('force_static_path') ? 1 : 0,
  239. 'form_key': FORM_KEY
  240. },
  241. context: this,
  242. showLoader: true
  243. }).done($.proxy(function (data) {
  244. if (targetEl.is('textarea')) {
  245. this.insertAtCursor(targetEl.get(0), data);
  246. } else {
  247. targetEl
  248. .val(data)
  249. .data('size', fileRow.data('size'))
  250. .data('mime-type', fileRow.data('mime-type'))
  251. .trigger('change');
  252. }
  253. MediabrowserUtility.closeDialog();
  254. targetEl.focus();
  255. jQuery(targetEl).change();
  256. }, this));
  257. },
  258. /**
  259. * Find document target element in next order:
  260. * in acive file browser opener:
  261. * - input field with ID: "src" in opener window
  262. * - input field with ID: "href" in opener window
  263. * in document:
  264. * - element with target ID
  265. *
  266. * return {HTMLElement|null}
  267. */
  268. getTargetElement: function () {
  269. var opener, targetElementId;
  270. if (typeof wysiwyg != 'undefined' && wysiwyg.get(this.options.targetElementId)) {
  271. opener = this.getMediaBrowserOpener() || window;
  272. targetElementId = tinyMceEditors.get(this.options.targetElementId).getMediaBrowserTargetElementId();
  273. return $(opener.document.getElementById(targetElementId));
  274. }
  275. return $('#' + this.options.targetElementId);
  276. },
  277. /**
  278. * Return opener Window object if it exists, not closed and editor is active
  279. *
  280. * return {Object|null}
  281. */
  282. getMediaBrowserOpener: function () {
  283. if (typeof wysiwyg != 'undefined' &&
  284. wysiwyg.get(this.options.targetElementId) &&
  285. typeof tinyMceEditors != 'undefined' &&
  286. !tinyMceEditors.get(this.options.targetElementId).getMediaBrowserOpener().closed
  287. ) {
  288. return tinyMceEditors.get(this.options.targetElementId).getMediaBrowserOpener();
  289. }
  290. return null;
  291. },
  292. /**
  293. * New folder.
  294. */
  295. newFolder: function () {
  296. var self = this;
  297. prompt({
  298. title: this.options.newFolderPrompt,
  299. actions: {
  300. /**
  301. * @param {*} folderName
  302. */
  303. confirm: function (folderName) {
  304. return $.ajax({
  305. url: self.options.newFolderUrl,
  306. dataType: 'json',
  307. data: {
  308. name: folderName,
  309. node: self.activeNode.id,
  310. store: self.options.storeId,
  311. 'form_key': FORM_KEY
  312. },
  313. context: self.element,
  314. showLoader: true
  315. }).done($.proxy(function (data) {
  316. if (data.error) {
  317. alert({
  318. content: data.message
  319. });
  320. } else {
  321. self.tree.jstree(
  322. 'refresh',
  323. self.element.find('[data-id="' + self.activeNode.id + '"]')
  324. );
  325. }
  326. }, this));
  327. }
  328. }
  329. });
  330. },
  331. /**
  332. * Delete folder.
  333. */
  334. deleteFolder: function () {
  335. var self = this;
  336. confirm({
  337. content: this.options.deleteFolderConfirmationMessage,
  338. actions: {
  339. /**
  340. * Confirm.
  341. */
  342. confirm: function () {
  343. return $.ajax({
  344. url: self.options.deleteFolderUrl,
  345. dataType: 'json',
  346. data: {
  347. node: self.activeNode.id,
  348. store: self.options.storeId,
  349. 'form_key': FORM_KEY
  350. },
  351. context: self.element,
  352. showLoader: true
  353. }).done($.proxy(function () {
  354. self.tree.jstree('refresh', self.activeNode.id);
  355. }, this));
  356. },
  357. /**
  358. * @return {Boolean}
  359. */
  360. cancel: function () {
  361. return false;
  362. }
  363. }
  364. });
  365. },
  366. /**
  367. * Delete files.
  368. */
  369. deleteFiles: function () {
  370. var self = this;
  371. confirm({
  372. content: this.options.deleteFileConfirmationMessage,
  373. actions: {
  374. /**
  375. * Confirm.
  376. */
  377. confirm: function () {
  378. var selectedFiles = self.element.find('[data-row=file].selected'),
  379. ids = selectedFiles.map(function () {
  380. return $(this).attr('id');
  381. }).toArray();
  382. return $.ajax({
  383. url: self.options.deleteFilesUrl,
  384. data: {
  385. files: ids,
  386. store: self.options.storeId,
  387. 'form_key': FORM_KEY
  388. },
  389. context: self.element,
  390. showLoader: true
  391. }).done($.proxy(function () {
  392. self.reload();
  393. self.element.find('#delete_files, #insert_files').toggleClass(self.options.hidden, true);
  394. $(window).trigger('fileDeleted.mediabrowser', {
  395. ids: ids
  396. });
  397. }, this));
  398. },
  399. /**
  400. * @return {Boolean}
  401. */
  402. cancel: function () {
  403. return false;
  404. }
  405. }
  406. });
  407. },
  408. /**
  409. * @param {Object} data
  410. */
  411. drawBreadcrumbs: function (data) {
  412. var node, breadcrumbs;
  413. if (this.element.find('#breadcrumbs').length) {
  414. this.element.find('#breadcrumbs').remove();
  415. }
  416. node = data.rslt.obj.data('node');
  417. if (node.id == 'root') { //eslint-disable-line eqeqeq
  418. return;
  419. }
  420. breadcrumbs = $('<ul class="breadcrumbs" id="breadcrumbs" />');
  421. $(data.rslt.obj.parents('[data-id]').get().reverse()).add(data.rslt.obj).each(function (index, element) {
  422. var nodeData = $(element).data('node');
  423. if (index > 0) {
  424. breadcrumbs.append($('<li>\/</li>'));
  425. }
  426. breadcrumbs.append($('<li />')
  427. .data('node', nodeData).attr('data-row', 'breadcrumb').text(nodeData.text));
  428. });
  429. breadcrumbs.insertAfter(this.element.find('#content_header'));
  430. }
  431. });
  432. return window.MediabrowserUtility;
  433. });