sonata_admin_js_sonata-page.back_15.js 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008
  1. /**
  2. *
  3. * This file is part of the Sonata package.
  4. *
  5. * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. *
  10. * generated on: Thu Oct 13 2016 14:45:24 GMT+0200 (CEST)
  11. * revision: 1b8c129eacbe075527ab2cc974ac9c14f2981ad2
  12. *
  13. */
  14. /**
  15. *
  16. * This file is part of the Sonata package.
  17. *
  18. * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
  19. *
  20. * For the full copyright and license information, please view the LICENSE
  21. * file that was distributed with this source code.
  22. *
  23. */
  24. (function ($, global) {
  25. /**
  26. * PageComposer class.
  27. *
  28. * @constructor
  29. */
  30. var PageComposer = function (pageId, options) {
  31. var settings = options || {};
  32. this.pageId = pageId;
  33. this.$container = $('.page-composer');
  34. this.$dynamicArea = $('.page-composer__dyn-content');
  35. this.$pagePreview = $('.page-composer__page-preview');
  36. this.$containerPreviews = this.$pagePreview.find('.page-composer__page-preview__container');
  37. this.routes = $.extend({}, settings.routes || {});
  38. this.translations = $.extend({}, settings.translations || {});
  39. this.csrfTokens = $.extend({}, settings.csrfTokens || {});
  40. this.bindPagePreviewHandlers();
  41. this.bindOrphansHandlers();
  42. // attach event listeners
  43. var self = this,
  44. $this = $(this);
  45. $this.on('containerclick', function (e) {
  46. self.loadContainer(e.$container);
  47. });
  48. $this.on('containerloaded', this.handleContainerLoaded);
  49. $this.on('blockcreated', this.handleBlockCreated);
  50. $this.on('blockremoved', this.handleBlockRemoved);
  51. $this.on('blockcreateformloaded', this.handleBlockCreateFormLoaded);
  52. $this.on('blockpositionsupdate', this.handleBlockPositionsUpdate);
  53. $this.on('blockeditformloaded', this.handleBlockEditFormLoaded);
  54. $this.on('blockparentswitched', this.handleBlockParentSwitched);
  55. };
  56. /**
  57. * Apply all Admin required functions.
  58. *
  59. * @param $context
  60. */
  61. function applyAdmin($context) {
  62. if (typeof global.admin != 'undefined') {
  63. return;
  64. }
  65. Admin.shared_setup($context);
  66. }
  67. PageComposer.prototype = {
  68. /**
  69. * Translates given label.
  70. *
  71. * @param {String} label
  72. * @return {String}
  73. */
  74. translate: function (label) {
  75. if (this.translations[label]) {
  76. return this.translations[label];
  77. }
  78. return label;
  79. },
  80. /**
  81. * @param id
  82. * @param parameters
  83. * @returns {*}
  84. */
  85. getRouteUrl: function (id, parameters) {
  86. if (!this.routes[id]) {
  87. throw new Error('Route "' + id + '" does not exist');
  88. }
  89. var url = this.routes[id];
  90. for (var paramKey in parameters) {
  91. url = url.replace(new RegExp(paramKey), parameters[paramKey]);
  92. }
  93. return url;
  94. },
  95. /**
  96. * Check if the given form element name attribute match specific type.
  97. * Used because form element names are 'hashes' (s5311aef39e552[name]).
  98. *
  99. * @param name
  100. * @param type
  101. * @returns {boolean}
  102. */
  103. isFormControlTypeByName: function (name, type) {
  104. if (typeof name != 'undefined') {
  105. var position = name.length,
  106. search = '[' + type + ']',
  107. lastIndex = name.lastIndexOf(search),
  108. position = position - search.length;
  109. return lastIndex !== -1 && lastIndex === position;
  110. }
  111. return false;
  112. },
  113. /**
  114. * Called when a child block has been created.
  115. * The event has the following properties:
  116. *
  117. * $childBlock The child container dom element
  118. * parentId The parent block id
  119. * blockId The child block id
  120. * blockName The block name
  121. * blockType The block type
  122. *
  123. * @param event
  124. */
  125. handleBlockCreated: function (event) {
  126. var self = this;
  127. $.ajax({
  128. url: this.getRouteUrl('block_preview', { 'BLOCK_ID': event.blockId }),
  129. type: 'GET',
  130. success: function (resp) {
  131. var $content = $(resp);
  132. event.$childBlock.replaceWith($content);
  133. self.controlChildBlock($content);
  134. // refresh parent block child count
  135. var newChildCount = self.getContainerChildCountFromList(event.parentId);
  136. if (newChildCount !== null) {
  137. self.updateChildCount(event.parentId, newChildCount);
  138. }
  139. },
  140. error: function () {
  141. self.containerNotification('composer_preview_error', 'error', true);
  142. }
  143. });
  144. },
  145. /**
  146. * Remove given block.
  147. *
  148. * @param event
  149. */
  150. handleBlockRemoved: function (event) {
  151. // refresh parent block child count
  152. var newChildCount = this.getContainerChildCountFromList(event.parentId);
  153. if (newChildCount !== null) {
  154. this.updateChildCount(event.parentId, newChildCount);
  155. }
  156. },
  157. /**
  158. * Display notification for current block container.
  159. *
  160. * @param message
  161. * @param type
  162. * @param persist
  163. */
  164. containerNotification: function (message, type, persist) {
  165. var $notice = this.$dynamicArea.find('.page-composer__container__view__notice');
  166. if ($notice.length === 1) {
  167. if (this.containerNotificationTimer) {
  168. clearTimeout(this.containerNotificationTimer);
  169. }
  170. $notice.removeClass('persist success error');
  171. if (type) {
  172. $notice.addClass(type);
  173. }
  174. $notice.text(this.translate(message));
  175. $notice.show();
  176. if (persist !== true) {
  177. this.containerNotificationTimer = setTimeout(function () {
  178. $notice.hide().empty();
  179. }, 2000);
  180. } else {
  181. var $close = $('<span class="close-notice">x</span>');
  182. $close.on('click', function () {
  183. $notice.hide().empty();
  184. });
  185. $notice.addClass('persist');
  186. $notice.append($close);
  187. }
  188. }
  189. },
  190. /**
  191. * Save block positions.
  192. * event.disposition contains positions data:
  193. *
  194. * [
  195. * { id: 126, page_id: 2, parent_id: 18, position: 0 },
  196. * { id: 21, page_id: 2, parent_id: 18, position: 1 },
  197. * ...
  198. * ]
  199. *
  200. * @param event
  201. */
  202. handleBlockPositionsUpdate: function (event) {
  203. var self = this;
  204. this.containerNotification('composer_update_saving');
  205. $.ajax({
  206. url: this.getRouteUrl('save_blocks_positions'),
  207. type: 'POST',
  208. data: { disposition: event.disposition },
  209. success: function (resp) {
  210. if (resp.result && resp.result === 'ok') {
  211. self.containerNotification('composer_update_saved', 'success');
  212. }
  213. },
  214. error: function () {
  215. self.containerNotification('composer_update_error', 'error', true);
  216. }
  217. });
  218. },
  219. /**
  220. * Called when a block parent has changed (typically on drag n' drop).
  221. * The event has the following properties:
  222. *
  223. * previousParentId
  224. * newParentId
  225. * blockId
  226. *
  227. * @param event
  228. */
  229. handleBlockParentSwitched: function (event) {
  230. var $previousParentPreview = $('.block-preview-' + event.previousParentId),
  231. $oldChildCountIndicator = $previousParentPreview.find('.child-count'),
  232. oldChildCount = parseInt($oldChildCountIndicator.text().trim(), 10),
  233. $newParentPreview = $('.block-preview-' + event.newParentId),
  234. $newChildCountIndicator = $newParentPreview.find('.child-count'),
  235. newChildCount = parseInt($newChildCountIndicator.text().trim(), 10);
  236. this.updateChildCount(event.previousParentId, oldChildCount - 1);
  237. this.updateChildCount(event.newParentId, newChildCount + 1);
  238. },
  239. /**
  240. * Compute child count for the given block container id.
  241. *
  242. * @param containerId
  243. * @returns {number}
  244. */
  245. getContainerChildCountFromList: function (containerId) {
  246. var $blockView = this.$dynamicArea.find('.block-view-' + containerId);
  247. if ($blockView.length === 0) {
  248. return null;
  249. }
  250. var $children = $blockView.find('.page-composer__container__child'),
  251. childCount = 0;
  252. $children.each(function () {
  253. var $child = $(this),
  254. blockId = $child.attr('data-block-id');
  255. if (typeof blockId != 'undefined') {
  256. childCount++;
  257. }
  258. });
  259. return childCount;
  260. },
  261. /**
  262. * Update child count for the given container block id.
  263. *
  264. * @param blockId
  265. * @param count
  266. */
  267. updateChildCount: function (blockId, count) {
  268. var $previewCount = $('.block-preview-' + blockId),
  269. $viewCount = $('.block-view-' + blockId);
  270. if ($previewCount.length > 0) {
  271. $previewCount.find('.child-count').text(count);
  272. }
  273. if ($viewCount.length > 0) {
  274. $viewCount.find('.page-composer__container__child-count span').text(count);
  275. }
  276. },
  277. /**
  278. * Handler called when block creation form is received.
  279. * Makes the form handled through ajax.
  280. *
  281. * @param containerId
  282. * @param blockType
  283. */
  284. handleBlockCreateFormLoaded: function (event) {
  285. var self = this,
  286. $containerChildren = this.$dynamicArea.find('.page-composer__container__children'),
  287. $container = this.$dynamicArea.find('.page-composer__container__main-edition-area');
  288. if (event.container) {
  289. var $childBlock = event.container,
  290. $childContent = $childBlock.find('.page-composer__container__child__content');
  291. $childContent.html(event.response);
  292. } else {
  293. var $childBlock = $(['<li class="page-composer__container__child">',
  294. '<a class="page-composer__container__child__edit">',
  295. '<h4 class="page-composer__container__child__name">',
  296. '<input type="text" class="page-composer__container__child__name__input">',
  297. '</h4>',
  298. '</a>',
  299. '<div class="page-composer__container__child__right">',
  300. '<span class="badge">' + event.blockTypeLabel + '</span>',
  301. '</div>',
  302. '<div class="page-composer__container__child__content">',
  303. '</div>',
  304. '</li>'].join('')),
  305. $childContent = $childBlock.find('.page-composer__container__child__content');
  306. $childContent.append(event.response);
  307. $containerChildren.append($childBlock);
  308. $childContent.show();
  309. }
  310. var $form = $childBlock.find('form'),
  311. formAction = $form.attr('action'),
  312. formMethod = $form.attr('method'),
  313. $formControls = $form.find('input, select, textarea'),
  314. $formActions = $form.find('.form-actions'),
  315. $childName = this.$dynamicArea.find('.page-composer__container__child__name'),
  316. $nameFormControl,
  317. $parentFormControl,
  318. $positionFormControl;
  319. applyAdmin($form);
  320. $(document).scrollTo($childBlock, 200);
  321. $container.show();
  322. // scan form elements to find name/parent/position,
  323. // then set value according to current container and hide it.
  324. $formControls.each(function () {
  325. var $formControl = $(this),
  326. formControlName = $formControl.attr('name');
  327. if (self.isFormControlTypeByName(formControlName, 'name')) {
  328. $nameFormControl = $formControl;
  329. $childName.find('.page-composer__container__child__name__input').bind("propertychange keyup input paste", function (e) {
  330. $nameFormControl.val($(this).val());
  331. });
  332. } else if (self.isFormControlTypeByName(formControlName, 'parent')) {
  333. $parentFormControl = $formControl;
  334. $parentFormControl.val(event.containerId);
  335. $parentFormControl.parent().parent().hide();
  336. } else if (self.isFormControlTypeByName(formControlName, 'position')) {
  337. $positionFormControl = $formControl;
  338. $positionFormControl.val($containerChildren.find('> *').length);
  339. $positionFormControl.closest('.form-group').hide();
  340. }
  341. });
  342. $formActions.each(function () {
  343. var $formAction = $(this),
  344. $cancelButton = $('<span class="btn btn-warning">' + self.translate('cancel') + '</span>');
  345. $cancelButton.on('click', function (e) {
  346. e.preventDefault();
  347. $childBlock.remove();
  348. $(document).scrollTo(self.$dynamicArea, 200);
  349. });
  350. $formAction.append($cancelButton);
  351. });
  352. // hook into the form submit event.
  353. $form.on('submit', function (e) {
  354. e.preventDefault();
  355. var blockName = $nameFormControl.val();
  356. if (blockName === '') {
  357. blockName = event.blockType;
  358. }
  359. $.ajax({
  360. url: formAction + '&' + $.param({'composer': 1}),
  361. data: $form.serialize(),
  362. type: formMethod,
  363. success: function (resp) {
  364. if (resp.result && resp.result === 'ok' && resp.objectId) {
  365. var createdEvent = $.Event('blockcreated');
  366. createdEvent.$childBlock = $childBlock;
  367. createdEvent.parentId = event.containerId;
  368. createdEvent.blockId = resp.objectId;
  369. createdEvent.blockName = blockName;
  370. createdEvent.blockType = event.blockType;
  371. $(self).trigger(createdEvent);
  372. } else {
  373. var loadedEvent = $.Event('blockcreateformloaded');
  374. loadedEvent.response = resp;
  375. loadedEvent.containerId = event.containerId;
  376. loadedEvent.blockType = event.blockType;
  377. loadedEvent.container = $childBlock;
  378. $(self).trigger(loadedEvent);
  379. applyAdmin($childContent);
  380. }
  381. }
  382. });
  383. return false;
  384. });
  385. },
  386. /**
  387. * Toggle a child block using '--expanded' class check.
  388. *
  389. * @param $childBlock
  390. */
  391. toggleChildBlock: function ($childBlock) {
  392. var expandedClass = 'page-composer__container__child--expanded',
  393. $children = this.$dynamicArea.find('.page-composer__container__child'),
  394. $childName = $childBlock.find('.page-composer__container__child__name'),
  395. $nameInput = $childName.find('.page-composer__container__child__name__input');
  396. if ($childBlock.hasClass(expandedClass)) {
  397. $childBlock.removeClass(expandedClass);
  398. if ($childName.has('.page-composer__container__child__name__input')) {
  399. $childName.html($nameInput.val());
  400. }
  401. } else {
  402. $children.not($childBlock).removeClass(expandedClass);
  403. $childBlock.addClass(expandedClass);
  404. }
  405. },
  406. /**
  407. * Called when a block edit form has been loaded.
  408. *
  409. * @param event
  410. */
  411. handleBlockEditFormLoaded: function (event) {
  412. var self = this,
  413. $title = event.$block.find('.page-composer__container__child__edit h4'),
  414. $container = event.$block.find('.page-composer__container__child__content'),
  415. $loader = event.$block.find('.page-composer__container__child__loader'),
  416. $form = $container.find('form'),
  417. url = $form.attr('action'),
  418. method = $form.attr('method'),
  419. blockType = event.$block.find('.page-composer__container__child__edit small').text().trim(),
  420. $nameFormControl,
  421. $positionFormControl;
  422. $form.find('input').each(function () {
  423. var $formControl = $(this),
  424. formControlName = $formControl.attr('name');
  425. if (self.isFormControlTypeByName(formControlName, 'name')) {
  426. $nameFormControl = $formControl;
  427. $title.html('<input type="text" class="page-composer__container__child__name__input" value="' + $title.text() + '">');
  428. $input = $title.find('input');
  429. $input.bind("propertychange keyup input paste", function (e) {
  430. $nameFormControl.val($input.val());
  431. });
  432. $input.on('click', function (e) {
  433. e.stopPropagation();
  434. e.preventDefault();
  435. });
  436. } else if (self.isFormControlTypeByName(formControlName, 'position')) {
  437. $positionFormControl = $formControl;
  438. $positionFormControl.closest('.form-group').hide();
  439. }
  440. });
  441. $form.on('submit', function (e) {
  442. e.preventDefault();
  443. $loader.show();
  444. $.ajax({
  445. url: url,
  446. data: $form.serialize(),
  447. type: method,
  448. success: function (resp) {
  449. $loader.hide();
  450. if (resp.result && resp.result === 'ok') {
  451. if (typeof $nameFormControl != 'undefined') {
  452. $title.text($nameFormControl.val() !== '' ? $nameFormControl.val() : blockType);
  453. }
  454. event.$block.removeClass('page-composer__container__child--expanded');
  455. $container.empty();
  456. } else {
  457. $container.html(resp);
  458. var editFormEvent = $.Event('blockeditformloaded');
  459. editFormEvent.$block = event.$block;
  460. $(self).trigger(editFormEvent);
  461. applyAdmin($container);
  462. }
  463. }
  464. });
  465. return false;
  466. });
  467. },
  468. /**
  469. * Takes control of a container child block.
  470. *
  471. * @param $childBlock
  472. */
  473. controlChildBlock: function ($childBlock) {
  474. var self = this,
  475. $container = $childBlock.find('.page-composer__container__child__content'),
  476. $loader = $childBlock.find('.page-composer__container__child__loader'),
  477. $edit = $childBlock.find('.page-composer__container__child__edit'),
  478. editUrl = $edit.attr('href'),
  479. $remove = $childBlock.find('.page-composer__container__child__remove'),
  480. $removeButton = $remove.find('a'),
  481. $switchEnabled = $childBlock.find('.page-composer__container__child__switch-enabled'),
  482. $switchLblEnbl = $switchEnabled.attr('data-label-enable'),
  483. $switchLblDsbl = $switchEnabled.attr('data-label-disable'),
  484. $switchButton = $switchEnabled.find('a'),
  485. $switchBtnIcon = $switchButton.find('i'),
  486. $switchLabel = $childBlock.find('.page-composer__container__child__enabled'),
  487. $switchLblSm = $switchLabel.find('small'),
  488. $switchLblIcon = $switchLabel.find('i'),
  489. switchUrl = $switchButton.attr('href'),
  490. enabled = parseInt($childBlock.attr('data-block-enabled'), 2);
  491. $edit.click(function (e) {
  492. e.preventDefault();
  493. // edit form already loaded, just toggle
  494. if ($container.find('form').length > 0) {
  495. self.toggleChildBlock($childBlock);
  496. return;
  497. }
  498. // load edit form, then toggle
  499. $loader.show();
  500. $.ajax({
  501. url: editUrl,
  502. success: function (resp) {
  503. $container.html(resp);
  504. var editFormEvent = $.Event('blockeditformloaded');
  505. editFormEvent.$block = $childBlock;
  506. $(self).trigger(editFormEvent);
  507. applyAdmin($container);
  508. $loader.hide();
  509. self.toggleChildBlock($childBlock);
  510. }
  511. });
  512. });
  513. $switchButton.on('click', function (e) {
  514. e.preventDefault();
  515. $.ajax({
  516. url: switchUrl,
  517. type: 'POST',
  518. data: {
  519. '_sonata_csrf_token': self.csrfTokens.switchEnabled,
  520. 'value': !enabled
  521. },
  522. success: function (resp) {
  523. if (resp.status && resp.status === 'OK') {
  524. $childBlock.attr('data-block-enabled', enabled ? "0" : "1");
  525. enabled = !enabled;
  526. $switchButton.toggleClass('bg-yellow bg-green');
  527. $switchBtnIcon.toggleClass('fa-toggle-off fa-toggle-on');
  528. if (enabled) {
  529. $switchButton.html($switchLblDsbl);
  530. } else {
  531. $switchButton.html($switchLblEnbl);
  532. }
  533. $switchLblSm.toggleClass('bg-yellow bg-green');
  534. $switchLblIcon.toggleClass('fa-times fa-check');
  535. if ($childBlock.has('form')) {
  536. var $form = $childBlock.find('form'),
  537. $inputs = $form.find('input');
  538. $inputs.each(function () {
  539. var $formControl = $(this),
  540. formControlName = $formControl.attr('name');
  541. if (self.isFormControlTypeByName(formControlName, 'enabled')) {
  542. $formControl.val(parseInt(!enabled));
  543. }
  544. });
  545. }
  546. } else {
  547. self.containerNotification('composer_status_error', 'error', true);
  548. }
  549. },
  550. error: function () {
  551. self.containerNotification('composer_status_error', 'error', true);
  552. }
  553. });
  554. });
  555. $removeButton.on('click', function (e) {
  556. e.preventDefault();
  557. self.confirmRemoveContainer($childBlock);
  558. });
  559. },
  560. /**
  561. * Shows a confirm dialog for a child removal request
  562. *
  563. * @param $childBlock
  564. */
  565. confirmRemoveContainer: function($childBlock) {
  566. var self = this,
  567. $remove = $childBlock.find('.page-composer__container__child__remove'),
  568. $removeButton = $remove.find('a'),
  569. $removeDialog = $childBlock.find('.page-composer__container__child__remove__dialog'),
  570. removeUrl = $removeButton.attr('href'),
  571. parentId = parseInt($childBlock.attr('data-parent-block-id'), 10);
  572. if ($removeDialog.length == 0) {
  573. $removeDialog = $(['<div class="modal fade page-composer__container__child__remove__dialog" tabindex="-1" role="dialog">',
  574. '<div class="modal-dialog" role="document">',
  575. '<div class="modal-content">',
  576. '<div class="modal-header">',
  577. '<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>',
  578. '<h4 class="modal-title">' + this.translate('composer_remove_confirm') + '</h4>',
  579. '</div>',
  580. '<div class="modal-body">',
  581. '</div>',
  582. '<div class="modal-footer">',
  583. '<button type="button" class="btn btn-default" data-dismiss="modal">' + this.translate('cancel') + '</button>',
  584. '<button type="button" class="btn btn-primary">' + this.translate('yes') + '</button>',
  585. '</div>',
  586. '</div>',
  587. '</div>',
  588. '</div>'].join(''));
  589. $childBlock.append($removeDialog);
  590. }
  591. var $removeYes = $removeDialog.find('.btn-primary');
  592. $removeYes.on('click', function (e) {
  593. $.ajax({
  594. url: removeUrl,
  595. type: 'POST',
  596. data: {
  597. '_method': 'DELETE',
  598. '_sonata_csrf_token': self.csrfTokens.remove
  599. },
  600. success: function (resp) {
  601. if (resp.result && resp.result === 'ok') {
  602. $childBlock.remove();
  603. var removedEvent = $.Event('blockremoved');
  604. removedEvent.parentId = parentId;
  605. $(self).trigger(removedEvent);
  606. }
  607. }
  608. });
  609. $removeDialog.modal('hide');
  610. // Sometimes, there is a bug and the modal backdrop didn't fade out, so we have to force it
  611. if ($('.modal-backdrop').length != 0) {
  612. $('.modal-backdrop').hide();
  613. }
  614. });
  615. $removeDialog.modal('show')
  616. },
  617. /**
  618. * Handler called when a container block has been loaded.
  619. *
  620. * @param event
  621. */
  622. handleContainerLoaded: function (event) {
  623. var self = this,
  624. $childrenContainer = this.$dynamicArea.find('.page-composer__container__children'),
  625. $children = this.$dynamicArea.find('.page-composer__container__child'),
  626. $blockTypeSelector = this.$dynamicArea.find('.page-composer__block-type-selector'),
  627. $blockTypeSelectorLoader = $blockTypeSelector.find('.page-composer__block-type-selector__loader'),
  628. $blockTypeSelectorSelect = $blockTypeSelector.find('select'),
  629. $blockTypeSelectorButton = $blockTypeSelector.find('.page-composer__block-type-selector__confirm'),
  630. blockTypeSelectorUrl = $blockTypeSelectorButton.attr('href');
  631. applyAdmin(this.$dynamicArea);
  632. // Load the block creation form trough ajax.
  633. $blockTypeSelectorButton.on('click', function (e) {
  634. e.preventDefault();
  635. $blockTypeSelectorLoader.css('display', 'inline-block');
  636. var blockType = $blockTypeSelectorSelect.val(),
  637. blockTypeLabel = $blockTypeSelectorSelect.find('option:selected').text();
  638. $.ajax({
  639. url: blockTypeSelectorUrl,
  640. data: {
  641. type: blockType
  642. },
  643. success: function (resp) {
  644. $blockTypeSelectorLoader.hide();
  645. $(self).trigger($.Event('blockcreateformloaded', {
  646. response: resp,
  647. containerId: event.containerId,
  648. blockType: blockType,
  649. blockTypeLabel: blockTypeLabel
  650. }));
  651. }
  652. });
  653. });
  654. // makes the container block children sortables.
  655. $childrenContainer.sortable({
  656. revert: true,
  657. cursor: 'move',
  658. revertDuration: 200,
  659. delay: 200,
  660. helper: function (event, element) {
  661. var $element = $(element),
  662. name = $element.find('.page-composer__container__child__edit h4').text().trim(),
  663. type = $element.find('.page-composer__container__child__edit small').text().trim();
  664. $element.removeClass('page-composer__container__child--expanded');
  665. return $('<div class="page-composer__container__child__helper">' +
  666. '<h4>' + name + '</h4>' +
  667. '</div>');
  668. },
  669. update: function (event, ui) {
  670. var newPositions = [];
  671. $childrenContainer.find('.page-composer__container__child').each(function (position) {
  672. var $child = $(this),
  673. parentId = $child.attr('data-parent-block-id'),
  674. childId = $child.attr('data-block-id');
  675. // pending block creation has an undefined child id
  676. if (typeof childId != 'undefined') {
  677. newPositions.push({
  678. 'id': parseInt(childId, 10),
  679. 'position': position,
  680. 'parent_id': parseInt(parentId, 10),
  681. 'page_id': self.pageId
  682. });
  683. }
  684. });
  685. if (newPositions.length > 0) {
  686. var updateEvent = $.Event('blockpositionsupdate');
  687. updateEvent.disposition = newPositions;
  688. $(self).trigger(updateEvent);
  689. }
  690. }
  691. });
  692. $children
  693. .each(function () {
  694. self.controlChildBlock($(this));
  695. });
  696. },
  697. /**
  698. * Bind click handlers to template layout preview blocks.
  699. */
  700. bindPagePreviewHandlers: function () {
  701. var self = this;
  702. this.$containerPreviews
  703. .each(function () {
  704. var $container = $(this);
  705. $container.on('click', function (e) {
  706. e.preventDefault();
  707. var event = $.Event('containerclick');
  708. event.$container = $container;
  709. $(self).trigger(event);
  710. });
  711. })
  712. .droppable({
  713. hoverClass: 'hover',
  714. tolerance: 'pointer',
  715. revert: true,
  716. connectToSortable: '.page-composer__container__children',
  717. accept: function (source) {
  718. var blockWhitelist = $(this).attr('data-block-whitelist');
  719. if (blockWhitelist === '') {
  720. return true;
  721. }
  722. blockWhitelist = blockWhitelist.split(',');
  723. var sourceBlockType = $(source).attr('data-block-type');
  724. return blockWhitelist.indexOf(sourceBlockType) !== -1;
  725. },
  726. drop: function (event, ui) {
  727. var droppedBlockId = ui.draggable.attr('data-block-id');
  728. if (typeof droppedBlockId != 'undefined') {
  729. ui.helper.remove();
  730. var $container = $(this),
  731. parentId = parseInt(ui.draggable.attr('data-parent-block-id'), 10),
  732. containerId = parseInt($container.attr('data-block-id'), 10);
  733. droppedBlockId = parseInt(droppedBlockId, 10);
  734. if (parentId !== containerId) {
  735. // play animation on drop, remove class on animation end to be able to re-apply
  736. $container.addClass('dropped');
  737. $container.on('webkitAnimationEnd oanimationend msAnimationEnd animationend', function (e) {
  738. $container.removeClass('dropped');
  739. });
  740. $.ajax({
  741. url: self.getRouteUrl('block_switch_parent'),
  742. data: {
  743. block_id: droppedBlockId,
  744. parent_id: containerId
  745. },
  746. success: function (resp) {
  747. if (resp.result && resp.result === 'ok') {
  748. ui.draggable.remove();
  749. var switchedEvent = $.Event('blockparentswitched');
  750. switchedEvent.previousParentId = parentId;
  751. switchedEvent.newParentId = containerId;
  752. switchedEvent.blockId = droppedBlockId;
  753. $(self).trigger(switchedEvent);
  754. }
  755. }
  756. });
  757. }
  758. }
  759. }
  760. })
  761. ;
  762. if (this.$containerPreviews.length > 0) {
  763. this.loadContainer(this.$containerPreviews.eq(0));
  764. }
  765. },
  766. bindOrphansHandlers: function () {
  767. var self = this;
  768. this.$container.find('.page-composer__orphan-container').each(function () {
  769. var $container = $(this);
  770. $container.on('click', function (e) {
  771. e.preventDefault();
  772. var event = $.Event('containerclick');
  773. event.$container = $container;
  774. $(self).trigger(event);
  775. });
  776. });
  777. },
  778. /**
  779. * Loads the container detailed view trough ajax.
  780. *
  781. * @param $container
  782. */
  783. loadContainer: function ($container) {
  784. var url = $container.attr('href'),
  785. containerId = $container.attr('data-block-id'),
  786. self = this;
  787. this.$dynamicArea.empty();
  788. this.$containerPreviews.removeClass('active');
  789. this.$container.find('.page-composer__orphan-container').removeClass('active');
  790. $container.addClass('active');
  791. $.ajax({
  792. url: url,
  793. success: function (resp) {
  794. self.$dynamicArea.html(resp);
  795. $(document).scrollTo(self.$dynamicArea, 200, {
  796. offset: { top: -100 }
  797. });
  798. var event = $.Event('containerloaded');
  799. event.containerId = containerId;
  800. $(self).trigger(event);
  801. }
  802. });
  803. }
  804. };
  805. global.PageComposer = PageComposer;
  806. // auto-initialize plugin
  807. $(function () {
  808. $('[data-page-composer]').each(function () {
  809. var attr = $(this).data('page-composer');
  810. new PageComposer(attr.pageId, attr);
  811. });
  812. });
  813. })(jQuery, window);
  814. /**
  815. *
  816. * This file is part of the Sonata package.
  817. *
  818. * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
  819. *
  820. * For the full copyright and license information, please view the LICENSE
  821. * file that was distributed with this source code.
  822. *
  823. */
  824. ;(function ( $, window, document, undefined ) {
  825. var pluginName = 'treeView',
  826. defaultRegistry = '.js-treeview',
  827. defaults = {
  828. togglersAttribute: '[data-treeview-toggler]',
  829. toggledState: 'is-toggled'
  830. };
  831. function TreeView( element, options ) {
  832. this.element = element;
  833. this.options = $.extend({}, defaults, options) ;
  834. this._defaults = defaults;
  835. this._name = pluginName;
  836. this.init();
  837. }
  838. TreeView.prototype = {
  839. /**
  840. * Constructor
  841. */
  842. init: function() {
  843. this.setElements();
  844. this.setEvents();
  845. },
  846. /**
  847. * Cache DOM elements to limit DOM parsing
  848. */
  849. setElements: function() {
  850. this.$element = $(this.element);
  851. this.$togglers = this.$element.find(this.options.togglersAttribute);
  852. },
  853. /**
  854. * Set events and delegates
  855. */
  856. setEvents: function() {
  857. this.$togglers.on('click', $.proxy(this.toggle, this));
  858. },
  859. /**
  860. * Toggle an item
  861. */
  862. toggle: function(ev) {
  863. var $target = $(ev.currentTarget),
  864. $parent = $target.parent();
  865. $parent.toggleClass(this.options.toggledState);
  866. $parent.next('ul').slideToggle();
  867. }
  868. };
  869. // A really lightweight plugin wrapper around the constructor,
  870. // preventing against multiple instantiations
  871. $.fn[pluginName] = function ( options ) {
  872. return this.each(function () {
  873. if (!$.data(this, 'plugin_' + pluginName)) {
  874. $.data(this, 'plugin_' + pluginName, new TreeView(this, options));
  875. }
  876. });
  877. };
  878. // Default standard registry
  879. $(function() {
  880. $(defaultRegistry)[pluginName]();
  881. });
  882. })( jQuery, window, document );