// Ensure the $ alias is owned by jQuery.
(function($) {
// randomly lock a pane.
// @debug only
Drupal.settings.Panels = Drupal.settings.Panels || {};
Drupal.settings.Panels.RegionLock = {
10: { 'top': false, 'left': true, 'middle': true }
}
Drupal.PanelsIPE = {
editors: {},
bindClickDelete: function(context) {
$('a.pane-delete:not(.pane-delete-processed)', context)
.addClass('pane-delete-processed')
.click(function() {
if (confirm(Drupal.t('Remove this pane?'))) {
$(this).parents('div.panels-ipe-portlet-wrapper').fadeOut('medium', function() {
var $sortable = $(this).closest('.ui-sortable');
$(this).empty().remove();
$sortable.trigger('sortremove');
});
$(this).parents('div.panels-ipe-display-container').addClass('changed');
}
return false;
});
}
}
Drupal.behaviors.PanelsIPE = {
attach: function(context) {
for (var i in Drupal.settings.PanelsIPECacheKeys) {
var key = Drupal.settings.PanelsIPECacheKeys[i];
$('div#panels-ipe-display-' + key + ':not(.panels-ipe-processed)')
.addClass('panels-ipe-processed')
.each(function() {
// If we're replacing an old IPE, clean it up a little.
if (Drupal.PanelsIPE.editors[key]) {
Drupal.PanelsIPE.editors[key].editing = false;
}
Drupal.PanelsIPE.editors[key] = new DrupalPanelsIPE(key);
Drupal.PanelsIPE.editors[key].showContainer();
});
}
$('.panels-ipe-hide-bar').once('panels-ipe-hide-bar-processed').click(function() {
Drupal.PanelsIPE.editors[key].hideContainer();
});
Drupal.PanelsIPE.bindClickDelete(context);
}
};
/**
* Base object (class) definition for the Panels In-Place Editor.
*
* A new instance of this object is instanciated for every unique IPE on a given
* page.
*
* Note that this form is provisional, and we hope to replace it with a more
* flexible, loosely-coupled model that utilizes separate controllers for the
* discrete IPE elements. This will result in greater IPE flexibility.
*/
function DrupalPanelsIPE(cache_key, cfg) {
cfg = cfg || {};
var ipe = this;
this.key = cache_key;
this.lockPath = null;
this.state = {};
this.container = $('#panels-ipe-control-container');
this.control = $('div#panels-ipe-control-' + cache_key);
this.initButton = $('div.panels-ipe-startedit', this.control);
this.cfg = cfg;
this.changed = false;
this.sortableOptions = $.extend({
opacity: 0.75, // opacity of sortable while sorting
items: 'div.panels-ipe-portlet-wrapper',
handle: 'div.panels-ipe-draghandle',
cancel: '.panels-ipe-nodrag',
dropOnEmpty: true
}, cfg.sortableOptions || {});
this.regions = [];
this.sortables = {};
$(document).bind('CToolsDetachBehaviors', function() {
// If the IPE is off and the container is not visible, then we need
// to reshow the container on modal close.
if (!$('.panels-ipe-form-container', ipe.control).html() && !ipe.container.is(':visible')) {
ipe.showContainer();
ipe.cancelLock();
}
// If the IPE is on and we've hidden the bar for a modal, we need to
// re-display it.
if (ipe.topParent && ipe.topParent.hasClass('panels-ipe-editing') && ipe.container.is(':not(visible)')) {
ipe.showContainer();
}
});
// If a user navigates away from a locked IPE, cancel the lock in the background.
$(window).bind('beforeunload', function() {
if (!ipe.editing) {
return;
}
if (ipe.topParent && ipe.topParent.hasClass('changed')) {
ipe.changed = true;
}
if (ipe.changed) {
return Drupal.t('This will discard all unsaved changes. Are you sure?');
}
});
// If a user navigates away from a locked IPE, cancel the lock in the background.
$(window).bind('unload', function() {
ipe.cancelLock(true);
});
/**
* If something caused us to abort what we were doing, send a background
* cancel lock request to the server so that we do not leave stale locks
* hanging around.
*/
this.cancelLock = function(sync) {
// If there's a lockpath and an ajax available, inform server to clear lock.
// We borrow the ajax options from the customize this page link.
if (ipe.lockPath && Drupal.ajax['panels-ipe-customize-page']) {
var ajaxOptions = {
type: 'POST',
url: ipe.lockPath
}
if (sync) {
ajaxOptions.async = false;
}
// Make sure we don't somehow get another one:
ipe.lockPath = null;
// Send the request. This is synchronous to prevent being cancelled.
$.ajax(ajaxOptions);
}
}
this.activateSortable = function(event, ui) {
if (!Drupal.settings.Panels || !Drupal.settings.Panels.RegionLock) {
// don't bother if there are no region locks in play.
return;
}
var region = event.data.region;
var paneId = ui.item.attr('id').replace('panels-ipe-paneid-', '');
var disabledRegions = false;
// Determined if this pane is locked out of this region.
if (!Drupal.settings.Panels.RegionLock[paneId] || Drupal.settings.Panels.RegionLock[paneId][region]) {
ipe.sortables[region].sortable('enable');
ipe.sortables[region].sortable('refresh');
}
else {
disabledRegions = true;
ipe.sortables[region].sortable('disable');
ipe.sortables[region].sortable('refresh');
}
// If we disabled regions, we need to
if (disabledRegions) {
$(event.srcElement).bind('dragstop', function(event, ui) {
// Go through
});
}
};
// When dragging is stopped, we need to ensure all sortable regions are enabled.
this.enableRegions = function(event, ui) {
for (var i in ipe.regions) {
ipe.sortables[ipe.regions[i]].sortable('enable');
ipe.sortables[ipe.regions[i]].sortable('refresh');
}
}
this.initSorting = function() {
var $region = $(this).parents('.panels-ipe-region');
var region = $region.attr('id').replace('panels-ipe-regionid-', '');
ipe.sortables[region] = $(this).sortable(ipe.sortableOptions);
ipe.regions.push(region);
$(this).bind('sortactivate', {region: region}, ipe.activateSortable);
};
this.initEditing = function(formdata) {
ipe.editing = true;
ipe.topParent = $('div#panels-ipe-display-' + cache_key);
ipe.backup = this.topParent.clone();
// See http://jqueryui.com/demos/sortable/ for details on the configuration
// parameters used here.
ipe.changed = false;
$('div.panels-ipe-sort-container', ipe.topParent).each(ipe.initSorting);
// Since the connectWith option only does a one-way hookup, iterate over
// all sortable regions to connect them with one another.
$('div.panels-ipe-sort-container', ipe.topParent)
.sortable('option', 'connectWith', ['div.panels-ipe-sort-container']);
$('div.panels-ipe-sort-container', ipe.topParent).bind('sortupdate', function() {
ipe.changed = true;
});
$('div.panels-ipe-sort-container', ipe.topParent).bind('sortstop', this.enableRegions);
$('.panels-ipe-form-container', ipe.control).append(formdata);
$('input:submit:not(.ajax-processed)', ipe.control).addClass('ajax-processed').each(function() {
var element_settings = {};
element_settings.url = $(this.form).attr('action');
element_settings.setClick = true;
element_settings.event = 'click';
element_settings.progress = { 'type': 'throbber' };
element_settings.ipe_cache_key = cache_key;
var base = $(this).attr('id');
Drupal.ajax[ipe.base] = new Drupal.ajax(base, this, element_settings);
});
// Perform visual effects in a particular sequence.
// .show() + .hide() cannot have speeds associated with them, otherwise
// it clears out inline styles.
$('.panels-ipe-on').show();
ipe.showForm();
ipe.topParent.addClass('panels-ipe-editing');
};
this.hideContainer = function() {
ipe.container.slideUp('fast');
};
this.showContainer = function() {
ipe.container.slideDown('normal');
};
this.showButtons = function() {
$('.panels-ipe-form-container').hide();
$('.panels-ipe-button-container').show();
ipe.showContainer();
};
this.showForm = function() {
$('.panels-ipe-button-container').hide();
$('.panels-ipe-form-container').show();
ipe.showContainer();
};
this.endEditing = function() {
ipe.editing = false;
ipe.lockPath = null;
$('.panels-ipe-form-container').empty();
// Re-show all the IPE non-editing meta-elements
$('div.panels-ipe-off').show('fast');
ipe.showButtons();
// Re-hide all the IPE meta-elements
$('div.panels-ipe-on').hide();
$('.panels-ipe-editing').removeClass('panels-ipe-editing');
$('div.panels-ipe-sort-container.ui-sortable', ipe.topParent).sortable("destroy");
};
this.saveEditing = function() {
$('div.panels-ipe-region', ipe.topParent).each(function() {
var val = '';
var region = $(this).attr('id').split('panels-ipe-regionid-')[1];
$(this).find('div.panels-ipe-portlet-wrapper').each(function() {
var id = $(this).attr('id').split('panels-ipe-paneid-')[1];
if (id) {
if (val) {
val += ',';
}
val += id;
}
});
$('input[name="panel[pane][' + region + ']"]', ipe.control).val(val);
});
}
this.cancelIPE = function() {
ipe.hideContainer();
ipe.topParent.fadeOut('medium', function() {
ipe.topParent.replaceWith(ipe.backup.clone());
ipe.topParent = $('div#panels-ipe-display-' + ipe.key);
// Processing of these things got lost in the cloning, but the classes remained behind.
// @todo this isn't ideal but I can't seem to figure out how to keep an unprocessed backup
// that will later get processed.
$('.ctools-use-modal-processed', ipe.topParent).removeClass('ctools-use-modal-processed');
$('.pane-delete-processed', ipe.topParent).removeClass('pane-delete-processed');
ipe.topParent.fadeIn('medium');
Drupal.attachBehaviors();
});
};
this.cancelEditing = function() {
if (ipe.topParent.hasClass('changed')) {
ipe.changed = true;
}
if (!ipe.changed || confirm(Drupal.t('This will discard all unsaved changes. Are you sure?'))) {
this.cancelIPE();
return true;
}
else {
// Cancel the submission.
return false;
}
};
this.createSortContainers = function() {
$('div.panels-ipe-region', this.topParent).each(function() {
$(this).children('div.panels-ipe-portlet-marker').parent()
.wrapInner('<div class="panels-ipe-sort-container" />');
// Move our gadgets outside of the sort container so that sortables
// cannot be placed after them.
$('div.panels-ipe-portlet-static', this).each(function() {
$(this).prependTo($(this).parent().parent());
});
});
}
this.createSortContainers();
};
$(function() {
Drupal.ajax.prototype.commands.initIPE = function(ajax, data, status) {
if (Drupal.PanelsIPE.editors[data.key]) {
Drupal.PanelsIPE.editors[data.key].initEditing(data.data);
Drupal.PanelsIPE.editors[data.key].lockPath = data.lockPath;
}
Drupal.attachBehaviors();
};
Drupal.ajax.prototype.commands.IPEsetLockState = function(ajax, data, status) {
if (Drupal.PanelsIPE.editors[data.key]) {
Drupal.PanelsIPE.editors[data.key].lockPath = data.lockPath;
}
};
Drupal.ajax.prototype.commands.addNewPane = function(ajax, data, status) {
if (Drupal.PanelsIPE.editors[data.key]) {
Drupal.PanelsIPE.editors[data.key].changed = true;
}
};
Drupal.ajax.prototype.commands.cancelIPE = function(ajax, data, status) {
if (Drupal.PanelsIPE.editors[data.key]) {
Drupal.PanelsIPE.editors[data.key].cancelIPE();
Drupal.PanelsIPE.editors[data.key].endEditing();
}
};
Drupal.ajax.prototype.commands.unlockIPE = function(ajax, data, status) {
if (confirm(data.message)) {
var ajaxOptions = ajax.options;
ajaxOptions.url = data.break_path;
$.ajax(ajaxOptions);
}
else {
Drupal.PanelsIPE.editors[data.key].endEditing();
}
};
Drupal.ajax.prototype.commands.endIPE = function(ajax, data, status) {
if (Drupal.PanelsIPE.editors[data.key]) {
Drupal.PanelsIPE.editors[data.key].endEditing();
}
};
Drupal.ajax.prototype.commands.insertNewPane = function(ajax, data, status) {
IPEContainerSelector = '#panels-ipe-regionid-' + data.regionId + ' div.panels-ipe-sort-container';
firstPaneSelector = IPEContainerSelector + ' div.panels-ipe-portlet-wrapper:first';
// Insert the new pane before the first existing pane in the region, if
// any.
if ($(firstPaneSelector).length) {
insertData = {
'method': 'before',
'selector': firstPaneSelector,
'data': data.renderedPane,
'settings': null
}
Drupal.ajax.prototype.commands.insert(ajax, insertData, status);
}
// Else, insert it as a first child of the container. Doing so might fall
// outside of the wrapping markup for the style, but it's the best we can
// do.
else {
insertData = {
'method': 'prepend',
'selector': IPEContainerSelector,
'data': data.renderedPane,
'settings': null
}
Drupal.ajax.prototype.commands.insert(ajax, insertData, status);
}
};
/**
* Override the eventResponse on ajax.js so we can add a little extra
* behavior.
*/
Drupal.ajax.prototype.ipeReplacedEventResponse = Drupal.ajax.prototype.eventResponse;
Drupal.ajax.prototype.eventResponse = function (element, event) {
if (element.ipeCancelThis) {
element.ipeCancelThis = null;
return false;
}
if ($(this.element).attr('id') == 'panels-ipe-cancel') {
if (!Drupal.PanelsIPE.editors[this.element_settings.ipe_cache_key].cancelEditing()) {
return false;
}
}
var retval = this.ipeReplacedEventResponse(element, event);
if (this.ajaxing && this.element_settings.ipe_cache_key) {
// Move the throbber so that it appears outside our container.
if (this.progress.element) {
$(this.progress.element).addClass('ipe-throbber').appendTo($('body'));
}
Drupal.PanelsIPE.editors[this.element_settings.ipe_cache_key].hideContainer();
}
// @TODO $('#panels-ipe-throbber-backdrop').remove();
return retval;
};
/**
* Override the eventResponse on ajax.js so we can add a little extra
* behavior.
*/
Drupal.ajax.prototype.ipeReplacedError = Drupal.ajax.prototype.error;
Drupal.ajax.prototype.error = function (response, uri) {
var retval = this.ipeReplacedError(response, uri);
if (this.element_settings.ipe_cache_key) {
Drupal.PanelsIPE.editors[this.element_settings.ipe_cache_key].showContainer();
}
};
Drupal.ajax.prototype.ipeReplacedBeforeSerialize = Drupal.ajax.prototype.beforeSerialize;
Drupal.ajax.prototype.beforeSerialize = function (element_settings, options) {
if ($(this.element).hasClass('panels-ipe-save')) {
Drupal.PanelsIPE.editors[this.element_settings.ipe_cache_key].saveEditing();
};
return this.ipeReplacedBeforeSerialize(element_settings, options);
};
});
/**
* Apply margin to bottom of the page.
*
* Note that directly applying marginBottom does not work in IE. To prevent
* flickering/jumping page content with client-side caching, this is a regular
* Drupal behavior.
*
* @see admin_menu.js via https://drupal.org/project/admin_menu
*/
Drupal.behaviors.panelsIpeMarginBottom = {
attach: function () {
$('body:not(.panels-ipe)').addClass('panels-ipe');
}
};
})(jQuery);