artabro/wire/modules/Jquery/JqueryUI/panel.js
2024-08-27 11:35:37 +02:00

492 lines
15 KiB
JavaScript

/**
* ProcessWire Panels
*
* Alternative to modal windows. Creates iframe panels that load URLs.
* Clicking outside the panel closes it. By default, panels load the requested
* URL on mouseover of the a.pw-panel toggle link, unless the pw-panel-reload
* class is specified (in which case it loads on click).
*
* Copyright 2016 by Ryan Cramer
* License: MPL 2.0
*
* REQUIREMENTS
* ============
* Include the panel.js and panel.css files in the request.
* jQuery is required. jQuery UI is strongly recommended but not required.
* So far tested only in PW admin themes, but should work outside them too.
*
* BASIC USAGE
* ===========
* Give any link a "pw-panel" class and it will open in a left-side panel:
*
* <a class='pw-panel' href='/path/'>Click to open panel</a>
*
* If clickable element is not an <a> tag, give "data-href" attr containing the URL the panel should open:
*
* <span class='pw-panel' data-href='/path/'>Click to open panel</span>
*
* To make a panel open on the right rather than the left, append "pw-panel-right" to the class attribute.
*
* <a class='pw-panel pw-panel-right' ...
*
* To specify that the panel should reload the page every time it is opened, append "pw-panel-reload" class.
* Note that this also makes the panel load the URL on "click" of the a.pw-panel link, rather than "mouseover".
*
* <a class='pw-panel pw-panel-reload' ...
*
* To specify the target width of the panel use the "data-panel-width" attribute (default width is 50%):
*
* <a class='pw-panel' data-panel-width='60%' ... (you may also specify value in "px" if preferred)
*
*
* TABBED USAGE
* ============
* To specify the text that should appear in the tab connected to the panel provide "data-tab-text" attribute:
*
* <a class='pw-panel' data-tab-text='Panel Title' ...
*
* To specify an icon, use "data-tab-icon" attribute with font-awesome icon name (excluding the "fa-" part):
*
* <a class='pw-panel' data-tab-icon='sitemap' ...
*
* To specify the offset of the tab from top or bottom, specify "data-tab-offset" attribute. Positive integer
* indicates offset from top, whereas negative integer indicates offset from bottom.
*
* <a class='pw-panel' data-tab-offset='200' ...
*
* To specify that a tab should appear even when the panel is closed (on left or right side of screen),
* append the "pw-panel-tab" class. This serves as an alternative to the toggle link.
*
* <a class='pw-panel pw-panel-tab' ...
*
*
* REFERENCE
* =========
*
* CSS Classes for panel toggle element:
* pw-panel: Required for anything that triggers a panel to open.
* pw-panel-right: Makes panel open on right side rather than left.
* pw-panel-reload: Makes panel reload URL it shows every time panel is opened.
* pw-panel-tab: Specify that a tab should appear for a closed panel (on left or right side of screen).
* pw-panel-links: Specify that links in panel should open in panel rather than parent window.
*
* Data Attributes for panel toggle element:
* data-panel-id: ID of element to serve as the panel content (if not using a URL).
* data-panel-width: Width of panel in "px" or "%", i.e. "500px" or "50%" (default=55%).
* data-tab-text: Text to appear in the panel's tab.
* data-tab-icon: Icon name to display in panel's tab (excluding the "fa-" part).
* data-tab-offset: Integer tab offset in px from top of bottom of screen (negative number for bottom offset).
* data-href: Use to specify panel URL when element is something other than <a>.
*
*
*
*/
var pwPanels = {
/**
* Quantity of panels initialized
*
*/
qty: 0,
/**
* Initialize pwPanels, to be called at document.ready
*
*/
init: function() {
var url = window.location.href;
if(url.indexOf('pw_panel=1') > -1) {
// initialize inside of a panel
$(document).on('mouseover', 'a', function() {
// make links target the parent window
var $a = $(this);
var target = $a.attr('target');
if(typeof target == "undefined" || target.length == 0) {
$a.attr('target', '_parent');
}
});
} else if(url.indexOf('pw_panel=2') > -1) {
// don't initialize anything (.pw-panel-links class option)
} else {
// initialize a page with panels in it
$('.pw-panel').each(function() {
var $toggler = $(this);
pwPanels.addPanel($toggler);
});
}
},
/**
* Add a new panel
*
* @param toggler Element that toggles the panel
*
*/
addPanel: function($toggler) {
var panelURL = $toggler.attr('data-href');
var panelID = $toggler.attr('data-panel-id');
var panelContainerID = 'pw-panel-container-' + (++pwPanels.qty);
// allow for use of data-href or href attribute that references URL to load in panel
if(typeof panelURL == 'undefined' || !panelURL.length) panelURL = $toggler.attr('href');
if(typeof panelURL != 'undefined' && panelURL.length) {
var hash = '';
if(panelURL.indexOf('#') > -1) {
var parts = panelURL.split('#');
panelURL = parts[0];
hash = '#' + parts[1];
}
panelURL += (panelURL.indexOf('?') > -1 ? '&' : '?') + 'modal=panel&pw_panel=';
if($toggler !== null && $toggler.hasClass('pw-panel-links')) {
panelURL += '2'; // don't update target of links in panel
} else {
panelURL += '1'; // update target of links in panel
}
panelURL += hash;
}
var $icon = $('<i />')
.attr('class', 'pw-panel-icon fa fa-angle-double-left');
var $span = $('<small />').attr('class', 'ui-button-text')
.append($icon);
var $btn = $('<a />')
.attr('class', 'pw-panel-button pw-panel-button-closed ui-button ui-state-default')
.attr('href', panelURL)
.on('click', pwPanels.buttonClickEvent)
.on('mouseover', pwPanels.buttonMouseoverEvent)
.on('mouseout', pwPanels.buttonMouseoutEvent)
.append($span);
var $panel = $('<div />')
.attr('id', panelContainerID)
.attr('class', 'pw-panel-container pw-panel-container-closed')
.append($btn);
$('body').append($panel);
if(typeof panelID != 'undefined' && panelID.length) {
// loading an in-page element rather than a URL
$('#' + panelID).hide().addClass('pw-panel-element'); // class assigned to in-page element
$panel.addClass('pw-panel-container-element').attr('data-panel-id', panelID);
}
// panel toggler
if($toggler !== null) {
pwPanels.initToggler($toggler, $btn, $panel);
} else {
$panel.addClass('pw-panel-left');
}
},
/**
* Initialize the toggler element, plus $btn and $panel with options from toggler element
*
* @param $toggler
* @param $btn
* @param $panel
*
*/
initToggler: function($toggler, $btn, $panel) {
var panelSide = $toggler.hasClass('pw-panel-right') ? 'right' : 'left';
var text = $toggler.attr('data-tab-text');
var icon = $toggler.attr('data-tab-icon');
var offset = $toggler.attr('data-tab-offset');
var panelWidth = $toggler.attr('data-panel-width');
var btnPos = panelSide == 'right' ? 'left' : 'right';
var btnExtraPx = 1;
$panel.addClass('pw-panel-tab pw-panel-' + panelSide);
$panel.attr('data-href', $btn.attr('href'));
if($toggler.hasClass('pw-panel-reload')) $panel.addClass('pw-panel-reload');
if(typeof offset != "undefined") {
offset = parseInt(offset);
if(offset > -1) {
// positive number indicates offset from top
$btn.css('top', offset + 'px');
} else {
// negative number indicates offset from bottom
$btn.css('top', 'auto');
$btn.css('bottom', Math.abs(offset) + 'px');
}
}
if(typeof text != "undefined" && text.length) {
var $btnText = $btn.children('.ui-button-text');
var $text = $("<span />").text(text);
$btnText.html('<span>' + $text.text() + '</span>');
$btn.addClass('pw-panel-button-text');
btnExtraPx = 7;
//$btn.css(btnPos, (-1 * ($btn.height() + 7)) + 'px');
//$btn.css('top', parseInt($btn.css('top')) + ($btn.width()) + 'px');
}
if(typeof icon != "undefined" && icon.length) {
var $icon = $('<i />').addClass('fa fa-fw fa-' + icon);
var $text = $btn.children('.ui-button-text');
if($btn.hasClass('pw-panel-button-text')) {
$text.prepend($icon);
} else {
$text.empty().append($icon);
$btn.css(btnPos, (-1 * ($btn.outerWidth())) + 'px');
}
}
if(typeof panelWidth != 'undefined' && panelWidth.length) {
$panel.css('width', panelWidth)
$panel.css(panelSide, '-' + panelWidth);
}
if(panelSide == 'right') {
// align button to right edge
//$btn.css('left', (-1 * (btnExtraPx + $btn.height())) + 'px');
} else {
// align button to left edge
$btn.css('right', (-1 * (btnExtraPx + $btn.height())) + 'px');
}
if(!$toggler.hasClass('pw-panel-tab')) {
// if toggler doesn't specify that a tab/button should show, hide our $btn element
$btn.addClass('pw-panel-button-hidden');
}
// delegate events from toggler to pw-panel-button
$toggler.on('click', function() {
$btn.trigger('click');
return false;
}).on('mouseover', function() {
$btn.trigger('mouseover');
}).on('mouseout', function() {
$btn.trigger('mouseout');
});
},
/**
* Populate the panel content (iframe if using URL) and return it
*
*/
initPanelContent: function($panel) {
var $content = $panel.find('.pw-panel-content');
var panelID = $panel.attr('data-panel-id');
if($content.length) {
return $content;
} else if(typeof panelID != "undefined") {
var $panelTarget = $('#' + panelID);
// var $btn = $panel.find('.pw-panel-button').addClass(panelID + '-pw-panel-button'); // if needed for external trigger
if($panelTarget.length) {
$content = $('<div />').addClass('pw-panel-content').css('overflow', 'auto');
$panel.append($content);
$content.append($panelTarget);
$panelTarget.show();
$panelTarget.trigger('pw-panel-init');
}
} else {
$content = $('<iframe />')
.addClass('pw-panel-content')
.attr('src', $panel.attr('data-href'));
$panel.append($content);
}
return $content;
},
/**
* Event called on window resize
*
*/
windowResizeEvent: function() {
$(".pw-panel-container-init").each(function() {
var $panel = $(this);
if($panel.hasClass('pw-panel-container-open')) return;
var panelWidth = $panel.width();
var px = (-1 * panelWidth) + 'px';
if($panel.hasClass('pw-panel-right')) {
$panel.css('right', px);
} else {
$panel.css('left', px);
}
});
},
/**
* Event called on panel button click
*
* @returns {boolean}
*
*/
buttonClickEvent: function() {
var $btn = $(this);
var $panel = $btn.closest('.pw-panel-container');
var $panelContent = $panel.find('.pw-panel-content');
var isOpen = $panel.hasClass('pw-panel-container-open');
var isLoaded = $panel.hasClass('pw-panel-container-loaded');
var panelWidth = $panel.width();
var panelSide = $panel.hasClass('pw-panel-right') ? 'right' : 'left';
var hasJQUI = typeof jQuery.ui != "undefined";
function animateFinished() {
$panel.toggleClass('pw-panel-container-open pw-panel-container-closed');
$btn.toggleClass('pw-panel-button-open pw-panel-button-closed');
}
if($('.pw-panel-container-init').length == 0) {
// attach window resize event only if no panels have been opened before
// so that we attach it only if needed, and not more than once
$(window).on('resize', pwPanels.windowResizeEvent);
}
if(isOpen) {
// close the panel
var px = (-1 * panelWidth) + 'px';
if(hasJQUI) {
if(panelSide == 'left') {
$panel.animate({left: px}, 150, animateFinished);
} else {
$panel.animate({right: px}, 150, animateFinished);
}
} else {
$panel.css(panelSide, px);
animateFinished();
}
$('body').css('overflow', '');
$("#pw-panel-shade").fadeOut('fast', function() {
$(this).remove();
});
$btn.fadeOut('fast', function() {
$btn.removeClass('ui-state-active');
$btn.fadeIn('fast');
});
if(hasJQUI && panelSide == 'left') $panel.resizable('destroy');
if($panel.hasClass('pw-panel-reload')) {
// force it to create new iframe on every load
$panel.find('iframe.pw-panel-content').remove();
}
// trigger panel-closed event
$(document).trigger('pw-panel-closed', $panel);
} else {
// open the panel
if($panel.hasClass('pw-panel-reload') || !isLoaded) {
// tell the panel to load or reload, since mouseover even didn't
pwPanels.initPanelContent($panel);
}
if(hasJQUI) {
if(panelSide == 'left') {
$panel.animate({left: 0}, 150, animateFinished);
} else {
$panel.animate({right: 0}, 150, animateFinished);
}
} else {
$panel.css(panelSide, 0);
animateFinished();
}
// shade the parent window
var $shade = $("<div id='pw-panel-shade'>&nbsp;</div>");
$panel.before($shade).fadeIn('fast');
// prevent scrolling in parent window
$('body').css('overflow', 'hidden');
// if shade is clicked, exit the panel
$shade.on('click', function() {
var $panel = $('.pw-panel-container-open');
if(!$panel.length) return false;
$panel.find('.pw-panel-button').trigger('click');
});
// enable panel to be resized
// note: resizing only works with left aligned panel
if(hasJQUI && panelSide == 'left') $panel.resizable({
handles: 'e',
start: function(event, ui) {
// the overlay helps prevent the resizable from losing "grip"
var $overlay = $('<div />').addClass('pw-panel-resizable-overlay').css({
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: 1001,
display: 'hidden'
});
$panel.append($overlay);
},
stop: function(event, ui) {
// pwPanels.lastTabWidth = ui.size.width;
$('.pw-panel-resizable-overlay').remove();
}
});
// indicate that this panel has been opened and is initialized
$panel.addClass('pw-panel-container-init');
// trigger panel-opened event
$(document).trigger('pw-panel-opened', $panel);
}
return false;
},
/**
* Event called on panel button mouseover
*
*/
buttonMouseoverEvent: function() {
var $btn = $(this);
var $panel = $btn.closest('.pw-panel-container');
$btn.removeClass('ui-state-active').addClass('ui-state-hover');
if($panel.hasClass('pw-panel-container-loaded')) return;
$panel.addClass('pw-panel-container-loaded');
if(!$panel.hasClass('pw-panel-reload')) {
// make panel content load on mouseover
pwPanels.initPanelContent($panel);
}
},
/**
* Event called on panel button mouseout
*
*/
buttonMouseoutEvent: function() {
var $btn = $(this);
var $panel = $btn.closest('.pw-panel-container');
if($panel.hasClass('pw-panel-container-open')) $btn.addClass('ui-state-active');
}
}
jQuery(document).ready(function() {
pwPanels.init();
});