punchcard-content-types
Version:
Combines with input plugins to create forms with validation. Creates forms for Punchcard CMS.
374 lines (323 loc) • 12.8 kB
JavaScript
/* eslint-env browser */
/* global allPlugins, allInputs, allSettings, allIDs, allRepeatables, validate */
(function formRepeatable() {
'use strict';
// Splits the string and pops the last element
var splitPop = function splitPop(input, splitter) {
var blocks = input.split(splitter);
blocks.pop();
blocks = blocks.join(splitter);
return blocks;
},
updateIndex = function updateIndex(input, index) {
var inputString = input;
inputString = inputString.split('--');
if (inputString.length > 1) {
inputString[1] = index;
}
else {
inputString.push(index);
}
return inputString.join('--');
},
// Removes add button
setAdd = function setAdd(node) {
var childNodes = node.querySelectorAll('.form--repeatable'),
element = document.createElement('button');
element.type = 'button';
element.textContent = 'Add';
element.id = node.id + '--add';
element.setAttribute('class', 'form--add');
// rule disable as adding 'add' button is required in deleteHandler and addHandler requires to add 'Delete' button
element.addEventListener('click', addHandler); // eslint-disable-line no-use-before-define
node.insertBefore(element, childNodes[childNodes.length - 1].nextSibling);
},
// update index of id to 'index'
updateId = function updateId(id, index) {
var splitId = id.split('--'),
num = splitId[1];
if (!isNaN(num)) {
if (index === undefined) {
return splitPop(id, '--');
}
splitId[1] = index;
return splitId.join('--');
}
splitId.push(index);
return splitId.join('--');
},
// Update Siblings in allInputs
updateSiblings = function updateSiblings(id, index) {
Object.keys(allInputs[id].siblings).forEach(function siblings(key) {
allInputs[id].siblings[key] = updateId(allInputs[id].siblings[key], index);
});
},
// Maintains state of allIds and allInputs
updateIdsAndInputs = function updateIdsAndInputs(id, oldId, index) {
if (allInputs.hasOwnProperty(oldId)) {
allInputs[id] = allInputs[oldId];
updateSiblings(id, index);
delete allInputs[oldId];
}
if (allIDs.hasOwnProperty(oldId)) {
allIDs[id] = allIDs[oldId];
delete allIDs[oldId];
}
},
// decrement index of htmlfor by 1
decrementHtmlFor = function decrementHtmlFor(node) {
var child = node,
splitChild = child.getAttribute('for').split('--'),
index = splitChild[1];
if (!isNaN(index)) {
splitChild[1] = index - 1;
child.setAttribute('for', splitChild.join('--'));
}
},
// decrement index of id by 1 and calls updateIdsAndInputs
decrementId = function decrementId(node) {
var child = node,
splitChild = child.id.split('--'),
index = splitChild[1],
oldChildId = child.id;
if (!isNaN(index)) {
splitChild[1] = index - 1;
child.id = splitChild.join('--');
updateIdsAndInputs(child.id, oldChildId, index - 1);
}
},
// decrement index of name by 1
decrementName = function decrementName(node) {
var child = node,
childName = child.getAttribute('name');
if (childName) {
childName = childName.split('--');
if (!isNaN(childName[2])) {
childName[2] = childName[2] - 1;
child.setAttribute('name', childName.join('--'));
}
}
},
// Updates node
updateNode = function updateNode(key) {
var child = key;
if (child.id && child.id !== '') {
decrementId(child);
decrementName(child);
}
else if (child.getAttribute('for') && child.getAttribute('for') !== '') {
decrementHtmlFor(child);
}
},
// Delete node from allIDs and allInputs
deleteIdsAndInputs = function deleteIdsAndInputs(nodes) {
var childNodes = nodes;
Array.prototype.forEach.call(childNodes, function deleteIDandInput(child) {
if (allInputs.hasOwnProperty(child.id)) {
delete allInputs[child.id];
}
if (allIDs.hasOwnProperty(child.id)) {
delete allIDs[child.id];
}
});
},
// Update values of instance
updateInstance = function updateInstance(instance) {
var nodeId = instance.id.split('--'),
childNodes = instance.querySelectorAll('*'),
node = instance;
// Decrements id of container
if (!isNaN(nodeId[1])) {
nodeId[1]--;
}
node.id = nodeId.join('--');
Array.prototype.forEach.call(childNodes, updateNode);
},
// Handles delete events
deleteHandler = function deleteHandler(e) {
var node = e.currentTarget.parentNode,
siblingNode = node.nextElementSibling,
containers,
parent = node.parentNode,
attribute = parent.id;
if (allRepeatables[attribute].length === allRepeatables[attribute].max) {
setAdd(node.parentNode);
}
// removes child
parent.removeChild(node);
allRepeatables[attribute].length--;
deleteIdsAndInputs(node.querySelectorAll('*'));
// change ids and names of subsequest instances
while (siblingNode) {
if (siblingNode.getAttribute('class') === 'form--repeatable') {
updateInstance(siblingNode);
}
siblingNode = siblingNode.nextElementSibling;
}
// remove index if there is just one instance left
if (allRepeatables[attribute].length === allRepeatables[attribute].min) {
containers = parent.querySelectorAll('.form--repeatable');
Array.prototype.forEach.call(containers, function removeDelete(instance) {
instance.removeChild(instance.querySelector('.form--delete'));
});
}
},
// Create and return delete button
newDeleteButton = function newDeleteButton(attribute, index) {
var element = document.createElement('button');
element.type = 'button';
element.textContent = 'Delete';
element.id = attribute + '--delete--' + index;
element.setAttribute('class', 'form--delete');
element.addEventListener('click', deleteHandler);
return element;
},
// Add new input to allInputs
setIDsAndInputs = function setIDsAndInputs(idMap, index) {
Object.keys(idMap).forEach(function setIdandInput(id) {
if (allInputs.hasOwnProperty(id)) {
allInputs[idMap[id]] = Object.assign({}, allInputs[id]);
updateSiblings(id, index);
if (!allIDs.hasOwnProperty(idMap[id])) {
allIDs[idMap[id]] = document.getElementById(idMap[id]);
// Adds validation to new input
allIDs[idMap[id]].addEventListener(allInputs[idMap[id]].validation.on, validate);
}
}
});
},
// Adds index to id
setId = function setId(node, map, index) {
var child = node,
idMap = map;
if (idMap.hasOwnProperty(child.id)) {
child.id = idMap[child.id];
}
else {
idMap[child.id] = updateIndex(child.id, index);
child.id = idMap[child.id];
}
},
// Adds index to HTMLFor
setHtmlFor = function setHtmlFor(node, map, index) {
var child = node,
idMap = map,
htmlFor = child.getAttribute('for');
if (idMap.hasOwnProperty(htmlFor)) {
child.setAttribute('for', idMap[htmlFor]);
}
else {
idMap[htmlFor] = updateIndex(htmlFor, index);
child.setAttribute('for', idMap[htmlFor]);
}
},
// Adds index to name attribute if exists
setName = function setName(node, index) {
var child = node,
childName = child.getAttribute('name');
if (childName) {
childName = childName.split('--');
if (childName.length > 2) {
childName[2] = index;
}
else {
childName.push(index);
}
child.setAttribute('name', childName.join('--'));
}
},
// Clear value of input
clearValues = function clearValues(node) {
var child = node;
if (child.type === 'checkbox' || child.type === 'radio') {
child.checked = false;
}
else if (child.tagName === 'SELECT') {
child.selectedIndex = 0;
}
else if (child.getAttribute('class') !== 'form--delete') {
child.value = '';
}
},
// Updates attribute of instance and returns the first input
newInstance = function newInstance(instance, map, index) {
var idMap = map,
container = instance,
firstInput;
// Adds index to id of container
container.id = updateIndex(container.id, index);
Array.prototype.forEach.call(container.querySelectorAll('.form--alert'), function removeAlert(alert) {
container.removeChild(alert);
});
Array.prototype.forEach.call(container.querySelectorAll('*'), function cloneAttribute(child) {
if (child.id !== '') {
setName(child, index);
setId(child, idMap, index);
clearValues(child);
// Sets first input as focus element
if (!firstInput) {
firstInput = child;
}
}
else if (child.getAttribute('for') && child.getAttribute('for') !== '') {
setHtmlFor(child, idMap, index);
}
});
if (container.querySelector('.form--delete')) {
// Adds event listener to delete button
container.querySelector('.form--delete').addEventListener('click', deleteHandler);
}
return firstInput;
},
// Adds new container to parent
addNewContainer = function addNewContainer(container, newContainer) {
var containerSet = container.parentNode.querySelectorAll('.form--repeatable'),
lastContainer = containerSet[containerSet.length - 1].nextSibling;
container.parentNode.insertBefore(newContainer, lastContainer);
},
// Handles add button event
addHandler = function addHandler(e) {
var firstInput,
container = e.currentTarget.parentNode.querySelectorAll('.form--repeatable'),
newContainer,
attribute = e.currentTarget.parentNode.id,
idMap = {};
// Modify name of initial instance and adds delete button
if (allRepeatables[attribute].length === allRepeatables[attribute].min) {
Array.prototype.forEach.call(container, function addDelete(instance, index) {
// Adds new delete button to container
instance.appendChild(newDeleteButton(attribute, index));
});
}
// Gets updated values first instance
container = e.currentTarget.parentNode.querySelector('.form--repeatable');
// deep clones the existing instance
newContainer = container.cloneNode(true);
// Updates each element in container
firstInput = newInstance(newContainer, idMap, allRepeatables[attribute].length);
addNewContainer(container, newContainer);
setIDsAndInputs(idMap, allRepeatables[attribute].length);
// Increments length of attribute
allRepeatables[attribute].length++;
// Return focus to first input of new instance
firstInput.focus();
if (allRepeatables[attribute].length === allRepeatables[attribute].max) {
e.currentTarget.parentNode.removeChild(e.currentTarget);
}
};
window.onload = function windowOnLoad() {
// Iterates through every instance of input-plugins
Object.keys(allSettings).forEach(function repeatableHandler(input) {
var idAdd = input + '--add',
addButton;
// Adds event listener for existing delete button
Array.prototype.forEach.call(document.querySelectorAll('.form--delete'), function addsDeleteEvent(button) {
button.addEventListener('click', deleteHandler);
});
addButton = document.getElementById(idAdd);
if (addButton) {
addButton.addEventListener('click', addHandler);
}
});
};
}());