display_editor.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. /**
  2. * @file display_editor.js
  3. *
  4. * Contains the javascript for the Panels display editor.
  5. */
  6. (function ($) {
  7. // randomly lock a pane.
  8. // @debug only
  9. Drupal.settings.Panels = Drupal.settings.Panels || {};
  10. /** Delete pane button **/
  11. Drupal.Panels.bindClickDelete = function(context) {
  12. $('a.pane-delete:not(.pane-delete-processed)', context)
  13. .addClass('pane-delete-processed')
  14. .click(function() {
  15. if (confirm(Drupal.t('Remove this pane?'))) {
  16. var id = '#' + $(this).attr('id').replace('pane-delete-', '');
  17. $(id).remove();
  18. Drupal.Panels.Draggable.savePositions();
  19. }
  20. return false;
  21. });
  22. };
  23. Drupal.Panels.bindPortlet = function() {
  24. var handle = $(this).find('.panel-pane-collapsible > div.pane-title');
  25. var content = $(this).find('.panel-pane-collapsible > div.pane-content');
  26. if (content.length) {
  27. var toggle = $('<span class="toggle toggle-collapsed"></span>');
  28. handle.before(toggle);
  29. toggle.click(function() {
  30. content.slideToggle(20);
  31. toggle.toggleClass('toggle-collapsed');
  32. });
  33. handle.click(function() {
  34. content.slideToggle(20);
  35. toggle.toggleClass('toggle-collapsed');
  36. });
  37. content.hide();
  38. }
  39. };
  40. Drupal.Panels.Draggable = {
  41. // The draggable object
  42. object: null,
  43. // Where objects can be dropped
  44. dropzones: [],
  45. current_dropzone: null,
  46. // positions within dropzones where an object can be plazed
  47. landing_pads: [],
  48. current_pad: null,
  49. // Where the object is
  50. mouseOffset: { x: 0, y: 0 },
  51. windowOffset: { x: 0, y: 0 },
  52. offsetDivHeight: 0,
  53. // original settings to be restored
  54. original: {},
  55. // a placeholder so that if the object is let go but not over a drop zone,
  56. // it can be put back where it belongs
  57. placeholder: {},
  58. hoverclass: 'hoverclass',
  59. helperclass: 'helperclass',
  60. accept: 'div.panel-region',
  61. handle: 'div.grabber',
  62. draggable: 'div.panel-portlet',
  63. main: 'div#panels-dnd-main',
  64. // part of the id to remove to get just the number
  65. draggableId: 'panel-pane-',
  66. // part of the id to remove to get just the number
  67. regionId: 'panel-region-',
  68. // What to add to the front of a the id to get the form id for a panel
  69. formId: '#edit-',
  70. maxWidth: 250,
  71. unsetDropZone: function() {
  72. $(this.current_dropzone.obj).removeClass(this.hoverclass);
  73. this.current_dropzone = null;
  74. for (var i in this.landing_pads) {
  75. $(this.landing_pads[i].obj).remove();
  76. }
  77. this.landing_pads = [];
  78. this.current_pad = null;
  79. },
  80. createLandingPad: function(where, append) {
  81. var obj = $('<div class="' + this.helperclass +'" id="' +
  82. $(where).attr('id') + '-dropzone">&nbsp;</div>');
  83. if (append) {
  84. $(where).append(obj);
  85. }
  86. else {
  87. $(where).before(obj);
  88. }
  89. var offset = $(obj).offset();
  90. $(obj).css({
  91. display: 'none'
  92. });
  93. this.landing_pads.push({
  94. centerX: offset.left + ($(obj).innerWidth() / 2),
  95. centerY: offset.top + ($(obj).innerHeight() / 2),
  96. obj: obj
  97. });
  98. return obj;
  99. },
  100. calculateDropZones: function(event, dropzone) {
  101. var draggable = Drupal.Panels.Draggable;
  102. var dropzones = [];
  103. $(this.accept).each(function() {
  104. var offset = $(this).offset();
  105. offset.obj = this;
  106. offset.region = this.id.replace(draggable.regionId, '');
  107. offset.width = $(this).outerWidth();
  108. offset.height = $(this).outerHeight();
  109. dropzones.push(offset);
  110. });
  111. this.dropzones = dropzones;
  112. },
  113. reCalculateDropZones: function() {
  114. for (var i in this.dropzones) {
  115. var offset = $(this.dropzones[i].obj).offset();
  116. offset.width = $(this.dropzones[i].obj).outerWidth();
  117. offset.height = $(this.dropzones[i].obj).outerHeight();
  118. $.extend(this.dropzones[i], offset);
  119. }
  120. },
  121. changeDropZone: function(new_dropzone) {
  122. // Unset our old dropzone.
  123. if (this.current_dropzone) {
  124. this.unsetDropZone();
  125. }
  126. // Set up our new dropzone.
  127. this.current_dropzone = new_dropzone;
  128. $(this.current_dropzone.obj).addClass(this.hoverclass);
  129. // add a landing pad
  130. this.createLandingPad(this.current_dropzone.obj, true);
  131. var that = this;
  132. // Create a landing pad before each existing portlet.
  133. $(this.current_dropzone.obj).find(this.draggable).each(function() {
  134. if (that.object.id != this.id) {
  135. that.createLandingPad(this, false);
  136. }
  137. });
  138. },
  139. findLandingPad: function(x, y) {
  140. var shortest_distance = null;
  141. var nearest_pad = null;
  142. // find the nearest pad.
  143. for (var i in this.landing_pads) {
  144. // This isn't the real distance, this is the square of the
  145. // distance -- no point in spending processing time on
  146. // sqrt.
  147. var dstx = Math.abs(x - this.landing_pads[i].centerX);
  148. var dsty = Math.abs(y - this.landing_pads[i].centerY);
  149. var distance = (dstx * dstx) + (dsty * dsty);
  150. if (shortest_distance == null || distance < shortest_distance) {
  151. shortest_distance = distance;
  152. nearest_pad = this.landing_pads[i];
  153. }
  154. }
  155. if (nearest_pad != this.current_pad) {
  156. if (this.current_pad) {
  157. $(this.current_pad.obj).hide();
  158. }
  159. this.current_pad = nearest_pad;
  160. $(nearest_pad.obj).show();
  161. }
  162. },
  163. findDropZone: function(x, y) {
  164. // Go through our dropzones and see if we're over one.
  165. var new_dropzone = null;
  166. for (var i in this.dropzones) {
  167. // console.log('x:' + x + ' left:' + this.dropzones[i].left + ' right: ' + this.dropzones[i].left + this.dropzones[i].width);
  168. if (this.dropzones[i].left < x &&
  169. x < this.dropzones[i].left + this.dropzones[i].width &&
  170. this.dropzones[i].top < y &&
  171. y < this.dropzones[i].top + this.dropzones[i].height) {
  172. new_dropzone = this.dropzones[i];
  173. break;
  174. }
  175. }
  176. // If we're over one, see if it's different.
  177. if (new_dropzone && (!this.regionLock || this.regionLockRegions[new_dropzone.region])) {
  178. var changed = false;
  179. if (!this.current_dropzone || new_dropzone.obj.id != this.current_dropzone.obj.id) {
  180. this.changeDropZone(new_dropzone);
  181. changed = true;
  182. }
  183. this.findLandingPad(x, y);
  184. if (changed) {
  185. // recalculate the size of our drop zones due to the fact that we're drawing landing pads.
  186. this.reCalculateDropZones();
  187. }
  188. }
  189. // If we're not over one, be sure to unhilite one if we were just
  190. // over it.
  191. else if (this.current_dropzone) {
  192. this.unsetDropZone();
  193. }
  194. },
  195. /** save button clicked, or pane deleted **/
  196. savePositions: function() {
  197. var draggable = Drupal.Panels.Draggable;
  198. $(draggable.accept).each(function() {
  199. var val = '';
  200. $(this).find(draggable.draggable).each(function() {
  201. if (val) {
  202. val += ',';
  203. }
  204. val += this.id.replace(draggable.draggableId, '');
  205. });
  206. var region = this.id.replace(draggable.regionId, '');
  207. $('input[name="panel[pane][' + region + ']"]').val(val);
  208. });
  209. return false;
  210. }
  211. };
  212. Drupal.Panels.DraggableHandler = function() {
  213. $(this).addClass('panel-draggable');
  214. var draggable = Drupal.Panels.Draggable;
  215. var scrollBuffer = 10;
  216. var scrollDistance = 10;
  217. var scrollTimer = 30;
  218. getMouseOffset = function(docPos, mousePos, windowPos) {
  219. return { x: mousePos.x - docPos.x + windowPos.x, y: mousePos.y - docPos.y + windowPos.y};
  220. };
  221. getMousePos = function(ev) {
  222. ev = ev || window.event;
  223. if (ev.pageX || ev.pageY) {
  224. return { x:ev.pageX, y:ev.pageY };
  225. }
  226. return {
  227. x:ev.clientX + document.body.scrollLeft - document.body.clientLeft,
  228. y:ev.clientY + document.body.scrollTop - document.body.clientTop
  229. };
  230. };
  231. getPosition = function(e) {
  232. /*
  233. if (document.defaultView && document.defaultView.getComputedStyle) {
  234. var css = document.defaultView.getComputedStyle(e, null);
  235. return {
  236. x: parseInt(css.getPropertyValue('left')),
  237. y: parseInt(css.getPropertyValue('top'))
  238. };
  239. }
  240. */
  241. var left = 0;
  242. var top = 0;
  243. while (e.offsetParent) {
  244. left += e.offsetLeft;
  245. top += e.offsetTop;
  246. e = e.offsetParent;
  247. }
  248. left += e.offsetLeft;
  249. top += e.offsetTop;
  250. return { x:left, y:top };
  251. };
  252. mouseUp = function(e) {
  253. clearTimeout(draggable.timeoutId);
  254. draggable.dropzones = [];
  255. if (draggable.current_pad) {
  256. // Drop the object where we're hovering
  257. $(draggable.object).insertAfter($(draggable.current_pad.obj));
  258. Drupal.Panels.changed($(draggable.object));
  259. }
  260. else {
  261. // or put it back where it came from
  262. $(draggable.object).insertAfter(draggable.placeholder);
  263. }
  264. // remove the placeholder
  265. draggable.placeholder.remove();
  266. // restore original settings.
  267. $(draggable.object).css(draggable.original);
  268. if (draggable.current_dropzone) {
  269. draggable.unsetDropZone();
  270. }
  271. $(document).unbind('mouseup').unbind('mousemove');
  272. draggable.savePositions();
  273. };
  274. mouseMove = function(e) {
  275. draggable.mousePos = getMousePos(e);
  276. draggable.findDropZone(draggable.mousePos.x, draggable.mousePos.y);
  277. var windowMoved = parseInt(draggable.offsetDivHeight - $(draggable.main).innerHeight());
  278. draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + windowMoved + 'px';
  279. draggable.object.style.left = draggable.mousePos.x - draggable.mouseOffset.x + 'px';
  280. $(draggable.object).toggleClass('moving');
  281. };
  282. mouseDown = function(e) {
  283. // If we mouse-downed over something clickable, don't drag!
  284. if (e.target.nodeName == 'A' || e.target.nodeName == 'INPUT' || e.target.parentNode.nodeName == 'A' || e.target.nodeName.nodeName == 'INPUT') {
  285. return;
  286. }
  287. draggable.object = $(this).parent(draggable.draggable).get(0);
  288. draggable.paneId = draggable.object.id.replace(draggable.draggableId, '');
  289. // create a placeholder so we can put this object back if dropped in an invalid location.
  290. draggable.placeholder = $('<div class="draggable-placeholder-object" style="display:none"></div>"');
  291. $(draggable.object).after(draggable.placeholder);
  292. // Store original CSS so we can put it back.
  293. draggable.original = {
  294. position: $(draggable.object).css('position'),
  295. width: 'auto',
  296. left: $(draggable.object).css('left'),
  297. top: $(draggable.object).css('top'),
  298. 'z-index': $(draggable.object).css('z-index'),
  299. 'margin-bottom': $(draggable.object).css('margin-bottom'),
  300. 'margin-top': $(draggable.object).css('margin-top'),
  301. 'margin-left': $(draggable.object).css('margin-left'),
  302. 'margin-right': $(draggable.object).css('margin-right'),
  303. 'padding-bottom': $(draggable.object).css('padding-bottom'),
  304. 'padding-top': $(draggable.object).css('padding-top'),
  305. 'padding-left': $(draggable.object).css('padding-left'),
  306. 'padding-right': $(draggable.object).css('padding-right')
  307. };
  308. draggable.mousePos = getMousePos(e);
  309. var originalPos = $(draggable.object).offset();
  310. var width = Math.min($(draggable.object).innerWidth(), draggable.maxWidth);
  311. draggable.offsetDivHeight = $(draggable.main).innerHeight();
  312. draggable.findDropZone(draggable.mousePos.x, draggable.mousePos.y);
  313. // Make copies of these because in FF3, they actually change when we
  314. // move the item, whereas they did not in FF2.
  315. if (e.layerX || e.layerY) {
  316. var layerX = e.layerX;
  317. var layerY = e.layerY;
  318. }
  319. else if (e.originalEvent && e.originalEvent.layerX) {
  320. var layerX = e.originalEvent.layerX;
  321. var layerY = e.originalEvent.layerY;
  322. }
  323. // Make the draggable relative, get it out of the way and make it
  324. // invisible.
  325. $(draggable.object).css({
  326. position: 'relative',
  327. 'z-index': 100,
  328. width: width + 'px',
  329. 'margin-bottom': (-1 * parseInt($(draggable.object).outerHeight())) + 'px',
  330. 'margin-top': 0,
  331. 'margin-left': 0,
  332. 'margin-right': (-1 * parseInt($(draggable.object).outerWidth())) + 'px',
  333. 'padding-bottom': 0,
  334. 'padding-top': 0,
  335. 'padding-left': 0,
  336. 'padding-right': 0,
  337. 'left': 0,
  338. 'top': 0
  339. })
  340. .insertAfter($(draggable.main));
  341. var newPos = $(draggable.object).offset();
  342. var windowOffset = { left: originalPos.left - newPos.left, top: originalPos.top - newPos.top }
  343. // if they grabbed outside the area where we make the draggable smaller, move it
  344. // closer to the cursor.
  345. if (layerX != 'undefined' && layerX > width) {
  346. windowOffset.left += layerX - 10;
  347. }
  348. else if (layerX != 'undefined' && e.offsetX > width) {
  349. windowOffset.left += e.offsetX - 10;
  350. }
  351. // This is stored so we can move with it.
  352. draggable.mouseOffset = { x: draggable.mousePos.x - windowOffset.left, y: draggable.mousePos.y - windowOffset.top};
  353. draggable.offsetDivHeight = $(draggable.main).innerHeight();
  354. draggable.object.style.top = windowOffset.top + 'px';
  355. draggable.object.style.left = windowOffset.left + 'px';
  356. $(document).unbind('mouseup').unbind('mousemove').mouseup(mouseUp).mousemove(mouseMove);
  357. draggable.calculateDropZones(draggable.mousePos, e);
  358. draggable.timeoutId = setTimeout('timer()', scrollTimer);
  359. // If locking to a particular set of regions, set that:
  360. if (Drupal.settings.Panels && Drupal.settings.Panels.RegionLock && Drupal.settings.Panels.RegionLock[draggable.paneId]) {
  361. draggable.regionLock = true;
  362. draggable.regionLockRegions = Drupal.settings.Panels.RegionLock[draggable.paneId];
  363. }
  364. else {
  365. draggable.regionLock = false;
  366. draggable.regionLockRegions = null;
  367. }
  368. return false;
  369. };
  370. timer = function() {
  371. if (!draggable.timeCount) {
  372. draggable.timeCount = 0;
  373. }
  374. draggable.timeCount = draggable.timeCount + 1;
  375. var left = $(window).scrollLeft();
  376. var right = left + $(window).width();
  377. var top = $(window).scrollTop();
  378. var bottom = top + $(window).height();
  379. if (draggable.mousePos.x < left + scrollBuffer && left > 0) {
  380. window.scrollTo(left - scrollDistance, top);
  381. draggable.mousePos.x -= scrollDistance;
  382. draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + 'px';
  383. }
  384. else if (draggable.mousePos.x > right - scrollBuffer) {
  385. window.scrollTo(left + scrollDistance, top);
  386. draggable.mousePos.x += scrollDistance;
  387. draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + 'px';
  388. }
  389. else if (draggable.mousePos.y < top + scrollBuffer && top > 0) {
  390. window.scrollTo(left, top - scrollDistance);
  391. draggable.mousePos.y -= scrollDistance;
  392. draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + 'px';
  393. }
  394. else if (draggable.mousePos.y > bottom - scrollBuffer) {
  395. window.scrollTo(left, top + scrollDistance);
  396. draggable.mousePos.y += scrollDistance;
  397. draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + 'px';
  398. }
  399. draggable.timeoutId = setTimeout('timer()', scrollTimer);
  400. }
  401. $(this).mousedown(mouseDown);
  402. };
  403. $.fn.extend({
  404. panelsDraggable: Drupal.Panels.DraggableHandler
  405. });
  406. /**
  407. * Implement Drupal behavior for autoattach
  408. */
  409. Drupal.behaviors.PanelsDisplayEditor = {
  410. attach: function(context) {
  411. // Show javascript only items.
  412. $('span#panels-js-only').css('display', 'inline');
  413. $('#panels-dnd-main div.panel-pane:not(.panel-portlet)')
  414. .addClass('panel-portlet')
  415. .each(Drupal.Panels.bindPortlet);
  416. // The above doesn't work if context IS the pane, so do this to catch that.
  417. if ($(context).hasClass('panel-pane') && !$(context).hasClass('panel-portlet')) {
  418. $(context)
  419. .addClass('panel-portlet')
  420. .each(Drupal.Panels.bindPortlet);
  421. }
  422. // Make draggables and make sure their positions are saved.
  423. $(context).find('div.grabber:not(.panel-draggable)').panelsDraggable();
  424. Drupal.Panels.Draggable.savePositions();
  425. // Bind buttons.
  426. $('#panels-hide-all', context).click(Drupal.Panels.clickHideAll);
  427. $('#panels-show-all', context).click(Drupal.Panels.clickShowAll);
  428. Drupal.Panels.bindClickDelete(context);
  429. $('#panels-live-preview-button:not(.panels-preview-processed)')
  430. .addClass('panels-preview-processed')
  431. .click(function () {
  432. if (!$('#panels-preview').size()) {
  433. $('#panels-dnd-main').parents('form').after('<div id="panels-preview" class="clearfix"></div>');
  434. }
  435. var html = '';
  436. html += ' <div id="modal-throbber">';
  437. html += ' <div class="modal-throbber-wrapper">';
  438. html += Drupal.settings.CToolsModal.throbber;
  439. html += ' </div>';
  440. html += ' </div>';
  441. $('#panels-preview').html(html);
  442. });
  443. // Bind modal detach behaviors to cancel current form.
  444. $(document).bind('CToolsDetachBehaviors', function(event, context) {
  445. $('#edit-cancel-style', context).trigger('click');
  446. });
  447. var setTitleClass = function () {
  448. if ($('#edit-display-title-hide-title').val() == 2) {
  449. $('#panels-dnd-main').removeClass('panels-set-title-hide');
  450. }
  451. else {
  452. $('#panels-dnd-main').addClass('panels-set-title-hide');
  453. }
  454. }
  455. // The panes have an option to set the display title, but only if
  456. // a select is set to the proper value. This sets a class on the
  457. // main edit div so that the option to set the display title
  458. // is hidden if that is not selected, and visible if it is.
  459. $('#edit-display-title-hide-title:not(.panels-title-processed)')
  460. .addClass('panels-title-processed')
  461. .change(setTitleClass);
  462. setTitleClass();
  463. }
  464. }
  465. $(function() {
  466. /**
  467. * AJAX responder command to render the preview.
  468. */
  469. Drupal.ajax.prototype.commands.panel_preview = function(ajax, command, status) {
  470. $('#panels-preview').html(command.output);
  471. }
  472. });
  473. })(jQuery);