Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Update fragments #522

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 65 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,39 @@ Honeycrisp contains JavaScript logic that deselects the other checkboxes when "N
selected. To enable it, you'll need to add `noneOfTheAbove.init()` to your JavaScript that runs
after page load.

The `content` parameter provides flexibility for the checkboxes list in the checkbox set and might be a better option
for dynamic values (like household member names).

The `options` parameter provides some convenience for static checkbox sets. A collection of objects that
implement `InputOption` can be passed into `checkboxFieldset`.

```java
enum ProgramOption implements InputOption {
SNAP("programs.SNAP", "programs-desc.SNAP"),
CCAP("programs.CCAP", "programs-desc.CCAP"),
GRH("programs.GRH", "programs-desc.GRH"),
CASH("programs.CASH", "programs-desc.CASH"),
OTHER("programs.Other", null);

// Should be defined by their message properties
final String label;
final String helpText;

// ... constructor and getters
}
```

```html

<th:block
th:with="programOptions=${T(ProgramOption).values()}"
th:replace="~{fragments/inputs/checkboxFieldset ::
checkboxFieldset(inputName='programs',
label='Select the programs',
noneOfTheAboveLabel='None of the above',
options=${programOptions})}"/>
```

##### Checkbox

```html
Expand Down Expand Up @@ -1336,6 +1369,34 @@ The `radioFieldset` has an optional `radioHelpText` field which will appear unde
fieldset's legend. `radio`, too, has an optional `radioHelpText` field which will appear
under the label's description text.

This fragment also supports passing a collection of `options` instead of using `content`.
The `options` parameter provides some convenience for static radio sets. A collection of objects that
implement `InputOption` can be passed into `radioFieldset`.

```java
enum ColorOption implements InputOption {
BLUE("color.blue", "color-desc.blue"),
RED("color.red", "color-desc.red"),
YELLOW("color.yellow", null);

// Should be defined by their message properties
final String label;
final String helpText;

// ... constructor and getters
}
```

```html

<th:block
th:with="colorOptions=${T(ColorOption).values()}"
th:replace="~{fragments/inputs/radioFieldset ::
radioFieldset(inputName='favoriteColor',
label='What is your favorite color?',
options=${colorOptions})}"/>
```

For convenience, we have provided a `cfa:inputFieldsetWithRadio` live template which can be used to
quickly create groupings of radio inputs. Not that when using this template, you can copy the inner
radio option fragment as many times as you like to create the necessary number of radio options.
Expand Down Expand Up @@ -2160,7 +2221,7 @@ public class ApplicantDateOfBirthPreparer implements SubmissionFieldPreparer {

@Override
public Map<String, SubmissionField> prepareSubmissionFields(Submission submission,
PdfMap pdfMap) {
PdfMap pdfMap) {
Map<String, SubmissionField> submissionFields = new HashMap<>();

String month = submission.getInputData().get("applicantBirthMonth").toString();
Expand Down Expand Up @@ -2211,7 +2272,7 @@ public class DataBaseFieldPreparer implements SubmissionFieldPreparer {

@Override
public Map<String, SubmissionField> prepareSubmissionFields(Submission submission,
PdfMap pdfMap) {
PdfMap pdfMap) {
Map<String, SubmissionField> submissionFields = new HashMap<>();

ArrayList<Map<String, Object>> houseHoldSubflow = (ArrayList<Map<String, Object>>) submission.getInputData()
Expand Down Expand Up @@ -2422,15 +2483,15 @@ Below is an example of a sendEmail() call being made by an application using the
Please note that pdfs is a list of files to be passed as attachments with the email.

```java
MessageResponse response = mailgunEmailClient.sendEmail(
MessageResponse response=mailgunEmailClient.sendEmail(
emailSubject,
recipientEmail,
emailToCc,
emailToBcc,
emailBody,
pdfs,
requireTls
);
);
```

The `sendEmail()` method will send an email and return the `MessageResponse` object it receives from
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/formflow/library/inputs/InputOption.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package formflow.library.inputs;

public interface InputOption {
String getValue();

String getLabel();

String getHelpText();

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@
hasHelpText=${!#strings.isEmpty(fieldsetHelpText)},
hasLabel=${!#strings.isEmpty(label)},
hasAriaLabel=${!#strings.isEmpty(ariaLabel)},
hasOptions=${options != null},
hasContent=${!hasOptions},
hasNoneOfTheAboveOption=${!#strings.isEmpty(noneOfTheAboveLabel)},
hasError=${
errorMessages != null &&
errorMessages.get(inputName) != null &&
(#arrays.length(errorMessages.get(inputName)) > 0) }"
th:assert="
${!#strings.isEmpty(inputName)},
${hasLabel || hasAriaLabel},
${content != null}">
${hasContent || hasOptions}">
<div th:class="'form-group' + ${(hasError ? ' form-group--error' : '')}">
<fieldset th:attr="
aria-describedby=${hasHelpText ? inputName + '-help-text' : ''},
Expand All @@ -27,7 +30,22 @@
th:text="${fieldsetHelpText}"></p>
</legend>
<input type="hidden" th:id="${inputName} + 'Hidden'" th:name="${inputName} + '[]'" value="">
<th:block th:replace="${content}"/>
<th:block th:if="${hasContent}">
<th:block th:replace="${content}"/>
</th:block>
<th:block th:if="${hasOptions}">
<th:block th:each="option : ${options}">
<th:block th:replace="~{fragments/inputs/checkboxInSet ::
checkboxInSet(inputName=${inputName},
value=${option.getValue()},
label=${#strings.isEmpty(option.getLabel()) ? null : #messages.msg(option.getLabel())},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be interpolated beforehand?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I sort of think it should just based on how the other messages work - it seems like for Actions and Validations, we interpolate them before the template gets rendered.
I also think it's kind of nice that it isn't interpolated beforehand because the enums work pretty well here and it's not straightforward to use the messageSource through an enum

checkboxHelpText=${#strings.isEmpty(option.getHelpText()) ? null : #messages.msg(option.getHelpText())})}"/>
</th:block>
<th:block th:if="${hasNoneOfTheAboveOption}">
<th:block th:replace="~{fragments/inputs/checkboxInSet ::
checkboxInSet(inputName=${inputName}, value='NONE', label=${noneOfTheAboveLabel}, noneOfTheAbove=true)}"/>
</th:block>
</th:block>
<th:block
th:replace="~{fragments/inputError :: validationError(inputName=${inputName})}"></th:block>
</fieldset>
Expand Down
17 changes: 15 additions & 2 deletions src/main/resources/templates/fragments/inputs/radioFieldset.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
hasHelpText=${!#strings.isEmpty(fieldsetHelpText)},
hasLabel=${!#strings.isEmpty(label)},
hasAriaLabel=${!#strings.isEmpty(ariaLabel)},
hasOptions=${options != null},
hasContent=${!hasOptions},
hasError=${
errorMessages != null &&
errorMessages.get(inputName) != null &&
(#arrays.length(errorMessages.get(inputName)) > 0) }"
th:assert="
${!#strings.isEmpty(inputName)},
${hasLabel || hasAriaLabel},
${content != null}">
${hasContent || hasOptions}">
<div th:class="'form-group' + ${(hasError ? ' form-group--error' : '')}">
<fieldset th:attr="
aria-describedby=${hasHelpText ? inputName + '-help-text' : ''},
Expand All @@ -26,7 +28,18 @@
th:text="${fieldsetHelpText}"></p>
</legend>
<input type="hidden" th:id="${inputName} + 'Hidden'" th:name="${inputName}" value="">
<th:block th:replace="${content}"/>
<th:block th:if="${hasContent}">
<th:block th:replace="${content}"/>
</th:block>
<th:block th:if="${hasOptions}">
<th:block th:each="option : ${options}">
<th:block th:replace="~{fragments/inputs/radio ::
radio(inputName=${inputName},
value=${option.getValue()},
label=${#strings.isEmpty(option.getLabel()) ? null : #messages.msg(option.getLabel())},
radioHelpText=${#strings.isEmpty(option.getHelpText()) ? null : #messages.msg(option.getHelpText())})}"/>
</th:block>
</th:block>
</fieldset>
<th:block
th:replace="~{fragments/inputError :: validationError(inputName=${inputName})}"></th:block>
Expand Down
7 changes: 7 additions & 0 deletions src/test/java/formflow/library/framework/InputsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import formflow.library.address_validation.AddressValidationService;
import formflow.library.address_validation.ValidatedAddress;
import formflow.library.inputs.TestOption;
import formflow.library.utilities.AbstractMockMvcTest;
import formflow.library.utilities.FormScreen;
import java.util.HashMap;
Expand Down Expand Up @@ -39,8 +40,10 @@ void shouldPersistInputValuesWhenNavigatingBetweenScreens() throws Exception {
String numberInput = "123";
// First "" value is from hidden input that a screen would submit
List<String> checkboxSet = List.of("", "Checkbox-A", "Checkbox-B");
List<String> checkboxEnumSet = List.of("MANGO", "OTHER");
List<String> checkboxInput = List.of("", "checkbox-value");
String radioInput = "Radio B";
String radioInputEnum = TestOption.MANGO.getValue();
String selectInput = "Select B";
String moneyInput = "100";
String phoneInput = "(555) 555-1234";
Expand All @@ -57,9 +60,11 @@ void shouldPersistInputValuesWhenNavigatingBetweenScreens() throws Exception {
Map.entry("numberInput", List.of(numberInput)),
// CheckboxSet's need to have the [] in their name for POST actions
Map.entry("checkboxSet[]", checkboxSet),
Map.entry("checkboxEnumSet[]", checkboxEnumSet),
// Checkboxes need to have the [] in their name for POST actions
Map.entry("checkboxInput[]", checkboxInput),
Map.entry("radioInput", List.of(radioInput)),
Map.entry("radioInputEnum", List.of(radioInputEnum)),
Map.entry("selectInput", List.of(selectInput)),
Map.entry("moneyInput", List.of(moneyInput)),
Map.entry("phoneInput", List.of(phoneInput)),
Expand All @@ -81,8 +86,10 @@ void shouldPersistInputValuesWhenNavigatingBetweenScreens() throws Exception {
assertThat(inputsScreen.getInputValue("dateYear")).isEqualTo(dateYear);
assertThat(inputsScreen.getInputValue("numberInput")).isEqualTo(numberInput);
assertThat(inputsScreen.getCheckboxSetValues("checkboxSet")).isEqualTo(removedHiddenCheckboxSet);
assertThat(inputsScreen.getCheckboxSetValues("checkboxEnumSet")).isEqualTo(checkboxEnumSet);
assertThat(inputsScreen.getCheckboxSetValues("checkboxInput")).isEqualTo(removedHiddenCheckboxInput);
assertThat(inputsScreen.getRadioValue("radioInput")).isEqualTo(radioInput);
assertThat(inputsScreen.getRadioValue("radioInputEnum")).isEqualTo(radioInputEnum);
assertThat(inputsScreen.getSelectValue("selectInput")).isEqualTo(selectInput);
assertThat(inputsScreen.getInputValue("moneyInput")).isEqualTo(moneyInput);
assertThat(inputsScreen.getInputValue("phoneInput")).isEqualTo(phoneInput);
Expand Down
2 changes: 2 additions & 0 deletions src/test/java/formflow/library/inputs/TestFlow.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ public class TestFlow extends FlowInputs {

String numberInput;
ArrayList<String> checkboxSet;
ArrayList<String> checkboxEnumSet;
ArrayList<String> checkboxInput;
String radioInput;
String radioInputEnum;
String selectInput;
String moneyInput;
String phoneInput;
Expand Down
30 changes: 30 additions & 0 deletions src/test/java/formflow/library/inputs/TestOption.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package formflow.library.inputs;

public enum TestOption implements InputOption {
MANGO("input-options.fruit.mango", "input-options.fruit.mango-desc"),
PAPAYA("input-options.fruit.papaya", "input-options.fruit.papaya-desc"),
GUAVA("input-options.fruit.guava", null),
OTHER("general.other", null);

public String getLabel() {
return label;
}

private final String label;

public String getHelpText() {
return helpText;
}

private final String helpText;

TestOption(String label, String helpText) {
this.label = label;
this.helpText = helpText;
}

@Override
public String getValue() {
return this.name();
}
}
14 changes: 14 additions & 0 deletions src/test/resources/templates/testFlow/inputs.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@
th:replace="~{fragments/inputs/checkboxInSet :: checkboxInSet(inputName='checkboxSet', value='Checkbox-B', label='Checkbox B')}"/>
</th:block>
</th:block>
<th:block th:with="checkboxOptions=${T(formflow.library.inputs.TestOption).values()}">
<th:block th:replace="~{fragments/inputs/checkboxFieldset ::
checkboxFieldset(inputName='checkboxEnumSet',
label='checkboxEnumSet',
options=${checkboxOptions})}">
</th:block>
</th:block>
<th:block
th:replace="~{fragments/inputs/checkbox :: checkbox(inputName='checkboxInput', value='checkbox-value', label='Single checkbox')}"/>
<th:block th:replace="~{fragments/inputs/radioFieldset ::
Expand All @@ -53,6 +60,13 @@
th:replace="~{fragments/inputs/radio :: radio(inputName='radioInput',value='Radio C', label='Radio C', radioHelpText='Radio C')}"/>
</th:block>
</th:block>
<th:block th:with="radioOptions=${T(formflow.library.inputs.TestOption).values()}">
<th:block th:replace="~{fragments/inputs/radioFieldset ::
radioFieldset(inputName='radioInputEnum',
label='radioInputEnum',
options=${radioOptions})}">
</th:block>
</th:block>
<th:block
th:replace="~{fragments/inputs/select ::
select(inputName='selectInput',
Expand Down