Skip to content

Commit c2e06ac

Browse files
committed
TreeRenderer: Use Icinga Web's collapsible implementation now
resolves #254
1 parent 6abcf8a commit c2e06ac

File tree

4 files changed

+96
-79
lines changed

4 files changed

+96
-79
lines changed

library/Businessprocess/Renderer/Renderer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ public function getSourceUrl(BpNode $node)
265265
*/
266266
public function getId(Node $node, $path)
267267
{
268-
return md5((empty($path) ? '' : implode(';', $path)) . $node->getName());
268+
return 'businessprocess-' . md5((empty($path) ? '' : implode(';', $path)) . $node->getName());
269269
}
270270

271271
public function setPath(array $path)

library/Businessprocess/Renderer/TreeRenderer.php

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,24 @@
22

33
namespace Icinga\Module\Businessprocess\Renderer;
44

5+
use Icinga\Application\Version;
56
use Icinga\Date\DateFormatter;
67
use Icinga\Module\Businessprocess\BpConfig;
78
use Icinga\Module\Businessprocess\BpNode;
89
use Icinga\Module\Businessprocess\ImportedNode;
910
use Icinga\Module\Businessprocess\Node;
1011
use Icinga\Module\Businessprocess\Web\Form\CsrfToken;
11-
use Icinga\Module\Icingadb\Model\State;
12+
use ipl\Html\Attributes;
1213
use ipl\Html\BaseHtmlElement;
1314
use ipl\Html\Html;
15+
use ipl\Html\HtmlElement;
16+
use ipl\Web\Widget\Icon;
1417
use ipl\Web\Widget\StateBall;
1518

1619
class TreeRenderer extends Renderer
1720
{
21+
const NEW_COLLAPSIBLE_IMPLEMENTATION_SINCE = '2.11.2';
22+
1823
public function assemble()
1924
{
2025
$bp = $this->config;
@@ -32,7 +37,6 @@ public function assemble()
3237
'put' => 'function:rowPutAllowed'
3338
]),
3439
'data-sortable-invert-swap' => 'true',
35-
'data-is-root-config' => $this->wantsRootNodes() ? 'true' : 'false',
3640
'data-csrf-token' => CsrfToken::generate()
3741
],
3842
$this->renderBp($bp)
@@ -42,6 +46,10 @@ public function assemble()
4246
'data-action-url',
4347
$this->getUrl()->with(['config' => $bp->getName()])->getAbsoluteUrl()
4448
);
49+
50+
if (version_compare(Version::VERSION, self::NEW_COLLAPSIBLE_IMPLEMENTATION_SINCE, '<')) {
51+
$tree->getAttributes()->add('data-is-root-config', true);
52+
}
4553
} else {
4654
$nodeName = $this->parent instanceof ImportedNode
4755
? $this->parent->getNodeName()
@@ -192,27 +200,35 @@ public function renderNode(BpConfig $bp, Node $node, $path = array())
192200
$attributes->add('class', 'node');
193201
}
194202

195-
$div = Html::tag('div');
196-
$li->add($div);
203+
$details = new HtmlElement('details', Attributes::create(['open' => true]));
204+
$summary = new HtmlElement('summary');
205+
if (version_compare(Version::VERSION, self::NEW_COLLAPSIBLE_IMPLEMENTATION_SINCE, '>=')) {
206+
$details->getAttributes()->add('class', 'collapsible');
207+
$summary->getAttributes()->add('class', 'collapsible-control'); // Helps JS, improves performance a bit
208+
}
197209

198-
$div->add($node->getLink());
199-
$div->add($this->getNodeIcons($node, $path));
210+
$summary->addHtml(
211+
new Icon('caret-down', ['class' => 'collapse-icon']),
212+
new Icon('caret-right', ['class' => 'expand-icon'])
213+
);
214+
215+
$summary->add($this->getNodeIcons($node, $path));
200216

201-
$div->add(Html::tag('span', null, $node->getAlias()));
217+
$summary->add(Html::tag('span', null, $node->getAlias()));
202218

203219
if ($node instanceof BpNode) {
204-
$div->add(Html::tag('span', ['class' => 'op'], $node->operatorHtml()));
220+
$summary->add(Html::tag('span', ['class' => 'op'], $node->operatorHtml()));
205221
}
206222

207223
if ($node instanceof BpNode && $node->hasInfoUrl()) {
208-
$div->add($this->createInfoAction($node));
224+
$summary->add($this->createInfoAction($node));
209225
}
210226

211227
$differentConfig = $node->getBpConfig()->getName() !== $this->getBusinessProcess()->getName();
212228
if (! $this->isLocked() && !$differentConfig) {
213-
$div->add($this->getActionIcons($bp, $node));
229+
$summary->add($this->getActionIcons($bp, $node));
214230
} elseif ($differentConfig) {
215-
$div->add($this->actionIcon(
231+
$summary->add($this->actionIcon(
216232
'forward',
217233
$this->getSourceUrl($node)->addParams(['mode' => 'tree'])->getAbsoluteUrl(),
218234
mt('businessprocess', 'Show this process as part of its original configuration')
@@ -240,7 +256,6 @@ public function renderNode(BpConfig $bp, Node $node, $path = array())
240256
])
241257
->getAbsoluteUrl()
242258
]);
243-
$li->add($ul);
244259

245260
$path[] = $differentConfig ? $node->getIdentifier() : $node->getName();
246261
foreach ($node->getChildren() as $name => $child) {
@@ -251,6 +266,10 @@ public function renderNode(BpConfig $bp, Node $node, $path = array())
251266
}
252267
}
253268

269+
$details->addHtml($summary);
270+
$details->addHtml($ul);
271+
$li->addHtml($details);
272+
254273
return $li;
255274
}
256275

public/css/module.less

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ ul.bp {
118118
}
119119
}
120120
&[data-sortable-disabled="true"] {
121-
li.process > div {
121+
li.process summary {
122122
cursor: pointer;
123123
}
124124
}
@@ -143,15 +143,17 @@ ul.bp {
143143

144144
// ghost style
145145
&.sortable > li.sortable-ghost {
146-
position: relative;
147-
overflow: hidden;
148-
max-height: 30em;
149-
background-color: @gray-lighter;
150-
border: .2em dotted @gray-light;
151-
border-left-width: 0;
152-
border-right-width: 0;
153-
154-
&.process:after {
146+
> details {
147+
position: relative;
148+
overflow: hidden;
149+
max-height: 30em;
150+
background-color: @gray-lighter;
151+
border: .2em dotted @gray-light;
152+
border-left-width: 0;
153+
border-right-width: 0;
154+
}
155+
156+
&.process > .details:after {
155157
// TODO: Only apply if content overflows?
156158
content: " ";
157159
position: absolute;
@@ -164,12 +166,14 @@ ul.bp {
164166
}
165167

166168
// header style
167-
li.process > div {
169+
li.process summary {
168170
padding: .291666667em 0;
169171
border-bottom: 1px solid @gray-light;
172+
user-select: none;
170173

171-
> a.toggle {
172-
min-width: 1.25em; // So that process icons align with their node's icons
174+
> .icon:nth-child(1),
175+
> .icon:nth-child(2) {
176+
min-width: 1.3em; // So that process icons align with their node's icons
173177
color: @gray;
174178
}
175179

@@ -187,8 +191,23 @@ ul.bp {
187191
}
188192
}
189193

194+
li.process.sortable-ghost details:not([open]) > summary {
195+
border-bottom: none;
196+
}
197+
198+
// TODO: Remove once support for Icinga Web 2.10.x is dropped
199+
li.process details:not(.collapsible) {
200+
&[open] > summary .expand-icon {
201+
display: none;
202+
}
203+
204+
&:not([open]) > summary .collapse-icon {
205+
display: none;
206+
}
207+
}
208+
190209
// subprocess style
191-
li.process > ul {
210+
li.process > details ul {
192211
padding-left: 2em;
193212
list-style-type: none;
194213

@@ -216,7 +235,7 @@ ul.bp {
216235
}
217236

218237
// horizontal layout
219-
li.process > div,
238+
li.process summary,
220239
li:not(.process) {
221240
display: flex;
222241
align-items: center;
@@ -241,42 +260,23 @@ ul.bp {
241260
}
242261

243262
// collapse handling
244-
li.process {
245-
// toggle, default
246-
> div > a.toggle > i:before {
247-
-webkit-transition: -webkit-transform 0.3s;
248-
-moz-transition: -moz-transform 0.3s;
249-
-o-transition: -o-transform 0.3s;
250-
transition: transform 0.3s;
251-
}
263+
li.process details:not([open]) {
264+
margin-bottom: (@vertical-tree-item-gap * 2);
252265

253-
// toggle, collapsed
254-
&.collapsed > div > a.toggle > i:before {
255-
-moz-transform:rotate(-90deg);
256-
-ms-transform:rotate(-90deg);
257-
-o-transform:rotate(-90deg);
258-
-webkit-transform:rotate(-90deg);
259-
transform:rotate(-90deg);
260-
}
261-
262-
&.collapsed {
263-
margin-bottom: (@vertical-tree-item-gap * 2);
264-
265-
> ul.bp {
266-
display: none;
267-
}
266+
> ul.bp {
267+
display: none;
268268
}
269269
}
270270

271271
// hover style
272-
li.process:hover > div {
272+
li.process:hover summary {
273273
background-color: @tr-active-color;
274274
}
275275
li:not(.process):hover {
276276
background-color: @tr-active-color;
277277
}
278278

279-
li.process > div > .state-ball,
279+
li.process summary > .state-ball,
280280
li:not(.process) > .state-ball {
281281
border: .15em solid @body-bg-color;
282282

public/js/module.js

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@
2525

2626
this.module.on('focus', 'form input, form textarea, form select', this.formElementFocus);
2727

28-
this.module.on('click', 'li.process a.toggle', this.processToggleClick);
29-
this.module.on('click', 'li.process > div', this.processHeaderClick);
28+
this.module.on('click', 'li.process summary:not(.collapsible-control)', this.processHeaderClick);
3029
this.module.on('end', 'ul.sortable', this.rowDropped);
3130

3231
this.module.on('click', 'div.tiles > div', this.tileClick);
@@ -42,42 +41,41 @@
4241
onRendered: function (event) {
4342
var $container = $(event.currentTarget);
4443
this.fixFullscreen($container);
45-
this.restoreCollapsedBps($container);
44+
this.restoreCollapsedBps(event.target);
4645
this.highlightFormErrors($container);
4746
this.hideInactiveFormDescriptions($container);
4847
this.fixTileLinksOnDashboard($container);
4948
},
5049

51-
processToggleClick: function (event) {
50+
// TODO: Remove once support for Icinga Web 2.10.x is dropped
51+
processHeaderClick: function (event) {
5252
event.stopPropagation();
53+
event.preventDefault();
5354

54-
var $li = $(event.currentTarget).closest('li.process');
55-
$li.toggleClass('collapsed');
55+
let details = event.currentTarget.parentNode;
56+
details.open = ! details.open;
5657

57-
var $bpUl = $(event.currentTarget).closest('.content > ul.bp');
58-
if (! $bpUl.length || !$bpUl.data('isRootConfig')) {
58+
let bpUl = event.currentTarget.closest('.content > ul.bp');
59+
if (! bpUl || ! ('isRootConfig' in bpUl.dataset)) {
5960
return;
6061
}
6162

62-
var bpName = $bpUl.attr('id');
63+
let bpName = bpUl.id;
6364
if (typeof this.idCache[bpName] === 'undefined') {
6465
this.idCache[bpName] = [];
6566
}
6667

67-
var index = this.idCache[bpName].indexOf($li.attr('id'));
68-
if ($li.is('.collapsed')) {
68+
let li = details.parentNode;
69+
let index = this.idCache[bpName].indexOf(li.id);
70+
if (! details.open) {
6971
if (index === -1) {
70-
this.idCache[bpName].push($li.attr('id'));
72+
this.idCache[bpName].push(li.id);
7173
}
7274
} else if (index !== -1) {
7375
this.idCache[bpName].splice(index, 1);
7476
}
7577
},
7678

77-
processHeaderClick: function (event) {
78-
this.processToggleClick(event);
79-
},
80-
8179
hideInactiveFormDescriptions: function($container) {
8280
$container.find('dd').not('.active').find('p.description').hide();
8381
},
@@ -226,23 +224,23 @@
226224
}
227225
},
228226

229-
restoreCollapsedBps: function($container) {
230-
var $bpUl = $container.find('.content > ul.bp');
231-
if (! $bpUl.length || !$bpUl.data('isRootConfig')) {
227+
// TODO: Remove once support for Icinga Web 2.10.x is dropped
228+
restoreCollapsedBps: function(container) {
229+
let bpUl = container.querySelector('.content > ul.bp');
230+
if (! bpUl || ! ('isRootConfig' in bpUl.dataset)) {
232231
return;
233232
}
234233

235-
var bpName = $bpUl.attr('id');
234+
let bpName = bpUl.id;
236235
if (typeof this.idCache[bpName] === 'undefined') {
237236
return;
238237
}
239238

240-
var _this = this;
241-
$bpUl.find('li.process')
242-
.filter(function () {
243-
return _this.idCache[bpName].indexOf(this.id) !== -1;
244-
})
245-
.addClass('collapsed');
239+
bpUl.querySelectorAll('li.process').forEach(li => {
240+
if (this.idCache[bpName].indexOf(li.id) !== -1) {
241+
li.querySelector(':scope > details').open = false;
242+
}
243+
});
246244
},
247245

248246
/** BEGIN Form handling, borrowed from Director **/

0 commit comments

Comments
 (0)