diff --git a/admin/code/LeftAndMain.php b/admin/code/LeftAndMain.php index 6b524eb29..27c2e6128 100644 --- a/admin/code/LeftAndMain.php +++ b/admin/code/LeftAndMain.php @@ -296,6 +296,7 @@ class LeftAndMain extends Controller implements PermissionProvider { array( FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Layout.js', FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.js', + FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.ActionTabSet.js', FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Panel.js', FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Tree.js', FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Ping.js', diff --git a/admin/javascript/LeftAndMain.ActionTabSet.js b/admin/javascript/LeftAndMain.ActionTabSet.js new file mode 100644 index 000000000..34fe1ba58 --- /dev/null +++ b/admin/javascript/LeftAndMain.ActionTabSet.js @@ -0,0 +1,222 @@ +/** + * File: LeftAndMain.ActionTabset.js + * + * Contains rules for .ss-ui-action-tabset, used for: + * * Site tree action tabs (to perform actions on the site tree) + * * Actions menu (Edit page actions) + * + */ +(function($){ + $.entwine('ss', function($) { + /** + * Generic rules for all ss-ui-action-tabsets + * * ActionMenus + * * SiteTree ActionTabs + */ + $('.ss-tabset.ss-ui-action-tabset').entwine({ + + onadd: function() { + // Make sure the .ss-tabset is already initialised to apply our modifications on top. + this._super(); + //Set actionTabs to allow closing and be closed by default + this.tabs({'collapsible': true, 'active': false}); + }, + + /** + * Deal with available vertical space + */ + 'ontabsbeforeactivate': function(event, ui) { + this.riseUp(event, ui); + }, + + /** + * Handle opening and closing tabs + */ + onclick: function(event, ui) { + this.attachCloseHandler(event, ui); + }, + + /** + * Generic function to close open tabs. Stores event in a handler, + * and removes the bound event once activated. + * + * Note: Should be called by a click event attached to 'this' + */ + attachCloseHandler: function(event, ui) { + var that = this, frame = $('.cms').find('iframe'), closeHandler; + + // Create a handler for the click event so we can close tabs + // and easily remove the event once done + closeHandler = function(event) { + var panel, frame; + panel = $(event.target).closest('.ss-ui-action-tabset .ui-tabs-panel'); + + // If anything except the ui-nav button is clicked, + // close panel and remove handler + if (!$(event.target).closest(that).length || $(panel).length) { + that.tabs('option', 'active', false); // close tabs + + // remove click event from objects it is bound to (iframe's and document) + frame = $('.cms').find('iframe'); + frame.each(function(index, iframe){ + $(iframe).contents().off('click', closeHandler); + }); + $(document).off('click', closeHandler); + } + }; + + // Bind click event to document, and use closeHandler to handle the event + $(document).on('click', closeHandler); + // Make sure iframe click also closes tab + // iframe needs a special case, else the click event will not register here + if(frame.length > 0){ + frame.each(function(index, iframe) { + $(iframe).contents().on('click', closeHandler); + }); + } + }, + /** + * Function riseUp checks to see if a tab should be opened upwards + * (based on space concerns). If true, the rise-up class is applied + * and a new position is calculated and applied to the element. + * + * Note: Should be called by a tabsbeforeactivate event + */ + riseUp: function(event, ui) { + var elHeight, trigger, endOfWindow, elPos, activePanel, activeTab, topPosition, containerSouth, padding; + + // Get the numbers needed to calculate positions + elHeight = $(this).find('.ui-tabs-panel').outerHeight(); + trigger = $(this).find('.ui-tabs-nav').outerHeight(); + endOfWindow = ($(window).height() + $(document).scrollTop()) - trigger; + elPos = $(this).find('.ui-tabs-nav').offset().top; + + activePanel = ui.newPanel; + activeTab = ui.newTab; + + if (elPos + elHeight >= endOfWindow && elPos - elHeight > 0){ + this.addClass('rise-up'); + + if (activeTab.position() !== null){ + topPosition = -activePanel.outerHeight(); + containerSouth = activePanel.parents('.south'); + if (containerSouth){ + // If container is the southern panel, make tab appear from the top of the container + padding = activeTab.offset().top - containerSouth.offset().top; + topPosition = topPosition-padding; + } + $(activePanel).css('top',topPosition+"px"); + } + } else { + // else remove the rise-up class and set top to 0 + this.removeClass('rise-up'); + if (activeTab.position() !== null){ + $(activePanel).css('top','0px'); + } + } + return false; + } + }); + + + /** + * ActionMenus + * * Specific rules for ActionMenus, used for edit page actions + */ + $('.cms-content-actions .ss-tabset.ss-ui-action-tabset').entwine({ + /** + * Make necessary adjustments before tab is activated + */ + 'ontabsbeforeactivate': function(event, ui) { + this._super(event, ui); + //Set the position of the opening tab (if it exists) + if($(ui.newPanel).length > 0){ + $(ui.newPanel).css('left', ui.newTab.position().left+"px"); + } + } + }); + + /** + * SiteTree ActionTabs + * Specific rules for site tree action tabs. Applies to tabs + * within the expanded content area, and within the sidebar + */ + $('.cms-actions-row.ss-tabset.ss-ui-action-tabset').entwine({ + /** + * Make necessary adjustments before tab is activated + */ + 'ontabsbeforeactivate': function(event, ui) { + this._super(event, ui); + // Remove tabset open classes (Last gets a unique class + // in the bigger sitetree. Remove this if we have it) + $(this).closest('.ss-ui-action-tabset') + .removeClass('tabset-open tabset-open-last'); + } + }); + + /** + * SiteTree ActionTabs: expanded + * * Specific rules for siteTree actions within the expanded content area. + */ + $('.cms-content-fields .ss-tabset.ss-ui-action-tabset').entwine({ + /** + * Make necessary adjustments before tab is activated + */ + 'ontabsbeforeactivate': function(event, ui) { + this._super(event, ui); + if($( ui.newPanel).length > 0){ + if($(ui.newTab).hasClass("last")){ + // Align open tab to the right (because opened tab is last) + $(ui.newPanel).css({'left': 'auto', 'right': '0px'}); + + // Last needs to be styled differently when open, so apply a unique class + $(ui.newPanel).parent().addClass('tabset-open-last'); + }else{ + // Assign position to tabpanel based on position of relivent active tab item + $(ui.newPanel).css('left', ui.newTab.position().left+"px"); + + // If this is the first tab, make sure the position doesn't include border + // (hard set position to 0 ), and add the tab-set open class + if($(ui.newTab).hasClass("first")){ + $(ui.newPanel).css('left',"0px"); + $(ui.newPanel).parent().addClass('tabset-open'); + } + } + } + } + }); + + /** + * SiteTree ActionTabs: sidebar + * * Specific rules for when the site tree actions panel appears in + * * the side-bar + */ + $('.cms-tree-view-sidebar .cms-actions-row.ss-tabset.ss-ui-action-tabset').entwine({ + + // If actions panel is within the sidebar, apply active class + // to help animate open/close on hover + 'from .ui-tabs-nav li': { + onhover: function(e) { + $(e.target).parent().find('li .active').removeClass('active'); + $(e.target).find('a').addClass('active'); + } + }, + + /** + * Make necessary adjustments before tab is activated + */ + 'ontabsbeforeactivate': function(event, ui) { + this._super(event, ui); + // Reset position of tabs, else anyone going between the large + // and the small sitetree will see broken tabs + // Apply styles with .css, to avoid overriding currently applied styles + $(ui.newPanel).css({'left': 'auto', 'right': 'auto'}); + + if($(ui.newPanel).length > 0){ + $(ui.newPanel).parent().addClass('tabset-open'); + } + } + }); + + }); +}(jQuery)); diff --git a/admin/javascript/LeftAndMain.js b/admin/javascript/LeftAndMain.js index dd11134dd..0adc315b4 100644 --- a/admin/javascript/LeftAndMain.js +++ b/admin/javascript/LeftAndMain.js @@ -572,7 +572,7 @@ jQuery.noConflict(); if(typeof(window.sessionStorage)=="undefined" || window.sessionStorage == null) return; var selectedTabs = [], url = this._tabStateUrl(); - this.find('.cms-tabset,.ss-tabset').each(function(i, el) { + this.find('.cms-tabset,.ss-tabset').each(function(i, el) { var id = $(el).attr('id'); if(!id) return; // we need a unique reference if(!$(el).data('tabs')) return; // don't act on uninit'ed controls diff --git a/admin/scss/_forms.scss b/admin/scss/_forms.scss index a80be6a61..abeea4f0f 100644 --- a/admin/scss/_forms.scss +++ b/admin/scss/_forms.scss @@ -812,4 +812,3 @@ fieldset.switch-states{ } //old web-kit browser fix @-webkit-keyframes bugfix { from { position: relative; } to { position: relative; } } - diff --git a/javascript/TabSet.js b/javascript/TabSet.js index 205384a12..d3b78f2bf 100644 --- a/javascript/TabSet.js +++ b/javascript/TabSet.js @@ -2,19 +2,8 @@ $.entwine('ss', function($){ /** * Lightweight wrapper around jQuery UI tabs for generic tab set-up - * and special rules for two other use cases (ss-ui-action-tabset): - * * Site tree action tabs (to perform actions on the site tree) - * * Actions menu (Edit page actions) */ $('.ss-tabset').entwine({ - - /****************************************************** - * Lightweight wrapper around jQuery UI tabs: - * * onadd - * * onremove - * * redrawTabs - * * rewriteHashlinks - *******************************************************/ onadd: function() { // Can't name redraw() as it clashes with other CMS entwine classes this.redrawTabs(); @@ -27,11 +16,6 @@ redrawTabs: function() { this.rewriteHashlinks(); this.tabs(); - - //Apply special behaviour to ss-ui-action-tabset - if(this.hasClass('ss-ui-action-tabset')){ - this.actionTabs(); - } }, /** @@ -46,226 +30,7 @@ if(!matches) return; $(this).attr('href', document.location.href.replace(/#.*/, '') + matches[0]); }); - }, - - /*************************************************** - * Custom functionality for special action tabsets - * * actionTabs - * * siteTreeActions - * * actionsMenu - * * closeTabs - * * riseUp - ***************************************************/ - - /* - Apply generic rules for action tabs (collapsible, rise, and close), - then call specific functions to handle each type of action tab - */ - actionTabs: function(){ - var that = this; - - //Set actionTabs to allow closing and be closed by default - this.tabs( - 'option', - 'collapsible', - true - ).tabs('option', 'active', false); - - - //Call close function on beforeactivate event - this.on( "tabsbeforeactivate", function(event, ui) { - that.closeTabs(event, ui); - }); - - // Call riseUp funciton on befporeactivate to check if tabs should - // open upwards (based on available space) and adjust - this.on( "tabsbeforeactivate", function(event, ui) { - that.riseUp(event, ui); - }); - - // Apply special behaviour depending on whether tabs are - // sitetree actions, or an actionmenu - if(this.parents('.cms-content-actions')){ - this.actionsMenu(); - } else if(this.hasClass('cms-actions-row')){ - this.siteTreeActions(); - } - }, - - /* - * Apply custom rules to the Actions Menu - * Currently includes positioning logic - */ - actionsMenu: function(){ - this.tabs({ - beforeActivate:function(event, ui){ //Set options before tab activated (but after clicked) - var activePanel = ui.newPanel; //panel about to open - var activeTab = ui.newTab; //tab nav item about to become active - - //Set the position of the opening tab (if it exists) - if($(activePanel).length > 0){ - $(activePanel).css('left', activeTab.position().left+"px"); - } - } - }); - }, - - /* - Apply rules to the siteTree actions. These action panels should - recieve different positions and classes depending on whether they are - appearing in the full page site tree view, or in the sidebar - */ - siteTreeActions: function(){ - var that = this; - var container = this.parent().parent(); - - //Remove open classes on beforeactivate - this.on( "tabsbeforeactivate", function(event, ui) { - // Remove tabset open classes (last gets a unique class - // in the bigger sitetree, so remove this too) - $(that).closest('.ss-ui-action-tabset') - .removeClass('tabset-open') - .removeClass('tabset-open-last'); - }); - - /* Apply specific rules if the actions panel appears in the side-bar - * Includes: - * * a hover helper class for animation, - * * reseting positioning of panels - */ - if($(container).hasClass('cms-tree-view-sidebar')){ - /* If actions panel is within the sidebar, apply active class - to help animate open/close on hover */ - $('.ui-tabs-nav li').hover(function(){ - $(this).parent().find('li .active').removeClass('active'); - $(this).find('a').addClass('active'); - }); - - /* Reset position of tabs, else anyone going between the large - and the small sitetree will see broken tabs */ - this.tabs({ - // Note: beforeActivate runs when a tab is clicked, - // but before it is visible. - beforeActivate:function(event, ui){ - var activePanel = ui.newPanel; //the new active panel - - //Apply styles with css, to avoid overriding currently applied styles - $(activePanel).css({'left': 'auto', 'right': 'auto'}); //reset left and right positioning - - if($(activePanel).length > 0){ - $(activePanel).parent().addClass('tabset-open'); - } - } - }); - }else{ - /* If the tabs are in the full site tree view, do some - positioning so tabPanel stays with relevent tab */ - this.tabs({ - beforeActivate:function(event, ui){ - var activePanel = ui.newPanel; - var activeTab = ui.newTab; - - if($(activePanel).length > 0){ - if($(activeTab).hasClass("last")){ - // Align open tab to the right (because opened tab is last) - $(activePanel).css({'left': 'auto', 'right': '0px'}); - - //last needs to be styled differently when open, so apply a unique class - $(activePanel).parent().addClass('tabset-open-last'); - }else{ - //Assign position to tabpanel based on position of relivent activeTab item - $(activePanel).css('left', activeTab.position().left+"px"); - - // If this is the first tab, make sure the position doesn't include border - // (hard set position to 0 ), and add the tab-set open class - if($(activeTab).hasClass("first")){ - $(activePanel).css('left',"0px"); - $(activePanel).parent().addClass('tabset-open'); - } - } - } - } - }); - } - }, - - /* - * Generic function to close open tabs when something other than - * the open tab is clicked. Stores event in a handler, and removes - * the bound event once activated. Used by ss-ui-action-tabset. - * - * Note: Should be called by a tabsbeforeactivate event - */ - closeTabs: function(event, ui){ - var that = this; - var frame = $('.cms').find('iframe'); //get all iframes on the page - - // Create a handler for the click event so we can close tabs - // and easily remove the event once done - var closeHandler = function(event){ - //close open tab - if (!$(event.target).closest(that).length) { - that.tabs('option', 'active', false); // close tabs - - //remove click event from objects it is bound to (iframe's and document) - var frame = $('.cms').find('iframe'); - frame.each(function(index, iframe){ - $(iframe).contents().off('click', closeHandler); - }); - $(document).off('click', closeHandler); - }; - } - - //Bind click event to document, and use closeHandler to handle the event - $(document).on('click', closeHandler); - // Make sure iframe click also closes tab - // iframe needs a special case, else the click event will not register here - if(frame.length > 0){ - frame.each(function(index, iframe){ - $(iframe).contents().on('click', closeHandler); - }); - } - }, - /***************************************************************** - * Function riseUp checks to see if a tab should be opened upwards - * (based on space concerns). If true, the rise-up class is applied - * and a new position is calculated and applied to the element. - * - * Note: Should be called by a tabsbeforeactivate event - ******************************************************************/ - riseUp: function(event, ui){ - - // Get the numbers needed to calculate positions - var elHeight = $(this).find('.ui-tabs-panel').outerHeight(); - var trigger = $(this).find('.ui-tabs-nav').outerHeight(); - var endOfWindow = ($(window).height() + $(document).scrollTop()) - trigger; - var elPos = $(this).find('.ui-tabs-nav').offset().top; - - var activePanel = ui.newPanel; - var activeTab = ui.newTab; - - if (elPos + elHeight >= endOfWindow && elPos - elHeight > 0){ - this.addClass('rise-up'); - - if (activeTab.position() != null){ - var topPosition = -activePanel.outerHeight(); - var containerSouth = activePanel.parents('.south'); - if (containerSouth){ - //If container is the southern panel, make tab appear from the top of the container - var padding = activeTab.offset().top - containerSouth.offset().top; - topPosition = topPosition-padding; - } - $(activePanel).css('top',topPosition+"px"); - } - } else { - //else remove the rise-up class and set top to 0 - this.removeClass('rise-up'); - if (activeTab.position() != null){ - $(activePanel).css('top','0px'); - } - } - return false; - } + } }); }); })(jQuery);