mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-18 16:18:47 +02:00
N°4022 - Rework search page to improve UX and usable height
This commit is contained in:
@@ -3,6 +3,20 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
$ibo-search-form-panel--z-index: 3 !default;
|
||||
|
||||
$ibo-search-form-panel--body--padding-top: 18px !default;
|
||||
$ibo-search-form-panel--body--padding-bottom: 10px !default;
|
||||
$ibo-search-form-panel--body--padding-x: 14px !default;
|
||||
|
||||
$ibo-search-results-area--z-index: $ibo-search-form-panel--z-index - 2 !default; /* Minus 2 because the criteria expands between the search form panel and the results area */
|
||||
|
||||
$ibo-search-results-area--datatable-toolbar--background-color--is-sticking: $ibo-panel--body--background-color !default;
|
||||
$ibo-search-results-area--datatable-toolbar--border--is-sticking: $ibo-panel--body--border !default;
|
||||
|
||||
$ibo-search-results-area--datatable-scrollhead--background-color--is-sticking: $ibo-search-results-area--datatable-toolbar--background-color--is-sticking !default;
|
||||
$ibo-search-results-area--datatable-scrollhead--border--is-sticking: $ibo-search-results-area--datatable-toolbar--border--is-sticking !default;
|
||||
|
||||
/* IMPORTANT: All the code below do NOT follow the coding convention and has NOT been refactored to the UIBlock system */
|
||||
|
||||
.search_box {
|
||||
@@ -832,10 +846,11 @@
|
||||
|
||||
|
||||
.ibo-search-form-panel {
|
||||
z-index: $ibo-search-form-panel--z-index;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.ibo-panel--body {
|
||||
padding: 18px 14px 10px;
|
||||
padding: $ibo-search-form-panel--body--padding-top $ibo-search-form-panel--body--padding-x $ibo-search-form-panel--body--padding-bottom;
|
||||
overflow: initial;
|
||||
}
|
||||
}
|
||||
@@ -864,4 +879,41 @@
|
||||
}
|
||||
.ibo-criterion-group:empty ~ .sf_more_criterion .sfm_tg_title{
|
||||
display: unset;
|
||||
}
|
||||
.sf_results_area {
|
||||
z-index: $ibo-search-results-area--z-index;
|
||||
}
|
||||
|
||||
/***********************/
|
||||
/* Sticky header rules */
|
||||
/***********************/
|
||||
.ibo-search-form-panel {
|
||||
.ibo-panel--body.ibo-is-sticking {
|
||||
position: fixed;
|
||||
|
||||
border-radius: 0;
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
}
|
||||
.sf_results_area {
|
||||
.ibo-datatable-panel.ibo-is-sticking {
|
||||
.ibo-panel--header {
|
||||
z-index: 0;
|
||||
}
|
||||
.ibo-datatable--toolbar {
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
padding-bottom: 4px;
|
||||
background-color: $ibo-search-results-area--datatable-toolbar--background-color--is-sticking;
|
||||
border-left: $ibo-search-results-area--datatable-toolbar--border--is-sticking;
|
||||
border-right: $ibo-search-results-area--datatable-toolbar--border--is-sticking;
|
||||
}
|
||||
.dataTables_scrollHead {
|
||||
position: fixed !important; /* Important is required as the property is already set in the style attribute by the JS lib */
|
||||
z-index: 2;
|
||||
background-color: $ibo-search-results-area--datatable-scrollhead--background-color--is-sticking;
|
||||
border-left: $ibo-search-results-area--datatable-scrollhead--border--is-sticking !important; /* Unfortunately the !important is necessary to overload the lib style attribute */
|
||||
border-right: $ibo-search-results-area--datatable-scrollhead--border--is-sticking !important; /* Unfortunately the !important is necessary to overload the lib style attribute */
|
||||
}
|
||||
}
|
||||
}
|
||||
2
css/backoffice/vendors/_datatables.scss
vendored
2
css/backoffice/vendors/_datatables.scss
vendored
@@ -30,7 +30,6 @@ $ibo-vendors-datatables--column-sorting-icon--content: "\f0dc" !default;
|
||||
$ibo-vendors-datatables--column-sorting-icon--content--is-sorted-asc: "\f0d8" !default;
|
||||
$ibo-vendors-datatables--column-sorting-icon--content--is-sorted-desc: "\f0d7" !default;
|
||||
|
||||
$ibo-vendors-datatables--columns-header--margin-bottom: 4px !default;
|
||||
$ibo-vendors-datatables--columns-header--border-bottom: 1px solid $ibo-color-grey-400 !default;
|
||||
|
||||
$ibo-vendors-datatables--row--background-color--is-odd: $ibo-color-white-100 !default;
|
||||
@@ -132,7 +131,6 @@ $ibo-vendors-datatables--row-highlight--colors:(
|
||||
}
|
||||
|
||||
.dataTables_scrollHeadInner {
|
||||
margin-bottom: $ibo-vendors-datatables--columns-header--margin-bottom;
|
||||
border-bottom: $ibo-vendors-datatables--columns-header--border-bottom;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -83,11 +83,14 @@ $(function()
|
||||
// Submit properties (XHR, throttle, ...)
|
||||
submit: null,
|
||||
|
||||
// {ScrollMagic.Controller} SM controller for the sticky header
|
||||
sticky_header_controller: null,
|
||||
|
||||
// the constructor
|
||||
_create: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
|
||||
this.element.addClass('search_form_handler');
|
||||
|
||||
// Init properties (complexe type properties would be static if not initialized with a simple type variable...)
|
||||
@@ -120,23 +123,28 @@ $(function()
|
||||
'base_oql': this.options.search.base_oql,
|
||||
'criterion': this.options.search.criterion,
|
||||
});
|
||||
|
||||
|
||||
// If auto submit is enabled, also submit on first display
|
||||
if(this.options.auto_submit === true)
|
||||
{
|
||||
this._submit();
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
// called when created, and later when changing options
|
||||
_refresh: function()
|
||||
{
|
||||
|
||||
this._updateStickyHeaderHandler();
|
||||
},
|
||||
// events bound via _bind are removed automatically
|
||||
// revert other modifications here
|
||||
_destroy: function()
|
||||
{
|
||||
// Remove ScrollMagic controller, typicaly useful when the search form is loaded to display one for another class
|
||||
if(this.sticky_header_controller !== null) {
|
||||
this.sticky_header_controller.destroy(true)
|
||||
}
|
||||
|
||||
this.element
|
||||
.removeClass('search_form_handler');
|
||||
},
|
||||
@@ -226,6 +234,25 @@ $(function()
|
||||
});
|
||||
});
|
||||
|
||||
// Refresh handler when the list has changed
|
||||
// - Initialization
|
||||
// - Destroy / reinitialization (changing the DM class of the search form)
|
||||
// - AJAX pagination, filtering
|
||||
// - Page length changes
|
||||
this.element.scrollParent().on('init.dt draw.dt column-sizing.dt', function(oEvent) {
|
||||
me._updateStickyHeaderHandler();
|
||||
});
|
||||
|
||||
// Refresh handler when resising:
|
||||
// - The window
|
||||
// - The search form when the numerous criteria wrap on a new line
|
||||
if(window.ResizeObserver) {
|
||||
const oPanelRO = new ResizeObserver(function(){
|
||||
me._updateStickyPositions();
|
||||
});
|
||||
oPanelRO.observe(this.element[0]);
|
||||
}
|
||||
|
||||
},
|
||||
// - Update search option of the widget
|
||||
_updateSearch: function()
|
||||
@@ -345,6 +372,9 @@ $(function()
|
||||
this.elements.message_area = this.element.find('.sf_message');
|
||||
this._cleanMessageArea();
|
||||
|
||||
// - Sticky header
|
||||
this._updateStickyHeaderHandler();
|
||||
|
||||
// Events
|
||||
// - Refresh icon
|
||||
this.element.find('.sft_refresh').on('click', function(oEvent){
|
||||
@@ -1093,8 +1123,318 @@ $(function()
|
||||
this._hideLoader();
|
||||
},
|
||||
|
||||
//---------------------------
|
||||
// Sticky header helpers
|
||||
//---------------------------
|
||||
/**
|
||||
* Main function for the sticky header, it creates or recreates all the mechanism (viewport, trigger points, sizing/positioning of fixed elements)
|
||||
* Must be called whenever the reference elements (viewport, trigger elements, ...) change to ensure that everything is well positioned.
|
||||
*
|
||||
* @return {void}
|
||||
* @private
|
||||
*/
|
||||
_updateStickyHeaderHandler: function ()
|
||||
{
|
||||
const me = this;
|
||||
|
||||
// Update the reference viewport
|
||||
this.options.viewport_elem = this.element.scrollParent()[0];
|
||||
|
||||
// Clean SM controller if there was already one
|
||||
if (null !== this.sticky_header_controller) {
|
||||
this.sticky_header_controller.destroy(true);
|
||||
}
|
||||
|
||||
// Prepare SM controller
|
||||
this.sticky_header_controller = new ScrollMagic.Controller({
|
||||
container: this.options.viewport_elem,
|
||||
});
|
||||
|
||||
this._addScrollSceneForForm();
|
||||
this._addScrollSceneForResults();
|
||||
},
|
||||
/**
|
||||
* Observe when the search form reaches/leaves the viewport's top
|
||||
* @private
|
||||
*/
|
||||
_addScrollSceneForForm: function()
|
||||
{
|
||||
const me = this;
|
||||
const oFormPanelHeaderElem = this._getFormPanelHeaderElem();
|
||||
|
||||
new ScrollMagic.Scene({
|
||||
triggerElement: oFormPanelHeaderElem[0],
|
||||
triggerHook: 0,
|
||||
offset: oFormPanelHeaderElem.outerHeight(),
|
||||
})
|
||||
.on('enter', function () {
|
||||
me._onFormBecomesSticky();
|
||||
})
|
||||
.on('leave', function () {
|
||||
me._onFormStopsBeingSticky();
|
||||
})
|
||||
.addTo(this.sticky_header_controller);
|
||||
},
|
||||
/**
|
||||
* Callback for the form element SM Scene
|
||||
* @return {void}
|
||||
* @private
|
||||
*/
|
||||
_onFormBecomesSticky: function()
|
||||
{
|
||||
this._getFormPanelBodyElem().addClass('ibo-is-sticking');
|
||||
this._updateStickyPositions();
|
||||
},
|
||||
/**
|
||||
* Callback for the form element SM Scene
|
||||
* @return {void}
|
||||
* @private
|
||||
*/
|
||||
_onFormStopsBeingSticky: function()
|
||||
{
|
||||
this._getFormPanelBodyElem().removeClass('ibo-is-sticking');
|
||||
this._updateStickyPositions();
|
||||
},
|
||||
/**
|
||||
* Observer when the results' top toolbar reaches/leaves the search form's bottom
|
||||
* @private
|
||||
*/
|
||||
_addScrollSceneForResults: function()
|
||||
{
|
||||
const me = this;
|
||||
const oFormPanelHeaderElem = this._getFormPanelHeaderElem();
|
||||
|
||||
new ScrollMagic.Scene({
|
||||
triggerElement: oFormPanelHeaderElem[0],
|
||||
triggerHook: 0,
|
||||
// Careful, this won't get updated dynamically, meaning that if the elements move or resize, it won't be exact anymore
|
||||
// Note: As offset() starts from the very top of the window, we need to take into account the top container height!
|
||||
offset: this._getResultsPanelElem().find('.ibo-panel--body:first').offset().top - $('#ibo-top-container').outerHeight() - this._getFormPanelBodyElem().outerHeight(),
|
||||
})
|
||||
.on('enter', function () {
|
||||
me._onResultsBecomesSticky();
|
||||
})
|
||||
.on('leave', function () {
|
||||
me._onResultsStopsBeingSticky();
|
||||
})
|
||||
.addTo(this.sticky_header_controller);
|
||||
},
|
||||
/**
|
||||
* Callback for the results element SM Scene
|
||||
* @return {void}
|
||||
* @private
|
||||
*/
|
||||
_onResultsBecomesSticky: function()
|
||||
{
|
||||
this._getResultsPanelElem().addClass('ibo-is-sticking');
|
||||
this._getResultsToolbarTopElem().addClass('ibo-is-sticking');
|
||||
this._getResultsTableHeaders().addClass('ibo-is-sticking');
|
||||
this._updateStickyPositions();
|
||||
},
|
||||
/**
|
||||
* Callback for the results element SM Scene
|
||||
* @return {void}
|
||||
* @private
|
||||
*/
|
||||
_onResultsStopsBeingSticky: function()
|
||||
{
|
||||
this._getResultsPanelElem().removeClass('ibo-is-sticking');
|
||||
this._getResultsToolbarTopElem().removeClass('ibo-is-sticking');
|
||||
this._getResultsTableHeaders().removeClass('ibo-is-sticking');
|
||||
this._updateStickyPositions();
|
||||
},
|
||||
/**
|
||||
* Update all the concerned elements position / size
|
||||
*
|
||||
* @param bImmediate {boolean} Set to true if the update of the positions should have a small delay. This can be useful when ahving CSS transitions that needs to be done before computing positions.
|
||||
* @private
|
||||
*/
|
||||
_updateStickyPositions: function(bImmediate = true)
|
||||
{
|
||||
const me = this;
|
||||
|
||||
if(!bImmediate) {
|
||||
setTimeout(function() {
|
||||
me._updateStickyPositions(true);
|
||||
}, 300);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the sticky elements positions
|
||||
this._updateFormPosition();
|
||||
this._updateResultsToolbarTopPosition();
|
||||
this._updateResultsTableHeadersPosition();
|
||||
|
||||
// Update the scrolling element's top padding to avoid having a visual glitch when the results panel elements becomes sticky and changes the result table vertical position
|
||||
// Note: The initial "-8" offset is there because we don't know yet how to retrieve the results panel body :before height, therefore this will not work well with custom themes... 😕
|
||||
const iInitialOffset = -8;
|
||||
let iResultsPanelOffset = iInitialOffset;
|
||||
const aStickableElems = [
|
||||
this._getFormPanelBodyElem(),
|
||||
this._getResultsToolbarTopElem(),
|
||||
this._getResultsTableHeaders()
|
||||
];
|
||||
for(let oElem of aStickableElems){
|
||||
if(oElem.hasClass('ibo-is-sticking')){
|
||||
iResultsPanelOffset += parseInt(oElem.outerHeight() + parseInt(oElem.css('margin-top')) + parseInt(oElem.css('margin-bottom')));
|
||||
}
|
||||
}
|
||||
|
||||
// If computed offset is the same as the initial, we should reset the padding.
|
||||
if(iInitialOffset === iResultsPanelOffset) {
|
||||
this._getResultsPanelElem().css('padding-top', '');
|
||||
} else {
|
||||
this._getResultsPanelElem().css('padding-top', iResultsPanelOffset);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Update only the search form position
|
||||
* @private
|
||||
*/
|
||||
_updateFormPosition: function()
|
||||
{
|
||||
const oFormPanelBodyElem = this._getFormPanelBodyElem();
|
||||
if(this._isElementSticking(oFormPanelBodyElem)) {
|
||||
const oFormPanelElem = this._getFormPanelElem();
|
||||
oFormPanelBodyElem.css({
|
||||
'top': $(this.options.viewport_elem).offset().top,
|
||||
'left': oFormPanelElem.offset().left,
|
||||
'width': oFormPanelElem.outerWidth(),
|
||||
});
|
||||
} else {
|
||||
oFormPanelBodyElem.css({
|
||||
'top': '',
|
||||
'left': '',
|
||||
'width': '',
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Update only the results top toolbar position
|
||||
* @private
|
||||
*/
|
||||
_updateResultsToolbarTopPosition: function()
|
||||
{
|
||||
if(this._isElementSticking(this._getResultsToolbarTopElem())){
|
||||
const oFormPanelBodyElem = this._getFormPanelBodyElem();
|
||||
|
||||
this._getResultsToolbarTopElem().css({
|
||||
'top': oFormPanelBodyElem.offset().top + oFormPanelBodyElem.outerHeight(),
|
||||
'left': oFormPanelBodyElem.offset().left,
|
||||
'width': oFormPanelBodyElem.outerWidth(),
|
||||
'padding-top': this._getResultsToolbarTopElem().css('margin-top'),
|
||||
'padding-bottom': this._getResultsToolbarTopElem().css('margin-bottom'),
|
||||
});
|
||||
}
|
||||
else {
|
||||
this._getResultsToolbarTopElem().css({
|
||||
'top': '',
|
||||
'left': '',
|
||||
'width': '',
|
||||
'padding-top': '',
|
||||
'padding-bottom': '',
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Update only the results table headers position
|
||||
* @private
|
||||
*/
|
||||
_updateResultsTableHeadersPosition: function()
|
||||
{
|
||||
if(this._isElementSticking(this._getResultsTableHeaders())){
|
||||
const oFormPanelElem = this._getFormPanelElem();
|
||||
const oResultsToolbarTopElem = this._getResultsToolbarTopElem();
|
||||
|
||||
this._getResultsTableHeaders().css({
|
||||
'top': oResultsToolbarTopElem.offset().top + oResultsToolbarTopElem.outerHeight(),
|
||||
'left': oFormPanelElem.offset().left,
|
||||
'width': oFormPanelElem.outerWidth(),
|
||||
'padding-top': this._getResultsTableHeaders().css('margin-top'),
|
||||
'padding-bottom': this._getResultsTableHeaders().css('margin-bottom'),
|
||||
});
|
||||
}else{
|
||||
this._getResultsTableHeaders().css({
|
||||
'top': '',
|
||||
'left': '',
|
||||
'width': '',
|
||||
'padding-top': '',
|
||||
'padding-bottom': '',
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param oElem {Object} jQuery object representing the element to test
|
||||
* @return {boolean} True if oElem is currently sticking
|
||||
* @private
|
||||
*/
|
||||
_isElementSticking: function(oElem)
|
||||
{
|
||||
return oElem.closest('.ibo-is-sticking').length > 0;
|
||||
},
|
||||
/**
|
||||
* @return {Object} The jQuery object representing the search form panel element (where the criteria are)
|
||||
* @private
|
||||
*/
|
||||
_getFormPanelElem: function()
|
||||
{
|
||||
return this.element.closest('.ibo-search-form-panel');
|
||||
},
|
||||
/**
|
||||
* @return {null|Object} The jQuery object representing the header of the search form panel; or null if none found
|
||||
* @private
|
||||
*/
|
||||
_getFormPanelHeaderElem: function()
|
||||
{
|
||||
const oFormPanelElem = this._getFormPanelElem();
|
||||
if(oFormPanelElem.length === 0){
|
||||
return null;
|
||||
}
|
||||
|
||||
return oFormPanelElem.find('[data-role="ibo-panel--header"]:first');
|
||||
},
|
||||
/**
|
||||
* @return {null|Object} The jQuery object representing the body of the search form panel; or null if none found
|
||||
* @private
|
||||
*/
|
||||
_getFormPanelBodyElem: function()
|
||||
{
|
||||
const oFormPanelElem = this._getFormPanelElem();
|
||||
if(oFormPanelElem.length === 0){
|
||||
return null;
|
||||
}
|
||||
|
||||
return oFormPanelElem.find('[data-role="ibo-panel--body"]:first');
|
||||
},
|
||||
/**
|
||||
* @return {Object} The jQuery object representing the complete results panel
|
||||
* @private
|
||||
*/
|
||||
_getResultsPanelElem: function()
|
||||
{
|
||||
return $(this.options.result_list_outer_selector).find('[data-role="ibo-panel"]:first')
|
||||
},
|
||||
/**
|
||||
* @return {Object} The jQuery object representing the top toolbar of the results (pagination, ...)
|
||||
* @private
|
||||
*/
|
||||
_getResultsToolbarTopElem: function()
|
||||
{
|
||||
return $(this.options.result_list_outer_selector).find('.ibo-datatable--toolbar:first');
|
||||
},
|
||||
/**
|
||||
* @return {Object} The jQuery object representing the columns headers of the results
|
||||
* @private
|
||||
*/
|
||||
_getResultsTableHeaders: function()
|
||||
{
|
||||
return $(this.options.result_list_outer_selector).find('.dataTables_scrollHead:first');
|
||||
},
|
||||
|
||||
|
||||
//---------------------------
|
||||
// Global helpers
|
||||
//---------------------------
|
||||
_refreshRecentlyUsed: function()
|
||||
{
|
||||
me = this;
|
||||
@@ -1162,7 +1502,9 @@ $(function()
|
||||
},
|
||||
|
||||
|
||||
//---------------------------
|
||||
// Debug helpers
|
||||
//---------------------------
|
||||
// - Show a trace in the javascript console
|
||||
_trace: function(sMessage, oData)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user