TreeRenderer: Use Icinga Web's collapsible implementation now

resolves #254
This commit is contained in:
Johannes Meyer 2022-07-21 15:48:15 +02:00
parent 6abcf8a757
commit c2e06aca0a
4 changed files with 95 additions and 78 deletions

View file

@ -265,7 +265,7 @@ abstract class Renderer extends HtmlDocument
*/
public function getId(Node $node, $path)
{
return md5((empty($path) ? '' : implode(';', $path)) . $node->getName());
return 'businessprocess-' . md5((empty($path) ? '' : implode(';', $path)) . $node->getName());
}
public function setPath(array $path)

View file

@ -2,19 +2,24 @@
namespace Icinga\Module\Businessprocess\Renderer;
use Icinga\Application\Version;
use Icinga\Date\DateFormatter;
use Icinga\Module\Businessprocess\BpConfig;
use Icinga\Module\Businessprocess\BpNode;
use Icinga\Module\Businessprocess\ImportedNode;
use Icinga\Module\Businessprocess\Node;
use Icinga\Module\Businessprocess\Web\Form\CsrfToken;
use Icinga\Module\Icingadb\Model\State;
use ipl\Html\Attributes;
use ipl\Html\BaseHtmlElement;
use ipl\Html\Html;
use ipl\Html\HtmlElement;
use ipl\Web\Widget\Icon;
use ipl\Web\Widget\StateBall;
class TreeRenderer extends Renderer
{
const NEW_COLLAPSIBLE_IMPLEMENTATION_SINCE = '2.11.2';
public function assemble()
{
$bp = $this->config;
@ -32,7 +37,6 @@ class TreeRenderer extends Renderer
'put' => 'function:rowPutAllowed'
]),
'data-sortable-invert-swap' => 'true',
'data-is-root-config' => $this->wantsRootNodes() ? 'true' : 'false',
'data-csrf-token' => CsrfToken::generate()
],
$this->renderBp($bp)
@ -42,6 +46,10 @@ class TreeRenderer extends Renderer
'data-action-url',
$this->getUrl()->with(['config' => $bp->getName()])->getAbsoluteUrl()
);
if (version_compare(Version::VERSION, self::NEW_COLLAPSIBLE_IMPLEMENTATION_SINCE, '<')) {
$tree->getAttributes()->add('data-is-root-config', true);
}
} else {
$nodeName = $this->parent instanceof ImportedNode
? $this->parent->getNodeName()
@ -192,27 +200,35 @@ class TreeRenderer extends Renderer
$attributes->add('class', 'node');
}
$div = Html::tag('div');
$li->add($div);
$details = new HtmlElement('details', Attributes::create(['open' => true]));
$summary = new HtmlElement('summary');
if (version_compare(Version::VERSION, self::NEW_COLLAPSIBLE_IMPLEMENTATION_SINCE, '>=')) {
$details->getAttributes()->add('class', 'collapsible');
$summary->getAttributes()->add('class', 'collapsible-control'); // Helps JS, improves performance a bit
}
$div->add($node->getLink());
$div->add($this->getNodeIcons($node, $path));
$summary->addHtml(
new Icon('caret-down', ['class' => 'collapse-icon']),
new Icon('caret-right', ['class' => 'expand-icon'])
);
$div->add(Html::tag('span', null, $node->getAlias()));
$summary->add($this->getNodeIcons($node, $path));
$summary->add(Html::tag('span', null, $node->getAlias()));
if ($node instanceof BpNode) {
$div->add(Html::tag('span', ['class' => 'op'], $node->operatorHtml()));
$summary->add(Html::tag('span', ['class' => 'op'], $node->operatorHtml()));
}
if ($node instanceof BpNode && $node->hasInfoUrl()) {
$div->add($this->createInfoAction($node));
$summary->add($this->createInfoAction($node));
}
$differentConfig = $node->getBpConfig()->getName() !== $this->getBusinessProcess()->getName();
if (! $this->isLocked() && !$differentConfig) {
$div->add($this->getActionIcons($bp, $node));
$summary->add($this->getActionIcons($bp, $node));
} elseif ($differentConfig) {
$div->add($this->actionIcon(
$summary->add($this->actionIcon(
'forward',
$this->getSourceUrl($node)->addParams(['mode' => 'tree'])->getAbsoluteUrl(),
mt('businessprocess', 'Show this process as part of its original configuration')
@ -240,7 +256,6 @@ class TreeRenderer extends Renderer
])
->getAbsoluteUrl()
]);
$li->add($ul);
$path[] = $differentConfig ? $node->getIdentifier() : $node->getName();
foreach ($node->getChildren() as $name => $child) {
@ -251,6 +266,10 @@ class TreeRenderer extends Renderer
}
}
$details->addHtml($summary);
$details->addHtml($ul);
$li->addHtml($details);
return $li;
}

View file

@ -118,7 +118,7 @@ ul.bp {
}
}
&[data-sortable-disabled="true"] {
li.process > div {
li.process summary {
cursor: pointer;
}
}
@ -143,15 +143,17 @@ ul.bp {
// ghost style
&.sortable > li.sortable-ghost {
position: relative;
overflow: hidden;
max-height: 30em;
background-color: @gray-lighter;
border: .2em dotted @gray-light;
border-left-width: 0;
border-right-width: 0;
> details {
position: relative;
overflow: hidden;
max-height: 30em;
background-color: @gray-lighter;
border: .2em dotted @gray-light;
border-left-width: 0;
border-right-width: 0;
}
&.process:after {
&.process > .details:after {
// TODO: Only apply if content overflows?
content: " ";
position: absolute;
@ -164,12 +166,14 @@ ul.bp {
}
// header style
li.process > div {
li.process summary {
padding: .291666667em 0;
border-bottom: 1px solid @gray-light;
user-select: none;
> a.toggle {
min-width: 1.25em; // So that process icons align with their node's icons
> .icon:nth-child(1),
> .icon:nth-child(2) {
min-width: 1.3em; // So that process icons align with their node's icons
color: @gray;
}
@ -187,8 +191,23 @@ ul.bp {
}
}
li.process.sortable-ghost details:not([open]) > summary {
border-bottom: none;
}
// TODO: Remove once support for Icinga Web 2.10.x is dropped
li.process details:not(.collapsible) {
&[open] > summary .expand-icon {
display: none;
}
&:not([open]) > summary .collapse-icon {
display: none;
}
}
// subprocess style
li.process > ul {
li.process > details ul {
padding-left: 2em;
list-style-type: none;
@ -216,7 +235,7 @@ ul.bp {
}
// horizontal layout
li.process > div,
li.process summary,
li:not(.process) {
display: flex;
align-items: center;
@ -241,42 +260,23 @@ ul.bp {
}
// collapse handling
li.process {
// toggle, default
> div > a.toggle > i:before {
-webkit-transition: -webkit-transform 0.3s;
-moz-transition: -moz-transform 0.3s;
-o-transition: -o-transform 0.3s;
transition: transform 0.3s;
}
li.process details:not([open]) {
margin-bottom: (@vertical-tree-item-gap * 2);
// toggle, collapsed
&.collapsed > div > a.toggle > i:before {
-moz-transform:rotate(-90deg);
-ms-transform:rotate(-90deg);
-o-transform:rotate(-90deg);
-webkit-transform:rotate(-90deg);
transform:rotate(-90deg);
}
&.collapsed {
margin-bottom: (@vertical-tree-item-gap * 2);
> ul.bp {
display: none;
}
> ul.bp {
display: none;
}
}
// hover style
li.process:hover > div {
li.process:hover summary {
background-color: @tr-active-color;
}
li:not(.process):hover {
background-color: @tr-active-color;
}
li.process > div > .state-ball,
li.process summary > .state-ball,
li:not(.process) > .state-ball {
border: .15em solid @body-bg-color;

View file

@ -25,8 +25,7 @@
this.module.on('focus', 'form input, form textarea, form select', this.formElementFocus);
this.module.on('click', 'li.process a.toggle', this.processToggleClick);
this.module.on('click', 'li.process > div', this.processHeaderClick);
this.module.on('click', 'li.process summary:not(.collapsible-control)', this.processHeaderClick);
this.module.on('end', 'ul.sortable', this.rowDropped);
this.module.on('click', 'div.tiles > div', this.tileClick);
@ -42,42 +41,41 @@
onRendered: function (event) {
var $container = $(event.currentTarget);
this.fixFullscreen($container);
this.restoreCollapsedBps($container);
this.restoreCollapsedBps(event.target);
this.highlightFormErrors($container);
this.hideInactiveFormDescriptions($container);
this.fixTileLinksOnDashboard($container);
},
processToggleClick: function (event) {
// TODO: Remove once support for Icinga Web 2.10.x is dropped
processHeaderClick: function (event) {
event.stopPropagation();
event.preventDefault();
var $li = $(event.currentTarget).closest('li.process');
$li.toggleClass('collapsed');
let details = event.currentTarget.parentNode;
details.open = ! details.open;
var $bpUl = $(event.currentTarget).closest('.content > ul.bp');
if (! $bpUl.length || !$bpUl.data('isRootConfig')) {
let bpUl = event.currentTarget.closest('.content > ul.bp');
if (! bpUl || ! ('isRootConfig' in bpUl.dataset)) {
return;
}
var bpName = $bpUl.attr('id');
let bpName = bpUl.id;
if (typeof this.idCache[bpName] === 'undefined') {
this.idCache[bpName] = [];
}
var index = this.idCache[bpName].indexOf($li.attr('id'));
if ($li.is('.collapsed')) {
let li = details.parentNode;
let index = this.idCache[bpName].indexOf(li.id);
if (! details.open) {
if (index === -1) {
this.idCache[bpName].push($li.attr('id'));
this.idCache[bpName].push(li.id);
}
} else if (index !== -1) {
this.idCache[bpName].splice(index, 1);
}
},
processHeaderClick: function (event) {
this.processToggleClick(event);
},
hideInactiveFormDescriptions: function($container) {
$container.find('dd').not('.active').find('p.description').hide();
},
@ -226,23 +224,23 @@
}
},
restoreCollapsedBps: function($container) {
var $bpUl = $container.find('.content > ul.bp');
if (! $bpUl.length || !$bpUl.data('isRootConfig')) {
// TODO: Remove once support for Icinga Web 2.10.x is dropped
restoreCollapsedBps: function(container) {
let bpUl = container.querySelector('.content > ul.bp');
if (! bpUl || ! ('isRootConfig' in bpUl.dataset)) {
return;
}
var bpName = $bpUl.attr('id');
let bpName = bpUl.id;
if (typeof this.idCache[bpName] === 'undefined') {
return;
}
var _this = this;
$bpUl.find('li.process')
.filter(function () {
return _this.idCache[bpName].indexOf(this.id) !== -1;
})
.addClass('collapsed');
bpUl.querySelectorAll('li.process').forEach(li => {
if (this.idCache[bpName].indexOf(li.id) !== -1) {
li.querySelector(':scope > details').open = false;
}
});
},
/** BEGIN Form handling, borrowed from Director **/