Skip to content

Commit 62e7e35

Browse files
Introduce AutosubmitDecorator
1 parent 01b4082 commit 62e7e35

3 files changed

Lines changed: 169 additions & 0 deletions

File tree

src/Compat/CompatForm.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ public function applyDefaultElementDecorators(): static
7272
]
7373
],
7474
'RenderElement',
75+
'Autosubmit' => [
76+
'name' => 'Autosubmit',
77+
'options' => [
78+
'uniqueName' => fn(string $name) => Icinga::app()->getRequest()->protectId($name)
79+
]
80+
],
7581
'Description' => [
7682
'name' => 'Description',
7783
'options' => [
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
namespace ipl\Web\Compat\FormDecorator;
4+
5+
use ipl\Html\Attributes;
6+
use ipl\Html\Contract\DecorationResult;
7+
use ipl\Html\Contract\DecoratorOptions;
8+
use ipl\Html\Contract\DecoratorOptionsInterface;
9+
use ipl\Html\Contract\FormElementDecoration;
10+
use ipl\Html\Contract\FormElement;
11+
use ipl\Html\FormElement\CheckboxElement;
12+
use ipl\Html\HtmlElement;
13+
use ipl\Html\Text;
14+
use ipl\I18n\Translation;
15+
use ipl\Web\Widget\Icon;
16+
17+
/**
18+
* Decorates the autosubmit elements with a spinner icon
19+
*/
20+
class AutosubmitDecorator implements FormElementDecoration, DecoratorOptionsInterface
21+
{
22+
use DecoratorOptions;
23+
use Translation;
24+
25+
/** @var callable A callback used to generate a unique ID based on the element name */
26+
private $uniqueName = 'uniqid';
27+
28+
public function decorateFormElement(DecorationResult $result, FormElement $formElement): void
29+
{
30+
if (
31+
! $formElement instanceof CheckboxElement
32+
|| ! in_array('autosubmit', (array) $formElement->getAttribute('class')->getValue(), true)
33+
) {
34+
return;
35+
}
36+
37+
if ($formElement->getAttributes()->has('id')) {
38+
$elementId = $formElement->getAttributes()->get('id')->getValue();
39+
} else {
40+
$elementId = call_user_func($this->uniqueName, $formElement->getName());
41+
}
42+
43+
$autosubmitId = 'autosubmit_spinner_' . $elementId;
44+
$formElement->getAttributes()->add('aria-describedby', $autosubmitId);
45+
46+
$title = $this->translate('This page will be automatically updated upon change of the value');
47+
48+
$result
49+
->append(new Icon(
50+
'rotate-right',
51+
Attributes::create(['aria-hidden' => 'true', 'role' => 'img', 'title' => $title, 'class' => 'spinner'])
52+
))
53+
->append(new HtmlElement(
54+
'span',
55+
Attributes::create(['class' => 'sr-only', 'id' => $autosubmitId]),
56+
new Text($title)
57+
));
58+
}
59+
60+
protected function registerAttributeCallbacks(Attributes $attributes): void
61+
{
62+
$attributes->registerAttributeCallback('uniqueName', null, fn($callback) => $this->uniqueName = $callback);
63+
}
64+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
namespace ipl\Tests\Web\Compat\FormDecorator;
4+
5+
use ipl\Html\FormDecoration\FormElementDecorationResult;
6+
use ipl\Html\FormElement\CheckboxElement;
7+
use ipl\Html\FormElement\TextElement;
8+
use ipl\I18n\NoopTranslator;
9+
use ipl\I18n\StaticTranslator;
10+
use ipl\Tests\Html\TestCase as IplHtmlTestCase;
11+
use ipl\Web\Compat\FormDecorator\AutosubmitDecorator;
12+
13+
class AutosubmitDecoratorTest extends IplHtmlTestCase
14+
{
15+
protected AutosubmitDecorator $decorator;
16+
17+
public function setUp(): void
18+
{
19+
$this->decorator = new AutosubmitDecorator();
20+
StaticTranslator::$instance = new NoopTranslator();
21+
}
22+
23+
public function testNoDecorationForNonCheckboxElement(): void
24+
{
25+
$result = new FormElementDecorationResult();
26+
$this->decorator->decorateFormElement(
27+
$result,
28+
new TextElement('test', ['class' => 'autosubmit', 'id' => 'test-id'])
29+
);
30+
31+
$this->assertSame('', $result->assemble()->render());
32+
}
33+
34+
public function testNoDecorationForCheckboxWithoutAutosubmitClass(): void
35+
{
36+
$element = new CheckboxElement('test', ['id' => 'test-id']);
37+
$result = new FormElementDecorationResult();
38+
$this->decorator->decorateFormElement($result, $element);
39+
40+
$this->assertSame('', $result->assemble()->render());
41+
$this->assertFalse($element->getAttributes()->has('aria-describedby'));
42+
}
43+
44+
public function testDecorationForAutosubmitCheckboxWithId(): void
45+
{
46+
$element = new CheckboxElement('test', ['class' => 'autosubmit', 'id' => 'test-id']);
47+
$result = new FormElementDecorationResult();
48+
$this->decorator->decorateFormElement($result, $element);
49+
50+
$title = 'This page will be automatically updated upon change of the value';
51+
$html = <<<HTML
52+
<i class="icon fa-rotate-right spinner fa" aria-hidden="true" role="img" title="$title"></i>
53+
<span class="sr-only" id="autosubmit_spinner_test-id">$title</span>
54+
HTML;
55+
56+
$this->assertHtml($html, $result->assemble());
57+
$this->assertSame(
58+
'autosubmit_spinner_test-id',
59+
$element->getAttributes()->get('aria-describedby')->getValue()
60+
);
61+
}
62+
63+
public function testDecoratorGeneratesIdWhenNoneIsSet(): void
64+
{
65+
$element = new CheckboxElement('test', ['class' => 'autosubmit']);
66+
$result = new FormElementDecorationResult();
67+
$this->decorator->decorateFormElement($result, $element);
68+
69+
$ariaDescribedBy = $element->getAttributes()->get('aria-describedby')->getValue();
70+
$this->assertNotEmpty($ariaDescribedBy);
71+
$this->assertStringStartsWith('autosubmit_spinner_', $ariaDescribedBy);
72+
}
73+
74+
public function testCustomUniqueNameCallback(): void
75+
{
76+
$this->decorator->getAttributes()->set('uniqueName', fn($name) => 'custom-' . $name);
77+
78+
$element = new CheckboxElement('test', ['class' => 'autosubmit']);
79+
$result = new FormElementDecorationResult();
80+
$this->decorator->decorateFormElement($result, $element);
81+
82+
$this->assertSame(
83+
'autosubmit_spinner_custom-test',
84+
$element->getAttributes()->get('aria-describedby')->getValue()
85+
);
86+
}
87+
88+
public function testSpinnerIdUsesExplicitIdOverGeneratedOne(): void
89+
{
90+
$element = new CheckboxElement('test', ['class' => 'autosubmit', 'id' => 'explicit-id']);
91+
$result = new FormElementDecorationResult();
92+
$this->decorator->decorateFormElement($result, $element);
93+
94+
$this->assertSame(
95+
'autosubmit_spinner_explicit-id',
96+
$element->getAttributes()->get('aria-describedby')->getValue()
97+
);
98+
}
99+
}

0 commit comments

Comments
 (0)