Create field repeaters easily, with no dependencies and in less than 30 KiB!
First go to the releases page and download repeater.min.js
and repeater.min.css
.
Add them to your HTML
file:
<!-- Add the stylesheet -->
<link rel="stylesheet" href="<path-to-css-files>/repeater.min.css">
<!-- And the script -->
<script type="text/javascript" src="<path-to-js-files>/repeater.min.js"></script>
Then in your DOMContentLoaded
event create your repeater.
To create a repeater, you will need a schema, check the README for more details on the schema structure.
So for example, with a very simple schema:
const schema = {
collapsed: 'name',
fields: [{
type: 'text',
name: 'name',
label: 'Name',
placeholder: 'Enter a name',
required: true
}, {
type: 'email',
name: 'email',
label: 'Email address',
placeholder: 'Enter an email address',
required: true
}]
};
const repeater = Repeater.create(document.getElementById('repeater'), schema, new Repeater.BootstrapAdapter());
To get the contents of the repeater just call the save
method. You may listen to the repeater.changed
event on the container element to keep it in sync.
The repeater data is serialized as JSON, so that you can easily send it to your backend through a simple textarea
.
This textarea
will be updated with the contents of the above repeater each time something changes:
const schema = {
collapsed: 'name',
fields: [{
type: 'text',
name: 'name',
label: 'Name',
placeholder: 'Enter a name',
required: true
}, {
type: 'email',
name: 'email',
label: 'Email address',
placeholder: 'Enter an email address',
required: true
}]
};
const container = document.getElementById('repeater');
const textarea = document.getElementById('textarea');
const repeater = Repeater.create(container, schema, new Repeater.BootstrapAdapter());
container.addEventListener('repeater.changed', () => {
textarea.value = repeater.save();
});
Populating the repeater with data from your backend (or localStorage, who knows!) is equally easy.
Just call the load
method with the data you want to load:
This is the format of the data that gets loaded:
[
{"_collapsed":true,"name":"John Doe","email":"john.doe@example.com"},
{"_collapsed":false,"name":"Jane Doe","email":"jane.doe@example.com"}
]
const schema = {
collapsed: 'name',
fields: [{
type: 'text',
name: 'name',
label: 'Name',
placeholder: 'Enter a name',
required: true
}, {
type: 'email',
name: 'email',
label: 'Email address',
placeholder: 'Enter an email address',
required: true
}]
};
const button = document.getElementById('load-data');
const repeater = Repeater.create(document.getElementById('repeater'), schema, new Repeater.BootstrapAdapter());
button.addEventListener('click', (e) => {
e.preventDefault();
repeater.load('[{"_collapsed":false,"name":"John Doe","email":"john.doe@example.com"},{"_collapsed":false,"name":"Jane Doe","email":"jane.doe@example.com"}]');
});
This is the kitchen-sink example, with all the available fields, some nested repeaters and multi-column fields.
const schema = {
collapsed: 'title',
fields: [{
type: 'text',
name: 'title',
label: 'Title',
placeholder: 'Enter a title',
required: true,
}, {
type: 'email',
name: 'email',
label: 'Contact email',
placeholder: 'Enter an email',
layout: {
column: 6
}
}, {
type: 'phone',
name: 'mobile',
label: 'Mobile phone',
placeholder: 'Enter a mobile phone number',
layout: {
column: 6
}
}, {
type: 'url',
name: 'link',
label: 'Link',
placeholder: 'Enter a link',
layout: {
newRow: true,
column: 6
}
}, {
type: 'date',
name: 'dob',
label: 'Date of birth',
layout: {
newRow: true,
column: 4
}
}, {
type: 'time',
name: 'sunrise',
label: 'Sunrise time',
layout: {
column: 4
}
}, {
type: 'datetime',
name: 'appointment',
label: 'Appointment date',
layout: {
column: 4
}
}, {
type: 'color',
name: 'bg_color',
label: 'Background color',
}, {
type: 'textarea',
name: 'description',
label: 'Description',
placeholder: 'Enter a description',
maxlength: 150,
}, {
type: 'password',
name: 'token',
label: 'API token',
placeholder: 'Enter your API token',
layout: {
column: 9
}
}, {
type: 'number',
name: 'quantity',
label: 'Quantity',
min: 1,
max: 10,
value: 1,
layout: {
column: 3
}
}, {
type: 'range',
name: 'columns',
label: 'Columns',
min: 1,
max: 12,
value: 6,
}, {
type: 'select',
name: 'status',
label: 'Status',
default: 'draft',
options: [
{'draft': 'Draft'},
{'published': 'Published'},
]
}, {
type: 'radio',
name: 'category',
label: 'Category',
options: [
{'news': 'News'},
{'events': 'Events'},
]
}, {
type: 'checkbox',
name: 'tags',
label: 'Tags',
options: [
{'cars': 'Cars'},
{'tech': 'Tech'},
{'medicine': 'Medicine'},
{'gadgets': 'Gadgets'},
]
}, {
type: 'repeater',
name: 'faqs',
label: 'FAQs',
schema: {
item: 'Question',
button: 'Add question',
collapsed: 'question',
fields: [{
type: 'text',
name: 'question',
label: 'Question',
required: true,
}, {
type: 'textarea',
name: 'answer',
label: 'Answer',
required: true,
}, {
type: 'repeater',
name: 'buttons',
label: 'Buttons',
schema: {
item: 'Button',
button: 'Add button',
collapsed: 'text',
fields: [{
type: 'text',
name: 'text',
label: 'Text',
required: true,
layout: {
column: 7
}
}, {
type: 'select',
name: 'type',
label: 'Type',
options: [
{ 'btn-primary': 'Primary' },
{ 'btn-secondary': 'Secondary' },
{ 'btn-dark': 'Dark' },
{ 'btn-light': 'Light' },
{ 'btn-info': 'Info' },
{ 'btn-success': 'Success' },
{ 'btn-warning': 'Warning' },
{ 'btn-danger': 'Danger' },
],
required: true,
layout: {
column: 5
}
}, {
type: 'toggle',
name: 'outline',
label: 'Outline',
details: 'Use outlined button style'
}, {
type: 'url',
name: 'link',
label: 'Link',
required: true,
}]
}
}]
}
}]
};
const repeater = Repeater.create(document.getElementById('repeater'), schema, new Repeater.BootstrapAdapter());
This is the conditionals example, where fields may have extra options; try changing the field types or adding new fields and behold the flexibility of repeater-js!
const schema = {
item: 'Field',
button: 'Add field',
collapsed: 'name',
fields: [{
type: 'text',
name: 'name',
label: 'Field name',
required: true,
layout: {
column: 7
}
}, {
type: 'select',
name: 'type',
label: 'Field type',
options: [
{ 'text': 'Single-line text' },
{ 'textarea': 'Multi-line text' },
{ 'select': 'Selection box' },
{ 'checkbox': 'Checkbox group' },
{ 'radio': 'Radio-button group' },
],
required: true,
layout: {
column: 5
}
}, {
type: 'number',
name: 'maxlength',
label: 'Maximum length',
min: 0,
max: 260,
conditional: {
'field': 'type',
'type': 'equal',
'value': 'textarea',
}
}, {
type: 'repeater',
name: 'options',
label: 'Options',
schema: {
item: 'Option',
button: 'Add option',
collapsed: 'label',
fields: [{
type: 'text',
name: 'label',
label: 'Label',
required: true,
transform: {
type: 'slug',
target: 'value'
},
layout: {
column: 7
}
},{
type: 'text',
name: 'value',
label: 'Value',
required: true,
layout: {
column: 5
}
}]
},
conditional: {
'field': 'type',
'type': 'matches',
'value': 'select|checkbox|radio',
}
}, {
type: 'number',
name: 'minimum',
label: 'Minimum selection',
min: 0,
max: 4,
conditional: {
field: 'type',
type: 'equal',
value: 'checkbox'
},
layout: {
column: 6
}
}, {
type: 'number',
name: 'maximum',
label: 'Maximum selection',
min: 0,
max: 4,
conditional: {
field: 'type',
type: 'equal',
value: 'checkbox'
},
layout: {
column: 6
}
}, {
type: 'toggle',
name: 'default',
details: 'This field has a default value',
conditional: {
'field': 'type',
'type': 'matches',
'value': 'text|textarea|select|radio',
}
}, {
type: 'text',
name: 'value',
label: 'Default value',
conditional: {
'field': 'default',
'type': 'equal',
'value': true,
}
}, {
type: 'toggle',
name: 'required',
details: 'This field is required',
checked: true
}]
};
const repeater = Repeater.create(document.getElementById('repeater'), schema, new Repeater.BootstrapAdapter());
In this last example we leverage two advanced functions: dynamic schema/options and field syncing.
const schema = {
collapsed: 'name',
fields: [{
type: 'text',
name: 'name',
label: 'Name',
transform: {
type: 'slug',
target: 'slug'
},
layout: {
column: 6
},
required: true
}, {
type: 'text',
name: 'slug',
label: 'Slug',
layout: {
column: 6
},
required: true
}, {
type: 'select',
name: 'type',
label: 'Type',
options: (field) => {
const nesting = field.item.repeater.nestingLevel ?? 0;
const ret = [
{ text: 'Single-line text' },
{ textarea: 'Multi-line text' },
{ select: 'Selection box' },
{ repeater: 'Repeater' }
];
if (nesting === 2) {
ret.pop();
}
return ret;
},
required: true
}, {
type: 'number',
name: 'maxlength',
label: 'Maximum length',
min: 0,
max: 150,
step: 10,
conditional: {
type: 'equal',
field: 'type',
value: 'textarea'
}
}, {
type: 'select',
name: 'collapsed',
label: 'Collapsed',
sync: {
field: 'schema',
callback: (field, other, items) => {
if (items !== null) {
field.select.innerHTML = '';
items.forEach(item => {
if (item.name && item.slug && item.type === 'text') {
field.select.insertAdjacentHTML('beforeend', `<option value="${item.slug}">${item.name}</option>`);
}
});
}
field.select.disabled = other.nestedRepeater.items.length === 0;
}
},
options: [],
conditional: {
type: 'equal',
field: 'type',
value: 'repeater'
}
}, {
type: 'repeater',
name: 'options',
label: 'Options',
schema: {
item: 'Option',
button: 'Add option',
collapsed: 'label',
fields: [{
type: 'text',
name: 'label',
label: 'Label',
transform: {
type: 'slug',
target: 'value'
},
layout: {
column: 6
}
}, {
type: 'text',
name: 'value',
label: 'Value',
layout: {
column: 6
}
}]
},
conditional: {
type: 'equal',
field: 'type',
value: 'select'
}
}, {
type: 'repeater',
name: 'schema',
label: 'Schema',
schema: (repeater) => {
const nesting = repeater.nestingLevel ?? 0;
if (nesting === 2) {
schema.fields = schema.fields.filter((field) => field.type !== 'repeater');
}
return schema;
},
conditional: {
type: 'equal',
field: 'type',
value: 'repeater'
}
}]
};
const repeater = Repeater.create(document.getElementById('repeater'), schema, new Repeater.BootstrapAdapter());
Please see the README for more details on how to contibute to the project or build the library.
This software is released under the MIT license.
Lead coder: biohzrdmx <github.com/biohzrdmx>