diff --git a/css/backoffice/blocks-integrations/_panel-within-main-content.scss b/css/backoffice/blocks-integrations/_panel-within-main-content.scss new file mode 100644 index 000000000..f6907e8a7 --- /dev/null +++ b/css/backoffice/blocks-integrations/_panel-within-main-content.scss @@ -0,0 +1,29 @@ +/*! + * @copyright Copyright (C) 2010-2021 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ + +$ibo-panel-within-main-content--sticky-sentinel-top--top: -1 * $ibo-main-content--padding-top !default; +$ibo-panel-within-main-content--sticky-sentinel-top--height: $ibo-main-content--padding-top !default; + +$ibo-panel-within-main-content--header--top--is-sticky: -1 * $ibo-main-content--padding-top !default; + +#ibo-main-content { + /* + * Careful: Here we get all the "descendants" instead of the first closest panel in the main content, which could cause some glitches in the future. + * For now we decided to stay that way for the following reasons, if that changes in the future keep the repercussions on descendants panels in mind. + * - We don't have nested sticky panels (yet) + * - We don't want to hardcode the main content's markup selector if not necessary as it could change in the future (and is already different in read-only vs edition) + * - Unlike in JS, there no easy way to find the closest descendant + */ + .ibo-panel.ibo-has-sticky-header { + /* Stickable header rules */ + > .ibo-sticky-sentinel-top { + top: $ibo-panel-within-main-content--sticky-sentinel-top--top; + height: $ibo-panel-within-main-content--sticky-sentinel-top--height; + } + > .ibo-panel--header.ibo-is-sticking { + top: $ibo-panel-within-main-content--header--top--is-sticky; + } + } +} \ No newline at end of file diff --git a/css/backoffice/blocks-integrations/_panel-within-modal.scss b/css/backoffice/blocks-integrations/_panel-within-modal.scss new file mode 100644 index 000000000..7d84589b6 --- /dev/null +++ b/css/backoffice/blocks-integrations/_panel-within-modal.scss @@ -0,0 +1,24 @@ +/*! + * @copyright Copyright (C) 2010-2021 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ + +$ibo-panel-within-modal--header--top--is-sticky: -1 * $ibo-vendors-jqueryui--ui-dialog-content--padding-y !default; + +.ui-dialog-content { + /* + * Careful: Here we get all the "descendants" instead of the first closest panel in the modal, which could cause some glitches in the future. + * For now we decided to stay that way for the following reasons, if that changes in the future keep the repercussions on descendants panels in mind. + * - We don't have nested sticky panels (yet) + * - We don't want to hardcode the modal's markup selector if not necessary as it could change in the future (and is already different in read-only vs edition) + * - Unlike in JS, there no easy way to find the closest descendant + */ + .ibo-panel { + /* Sticky header rules */ + > .ibo-panel--header { + &.ibo-is-sticking { + top: $ibo-panel-within-modal--header--top--is-sticky; + } + } + } +} \ No newline at end of file diff --git a/css/backoffice/components/_panel.scss b/css/backoffice/components/_panel.scss index 0b69dc597..c9b9d801c 100644 --- a/css/backoffice/components/_panel.scss +++ b/css/backoffice/components/_panel.scss @@ -19,23 +19,28 @@ /* SCSS variables */ $ibo-panel--spacing-top: 24px !default; +/* - Base variables for the block */ +$ibo-panel--base-border-size: 1px !default; +$ibo-panel--base-border-style: solid !default; +$ibo-panel--base-border-color: $ibo-color-grey-400 !default; +$ibo-panel--base-border: $ibo-panel--base-border-size $ibo-panel--base-border-style $ibo-panel--base-border-color !default; + +$ibo-panel--base-transition-property: all !default; +$ibo-panel--base-transition-duration: 0.15s !default; +$ibo-panel--base-transition-timing-function: linear !default; +$ibo-panel--base-transition: $ibo-panel--base-transition-property $ibo-panel--base-transition-duration $ibo-panel--base-transition-timing-function !default; + +%ibo-panel--base-transition { + transition: $ibo-panel--base-transition; +} + +/* - Specific variables for the block */ $ibo-panel--header--margin-bottom: 4px !default; -$ibo-panel--icon--size: 48px !default; -$ibo-panel--icon--spacing: 16px !default; -$ibo-panel--icon--size-as-medallion: 72px !default; -$ibo-panel--icon--spacing--as-medallion: 16px !default; -$ibo-panel--icon--bottom--as-medallion: -24px !default; -$ibo-panel--icon--background-color--as-medallion: $ibo-color-grey-100 !default; -$ibo-panel--icon--border--as-medallion: 2px solid $ibo-color-blue-grey-300 !default; -$ibo-panel--icon--border-radius--as-medallion: $ibo-border-radius-full !default; +$ibo-panel--header--background-color--is-sticking: $ibo-color-grey-100 !default; +$ibo-panel--header--border--is-sticking: $ibo-panel--base-border !default; -$ibo-panel--icon-background--size--must-contain: contain !default; -$ibo-panel--icon-background--size--must-cover: cover !default; -$ibo-panel--icon-background--size--must-zoomout: 66.67% !default; - -$ibo-panel--title--color: $ibo-color-grey-900 !default; -$ibo-panel--subtitle--color: $ibo-color-grey-800 !default; +$ibo-panel--header--padding-y--is-sticking: 4px !default; $ibo-panel--highlight--width: 100% !default; $ibo-panel--highlight--height: 8px !default; @@ -47,8 +52,30 @@ $ibo-panel--body--padding-bottom: 24px !default; $ibo-panel--body--padding-top: $ibo-panel--body--padding-bottom + $ibo-panel--highlight--height !default; $ibo-panel--body--padding-x: 16px !default; $ibo-panel--body--border-radius: $ibo-border-radius-500 !default; -$ibo-panel--body--border-size: 1px !default; -$ibo-panel--body--border-color: $ibo-color-grey-400 !default; +$ibo-panel--body--border: $ibo-panel--base-border !default; + +$ibo-panel--icon--size: 48px !default; +$ibo-panel--icon--spacing: 16px !default; +$ibo-panel--icon--size-as-medallion: 72px !default; +$ibo-panel--icon--spacing--as-medallion: 16px !default; +$ibo-panel--icon--bottom--as-medallion: -24px !default; +$ibo-panel--icon--background-color--as-medallion: $ibo-color-grey-100 !default; +$ibo-panel--icon--border--as-medallion: 2px solid $ibo-color-blue-grey-300 !default; +$ibo-panel--icon--border-radius--as-medallion: $ibo-border-radius-full !default; + +$ibo-panel--icon--size-as-medallion--is-sticking: 48px !default; +$ibo-panel--icon--spacing--as-medallion--is-sticking: $ibo-panel--icon--spacing--as-medallion !default; +$ibo-panel--icon--bottom--as-medallion--is-sticking: -12px !default; +$ibo-panel--icon--border--as-medallion--is-sticking: 1px $ibo-panel--base-border-style $ibo-panel--base-border-color !default; + +$ibo-panel--icon-background--size--must-contain: contain !default; +$ibo-panel--icon-background--size--must-cover: cover !default; +$ibo-panel--icon-background--size--must-zoomout: 66.67% !default; + +$ibo-panel--title--font-size--is-sticking: $ibo-font-size-150 !default; +$ibo-panel--title--color: $ibo-color-grey-900 !default; +$ibo-panel--subtitle--font-size--is-sticking: $ibo-font-size-100 !default; +$ibo-panel--subtitle--color: $ibo-color-grey-800 !default; $ibo-panel--collapsible-toggler--margin-right: 8px !default; $ibo-panel--collapsible-toggler--font-size: $ibo-font-size-250 !default; @@ -95,6 +122,8 @@ $ibo-panel-colors: ( } .ibo-panel { + position: relative; + &.ibo-has-icon { .ibo-panel--titles { padding-left: $ibo-panel--icon--spacing; @@ -107,7 +136,7 @@ $ibo-panel-colors: ( margin-left: $ibo-panel--icon--spacing--as-medallion; } - // Note: Direct child selector is mandatory, otherwise a panel within a panel with a medallion icon will have its icon as a medallion too (eg. dashboard in an object) + // Note: Direct child selector is mandatory, otherwise a panel within a panel could be affected too when it shouldn't (eg. dashboard in an object, n:n panel) > .ibo-panel--header { .ibo-panel--header-left { .ibo-panel--icon { @@ -176,13 +205,13 @@ $ibo-panel-colors: ( .ibo-panel--title { display: inline-block; color: $ibo-panel--title--color; - @extend %ibo-font-ral-sembol-250; + @extend %ibo-font-ral-med-350; flex-grow: 1; } .ibo-panel--subtitle { display: flex; - @extend %ibo-font-ral-med-250; + @extend %ibo-font-ral-med-150; color: $ibo-panel--subtitle--color; } @@ -190,7 +219,7 @@ $ibo-panel-colors: ( position: relative; padding: $ibo-panel--body--padding-top $ibo-panel--body--padding-x $ibo-panel--body--padding-bottom $ibo-panel--body--padding-x; background-color: $ibo-panel--body--background-color; - border: solid $ibo-panel--body--border-size $ibo-panel--body--border-color; + border: $ibo-panel--body--border; border-radius: $ibo-panel--body--border-radius; overflow: hidden; /* To force highlight color to be cropped by the border radius */ @@ -226,4 +255,67 @@ $ibo-panel-colors: ( .ibo-panel--body { display: none; } +} + +/* Sticky header rules */ +.ibo-panel.ibo-has-sticky-header { + // Note: Direct child selector is mandatory, otherwise a panel within a panel could be affected too when it shouldn't (eg. dashboard in an object, n:n panel) + > .ibo-panel--header { + position: sticky; + z-index: 1; + top: 0; /* Default, stick on the top of the panel. Custom integrations should be done in the "blocks-integrations" partials */ + + /* All transitions should have a specific duration except for the header's "top" property otherwise it feels laggy */ + /* - The header itself */ + transition-property: $ibo-panel--base-transition-property, top; + transition-duration: $ibo-panel--base-transition-duration, 0s; + transition-timing-function: $ibo-panel--base-transition-timing-function; + /* - Impacted descendants (we don't put "*" as it can get shaky otherwise) */ + .ibo-panel--title, + .ibo-panel--subtitle, + .ibo-panel--icon, + .ibo-panel--titles{ + @extend %ibo-panel--base-transition; + } + + /* Transition rules */ + &.ibo-is-sticking { + padding-top: $ibo-panel--header--padding-y--is-sticking; + padding-bottom: $ibo-panel--header--padding-y--is-sticking; + background-color: $ibo-panel--header--background-color--is-sticking; + border: $ibo-panel--header--border--is-sticking; + align-items: center; + + .ibo-panel--title { + font-size: $ibo-panel--title--font-size--is-sticking; + } + .ibo-panel--subtitle { + font-size: $ibo-panel--subtitle--font-size--is-sticking; + } + + } + + } + + &.ibo-has-icon { + &.ibo-has-medallion-icon { + // Note: Direct child selector is mandatory, otherwise a panel within a panel could be affected too when it shouldn't (eg. dashboard in an object, n:n panel) + > .ibo-panel--header { + /* Transition rules */ + &.ibo-is-sticking { + .ibo-panel--icon { + bottom: $ibo-panel--icon--bottom--as-medallion--is-sticking; + width: $ibo-panel--icon--size-as-medallion--is-sticking; + height: $ibo-panel--icon--size-as-medallion--is-sticking; + min-width: $ibo-panel--icon--size-as-medallion--is-sticking; + min-height: $ibo-panel--icon--size-as-medallion--is-sticking; + border: $ibo-panel--icon--border--as-medallion--is-sticking; + } + .ibo-panel--titles { + padding-left: calc(#{$ibo-panel--icon--size-as-medallion--is-sticking} + #{$ibo-panel--icon--spacing--as-medallion--is-sticking}); + } + } + } + } + } } \ No newline at end of file diff --git a/css/backoffice/layout/object/_object-details.scss b/css/backoffice/layout/object/_object-details.scss index c3068d78d..8ed992e1f 100644 --- a/css/backoffice/layout/object/_object-details.scss +++ b/css/backoffice/layout/object/_object-details.scss @@ -10,6 +10,9 @@ $ibo-object-details--icon--background-color--as-medallion: $ibo-color-grey-100 ! $ibo-object-details--icon--border--as-medallion: 2px solid $ibo-color-blue-grey-300 !default; $ibo-object-details--icon--border-radius--as-medallion: $ibo-border-radius-full !default; +$ibo-object-details--icon--size--is-sticking: $ibo-panel--icon--size-as-medallion--is-sticking !default; +$ibo-object-details--icon--spacing--as-medallion--is-sticking: $ibo-object-details--icon--spacing--as-medallion !default; + $ibo-object-details--status-dot--size: 10px !default; $ibo-object-details--status-dot--spacing: 8px !default; $ibo-object-details--status-dot--border-radius: $ibo-border-radius-full !default; @@ -48,14 +51,6 @@ $ibo-object-details--tag--separator--border-radius: $ibo-border-radius-full !def } } } - - .ibo-panel--title { - @extend %ibo-font-ral-med-350; - } - - .ibo-panel--subtitle { - @extend %ibo-font-ral-med-150; - } } .ibo-object-details--status { @@ -122,3 +117,40 @@ $ibo-object-details--tag--separator--border-radius: $ibo-border-radius-full !def background-color: $ibo-object-details--tag--separator--background-color; } } + +/* Sticky header rules */ +.ibo-object-details.ibo-has-sticky-header { + // Note: Direct child selector is mandatory, otherwise a panel within a panel could be affected too when it shouldn't (eg. dashboard in an object, n:n panel) + > .ibo-panel--header { + /* All transitions should have a specific duration except for the header's "top" property otherwise it feels laggy */ + /* - Impacted descendants (we don't put "*" as it can get shaky otherwise) */ + .ibo-panel--header-left { + @extend %ibo-panel--base-transition; + } + + /* Transition rules */ + &.ibo-is-sticking { + .ibo-object-details--object-class { + display: none; /* Make space by hiding unnecessary info */ + } + } + } + + &.ibo-has-icon { + &.ibo-has-medallion-icon { + // Note: Direct child selector is mandatory, otherwise a panel within a panel with a medallion icon will have its icon as a medallion too (eg. dashboard in an object) + > .ibo-panel--header { + /* Transition rules */ + &.ibo-is-sticking { + .ibo-panel--header-left { + padding-left: $ibo-object-details--icon--size--is-sticking; + } + + .ibo-panel--titles { + padding-left: $ibo-object-details--icon--spacing--as-medallion--is-sticking; + } + } + } + } + } +} \ No newline at end of file diff --git a/css/backoffice/utils/helpers/_misc.scss b/css/backoffice/utils/helpers/_misc.scss index 231755f26..3e2605709 100644 --- a/css/backoffice/utils/helpers/_misc.scss +++ b/css/backoffice/utils/helpers/_misc.scss @@ -20,6 +20,14 @@ $ibo-is-code--background-color: $ibo-color-white-200 !default; $ibo-is-code--padding: 1.25rem 1.5rem !default; +$ibo-sticky-sentinel--left: 0 !default; +$ibo-sticky-sentinel--right: 0 !default; +$ibo-sticky-sentinel--height: 0 !default; +$ibo-sticky-sentinel-top--top: 0 !default; +$ibo-sticky-sentinel-top--height: $ibo-sticky-sentinel--height !default; +$ibo-sticky-sentinel-bottom--bottom: 0 !default; +$ibo-sticky-sentinel-bottom--height: $ibo-sticky-sentinel--height !default; + /**************/ /* Visibility */ /**************/ @@ -126,6 +134,28 @@ body.ibo-has-fullscreen-descendant { @extend %ibo-font-code-150; } +/***********************************************************************/ +/* Sticky headers */ +/* */ +/* Used as a trigger to make an element stick to another during scroll */ +/***********************************************************************/ + +.ibo-sticky-sentinel { + position: absolute; + left: $ibo-sticky-sentinel--left; + right: $ibo-sticky-sentinel--right; + visibility: hidden; +} +.ibo-sticky-sentinel-top { + top: $ibo-sticky-sentinel-top--top; + height: $ibo-sticky-sentinel-top--height; /* To be overloaded by use cases */ +} +.ibo-sticky-sentinel-bottom { + bottom: $ibo-sticky-sentinel-bottom--bottom; + height: $ibo-sticky-sentinel-bottom--height; /* To be overloaded by use cases */ +} + + %ibo-medallion { position: relative; border-radius: var(--ibo-border-radius-full); diff --git a/js/components/panel.js b/js/components/panel.js new file mode 100644 index 000000000..66f477477 --- /dev/null +++ b/js/components/panel.js @@ -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 + $('
') + .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); + $('
') + .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; + }, + }); +}); diff --git a/js/layouts/object/object-details.js b/js/layouts/object/object-details.js new file mode 100644 index 000000000..73baf4960 --- /dev/null +++ b/js/layouts/object/object-details.js @@ -0,0 +1,36 @@ +/* + * @copyright Copyright (C) 2010-2021 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ + +; +$(function() +{ + // the widget definition, where 'itop' is the namespace, + // 'panel' the widget name + $.widget( 'itop.object_details', $.itop.panel, + { + // default options + options: + { + }, + css_classes: + { + }, + js_selectors: + { + }, + + // the constructor + _create: function() + { + this._super(); + }, + // events bound via _bind are removed automatically + // revert other modifications here + _destroy: function() + { + this._super(); + }, + }); +}); diff --git a/sources/application/UI/Base/Component/Panel/Panel.php b/sources/application/UI/Base/Component/Panel/Panel.php index bfc28a2e4..7504720dd 100644 --- a/sources/application/UI/Base/Component/Panel/Panel.php +++ b/sources/application/UI/Base/Component/Panel/Panel.php @@ -44,6 +44,9 @@ class Panel extends UIContentBlock public const BLOCK_CODE = 'ibo-panel'; public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/components/panel/layout'; public const DEFAULT_JS_TEMPLATE_REL_PATH = 'base/components/panel/layout'; + public const DEFAULT_JS_FILES_REL_PATH = [ + 'js/components/panel.js', + ]; // Specific constants /** @var string ENUM_COLOR_PRIMARY */ @@ -103,6 +106,11 @@ class Panel extends UIContentBlock public const DEFAULT_ICON_COVER_METHOD = self::ENUM_ICON_COVER_METHOD_CONTAIN; /** @var bool */ public const DEFAULT_ICON_AS_MEDALLION = false; + /** + * @var bool + * @see static::$bIsHeaderVisibleOnScroll + */ + public const DEFAULT_IS_HEADER_VISIBLE_ON_SCROLL = false; /** @var iUIContentBlock $oTitleBlock */ protected $oTitleBlock; @@ -118,6 +126,8 @@ class Panel extends UIContentBlock protected $sColor; /** @var bool $bIsCollapsible */ protected $bIsCollapsible; + /** @var bool $bIsHeaderVisibleOnScroll True if the header of the panel should remain visible when scrolling */ + protected $bIsHeaderVisibleOnScroll; /** * Panel constructor. @@ -146,6 +156,7 @@ class Panel extends UIContentBlock $this->SetMainBlocks([]); $this->SetToolBlocks([]); $this->bIsCollapsible = false; + $this->bIsHeaderVisibleOnScroll = static::DEFAULT_IS_HEADER_VISIBLE_ON_SCROLL; } /** @@ -448,6 +459,27 @@ class Panel extends UIContentBlock return $this; } + /** + * @see static::$bIsHeaderVisibleOnScroll + * @return bool + */ + public function IsHeaderVisibleOnScroll(): bool + { + return $this->bIsHeaderVisibleOnScroll; + } + + /** + * @see static::$bIsHeaderVisibleOnScroll + * + * @param bool $bIsHeaderVisibleOnScroll + * + * @return $this + */ + public function SetIsHeaderVisibleOnScroll(bool $bIsHeaderVisibleOnScroll) + { + $this->bIsHeaderVisibleOnScroll = $bIsHeaderVisibleOnScroll; + return $this; + } //---------------------- // Specific content area diff --git a/sources/application/UI/Base/Layout/Object/ObjectDetails.php b/sources/application/UI/Base/Layout/Object/ObjectDetails.php index e7d59fdd7..fbc5d6c66 100644 --- a/sources/application/UI/Base/Layout/Object/ObjectDetails.php +++ b/sources/application/UI/Base/Layout/Object/ObjectDetails.php @@ -20,6 +20,10 @@ class ObjectDetails extends Panel implements iKeyboardShortcut public const BLOCK_CODE = 'ibo-object-details'; public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/layouts/object/object-details/layout'; public const DEFAULT_JS_TEMPLATE_REL_PATH = 'base/layouts/object/object-details/layout'; + public const DEFAULT_JS_FILES_REL_PATH = [ + 'js/components/panel.js', + 'js/layouts/object/object-details.js', + ]; /** @var string Class name of the object (eg. "UserRequest") */ protected $sClassName; diff --git a/sources/application/UI/Base/Layout/Object/ObjectFactory.php b/sources/application/UI/Base/Layout/Object/ObjectFactory.php index 462e334f8..41d36c476 100644 --- a/sources/application/UI/Base/Layout/Object/ObjectFactory.php +++ b/sources/application/UI/Base/Layout/Object/ObjectFactory.php @@ -31,6 +31,9 @@ class ObjectFactory */ public static function MakeDetails(DBObject $oObject, ?string $sMode = cmdbAbstractObject::DEFAULT_OBJECT_MODE) { - return new ObjectDetails($oObject, $sMode); + $oObjectDetails = new ObjectDetails($oObject, $sMode); + $oObjectDetails->SetIsHeaderVisibleOnScroll(true); + + return $oObjectDetails; } } \ No newline at end of file diff --git a/templates/base/components/panel/layout.js.twig b/templates/base/components/panel/layout.js.twig index 6ac99f891..0e17d58ce 100644 --- a/templates/base/components/panel/layout.js.twig +++ b/templates/base/components/panel/layout.js.twig @@ -1,5 +1,14 @@ -{% if oUIBlock.isCollapsible() %} - $('#{{ oUIBlock.GetId() }}').find('[data-role="ibo-panel--collapsible-toggler"]').on('click', function(){ - $('#{{ oUIBlock.GetId() }}').toggleClass('ibo-is-opened'); +{# Collapsible handler #} +{% block iboCollapsibleHandlers %} + {% if oUIBlock.isCollapsible() %} + $('#{{ oUIBlock.GetId() }}').find('[data-role="ibo-panel--collapsible-toggler"]').on('click', function(){ + $('#{{ oUIBlock.GetId() }}').toggleClass('ibo-is-opened'); + }); + {% endif %} +{% endblock %} + +{% block iboWidgetInstantiation %} + $('#{{ oUIBlock.GetId() }}').panel({ + is_header_visible_on_scroll: {{ oUIBlock.IsHeaderVisibleOnScroll|var_export }} }); -{% endif %} +{% endblock %} \ No newline at end of file diff --git a/templates/base/layouts/object/object-details/layout.js.twig b/templates/base/layouts/object/object-details/layout.js.twig index 848d4db85..9d8f70890 100644 --- a/templates/base/layouts/object/object-details/layout.js.twig +++ b/templates/base/layouts/object/object-details/layout.js.twig @@ -1,29 +1,33 @@ {# @copyright Copyright (C) 2010-2020 Combodo SARL #} {# @license http://opensource.org/licenses/AGPL-3.0 #} +{% extends 'base/components/panel/layout.js.twig' %} -$('#{{ oUIBlock.GetId() }}').on('edit_object', function(){ - $(this).find('button[name="UI:Menu:Modify"]').click(); -}); +{% block iboKeyboardShortcutsHandlers %} + $('#{{ oUIBlock.GetId() }}').on('edit_object', function(){ + $(this).find('button[name="UI:Menu:Modify"]').click(); + }); -$('#{{ oUIBlock.GetId() }}').on('delete_object', function(){ - $(this).find('button[name="UI:Menu:Delete"]').click(); -}); + $('#{{ oUIBlock.GetId() }}').on('delete_object', function(){ + $(this).find('button[name="UI:Menu:Delete"]').click(); + }); -$('#{{ oUIBlock.GetId() }}').on('new_object', function(){ - $(this).find('button[name="UI:Menu:New"]').click(); -}); -{% if oUIBlock.GetObjectMode() == constant('cmdbAbstractObject::ENUM_OBJECT_MODE_EDIT') or oUIBlock.GetObjectMode() == constant('cmdbAbstractObject::ENUM_OBJECT_MODE_CREATE') %} -$('#{{ oUIBlock.GetId() }}').on('save_object', function(){ - $(this).find('button[type="submit"][name=""][value=""]').click(); -}); -{% elseif oUIBlock.GetObjectMode() == constant('cmdbAbstractObject::ENUM_OBJECT_MODE_STIMULUS') %} -$('#{{ oUIBlock.GetId() }}').on('save_object', function(){ - $(this).find('button[type="submit"][name="submit"][value="submit"]').click(); -}); -{% endif %} + $('#{{ oUIBlock.GetId() }}').on('new_object', function(){ + $(this).find('button[name="UI:Menu:New"]').click(); + }); + {% if oUIBlock.GetObjectMode() == constant('cmdbAbstractObject::ENUM_OBJECT_MODE_EDIT') or oUIBlock.GetObjectMode() == constant('cmdbAbstractObject::ENUM_OBJECT_MODE_CREATE') %} + $('#{{ oUIBlock.GetId() }}').on('save_object', function(){ + $(this).find('button[type="submit"][name=""][value=""]').click(); + }); + {% elseif oUIBlock.GetObjectMode() == constant('cmdbAbstractObject::ENUM_OBJECT_MODE_STIMULUS') %} + $('#{{ oUIBlock.GetId() }}').on('save_object', function(){ + $(this).find('button[type="submit"][name="submit"][value="submit"]').click(); + }); + {% endif %} +{% endblock %} -// Keep URL's hash parameters when clicking on a link of the header -$('#{{ oUIBlock.GetId() }}').on('click', '[data-role="ibo-panel--header-right"] a', function() { +{% block iboPanelHeaderRightActionsHandlers %} + // Keep URL's hash parameters when clicking on a link of the header + $('#{{ oUIBlock.GetId() }}').on('click', '[data-role="ibo-panel--header-right"] a', function() { aMatches = /#(.*)$/.exec(window.location.href); if (aMatches != null) { currentHash = aMatches[1]; @@ -31,4 +35,11 @@ $('#{{ oUIBlock.GetId() }}').on('click', '[data-role="ibo-panel--header-right"] this.href = this.href.replace(/#(.*)$/, '#'+currentHash); } } -}); \ No newline at end of file +}); +{% endblock %} + +{% block iboWidgetInstantiation %} +$('#{{ oUIBlock.GetId() }}').object_details({ + is_header_visible_on_scroll: {{ oUIBlock.IsHeaderVisibleOnScroll|var_export }} +}); +{% endblock %} \ No newline at end of file