Skip to content

Commit

Permalink
Add disabled attribute to checkbox and radio (#745)
Browse files Browse the repository at this point in the history
* Add disabled attribute to checkbox and radio

Closes #708

* Add some builder assertions for checkboxes
  • Loading branch information
timyates authored Mar 21, 2024
1 parent 43f49c2 commit a7d09d2
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<th:block th:fragment="inputcheckbox(el)" xmlns:th="http://www.thymeleaf.org"><th:block th:if="${el.label()}"><label th:replace="~{fieldset/label :: label(null, ${el.label()})}"></label></th:block><div class="form-check" th:each="checkbox : ${el.checkboxes()}"><input type="checkbox" th:name="${checkbox.name()}" th:value="${checkbox.value()}" th:id="${checkbox.id()}" class="form-check-input" th:required="${checkbox.required()}" th:checked="${checkbox.checked()}" th:classappend="${el.hasErrors() ? 'is-invalid' : ''}"/><label th:replace="~{fieldset/label :: label(${checkbox.id()}, ${checkbox.label()})}"></label></div></th:block>
<th:block th:fragment="inputcheckbox(el)" xmlns:th="http://www.thymeleaf.org"><th:block th:if="${el.label()}"><label th:replace="~{fieldset/label :: label(null, ${el.label()})}"></label></th:block><div class="form-check" th:each="checkbox : ${el.checkboxes()}"><input type="checkbox" th:name="${checkbox.name()}" th:value="${checkbox.value()}" th:id="${checkbox.id()}" class="form-check-input" th:required="${checkbox.required()}" th:disabled="${checkbox.disabled()}" th:checked="${checkbox.checked()}" th:classappend="${el.hasErrors() ? 'is-invalid' : ''}"/><label th:replace="~{fieldset/label :: label(${checkbox.id()}, ${checkbox.label()})}"></label></div></th:block>
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<input th:fragment="inputradio(el,radio)" xmlns:th="http://www.thymeleaf.org" type="radio" th:name="${el.name()}" th:value="${radio.value()}" th:id="${radio.id()}" class="form-check-input" th:required="${el.required()}" th:checked="${radio.checked()}"/>
<input th:fragment="inputradio(el,radio)" xmlns:th="http://www.thymeleaf.org" type="radio" th:name="${el.name()}" th:value="${radio.value()}" th:id="${radio.id()}" class="form-check-input" th:required="${el.required()}" th:disabled="${radio.disabled()}" th:checked="${radio.checked()}"/>
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,25 @@ void render(ViewsRenderer<Map<String, Object>, ?> viewsRenderer) throws IOExcept
.label(Message.of("Attributes", "foobar"))
.checkboxes(List.of(
Checkbox.builder().id("scales").name("scales").label(Message.of("Scales")).checked(true).build(),
Checkbox.builder().id("horns").name("horns").label(Message.of("Horns")).build()
Checkbox.builder().id("horns").name("horns").label(Message.of("Horns")).build(),
Checkbox.builder().id("devils").name("devils").disabled(true).label(Message.of("Devils")).build()
))
.build();
assertEquals("""
<label class="form-label">Attributes</label>\
<div class="form-check"><input type="checkbox" name="scales" value="" id="scales" class="form-check-input" checked="checked"/><label for="scales" class="form-label">Scales</label></div>\
<div class="form-check"><input type="checkbox" name="horns" value="" id="horns" class="form-check-input"/><label for="horns" class="form-label">Horns</label></div>""",
TestUtils.render("fieldset/inputcheckbox.html", viewsRenderer, Map.of("el", el))
<div class="form-check">\
<input type="checkbox" name="scales" value="" id="scales" class="form-check-input" checked="checked"/>\
<label for="scales" class="form-label">Scales</label>\
</div>\
<div class="form-check">\
<input type="checkbox" name="horns" value="" id="horns" class="form-check-input"/>\
<label for="horns" class="form-label">Horns</label>\
</div>\
<div class="form-check">\
<input type="checkbox" name="devils" value="" id="devils" class="form-check-input" disabled="disabled"/>\
<label for="devils" class="form-label">Devils</label>\
</div>""",
TestUtils.render("fieldset/inputcheckbox.html", viewsRenderer, Map.of("el", el)).trim()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,12 @@ void render(ViewsRenderer<Map<String, Object>, ?> viewsRenderer) throws IOExcept
Radio.builder()
.id("louie")
.value("louie")
.disabled(true)
.label(Message.of("Louie"))
.build()
))
.build();
String html = TestUtils.render("fieldset/inputradio.html", viewsRenderer, Map.of("el", el, "radio", huey));
String html = TestUtils.render("fieldset/inputradio.html", viewsRenderer, Map.of("el", el, "radio", huey)).trim();

assertTrue(
"<input type=\"radio\" name=\"drone\" value=\"huey\" id=\"huey\" class=\"form-check-input\" checked/>".equals(html)
Expand All @@ -69,9 +70,18 @@ void render(ViewsRenderer<Map<String, Object>, ?> viewsRenderer) throws IOExcept

html = TestUtils.render("fieldset/inputradios.html", viewsRenderer, Map.of("el", el));
assertEquals("""
<div class="form-check"><input type="radio" name="drone" value="huey" id="huey" class="form-check-input" checked="checked"/><label for="huey" class="form-label">Huey</label></div>\
<div class="form-check"><input type="radio" name="drone" value="dewey" id="dewey" class="form-check-input"/><label for="dewey" class="form-label">Dewey</label></div>\
<div class="form-check"><input type="radio" name="drone" value="louie" id="louie" class="form-check-input"/><label for="louie" class="form-label">Louie</label></div>""",
<div class="form-check">\
<input type="radio" name="drone" value="huey" id="huey" class="form-check-input" checked="checked"/>\
<label for="huey" class="form-label">Huey</label>\
</div>\
<div class="form-check">\
<input type="radio" name="drone" value="dewey" id="dewey" class="form-check-input"/>\
<label for="dewey" class="form-label">Dewey</label>\
</div>\
<div class="form-check">\
<input type="radio" name="drone" value="louie" id="louie" class="form-check-input" disabled="disabled"/>\
<label for="louie" class="form-label">Louie</label>\
</div>""",
html
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ void render() {
assertEquals("""
<div class="form-check"><input type="checkbox" name="scales" value="" id="scales" class="form-check-input" checked="checked"/><label for="scales" class="form-label">Scales</label></div>\
<div class="form-check"><input type="checkbox" name="horns" value="" id="horns" class="form-check-input"/><label for="horns" class="form-label">Horns</label></div>""",
renderer.render(el, Locale.ENGLISH)
renderer.render(el, Locale.ENGLISH).trim()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2023 original authors
* Copyright 2017-2024 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -24,27 +24,53 @@

/**
* A Checkbox Form Element.
* @see <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox">Input Checkbox</a>
* @param name Name of the form control. Submitted with the form as part of a name/value pair
* @param value A string representing the value of the checkbox.
* @param checked A boolean attribute indicating whether this checkbox is checked by default (when the page loads).
*
* @param name Name of the form control. Submitted with the form as part of a name/value pair
* @param value A string representing the value of the checkbox.
* @param checked A boolean attribute indicating whether this checkbox is checked by default (when the page loads).
* @param disabled A boolean attribute indicating whether this checkbox is disabled by default (when the page loads).
* @param required If true indicates that the user must specify a value for the input before the owning form can be submitted.
* @param id It defines an identifier (ID) which must be unique in the whole document
* @param label represents a caption for an item in a user interface
* @param id It defines an identifier (ID) which must be unique in the whole document
* @param label represents a caption for an item in a user interface
*
* @author Sergio del Amo
* @see <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox">Input Checkbox</a>
* @since 4.1.0
*/
@Experimental
@Introspected(builder = @Introspected.IntrospectionBuilder(builderClass = Checkbox.Builder.class))
public record Checkbox(@NonNull String name,
@NonNull String value,
boolean checked,
boolean disabled,
boolean required,
@Nullable String id,
@Nullable Message label) implements FormElement, GlobalAttributes {

/**
* Instantiates a Checkbox.
*
* @param name Name of the form control. Submitted with the form as part of a name/value pair
* @param value A string representing the value of the checkbox.
* @param checked A boolean attribute indicating whether this checkbox is checked by default (when the page loads).
* @param required If true indicates that the user must specify a value for the input before the owning form can be submitted.
* @param id It defines an identifier (ID) which must be unique in the whole document
* @param label represents a caption for an item in a user interface
* @deprecated Use {@link Checkbox(String, String, boolean, boolean, boolean, String, Message)} instead which includes disabled attribute.
*/
@Deprecated(forRemoval = true, since = "5.2.0")
public Checkbox(
@NonNull String name,
@NonNull String value,
boolean checked,
boolean required,
@Nullable String id,
@Nullable Message label
) {
this(name, value, checked, false, required, id, label);
}

/**
* @return A checkbox builder.
*/
@NonNull
Expand All @@ -60,12 +86,12 @@ public static final class Builder {
private String name;
private String id;
private boolean checked;
private boolean disabled;
private boolean required;
private String value;
private Message label;

/**
*
* @param name Name of the form control. Submitted with the form as part of a name/value pair
* @return The Checkbox Builder
*/
Expand All @@ -76,7 +102,6 @@ public Builder name(@NonNull String name) {
}

/**
*
* @param id It defines an identifier (ID) which must be unique in the whole document
* @return The Checkbox Builder
*/
Expand All @@ -87,7 +112,6 @@ public Builder id(@NonNull String id) {
}

/**
*
* @param value A string representing the value of the checkbox.
* @return the Builder
*/
Expand All @@ -98,7 +122,6 @@ public Builder value(@NonNull String value) {
}

/**
*
* @param label represents a caption for an item in a user interface
* @return The Checkbox Builder
*/
Expand All @@ -109,7 +132,6 @@ public Builder label(Message label) {
}

/**
*
* @param checked A boolean attribute indicating whether this checkbox is checked by default (when the page loads).
* @return The Checkbox Builder
*/
Expand All @@ -120,7 +142,16 @@ public Builder checked(boolean checked) {
}

/**
*
* @param disabled A boolean attribute indicating whether this checkbox is disabled by default (when the page loads).
* @return The Checkbox Builder
*/
@NonNull
public Builder disabled(boolean disabled) {
this.disabled = disabled;
return this;
}

/**
* @param required If true indicates that the user must specify a value for the input before the owning form can be submitted.
* @return The Checkbox Builder
*/
Expand All @@ -132,13 +163,15 @@ public Builder required(boolean required) {

/**
* Instantiates a Checkbox.
*
* @return A Checkbox
*/
@NonNull
public Checkbox build() {
return new Checkbox(name,
value,
checked,
disabled,
required,
id,
label);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,27 @@
public record Radio(@NonNull String value,
@Nullable String id,
@NonNull Message label,
@NonNull boolean checked) implements FormElement {
@NonNull boolean checked,
boolean disabled
) implements FormElement {

/**
* Instantiates a Radio.
* @param value the value of the input radio element
* @param id It defines an identifier (ID) which must be unique in the whole document
* @param label represents a caption for an item in a user interface
* @param checked whether the radio button is checked
* @deprecated Use {@link Radio(String, String, Message, boolean, boolean)} instead which includes disabled attribute.
*/
@Deprecated(since = "5.2.0", forRemoval = true)
public Radio(
@NonNull String value,
@Nullable String id,
@NonNull Message label,
@NonNull boolean checked
) {
this(value, id, label, checked, false);
}

/**
*
Expand Down Expand Up @@ -93,6 +113,7 @@ public static final class Builder {
private Message label;

private boolean checked;
private boolean disabled;

/**
*
Expand Down Expand Up @@ -137,13 +158,24 @@ public Builder checked(boolean checked) {
return this;
}

/**
*
* @param disabled whether the radio button is disabled
* @return The Builder
*/
@NonNull
public Builder disabled(boolean disabled) {
this.disabled = disabled;
return this;
}

/**
*
* @return creates the radio button
*/
@NonNull
public Radio build() {
return new Radio(value, id, label, checked);
return new Radio(value, id, label, checked, disabled);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import static org.junit.jupiter.api.Assertions.*;

class InputCheckboxFormElementTest {

@Test
void testTagAndType() {
InputCheckboxFormElement formElement = InputCheckboxFormElement.builder().build();
Expand All @@ -25,16 +26,25 @@ void isAnnotatedWithIntrospected() {

@Test
void builder() {
Checkbox interest = Checkbox.builder().name("interest").value("coding").checked(false).id("coding").label(Message.of("Coding", "interest.coding")).build();
Checkbox music = Checkbox.builder().name("interest").value("music").checked(false).id("music").label(Message.of("Coding", "interest.music")).build();
Checkbox coding = Checkbox.builder().name("interest").value("coding").checked(false).id("coding").label(Message.of("Coding", "interest.coding")).build();
Checkbox music = Checkbox.builder().name("interest").value("music").checked(true).id("music").label(Message.of("Music", "interest.music")).build();
Checkbox cookery = Checkbox.builder().name("interest").value("cookery").disabled(true).checked(false).id("cookery").label(Message.of("Cookery", "interest.cookery")).build();

assertFalse(coding.checked(), "Coding checkbox should not be checked");
assertTrue(music.checked(), "Music checkbox should be checked");
assertFalse(cookery.checked(), "Cookery checkbox should not be checked");

assertFalse(coding.disabled(), "Coding checkbox should not be disabled");
assertFalse(music.disabled(), "Music checkbox should not be disabled");
assertTrue(cookery.disabled(), "Cookery checkbox should be disabled");

InputCheckboxFormElement formElement = InputCheckboxFormElement.builder()
.checkboxes(List.of(interest, music))
.checkboxes(List.of(coding, music, cookery))
.build();
assertEquals(HtmlTag.INPUT, formElement.getTag());
assertEquals(InputType.CHECKBOX, formElement.getType());

List<Checkbox> checkboxes = Arrays.asList(interest, music);
List<Checkbox> checkboxes = Arrays.asList(coding, music, cookery);
assertFormElement(checkboxes, formElement);
BeanIntrospection<InputCheckboxFormElement> introspection = BeanIntrospection.getIntrospection(InputCheckboxFormElement.class);
BeanIntrospection.Builder<InputCheckboxFormElement> builder = introspection.builder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ void inputRadioFormElementHasaBuilderApi() {
.value(value)
.id(id)
.label(label)
.disabled(true)
.build();
List<Radio> buttons = Collections.singletonList(radio);
InputRadioFormElement input = InputRadioFormElement.builder().name(name).buttons(buttons).build();
assertEquals(name, input.name());
assertEquals(id, input.buttons().get(0).getId());
assertEquals(label, input.buttons().get(0).getLabel());
assertEquals(value, input.buttons().get(0).getValue());
assertTrue(input.buttons().get(0).disabled());

BeanIntrospection<InputRadioFormElement> introspection = BeanIntrospection.getIntrospection(InputRadioFormElement.class);
BeanIntrospection.Builder<InputRadioFormElement> builder = introspection.builder();
Expand Down

0 comments on commit a7d09d2

Please sign in to comment.