N°4022 - Rework search page to improve UX and usable height

This commit is contained in:
Molkobain
2021-06-02 23:47:46 +02:00
parent 94c37b2e14
commit 1b9eb2a6ad
5 changed files with 473 additions and 13 deletions

View File

@@ -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 */
}
}
}

View File

@@ -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

View File

@@ -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)
{