N°4408 - Improve transition buttons UX in object details

Transition buttons are now grouped in the "Apply" button when there is not enough space to display them all
This commit is contained in:
Molkobain
2021-11-10 16:47:30 +01:00
parent 90c866f696
commit 206143824b
5 changed files with 105 additions and 7 deletions

View File

@@ -10,6 +10,7 @@ use Combodo\iTop\Application\Search\SearchForm;
use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Button\Button;
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\ButtonGroup\ButtonGroupUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\CollapsibleSection\CollapsibleSection;
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableSettings;
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
@@ -27,6 +28,8 @@ use Combodo\iTop\Application\UI\Base\Component\Input\Select\SelectOptionUIBlockF
use Combodo\iTop\Application\UI\Base\Component\Input\SelectUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\MedallionIcon\MedallionIcon;
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenu;
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenuItem\JsPopoverMenuItem;
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Toolbar\ToolbarUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\ActivityPanel\ActivityPanel;
@@ -2805,24 +2808,41 @@ JS
$aTransitions = $this->EnumTransitions();
if (!isset($aExtraParams['custom_operation']) && count($aTransitions)) {
// transitions are displayed only for the standard new/modify actions, not for modify_all or any other case...
// Transitions are displayed only for the standard new/modify actions, not for modify_all or any other case...
$oSetToCheckRights = DBObjectSet::FromObject($this);
$oTransitionPopoverMenu = new PopoverMenu();
$sTPMSectionId = 'transitions';
$oTransitionPopoverMenu->AddSection($sTPMSectionId);
$aStimuli = Metamodel::EnumStimuli($sClass);
foreach ($aTransitions as $sStimulusCode => $aTransitionDef) {
$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass,
$sStimulusCode, $oSetToCheckRights) : UR_ALLOWED_NO;
switch ($iActionAllowed) {
case UR_ALLOWED_YES:
// Button to be displayed on its own on large screens
$oButton = ButtonUIBlockFactory::MakeForPrimaryAction($aStimuli[$sStimulusCode]->GetLabel(), 'next_action', $sStimulusCode, true);
$oButton->AddCSSClass('action');
$oButton->SetColor(Button::ENUM_COLOR_SCHEME_NEUTRAL);
$oToolbarButtons->AddSubBlock($oButton);
// Button to be displayed in a grouped button on smaller screens
$oTPMPopupMenuItem = new JSPopupMenuItem('next_action--'.$oButton->GetId(), $oButton->GetLabel(), "$(`#{$oButton->GetId()}`).trigger(`click`);");
$oTransitionPopoverMenu->AddItem($sTPMSectionId, new JsPopoverMenuItem($oTPMPopupMenuItem));
break;
default:
// Do nothing
}
}
// If there are some allowed transitions, build the grouped button
if ($oTransitionPopoverMenu->HasItems()) {
$oApplyForButtonGroup = ButtonUIBlockFactory::MakeForPrimaryAction($oApplyButton->GetLabel(), null, null, true);
$oApplyAndTransitionsButtonGroup = ButtonGroupUIBlockFactory::MakeButtonWithOptionsMenu($oApplyForButtonGroup, $oTransitionPopoverMenu)
->SetIsHidden(true);
$oToolbarButtons->AddSubBlock($oApplyAndTransitionsButtonGroup);
}
}
$sStatesSelection = '';

View File

@@ -51,6 +51,13 @@ $ibo-object-details--tag--separator--border-radius: $ibo-border-radius-full !def
}
}
}
// Note: Direct child selector is mandatory, otherwise a panel within a panel will have be impacted (eg. n:n relations tabs)
> .ibo-panel--header {
.ibo-panel--subtitle {
@extend %ibo-text-truncated-with-ellipsis;
}
}
}
.ibo-object-details--status {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -25,6 +25,8 @@ $(function()
_create: function()
{
this._super();
this._initializeSubmittingButtonsObserver();
},
// events bound via _bind are removed automatically
// revert other modifications here
@@ -38,7 +40,8 @@ $(function()
this._super();
// Keep URL's hash parameters when clicking on a link of the header
this.element.on('click', '[data-role="ibo-panel--header-right"] a', function() {
// Note: ":first" used to only target the header of the object, not what could be in the content of its body
this.element.on('click', '[data-role="ibo-panel--header-right"]:first a', function() {
aMatches = /#(.*)$/.exec(window.location.href);
if (aMatches != null) {
currentHash = aMatches[1];
@@ -48,5 +51,73 @@ $(function()
}
});
},
/**
* Initialize the observer on the submitting buttons (cancel, apply, transitions, ...) to display only the grouped button depending on the available space
* @private
*/
_initializeSubmittingButtonsObserver: function()
{
// This only applies in edit mode
if (this._getObjectMode() !== 'edit') {
return false;
}
// If no ResizeObserver, fallback is that transition buttons will overflow on smaller screen
if (window.ResizeObserver === undefined) {
return false;
}
// Check if transition available
const oHeaderElem = this.element.find('[data-role="ibo-panel--header"]:first');
const oButtonsToolbarElem = oHeaderElem.find('[data-role="ibo-panel--header-right"] [data-role="ibo-toolbar"]');
const oTransitionButtonsElems = oButtonsToolbarElem.find('[name="next_action"][data-role="ibo-button"]');
if (oHeaderElem.find('[name="next_action"][data-role="ibo-button"]').length === 0) {
return false;
}
let iCurrentHeaderWidth = 0;
let hTimeout = null;
const oObserver = new ResizeObserver(function(aEntries) {
// We hide the transition buttons during the resize to minimize visual glitches
oTransitionButtonsElems.addClass('ibo-is-hidden');
// Throttle the processing in order to limit CPU usage
clearTimeout(hTimeout);
hTimeout = setTimeout(() => {
let iNewHeaderWidth = parseInt(oHeaderElem.outerWidth());
if (Math.abs(iNewHeaderWidth - iCurrentHeaderWidth) < 5) {
return;
}
oTransitionButtonsElems.removeClass('ibo-is-hidden');
let oLastTransitionButton = oButtonsToolbarElem.find('[name="next_action"][data-role="ibo-button"]:last');
let iLastTransitionButtonBorderY = oLastTransitionButton.offset().left + oLastTransitionButton.outerWidth();
let iPanelRightBorderY = oHeaderElem.offset().left + oHeaderElem.outerWidth();
if (iLastTransitionButtonBorderY > iPanelRightBorderY) {
oButtonsToolbarElem.find('.action[data-role="ibo-button"]:not([name="cancel"])').addClass('ibo-is-hidden');
oButtonsToolbarElem.find('[data-role="ibo-button-group"]:last').removeClass('ibo-is-hidden');
}
else {
oButtonsToolbarElem.find('.action[data-role="ibo-button"]:not([name="cancel"])').removeClass('ibo-is-hidden');
oButtonsToolbarElem.find('[data-role="ibo-button-group"]:last').addClass('ibo-is-hidden');
}
iCurrentHeaderWidth = parseInt(oHeaderElem.outerWidth());
}, 100);
});
// Note: ":first" used to only target the header of the object, not what could be in the content of its body
oObserver.observe(oHeaderElem[0]);
},
// Helpers
/**
* @return {String} The current object display mode ({@see PHP \cmdbAbstractObject for possible values})
* @private
*/
_getObjectMode: function()
{
return this.element.attr('data-object-mode');
}
});
});