This is an automated email from the ASF dual-hosted git repository.
ljmotta pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-tools.git
The following commit(s) were added to refs/heads/main by this push:
new f331fb04f06 kie-issues#1828: Implement ListField in the
“form-code-generator-bootstrap4-theme” package (#2941)
f331fb04f06 is described below
commit f331fb04f0641354b1a83d9b0796be02ac3f8d82
Author: Luiz João Motta <[email protected]>
AuthorDate: Fri Feb 28 16:27:42 2025 -0300
kie-issues#1828: Implement ListField in the
“form-code-generator-bootstrap4-theme” package (#2941)
---
.rat-excludes | 12 +
.../src/api/types.ts | 5 +-
.../resources/staticCode/listHelperFunctions.txt | 192 +++++++++++++++
.../templates/checkbox.setModelData.template | 2 +-
.../src/resources/templates/checkbox.template | 4 +-
.../templates/checkboxGroup.setModelData.template | 2 +-
.../src/resources/templates/checkboxGroup.template | 10 +-
.../resources/templates/date.setModelData.template | 2 +-
.../src/resources/templates/date.template | 16 +-
.../src/resources/templates/form.template | 24 +-
.../src/resources/templates/formGroup.template | 2 +-
.../templates/input.setModelData.template | 2 +-
.../src/resources/templates/input.template | 14 +-
.../templates/listField.globalFunctions.template | 12 +
.../templates/listField.setModelData.template | 6 +
.../src/resources/templates/listField.template | 15 ++
.../templates/listField.writeModelData.template | 5 +
.../templates/nestField.globalFunctions.template | 5 +
.../src/resources/templates/nestField.template | 2 +-
.../src/resources/templates/number.template | 18 +-
.../templates/radioGroup.setModelData.template | 2 +-
.../src/resources/templates/radioGroup.template | 10 +-
.../templates/select.setModelData.template | 8 +-
.../src/resources/templates/select.template | 10 +-
.../src/uniforms/AutoField.tsx | 5 +-
.../src/uniforms/AutoForm.tsx | 7 +-
.../src/uniforms/BoolField.tsx | 9 +-
.../src/uniforms/CheckBoxGroupField.tsx | 24 +-
.../src/uniforms/DateField.tsx | 9 +-
.../src/uniforms/{NestField.tsx => ListField.tsx} | 53 ++--
.../src/uniforms/NestField.tsx | 34 ++-
.../src/uniforms/NumField.tsx | 9 +-
.../src/uniforms/RadioField.tsx | 24 +-
.../src/uniforms/SelectField.tsx | 26 +-
.../src/uniforms/TextField.tsx | 9 +-
.../src/uniforms/UnsupportedField.tsx | 12 +-
.../src/uniforms/index.ts | 1 +
.../src/uniforms/renderForm.tsx | 4 +-
.../{NestedFieldInput.tsx => ListFieldInput.tsx} | 16 +-
.../src/uniforms/rendering/NestedFieldInput.tsx | 7 +-
.../src/uniforms/rendering/RenderingUtils.tsx | 29 ++-
.../templates/AbstractFormGroupTemplate.ts | 108 ++++++++
.../src/uniforms/templates/AutoFormTemplate.ts | 8 +-
.../src/uniforms/templates/BoolFieldTemplate.ts | 62 +++--
.../templates/CheckboxGroupFieldTemplate.ts | 70 +++---
.../src/uniforms/templates/DateFieldTemplate.ts | 4 +-
.../src/uniforms/templates/ListFieldTemplate.ts | 155 ++++++++++++
.../src/uniforms/templates/NestFieldTemplate.ts | 50 ++--
.../src/uniforms/templates/NumFieldTemplate.ts | 4 +-
.../uniforms/templates/RadioGroupFieldTemplate.ts | 76 +++---
.../src/uniforms/templates/SelectFieldTemplate.ts | 74 +++---
.../src/uniforms/templates/TextFieldTemplate.ts | 4 +-
.../src/uniforms/templates/UnsupportedTemplate.ts | 13 +-
.../src/uniforms/templates/templates.ts | 7 +-
.../src/uniforms/templates/types.ts | 97 --------
.../src/uniforms/templates/utils.ts | 19 +-
.../tests/AutoField.test.tsx | 9 +-
.../tests/AutoForm.test.tsx | 2 +
...nsupportedField.test.tsx => ListField.test.tsx} | 37 ++-
.../tests/UnsupportedField.test.tsx | 10 +-
.../tests/__snapshots__/AutoForm.test.tsx.snap | 273 ++++++++++++++++++++-
.../tests/__snapshots__/ListField.test.tsx.snap | 7 +
.../tests/__snapshots__/NestField.test.tsx.snap | 4 +-
.../__snapshots__/UnsupportedField.test.tsx.snap | 2 +-
64 files changed, 1245 insertions(+), 508 deletions(-)
diff --git a/.rat-excludes b/.rat-excludes
index 34610b5e47a..4e47f2f52e3 100644
--- a/.rat-excludes
+++ b/.rat-excludes
@@ -378,6 +378,8 @@ getCheckboxGroupValue.txt
getMultipleSelectValue.txt
#
packages/form-code-generator-bootstrap4-theme/src/resources/staticCode/getRadioGroupValue.txt
getRadioGroupValue.txt
+#
packages/form-code-generator-bootstrap4-theme/src/resources/staticCode/listHelperFunctions.txt
+listHelperFunctions.txt
#
packages/form-code-generator-bootstrap4-theme/src/resources/staticCode/setCheckboxGroupValue.txt
setCheckboxGroupValue.txt
#
packages/form-code-generator-bootstrap4-theme/src/resources/staticCode/setRadioGroupValue.txt
@@ -412,6 +414,16 @@ input.setModelData.template
input.template
#
packages/form-code-generator-bootstrap4-theme/src/resources/templates/input.writeModelData.template
input.writeModelData.template
+#
packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.globalFunctions.template
+listField.globalFunctions.template
+#
packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.setModelData.template
+listField.setModelData.template
+#
packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.template
+listField.template
+#
packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.writeModelData.template
+listField.writeModelData.template
+#
packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.globalFunctions.template
+nestField.globalFunctions.template
#
packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.setModelData.template
nestField.setModelData.template
#
packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.template
diff --git a/packages/form-code-generator-bootstrap4-theme/src/api/types.ts
b/packages/form-code-generator-bootstrap4-theme/src/api/types.ts
index e541e0109f0..5473446869b 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/api/types.ts
+++ b/packages/form-code-generator-bootstrap4-theme/src/api/types.ts
@@ -31,8 +31,9 @@ export interface FormElement<REFERENCE_TYPE> extends
CodeGenElement {
disabled?: boolean;
- setValueFromModelCode?: CodeFragment;
- writeValueToModelCode?: CodeFragment;
+ setValueFromModelCode: CodeFragment | undefined;
+ writeValueToModelCode: CodeFragment | undefined;
+ globalFunctions: CodeFragment | undefined;
}
export interface FormInput extends FormElement<InputReference> {}
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/staticCode/listHelperFunctions.txt
b/packages/form-code-generator-bootstrap4-theme/src/resources/staticCode/listHelperFunctions.txt
new file mode 100644
index 00000000000..bcda00c8bc2
--- /dev/null
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/staticCode/listHelperFunctions.txt
@@ -0,0 +1,192 @@
+// List Field Helper functions -- START --
+function delListItem(name, minCount, itemIndex, onDelListItem) {
+ const formData = getFormData();
+ const value = accessObjectPath(formData, name) ?? [];
+
+ if ((minCount ?? 0) <= value.length) {
+ document.getElementById(`item-${name}.${itemIndex}`).remove();
+ }
+
+ // Re-organize list
+ Array.from(document.getElementById(name).childNodes ?? [])
+ .filter((node) => node.nodeType === 1)
+ .forEach((element, index) => {
+ const delElement = element.querySelector(`[id='remove-item-${name}']`);
+ delElement.onclick = function () {
+ onDelListItem(name, `${index}`);
+ };
+ delElement.onkeydown = function () {
+ onDelListItem(name, `${index}`);
+ };
+
+ const inputOrSelect = element.querySelector("input, select");
+ inputOrSelect.id = `${name}.${index}`;
+ inputOrSelect.name = `${name}.${index}`;
+
+ element.id = `item-${name}.${index}`;
+ });
+}
+
+function addListItem(name, defaultValue, maxCount, childrenHtml, functionName)
{
+ const formData = getFormData();
+ let value = accessObjectPath(formData, name) ?? [];
+ const itemIndex = value.length;
+ if (maxCount !== undefined && maxCount > value.length) {
+ value = value.concat([defaultValue]);
+ } else {
+ value = value.concat([defaultValue]);
+ }
+ const listContainer = document.getElementById(name);
+ const newItem = document.createElement("div");
+ newItem.class = "row";
+ newItem.id = `item-${name}.${itemIndex}`;
+ newItem.innerHTML = `
+<div class="col-1">
+<span id="remove-item-${name}" name="Remove Item" class="badge badge-pill"
role="button" tabindex="0" onclick="onDelListItem${functionName}('${name}',
${itemIndex})" onkeydown="onDelListItem${functionName}('${name}',
${itemIndex})">
+ <i className="octicon octicon-dash"><span>-</span></i>
+</span>
+</div>
+<div class="col-11">
+${childrenHtml}
+</div>
+`;
+ listContainer.appendChild(newItem);
+ // Replace all "$" from `input` and `select` with the `itemIndex`
+ const newItemElement = document.getElementById(newItem.id);
+ [
+ ...newItemElement.querySelectorAll('input[id*="$"], select[id*="$"]'),
+ ].forEach((el) => {
+ if (el.id.split(".").pop() !== "$") {
+ el.id = `${name}.${itemIndex}.${el.id.split(".").pop()}`;
+ el.name = `${name}.${itemIndex}.${el.id.split(".").pop()}`;
+ } else {
+ el.id = `${name}.${itemIndex}`;
+ el.name = `${name}.${itemIndex}`;
+ }
+ });
+ // Get the root element of a nested list
+ [...newItemElement.querySelectorAll("div[role='list']")].forEach(
+ (nestedListContainer) => {
+ if (!nestedListContainer) {
+ return;
+ }
+ // Replace "$" from the root element if necessary
+ const nestedListPathWithIndex = splitLastOccurrence(
+ nestedListContainer.id,
+ "$"
+ )?.[1]?.replace("$", `${itemIndex}`);
+ nestedListContainer.id = nestedListPathWithIndex
+ ? `${name}.${nestedListPathWithIndex}`
+ : name;
+ // Replace "$" from all nested Add Item element
+ [...newItemElement.querySelectorAll("[id^=add-item]")].forEach(
+ (addItem) => {
+ const addItemPathWithIndex = splitLastOccurrence(
+ addItem.id,
+ "$"
+ )?.[1]?.replace("$", `${itemIndex}`);
+ addItem.id = addItemPathWithIndex
+ ? `add-item-${name}.${addItemPathWithIndex}`
+ : `add-item-${name}`;
+ const addItemOnClick = addItem.getAttribute("onclick");
+ if (addItemOnClick) {
+ addItem.setAttribute(
+ "onclick",
+ getFunctionName(addItemOnClick, itemIndex)
+ );
+ }
+ const addItemOnKeydown = addItem.getAttribute("onkeydown");
+ if (addItemOnKeydown) {
+ addItem.setAttribute(
+ "onkeydown",
+ getFunctionName(addItemOnKeydown, itemIndex)
+ );
+ }
+ }
+ );
+ // Replace "$" from the Remove Item element
+ [...newItemElement.querySelectorAll("[id^=remove-item]")].forEach(
+ (removeItem) => {
+ const removeItemPathWithIndex = splitLastOccurrence(
+ removeItem.id,
+ "$"
+ )?.[1]?.replace("$", `${itemIndex}`);
+ removeItem.id = removeItemPathWithIndex
+ ? `remove-item-${name}.${removeItemPathWithIndex}`
+ : `remove-item-${name}`;
+ const removeItemOnClick = removeItem.getAttribute("onclick");
+ if (removeItemOnClick) {
+ removeItem.setAttribute(
+ "onclick",
+ getFunctionName(removeItemOnClick, itemIndex)
+ );
+ }
+ const removeItemOnKeydown = removeItem.getAttribute("onkeydown");
+ if (removeItemOnKeydown) {
+ removeItem.setAttribute(
+ "onkeydown",
+ getFunctionName(removeItemOnKeydown, itemIndex)
+ );
+ }
+ }
+ );
+ }
+ );
+}
+function getFunctionName(functionCall, itemIndex) {
+ const [call, argument, end] = functionCall.split("'");
+ const [argumentName, argumentNameToReplace] = splitLastOccurrence(
+ argument,
+ "$"
+ );
+ return `${call}'${argumentName}${
+ argumentNameToReplace?.replace("$", `${itemIndex}`) ?? ""
+ }'${end}`;
+}
+function splitLastOccurrence(str, char) {
+ const lastIndex = str.lastIndexOf(char);
+ if (lastIndex === -1) {
+ return [str];
+ }
+ return [str.substring(0, lastIndex), str.substring(lastIndex)];
+}
+function getListValue(itemListElement) {
+ function setValue(obj, path, value) {
+ const keys = path.split(".");
+ let current = obj;
+
+ for (let i = 0; i < keys.length; i++) {
+ const key = keys[i];
+ const isArray = !isNaN(keys[i + 1]);
+
+ if (i === keys.length - 1) {
+ current[key] = value;
+ } else {
+ if (!current[key]) {
+ current[key] = isArray ? [] : {};
+ }
+ if (isArray && !Array.isArray(current[key])) {
+ current[key] = [];
+ }
+ current = current[key];
+ }
+ }
+ }
+
+ function traverse(element, result, path = "") {
+ element.querySelectorAll("input, select").forEach((input) => {
+ if (input.id) {
+ setValue(result, input.id, input.value || "");
+ }
+ });
+ }
+
+ const result = {};
+ traverse(itemListElement, result);
+ const [_, path] = itemListElement.id.split("item-");
+ return accessObjectPath(result, path);
+}
+function accessObjectPath(obj, objPath) {
+ return objPath.split(".")?.reduce((acc, pathPiece) => acc?.[pathPiece], obj);
+}
+// List Field Helper functions -- END --
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkbox.setModelData.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkbox.setModelData.template
index 5c03f19358e..7eba58bbad8 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkbox.setModelData.template
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkbox.setModelData.template
@@ -1 +1 @@
-document.getElementById("<%=id%>").checked = data?.<%=path%>;
\ No newline at end of file
+<% if (isListItem) { %>document.getElementById(`<%=id%>`).checked = value<%=
valuePath %> ?? "";<% } else { %>document.getElementById("<%=id%>").checked =
data?.<%=path%>;<% } %>
\ No newline at end of file
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkbox.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkbox.template
index cfd3b1670d7..561bed85ad9 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkbox.template
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkbox.template
@@ -1,4 +1,4 @@
<div class="form-check">
- <input type="checkbox" id="<%=props.id%>" name="<%=props.name%>"
class="form-check-input"<%=props.disabled ? " disabled" : ""%><%=props.checked
? " checked" : ""%>/>
- <label class="form-check-label"
for="<%=props.id%>"><%=props.label%></label>
+ <input type="checkbox" id="<%=id%>" name="<%=name%>"
class="form-check-input"<%=disabled ? " disabled" : ""%><%=checked ? " checked"
: ""%>/>
+ <label class="form-check-label" for="<%=id%>"><%=label%></label>
</div>
\ No newline at end of file
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkboxGroup.setModelData.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkboxGroup.setModelData.template
index 8da59d28557..11f164b0205 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkboxGroup.setModelData.template
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkboxGroup.setModelData.template
@@ -1 +1 @@
-setCheckboxGroupValues("<%=name%>", data?.<%=path%>);
\ No newline at end of file
+<% if (isListItem) { %>setCheckboxGroupValues(`<%=name%>`, value<%= valuePath
%> ?? "");<% } else { %>setCheckboxGroupValues("<%=name%>", data?.<%=path%>);<%
} %>
\ No newline at end of file
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkboxGroup.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkboxGroup.template
index 92a7aaf19d9..a0993c88ed0 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkboxGroup.template
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/checkboxGroup.template
@@ -1,11 +1,11 @@
-<% _.each(props.options, function(option) { %>
+<% _.each(options, function(option) { %>
<div class="form-check">
<input type="checkbox" class="form-check-input"
- id="<%=props.id + '__' + option.value%>"
- name="<%=props.name%>"
+ id="<%=id + '__' + option.value%>"
+ name="<%=name%>"
value="<%=option.value%>"
- <%=props.disabled ? "disabled" : ""%>
+ <%=disabled ? "disabled" : ""%>
<%=option.checked ? "checked" : ""%>/>
- <label class="form-check-label" for="<%=props.id + '__' +
option.value%>"><%=option.label%></label>
+ <label class="form-check-label" for="<%=id + '__' +
option.value%>"><%=option.label%></label>
</div>
<% }); %>
\ No newline at end of file
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/date.setModelData.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/date.setModelData.template
index 153c02c53ac..725de6e72a7 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/date.setModelData.template
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/date.setModelData.template
@@ -1 +1 @@
-document.getElementById("<%=id%>").value = data?.<%=path%> ? new
Date(data?.<%=path%>).toISOString().slice(0, 16) : "";
\ No newline at end of file
+<% if (isListItem) { %>document.getElementById(`<%=id%>`).value = value<%=
valuePath %> ? new Date(value<%= valuePath %>).toISOString().slice(0, 16) :
"";<% } else { %>document.getElementById("<%=id%>").value = data?.<%=path%> ?
new Date(data?.<%=path%>).toISOString().slice(0, 16) : "";<% } %>
\ No newline at end of file
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/date.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/date.template
index 066f4956797..39cbe1c3794 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/date.template
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/date.template
@@ -1,11 +1,11 @@
<input
type="datetime-local"
- id="<%=props.id%>"
- name="<%=props.name%>"
+ id="<%=id%>"
+ name="<%=name%>"
class="form-control"
- <%=props.autoComplete==='off' ? ' autoComplete="off"' : ''%>
- <%=props.disabled ? "disabled" : ""%>
- <%=props.placeholder ? `placeholder="${props.placeholder}"` : ''%>
- <%=props.min ? `min="${props.min}"` : ''%>
- <%=props.max ? `max="${props.max}"` : ''%>
- <%=props.value ? `value="${props.value}"` : ''%>/>
\ No newline at end of file
+ <%=autoComplete==='off' ? ' autoComplete="off"' : ''%>
+ <%=disabled ? "disabled" : ""%>
+ <%=placeholder ? `placeholder="${placeholder}"` : ''%>
+ <%=min ? `min="${min}"` : ''%>
+ <%=max ? `max="${max}"` : ''%>
+ <%=value ? `value="${value}"` : ''%>/>
\ No newline at end of file
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/form.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/form.template
index 42d1b4af13f..71327a87fd2 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/form.template
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/form.template
@@ -2,8 +2,30 @@
<% _.each(props.children, function(child) { %>
<%=child.html%>
<% }); %>
-
+<% if (props.submit) { %>
+<input
+ class="btn btn-primary"
+ disabled={<%= props.disabled %>}
+ type="submit"
+/>
+<% } %>
<script>
+<% const addedGlobalFunctionsCode = []; %>
+<% _.each(props.children, function(child) { %>
+ <% if (child.globalFunctions && child.globalFunctions.requiredCode) { %>
+ <% _.each(child.globalFunctions.requiredCode, function(codeFragment) {
%>
+ <% if (!addedGlobalFunctionsCode.includes(codeFragment)) { %>
+ <% addedGlobalFunctionsCode.push(codeFragment); %>
+ <%= codeFragment %>
+ <% } %>
+ <% }); %>
+ <% } %>
+<% }); %>
+<% _.each(props.children, function(child) { %>
+ <% if (child.globalFunctions) { %>
+ <%= child.globalFunctions.code %>
+ <% } %>
+<% }); %>
/* Utility function that fills the form with the data received from
the kogito runtime */
function setFormData(data) {
if(!data) {
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/formGroup.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/formGroup.template
index 84a5b2a6a79..310e52c0168 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/formGroup.template
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/formGroup.template
@@ -1,4 +1,4 @@
<div class="form-group">
- <label for="<%=props.id%>"><%=props.label%></label>
+ <label for="<%= isListItem ? "item-"+id : id %>"><%= label %></label>
<%= input %>
</div>
\ No newline at end of file
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/input.setModelData.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/input.setModelData.template
index a116b601037..a9a3bbafd63 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/input.setModelData.template
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/input.setModelData.template
@@ -1 +1 @@
-document.getElementById("<%=id%>").value = data?.<%=path%> ?? "";
\ No newline at end of file
+<% if (isListItem) { %>document.getElementById(`<%=id%>`).value = value<%=
valuePath %> ?? "";<% } else { %>document.getElementById("<%=id%>").value =
data?.<%=path%> ?? "";<% } %>
\ No newline at end of file
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/input.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/input.template
index e235979225b..c4699d9a27f 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/input.template
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/input.template
@@ -1,8 +1,8 @@
-<input type="<%=props.type%>"
- id="<%=props.id%>"
- name="<%=props.name%>"
+<input type="<%=type%>"
+ id="<%=id%>"
+ name="<%=name%>"
class="form-control"
- <%=props.autoComplete==='off' ? ' autoComplete="off"' : ''%>
- <%=props.disabled ? "disabled" : ""%>
- <%=props.placeholder ? `placeholder="${props.placeholder}"` : ''%>
- value="<%=props.value%>"/>
\ No newline at end of file
+ <%=autoComplete==='off' ? ' autoComplete="off"' : ''%>
+ <%=disabled ? "disabled" : ""%>
+ <%=placeholder ? `placeholder="${placeholder}"` : ''%>
+ value="<%=value%>"/>
\ No newline at end of file
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.globalFunctions.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.globalFunctions.template
new file mode 100644
index 00000000000..376698f3355
--- /dev/null
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.globalFunctions.template
@@ -0,0 +1,12 @@
+function onDelListItem<%= functionName %>(name, itemIndex) {
+ const disabled = <%= disabled %>
+ if (!disabled) {
+ delListItem(name, <%= minCount !== undefined ? minCount : "undefined" %>,
itemIndex, onDelListItem<%= functionName %>)
+ }
+}
+function onAddListItem<%= functionName %>(name, initialSetValues) {
+ const disabled = <%= disabled %>
+ if (!disabled || initialSetValues) {
+ addListItem(name, <%= defaultValue %>, <%= maxCount !== undefined ?
maxCount : "undefined" %>, `<%= childrenHtml %>`, "<%= functionName %>")
+ }
+}
\ No newline at end of file
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.setModelData.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.setModelData.template
new file mode 100644
index 00000000000..6cb5ed587ef
--- /dev/null
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.setModelData.template
@@ -0,0 +1,6 @@
+<%=path%>?.forEach((value, <%= indexVariableName %>) => {
+// Add element;
+onAddListItem<%= functionName %>(`<%= name %>`, true)
+const <%= prefix %>__currentItem = `<%= name %>.${<%= indexVariableName %>}`
+<%= itemsSetValueFromModel.code %>
+});
\ No newline at end of file
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.template
new file mode 100644
index 00000000000..87c354cba3f
--- /dev/null
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.template
@@ -0,0 +1,15 @@
+<div class="form-group">
+ <div class="card mb-3">
+ <div class="card-body">
+ <div class="card-title">
+ <label class="col-form-label"><%=label%></label>
+ <div id="add-item-<%= id %>" name="Add Item" class="badge
badge-pill float-right" role="button" tabindex="0" <%=disabled ? " disabled" :
""%>
+ onclick="onAddListItem<%= functionName %>('<%= id %>')"
+ onkeydown="onAddListItem<%= functionName %>('<%= id %>')">
+ <i className="octicon octicon-plus"><span>+</span></i>
+ </div>
+ </div>
+ <div role="list" id="<%= id %>"></div>
+ </div>
+ </div>
+</div>
\ No newline at end of file
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.writeModelData.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.writeModelData.template
new file mode 100644
index 00000000000..4d76efb14c1
--- /dev/null
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/listField.writeModelData.template
@@ -0,0 +1,5 @@
+<% if(!disabled) { %>
+formData.<%=name%> =
Array.from(document.getElementById("<%=name%>").childNodes).reduce((values,
element) => {
+ return [...values, getListValue(element)];
+}, []);
+<% } %>
\ No newline at end of file
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.globalFunctions.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.globalFunctions.template
new file mode 100644
index 00000000000..bb6e0bed409
--- /dev/null
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.globalFunctions.template
@@ -0,0 +1,5 @@
+<% _.each(props.children, function(child) { %>
+ <% if (child.globalFunctions) { %>
+ <%= child.globalFunctions.code %>
+ <% } %>
+<% }); %>
\ No newline at end of file
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.template
index 37aad5ee969..b2387f7f4d2 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.template
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/nestField.template
@@ -1,6 +1,6 @@
<fieldset<%=props.disabled ? " disabled" : ""%>>
<legend><%=props.label%></legend>
- <div>
+ <div role="group">
<% _.each(props.children, function(child) { %>
<%=child.html%>
<% }); %>
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/number.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/number.template
index 291a8d24f49..ff55355d8a3 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/number.template
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/number.template
@@ -1,12 +1,12 @@
<input
type="number"
class="form-control"
- id="<%=props.id%>"
- name="<%=props.name%>"
- <%=props.autoComplete==='off' ? ' autoComplete="off"' : ''%>
- <%=props.disabled ? "disabled" : ""%>
- <%=props.placeholder ? `placeholder="${props.placeholder}"` : ''%>
- <%=props.min ? `min="${props.min}"` : ''%>
- <%=props.max ? `max="${props.max}"` : ''%>
- <%=props.step ? `step="${props.step}"` : ''%>
- value="<%=props.value%>"/>
\ No newline at end of file
+ id="<%=id%>"
+ name="<%=name%>"
+ <%=autoComplete==='off' ? ' autoComplete="off"' : ''%>
+ <%=disabled ? "disabled" : ""%>
+ <%=placeholder ? `placeholder="${placeholder}"` : ''%>
+ <%=min ? `min="${min}"` : ''%>
+ <%=max ? `max="${max}"` : ''%>
+ <%=step ? `step="${step}"` : ''%>
+ value="<%=value%>"/>
\ No newline at end of file
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/radioGroup.setModelData.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/radioGroup.setModelData.template
index 37c30f4adfa..8218fe9ef05 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/radioGroup.setModelData.template
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/radioGroup.setModelData.template
@@ -1 +1 @@
-setRadioButtonGroupValue("<%=name%>", data?.<%=path%>);
\ No newline at end of file
+<% if (isListItem) { %>setRadioButtonGroupValue(`<%=name%>`, value<%=
valuePath %> ?? "");<% } else { %>setRadioButtonGroupValue("<%=name%>",
data?.<%=path%>);<% } %>
\ No newline at end of file
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/radioGroup.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/radioGroup.template
index 916abd3518a..1b7978fa951 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/radioGroup.template
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/radioGroup.template
@@ -1,11 +1,11 @@
-<% _.each(props.options, function(option) { %>
+<% _.each(options, function(option) { %>
<div class="form-check">
<input type="radio" class="form-check-input"
- id="<%=props.id + '__' + option.value%>"
- name="<%=props.name%>"
+ id="<%=id + '__' + option.value%>"
+ name="<%=name%>"
value="<%=option.value%>"
- <%=props.disabled ? "disabled" : ""%>
+ <%=disabled ? "disabled" : ""%>
<%=option.checked ? "checked" : ""%>/>
- <label class="form-check-label" for="<%=props.id + '__' +
option.value%>"><%=option.label%></label>
+ <label class="form-check-label" for="<%=id + '__' +
option.value%>"><%=option.label%></label>
</div>
<% }); %>
\ No newline at end of file
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/select.setModelData.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/select.setModelData.template
index e7639e58e2b..37f33ef13a9 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/select.setModelData.template
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/select.setModelData.template
@@ -1,5 +1,9 @@
-<% if(multiple) { %>
+<% if (isListItem) { %><% if(multiple) { %>
+setMultipleSelectValues(`<%=id%>`, value<%= valuePath %>);
+<% } else { %>
+setSelectValue(`<%=id%>`, value<%= valuePath %>);
+<% } %><% } else { %><% if(multiple) { %>
setMultipleSelectValues("<%=id%>", data?.<%=path%>);
<% } else { %>
setSelectValue("<%=id%>", data?.<%=path%>);
-<% } %>
\ No newline at end of file
+<% } %><% } %>
\ No newline at end of file
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/select.template
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/select.template
index 01539cebb1c..a1ec70dcdd6 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/resources/templates/select.template
+++
b/packages/form-code-generator-bootstrap4-theme/src/resources/templates/select.template
@@ -1,8 +1,8 @@
-<select class="form-control" id="<%=props.id%>"
name="<%=props.name%>"<%=props.disabled ? " disabled" : ""%><%=props.multiple ?
" multiple" : ""%>>
-<% if(props.placeHolder) { %>
- <option value=""><%=props.placeHolder%></option>
+<select class="form-control" id="<%=id%>" name="<%=name%>"<%=disabled ? "
disabled" : ""%><%=multiple ? " multiple" : ""%>>
+<% if(placeHolder) { %>
+ <option value=""><%=placeHolder%></option>
<% } %>
-<% _.each(props.options, function(option) { %>
- <option value="<%=option.value%>"<%=props.value===option.value ? "
selected" : ""%>><%=option.label%></option>
+<% _.each(options, function(option) { %>
+ <option value="<%=option.value%>"<%=value===option.value ? " selected" :
""%>><%=option.label%></option>
<% }); %>
</select>
\ No newline at end of file
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/AutoField.tsx
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/AutoField.tsx
index 1ecad9d1d07..3ea1aaa96dc 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/AutoField.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/AutoField.tsx
@@ -22,6 +22,7 @@ import {
BoolField,
CheckBoxGroupField,
DateField,
+ ListField,
NestField,
NumField,
RadioField,
@@ -41,10 +42,8 @@ const AutoField = createAutoField((props) => {
}
switch (props.fieldType) {
- /*
- TODO: implement array support
case Array:
- return ListField;*/
+ return ListField;
case Boolean:
return BoolField;
case Date:
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/AutoForm.tsx
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/AutoForm.tsx
index cd9463ba497..174ec799770 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/AutoForm.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/AutoForm.tsx
@@ -32,12 +32,7 @@ export type AutoFormProps = {
};
const AutoForm: React.FC<AutoFormProps> = (props) => {
- const properties = {
- children: renderFormInputs(props.schema),
- };
-
- const form: CodeGenElement = renderCodeGenElement(FORM, properties);
-
+ const form: CodeGenElement = renderCodeGenElement(FORM, { children:
renderFormInputs(props.schema) });
return <>{escape(form.html)}</>;
};
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/BoolField.tsx
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/BoolField.tsx
index 9254b235195..78f97e40303 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/BoolField.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/BoolField.tsx
@@ -23,6 +23,7 @@ import { FormInput } from "../api";
import { useAddFormElementToBootstrapContext } from
"./BootstrapCodeGenContext";
import { CHECKBOX, renderCodeGenElement } from "./templates/templates";
+import { ListItemProps } from "./rendering/ListFieldInput";
export type BoolFieldProps = HTMLFieldProps<
boolean,
@@ -30,19 +31,19 @@ export type BoolFieldProps = HTMLFieldProps<
{
name: string;
label: string;
+ itemProps?: ListItemProps;
}
>;
const Bool: React.FC<BoolFieldProps> = (props: BoolFieldProps) => {
- const properties = {
+ const element: FormInput = renderCodeGenElement(CHECKBOX, {
id: props.name,
name: props.name,
label: props.label,
disabled: props.disabled ?? false,
checked: props.value ?? false,
- };
-
- const element: FormInput = renderCodeGenElement(CHECKBOX, properties);
+ itemProps: props.itemProps,
+ });
useAddFormElementToBootstrapContext(element);
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/CheckBoxGroupField.tsx
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/CheckBoxGroupField.tsx
index 27e7eb31922..4330256759f 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/CheckBoxGroupField.tsx
+++
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/CheckBoxGroupField.tsx
@@ -22,6 +22,7 @@ import { connectField, HTMLFieldProps } from "uniforms/cjs";
import { CHECKBOXGROUP, renderCodeGenElement } from "./templates/templates";
import { useAddFormElementToBootstrapContext } from
"./BootstrapCodeGenContext";
import { FormInput } from "../api";
+import { ListItemProps } from "./rendering/ListFieldInput";
export type CheckBoxGroupProps = HTMLFieldProps<
string[],
@@ -32,28 +33,25 @@ export type CheckBoxGroupProps = HTMLFieldProps<
allowedValues?: string[];
required: boolean;
transform?(value: string): string;
+ itemProps?: ListItemProps;
}
>;
const CheckBoxGroup: React.FC<CheckBoxGroupProps> = (props:
CheckBoxGroupProps) => {
- const options =
- props.allowedValues?.map((option) => {
+ const element: FormInput = renderCodeGenElement(CHECKBOXGROUP, {
+ id: props.name,
+ name: props.name,
+ label: props.label,
+ disabled: props.disabled,
+ itemProps: props.itemProps,
+ options: (props.allowedValues ?? [])?.map((option) => {
return {
value: option,
label: props.transform ? props.transform(option) : option,
checked: props.value?.includes(option),
};
- }) || [];
-
- const inputProps = {
- id: props.name,
- name: props.name,
- label: props.label,
- disabled: props.disabled,
- options: options,
- };
-
- const element: FormInput = renderCodeGenElement(CHECKBOXGROUP, inputProps);
+ }),
+ });
useAddFormElementToBootstrapContext(element);
return <>{JSON.stringify(element)}</>;
};
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/DateField.tsx
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/DateField.tsx
index cd98b1e14dc..b8b3c781ebf 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/DateField.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/DateField.tsx
@@ -22,6 +22,7 @@ import { connectField, HTMLFieldProps } from "uniforms/cjs";
import { DATE, renderCodeGenElement } from "./templates/templates";
import { useAddFormElementToBootstrapContext } from
"./BootstrapCodeGenContext";
import { FormInput } from "../api";
+import { ListItemProps } from "./rendering/ListFieldInput";
export type DateFieldProps = HTMLFieldProps<
Date,
@@ -32,6 +33,7 @@ export type DateFieldProps = HTMLFieldProps<
required: boolean;
max?: Date;
min?: Date;
+ itemProps?: ListItemProps;
}
>;
@@ -40,7 +42,7 @@ const Date: React.FC<DateFieldProps> = (props:
DateFieldProps) => {
return date?.toISOString().slice(0, -8);
}
- const properties = {
+ const element: FormInput = renderCodeGenElement(DATE, {
id: props.name,
name: props.name,
label: props.label,
@@ -50,9 +52,8 @@ const Date: React.FC<DateFieldProps> = (props:
DateFieldProps) => {
value: formatDate(props.value),
max: formatDate(props.max),
min: formatDate(props.min),
- };
-
- const element: FormInput = renderCodeGenElement(DATE, properties);
+ itemProps: props.itemProps,
+ });
useAddFormElementToBootstrapContext(element);
return <>{JSON.stringify(element)}</>;
};
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/NestField.tsx
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/ListField.tsx
similarity index 59%
copy from
packages/form-code-generator-bootstrap4-theme/src/uniforms/NestField.tsx
copy to packages/form-code-generator-bootstrap4-theme/src/uniforms/ListField.tsx
index 2e99c297494..0116b91d206 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/NestField.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/ListField.tsx
@@ -19,14 +19,24 @@
import React, { useContext } from "react";
import { connectField, context, HTMLFieldProps } from "uniforms/cjs";
-import { renderNestedInputFragmentWithContext } from
"./rendering/RenderingUtils";
-import { FormElement, FormInput, FormInputContainer } from "../api";
+import { FormInputContainer } from "../api";
+import { renderListItemFragmentWithContext } from "./rendering/RenderingUtils";
import { useBootstrapCodegenContext } from "./BootstrapCodeGenContext";
-import { NESTED, renderCodeGenElement } from "./templates/templates";
+import { LIST, renderCodeGenElement } from "./templates/templates";
+import { ListItemProps } from "./rendering/ListFieldInput";
+import { getNextIndexVariableName } from "./templates/ListFieldTemplate";
-export type NestFieldProps = HTMLFieldProps<object, HTMLDivElement, {
itemProps?: object }>;
+export type ListFieldProps = HTMLFieldProps<
+ unknown[],
+ HTMLDivElement,
+ {
+ itemProps: ListItemProps;
+ maxCount?: number;
+ minCount?: number;
+ }
+>;
-const Nest: React.FunctionComponent<NestFieldProps> = ({
+const List: React.FunctionComponent<ListFieldProps> = ({
id,
children,
error,
@@ -38,33 +48,30 @@ const Nest: React.FunctionComponent<NestFieldProps> = ({
showInlineError,
disabled,
...props
-}: NestFieldProps) => {
+}: ListFieldProps) => {
const uniformsContext = useContext(context);
const codegenCtx = useBootstrapCodegenContext();
- const nestedFields: FormElement<any>[] = [];
- if (fields) {
- fields.forEach((field) => {
- const nestedElement =
renderNestedInputFragmentWithContext(uniformsContext, field, itemProps,
disabled);
- if (nestedElement) {
- nestedFields.push(nestedElement);
- } else {
- console.log(`Cannot render form field for: '${field}'`);
- }
- });
- }
-
- const properties = {
+ const element: FormInputContainer = renderCodeGenElement(LIST, {
id: name,
name: name,
label: label,
disabled: disabled,
- children: nestedFields,
- };
+ itemProps: itemProps,
+ children: renderListItemFragmentWithContext(
+ uniformsContext,
+ "$",
+ {
+ isListItem: true,
+ indexVariableName: getNextIndexVariableName(itemProps),
+ listName: name,
+ },
+ disabled
+ ),
+ });
- const element: FormInputContainer = renderCodeGenElement(NESTED, properties);
codegenCtx?.rendered.push(element);
return <>{JSON.stringify(element)}</>;
};
-export default connectField(Nest);
+export default connectField(List);
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/NestField.tsx
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/NestField.tsx
index 2e99c297494..72f2243bc11 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/NestField.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/NestField.tsx
@@ -23,8 +23,9 @@ import { renderNestedInputFragmentWithContext } from
"./rendering/RenderingUtils
import { FormElement, FormInput, FormInputContainer } from "../api";
import { useBootstrapCodegenContext } from "./BootstrapCodeGenContext";
import { NESTED, renderCodeGenElement } from "./templates/templates";
+import { ListItemProps } from "./rendering/ListFieldInput";
-export type NestFieldProps = HTMLFieldProps<object, HTMLDivElement, {
itemProps?: object }>;
+export type NestFieldProps = HTMLFieldProps<object, HTMLDivElement, {
itemProps: ListItemProps }>;
const Nest: React.FunctionComponent<NestFieldProps> = ({
id,
@@ -42,27 +43,24 @@ const Nest: React.FunctionComponent<NestFieldProps> = ({
const uniformsContext = useContext(context);
const codegenCtx = useBootstrapCodegenContext();
- const nestedFields: FormElement<any>[] = [];
- if (fields) {
- fields.forEach((field) => {
- const nestedElement =
renderNestedInputFragmentWithContext(uniformsContext, field, itemProps,
disabled);
- if (nestedElement) {
- nestedFields.push(nestedElement);
- } else {
- console.log(`Cannot render form field for: '${field}'`);
- }
- });
- }
-
- const properties = {
+ const element: FormInputContainer = renderCodeGenElement(NESTED, {
id: name,
name: name,
label: label,
disabled: disabled,
- children: nestedFields,
- };
-
- const element: FormInputContainer = renderCodeGenElement(NESTED, properties);
+ itemProps: itemProps,
+ children: fields
+ ? fields.reduce((nestedFields: FormElement<any>[], field) => {
+ const nestedElement =
renderNestedInputFragmentWithContext(uniformsContext, field, itemProps,
disabled);
+ if (nestedElement) {
+ nestedFields.push(nestedElement);
+ } else {
+ console.log(`Cannot render form field for: '${field}'`);
+ }
+ return nestedFields;
+ }, [])
+ : [],
+ });
codegenCtx?.rendered.push(element);
return <>{JSON.stringify(element)}</>;
};
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/NumField.tsx
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/NumField.tsx
index 1bb0e8fdaf6..74977cdd707 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/NumField.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/NumField.tsx
@@ -22,6 +22,7 @@ import { connectField, HTMLFieldProps } from "uniforms/cjs";
import { NUMBER, renderCodeGenElement } from "./templates/templates";
import { useAddFormElementToBootstrapContext } from
"./BootstrapCodeGenContext";
import { FormInput } from "../api";
+import { ListItemProps } from "./rendering/ListFieldInput";
export type NumFieldProps = HTMLFieldProps<
string,
@@ -32,10 +33,11 @@ export type NumFieldProps = HTMLFieldProps<
decimal?: boolean;
min?: string;
max?: string;
+ itemProps?: ListItemProps;
}
>;
const Num: React.FC<NumFieldProps> = (props: NumFieldProps) => {
- const properties = {
+ const element: FormInput = renderCodeGenElement(NUMBER, {
id: props.name,
name: props.name,
label: props.label,
@@ -47,9 +49,8 @@ const Num: React.FC<NumFieldProps> = (props: NumFieldProps)
=> {
max: props.max,
min: props.min,
step: props.decimal ? 0.01 : 1,
- };
-
- const element: FormInput = renderCodeGenElement(NUMBER, properties);
+ itemProps: props.itemProps,
+ });
useAddFormElementToBootstrapContext(element);
return <>{JSON.stringify(element)}</>;
};
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/RadioField.tsx
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/RadioField.tsx
index d49e534d110..90ee646a99a 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/RadioField.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/RadioField.tsx
@@ -22,6 +22,7 @@ import { connectField, HTMLFieldProps } from "uniforms/cjs";
import { RADIOGROUP, renderCodeGenElement } from "./templates/templates";
import { useAddFormElementToBootstrapContext } from
"./BootstrapCodeGenContext";
import { FormInput } from "../api";
+import { ListItemProps } from "./rendering/ListFieldInput";
export type RadioFieldProps = HTMLFieldProps<
string,
@@ -33,28 +34,25 @@ export type RadioFieldProps = HTMLFieldProps<
allowedValues?: string[];
required: boolean;
disabled: boolean;
+ itemProps?: ListItemProps;
}
>;
const Radio = (props: RadioFieldProps) => {
- const options =
- props.allowedValues?.map((option) => {
+ const element: FormInput = renderCodeGenElement(RADIOGROUP, {
+ id: props.name,
+ name: props.name,
+ label: props.label,
+ disabled: props.disabled,
+ itemProps: props.itemProps,
+ options: (props.allowedValues ?? [])?.map((option) => {
return {
value: option,
label: props.transform ? props.transform(option) : option,
checked: props.value === option,
};
- }) || [];
-
- const inputProps = {
- id: props.name,
- name: props.name,
- label: props.label,
- disabled: props.disabled,
- options: options,
- };
-
- const element: FormInput = renderCodeGenElement(RADIOGROUP, inputProps);
+ }),
+ });
useAddFormElementToBootstrapContext(element);
return <>{JSON.stringify(element)}</>;
};
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/SelectField.tsx
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/SelectField.tsx
index f2dd2ec6a6b..9438ebd1eec 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/SelectField.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/SelectField.tsx
@@ -22,6 +22,7 @@ import { connectField, HTMLFieldProps } from "uniforms/cjs";
import { renderCodeGenElement, SELECT } from "./templates/templates";
import { useAddFormElementToBootstrapContext } from
"./BootstrapCodeGenContext";
import { FormInput } from "../api";
+import { ListItemProps } from "./rendering/ListFieldInput";
export type SelectInputProps = HTMLFieldProps<
string[],
@@ -33,31 +34,28 @@ export type SelectInputProps = HTMLFieldProps<
allowedValues?: string[];
required: boolean;
transform?(value: string): string;
+ itemProps?: ListItemProps;
}
>;
const Select: React.FC<SelectInputProps> = (props: SelectInputProps) => {
- const options =
- props.allowedValues?.map((option) => {
- return {
- value: option,
- label: props.transform ? props.transform(option) : option,
- checked: props.value?.includes(option),
- };
- }) || [];
-
- const inputProps = {
+ const element: FormInput = renderCodeGenElement(SELECT, {
id: props.name,
name: props.name,
label: props.label,
multiple: props.fieldType === Array,
placeHolder: props.placeHolder,
disabled: props.disabled,
- options: options,
value: props.value,
- };
-
- const element: FormInput = renderCodeGenElement(SELECT, inputProps);
+ itemProps: props.itemProps,
+ options: (props.allowedValues ?? [])?.map((option) => {
+ return {
+ value: option,
+ label: props.transform ? props.transform(option) : option,
+ checked: props.value?.includes(option),
+ };
+ }),
+ });
useAddFormElementToBootstrapContext(element);
return <>{JSON.stringify(element)}</>;
};
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/TextField.tsx
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/TextField.tsx
index 49db3122e17..a5c7204d4c4 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/TextField.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/TextField.tsx
@@ -22,6 +22,7 @@ import { connectField, HTMLFieldProps } from "uniforms/cjs";
import { INPUT, renderCodeGenElement } from "./templates/templates";
import { useAddFormElementToBootstrapContext } from
"./BootstrapCodeGenContext";
import { FormInput } from "../api";
+import { ListItemProps } from "./rendering/ListFieldInput";
export type TextFieldProps = HTMLFieldProps<
string,
@@ -29,11 +30,12 @@ export type TextFieldProps = HTMLFieldProps<
{
label: string;
required: boolean;
+ itemProps?: ListItemProps;
}
>;
const Text: React.FC<TextFieldProps> = (props: TextFieldProps) => {
- const properties = {
+ const element: FormInput = renderCodeGenElement(INPUT, {
id: props.name,
name: props.name,
label: props.label,
@@ -42,9 +44,8 @@ const Text: React.FC<TextFieldProps> = (props:
TextFieldProps) => {
placeholder: props.placeholder,
autoComplete: props.autoComplete ?? false,
value: props.value,
- };
-
- const element: FormInput = renderCodeGenElement(INPUT, properties);
+ itemProps: props.itemProps,
+ });
useAddFormElementToBootstrapContext(element);
return <>{JSON.stringify(element)}</>;
};
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/UnsupportedField.tsx
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/UnsupportedField.tsx
index c2af65f0db8..e045ac57959 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/UnsupportedField.tsx
+++
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/UnsupportedField.tsx
@@ -22,6 +22,7 @@ import { FormInput } from "../api";
import { connectField, HTMLFieldProps } from "uniforms/cjs";
import { renderCodeGenElement, UNSUPPORTED } from "./templates/templates";
import { useAddFormElementToBootstrapContext } from
"./BootstrapCodeGenContext";
+import { ListItemProps } from "./rendering/ListFieldInput";
export type UnsupportedFieldProps = HTMLFieldProps<
any,
@@ -29,18 +30,17 @@ export type UnsupportedFieldProps = HTMLFieldProps<
{
label: string;
required: boolean;
+ itemProps: ListItemProps;
}
>;
const Unsupported: React.FC<UnsupportedFieldProps> = (props:
UnsupportedFieldProps) => {
- const properties = {
- id: props.id,
- label: props.label,
+ const element: FormInput = renderCodeGenElement(UNSUPPORTED, {
+ id: props.name,
name: props.name,
+ label: props.label,
fieldType: props.fieldType.name,
- };
-
- const element: FormInput = renderCodeGenElement(UNSUPPORTED, properties);
+ });
useAddFormElementToBootstrapContext(element);
return <>{JSON.stringify(element)}</>;
};
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/index.ts
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/index.ts
index 34dd64bb451..3f38e3c7ce8 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/index.ts
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/index.ts
@@ -23,6 +23,7 @@ export { default as AutoForm } from "./AutoForm";
export { default as BoolField } from "./BoolField";
export { default as CheckBoxGroupField } from "./CheckBoxGroupField";
export { default as DateField } from "./DateField";
+export { default as ListField } from "./ListField";
export { default as NestField } from "./NestField";
export { default as NumField } from "./NumField";
export { default as RadioField } from "./RadioField";
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/renderForm.tsx
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/renderForm.tsx
index 8f63ae0c5f2..bfe7f1dca79 100644
--- a/packages/form-code-generator-bootstrap4-theme/src/uniforms/renderForm.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/src/uniforms/renderForm.tsx
@@ -32,7 +32,5 @@ interface Args {
}
export function renderForm(args: Args): string {
- const form = React.createElement(AutoForm, { ...args });
-
- return unescape(ReactDOMServer.renderToString(form));
+ return unescape(ReactDOMServer.renderToString(React.createElement(AutoForm,
{ ...args })));
}
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/NestedFieldInput.tsx
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/ListFieldInput.tsx
similarity index 76%
copy from
packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/NestedFieldInput.tsx
copy to
packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/ListFieldInput.tsx
index 2d80b4f32a2..92fc271aa84 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/NestedFieldInput.tsx
+++
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/ListFieldInput.tsx
@@ -22,20 +22,26 @@ import * as React from "react";
import AutoField from "../AutoField";
import { BootstrapCodeGenContext, CodeGenContextProvider } from
"../BootstrapCodeGenContext";
+export interface ListItemProps {
+ isListItem: boolean;
+ indexVariableName: string;
+ listName: string;
+}
+
export interface Props {
codegenCtx: BootstrapCodeGenContext;
uniformsContext: Context<any>;
- field: any;
- itempProps: any;
+ fieldName: any;
+ itemProps: ListItemProps;
disabled?: boolean;
}
-export const NestedFieldInput: React.FC<Props> = ({ codegenCtx,
uniformsContext, field, itempProps, disabled }) => {
+export const ListFieldInput: React.FC<Props> = ({ codegenCtx, uniformsContext,
fieldName, itemProps, disabled }) => {
return (
<CodeGenContextProvider schema={uniformsContext.schema}
codegenCtx={codegenCtx} uniformsCtx={uniformsContext}>
- <AutoField key={field} name={field} disabled={disabled} {...itempProps}
/>
+ <AutoField key={fieldName} name={fieldName} disabled={disabled}
itemProps={itemProps} />
</CodeGenContextProvider>
);
};
-export default NestedFieldInput;
+export default ListFieldInput;
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/NestedFieldInput.tsx
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/NestedFieldInput.tsx
index 2d80b4f32a2..8c1a9877560 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/NestedFieldInput.tsx
+++
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/NestedFieldInput.tsx
@@ -21,19 +21,20 @@ import { Context } from "uniforms";
import * as React from "react";
import AutoField from "../AutoField";
import { BootstrapCodeGenContext, CodeGenContextProvider } from
"../BootstrapCodeGenContext";
+import { ListItemProps } from "./ListFieldInput";
export interface Props {
codegenCtx: BootstrapCodeGenContext;
uniformsContext: Context<any>;
field: any;
- itempProps: any;
+ itemProps: ListItemProps;
disabled?: boolean;
}
-export const NestedFieldInput: React.FC<Props> = ({ codegenCtx,
uniformsContext, field, itempProps, disabled }) => {
+export const NestedFieldInput: React.FC<Props> = ({ codegenCtx,
uniformsContext, field, itemProps, disabled }) => {
return (
<CodeGenContextProvider schema={uniformsContext.schema}
codegenCtx={codegenCtx} uniformsCtx={uniformsContext}>
- <AutoField key={field} name={field} disabled={disabled} {...itempProps}
/>
+ <AutoField key={field} name={field} disabled={disabled}
itemProps={itemProps} />
</CodeGenContextProvider>
);
};
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/RenderingUtils.tsx
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/RenderingUtils.tsx
index a4218892f76..ed3e8cff4bb 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/RenderingUtils.tsx
+++
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/rendering/RenderingUtils.tsx
@@ -24,6 +24,7 @@ import { FormElement, FormInput } from "../../api";
import FormInputs from "./FormInputs";
import NestedFieldInput from "./NestedFieldInput";
import { BootstrapCodeGenContext } from "../BootstrapCodeGenContext";
+import ListFieldInput, { ListItemProps } from "./ListFieldInput";
export const renderFormInputs = (schema: Bridge): FormElement<any>[] => {
const codegenCtx: BootstrapCodeGenContext = {
@@ -36,14 +37,13 @@ export const renderFormInputs = (schema: Bridge):
FormElement<any>[] => {
});
ReactDOMServer.renderToString(inputsElement);
-
return codegenCtx.rendered;
};
export const renderNestedInputFragmentWithContext = (
uniformsContext: any,
field: any,
- itempProps: any,
+ itemProps: ListItemProps,
disabled?: boolean
): FormInput | undefined => {
const codegenCtx: BootstrapCodeGenContext = {
@@ -55,7 +55,30 @@ export const renderNestedInputFragmentWithContext = (
codegenCtx,
uniformsContext,
field,
- itempProps,
+ itemProps,
+ disabled,
+ })
+ );
+
+ return codegenCtx.rendered.length === 1 ? codegenCtx.rendered[0] : undefined;
+};
+
+export const renderListItemFragmentWithContext = (
+ uniformsContext: any,
+ fieldName: string,
+ itemProps: ListItemProps,
+ disabled?: boolean
+): FormInput | undefined => {
+ const codegenCtx: BootstrapCodeGenContext = {
+ rendered: [],
+ };
+
+ ReactDOMServer.renderToString(
+ React.createElement(ListFieldInput, {
+ codegenCtx,
+ uniformsContext,
+ fieldName,
+ itemProps,
disabled,
})
);
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/AbstractFormGroupTemplate.ts
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/AbstractFormGroupTemplate.ts
new file mode 100644
index 00000000000..e6eba8530a8
--- /dev/null
+++
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/AbstractFormGroupTemplate.ts
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import formGroupTemplate from
"!!raw-loader!../../resources/templates/formGroup.template";
+import { CodeGenElement, FormElement, FormInput } from "../../api";
+import { CompiledTemplate, template } from "underscore";
+import { getInputReference } from "../utils/Utils";
+import { fieldNameToOptionalChain, flatFieldName, getItemValeuPath } from
"./utils";
+import { ListItemProps } from "../rendering/ListFieldInput";
+import { DEFAULT_LIST_INDEX_NAME, getCurrentItemSetModelData,
getNormalizedListIdOrName } from "./ListFieldTemplate";
+
+export interface CodeGenTemplate<Element extends CodeGenElement, Properties> {
+ render: (props: Properties) => Element;
+}
+
+export interface FormElementTemplateProps<Type> {
+ id: string;
+ name: string;
+ label: string;
+ disabled: boolean;
+ value: Type;
+ itemProps: ListItemProps;
+ type: string;
+ placeholder?: string;
+ autoComplete?: boolean;
+ min?: string | number;
+ max?: string | number;
+ step?: number;
+}
+
+export interface FormElementTemplate<
+ Element extends FormElement<any>,
+ Properties extends FormElementTemplateProps<any>,
+> {
+ render: (props: Properties) => Element;
+}
+
+export abstract class AbstractFormGroupTemplate<Properties extends
FormElementTemplateProps<any>>
+ implements FormElementTemplate<FormInput, Properties>
+{
+ protected constructor(
+ readonly inputTemplate: CompiledTemplate,
+ readonly setValueFromModelTemplate: CompiledTemplate,
+ readonly writeValueToModelTemplate: CompiledTemplate
+ ) {}
+
+ render(props: Properties): FormInput {
+ return {
+ ref: getInputReference(props),
+ html: template(formGroupTemplate)({
+ id: props.itemProps?.isListItem ? getNormalizedListIdOrName(props.id)
: props.id,
+ label: props.label,
+ input: this.inputTemplate({
+ id: props.itemProps?.isListItem ?
getNormalizedListIdOrName(props.id) : props.id,
+ name: props.itemProps?.isListItem ?
getNormalizedListIdOrName(props.name) : props.name,
+ value: props.value,
+ type: props.type,
+ disabled: props.disabled,
+ placeholder: props.placeholder,
+ autoComplete: props.autoComplete,
+ min: props.min,
+ max: props.max,
+ step: props.step,
+ }),
+ isListItem: props.itemProps?.isListItem ?? false,
+ }),
+ disabled: props.disabled,
+ globalFunctions: undefined,
+ setValueFromModelCode: {
+ code: this.setValueFromModelTemplate({
+ ...props,
+ id: props.itemProps?.isListItem
+ ? getCurrentItemSetModelData(props.id,
props.itemProps?.indexVariableName ?? DEFAULT_LIST_INDEX_NAME)
+ : props.id,
+ isListItem: props.itemProps?.isListItem ?? false,
+ path: fieldNameToOptionalChain(props.name),
+ valuePath: props.itemProps?.isListItem ?
getItemValeuPath(props.name) : "",
+ flatFieldName: flatFieldName(props.name),
+ }),
+ },
+ writeValueToModelCode:
+ props.disabled || !this.writeValueToModelTemplate
+ ? undefined
+ : {
+ code: this.writeValueToModelTemplate({
+ ...props,
+ flatFieldName: flatFieldName(props.name),
+ }),
+ },
+ };
+ }
+}
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/AutoFormTemplate.ts
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/AutoFormTemplate.ts
index 925f33a5fd3..c9da6140575 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/AutoFormTemplate.ts
+++
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/AutoFormTemplate.ts
@@ -20,7 +20,7 @@
import form from "!!raw-loader!../../resources/templates/form.template";
import * as prettier from "prettier";
import trim from "lodash/trim";
-import { CodeGenTemplate } from "./types";
+import { CodeGenTemplate } from "./AbstractFormGroupTemplate";
import { CompiledTemplate, template } from "underscore";
import { CodeGenElement, FormElement } from "../../api";
@@ -36,12 +36,8 @@ export class AutoFormTemplate implements
CodeGenTemplate<CodeGenElement, AutoFor
}
render(props: AutoFormProps): CodeGenElement {
- const data = {
- props: props,
- };
-
const rawTemplate = trim(
- this.formTemplate(data)
+ this.formTemplate({ props })
.split("\n")
.filter((line) => line && line.trim().length > 0)
.join("\n")
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/BoolFieldTemplate.ts
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/BoolFieldTemplate.ts
index 5f1fdde7782..2bd42fd5948 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/BoolFieldTemplate.ts
+++
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/BoolFieldTemplate.ts
@@ -20,11 +20,12 @@
import checkbox from
"!!raw-loader!../../resources/templates/checkbox.template";
import checkboxSetValueFromModel from
"!!raw-loader!../../resources/templates/checkbox.setModelData.template";
import checkboxWriteModelData from
"!!raw-loader!../../resources/templates/checkbox.writeModelData.template";
-import { FormElementTemplate, FormElementTemplateProps } from "./types";
-import { CodeFragment, FormInput } from "../../api";
+import { FormElementTemplate, FormElementTemplateProps } from
"./AbstractFormGroupTemplate";
+import { FormInput } from "../../api";
import { CompiledTemplate, template } from "underscore";
import { getInputReference } from "../utils/Utils";
-import { fieldNameToOptionalChain } from "./utils";
+import { fieldNameToOptionalChain, getItemValeuPath } from "./utils";
+import { DEFAULT_LIST_INDEX_NAME, getCurrentItemSetModelData,
getNormalizedListIdOrName } from "./ListFieldTemplate";
interface BoolFieldProps extends FormElementTemplateProps<boolean> {
checked: boolean;
@@ -42,38 +43,35 @@ export class BoolFieldTemplate implements
FormElementTemplate<FormInput, BoolFie
}
render(props: BoolFieldProps): FormInput {
- const data = {
- props: props,
- };
return {
ref: getInputReference(props),
- html: this.checkboxTemplate(data),
+ html: this.checkboxTemplate({
+ id: props.itemProps?.isListItem ? getNormalizedListIdOrName(props.id)
: props.id,
+ name: props.itemProps?.isListItem ?
getNormalizedListIdOrName(props.name) : props.name,
+ disabled: props.disabled,
+ checked: props.checked,
+ label: props.label,
+ }),
disabled: props.disabled,
- setValueFromModelCode: this.buildSetValueFromModelCode(props),
- writeValueToModelCode: this.buildWriteModelDataCode(props),
- };
- }
-
- protected buildSetValueFromModelCode(props: BoolFieldProps): CodeFragment {
- const properties = {
- id: props.id,
- path: fieldNameToOptionalChain(props.name),
- };
- return {
- code: this.checkboxSetValueFromModelTemplate(properties),
- };
- }
-
- protected buildWriteModelDataCode(props: BoolFieldProps): CodeFragment |
undefined {
- if (props.disabled) {
- return undefined;
- }
- const properties = {
- id: props.id,
- name: props.name,
- };
- return {
- code: this.checkboxWriteModelTemplate(properties),
+ globalFunctions: undefined,
+ setValueFromModelCode: {
+ code: this.checkboxSetValueFromModelTemplate({
+ id: props.itemProps?.isListItem
+ ? getCurrentItemSetModelData(props.id,
props.itemProps?.indexVariableName ?? DEFAULT_LIST_INDEX_NAME)
+ : props.id,
+ path: fieldNameToOptionalChain(props.name),
+ valuePath: props.itemProps?.isListItem ?
getItemValeuPath(props.name) : "",
+ isListItem: props.itemProps?.isListItem ?? false,
+ }),
+ },
+ writeValueToModelCode: props.disabled
+ ? undefined
+ : {
+ code: this.checkboxWriteModelTemplate({
+ id: props.id,
+ name: props.name,
+ }),
+ },
};
}
}
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/CheckboxGroupFieldTemplate.ts
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/CheckboxGroupFieldTemplate.ts
index 256af1d5275..e9d39d233a3 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/CheckboxGroupFieldTemplate.ts
+++
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/CheckboxGroupFieldTemplate.ts
@@ -22,11 +22,13 @@ import getRequiredCode from
"!!raw-loader!../../resources/staticCode/getCheckbox
import input from
"!!raw-loader!../../resources/templates/checkboxGroup.template";
import setValueFromModel from
"!!raw-loader!../../resources/templates/checkboxGroup.setModelData.template";
import writeValueToModel from
"!!raw-loader!../../resources/templates/checkboxGroup.writeModelData.template";
-import { FORM_GROUP_TEMPLATE, FormElementTemplate, FormElementTemplateProps }
from "./types";
+import formGroupTemplate from
"!!raw-loader!../../resources/templates/formGroup.template";
+import { FormElementTemplate, FormElementTemplateProps } from
"./AbstractFormGroupTemplate";
import { CompiledTemplate, template } from "underscore";
-import { CodeFragment, FormInput } from "../../api";
-import { fieldNameToOptionalChain, flatFieldName } from "./utils";
+import { FormInput } from "../../api";
+import { fieldNameToOptionalChain, getItemValeuPath } from "./utils";
import { getInputReference } from "../utils/Utils";
+import { DEFAULT_LIST_INDEX_NAME, getCurrentItemSetModelData,
getNormalizedListIdOrName } from "./ListFieldTemplate";
export interface Option {
value: string;
@@ -42,47 +44,49 @@ export class CheckBoxGroupFieldTemplate implements
FormElementTemplate<FormInput
private readonly inputTemplate: CompiledTemplate;
private readonly setValueFromModelTemplate: CompiledTemplate;
private readonly writeValueToModelTemplate: CompiledTemplate;
+ private readonly formGroupTemplate: CompiledTemplate;
constructor() {
this.inputTemplate = template(input);
this.setValueFromModelTemplate = template(setValueFromModel);
this.writeValueToModelTemplate = template(writeValueToModel);
+ this.formGroupTemplate = template(formGroupTemplate);
}
render(props: CheckBoxGroupFieldProps): FormInput {
- const data = {
- props: props,
- input: this.inputTemplate({ props: props }),
- };
-
return {
ref: getInputReference(props),
- html: FORM_GROUP_TEMPLATE(data),
+ html: this.formGroupTemplate({
+ id: props.itemProps?.isListItem ? getNormalizedListIdOrName(props.id)
: props.id,
+ label: props.label,
+ input: this.inputTemplate({
+ id: props.itemProps?.isListItem ?
getNormalizedListIdOrName(props.id) : props.id,
+ name: props.itemProps?.isListItem ?
getNormalizedListIdOrName(props.name) : props.name,
+ options: props.options,
+ disabled: props.disabled,
+ }),
+ isListItem: props.itemProps?.isListItem ?? false,
+ }),
disabled: props.disabled,
- setValueFromModelCode: this.buildSetValueFromModelCode(props),
- writeValueToModelCode: this.buildWriteValueToModelCode(props),
- };
- }
-
- private buildSetValueFromModelCode(props: CheckBoxGroupFieldProps):
CodeFragment {
- const properties = {
- ...props,
- path: fieldNameToOptionalChain(props.name),
- };
- return {
- code: this.setValueFromModelTemplate(properties),
- requiredCode: [setRequiredCode],
- };
- }
-
- private buildWriteValueToModelCode(props: CheckBoxGroupFieldProps):
CodeFragment | undefined {
- if (props.disabled) {
- return undefined;
- }
-
- return {
- code: this.writeValueToModelTemplate(props),
- requiredCode: [getRequiredCode],
+ globalFunctions: undefined,
+ setValueFromModelCode: {
+ code: this.setValueFromModelTemplate({
+ ...props,
+ name: props.itemProps?.isListItem
+ ? getCurrentItemSetModelData(props.name,
props.itemProps?.indexVariableName ?? DEFAULT_LIST_INDEX_NAME)
+ : props.name,
+ path: fieldNameToOptionalChain(props.name),
+ valuePath: props.itemProps?.isListItem ?
getItemValeuPath(props.name) : "",
+ isListItem: props.itemProps?.isListItem ?? false,
+ }),
+ requiredCode: [setRequiredCode],
+ },
+ writeValueToModelCode: props.disabled
+ ? undefined
+ : {
+ code: this.writeValueToModelTemplate(props),
+ requiredCode: [getRequiredCode],
+ },
};
}
}
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/DateFieldTemplate.ts
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/DateFieldTemplate.ts
index ac96bb532ce..264c27243b1 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/DateFieldTemplate.ts
+++
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/DateFieldTemplate.ts
@@ -21,7 +21,7 @@ import date from
"!!raw-loader!../../resources/templates/date.template";
import setValueFromModel from
"!!raw-loader!../../resources/templates/date.setModelData.template";
import writeValueToModel from
"!!raw-loader!../../resources/templates/input.writeModelData.template";
import { template } from "underscore";
-import { AbstractFormGroupInputTemplate, FormElementTemplateProps } from
"./types";
+import { AbstractFormGroupTemplate, FormElementTemplateProps } from
"./AbstractFormGroupTemplate";
interface DateFieldProps extends FormElementTemplateProps<string> {
type: string;
@@ -31,7 +31,7 @@ interface DateFieldProps extends
FormElementTemplateProps<string> {
min: string;
}
-export class DateFieldTemplate extends
AbstractFormGroupInputTemplate<DateFieldProps> {
+export class DateFieldTemplate extends
AbstractFormGroupTemplate<DateFieldProps> {
constructor() {
super(template(date), template(setValueFromModel),
template(writeValueToModel));
}
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/ListFieldTemplate.ts
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/ListFieldTemplate.ts
new file mode 100644
index 00000000000..6ece259d7cd
--- /dev/null
+++
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/ListFieldTemplate.ts
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import listField from
"!!raw-loader!../../resources/templates/listField.template";
+import globalFunctions from
"!!raw-loader!../../resources/templates/listField.globalFunctions.template";
+import setValueFromModel from
"!!raw-loader!../../resources/templates/listField.setModelData.template";
+import writeValueToModel from
"!!raw-loader!../../resources/templates/listField.writeModelData.template";
+import listHelperFunctions from
"!!raw-loader!../../resources/staticCode/listHelperFunctions.txt";
+import { FormElementTemplate, FormElementTemplateProps } from
"./AbstractFormGroupTemplate";
+import { FormElement, FormInputContainer, InputReference } from "../../api";
+import { CompiledTemplate, template } from "underscore";
+import { fieldNameToOptionalChain } from "./utils";
+import { ListItemProps } from "../rendering/ListFieldInput";
+
+export const DEFAULT_LIST_INDEX_NAME = "itemIndex";
+
+interface ListFieldTemplateProps extends FormElementTemplateProps<any> {
+ children: FormElement<any>;
+ maxCount: number;
+ minCount: number;
+}
+
+export class ListFieldTemplate implements
FormElementTemplate<FormInputContainer, ListFieldTemplateProps> {
+ private readonly listFieldTemplate: CompiledTemplate = template(listField);
+ private readonly listFieldGlobalFunctionsTemplate: CompiledTemplate =
template(globalFunctions);
+ private readonly listFieldSetValueFromModelTemplate: CompiledTemplate =
template(setValueFromModel);
+ private readonly listFieldWriteValueToModelTemplate: CompiledTemplate =
template(writeValueToModel);
+
+ render({
+ id,
+ disabled,
+ label,
+ children,
+ maxCount,
+ minCount,
+ value,
+ name,
+ itemProps,
+ }: ListFieldTemplateProps): FormInputContainer {
+ const ref: InputReference[] = children.ref;
+
+ const getDefaultItemValue = () => {
+ return "undefined";
+ };
+
+ return {
+ ref,
+ html: this.listFieldTemplate({
+ id: getNormalizedListIdOrName(id),
+ disabled,
+ label,
+ value,
+ childrenHtml: children.html,
+ functionName: getFunctionName(name),
+ }),
+ disabled: disabled,
+ globalFunctions: {
+ code:
+ this.listFieldGlobalFunctionsTemplate({
+ defaultValue: getDefaultItemValue(),
+ disabled,
+ minCount,
+ maxCount,
+ childrenHtml: `${children.html}`,
+ name,
+ functionName: getFunctionName(name),
+ }) + (children?.globalFunctions?.code !== undefined ?
children?.globalFunctions?.code : ""),
+ requiredCode: [listHelperFunctions],
+ },
+ setValueFromModelCode: {
+ code: this.listFieldSetValueFromModelTemplate({
+ id,
+ path: getSetItemPath(name),
+ indexVariableName: "index",
+ itemsSetValueFromModel: children.setValueFromModelCode,
+ name: getCurrentItemSetModelData(name, itemProps?.indexVariableName
?? DEFAULT_LIST_INDEX_NAME),
+ functionName: getFunctionName(name),
+ prefix: getNextIndexVariableName(itemProps),
+ }),
+ requiredCode: [],
+ },
+ writeValueToModelCode: {
+ code: this.listFieldWriteValueToModelTemplate({
+ disabled,
+ name,
+ }),
+ requiredCode: [],
+ },
+ };
+ }
+}
+
+// "value" is the name used in the listField.setModelData.template
+function getSetItemPath(name: string) {
+ if (name.endsWith("$")) {
+ return "value";
+ }
+ if (name.includes("$.")) {
+ return `value?.${name.split(".").pop()}`;
+ }
+ return `data?.${fieldNameToOptionalChain(name)}`;
+}
+
+// "currentItem" is the name used in the listField.setModelData.template
+export function getCurrentItemSetModelData(name: string, prefix: string) {
+ const splittedName = name.split(`.$`);
+ // Is nested
+ if (splittedName.length > 1) {
+ // ${currentItem}. with last part of the field name
+ return `\${${prefix}__currentItem}` + (splittedName?.[splittedName.length
- 1] ?? "");
+ }
+ return name;
+}
+
+function getFunctionName(name: string) {
+ return name
+ .split(".")
+ .map((word) => `${word?.[0]?.toUpperCase()}${word?.slice(1)}`)
+ .join("");
+}
+
+export function getNextIndexVariableName(itemProps: ListItemProps) {
+ if (itemProps === undefined) {
+ return DEFAULT_LIST_INDEX_NAME;
+ }
+ return "nested__" + itemProps.indexVariableName;
+}
+
+function splitLastOccurrence(str: string, char: string) {
+ const lastIndex = str.lastIndexOf(char);
+ if (lastIndex === -1) {
+ return [str];
+ }
+ return [str.substring(0, lastIndex), str.substring(lastIndex)];
+}
+
+export function getNormalizedListIdOrName(id: string) {
+ return `${splitLastOccurrence(id, "$")[1] ?
`\${name}.${splitLastOccurrence(id, "$")[1]}` : id}`;
+}
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/NestFieldTemplate.ts
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/NestFieldTemplate.ts
index 51550932263..920d022fb7a 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/NestFieldTemplate.ts
+++
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/NestFieldTemplate.ts
@@ -18,10 +18,11 @@
*/
import nestField from
"!!raw-loader!../../resources/templates/nestField.template";
+import globalFunctions from
"!!raw-loader!../../resources/templates/nestField.globalFunctions.template";
import setValueFromModel from
"!!raw-loader!../../resources/templates/nestField.setModelData.template";
import writeValueToModel from
"!!raw-loader!../../resources/templates/nestField.writeModelData.template";
-import { FormElementTemplate, FormElementTemplateProps } from "./types";
-import { CodeFragment, FormElement, FormInputContainer, InputReference } from
"../../api";
+import { FormElementTemplate, FormElementTemplateProps } from
"./AbstractFormGroupTemplate";
+import { FormElement, FormInputContainer, InputReference } from "../../api";
import { CompiledTemplate, template } from "underscore";
import { union } from "lodash";
@@ -31,12 +32,14 @@ interface NestFieldTemplateProps extends
FormElementTemplateProps<any> {
export class NestFieldTemplate implements
FormElementTemplate<FormInputContainer, NestFieldTemplateProps> {
private readonly nestFieldTemplate: CompiledTemplate = template(nestField);
+ private readonly nestFieldGlobalFunctionsTemplate: CompiledTemplate =
template(globalFunctions);
private readonly nestFieldSetValueFromModelTemplate: CompiledTemplate =
template(setValueFromModel);
private readonly nestFieldWriteValueToModelTemplate: CompiledTemplate =
template(writeValueToModel);
render(props: NestFieldTemplateProps): FormInputContainer {
const ref: InputReference[] = [];
+ let globalFunctionsRequiredCode: string[] = [];
let setValueFromModelRequiredCode: string[] = [];
let writeValueToModelRequiredCode: string[] = [];
@@ -47,6 +50,10 @@ export class NestFieldTemplate implements
FormElementTemplate<FormInputContainer
ref.push(child.ref);
}
+ if (child.globalFunctions) {
+ globalFunctionsRequiredCode = union(globalFunctionsRequiredCode,
child.globalFunctions.requiredCode);
+ }
+
if (child.setValueFromModelCode) {
setValueFromModelRequiredCode = union(setValueFromModelRequiredCode,
child.setValueFromModelCode.requiredCode);
}
@@ -60,31 +67,20 @@ export class NestFieldTemplate implements
FormElementTemplate<FormInputContainer
ref,
html: this.nestFieldTemplate({ props: props }),
disabled: props.disabled,
- setValueFromModelCode: this.buildSetValueFromModelCode(props,
setValueFromModelRequiredCode),
- writeValueToModelCode: this.buildWriteValueFromModelCode(props,
writeValueToModelRequiredCode),
- };
- }
-
- protected buildSetValueFromModelCode(
- props: NestFieldTemplateProps,
- setValueFromModelRequiredCode: string[]
- ): CodeFragment {
- return {
- code: this.nestFieldSetValueFromModelTemplate({ props: props }),
- requiredCode: setValueFromModelRequiredCode,
- };
- }
-
- protected buildWriteValueFromModelCode(
- props: NestFieldTemplateProps,
- writeValueToModelRequiredCode: string[]
- ): CodeFragment | undefined {
- if (props.disabled) {
- return undefined;
- }
- return {
- code: this.nestFieldWriteValueToModelTemplate({ props: props }),
- requiredCode: writeValueToModelRequiredCode,
+ globalFunctions: {
+ code: this.nestFieldGlobalFunctionsTemplate({ props: props }),
+ requiredCode: globalFunctionsRequiredCode,
+ },
+ setValueFromModelCode: {
+ code: this.nestFieldSetValueFromModelTemplate({ props: props }),
+ requiredCode: setValueFromModelRequiredCode,
+ },
+ writeValueToModelCode: props.disabled
+ ? undefined
+ : {
+ code: this.nestFieldWriteValueToModelTemplate({ props: props }),
+ requiredCode: writeValueToModelRequiredCode,
+ },
};
}
}
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/NumFieldTemplate.ts
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/NumFieldTemplate.ts
index 6993b3cd8d1..b0340bd6552 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/NumFieldTemplate.ts
+++
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/NumFieldTemplate.ts
@@ -21,7 +21,7 @@ import number from
"!!raw-loader!../../resources/templates/number.template";
import setValueFromModel from
"!!raw-loader!../../resources/templates/input.setModelData.template";
import writeValueToModel from
"!!raw-loader!../../resources/templates/number.writeModelData.template";
import { template } from "underscore";
-import { AbstractFormGroupInputTemplate, FormElementTemplateProps } from
"./types";
+import { AbstractFormGroupTemplate, FormElementTemplateProps } from
"./AbstractFormGroupTemplate";
interface NumFieldProps extends FormElementTemplateProps<string> {
autoComplete: boolean;
@@ -31,7 +31,7 @@ interface NumFieldProps extends
FormElementTemplateProps<string> {
step: number;
}
-export class NumFieldTemplate extends
AbstractFormGroupInputTemplate<NumFieldProps> {
+export class NumFieldTemplate extends AbstractFormGroupTemplate<NumFieldProps>
{
constructor() {
super(template(number), template(setValueFromModel),
template(writeValueToModel));
}
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/RadioGroupFieldTemplate.ts
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/RadioGroupFieldTemplate.ts
index 980b6b1bd20..fc10c56c62b 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/RadioGroupFieldTemplate.ts
+++
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/RadioGroupFieldTemplate.ts
@@ -22,16 +22,13 @@ import getRequiredCode from
"!!raw-loader!../../resources/staticCode/getRadioGro
import input from "!!raw-loader!../../resources/templates/radioGroup.template";
import setValueFromModel from
"!!raw-loader!../../resources/templates/radioGroup.setModelData.template";
import writeValueToModel from
"!!raw-loader!../../resources/templates/radioGroup.writeModelData.template";
-import {
- AbstractFormGroupInputTemplate,
- FORM_GROUP_TEMPLATE,
- FormElementTemplate,
- FormElementTemplateProps,
-} from "./types";
+import formGroupTemplate from
"!!raw-loader!../../resources/templates/formGroup.template";
+import { FormElementTemplate, FormElementTemplateProps } from
"./AbstractFormGroupTemplate";
import { CompiledTemplate, template } from "underscore";
-import { CodeFragment, FormInput } from "../../api";
-import { fieldNameToOptionalChain, flatFieldName } from "./utils";
+import { FormInput } from "../../api";
+import { fieldNameToOptionalChain, getItemValeuPath } from "./utils";
import { getInputReference } from "../utils/Utils";
+import { DEFAULT_LIST_INDEX_NAME, getCurrentItemSetModelData,
getNormalizedListIdOrName } from "./ListFieldTemplate";
export interface Option {
value: string;
@@ -47,48 +44,49 @@ export class RadioGroupFieldTemplate implements
FormElementTemplate<FormInput, R
private readonly inputTemplate: CompiledTemplate;
private readonly setValueFromModelTemplate: CompiledTemplate;
private readonly writeValueToModelTemplate: CompiledTemplate;
+ private readonly formGroupTemplate: CompiledTemplate;
constructor() {
this.inputTemplate = template(input);
this.setValueFromModelTemplate = template(setValueFromModel);
this.writeValueToModelTemplate = template(writeValueToModel);
+ this.formGroupTemplate = template(formGroupTemplate);
}
render(props: RadioGroupFieldProps): FormInput {
- const data = {
- props: props,
- input: this.inputTemplate({ props: props }),
- };
-
return {
ref: getInputReference(props),
- html: FORM_GROUP_TEMPLATE(data),
+ html: this.formGroupTemplate({
+ id: props.itemProps?.isListItem ? getNormalizedListIdOrName(props.id)
: props.id,
+ label: props.label,
+ input: this.inputTemplate({
+ id: props.itemProps?.isListItem ?
getNormalizedListIdOrName(props.id) : props.id,
+ name: props.itemProps?.isListItem ?
getNormalizedListIdOrName(props.name) : props.name,
+ options: props.options,
+ disabled: props.disabled,
+ }),
+ isListItem: props.itemProps?.isListItem ?? false,
+ }),
disabled: props.disabled,
- setValueFromModelCode: this.buildSetValueFromModelCode(props),
- writeValueToModelCode: this.writeValueToModelCode(props),
- };
- }
-
- protected buildSetValueFromModelCode(props: RadioGroupFieldProps):
CodeFragment {
- const properties = {
- ...props,
- path: fieldNameToOptionalChain(props.name),
- };
-
- return {
- requiredCode: [setRequiredCode],
- code: this.setValueFromModelTemplate(properties),
- };
- }
-
- protected writeValueToModelCode(props: RadioGroupFieldProps): CodeFragment |
undefined {
- if (props.disabled) {
- return undefined;
- }
-
- return {
- requiredCode: [getRequiredCode],
- code: this.writeValueToModelTemplate(props),
+ globalFunctions: undefined,
+ setValueFromModelCode: {
+ requiredCode: [setRequiredCode],
+ code: this.setValueFromModelTemplate({
+ ...props,
+ name: props.itemProps?.isListItem
+ ? getCurrentItemSetModelData(props.name,
props.itemProps?.indexVariableName ?? DEFAULT_LIST_INDEX_NAME)
+ : props.name,
+ path: fieldNameToOptionalChain(props.name),
+ valuePath: props.itemProps?.isListItem ?
getItemValeuPath(props.name) : "",
+ isListItem: props.itemProps?.isListItem ?? false,
+ }),
+ },
+ writeValueToModelCode: props.disabled
+ ? undefined
+ : {
+ requiredCode: [getRequiredCode],
+ code: this.writeValueToModelTemplate(props),
+ },
};
}
}
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/SelectFieldTemplate.ts
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/SelectFieldTemplate.ts
index 9d2e13a2645..325b95dcbbd 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/SelectFieldTemplate.ts
+++
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/SelectFieldTemplate.ts
@@ -23,11 +23,13 @@ import getMultipleRequiredCode from
"!!raw-loader!../../resources/staticCode/get
import input from "!!raw-loader!../../resources/templates/select.template";
import setValueFromModel from
"!!raw-loader!../../resources/templates/select.setModelData.template";
import writeValueToModel from
"!!raw-loader!../../resources/templates/select.writeModelData.template";
-import { FORM_GROUP_TEMPLATE, FormElementTemplate, FormElementTemplateProps }
from "./types";
+import formGroupTemplate from
"!!raw-loader!../../resources/templates/formGroup.template";
+import { FormElementTemplate, FormElementTemplateProps } from
"./AbstractFormGroupTemplate";
import { CompiledTemplate, template } from "underscore";
-import { CodeFragment, FormInput } from "../../api";
-import { fieldNameToOptionalChain } from "./utils";
+import { FormInput } from "../../api";
+import { fieldNameToOptionalChain, getItemValeuPath } from "./utils";
import { getInputReference } from "../utils/Utils";
+import { DEFAULT_LIST_INDEX_NAME, getCurrentItemSetModelData,
getNormalizedListIdOrName } from "./ListFieldTemplate";
export interface Option {
value: string;
@@ -45,47 +47,53 @@ export class SelectFieldTemplate implements
FormElementTemplate<FormInput, Selec
private readonly inputTemplate: CompiledTemplate;
private readonly setValueFromModelTemplate: CompiledTemplate;
private readonly writeValueToModelTemplate: CompiledTemplate;
+ private readonly formGroupTemplate: CompiledTemplate;
constructor() {
this.inputTemplate = template(input);
this.setValueFromModelTemplate = template(setValueFromModel);
this.writeValueToModelTemplate = template(writeValueToModel);
+ this.formGroupTemplate = template(formGroupTemplate);
}
render(props: SelectFieldProps): FormInput {
- const data = {
- props: props,
- input: this.inputTemplate({ props: props }),
- };
-
return {
ref: getInputReference(props),
- html: FORM_GROUP_TEMPLATE(data),
+ html: this.formGroupTemplate({
+ id: props.itemProps?.isListItem ? getNormalizedListIdOrName(props.id)
: props.id,
+ label: props.label,
+ input: this.inputTemplate({
+ id: props.itemProps?.isListItem ?
getNormalizedListIdOrName(props.id) : props.id,
+ name: props.itemProps?.isListItem ?
getNormalizedListIdOrName(props.name) : props.name,
+ placeHolder: props.placeHolder,
+ disabled: props.disabled,
+ multiple: props.multiple,
+ options: props.options,
+ value: props.value,
+ label: props.label,
+ }),
+ isListItem: props.itemProps?.isListItem ?? false,
+ }),
disabled: props.disabled,
- setValueFromModelCode: this.buildSetValueFromModelCode(props),
- writeValueToModelCode: this.writeValueToModelCode(props),
- };
- }
-
- protected buildSetValueFromModelCode(props: SelectFieldProps): CodeFragment {
- const properties = {
- ...props,
- path: fieldNameToOptionalChain(props.name),
- };
- return {
- code: this.setValueFromModelTemplate(properties),
- requiredCode: props.multiple ? [setMultipleRequiredCode] :
[setRequiredCode],
- };
- }
-
- protected writeValueToModelCode(props: SelectFieldProps): CodeFragment |
undefined {
- if (props.disabled) {
- return undefined;
- }
-
- return {
- requiredCode: props.multiple ? [getMultipleRequiredCode] : undefined,
- code: this.writeValueToModelTemplate(props),
+ globalFunctions: undefined,
+ setValueFromModelCode: {
+ code: this.setValueFromModelTemplate({
+ ...props,
+ id: props.itemProps?.isListItem
+ ? getCurrentItemSetModelData(props.id,
props.itemProps?.indexVariableName ?? DEFAULT_LIST_INDEX_NAME)
+ : props.id,
+ path: fieldNameToOptionalChain(props.name),
+ valuePath: props.itemProps?.isListItem ?
getItemValeuPath(props.name) : "",
+ isListItem: props.itemProps?.isListItem ?? false,
+ }),
+ requiredCode: props.multiple ? [setMultipleRequiredCode] :
[setRequiredCode],
+ },
+ writeValueToModelCode: props.disabled
+ ? undefined
+ : {
+ requiredCode: props.multiple ? [getMultipleRequiredCode] :
undefined,
+ code: this.writeValueToModelTemplate(props),
+ },
};
}
}
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/TextFieldTemplate.ts
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/TextFieldTemplate.ts
index 1bafd335e32..90893307e50 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/TextFieldTemplate.ts
+++
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/TextFieldTemplate.ts
@@ -21,7 +21,7 @@ import input from
"!!raw-loader!../../resources/templates/input.template";
import inputBindModelData from
"!!raw-loader!../../resources/templates/input.setModelData.template";
import inputWriteModelData from
"!!raw-loader!../../resources/templates/input.writeModelData.template";
import { template } from "underscore";
-import { AbstractFormGroupInputTemplate, FormElementTemplateProps } from
"./types";
+import { AbstractFormGroupTemplate, FormElementTemplateProps } from
"./AbstractFormGroupTemplate";
interface TextFieldProps extends FormElementTemplateProps<string> {
type: string;
@@ -29,7 +29,7 @@ interface TextFieldProps extends
FormElementTemplateProps<string> {
placeholder: string;
}
-export class TextFieldTemplate extends
AbstractFormGroupInputTemplate<TextFieldProps> {
+export class TextFieldTemplate extends
AbstractFormGroupTemplate<TextFieldProps> {
constructor() {
super(template(input), template(inputBindModelData),
template(inputWriteModelData));
}
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/UnsupportedTemplate.ts
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/UnsupportedTemplate.ts
index 649f053be36..8c34ef6db43 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/UnsupportedTemplate.ts
+++
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/UnsupportedTemplate.ts
@@ -18,9 +18,9 @@
*/
import unsupported from
"!!raw-loader!../../resources/templates/unsupported.template";
-import { FormElementTemplate, FormElementTemplateProps } from "./types";
+import { FormElementTemplate, FormElementTemplateProps } from
"./AbstractFormGroupTemplate";
import { CompiledTemplate, template } from "underscore";
-import { FormElement, FormInput } from "../../api";
+import { FormInput } from "../../api";
import { getInputReference } from "../utils/Utils";
export interface UnsupportedFieldProps extends FormElementTemplateProps<any> {
@@ -35,13 +35,12 @@ export class UnsupportedFieldTemplate implements
FormElementTemplate<FormInput,
}
render(props: UnsupportedFieldProps): FormInput {
- const data = {
- props: props,
- };
-
return {
ref: getInputReference(props),
- html: this.unsupportedTemplate(data),
+ html: this.unsupportedTemplate({ props }),
+ globalFunctions: undefined,
+ setValueFromModelCode: undefined,
+ writeValueToModelCode: undefined,
};
}
}
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/templates.ts
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/templates.ts
index 84e22cd9e0c..ba171142db9 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/templates.ts
+++
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/templates.ts
@@ -17,10 +17,10 @@
* under the License.
*/
-import { CodeGenElement, FormElement } from "../../api";
+import { CodeGenElement } from "../../api";
import { TextFieldTemplate } from "./TextFieldTemplate";
import { NumFieldTemplate } from "./NumFieldTemplate";
-import { CodeGenTemplate, FormElementTemplate } from "./types";
+import { CodeGenTemplate } from "./AbstractFormGroupTemplate";
import { BoolFieldTemplate } from "./BoolFieldTemplate";
import { DateFieldTemplate } from "./DateFieldTemplate";
import { RadioGroupFieldTemplate } from "./RadioGroupFieldTemplate";
@@ -29,12 +29,14 @@ import { SelectFieldTemplate } from "./SelectFieldTemplate";
import { NestFieldTemplate } from "./NestFieldTemplate";
import { AutoFormTemplate } from "./AutoFormTemplate";
import { UnsupportedFieldTemplate } from "./UnsupportedTemplate";
+import { ListFieldTemplate } from "./ListFieldTemplate";
export const FORM: string = "form";
export const CHECKBOX: string = "checkbox";
export const CHECKBOXGROUP: string = "checkboxGroup";
export const DATE: string = "date";
export const INPUT: string = "input";
+export const LIST: string = "listField";
export const NESTED: string = "nestField";
export const NUMBER: string = "number";
export const RADIOGROUP: string = "radioGroup";
@@ -48,6 +50,7 @@ try {
_templates.set(CHECKBOX, new BoolFieldTemplate());
_templates.set(CHECKBOXGROUP, new CheckBoxGroupFieldTemplate());
_templates.set(DATE, new DateFieldTemplate());
+ _templates.set(LIST, new ListFieldTemplate());
_templates.set(NESTED, new NestFieldTemplate());
_templates.set(NUMBER, new NumFieldTemplate());
_templates.set(RADIOGROUP, new RadioGroupFieldTemplate());
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/types.ts
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/types.ts
deleted file mode 100644
index ce2b433e4e5..00000000000
---
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/types.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import formGroupTemplate from
"!!raw-loader!../../resources/templates/formGroup.template";
-import { CodeFragment, CodeGenElement, FormElement, FormInput } from
"../../api";
-import { CompiledTemplate, template } from "underscore";
-import { getInputReference } from "../utils/Utils";
-import { fieldNameToOptionalChain, flatFieldName } from "./utils";
-
-export interface CodeGenTemplate<Element extends CodeGenElement, Properties> {
- render: (props: Properties) => Element;
-}
-
-export interface FormElementTemplateProps<Type> {
- id: string;
- name: string;
- label: string;
- disabled: boolean;
- value: Type;
-}
-
-export interface FormElementTemplate<
- Element extends FormElement<any>,
- Properties extends FormElementTemplateProps<any>,
-> {
- render: (props: Properties) => Element;
-}
-
-export const FORM_GROUP_TEMPLATE: CompiledTemplate =
template(formGroupTemplate);
-
-export abstract class AbstractFormGroupInputTemplate<Properties extends
FormElementTemplateProps<any>>
- implements FormElementTemplate<FormInput, Properties>
-{
- protected constructor(
- readonly inputTemplate: CompiledTemplate,
- readonly setValueFromModelTemplate: CompiledTemplate,
- readonly writeValueToModelTemplate: CompiledTemplate
- ) {}
-
- render(props: Properties): FormInput {
- const data = {
- props: props,
- input: this.inputTemplate({ props: props }),
- };
-
- return {
- ref: getInputReference(props),
- html: FORM_GROUP_TEMPLATE(data),
- disabled: props.disabled,
- setValueFromModelCode: this.buildSetValueFromModelCode(props),
- writeValueToModelCode: this.writeValueToModelCode(props),
- };
- }
-
- protected buildSetValueFromModelCode(props: Properties): CodeFragment {
- const properties = {
- ...props,
- path: fieldNameToOptionalChain(props.name),
- flatFieldName: flatFieldName(props.name),
- };
-
- return {
- code: this.setValueFromModelTemplate(properties),
- };
- }
-
- protected writeValueToModelCode(props: Properties): CodeFragment | undefined
{
- if (props.disabled || !this.writeValueToModelTemplate) {
- return undefined;
- }
-
- const properties = {
- ...props,
- flatFieldName: flatFieldName(props.name),
- };
-
- return {
- code: this.writeValueToModelTemplate(properties),
- };
- }
-}
diff --git
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/utils.ts
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/utils.ts
index 8941c87139e..de1d285e0a4 100644
---
a/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/utils.ts
+++
b/packages/form-code-generator-bootstrap4-theme/src/uniforms/templates/utils.ts
@@ -18,17 +18,20 @@
*/
export function fieldNameToOptionalChain(fieldName: string): string {
- if (!fieldName) {
- return "";
- }
-
- return fieldName.split(".").join("?.");
+ return !fieldName ? "" : fieldName.split(".").join("?.");
}
export function flatFieldName(fieldName: string): string {
- if (!fieldName) {
+ return !fieldName ? "" : fieldName.split(".").join("__");
+}
+
+export function getItemValeuPath(name: string) {
+ if (name.endsWith("$")) {
return "";
}
-
- return fieldName.split(".").join("__");
+ // nested object
+ if (name.includes("$.")) {
+ return `?.${name.split(".").pop()}`;
+ }
+ return "";
}
diff --git
a/packages/form-code-generator-bootstrap4-theme/tests/AutoField.test.tsx
b/packages/form-code-generator-bootstrap4-theme/tests/AutoField.test.tsx
index b04e7d76ea5..08b48604440 100644
--- a/packages/form-code-generator-bootstrap4-theme/tests/AutoField.test.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/tests/AutoField.test.tsx
@@ -58,10 +58,7 @@ const schema = {
type: "date",
},
},
- friends: { type: Array },
- "friends.$": Object,
- "friends.$.name": { type: String },
- "friends.$.age": { type: Number },
+ friends: { type: "foo" },
};
const doRenderField = (fieldName: string) => {
@@ -138,9 +135,9 @@ describe("<AutoField> tests", () => {
it("<UnsupportedField> - rendering", () => {
const { formElement } = doRenderField("friends");
- expect(formElement.html).toContain("Unsupported field type: Array");
+ expect(formElement.html).toContain("Unsupported field type: ");
expect(formElement.html).toContain(
- `Cannot find form control for property <code>friends</code> with type
<code>Array</code>.</p>`
+ `Cannot find form control for property <code>friends</code> with type
<code></code>.</p>`
);
});
});
diff --git
a/packages/form-code-generator-bootstrap4-theme/tests/AutoForm.test.tsx
b/packages/form-code-generator-bootstrap4-theme/tests/AutoForm.test.tsx
index bfd59cd405c..20f3b6ce1a3 100644
--- a/packages/form-code-generator-bootstrap4-theme/tests/AutoForm.test.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/tests/AutoForm.test.tsx
@@ -70,6 +70,8 @@ const schema = {
"friends.$": Object,
"friends.$.name": { type: String },
"friends.$.age": { type: Number },
+ "friends.$.known": { type: Array },
+ "friends.$.known.$": String,
};
const props: AutoFormProps = {
diff --git
a/packages/form-code-generator-bootstrap4-theme/tests/UnsupportedField.test.tsx
b/packages/form-code-generator-bootstrap4-theme/tests/ListField.test.tsx
similarity index 55%
copy from
packages/form-code-generator-bootstrap4-theme/tests/UnsupportedField.test.tsx
copy to packages/form-code-generator-bootstrap4-theme/tests/ListField.test.tsx
index 74e2bd7bd17..f83c48b7eac 100644
---
a/packages/form-code-generator-bootstrap4-theme/tests/UnsupportedField.test.tsx
+++ b/packages/form-code-generator-bootstrap4-theme/tests/ListField.test.tsx
@@ -17,36 +17,49 @@
* under the License.
*/
-import * as React from "react";
import { renderField } from "./_render";
-import { UnsupportedField } from "../src/uniforms";
+import { ListField } from "../src/uniforms";
const schema = {
friends: { type: Array },
"friends.$": Object,
"friends.$.name": { type: String },
"friends.$.age": { type: Number },
+ "friends.$.country": { type: String, allowedValues: ["US", "Brazil"] },
+ "friends.$.married": { type: Boolean },
+ "friends.$.know": {
+ type: Array,
+ uniforms: {
+ checkboxes: true,
+ },
+ },
+ "friends.$.know.$": String,
+ "friends.$.areas": {
+ type: String,
+ allowedValues: ["Developer", "HR", "UX"],
+ },
+ "friends.$.birthday": { type: Date },
};
-describe("<UnsupportedField> tests", () => {
- it("<UnsupportedField> - rendering", () => {
+describe("<ListField> tests", () => {
+ it("<ListField> - rendering enabled", () => {
const props = {
id: "id",
- label: "Friends?",
+ label: "Friends",
name: "friends",
disabled: false,
};
- const { container, formElement } = renderField(UnsupportedField, props,
schema);
+ const { container, formElement } = renderField(ListField, props, schema);
expect(container).toMatchSnapshot();
- expect(formElement.html).toContain("Unsupported field type: Array");
- expect(formElement.html).toContain(
- `Cannot find form control for property <code>${props.name}</code> with
type <code>Array</code>.</p>`
- );
+ expect(formElement.html).toContain('<div role="list" id="friends">');
- expect(formElement.setValueFromModelCode).toBeUndefined();
- expect(formElement.writeValueToModelCode).toBeUndefined();
+ expect(formElement.ref).toHaveLength(7);
+
+ expect(formElement.globalFunctions).not.toBeUndefined();
+ expect(formElement.setValueFromModelCode).not.toBeUndefined();
+ expect(formElement.writeValueToModelCode).not.toBeUndefined();
});
});
diff --git
a/packages/form-code-generator-bootstrap4-theme/tests/UnsupportedField.test.tsx
b/packages/form-code-generator-bootstrap4-theme/tests/UnsupportedField.test.tsx
index 74e2bd7bd17..564222b90d2 100644
---
a/packages/form-code-generator-bootstrap4-theme/tests/UnsupportedField.test.tsx
+++
b/packages/form-code-generator-bootstrap4-theme/tests/UnsupportedField.test.tsx
@@ -22,10 +22,7 @@ import { renderField } from "./_render";
import { UnsupportedField } from "../src/uniforms";
const schema = {
- friends: { type: Array },
- "friends.$": Object,
- "friends.$.name": { type: String },
- "friends.$.age": { type: Number },
+ friends: { type: "foo" },
};
describe("<UnsupportedField> tests", () => {
@@ -34,16 +31,15 @@ describe("<UnsupportedField> tests", () => {
id: "id",
label: "Friends?",
name: "friends",
- disabled: false,
};
const { container, formElement } = renderField(UnsupportedField, props,
schema);
expect(container).toMatchSnapshot();
- expect(formElement.html).toContain("Unsupported field type: Array");
+ expect(formElement.html).toContain("Unsupported field type: ");
expect(formElement.html).toContain(
- `Cannot find form control for property <code>${props.name}</code> with
type <code>Array</code>.</p>`
+ `Cannot find form control for property <code>${props.name}</code> with
type <code></code>.</p>`
);
expect(formElement.setValueFromModelCode).toBeUndefined();
diff --git
a/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/AutoForm.test.tsx.snap
b/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/AutoForm.test.tsx.snap
index 852267f6cfb..99396b3a3d2 100644
---
a/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/AutoForm.test.tsx.snap
+++
b/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/AutoForm.test.tsx.snap
@@ -5,7 +5,7 @@ exports[`<AutoForm> tests <AutoForm> - Full rendering 1`] = `
<div>
<fieldset>
<legend>Personal data</legend>
- <div>
+ <div role="group">
<div class="form-group">
<label
for="personalData.name">Name</label>
<input type="text"
id="personalData.name" name="personalData.name"
class="form-control" value="" />
@@ -18,7 +18,7 @@ exports[`<AutoForm> tests <AutoForm> - Full rendering 1`] = `
</fieldset>
<fieldset>
<legend>Address</legend>
- <div>
+ <div role="group">
<div class="form-group">
<label
for="address.street">Street</label>
<input type="text"
id="address.street" name="address.street"
class="form-control" value="" />
@@ -39,7 +39,7 @@ exports[`<AutoForm> tests <AutoForm> - Full rendering 1`] = `
</fieldset>
<fieldset>
<legend>Interview</legend>
- <div>
+ <div role="group">
<div class="form-group">
<label
for="interview.position">Position</label>
<select class="form-control"
id="interview.position" name="interview.position">
@@ -116,16 +116,251 @@ exports[`<AutoForm> tests <AutoForm> - Full rendering
1`] = `
</div>
</div>
</fieldset>
- <div class="alert alert-warning" role="alert">
- <h4 class="alert-heading">Unsupported field
type: Array</h4>
- <p>Cannot find form control for property
<code>friends</code> with type
<code>Array</code>.</p>
- <hr />
- <p class="mb-0">
- Some complex property types, such as
<code>Array</code> aren't yet supported, however, you can still
write your own component into the
- form and manually bind it.
- </p>
+ <div class="form-group">
+ <div class="card mb-3">
+ <div class="card-body">
+ <div class="card-title">
+ <label
class="col-form-label">Friends</label>
+ <div
+ id="add-item-friends"
+ name="Add Item"
+ class="badge badge-pill
float-right"
+ role="button"
+ tabindex="0"
+
onclick="onAddListItemFriends('friends')"
+
onkeydown="onAddListItemFriends('friends')"
+ >
+ <i className="octicon
octicon-plus"><span>+</span></i>
+ </div>
+ </div>
+ <div role="list"
id="friends"></div>
+ </div>
+ </div>
</div>
<script>
+ // List Field Helper functions -- START --
+ function delListItem(name, minCount, itemIndex, onDelListItem) {
+ const formData = getFormData();
+ const value = accessObjectPath(formData, name) ?? [];
+ if ((minCount ?? 0) <= value.length) {
+
document.getElementById(\`item-\${name}.\${itemIndex}\`).remove();
+ }
+ // Re-organize list
+ Array.from(document.getElementById(name).childNodes ??
[])
+ .filter((node) => node.nodeType === 1)
+ .forEach((element, index) => {
+ const delElement =
element.querySelector(\`[id='remove-item-\${name}']\`);
+ delElement.onclick = function () {
+ onDelListItem(name,
\`\${index}\`);
+ };
+ delElement.onkeydown = function () {
+ onDelListItem(name,
\`\${index}\`);
+ };
+ const inputOrSelect =
element.querySelector("input, select");
+ inputOrSelect.id =
\`\${name}.\${index}\`;
+ inputOrSelect.name =
\`\${name}.\${index}\`;
+ element.id =
\`item-\${name}.\${index}\`;
+ });
+ }
+ function addListItem(name, defaultValue, maxCount,
childrenHtml, functionName) {
+ const formData = getFormData();
+ let value = accessObjectPath(formData, name) ?? [];
+ const itemIndex = value.length;
+ if (maxCount !== undefined && maxCount >
value.length) {
+ value = value.concat([defaultValue]);
+ } else {
+ value = value.concat([defaultValue]);
+ }
+ const listContainer = document.getElementById(name);
+ const newItem = document.createElement("div");
+ newItem.class = "row";
+ newItem.id = \`item-\${name}.\${itemIndex}\`;
+ newItem.innerHTML = \`
+<div class="col-1">
+<span id="remove-item-\${name}" name="Remove Item"
class="badge badge-pill" role="button"
tabindex="0"
onclick="onDelListItem\${functionName}('\${name}',
\${itemIndex})"
onkeydown="onDelListItem\${functionName}('\${name}',
\${itemIndex})">
+ <i className="octicon
octicon-dash"><span>-</span></i>
+</span>
+</div>
+<div class="col-11">
+\${childrenHtml}
+</div>
+\`;
+ listContainer.appendChild(newItem);
+ // Replace all "$" from \`input\` and
\`select\` with the \`itemIndex\`
+ const newItemElement =
document.getElementById(newItem.id);
+
[...newItemElement.querySelectorAll('input[id*="$"],
select[id*="$"]')].forEach((el) => {
+ if (el.id.split(".").pop() !==
"$") {
+ el.id =
\`\${name}.\${itemIndex}.\${el.id.split(".").pop()}\`;
+ el.name =
\`\${name}.\${itemIndex}.\${el.id.split(".").pop()}\`;
+ } else {
+ el.id = \`\${name}.\${itemIndex}\`;
+ el.name = \`\${name}.\${itemIndex}\`;
+ }
+ });
+ // Get the root element of a nested list
+
[...newItemElement.querySelectorAll("div[role='list']")].forEach((nestedListContainer)
=> {
+ if (!nestedListContainer) {
+ return;
+ }
+ // Replace "$" from the root element
if necessary
+ const nestedListPathWithIndex =
splitLastOccurrence(nestedListContainer.id,
"$")?.[1]?.replace("$", \`\${itemIndex}\`);
+ nestedListContainer.id =
nestedListPathWithIndex ? \`\${name}.\${nestedListPathWithIndex}\` : name;
+ // Replace "$" from all nested Add
Item element
+
[...newItemElement.querySelectorAll("[id^=add-item]")].forEach((addItem)
=> {
+ const addItemPathWithIndex =
splitLastOccurrence(addItem.id, "$")?.[1]?.replace("$",
\`\${itemIndex}\`);
+ addItem.id = addItemPathWithIndex ?
\`add-item-\${name}.\${addItemPathWithIndex}\` : \`add-item-\${name}\`;
+ const addItemOnClick =
addItem.getAttribute("onclick");
+ if (addItemOnClick) {
+
addItem.setAttribute("onclick", getFunctionName(addItemOnClick,
itemIndex));
+ }
+ const addItemOnKeydown =
addItem.getAttribute("onkeydown");
+ if (addItemOnKeydown) {
+
addItem.setAttribute("onkeydown", getFunctionName(addItemOnKeydown,
itemIndex));
+ }
+ });
+ // Replace "$" from the Remove Item
element
+
[...newItemElement.querySelectorAll("[id^=remove-item]")].forEach((removeItem)
=> {
+ const removeItemPathWithIndex =
splitLastOccurrence(removeItem.id, "$")?.[1]?.replace("$",
\`\${itemIndex}\`);
+ removeItem.id = removeItemPathWithIndex
? \`remove-item-\${name}.\${removeItemPathWithIndex}\` :
\`remove-item-\${name}\`;
+ const removeItemOnClick =
removeItem.getAttribute("onclick");
+ if (removeItemOnClick) {
+
removeItem.setAttribute("onclick", getFunctionName(removeItemOnClick,
itemIndex));
+ }
+ const removeItemOnKeydown =
removeItem.getAttribute("onkeydown");
+ if (removeItemOnKeydown) {
+
removeItem.setAttribute("onkeydown",
getFunctionName(removeItemOnKeydown, itemIndex));
+ }
+ });
+ });
+ }
+ function getFunctionName(functionCall, itemIndex) {
+ const [call, argument, end] =
functionCall.split("'");
+ const [argumentName, argumentNameToReplace] =
splitLastOccurrence(argument, "$");
+ return
\`\${call}'\${argumentName}\${argumentNameToReplace?.replace("$",
\`\${itemIndex}\`) ?? ""}'\${end}\`;
+ }
+ function splitLastOccurrence(str, char) {
+ const lastIndex = str.lastIndexOf(char);
+ if (lastIndex === -1) {
+ return [str];
+ }
+ return [str.substring(0, lastIndex),
str.substring(lastIndex)];
+ }
+ function getListValue(itemListElement) {
+ function setValue(obj, path, value) {
+ const keys = path.split(".");
+ let current = obj;
+ for (let i = 0; i < keys.length; i++) {
+ const key = keys[i];
+ const isArray = !isNaN(keys[i + 1]);
+ if (i === keys.length - 1) {
+ current[key] = value;
+ } else {
+ if (!current[key]) {
+ current[key] = isArray
? [] : {};
+ }
+ if (isArray &&
!Array.isArray(current[key])) {
+ current[key] = [];
+ }
+ current = current[key];
+ }
+ }
+ }
+ function traverse(element, result, path = "")
{
+ element.querySelectorAll("input,
select").forEach((input) => {
+ if (input.id) {
+ setValue(result, input.id,
input.value || "");
+ }
+ });
+ }
+ const result = {};
+ traverse(itemListElement, result);
+ const [_, path] =
itemListElement.id.split("item-");
+ return accessObjectPath(result, path);
+ }
+ function accessObjectPath(obj, objPath) {
+ return objPath.split(".")?.reduce((acc,
pathPiece) => acc?.[pathPiece], obj);
+ }
+ // List Field Helper functions -- END --
+ function onDelListItemFriends(name, itemIndex) {
+ const disabled = false;
+ if (!disabled) {
+ delListItem(name, undefined, itemIndex,
onDelListItemFriends);
+ }
+ }
+ function onAddListItemFriends(name, initialSetValues) {
+ const disabled = false;
+ if (!disabled || initialSetValues) {
+ addListItem(
+ name,
+ undefined,
+ undefined,
+ \`<fieldset>
+ <legend>Friends</legend>
+ <div role="group">
+ <div class="form-group">
+ <label for="item-\${name}.$.name">Name</label>
+ <input type="text"
+ id="\${name}.$.name"
+ name="\${name}.$.name"
+ class="form-control"
+ value=""/>
+</div>
+ <div class="form-group">
+ <label for="item-\${name}.$.age">Age</label>
+ <input
+ type="number"
+ class="form-control"
+ id="\${name}.$.age"
+ name="\${name}.$.age"
+ step="0.01"
+ value=""/>
+</div>
+ <div class="form-group">
+ <div class="card mb-3">
+ <div class="card-body">
+ <div class="card-title">
+ <label class="col-form-label">Known</label>
+ <div id="add-item-\${name}.$.known" name="Add
Item" class="badge badge-pill float-right"
role="button" tabindex="0"
+
onclick="onAddListItemFriends$Known('\${name}.$.known')"
+
onkeydown="onAddListItemFriends$Known('\${name}.$.known')">
+ <i className="octicon
octicon-plus"><span>+</span></i>
+ </div>
+ </div>
+ <div role="list"
id="\${name}.$.known"></div>
+ </div>
+ </div>
+</div>
+ </div>
+</fieldset>\`,
+ "Friends"
+ );
+ }
+ }
+ function onDelListItemFriends$Known(name, itemIndex) {
+ const disabled = false;
+ if (!disabled) {
+ delListItem(name, undefined, itemIndex,
onDelListItemFriends$Known);
+ }
+ }
+ function onAddListItemFriends$Known(name, initialSetValues) {
+ const disabled = false;
+ if (!disabled || initialSetValues) {
+ addListItem(
+ name,
+ undefined,
+ undefined,
+ \`<div
class="form-group">
+ <label for="item-\${name}.$">Known</label>
+ <input type="text"
+ id="\${name}.$"
+ name="\${name}.$"
+ class="form-control"
+ value=""/>
+</div>\`,
+ "Friends$Known"
+ );
+ }
+ }
/* Utility function that fills the form with the data received
from the kogito runtime */
function setFormData(data) {
if (!data) {
@@ -182,6 +417,19 @@ exports[`<AutoForm> tests <AutoForm> - Full rendering 1`]
= `
document.getElementById("interview.hiringDate").value =
data?.interview?.hiringDate
? new
Date(data?.interview?.hiringDate).toISOString().slice(0, 16)
: "";
+ data?.friends?.forEach((value, index) => {
+ // Add element;
+ onAddListItemFriends(\`friends\`, true);
+ const itemIndex__currentItem =
\`friends.\${index}\`;
+
document.getElementById(\`\${itemIndex__currentItem}.name\`).value =
value?.name ?? "";
+
document.getElementById(\`\${itemIndex__currentItem}.age\`).value = value?.age
?? "";
+ value?.known?.forEach((value, index) => {
+ // Add element;
+
onAddListItemFriends$Known(\`\${itemIndex__currentItem}.known\`, true);
+ const nested__itemIndex__currentItem =
\`\${itemIndex__currentItem}.known.\${index}\`;
+
document.getElementById(\`\${nested__itemIndex__currentItem}\`).value = value
?? "";
+ });
+ });
}
/* Utility function to generate the expected form output as a
json object */
function getFormData() {
@@ -234,6 +482,9 @@ exports[`<AutoForm> tests <AutoForm> - Full rendering 1`] =
`
formData.interview.rating =
getRadioButtonGroupValue("interview.rating");
formData.interview.hire =
document.getElementById("interview.hire").checked;
formData.interview.hiringDate =
document.getElementById("interview.hiringDate").value;
+ formData.friends =
Array.from(document.getElementById("friends").childNodes).reduce((values,
element) => {
+ return [...values, getListValue(element)];
+ }, []);
return formData;
}
/* Utility function to validate the form on the
'beforeSubmit' Lifecycle Hook */
diff --git
a/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/ListField.test.tsx.snap
b/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/ListField.test.tsx.snap
new file mode 100644
index 00000000000..4ff83ec15ab
--- /dev/null
+++
b/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/ListField.test.tsx.snap
@@ -0,0 +1,7 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<ListField> tests <ListField> - rendering enabled 1`] = `
+<div>
+
{"ref":[{"id":"friends.$.name","binding":"friends.$.name"},{"id":"friends.$.age","binding":"friends.$.age"},{"id":"friends.$.country","binding":"friends.$.country"},{"id":"friends.$.married","binding":"friends.$.married"},{"id":"friends.$.know.$","binding":"friends.$.know.$"},{"id":"friends.$.areas","binding":"friends.$.areas"},{"id":"friends.$.birthday","binding":"friends.$.birthday"}],"html":"<div
class=\\"form-group\\">\\n <div class=\\"card mb-3\\">\\n
<div cla [...]
+</div>
+`;
diff --git
a/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/NestField.test.tsx.snap
b/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/NestField.test.tsx.snap
index 3cc26039498..78cbf87194c 100644
---
a/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/NestField.test.tsx.snap
+++
b/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/NestField.test.tsx.snap
@@ -2,12 +2,12 @@
exports[`<NestField> tests <NestField> - rendering 1`] = `
<div>
-
{"ref":[{"id":"candidate.name","binding":"candidate.name"},{"id":"candidate.age","binding":"candidate.age"},{"id":"candidate.role","binding":"candidate.role"}],"html":"<fieldset>\\n
<legend>Candidate</legend>\\n <div>\\n\\n
<div class=\\"form-group\\">\\n <label
for=\\"candidate.name\\">Name</label>\\n <input
type=\\"text\\"\\n id=\\"candidate.name\\"\\n
name=\\"candidate.name\\"\\n class=\\"form-control\\"\\n [...]
+
{"ref":[{"id":"candidate.name","binding":"candidate.name"},{"id":"candidate.age","binding":"candidate.age"},{"id":"candidate.role","binding":"candidate.role"}],"html":"<fieldset>\\n
<legend>Candidate</legend>\\n <div
role=\\"group\\">\\n\\n <div class=\\"form-group\\">\\n
<label for=\\"candidate.name\\">Name</label>\\n <input
type=\\"text\\"\\n id=\\"candidate.name\\"\\n
name=\\"candidate.name\\"\\n class=\\"form- [...]
</div>
`;
exports[`<NestField> tests <NestField> - rendering disabled 1`] = `
<div>
-
{"ref":[{"id":"candidate.name","binding":"candidate.name"},{"id":"candidate.age","binding":"candidate.age"},{"id":"candidate.role","binding":"candidate.role"}],"html":"<fieldset
disabled>\\n <legend>Candidate</legend>\\n
<div>\\n\\n <div class=\\"form-group\\">\\n <label
for=\\"candidate.name\\">Name</label>\\n <input
type=\\"text\\"\\n id=\\"candidate.name\\"\\n
name=\\"candidate.name\\"\\n class=\\"form-control\ [...]
+
{"ref":[{"id":"candidate.name","binding":"candidate.name"},{"id":"candidate.age","binding":"candidate.age"},{"id":"candidate.role","binding":"candidate.role"}],"html":"<fieldset
disabled>\\n <legend>Candidate</legend>\\n <div
role=\\"group\\">\\n\\n <div class=\\"form-group\\">\\n
<label for=\\"candidate.name\\">Name</label>\\n <input
type=\\"text\\"\\n id=\\"candidate.name\\"\\n
name=\\"candidate.name\\"\\n class [...]
</div>
`;
diff --git
a/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/UnsupportedField.test.tsx.snap
b/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/UnsupportedField.test.tsx.snap
index f3df9f01f0c..3380b605af4 100644
---
a/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/UnsupportedField.test.tsx.snap
+++
b/packages/form-code-generator-bootstrap4-theme/tests/__snapshots__/UnsupportedField.test.tsx.snap
@@ -2,6 +2,6 @@
exports[`<UnsupportedField> tests <UnsupportedField> - rendering 1`] = `
<div>
- {"ref":{"id":"id","binding":"friends"},"html":"<div class=\\"alert
alert-warning\\" role=\\"alert\\">\\n <h4
class=\\"alert-heading\\">Unsupported field type: Array</h4>\\n
<p>Cannot find form control for property <code>friends</code>
with type <code>Array</code>.</p>\\n <hr>\\n <p
class=\\"mb-0\\">Some complex property types, such as
<code>Array</code> aren't yet supported, however, you can still
write y [...]
+ {"ref":{"id":"friends","binding":"friends"},"html":"<div class=\\"alert
alert-warning\\" role=\\"alert\\">\\n <h4
class=\\"alert-heading\\">Unsupported field type: </h4>\\n
<p>Cannot find form control for property <code>friends</code>
with type <code></code>.</p>\\n <hr>\\n <p
class=\\"mb-0\\">Some complex property types, such as
<code></code> aren't yet supported, however, you can still write
your own co [...]
</div>
`;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]