Created
November 10, 2020 10:57
-
-
Save monorkin/1ff71aaf886b386d68486b64fb984fcc to your computer and use it in GitHub Desktop.
Stimulus has_many fields_for controller
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
= f.simple_fields_for :item do |ff| | |
.row.mt-4 | |
.col-12 | |
label Categories | |
.preview_images data-controller="has-many-fields-for" | |
.preview_images__container data-target="has-many-fields-for.container" | |
= ff.simple_fields_for :taggings do |ff| | |
= render 'category_tagging_form', f: ff | |
script type="text/html" data-target="has-many-fields-for.template" data-index-placeholder="new_tagging" | |
= ff.simple_fields_for :taggings, ff.object.taggings.build, child_index: 'new_tagging' do |ff| | |
= render 'category_tagging_form', f: ff | |
.preview_images__actions | |
.btn.btn-block.btn-secondary data-action="click->has-many-fields-for#add" | |
i.fas.fa-plus | |
| Add new category |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Controller } from "stimulus" | |
export default class extends Controller { | |
static targets = ["template", "container"] | |
connect() { | |
if (!this.hasContainerTarget) { | |
return | |
} | |
if (!this.hasTemplateTarget && !this.element.dataset.template) { | |
return | |
} | |
this.childElementClass = | |
`hmff-ele-${Math.random().toString(16).substring(7)}` | |
this.template = this.element.dataset.template || | |
this.templateTarget.innerHTML | |
this.placeholder = this.element.dataset.indexPlaceholder || | |
this.templateTarget.dataset.indexPlaceholder | |
if (this.hasTemplateTarget) { | |
this.templateTarget.remove() | |
} | |
this.normalizeExistingChildren() | |
this.reindexElements() | |
} | |
add(event) { | |
event.preventDefault() | |
if (!this.template) { | |
return | |
} | |
this.addElement() | |
} | |
remove(event) { | |
let wrapper = this.closestWrapper(event.target) | |
if (!wrapper) { | |
return | |
} | |
var destroyFlagInput = null | |
wrapper.querySelectorAll('input').forEach(function(element) { | |
if (destroyFlagInput) { | |
return | |
} | |
let name = element.getAttribute('name') | |
if (name && name.endsWith('[_destroy]')) { | |
destroyFlagInput = element | |
return | |
} | |
}) | |
if (!destroyFlagInput) { | |
wrapper.remove() | |
return | |
} | |
destroyFlagInput.setAttribute('value', 'true') | |
wrapper.style.display = 'none' | |
} | |
moveUp(event) { | |
let wrapper = this.closestWrapper(event.target) | |
if (!wrapper) { | |
return | |
} | |
if(!wrapper.previousElementSibling) { | |
return | |
} | |
wrapper.parentNode.insertBefore(wrapper, wrapper.previousElementSibling) | |
this.reindexElements() | |
} | |
moveDown(event) { | |
let wrapper = this.closestWrapper(event.target) | |
if (!wrapper) { | |
return | |
} | |
if(!wrapper.nextElementSibling) { | |
return | |
} | |
wrapper.parentNode.insertBefore(wrapper.nextElementSibling, wrapper) | |
this.reindexElements() | |
} | |
reindexElements() { | |
let children = this.containerTarget.querySelectorAll('input') | |
for(let i = 0; i < children.length; i++) { | |
let child = children.item(i) | |
if (!this.isPositionInput(child)) { | |
continue | |
} | |
child.setAttribute('value', i) | |
} | |
} | |
closestWrapper(element) { | |
return element.closest(`.${this.childElementClass}`) | |
} | |
normalizeExistingChildren() { | |
let children = this.containerTarget.children | |
for(let i = 0; i < children.length; i++) { | |
let child = children.item(i) | |
if (this.isIdInput(child)) { | |
continue | |
} | |
child.classList.add(this.childElementClass) | |
if (!this.isIdInput(child.nextElementSibling)) { | |
continue | |
} | |
child.appendChild(child.nextElementSibling) | |
} | |
} | |
isIdInput(element) { | |
return this.isInputFor(element, 'id') | |
} | |
isPositionInput(element) { | |
return this.isInputFor(element, 'position') | |
} | |
isInputFor(element, attrName) { | |
if (!element || !attrName) { | |
return false | |
} | |
const name = element.getAttribute('name') | |
return element.tagName.toLowerCase() === "input" && | |
name && name.endsWith(`[${attrName}]`) | |
} | |
addElement() { | |
let element = document.createElement('div') | |
var template = this.template | |
if (this.placeholder) { | |
template = template.replaceAll(`[${this.placeholder}]`, `[${(new Date).getTime()}]`) | |
} | |
element.innerHTML = template | |
if (!element.firstChild) { | |
element.remove() | |
return | |
} | |
element.firstChild.classList.add(this.childElementClass) | |
this.containerTarget.appendChild(element.firstChild) | |
element.remove() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment