diff --git a/css/backoffice/layout/tab-container/_tab-container.scss b/css/backoffice/layout/tab-container/_tab-container.scss index 66d1b71d4..5221f7e66 100644 --- a/css/backoffice/layout/tab-container/_tab-container.scss +++ b/css/backoffice/layout/tab-container/_tab-container.scss @@ -38,6 +38,14 @@ $ibo-tab-container--tab-container--label--spacing: 20px !default; $ibo-tab-container--tab-container--label--margin-bottom: 20px !default; $ibo-tab-container--tab-container--label--span--margin-left: 40px !default; +$ibo-tab--temporary-remote-content-max-height: 300px !default; + +$ibo-tab--temporary-remote-content--button--color: $ibo-color-grey-800 !default; + +$ibo-tab--temporary-remote-content--button--hover--opacity: 0.5 !default; +$ibo-tab--temporary-remote-content--button--hover--background-color: $ibo-color-grey-900 !default; +$ibo-tab--temporary-remote-content--button--hover--color: $ibo-color-grey-200 !default; + /* Rules */ .ibo-tab-container--tabs-list { position: relative; @@ -116,7 +124,12 @@ $ibo-tab-container--tab-container--label--span--margin-left: 40px !default; padding: $ibo-tab-container--tab-container--padding-y $ibo-tab-container--tab-container--padding-x; } +.ibo-is-scrollable .ibo-tab-container--tab-container--label{ + display: block; +} + .ibo-tab-container--tab-container--label{ + display: none; margin-bottom: $ibo-tab-container--tab-container--label--margin-bottom; overflow-x: hidden; > span{ @@ -144,4 +157,41 @@ $ibo-tab-container--tab-container--label--span--margin-left: 40px !default; left: 100%; margin-left: $ibo-tab-container--tab-container--label--spacing; } +} + +.ibo-tab--temporary-remote-content{ + position: relative; +} + +.ibo-tab--temporary-remote-content--placeholder{ + height: auto; + position: relative; + max-height: $ibo-tab--temporary-remote-content-max-height; + >svg{ + max-height: $ibo-tab--temporary-remote-content-max-height; + } +} + +.ibo-tab--temporary-remote-content--button{ + position: absolute; + top: 0; + display: flex; + justify-content: center; + align-content: center; + flex-direction: column; + text-align: center; + + height: 100%; + width: 100%; + + cursor: pointer; + @extend %ibo-font-ral-med-300; + + background-color: transparent; + color: $ibo-tab--temporary-remote-content--button--color; + &:hover{ + opacity: $ibo-tab--temporary-remote-content--button--hover--opacity; + background-color: $ibo-tab--temporary-remote-content--button--hover--background-color; + color: $ibo-tab--temporary-remote-content--button--hover--color; + } } \ No newline at end of file diff --git a/dictionaries/ui/layouts/en.dictionary.itop.tab-container.php b/dictionaries/ui/layouts/en.dictionary.itop.tab-container.php index e8b661324..0892bca76 100644 --- a/dictionaries/ui/layouts/en.dictionary.itop.tab-container.php +++ b/dictionaries/ui/layouts/en.dictionary.itop.tab-container.php @@ -6,4 +6,5 @@ Dict::Add('EN US', 'English', 'English', [ 'UIBlock:Error:AddBlockNotTabForbidden' => 'Cannot add block %1$s to %2$s (only Tab blocks are allowed)', + 'UIBlock:TabContainer:RemoteTabLoad' => 'Click to load this tab', ]); diff --git a/images/placeholders/skeleton.svg b/images/placeholders/skeleton.svg new file mode 100644 index 000000000..8c945d230 --- /dev/null +++ b/images/placeholders/skeleton.svg @@ -0,0 +1,87 @@ + + Loading... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/js/layouts/tab-container/scrollable-tabs.js b/js/layouts/tab-container/scrollable-tabs.js index 69c53597a..3eef3427a 100644 --- a/js/layouts/tab-container/scrollable-tabs.js +++ b/js/layouts/tab-container/scrollable-tabs.js @@ -4,10 +4,25 @@ $.widget( "itop.scrollabletabs", $.ui.tabs, { tab_toggler: '[data-role="ibo-tab-container--tab-toggler"]', tab_container_list: '[data-role="ibo-tab-container--tab-container-list"]' }, + options: + { + remote_tab_load_dict: 'Click to load', + remotePanelCreated: function( panel, tab , placeholder ) { + if(tab.attr('data-role') === 'ibo-tab-container--tab-header') + { + panel.prepend('
' + tab.text() + '
'); + var oTempDiv = $('
').addClass('ibo-tab--temporary-remote-content') + var oPlaceholder = $('
').addClass('ibo-tab--temporary-remote-content--placeholder').load(GetAbsoluteUrlAppRoot()+'images/placeholders/skeleton.svg'); + var oLoadButton = $('
').addClass('ibo-tab--temporary-remote-content--button').text(placeholder).on('click', function(){tab.find('a').click()}) + oTempDiv.append(oPlaceholder) + oTempDiv.append(oLoadButton) + panel.append(oTempDiv); + } + }, + }, controller: null, _create: function() { var me = this; - // Initialize a single controller for this tab container this.controller = new ScrollMagic.Controller({'container': '#' + this.element.find(this.js_selectors.tab_container_list).attr('id'), 'refreshInterval' : 200}); @@ -20,12 +35,12 @@ $.widget( "itop.scrollabletabs", $.ui.tabs, { this._super(this.options); - // Load remote tabs as soon as possible + //Load remote tabs as soon as possible $(this.js_selectors.tab_toggler).each(function() { var that = this; if($(that).attr('href').charAt(0) !== '#') { var index = $(this).parent('li').prevAll().length - me.load(index); + //me.load(index); } }); @@ -173,7 +188,169 @@ $.widget( "itop.scrollabletabs", $.ui.tabs, { tabIndex: 0 } ); }, - + // jQuery UI overload +// Trigger a new event + _processTabs: function() { + var that = this, + prevTabs = this.tabs, + prevAnchors = this.anchors, + prevPanels = this.panels; + + this.tablist = this._getList().attr( "role", "tablist" ); + this._addClass( this.tablist, "ui-tabs-nav", + "ui-helper-reset ui-helper-clearfix ui-widget-header" ); + + // Prevent users from focusing disabled tabs via click + this.tablist + .on( "mousedown" + this.eventNamespace, "> li", function( event ) { + if ( $( this ).is( ".ui-state-disabled" ) ) { + event.preventDefault(); + } + } ) + + // Support: IE <9 + // Preventing the default action in mousedown doesn't prevent IE + // from focusing the element, so if the anchor gets focused, blur. + // We don't have to worry about focusing the previously focused + // element since clicking on a non-focusable element should focus + // the body anyway. + .on( "focus" + this.eventNamespace, ".ui-tabs-anchor", function() { + if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) { + this.blur(); + } + } ); + + this.tabs = this.tablist.find( "> li:has(a[href])" ) + .attr( { + role: "tab", + tabIndex: -1 + } ); + this._addClass( this.tabs, "ui-tabs-tab", "ui-state-default" ); + + this.anchors = this.tabs.map( function() { + return $( "a", this )[ 0 ]; + } ) + .attr( { + tabIndex: -1 + } ); + this._addClass( this.anchors, "ui-tabs-anchor" ); + + this.panels = $(); + + this.anchors.each( function( i, anchor ) { + var selector, panel, panelId, + anchorId = $( anchor ).uniqueId().attr( "id" ), + tab = $( anchor ).closest( "li" ), + originalAriaControls = tab.attr( "aria-controls" ); + + // Inline tab + if ( that._isLocal( anchor ) ) { + selector = anchor.hash; + panelId = selector.substring( 1 ); + panel = that.element.find( that._sanitizeSelector( selector ) ); + + // remote tab + } else { + + // If the tab doesn't already have aria-controls, + // generate an id by using a throw-away element + panelId = tab.attr( "aria-controls" ) || $( {} ).uniqueId()[ 0 ].id; + selector = "#" + panelId; + panel = that.element.find( selector ); + if ( !panel.length ) { + panel = that._createPanel( panelId ); + panel.insertAfter( that.panels[ i - 1 ] || that.tablist ); + that.options.remotePanelCreated(panel, tab, that.options.remote_tab_load_dict); + } + panel.attr( "aria-live", "polite" ); + } + + if ( panel.length ) { + that.panels = that.panels.add( panel ); + } + if ( originalAriaControls ) { + tab.data( "ui-tabs-aria-controls", originalAriaControls ); + } + tab.attr( { + "aria-controls": panelId, + "aria-labelledby": anchorId + } ); + panel.attr( "aria-labelledby", anchorId ); + } ); + + this.panels.attr( "role", "tabpanel" ); + this._addClass( this.panels, "ui-tabs-panel", "ui-widget-content" ); + + // Avoid memory leaks (#10056) + if ( prevTabs ) { + this._off( prevTabs.not( this.tabs ) ); + this._off( prevAnchors.not( this.anchors ) ); + this._off( prevPanels.not( this.panels ) ); + } + }, + // jQuery UI overload +// Append content to panel instead of replacing all html + load: function( index, event ) { + index = this._getIndex( index ); + var that = this, + tab = this.tabs.eq( index ), + anchor = tab.find( ".ui-tabs-anchor" ), + panel = this._getPanelForTab( tab ), + eventData = { + tab: tab, + panel: panel + }, + complete = function( jqXHR, status ) { + if ( status === "abort" ) { + that.panels.stop( false, true ); + } + + that._removeClass( tab, "ui-tabs-loading" ); + panel.removeAttr( "aria-busy" ); + + if ( jqXHR === that.xhr ) { + delete that.xhr; + } + }; + + // Not remote + if ( this._isLocal( anchor[ 0 ] ) ) { + return; + } + + this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) ); + + // Support: jQuery <1.8 + // jQuery <1.8 returns false if the request is canceled in beforeSend, + // but as of 1.8, $.ajax() always returns a jqXHR object. + if ( this.xhr && this.xhr.statusText !== "canceled" ) { + this._addClass( tab, "ui-tabs-loading" ); + panel.attr( "aria-busy", "true" ); + + this.xhr + .done( function( response, status, jqXHR ) { + + // support: jQuery <1.8 + // http://bugs.jquery.com/ticket/11778 + setTimeout( function() { + var tempdiv = $('
').addClass('ibo-tab').html(response); + panel.find('.ibo-tab--temporary-remote-content').remove(); + panel.append( tempdiv ); + that._trigger( "load", event, eventData ); + + complete( jqXHR, status ); + }, 1 ); + } ) + .fail( function( jqXHR, status ) { + + // support: jQuery <1.8 + // http://bugs.jquery.com/ticket/11778 + setTimeout( function() { + complete( jqXHR, status ); + }, 1 ); + } ); + } + }, // Set the current tab information setTab : function(tab){ this.active = tab; diff --git a/js/layouts/tab-container/tab-container.js b/js/layouts/tab-container/tab-container.js index b11be02a9..38644ff2b 100644 --- a/js/layouts/tab-container/tab-container.js +++ b/js/layouts/tab-container/tab-container.js @@ -23,6 +23,7 @@ $(function() // default options options: { + remote_tab_load_dict: 'Click to load', }, css_classes: { @@ -64,7 +65,8 @@ $(function() classes: { 'ui-tabs-panel': 'ibo-tab-container--tab-container', // For ajax tabs, so their containers have the right CSS classes }, - active: $.bbq.getState( this.element.attr('id'), true ) || 0 + active: $.bbq.getState( this.element.attr('id'), true ) || 0, + remote_tab_load_dict: this.options.remote_tab_load_dict }; if ($.bbq) { // Enable tabs on all tab widgets. The `event` property must be overridden so diff --git a/templates/base/layouts/tab-container/layout.html.twig b/templates/base/layouts/tab-container/layout.html.twig index 74d578a08..f205446c3 100644 --- a/templates/base/layouts/tab-container/layout.html.twig +++ b/templates/base/layouts/tab-container/layout.html.twig @@ -47,14 +47,12 @@ {% block iboTabContainerTabsContainers %} {% if not aPage.isPrintable %} -
+
{% for oTab in oUIBlock.GetSubBlocks() %} {% if oTab.GetType() == constant('TabManager::ENUM_TAB_TYPE_HTML') %}
- {% if oUIBlock.GetIsScrollable() %} -
{{ oTab.GetTitle() }}
- {% endif %} +
{{ oTab.GetTitle() }}
{{ render_block(oTab, {aPage: aPage}) }}
{% endif %} diff --git a/templates/base/layouts/tab-container/layout.js.twig b/templates/base/layouts/tab-container/layout.js.twig index 82613cc15..dc51e0cf6 100644 --- a/templates/base/layouts/tab-container/layout.js.twig +++ b/templates/base/layouts/tab-container/layout.js.twig @@ -2,7 +2,7 @@ {# @license http://opensource.org/licenses/AGPL-3.0 #} {% if not aPage.isPrintable %} - $('#{{ oUIBlock.GetId() }}').tab_container(); + $('#{{ oUIBlock.GetId() }}').tab_container({'remote_tab_load_dict': '{{ 'UIBlock:TabContainer:RemoteTabLoad'|dict_s|escape('js') }}'}); {% else %} {% for oTab in oUIBlock.GetSubBlocks() %} oHiddeableChapters['tab_{{ oTab.GetId()|sanitize(constant('utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER')) }}'] = '{{ oTab.GetTitle()|escape('js') }}';