Skip to content

Instantly share code, notes, and snippets.

@jaz303
Last active May 4, 2020 11:36
Show Gist options
  • Save jaz303/a415342b6e3eb06b3654b0f9d3e08e06 to your computer and use it in GitHub Desktop.
Save jaz303/a415342b6e3eb06b3654b0f9d3e08e06 to your computer and use it in GitHub Desktop.
// Simple jQuery helper for coordinating a dynamic list of nested
// form items, compatible with Rails' nested attributes.
//
// Supports deletion of saved items.
//
// HTML for existing list items should be pre-generated by the
// template (see template.html.erb). It is assumed the last
// item will be an empty "template" row; this can be added
// in the controller with `@model.association.new`
//
// Hook classes:
// x-empty: element to show when list is empty
// x-add: button to click to add an item
// x-list: container for list items
// x-item: outermost wrapper element for each item
// x-delete: delete button (must be inside .x-item)
//
function repeater($root) {
const RepeaterIndex = /^(\w+\[\w+\])\[(\d+)\]/;
const ui = {
root: $root,
empty: $root.find('.x-empty'),
add: $root.find('.x-add'),
list: $root.find('.x-list'),
items: $root.find('.x-item'),
};
const template = ui.items.last().clone();
let nextIndex = ui.items.length;
let count = ui.items.length;
countChanged(0);
ui.add.on('click', function(evt) {
generateNewItem().appendTo(ui.list);
countChanged(1);
});
ui.root.on('click', '.x-delete', function() {
const item = $(this).closest('.x-item');
item.append(makeDeletedField(item)).hide();
countChanged(-1);
});
function makeDeletedField(item) {
const named = item.find('[name]');
for (let i = 0; i < named.length; ++i) {
if (named[i].name.match(RepeaterIndex)) {
return `<input type="hidden" name="${RegExp.$1 + '[' + RegExp.$2 + ']'}[_destroy]" value="1"/>`;
}
}
throw new Error("couldn't make deleted field");
}
function generateNewItem() {
const index = nextIndex++;
const item = template.clone();
item.find('[name]').each(function() {
this.name = this.name.replace(RepeaterIndex, (_, baseName) => baseName + "[" + index + "]");
});
return item;
}
function countChanged(delta) {
count += delta;
ui.empty[count < 1 ? 'show' : 'hide']();
}
}
<div class="field">
<label class="label">Files</label>
<div class="field-body repeater">
<div class='x-empty' style='display:none'>Select some files to upload!</div>
<button class='x-add' type='button'>Add</button>
<div class='x-list'>
<%= f.fields_for :files do |b| %>
<div class='x-item'>
<%= b.select :file_type, UploadFile::FILE_TYPES %>
<%= b.file_field :file_data %>
<button type='button' class='x-delete'>Delete</button>
</div>
<% end %>
</div>
</div>
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment