/** * @file display_editor.js * * Contains the javascript for the Panels display editor. */ (function ($) { // randomly lock a pane. // @debug only Drupal.settings.Panels = Drupal.settings.Panels || {}; /** Delete pane button **/ Drupal.Panels.bindClickDelete = function(context) { $('a.pane-delete:not(.pane-delete-processed)', context) .addClass('pane-delete-processed') .click(function() { if (confirm(Drupal.t('Remove this pane?'))) { var id = '#' + $(this).attr('id').replace('pane-delete-', ''); $(id).remove(); Drupal.Panels.Draggable.savePositions(); } return false; }); }; Drupal.Panels.bindPortlet = function() { var handle = $(this).find('.panel-pane-collapsible > div.pane-title'); var content = $(this).find('.panel-pane-collapsible > div.pane-content'); if (content.length) { var toggle = $(''); handle.before(toggle); toggle.click(function() { content.slideToggle(20); toggle.toggleClass('toggle-collapsed'); }); handle.click(function() { content.slideToggle(20); toggle.toggleClass('toggle-collapsed'); }); content.hide(); } }; Drupal.Panels.Draggable = { // The draggable object object: null, // Where objects can be dropped dropzones: [], current_dropzone: null, // positions within dropzones where an object can be plazed landing_pads: [], current_pad: null, // Where the object is mouseOffset: { x: 0, y: 0 }, windowOffset: { x: 0, y: 0 }, offsetDivHeight: 0, // original settings to be restored original: {}, // a placeholder so that if the object is let go but not over a drop zone, // it can be put back where it belongs placeholder: {}, hoverclass: 'hoverclass', helperclass: 'helperclass', accept: 'div.panel-region', handle: 'div.grabber', draggable: 'div.panel-portlet', main: 'div#panels-dnd-main', // part of the id to remove to get just the number draggableId: 'panel-pane-', // part of the id to remove to get just the number regionId: 'panel-region-', // What to add to the front of a the id to get the form id for a panel formId: '#edit-', maxWidth: 250, unsetDropZone: function() { $(this.current_dropzone.obj).removeClass(this.hoverclass); this.current_dropzone = null; for (var i in this.landing_pads) { $(this.landing_pads[i].obj).remove(); } this.landing_pads = []; this.current_pad = null; }, createLandingPad: function(where, append) { var obj = $('
 
'); if (append) { $(where).append(obj); } else { $(where).before(obj); } var offset = $(obj).offset(); $(obj).css({ display: 'none' }); this.landing_pads.push({ centerX: offset.left + ($(obj).innerWidth() / 2), centerY: offset.top + ($(obj).innerHeight() / 2), obj: obj }); return obj; }, calculateDropZones: function(event, dropzone) { var draggable = Drupal.Panels.Draggable; var dropzones = []; $(this.accept).each(function() { var offset = $(this).offset(); offset.obj = this; offset.region = this.id.replace(draggable.regionId, ''); offset.width = $(this).outerWidth(); offset.height = $(this).outerHeight(); dropzones.push(offset); }); this.dropzones = dropzones; }, reCalculateDropZones: function() { for (var i in this.dropzones) { var offset = $(this.dropzones[i].obj).offset(); offset.width = $(this.dropzones[i].obj).outerWidth(); offset.height = $(this.dropzones[i].obj).outerHeight(); $.extend(this.dropzones[i], offset); } }, changeDropZone: function(new_dropzone) { // Unset our old dropzone. if (this.current_dropzone) { this.unsetDropZone(); } // Set up our new dropzone. this.current_dropzone = new_dropzone; $(this.current_dropzone.obj).addClass(this.hoverclass); // add a landing pad this.createLandingPad(this.current_dropzone.obj, true); var that = this; // Create a landing pad before each existing portlet. $(this.current_dropzone.obj).find(this.draggable).each(function() { if (that.object.id != this.id) { that.createLandingPad(this, false); } }); }, findLandingPad: function(x, y) { var shortest_distance = null; var nearest_pad = null; // find the nearest pad. for (var i in this.landing_pads) { // This isn't the real distance, this is the square of the // distance -- no point in spending processing time on // sqrt. var dstx = Math.abs(x - this.landing_pads[i].centerX); var dsty = Math.abs(y - this.landing_pads[i].centerY); var distance = (dstx * dstx) + (dsty * dsty); if (shortest_distance == null || distance < shortest_distance) { shortest_distance = distance; nearest_pad = this.landing_pads[i]; } } if (nearest_pad != this.current_pad) { if (this.current_pad) { $(this.current_pad.obj).hide(); } this.current_pad = nearest_pad; $(nearest_pad.obj).show(); } }, findDropZone: function(x, y) { // Go through our dropzones and see if we're over one. var new_dropzone = null; for (var i in this.dropzones) { // console.log('x:' + x + ' left:' + this.dropzones[i].left + ' right: ' + this.dropzones[i].left + this.dropzones[i].width); if (this.dropzones[i].left < x && x < this.dropzones[i].left + this.dropzones[i].width && this.dropzones[i].top < y && y < this.dropzones[i].top + this.dropzones[i].height) { new_dropzone = this.dropzones[i]; break; } } // If we're over one, see if it's different. if (new_dropzone && (!this.regionLock || this.regionLockRegions[new_dropzone.region])) { var changed = false; if (!this.current_dropzone || new_dropzone.obj.id != this.current_dropzone.obj.id) { this.changeDropZone(new_dropzone); changed = true; } this.findLandingPad(x, y); if (changed) { // recalculate the size of our drop zones due to the fact that we're drawing landing pads. this.reCalculateDropZones(); } } // If we're not over one, be sure to unhilite one if we were just // over it. else if (this.current_dropzone) { this.unsetDropZone(); } }, /** save button clicked, or pane deleted **/ savePositions: function() { var draggable = Drupal.Panels.Draggable; $(draggable.accept).each(function() { var val = ''; $(this).find(draggable.draggable).each(function() { if (val) { val += ','; } val += this.id.replace(draggable.draggableId, ''); }); var region = this.id.replace(draggable.regionId, ''); $('input[name="panel[pane][' + region + ']"]').val(val); }); return false; } }; Drupal.Panels.DraggableHandler = function() { $(this).addClass('panel-draggable'); var draggable = Drupal.Panels.Draggable; var scrollBuffer = 10; var scrollDistance = 10; var scrollTimer = 30; getMouseOffset = function(docPos, mousePos, windowPos) { return { x: mousePos.x - docPos.x + windowPos.x, y: mousePos.y - docPos.y + windowPos.y}; }; getMousePos = function(ev) { ev = ev || window.event; if (ev.pageX || ev.pageY) { return { x:ev.pageX, y:ev.pageY }; } return { x:ev.clientX + document.body.scrollLeft - document.body.clientLeft, y:ev.clientY + document.body.scrollTop - document.body.clientTop }; }; getPosition = function(e) { /* if (document.defaultView && document.defaultView.getComputedStyle) { var css = document.defaultView.getComputedStyle(e, null); return { x: parseInt(css.getPropertyValue('left')), y: parseInt(css.getPropertyValue('top')) }; } */ var left = 0; var top = 0; while (e.offsetParent) { left += e.offsetLeft; top += e.offsetTop; e = e.offsetParent; } left += e.offsetLeft; top += e.offsetTop; return { x:left, y:top }; }; mouseUp = function(e) { clearTimeout(draggable.timeoutId); draggable.dropzones = []; if (draggable.current_pad) { // Drop the object where we're hovering $(draggable.object).insertAfter($(draggable.current_pad.obj)); Drupal.Panels.changed($(draggable.object)); } else { // or put it back where it came from $(draggable.object).insertAfter(draggable.placeholder); } // remove the placeholder draggable.placeholder.remove(); // restore original settings. $(draggable.object).css(draggable.original); if (draggable.current_dropzone) { draggable.unsetDropZone(); } $(document).unbind('mouseup').unbind('mousemove'); draggable.savePositions(); }; mouseMove = function(e) { draggable.mousePos = getMousePos(e); draggable.findDropZone(draggable.mousePos.x, draggable.mousePos.y); var windowMoved = parseInt(draggable.offsetDivHeight - $(draggable.main).innerHeight()); draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + windowMoved + 'px'; draggable.object.style.left = draggable.mousePos.x - draggable.mouseOffset.x + 'px'; $(draggable.object).toggleClass('moving'); }; mouseDown = function(e) { // If we mouse-downed over something clickable, don't drag! if (e.target.nodeName == 'A' || e.target.nodeName == 'INPUT' || e.target.parentNode.nodeName == 'A' || e.target.nodeName.nodeName == 'INPUT') { return; } draggable.object = $(this).parent(draggable.draggable).get(0); draggable.paneId = draggable.object.id.replace(draggable.draggableId, ''); // create a placeholder so we can put this object back if dropped in an invalid location. draggable.placeholder = $('"'); $(draggable.object).after(draggable.placeholder); // Store original CSS so we can put it back. draggable.original = { position: $(draggable.object).css('position'), width: 'auto', left: $(draggable.object).css('left'), top: $(draggable.object).css('top'), 'z-index': $(draggable.object).css('z-index'), 'margin-bottom': $(draggable.object).css('margin-bottom'), 'margin-top': $(draggable.object).css('margin-top'), 'margin-left': $(draggable.object).css('margin-left'), 'margin-right': $(draggable.object).css('margin-right'), 'padding-bottom': $(draggable.object).css('padding-bottom'), 'padding-top': $(draggable.object).css('padding-top'), 'padding-left': $(draggable.object).css('padding-left'), 'padding-right': $(draggable.object).css('padding-right') }; draggable.mousePos = getMousePos(e); var originalPos = $(draggable.object).offset(); var width = Math.min($(draggable.object).innerWidth(), draggable.maxWidth); draggable.offsetDivHeight = $(draggable.main).innerHeight(); draggable.findDropZone(draggable.mousePos.x, draggable.mousePos.y); // Make copies of these because in FF3, they actually change when we // move the item, whereas they did not in FF2. if (e.layerX || e.layerY) { var layerX = e.layerX; var layerY = e.layerY; } else if (e.originalEvent && e.originalEvent.layerX) { var layerX = e.originalEvent.layerX; var layerY = e.originalEvent.layerY; } // Make the draggable relative, get it out of the way and make it // invisible. $(draggable.object).css({ position: 'relative', 'z-index': 100, width: width + 'px', 'margin-bottom': (-1 * parseInt($(draggable.object).outerHeight())) + 'px', 'margin-top': 0, 'margin-left': 0, 'margin-right': (-1 * parseInt($(draggable.object).outerWidth())) + 'px', 'padding-bottom': 0, 'padding-top': 0, 'padding-left': 0, 'padding-right': 0, 'left': 0, 'top': 0 }) .insertAfter($(draggable.main)); var newPos = $(draggable.object).offset(); var windowOffset = { left: originalPos.left - newPos.left, top: originalPos.top - newPos.top } // if they grabbed outside the area where we make the draggable smaller, move it // closer to the cursor. if (layerX != 'undefined' && layerX > width) { windowOffset.left += layerX - 10; } else if (layerX != 'undefined' && e.offsetX > width) { windowOffset.left += e.offsetX - 10; } // This is stored so we can move with it. draggable.mouseOffset = { x: draggable.mousePos.x - windowOffset.left, y: draggable.mousePos.y - windowOffset.top}; draggable.offsetDivHeight = $(draggable.main).innerHeight(); draggable.object.style.top = windowOffset.top + 'px'; draggable.object.style.left = windowOffset.left + 'px'; $(document).unbind('mouseup').unbind('mousemove').mouseup(mouseUp).mousemove(mouseMove); draggable.calculateDropZones(draggable.mousePos, e); draggable.timeoutId = setTimeout('timer()', scrollTimer); // If locking to a particular set of regions, set that: if (Drupal.settings.Panels && Drupal.settings.Panels.RegionLock && Drupal.settings.Panels.RegionLock[draggable.paneId]) { draggable.regionLock = true; draggable.regionLockRegions = Drupal.settings.Panels.RegionLock[draggable.paneId]; } else { draggable.regionLock = false; draggable.regionLockRegions = null; } return false; }; timer = function() { if (!draggable.timeCount) { draggable.timeCount = 0; } draggable.timeCount = draggable.timeCount + 1; var left = $(window).scrollLeft(); var right = left + $(window).width(); var top = $(window).scrollTop(); var bottom = top + $(window).height(); if (draggable.mousePos.x < left + scrollBuffer && left > 0) { window.scrollTo(left - scrollDistance, top); draggable.mousePos.x -= scrollDistance; draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + 'px'; } else if (draggable.mousePos.x > right - scrollBuffer) { window.scrollTo(left + scrollDistance, top); draggable.mousePos.x += scrollDistance; draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + 'px'; } else if (draggable.mousePos.y < top + scrollBuffer && top > 0) { window.scrollTo(left, top - scrollDistance); draggable.mousePos.y -= scrollDistance; draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + 'px'; } else if (draggable.mousePos.y > bottom - scrollBuffer) { window.scrollTo(left, top + scrollDistance); draggable.mousePos.y += scrollDistance; draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + 'px'; } draggable.timeoutId = setTimeout('timer()', scrollTimer); } $(this).mousedown(mouseDown); }; $.fn.extend({ panelsDraggable: Drupal.Panels.DraggableHandler }); /** * Implement Drupal behavior for autoattach */ Drupal.behaviors.PanelsDisplayEditor = { attach: function(context) { // Show javascript only items. $('span#panels-js-only').css('display', 'inline'); $('#panels-dnd-main div.panel-pane:not(.panel-portlet)') .addClass('panel-portlet') .each(Drupal.Panels.bindPortlet); // The above doesn't work if context IS the pane, so do this to catch that. if ($(context).hasClass('panel-pane') && !$(context).hasClass('panel-portlet')) { $(context) .addClass('panel-portlet') .each(Drupal.Panels.bindPortlet); } // Make draggables and make sure their positions are saved. $(context).find('div.grabber:not(.panel-draggable)').panelsDraggable(); Drupal.Panels.Draggable.savePositions(); // Bind buttons. $('#panels-hide-all', context).click(Drupal.Panels.clickHideAll); $('#panels-show-all', context).click(Drupal.Panels.clickShowAll); Drupal.Panels.bindClickDelete(context); $('#panels-live-preview-button:not(.panels-preview-processed)') .addClass('panels-preview-processed') .click(function () { if (!$('#panels-preview').size()) { $('#panels-dnd-main').parents('form').after('
'); } var html = ''; html += ' '; $('#panels-preview').html(html); }); // Bind modal detach behaviors to cancel current form. $(document).bind('CToolsDetachBehaviors', function(event, context) { $('#edit-cancel-style', context).trigger('click'); }); var setTitleClass = function () { if ($('#edit-display-title-hide-title').val() == 2) { $('#panels-dnd-main').removeClass('panels-set-title-hide'); } else { $('#panels-dnd-main').addClass('panels-set-title-hide'); } } // The panes have an option to set the display title, but only if // a select is set to the proper value. This sets a class on the // main edit div so that the option to set the display title // is hidden if that is not selected, and visible if it is. $('#edit-display-title-hide-title:not(.panels-title-processed)') .addClass('panels-title-processed') .change(setTitleClass); setTitleClass(); } } $(function() { /** * AJAX responder command to render the preview. */ Drupal.ajax.prototype.commands.panel_preview = function(ajax, command, status) { $('#panels-preview').html(command.output); } }); })(jQuery);