Skip to content

Instantly share code, notes, and snippets.

@Mirv
Forked from thedanbob/cocoon.js
Last active March 5, 2019 23:16
Show Gist options
  • Save Mirv/ee6b7bee14578fc20f489a316d34e97a to your computer and use it in GitHub Desktop.
Save Mirv/ee6b7bee14578fc20f489a316d34e97a 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)
var insertANode = options.insertionNode ? options.insertionNode : this.addFieldsLink.getAttribute('data-association-insertion-node')
this.insertionNode = this.getNodeFromOption(insertANode, 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 = options.insertionFunc || function (refNode, content) {
refNode.insertAdjacentHTML('beforebegin', content)
return refNode.previousSibling
}
this.beforeInsert = options.beforeInsert
this.afterInsert = options.afterInsert
this.beforeRemove = options.beforeRemove
this.afterRemove = options.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 = findWrapperClass(el)
var removed = el.parentNode
while (!removed.classList.includes(wrapperClass)) {
removed = removed.parentNode
}
removed.style.display = 'none'
})
}
document.addEventListener('DOMContentLoaded', removeFunc)
document.addEventListener('turbolinks:load', removeFunc)
}
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`
}
findWrapperClass (el) {
return el.getAttribute('data-wrapper-class') || 'nested-fields'
}
// Format association to add text
// var regexp_bracer = function (targetAssociation) {
regexp_bracer (targetAssociation) {
return new RegExp('\\[new_' + targetAssociation + '\\](.*?\\s)', 'g')
}
// Format association to add text
regexp_underscorer (targetAssociation) {
return new RegExp('_new_' + targetAssociation + '_(\\w*)', 'g')
}
addFields (e) {
e.preventDefault()
var link = e.target
var newContents = []
var assoc = link.getAttribute('data-association')
var assocs = link.getAttribute('data-associations')
var content = link.getAttribute('data-association-insertion-template')
var count = parseInt(link.getAttribute('data-count'), 10)
var newContents = associationIdBuilder(count, assoc, assocs, content)
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) }
})
}
// Return array of regex wrappers assocation ids & loop through all the id(s)
associationIdBuilder (count, assoc, assocs, content) {
var regexp_braced = regexp_bracer(assoc)
var regexp_underscored = regexp_underscorer(assoc)
var newId = createNewId()
var newContents = []
var newContent = content.replace(regexp_braced, newcontent_braced(newId))
// Toggle over to multiple associations if found
if (!regexpBraced.test(content)) {
regexp_braced = regexp_bracer(assocs)
regexp_underscored = regexp_underscorer(assocs)
// Your version doesn't execute here and I don't have tests to tell if necessary in edge cases or not
// newContent = content.replace(regexp_braced, newcontent_braced(newId));
}
// Your version doesn't execute here and I don't have tests to tell if necessary in edge cases or not
// newContent = newContent.replace(regexp_underscored, newcontent_underscored(newId));
// newContents = [newContent]
count = isNaN(count) ? 1 : Math.max(count, 1)
count -= 1
// If count over 0, we know to keep adding on rest of names with new ids
while (count) {
newId = this.createNewId()
newContent = content.replace(regexp_braced, this.newcontent_braced(newId))
newContent = newContent.replace(regexp_underscored, this.newcontent_underscored(newId))
newContents.push(newContent)
count -= 1
}
return newContents
}
removeFields (e) {
var removeLink = e.target
var toRemove = removeLink.parentNode
var wrapperClass = findWrapperClass(removeLink)
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)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment