diff --git a/js/layouts/tab-container/regular-tabs.js b/js/layouts/tab-container/regular-tabs.js new file mode 100644 index 000000000..962332b92 --- /dev/null +++ b/js/layouts/tab-container/regular-tabs.js @@ -0,0 +1,115 @@ +$.widget( "itop.regulartabs", $.ui.tabs, { + js_selectors: + { + tab_container_list: '[data-role="ibo-tab-container--tab-container-list"]' + }, + // jQuery UI overload + _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 ); + // If we can't attach to an other tab, try to get tab-container-list right after + if( that.panels[ i - 1 ] ) { + panel.insertAfter( that.panels[ i - 1 ] ); + } + else if( that.element.find(that.js_selectors.tab_container_list) ) { + that.element.find(that.js_selectors.tab_container_list).append( panel ); + } + else { + panel.insertAfter( that.tablist ); + + } + } + 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 ) ); + } + }, + +}); \ 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 3a9197492..ab0af24a8 100644 --- a/js/layouts/tab-container/scrollable-tabs.js +++ b/js/layouts/tab-container/scrollable-tabs.js @@ -251,7 +251,17 @@ $.widget( "itop.scrollabletabs", $.ui.tabs, { panel = that.element.find( selector ); if ( !panel.length ) { panel = that._createPanel( panelId ); - panel.insertAfter( that.panels[ i - 1 ] || that.tablist ); + // If we can't attach to an other tab, try to get tab-container-list right after + if( that.panels[ i - 1 ] ) { + panel.insertAfter( that.panels[ i - 1 ] ); + } + else if( that.element.find(that.js_selectors.tab_container_list) ) { + that.element.find(that.js_selectors.tab_container_list).append( panel ); + } + else { + panel.insertAfter( that.tablist ); + + } that.options.remotePanelCreated(panel, tab, that.options.remote_tab_load_dict); } panel.attr( "aria-live", "polite" ); diff --git a/js/layouts/tab-container/tab-container.js b/js/layouts/tab-container/tab-container.js index e4440d72c..42cd96ead 100644 --- a/js/layouts/tab-container/tab-container.js +++ b/js/layouts/tab-container/tab-container.js @@ -75,7 +75,7 @@ $(function() if (this.element.hasClass(this.css_classes.is_scrollable)) { this.element.scrollabletabs(oParams); } else { - this.element.tabs(oParams); + this.element.regulartabs(oParams); } }, // events bound via _bind are removed automatically diff --git a/sources/application/UI/Base/Layout/TabContainer/TabContainer.php b/sources/application/UI/Base/Layout/TabContainer/TabContainer.php index 52c6f85c5..080b9f667 100644 --- a/sources/application/UI/Base/Layout/TabContainer/TabContainer.php +++ b/sources/application/UI/Base/Layout/TabContainer/TabContainer.php @@ -42,6 +42,7 @@ class TabContainer extends UIContentBlock public const DEFAULT_JS_TEMPLATE_REL_PATH = 'base/layouts/tab-container/layout'; public const DEFAULT_JS_FILES_REL_PATH = [ 'js/layouts/tab-container/tab-container.js', + 'js/layouts/tab-container/regular-tabs.js', 'js/layouts/tab-container/scrollable-tabs.js' ];