-
-
Save Mirv/ee6b7bee14578fc20f489a316d34e97a to your computer and use it in GitHub Desktop.
Cocoon rewrite in ES6 (WIP)
This file contains 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
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