Skip to content

Commit 9b03fef

Browse files
authored
Merge pull request #202 from communitiesuk/FLS-1423_multi-input-row-limit
FLS-1423 - Multi-input field row limit
2 parents 04e48c1 + 3c81184 commit 9b03fef

File tree

8 files changed

+140
-34
lines changed

8 files changed

+140
-34
lines changed

designer/client/components/multiinput-field-edit/MultiInputFieldEdit.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ enum MultiInputFieldAction {
4242
EDIT_OPTIONS_HIDE_ROW_TITLE = "EDIT_OPTIONS_HIDE_ROW_TITLE",
4343
EDIT_OPTIONS_SHOW_TABLE_TITLE = "EDIT_OPTIONS_SHOW_TABLE_TITLE",
4444
EDIT_OPTIONS_SHOW_TABLE_ITEM_NAME = "EDIT_OPTIONS_SHOW_TABLE_ITEM_NAME",
45+
EDIT_MAX_MULTI_INPUT_FIELD_ROWS = "EDIT_MAX_MULTI_INPUT_FIELD_ROWS",
4546
}
4647

4748
export const MultiInputFieldEdit: any = ({context = AdapterComponentContext}) => {
@@ -92,6 +93,9 @@ export const MultiInputFieldEdit: any = ({context = AdapterComponentContext}) =>
9293
const [tableTitles, setTableTitles] = useState<string[]>(options.columnTitles ? options.columnTitles : []);
9394

9495
const [showEditor, setShowEditor] = useState(false);
96+
97+
const [maxRows, setMaxRows] = useState<string>(selectedComponent.options?.maxMultiInputFieldRows?.toString() || "");
98+
9599
const subComponentRef = useRef(null);
96100

97101
const toggleShowEditor = () => {
@@ -622,6 +626,14 @@ export const MultiInputFieldEdit: any = ({context = AdapterComponentContext}) =>
622626
}
623627
})
624628
}
629+
if (e.type === MultiInputFieldAction.EDIT_MAX_MULTI_INPUT_FIELD_ROWS) {
630+
setMaxRows(e.payload);
631+
const numValue = parseInt(e.payload);
632+
if (!selectedComponent.options) {
633+
selectedComponent.options = {};
634+
}
635+
selectedComponent.options.maxMultiInputFieldRows = isNaN(numValue) || numValue <= 0 ? undefined : numValue;
636+
}
625637
}
626638

627639
const renderSelectedSubComponentConfigEdit = () => {
@@ -932,6 +944,26 @@ export const MultiInputFieldEdit: any = ({context = AdapterComponentContext}) =>
932944
children: ["Table Item Name"]
933945
}}/>
934946
</div>
947+
948+
<div className="govuk-form-group" data-test-id="max-multi-input-field-rows-wrapper">
949+
<Input id="max-multi-input-field-rows" name="maxMultiInputFieldRows"
950+
onChange={(e) =>
951+
handleData({
952+
type: MultiInputFieldAction.EDIT_MAX_MULTI_INPUT_FIELD_ROWS,
953+
payload: e.target.value,
954+
})
955+
}
956+
value={maxRows}
957+
type="number"
958+
label={{
959+
className: "govuk-label--s",
960+
children: ["Maximum number of rows"]
961+
}}
962+
hint={{
963+
children: ["Set the maximum number of rows users can add. Leave empty for unlimited rows."]
964+
}}/>
965+
</div>
966+
935967
<div className="govuk-label govuk-label--s">----- Sub Components list -----</div>
936968
<div className="govuk-form-group">
937969
<label className="govuk-label govuk-label--s" htmlFor="select-field-types">

e2e-test/cypress/e2e/designer/multiInputFieldEdit.feature

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,22 @@ Feature: Multi Input Field Edit
140140
| YesNoField | This is a yes no field | |
141141
| MonthYearField | This is a month field | {"items": [{"name": "Month"},{"name": "Year"}]} |
142142
| MultilineTextField | This is a multiline field | |
143+
144+
Scenario: Test multi input field with maximum rows configuration
145+
And I continue create a component
146+
| page | component | title |
147+
| First page | Multi Input Field | Which eggs do you like? |
148+
And I add table title name "MaxRowsTest"
149+
And I configure maximum rows as "2"
150+
And I add child components
151+
| component | title | options | additional | listItem | name |
152+
| TextField | Item name | {} | false | | itemname |
153+
And I save component
154+
And I preview the page "First page" without href
155+
When I enter "First item" for "Item name"
156+
And I click "Save and add another"
157+
When I enter "Second item" for "Item name"
158+
And I click "Save"
159+
Then I should not see the "Save and add another" button
160+
And I should not see the "Save" button
161+
And I should see the "Save and continue" button
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { When, Then } from "@badeball/cypress-cucumber-preprocessor";
2+
3+
When("I configure maximum rows as {string}", (maxRows) => {
4+
cy.get("#max-multi-input-field-rows").type(maxRows);
5+
});
6+
7+
When("I click {string}", (buttonText) => {
8+
cy.findByRole("button", { name: buttonText }).click();
9+
});
10+
11+
Then("I should not see the {string} button", (buttonText) => {
12+
cy.findByRole("button", { name: buttonText }).should('not.exist');
13+
});
14+
15+
Then("I should see the {string} button", (buttonText) => {
16+
cy.findByRole("button", { name: buttonText }).should('exist');
17+
});

fsd_config/form_jsons/pfn_rp/pfn-rp-payment-profile-and-spend-forecast.json

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@
102102
"Capacity funding (2033-34)",
103103
"Capacity funding (2034-35)",
104104
"Capacity funding (2035-36)"
105-
]
105+
],
106+
"maxMultiInputFieldRows": 1
106107
},
107108
"type": "MultiInputField",
108109
"title": "Tell us your indicative spend forecast for capacity funding throughout the programme.",
@@ -248,8 +249,7 @@
248249
"customText": {
249250
"samePageTitle": "Capacity funding spend forecast",
250251
"samePageTableItemName": ""
251-
},
252-
"maxMultiInputFieldRows": 1
252+
}
253253
},
254254
"controller": "RepeatingFieldPageController"
255255
},
@@ -278,7 +278,8 @@
278278
"Programme delivery funding - capital (2033-34)",
279279
"Programme delivery funding - capital (2034-35)",
280280
"Programme delivery funding - capital (2035-36)"
281-
]
281+
],
282+
"maxMultiInputFieldRows": 1
282283
},
283284
"type": "MultiInputField",
284285
"title": "Tell us your indicative spend forecast for programme delivery funding (capital) throughout the programme.",
@@ -403,8 +404,7 @@
403404
"customText": {
404405
"samePageTitle": "Programme delivery funding (capital) spend forecast",
405406
"samePageTableItemName": ""
406-
},
407-
"maxMultiInputFieldRows": 1
407+
}
408408
},
409409
"controller": "RepeatingFieldPageController"
410410
},
@@ -433,7 +433,8 @@
433433
"Programme delivery funding - revenue (2033-34)",
434434
"Programme delivery funding - revenue (2034-35)",
435435
"Programme delivery funding - revenue (2035-36)"
436-
]
436+
],
437+
"maxMultiInputFieldRows": 1
437438
},
438439
"type": "MultiInputField",
439440
"title": "Tell us your indicative spend forecast for programme delivery funding (revenue) throughout the programme.",
@@ -557,8 +558,7 @@
557558
"customText": {
558559
"samePageTitle": "Programme delivery funding (revenue) spend forecast",
559560
"samePageTableItemName": ""
560-
},
561-
"maxMultiInputFieldRows": 1
561+
}
562562
},
563563
"controller": "RepeatingFieldPageController"
564564
},
@@ -605,7 +605,8 @@
605605
"Safety and security (2026-27)",
606606
"Transport (2026-27)",
607607
"Work, productivity and skills (2026-27)"
608-
]
608+
],
609+
"maxMultiInputFieldRows": 1
609610
},
610611
"type": "MultiInputField",
611612
"title": "Tell us your indicative spend forecast for pre-approved interventions in year 1.",
@@ -709,8 +710,7 @@
709710
"customText": {
710711
"samePageTitle": "Pre-approved interventions spend forecast (Year 1)",
711712
"samePageTableItemName": ""
712-
},
713-
"maxMultiInputFieldRows": 1
713+
}
714714
},
715715
"controller": "RepeatingFieldPageController"
716716
},
@@ -737,7 +737,8 @@
737737
"Safety and security (2027-28)",
738738
"Transport (2027-28)",
739739
"Work, productivity and skills (2027-28)"
740-
]
740+
],
741+
"maxMultiInputFieldRows": 1
741742
},
742743
"type": "MultiInputField",
743744
"title": "Tell us your indicative spend forecast for pre-approved interventions in year 2.",
@@ -841,8 +842,7 @@
841842
"customText": {
842843
"samePageTitle": "Pre-approved interventions spend forecast (Year 2)",
843844
"samePageTableItemName": ""
844-
},
845-
"maxMultiInputFieldRows": 1
845+
}
846846
},
847847
"controller": "RepeatingFieldPageController"
848848
},
@@ -869,7 +869,8 @@
869869
"Safety and security (2028-29)",
870870
"Transport (2028-29)",
871871
"Work, productivity and skills (2028-29)"
872-
]
872+
],
873+
"maxMultiInputFieldRows": 1
873874
},
874875
"type": "MultiInputField",
875876
"title": "Tell us your indicative spend forecast for pre-approved interventions in year 3.",
@@ -973,8 +974,7 @@
973974
"customText": {
974975
"samePageTitle": "Pre-approved interventions spend forecast (Year 3)",
975976
"samePageTableItemName": ""
976-
},
977-
"maxMultiInputFieldRows": 1
977+
}
978978
},
979979
"controller": "RepeatingFieldPageController"
980980
},
@@ -1001,7 +1001,8 @@
10011001
"Safety and security (2029-30)",
10021002
"Transport (2029-30)",
10031003
"Work, productivity and skills (2029-30)"
1004-
]
1004+
],
1005+
"maxMultiInputFieldRows": 1
10051006
},
10061007
"type": "MultiInputField",
10071008
"title": "Tell us your indicative spend forecast for pre-approved interventions in year 4.",
@@ -1105,8 +1106,7 @@
11051106
"customText": {
11061107
"samePageTitle": "Pre-approved interventions spend forecast (Year 4)",
11071108
"samePageTableItemName": ""
1108-
},
1109-
"maxMultiInputFieldRows": 1
1109+
}
11101110
},
11111111
"controller": "RepeatingFieldPageController"
11121112
},
@@ -1129,7 +1129,8 @@
11291129
"Off-menu interventions (2027-28)",
11301130
"Off-menu interventions (2028-29)",
11311131
"Off-menu interventions (2029-30)"
1132-
]
1132+
],
1133+
"maxMultiInputFieldRows": 1
11331134
},
11341135
"type": "MultiInputField",
11351136
"title": "Tell us your indicative spend forecast for any off-menu interventions in the first investment period.",
@@ -1193,8 +1194,7 @@
11931194
"customText": {
11941195
"samePageTitle": "Off-menu interventions spend forecast",
11951196
"samePageTableItemName": ""
1196-
},
1197-
"maxMultiInputFieldRows": 1
1197+
}
11981198
},
11991199
"controller": "RepeatingFieldPageController"
12001200
},
@@ -1217,7 +1217,8 @@
12171217
"Management costs (2027-28)",
12181218
"Management costs (2028-29)",
12191219
"Management costs (2029-30)"
1220-
]
1220+
],
1221+
"maxMultiInputFieldRows": 1
12211222
},
12221223
"type": "MultiInputField",
12231224
"title": "Tell us your indicative spend forecast for management costs in the first investment period.",
@@ -1281,8 +1282,7 @@
12811282
"customText": {
12821283
"samePageTitle": "Management costs spend forecast",
12831284
"samePageTableItemName": ""
1284-
},
1285-
"maxMultiInputFieldRows": 1
1285+
}
12861286
},
12871287
"controller": "RepeatingFieldPageController"
12881288
},
@@ -1305,7 +1305,8 @@
13051305
"Unknown uses of funding (2027-28)",
13061306
"Unknown uses of funding (2028-29)",
13071307
"Unknown uses of funding (2029-30)"
1308-
]
1308+
],
1309+
"maxMultiInputFieldRows": 1
13091310
},
13101311
"type": "MultiInputField",
13111312
"title": "Tell us about any unknown uses of funding in the first investment period.",
@@ -1370,8 +1371,7 @@
13701371
"customText": {
13711372
"samePageTitle": "Unknown uses of funding forecast",
13721373
"samePageTableItemName": ""
1373-
},
1374-
"maxMultiInputFieldRows": 1
1374+
}
13751375
},
13761376
"controller": "RepeatingFieldPageController"
13771377
}
@@ -1401,4 +1401,4 @@
14011401
"url": ""
14021402
},
14031403
"phaseBanner": {}
1404-
}
1404+
}

runner/locales/cy.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
"signOut": "Allgofnodi",
33
"markAsComplete": "A ydych chi eisiau marcio bod yr adran hon wedi'i chwblhau?",
44
"removeText": "Dileu",
5+
"saveText": "Cadw",
6+
"saveAndAddAnotherText": "Cadw ac ychwanegu un arall",
57
"validation": {
68
"required": "{#label} yn ofynnol",
79
"max": "{#label} rhaid iddo fod yn {#limit} nod neu lai",

runner/locales/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
"signOut": "Sign out",
33
"markAsComplete": "Have you completed this section?",
44
"removeText": "Remove",
5+
"saveText": "Save",
6+
"saveAndAddAnotherText": "Save and add another",
57
"validation": {
68
"required": "{#label} is required",
79
"max": "{#label} must be {#limit} characters or less",

runner/src/server/plugins/engine/page-controllers/RepeatingFieldPageController.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,11 @@ export class RepeatingFieldPageController extends PageController {
9797
this.tableEmptyMessageTitle = this.options.customText && this.options.customText.samePageTableItemName ? `You have not added any ${this.options.customText.samePageTableItemName}s yet` : "You have not added any costs yet";
9898
//@ts-ignore
9999
this.tableEmptyMessageHint = this.options.customText && this.options.customText.samePageTableItemName ? `Each ${this.options.customText.samePageTableItemName} you add will be shown here` : "Each cost you add will be shown here";
100-
this.saveText = "Save and add another";
101100
if (model?.def?.metadata?.isWelsh) {
102101
//@ts-ignore
103102
this.tableEmptyMessageTitle = this.options.customText && this.options.customText.samePageTableItemName ? `Nid ydych chi wedi ychwanegu unrhyw ${this.options.customText.samePageTableItemName} eto` : "Nid ydych chi wedi ychwanegu unrhyw gostau eto";
104103
//@ts-ignore
105104
this.tableEmptyMessageHint = this.options.customText && this.options.customText.samePageTableItemName ? `Bydd pob ${this.options.customText.samePageTableItemName} yr ychwanegwch yn cael ei dangos yma` : "Bydd pob cost yr ychwanegwch yn cael ei dangos yma";
106-
this.saveText = "Cadw ac ychwanegu un arall";
107105
}
108106
}
109107

@@ -126,6 +124,26 @@ export class RepeatingFieldPageController extends PageController {
126124
return parentSchema;
127125
}
128126

127+
async validateComponentFunctions(request, viewModel) {
128+
let errors = await super.validateComponentFunctions(request, viewModel);
129+
const maxRows = this.inputComponent.options?.maxMultiInputFieldRows;
130+
if (maxRows) {
131+
const {adapterCacheService} = request.services([]);
132+
const state = await adapterCacheService.getState(request);
133+
const currentRows = this.getPartialState(state) || [];
134+
if (currentRows.length >= maxRows) {
135+
const rowText = maxRows === 1 ? "row" : "rows";
136+
errors.push({
137+
path: this.inputComponent.name,
138+
name: this.inputComponent.name,
139+
text: `You cannot add more than ${maxRows} ${rowText}`,
140+
});
141+
}
142+
}
143+
144+
return errors;
145+
}
146+
129147
makeGetRouteHandler() {
130148
return async (request: HapiRequest, h: HapiResponseToolkit) => {
131149
const {query} = request;
@@ -219,6 +237,17 @@ export class RepeatingFieldPageController extends PageController {
219237
headings: this.inputComponent.options.columnTitles,
220238
rows,
221239
};
240+
241+
response.source.context.maxMultiInputFieldRows = this.inputComponent.options?.maxMultiInputFieldRows;
242+
243+
const maxRows = this.inputComponent.options?.maxMultiInputFieldRows;
244+
const currentRowCount = rows ? rows.length : 0;
245+
246+
if (maxRows && (currentRowCount >= maxRows - 1)) {
247+
this.saveText = request.i18n.__("saveText");
248+
} else {
249+
this.saveText = request.i18n.__("saveAndAddAnotherText");
250+
}
222251
}
223252
}
224253

runner/src/server/plugins/engine/views/partials/form.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66

77
{% if page.isRepeatingFieldPageController and details %}
88
{# maxMultiInputFieldRows allow the users to control how many data rows that can be added in MultiInputField component in a page #}
9-
{% if not page.options.maxMultiInputFieldRows or not details.rows.length or page.options.maxMultiInputFieldRows > details.rows.length %}
9+
{% set multiInputLimitReached = false %}
10+
{% if maxMultiInputFieldRows and details.rows.length >= maxMultiInputFieldRows %}
11+
{% set multiInputLimitReached = true %}
12+
{% endif %}
13+
14+
{% if not multiInputLimitReached %}
1015
{{ componentList(components) }}
1116
{{ govukButton({
1217
attributes: { id: "add-another" },

0 commit comments

Comments
 (0)