Repeater fields
Repeater fields allow editors to dynamically add or remove multiple values for the same input field. Instead of defining a fixed number of fields, a repeater lets users create as many entries as needed directly in the form interface.
This example demonstrates how to implement repeater fields. By following this pattern, your app can:
- Dynamically render and manage input fields.
- Save and restore repeating values via appData.
- Keep the first field static while allowing additional fields to be removed.
js
(() => {
/**
* Initialize the repeater fields once the configuration framework is ready.
*/
document.documentElement.addEventListener('initialized', () => {
initRepeaterFields();
});
/**
* Creates and returns a repeater field element.
*/
function createRepeaterField(value = '', isFirst = false) {
const wrapper = document.createElement('div');
wrapper.className = isFirst
? 'form-group'
: 'form-group additionalRepeaterField';
const input = document.createElement('input');
input.className = 'form-control';
input.type = 'text';
input.value = value;
wrapper.appendChild(input);
if (!isFirst) {
const button = document.createElement('button');
button.type = 'button';
button.className = 'btn removeRepeaterField';
button.textContent = 'Remove';
wrapper.appendChild(button);
}
return wrapper;
}
/**
* Initializes the repeater field UI.
* Sets up add/remove behavior and restores existing values.
*/
function initRepeaterFields() {
const container = document.getElementById('repeaterFieldsContainer');
const addButton = document.getElementById('add-repeater-field');
if (!container || !addButton) {
return;
}
// Handle removing fields (event delegation)
container.addEventListener('click', (e) => {
if (e.target.classList.contains('removeRepeaterField')) {
e.preventDefault();
const field = e.target.closest('.form-group');
if (field) {
field.remove();
}
}
});
// Handle adding new fields
addButton.addEventListener('click', (e) => {
e.preventDefault();
const fieldEl = createRepeaterField('', false);
container.appendChild(fieldEl);
const input = fieldEl.querySelector('input');
if (input) {
input.focus();
}
});
// Initialize with existing values from window.repeaterFields
const values = Array.isArray(window.repeaterFields)
? window.repeaterFields
: [''];
values.forEach((val, i) => {
const fieldEl = createRepeaterField(val, i === 0);
container.appendChild(fieldEl);
});
}
/**
* Reads all form values and includes repeater field data.
* Called automatically by the configuration framework.
*/
window.getValues = function () {
const inputs = document.querySelectorAll('#repeaterFieldsContainer input');
const repeaterFields = Array.from(inputs)
.map((input) => input.value.trim())
.filter((v) => v.length > 0);
return {
...window._getValues(),
repeaterFields,
};
};
/**
* Sets initial form values and makes repeater fields available globally.
* Called automatically by the configuration framework.
*/
window.setValues = function (values) {
let repeaterFields = [''];
if (
values &&
Array.isArray(values.repeaterFields) &&
values.repeaterFields.length > 0
) {
repeaterFields = values.repeaterFields;
}
window.repeaterFields = repeaterFields;
return window._setValues(values);
};
})();
html
<div class="form-group">
<label><%= i18n('repeaterFields') %></label>
<div id="repeaterFieldsContainer"></div>
<p class="help-block info-message">
<%= i18n('repeaterFieldsHelp') %>
</p>
<button id="add-repeater-field" class="btn btn-primary">
<%= i18n('addRepeaterField') %>
</button>
</div>
css
.additionalRepeaterField {
display: flex;
}
.removeRepeaterField {
margin-left: 1em;
}
Once added repeated fields may be accessed using appData.
js
import appData from '@sitevision/api/server/appData';
import router from '@sitevision/api/common/router';
router.get('/', (req, res) => {
// array of strings from fields
const repeaterFields = appData.get('repeaterFields');
...
});
For an additional example containing initialization of Sitevision components dynamically, check our github.
Did you find the content on this page useful?