mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 07:24:13 +01:00
N°3560 Add placeholder and a button to load remote tabs in scroll mode
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
]);
|
||||
|
||||
87
images/placeholders/skeleton.svg
Normal file
87
images/placeholders/skeleton.svg
Normal file
@@ -0,0 +1,87 @@
|
||||
<svg
|
||||
role="img"
|
||||
aria-labelledby="loading-aria"
|
||||
viewBox="0 0 1000 300"
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
<title id="loading-aria">Loading...</title>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
clip-path="url(#clip-path)"
|
||||
style='fill: url("#fill");'
|
||||
></rect>
|
||||
<defs>
|
||||
<clipPath id="clip-path">
|
||||
<rect x="0" y="0" rx="0" ry="0" width="NaN" height="NaN" />
|
||||
<rect x="36" y="16" rx="5" ry="5" width="326" height="15" />
|
||||
<circle cx="14" cy="68" r="12" />
|
||||
<rect x="36" y="61" rx="5" ry="5" width="326" height="15" />
|
||||
<circle cx="13" cy="26" r="12" />
|
||||
<rect x="405" y="16" rx="0" ry="0" width="163" height="146" />
|
||||
<circle cx="45" cy="135" r="42" />
|
||||
<rect x="103" y="104" rx="5" ry="5" width="156" height="15" />
|
||||
<rect x="104" y="146" rx="5" ry="5" width="257" height="15" />
|
||||
<rect x="584" y="14" rx="5" ry="5" width="326" height="15" />
|
||||
<rect x="922" y="14" rx="5" ry="5" width="69" height="15" />
|
||||
<rect x="584" y="46" rx="5" ry="5" width="326" height="15" />
|
||||
<rect x="922" y="46" rx="5" ry="5" width="69" height="15" />
|
||||
<rect x="584" y="78" rx="5" ry="5" width="326" height="15" />
|
||||
<rect x="922" y="78" rx="5" ry="5" width="69" height="15" />
|
||||
<rect x="584" y="110" rx="5" ry="5" width="326" height="15" />
|
||||
<rect x="922" y="110" rx="5" ry="5" width="69" height="15" />
|
||||
<rect x="440" y="186" rx="5" ry="5" width="326" height="15" />
|
||||
<circle cx="418" cy="238" r="12" />
|
||||
<rect x="440" y="231" rx="5" ry="5" width="326" height="15" />
|
||||
<circle cx="417" cy="196" r="12" />
|
||||
<rect x="410" y="269" rx="5" ry="5" width="352" height="27" />
|
||||
<rect x="805" y="176" rx="0" ry="0" width="163" height="146" />
|
||||
<circle cx="44" cy="250" r="42" />
|
||||
<rect x="102" y="219" rx="5" ry="5" width="156" height="15" />
|
||||
<rect x="103" y="261" rx="5" ry="5" width="257" height="15" />
|
||||
</clipPath>
|
||||
<linearGradient id="fill">
|
||||
<stop
|
||||
offset="0.599964"
|
||||
stop-color="#f3f3f3"
|
||||
stop-opacity="1"
|
||||
>
|
||||
<animate
|
||||
attributeName="offset"
|
||||
values="-2; -2; 1"
|
||||
keyTimes="0; 0.25; 1"
|
||||
dur="2s"
|
||||
repeatCount="indefinite"
|
||||
></animate>
|
||||
</stop>
|
||||
<stop
|
||||
offset="1.59996"
|
||||
stop-color="#ecebeb"
|
||||
stop-opacity="1"
|
||||
>
|
||||
<animate
|
||||
attributeName="offset"
|
||||
values="-1; -1; 2"
|
||||
keyTimes="0; 0.25; 1"
|
||||
dur="2s"
|
||||
repeatCount="indefinite"
|
||||
></animate>
|
||||
</stop>
|
||||
<stop
|
||||
offset="2.59996"
|
||||
stop-color="#f3f3f3"
|
||||
stop-opacity="1"
|
||||
>
|
||||
<animate
|
||||
attributeName="offset"
|
||||
values="0; 0; 3"
|
||||
keyTimes="0; 0.25; 1"
|
||||
dur="2s"
|
||||
repeatCount="indefinite"
|
||||
></animate>
|
||||
</stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
@@ -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('<div class="ibo-tab-container--tab-container--label"><span>' + tab.text() + '</span></div>');
|
||||
var oTempDiv = $('<div>').addClass('ibo-tab--temporary-remote-content')
|
||||
var oPlaceholder = $('<div>').addClass('ibo-tab--temporary-remote-content--placeholder').load(GetAbsoluteUrlAppRoot()+'images/placeholders/skeleton.svg');
|
||||
var oLoadButton = $('<div>').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 = $('<div>').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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -47,14 +47,12 @@
|
||||
|
||||
{% block iboTabContainerTabsContainers %}
|
||||
{% if not aPage.isPrintable %}
|
||||
<div id="{{ oUIBlock.GetId() }}--tab-container-list" class="ibo-tab-container--tab-container-list" data-role="ibo-tab-container--tab-container-list">
|
||||
<div id="{{ oUIBlock.GetId() }}--tab-container-list" class="ibo-tab-container--tab-container-list{% if oUIBlock.GetIsScrollable() %} ibo-is-scrollable{% endif %}" data-role="ibo-tab-container--tab-container-list">
|
||||
{% for oTab in oUIBlock.GetSubBlocks() %}
|
||||
{% if oTab.GetType() == constant('TabManager::ENUM_TAB_TYPE_HTML') %}
|
||||
<div id="tab_{{ oTab.GetId()|sanitize(constant('utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER')) }}"
|
||||
class="ibo-tab-container--tab-container">
|
||||
{% if oUIBlock.GetIsScrollable() %}
|
||||
<div class="ibo-tab-container--tab-container--label"><span>{{ oTab.GetTitle() }}</span></div>
|
||||
{% endif %}
|
||||
<div class="ibo-tab-container--tab-container--label"><span>{{ oTab.GetTitle() }}</span></div>
|
||||
{{ render_block(oTab, {aPage: aPage}) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -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') }}';
|
||||
|
||||
Reference in New Issue
Block a user