Skip to content

Commit

Permalink
Bootstrap 5: add support for rendering toggle buttons
Browse files Browse the repository at this point in the history
  • Loading branch information
xificurk committed Dec 29, 2023
1 parent 9c69004 commit 0dcace5
Show file tree
Hide file tree
Showing 30 changed files with 893 additions and 2 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,13 @@ In inline mode the error messages are always rendered as tooltips. In the other

To render a checkbox as a switch, you need to set type option: `$checkboxInput->setOption('type', 'switch')`.

To render radio or checkbox as a [toggle button](https://getbootstrap.com/docs/5.3/forms/checks-radios/#toggle-buttons), add `btn` class (and any desired button styling class) to label prototype: `$radio->getItemLabelPrototype()->addClass('btn btn-outline-primary')`.

`Bootstrap5Renderer` makes a couple of adjustments to the form before it is passed over to `TemplateRenderer`:
1) It adds `btn btn-primary` classes to the control prototype of first `SubmitButton` in the form, unless there already is such a control in the form.
2) It adds `btn btn-secondary` classes to the control prototype of every `Button` control, unless it already has `btn` class set.
3) Changes `type` option on all `CheckboxList` controls from `checkbox` to `checkboxlist`.
3) Changes `type` option on all `Checkbox`, `CheckboxList`, `RadioList` controls setup to be rendered as toggle buttons from `checkbox`/`radio` to `togglebutton`/`togglebuttonlist`.
4) Changes `type` option on all `CheckboxList` controls from `checkbox` to `checkboxlist`.

You can change the default renderer configuration from your `config.neon`:
```yaml
Expand Down
26 changes: 25 additions & 1 deletion src/FormRenderer/Bootstrap5Renderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Nette;
use Nette\Forms\Controls;
use Nette\Forms\Form;
use function in_array;

class Bootstrap5Renderer implements Nette\Forms\FormRenderer
{
Expand Down Expand Up @@ -119,12 +120,35 @@ protected function prepareForm(Form $form): void
}
}

/** @var Controls\Checkbox $control */
foreach ($form->getComponents(true, Controls\Checkbox::class) as $control) {
if ($control->getOption('type') !== 'checkbox') {
continue;
}
if (in_array('btn', Helpers::parseClassList($control->getLabelPrototype()->getClass()), true)) {
$control->setOption('type', 'togglebutton');
}
}
/** @var Controls\CheckboxList $control */
foreach ($form->getComponents(true, Controls\CheckboxList::class) as $control) {
if ($control->getOption('type') === 'checkbox') {
if ($control->getOption('type') !== 'checkbox') {
continue;
}
if (in_array('btn', Helpers::parseClassList($control->getItemLabelPrototype()->getClass()), true)) {
$control->setOption('type', 'togglebuttonlist');
} else {
$control->setOption('type', 'checkboxlist');
}
}
/** @var Controls\RadioList $control */
foreach ($form->getComponents(true, Controls\RadioList::class) as $control) {
if ($control->getOption('type') !== 'radio') {
continue;
}
if (in_array('btn', Helpers::parseClassList($control->getItemLabelPrototype()->getClass()), true)) {
$control->setOption('type', 'togglebuttonlist');
}
}
}

protected function findPrimaryButton(Form $form): ?Controls\SubmitButton
Expand Down
24 changes: 24 additions & 0 deletions src/FormRenderer/templates/bootstrap5.latte
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@
{include #control-type-checkboxlist, control => $control}
{/define}

{define #control-type-togglebuttonlist}
{include #control-type-checkboxlist, control => $control}
{/define}

{define #control-errors}
{if $useErrorTooltips}
<div n:if="count($control->getErrors()) > 0" class="d-block invalid-tooltip" n:inner-foreach="$control->getErrors() as $error">{$error}{sep} {/sep}</div>
Expand Down Expand Up @@ -217,6 +221,15 @@
</div>
{/define}

{define #input-type-togglebutton}
<input n:name="$control:" n:class="
$control->getControlPrototype()->class,
btn-check,
($control|validationClass)
">
<label n:name="$control:" />
{/define}

{define #input-type-checkboxlist}
<div n:foreach="$control->items as $k => $v" n:class="
$control->getSeparatorPrototype()->class,
Expand All @@ -239,3 +252,14 @@
{define #input-type-radio}
{include #input-type-checkboxlist, control => $control}
{/define}

{define #input-type-togglebuttonlist}
{foreach $control->items as $k => $v}
<input n:name="$control:$k" n:class="
$control->getControlPrototype()->class,
btn-check,
($control|validationClass)
">
<label n:name="$control:$k">{=$control->translate($v)}</label>
{/foreach}
{/define}
12 changes: 12 additions & 0 deletions tests/FormRenderer/Bootstrap5RendererTest.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,18 @@ class Bootstrap5RendererTest extends TestCase
$switch->setOption('type', 'switch');
$switch->setOption('description', 'Switch description');

$toggle = $form->addCheckbox('toggle', 'Toggle');
$toggle->getLabelPrototype()->addClass('btn');

$checkboxToggles = $form->addCheckboxList('checkboxtogglelist', 'Checkbox toggle list', ['foo', 'bar', 'baz']);
$checkboxToggles->getItemLabelPrototype()->addClass('btn btn-secondary');
$checkboxToggles->setDisabled(['1']);
$checkboxToggles->getSeparatorPrototype()->setName('');

$radioToggles = $form->addRadioList('radiotogglelist', 'Radio toggle list', ['foo', 'bar', 'baz']);
$radioToggles->getItemLabelPrototype()->addClass('btn btn-outline-primary');
$radioToggles->setDisabled(['1']);

return $form;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,35 @@
<div class="form-text">Control switch description.</div>

</div>

<div class="mb-3">
<input class="btn-check" id="frm-toggle" name="toggle" type="checkbox">
<label class="btn" for="frm-toggle">Toggle</label>
<div class="form-text">Control toggle description.</div>
</div>
<div class="mb-3">
<label class="form-label">Checkbox toggle list</label>
<div>
<input class="btn-check" id="frm-checkboxtogglelist-0" name="checkboxtogglelist[]" type="checkbox" value="0">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-0">foo</label>
<input class="btn-check" disabled id="frm-checkboxtogglelist-1" name="checkboxtogglelist[]" type="checkbox" value="1">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-1">bar</label>
<input class="btn-check" id="frm-checkboxtogglelist-2" name="checkboxtogglelist[]" type="checkbox" value="2">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-2">baz</label>
</div>
<div class="form-text">Control checkboxtogglelist description.</div>
</div>
<div class="mb-3">
<label class="form-label">Radio toggle list</label>
<div>
<input class="btn-check" id="frm-radiotogglelist-0" name="radiotogglelist" type="radio" value="0">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-0">foo</label>
<input class="btn-check" disabled id="frm-radiotogglelist-1" name="radiotogglelist" type="radio" value="1">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-1">bar</label>
<input class="btn-check" id="frm-radiotogglelist-2" name="radiotogglelist" type="radio" value="2">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-2">baz</label>
</div>
<div class="form-text">Control radiotogglelist description.</div>
</div>

<input type="hidden" name="hidden" value=""> </form>
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,32 @@
<div class="form-text">Switch description</div>

</div>

<div class="custom-toggle mb-3">
<input class="btn-check" id="frm-toggle" name="toggle" type="checkbox">
<label class="btn" for="frm-toggle">Toggle</label>
</div>
<div class="custom-checkboxtogglelist mb-3">
<label class="form-label">Checkbox toggle list</label>
<div>
<input class="btn-check" id="frm-checkboxtogglelist-0" name="checkboxtogglelist[]" type="checkbox" value="0">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-0">foo</label>
<input class="btn-check" disabled id="frm-checkboxtogglelist-1" name="checkboxtogglelist[]" type="checkbox" value="1">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-1">bar</label>
<input class="btn-check" id="frm-checkboxtogglelist-2" name="checkboxtogglelist[]" type="checkbox" value="2">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-2">baz</label>
</div>
</div>
<div class="custom-radiotogglelist mb-3">
<label class="form-label">Radio toggle list</label>
<div>
<input class="btn-check" id="frm-radiotogglelist-0" name="radiotogglelist" type="radio" value="0">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-0">foo</label>
<input class="btn-check" disabled id="frm-radiotogglelist-1" name="radiotogglelist" type="radio" value="1">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-1">bar</label>
<input class="btn-check" id="frm-radiotogglelist-2" name="radiotogglelist" type="radio" value="2">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-2">baz</label>
</div>
</div>

<input type="hidden" name="hidden" value=""> </form>
28 changes: 28 additions & 0 deletions tests/FormRenderer/expected/bootstrap5-basic-customControlId.html
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,32 @@
<div class="form-text">Switch description</div>

</div>

<div class="mb-3" id="custom-toggle">
<input class="btn-check" id="frm-toggle" name="toggle" type="checkbox">
<label class="btn" for="frm-toggle">Toggle</label>
</div>
<div class="mb-3" id="custom-checkboxtogglelist">
<label class="form-label">Checkbox toggle list</label>
<div>
<input class="btn-check" id="frm-checkboxtogglelist-0" name="checkboxtogglelist[]" type="checkbox" value="0">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-0">foo</label>
<input class="btn-check" disabled id="frm-checkboxtogglelist-1" name="checkboxtogglelist[]" type="checkbox" value="1">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-1">bar</label>
<input class="btn-check" id="frm-checkboxtogglelist-2" name="checkboxtogglelist[]" type="checkbox" value="2">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-2">baz</label>
</div>
</div>
<div class="mb-3" id="custom-radiotogglelist">
<label class="form-label">Radio toggle list</label>
<div>
<input class="btn-check" id="frm-radiotogglelist-0" name="radiotogglelist" type="radio" value="0">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-0">foo</label>
<input class="btn-check" disabled id="frm-radiotogglelist-1" name="radiotogglelist" type="radio" value="1">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-1">bar</label>
<input class="btn-check" id="frm-radiotogglelist-2" name="radiotogglelist" type="radio" value="2">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-2">baz</label>
</div>
</div>

<input type="hidden" name="hidden" value=""> </form>
31 changes: 31 additions & 0 deletions tests/FormRenderer/expected/bootstrap5-basic-errors-tooltips.html
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,35 @@
</div>
<div class="form-text">Switch description</div>
</div>
<div class="mb-3">
<div class="position-relative">
<input class="btn-check is-invalid" id="frm-toggle" name="toggle" type="checkbox">
<label class="btn" for="frm-toggle">Toggle</label>
<div class="d-block invalid-tooltip">Control toggle error 1. Control toggle error 2.</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Checkbox toggle list</label>
<div class="position-relative">
<input class="btn-check is-invalid" id="frm-checkboxtogglelist-0" name="checkboxtogglelist[]" type="checkbox" value="0">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-0">foo</label>
<input class="btn-check is-invalid" disabled id="frm-checkboxtogglelist-1" name="checkboxtogglelist[]" type="checkbox" value="1">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-1">bar</label>
<input class="btn-check is-invalid" id="frm-checkboxtogglelist-2" name="checkboxtogglelist[]" type="checkbox" value="2">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-2">baz</label>
<div class="d-block invalid-tooltip">Control checkboxtogglelist error 1. Control checkboxtogglelist error 2.</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Radio toggle list</label>
<div class="position-relative">
<input class="btn-check is-invalid" id="frm-radiotogglelist-0" name="radiotogglelist" type="radio" value="0">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-0">foo</label>
<input class="btn-check is-invalid" disabled id="frm-radiotogglelist-1" name="radiotogglelist" type="radio" value="1">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-1">bar</label>
<input class="btn-check is-invalid" id="frm-radiotogglelist-2" name="radiotogglelist" type="radio" value="2">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-2">baz</label>
<div class="d-block invalid-tooltip">Control radiotogglelist error 1. Control radiotogglelist error 2.</div>
</div>
</div>
<input type="hidden" name="hidden" value=""> </form>
34 changes: 34 additions & 0 deletions tests/FormRenderer/expected/bootstrap5-basic-errors.html
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,38 @@
<div class="d-block invalid-feedback">Control switch error 2.</div>

</div>

<div class="mb-3">
<input class="btn-check is-invalid" id="frm-toggle" name="toggle" type="checkbox">
<label class="btn" for="frm-toggle">Toggle</label>
<div class="d-block invalid-feedback">Control toggle error 1.</div>
<div class="d-block invalid-feedback">Control toggle error 2.</div>
</div>
<div class="mb-3">
<label class="form-label">Checkbox toggle list</label>
<div>
<input class="btn-check is-invalid" id="frm-checkboxtogglelist-0" name="checkboxtogglelist[]" type="checkbox" value="0">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-0">foo</label>
<input class="btn-check is-invalid" disabled id="frm-checkboxtogglelist-1" name="checkboxtogglelist[]" type="checkbox" value="1">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-1">bar</label>
<input class="btn-check is-invalid" id="frm-checkboxtogglelist-2" name="checkboxtogglelist[]" type="checkbox" value="2">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-2">baz</label>
</div>
<div class="d-block invalid-feedback">Control checkboxtogglelist error 1.</div>
<div class="d-block invalid-feedback">Control checkboxtogglelist error 2.</div>
</div>
<div class="mb-3">
<label class="form-label">Radio toggle list</label>
<div>
<input class="btn-check is-invalid" id="frm-radiotogglelist-0" name="radiotogglelist" type="radio" value="0">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-0">foo</label>
<input class="btn-check is-invalid" disabled id="frm-radiotogglelist-1" name="radiotogglelist" type="radio" value="1">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-1">bar</label>
<input class="btn-check is-invalid" id="frm-radiotogglelist-2" name="radiotogglelist" type="radio" value="2">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-2">baz</label>
</div>
<div class="d-block invalid-feedback">Control radiotogglelist error 1.</div>
<div class="d-block invalid-feedback">Control radiotogglelist error 2.</div>
</div>

<input type="hidden" name="hidden" value=""> </form>
28 changes: 28 additions & 0 deletions tests/FormRenderer/expected/bootstrap5-basic-imports.html
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,32 @@
<div class="form-text">Switch description</div>

</div>

<div class="mb-3">
<input class="btn-check" id="frm-toggle" name="toggle" type="checkbox">
<label class="btn" for="frm-toggle">Toggle</label>
</div>
<div class="mb-3">
<label class="form-label">Checkbox toggle list</label>
<div>
<input class="btn-check" id="frm-checkboxtogglelist-0" name="checkboxtogglelist[]" type="checkbox" value="0">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-0">foo</label>
<input class="btn-check" disabled id="frm-checkboxtogglelist-1" name="checkboxtogglelist[]" type="checkbox" value="1">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-1">bar</label>
<input class="btn-check" id="frm-checkboxtogglelist-2" name="checkboxtogglelist[]" type="checkbox" value="2">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-2">baz</label>
</div>
</div>
<div class="mb-3">
<label class="form-label">Radio toggle list</label>
<div>
<input class="btn-check" id="frm-radiotogglelist-0" name="radiotogglelist" type="radio" value="0">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-0">foo</label>
<input class="btn-check" disabled id="frm-radiotogglelist-1" name="radiotogglelist" type="radio" value="1">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-1">bar</label>
<input class="btn-check" id="frm-radiotogglelist-2" name="radiotogglelist" type="radio" value="2">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-2">baz</label>
</div>
</div>

<input type="hidden" name="hidden" value=""> </form>
28 changes: 28 additions & 0 deletions tests/FormRenderer/expected/bootstrap5-basic-renderValidState.html
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,32 @@
<div class="form-text">Switch description</div>

</div>

<div class="mb-3">
<input class="btn-check" id="frm-toggle" name="toggle" type="checkbox">
<label class="btn" for="frm-toggle">Toggle</label>
</div>
<div class="mb-3">
<label class="form-label">Checkbox toggle list</label>
<div>
<input class="btn-check" id="frm-checkboxtogglelist-0" name="checkboxtogglelist[]" type="checkbox" value="0">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-0">foo</label>
<input class="btn-check" disabled id="frm-checkboxtogglelist-1" name="checkboxtogglelist[]" type="checkbox" value="1">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-1">bar</label>
<input class="btn-check" id="frm-checkboxtogglelist-2" name="checkboxtogglelist[]" type="checkbox" value="2">
<label class="btn btn-secondary" for="frm-checkboxtogglelist-2">baz</label>
</div>
</div>
<div class="mb-3">
<label class="form-label">Radio toggle list</label>
<div>
<input class="btn-check" id="frm-radiotogglelist-0" name="radiotogglelist" type="radio" value="0">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-0">foo</label>
<input class="btn-check" disabled id="frm-radiotogglelist-1" name="radiotogglelist" type="radio" value="1">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-1">bar</label>
<input class="btn-check" id="frm-radiotogglelist-2" name="radiotogglelist" type="radio" value="2">
<label class="btn btn-outline-primary" for="frm-radiotogglelist-2">baz</label>
</div>
</div>

<input type="hidden" name="hidden" value=""> </form>
28 changes: 28 additions & 0 deletions tests/FormRenderer/expected/bootstrap5-basic-requiredControl.html
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,32 @@
<div class="form-text">Switch description</div>

</div>

<div class="mb-3 required">
<input class="btn-check" id="frm-toggle" name="toggle" type="checkbox" data-nette-rules='[{"op":":filled","msg":"REQUIRED"}]' required>
<label class="btn" for="frm-toggle">Toggle</label>
</div>
<div class="mb-3 required">
<label class="form-label">Checkbox toggle list</label>
<div>
<input class="btn-check" id="frm-checkboxtogglelist-0" name="checkboxtogglelist[]" type="checkbox" value="0" data-nette-rules='[{"op":":filled","msg":"REQUIRED"}]'>
<label class="btn btn-secondary" for="frm-checkboxtogglelist-0">foo</label>
<input class="btn-check" disabled id="frm-checkboxtogglelist-1" name="checkboxtogglelist[]" type="checkbox" value="1" data-nette-rules='[{"op":":filled","msg":"REQUIRED"}]'>
<label class="btn btn-secondary" for="frm-checkboxtogglelist-1">bar</label>
<input class="btn-check" id="frm-checkboxtogglelist-2" name="checkboxtogglelist[]" type="checkbox" value="2" data-nette-rules='[{"op":":filled","msg":"REQUIRED"}]'>
<label class="btn btn-secondary" for="frm-checkboxtogglelist-2">baz</label>
</div>
</div>
<div class="mb-3 required">
<label class="form-label">Radio toggle list</label>
<div>
<input class="btn-check" id="frm-radiotogglelist-0" name="radiotogglelist" type="radio" value="0" data-nette-rules='[{"op":":filled","msg":"REQUIRED"}]' required>
<label class="btn btn-outline-primary" for="frm-radiotogglelist-0">foo</label>
<input class="btn-check" disabled id="frm-radiotogglelist-1" name="radiotogglelist" type="radio" value="1" data-nette-rules='[{"op":":filled","msg":"REQUIRED"}]' required>
<label class="btn btn-outline-primary" for="frm-radiotogglelist-1">bar</label>
<input class="btn-check" id="frm-radiotogglelist-2" name="radiotogglelist" type="radio" value="2" data-nette-rules='[{"op":":filled","msg":"REQUIRED"}]' required>
<label class="btn btn-outline-primary" for="frm-radiotogglelist-2">baz</label>
</div>
</div>

<input type="hidden" name="hidden" value=""> </form>
Loading

0 comments on commit 0dcace5

Please sign in to comment.