Logotype Sitevision Developer
Log in
Log in

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.
config.js
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); }; })();
index.html
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>
config.css
css
.additionalRepeaterField { display: flex; } .removeRepeaterField { margin-left: 1em; }

Once added repeated fields may be accessed using appData.

index.js
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?