mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-12 23:14:18 +01:00
N°4021 - Introduce sticky header for panels and object details (tab container to be done)
This commit is contained in:
170
js/components/panel.js
Normal file
170
js/components/panel.js
Normal file
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2021 Combodo SARL
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
;
|
||||
$(function () {
|
||||
// the widget definition, where 'itop' is the namespace,
|
||||
// 'panel' the widget name
|
||||
$.widget('itop.panel',
|
||||
{
|
||||
// default options
|
||||
options:
|
||||
{
|
||||
// The viewport element (jQuery object) to consider for the panel
|
||||
viewport_elem: null,
|
||||
// Whether the header should stay visible or not during the scroll in the "viewport_elem"
|
||||
is_header_visible_on_scroll: false,
|
||||
},
|
||||
css_classes:
|
||||
{
|
||||
has_sticky_header: 'ibo-has-sticky-header',
|
||||
is_sticking: 'ibo-is-sticking',
|
||||
sticky_sentinel: 'ibo-sticky-sentinel',
|
||||
sticky_sentinel_top: 'ibo-sticky-sentinel-top',
|
||||
sticky_sentinel_bottom: 'ibo-sticky-sentinel-bottom',
|
||||
},
|
||||
js_selectors:
|
||||
{
|
||||
modal: '.ui-dialog',
|
||||
modal_content: '.ui-dialog-content',
|
||||
panel_header: '[data-role="ibo-panel--header"]:first',
|
||||
panel_header_sticky_sentinel_top: '[data-role="ibo-panel--header--sticky-sentinel-top"]',
|
||||
panel_header_sticky_sentinel_bottom: '[data-role="ibo-panel--header--sticky-sentinel-bottom"]',
|
||||
},
|
||||
// {IntersectionObserver} Observer for the sticky header
|
||||
sticky_header_observer: null,
|
||||
|
||||
// the constructor
|
||||
_create: function () {
|
||||
this._initializeMarkup();
|
||||
this._bindEvents();
|
||||
},
|
||||
// events bound via _bind are removed automatically
|
||||
// revert other modifications here
|
||||
_destroy: function () {
|
||||
},
|
||||
_initializeMarkup: function () {
|
||||
const me = this;
|
||||
|
||||
if (this._isHeaderVisibleOnScroll()) {
|
||||
this.element.addClass(this.css_classes.has_sticky_header);
|
||||
|
||||
// Add sentinels to the markup to detect when the element changes between scrolling & sticked states
|
||||
$('<div></div>')
|
||||
.addClass(this.css_classes.sticky_sentinel)
|
||||
.addClass(this.css_classes.sticky_sentinel_top)
|
||||
.attr('data-role', 'ibo-panel--header--sticky-sentinel-top')
|
||||
.prependTo(this.element);
|
||||
$('<div></div>')
|
||||
.addClass(this.css_classes.sticky_sentinel)
|
||||
.addClass(this.css_classes.sticky_sentinel_bottom)
|
||||
.attr('data-role', 'ibo-panel--header--sticky-sentinel-bottom')
|
||||
.appendTo(this.element);
|
||||
}
|
||||
},
|
||||
_bindEvents: function () {
|
||||
const me = this;
|
||||
const oBodyElem = $('body');
|
||||
|
||||
if (this._isHeaderVisibleOnScroll()) {
|
||||
this._observeStickyHeaderChanges();
|
||||
|
||||
// When a modal opens, it could have been for this panel. As the panel is moved into the modal's markup after this JS widget is instantiated
|
||||
// the viewport is not the right one and we need to update it.
|
||||
oBodyElem.on('dialogopen', function(){
|
||||
me._observeStickyHeaderChanges();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Stickey header helpers
|
||||
/**
|
||||
* Instantiate an observer on the header to toggle its "sticky" state and fire an event
|
||||
* @private
|
||||
*/
|
||||
_observeStickyHeaderChanges: function () {
|
||||
const me = this;
|
||||
|
||||
// Determine in which kind of container the panel is
|
||||
let oNewViewportElem = null;
|
||||
// - In a modal
|
||||
if (this.element.closest(this.js_selectors.modal_content).length > 0) {
|
||||
oNewViewportElem = this.element.closest(this.js_selectors.modal_content)[0];
|
||||
}
|
||||
// - In a standard page
|
||||
else if (this.element.closest('#ibo-center-container').length > 0) {
|
||||
oNewViewportElem = this.element.closest('#ibo-center-container')[0];
|
||||
}
|
||||
|
||||
// If viewport hasn't changed, there is no need to refresh the observer
|
||||
if (oNewViewportElem === this.options.viewport_elem) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the reference viewport
|
||||
this.options.viewport_elem = oNewViewportElem;
|
||||
|
||||
// Clean observer if there was already one
|
||||
if (null !== this.sticky_header_observer) {
|
||||
this.sticky_header_observer.disconnect();
|
||||
}
|
||||
|
||||
// Prepare observer options
|
||||
const oOptions = {
|
||||
root: this.options.viewport_elem,
|
||||
threshold: 0,
|
||||
};
|
||||
|
||||
// Instantiate observer and callback for the top sentinel
|
||||
this.sticky_header_observer = new IntersectionObserver(function (aEntries, oObserver) {
|
||||
for (const oEntry of aEntries) {
|
||||
const oSentinelInfo = oEntry.boundingClientRect;
|
||||
const oRootInfo = oEntry.rootBounds;
|
||||
|
||||
// Started sticking.
|
||||
if (oSentinelInfo.bottom < oRootInfo.top) {
|
||||
me._onHeaderBecomesSticky();
|
||||
|
||||
}
|
||||
|
||||
// Stopped sticking.
|
||||
if (oSentinelInfo.bottom >= oRootInfo.top &&
|
||||
oSentinelInfo.bottom < oRootInfo.bottom) {
|
||||
me._onHeaderStopsBeingSticky();
|
||||
}
|
||||
}
|
||||
}, oOptions);
|
||||
this.sticky_header_observer.observe(this.element.find(this.js_selectors.panel_header_sticky_sentinel_top)[0]);
|
||||
},
|
||||
_onHeaderBecomesSticky: function () {
|
||||
this.element.find(this.js_selectors.panel_header).addClass(this.css_classes.is_sticking);
|
||||
},
|
||||
_onHeaderStopsBeingSticky: function () {
|
||||
this.element.find(this.js_selectors.panel_header).removeClass(this.css_classes.is_sticking);
|
||||
},
|
||||
|
||||
// Helpers
|
||||
/**
|
||||
* @return {boolean} True if the panel should have its header visible during scroll
|
||||
* @private
|
||||
*/
|
||||
_isHeaderVisibleOnScroll: function () {
|
||||
return this.options.is_header_visible_on_scroll;
|
||||
},
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user