Skip to content

Instantly share code, notes, and snippets.

@thedanbob
Created March 5, 2019 13:52
Show Gist options
  • Save thedanbob/b48a9e20b57341ced21fdd9e2e0609b9 to your computer and use it in GitHub Desktop.
Save thedanbob/b48a9e20b57341ced21fdd9e2e0609b9 to your computer and use it in GitHub Desktop.
Cocoon rewrite in ES6 (WIP)
class Cocoon {
constructor(container, options) {
this.container = this.getNodeFromOption(container, false)
if (!this.container) {
throw new TypeError('Container must be supplied')
}
this.addFieldsLink = this.getNodeFromOption(options.addFieldsLink, container.querySelector('.add_fields'))
if (!this.addFieldsLink)
console.warn("Couldn't find the link to add fields. Make sure your `link_to_add_association` is correct.")
this.insertionNode = this.getNodeFromOption(options.insertionNode || this.addFieldsLink.getAttribute('data-association-insertion-node'), this.addFieldsLink.parentNode)
if (!this.insertionNode)
console.warn("Couldn't find the element to insert the template. Make sure your `insertionNode` option is correct.")
this.insertionFunc = option.insertionFunc || function(refNode, content) {
refNode.insertAdjacentHTML('beforebegin', content)
return refNode.previousSibling
}
this.beforeInsert = option.beforeInsert
this.afterInsert = option.afterInsert
this.beforeRemove = option.beforeRemove
this.afterRemove = option.afterRemove
this.elementCount = 0
document.addEventListener('click', (e) => {
if (e.target.classList.includes('add_fields'))
this.addFields(e)
else if (e.target.classList.includes('remove_fields'))
this.removeFields(e)
})
var removeFunc = function() {
[...document.querySelectorAll('.remove_fields.existing.destroyed')].forEach(function(el) {
var wrapperClass = el.getAttribute('data-wrapper-class') || 'nested-fields'
var removed = el.parentNode
while (!removed.classList.includes(wrapperClass)) {
removed = removed.parentNode
}
removed.style.display = 'none'
})
}
document.addEventListener('DOMContentLoaded', removeFunc)
document.addEventListener('turbolinks:load', removeFund)
}
getNodeFromOption(option, defaultOpt) {
if (!option) return defaultOpt
if (typeof option == 'string')
return document.querySelector(option)
else
return option
}
createNewId() {
return new Date().getTime() + this.elementCount++
}
newIdBraced(id) {
return `[${id}]$1`
}
newIdUnderscored(id) {
return `_${id}_$1`
}
addFields(e) {
e.preventDefault()
var link = e.target,
assoc = link.getAttribute('data-association'),
assocs = link.getAttribute('data-associations'),
content = link.getAttribute('data-association-insertion-template'),
count = parseInt(link.getAttribute('data-count'), 10),
regexpBraced = new RegExp(`\\[new_${assoc}\\](.*?\\s)`, 'g'),
regexpUnderscored = new RegExp(`_new_${assoc}_(\\w*)`, 'g'),
newContents = []
if (!regexpBraced.test(content)) {
regexpBraced = new RegExp(`\\[new_${assocs}\\](.*?\\s)`, 'g')
regexpUnderscored = new RegExp(`_new_${assocs}_(\\w*)`, 'g')
}
count = isNaN(count) ? 1 : Math.max(count, 1)
var newContent
while (count) {
newId = this.createNewId()
newContent = content.replace(regexpBraced, this.newcontentBraced(newId));
newContent = newContent.replace(regexpUnderscored, this.newcontentUnderscored(newId));
newContents.push(newContent);
count -= 1;
}
var insertionNodeElem = this.insertionFunc(insertionNode, insertionTraversal, $this)
newContents.forEach((html) => {
if (typeof this.beforeInsert == 'function')
html = this.beforeInsert(html)
if (!html)
return
var newNode = this.insertionFunc(this.insertionNode, html)
if (typeof this.afterInsert == 'function')
this.afterInsert(newNode)
})
}
removeFields = function(e) {
var removeLink = e.target
var toRemove = removeLink.parentNode
var wrapperClass = removeLink.getAttribute('data-wrapper-class') || 'nested-fields'
while (!toRemove.classList.includes(wrapperClass)) {
toRemove = toRemove.parentNode
if (!toRemove) {
throw new Error('Cannot find element to remove, please check `data-wrapper-class` on `link_to_remove_association`')
}
}
e.preventDefault()
e.stopPropagation()
var container = toRemove.parentNode
if (typeof this.beforeRemove == 'function')
toRemove = this.beforeRemove(toRemove)
if (!toRemove)
return
var timeout = parseInt(container.getAttribute('data-remove-timeout'))
if (isNaN(timeout)) timeout = 0
setTimeout(() => {
if (removeLink.classList.includes('dynamic'))
container.removeChild(toRemove)
else {
var input = toRemove.previousSibling
if (input.matches('input[type="hidden"]'))
input.value = '1'
toDelete.style.display = 'none'
}
if (typeof this.afterRemove == 'function')
this.afterRemove(toDelete)
}, timeout)
}
}
export default Cocoon
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment