I have a JavaScript code that clones the last li
node, if a user clicks on the button Add Choice or if an input clicked is the last input. The cross button on the right removes the li
node it is located in. It won't remove if there's only one input
element left.
Everything works fine except:
If you start removing the li
s from bottom to top until there's only one input
element and click on the input element, it will clone and add a new li
. Now if you click on the second(cloned) input element, it doesn't clone. I get the following error in the console.
TypeError: el.parentNode.nextSibling.nextSibling.classList is undefined
Also, how would I go about attaching a focus
event listener to the input
element and trigger the cloning process without conflicting with the click
event. I tried doing this the cloning is being done twice. If the user navigates through the input
s using the Tab key, and if the last input
is focused. I'd like to trigger the cloning process.
var wheelBuilder = {
getNodes: function(c) {
return document.querySelectorAll(c);
},
getLast: function(e) {
return [].slice.call(e).pop();
},
insertAfter: function(n, r) {
r.parentNode.insertBefore(n, r.nextSibling);
},
clone: function() {
var inputs = wheelBuilder.getNodes('.choiceInput'),
lastInput = wheelBuilder.getLast(inputs),
cl = lastInput.parentNode.cloneNode(true);
wheelBuilder.insertAfter(cl, lastInput.parentNode);
var cross = wheelBuilder.getNodes('.cross'),
choiceInput = wheelBuilder.getNodes('.choiceInput'),
lastCross = wheelBuilder.getLast(cross),
lastChoiceInput = wheelBuilder.getLast(choiceInput);
lastCross.addEventListener('click', wheelBuilder.removeChoice);
lastChoiceInput.addEventListener('click', wheelBuilder.addIfLastInput);
},
addIfLastInput: function(e) {
var el = e.target,
inputs = wheelBuilder.getNodes('.choiceInput');
isLast = (inputs.length > 1) ? el.parentNode.nextSibling.nextSibling.classList.contains('input') : false;
if (!isLast) {
wheelBuilder.clone();
el.focus();
}
},
removeChoice: function(e) {
var choice = e.target.parentNode.parentNode.parentNode.parentNode,
node = choice.parentNode;
if (wheelBuilder.getNodes('.choiceInput').length > 1) {
node.removeChild(choice);
}
}
}
wheelBuilder.getNodes('.cross').forEach(function(el, _) {
el.addEventListener('click', wheelBuilder.removeChoice);
});
wheelBuilder.getNodes('.choiceInput').forEach(function(el, _) {
el.addEventListener('click', wheelBuilder.addIfLastInput);
});
var addChoice = document.getElementById('addChoice');
addChoice.addEventListener('click', wheelBuilder.clone);
.wheelBuilder {
position: absolute;
width: 100%;
font-size: 1.3em;
font-family: Sans;
}
.wheelBuilder .wrapper {
max-width: 60%;
margin: 0 auto;
margin-bottom: 50px;
padding: 0 10px 10px 10px;
}
.title #gears svg {
transform: translate(5px, 5px);
fill: #565656;
}
.wheelBuilder .title {
text-align: center;
font-family: Arial, Helvetica, sans-serif;
font-size: 1.5em;
font-weight: 500;
padding: 15px 20px;
margin: 0 0 20px 0px;
line-height: 40px;
outline: 0;
width: 100%;
background: #ffffff;
color: #565656;
box-shadow: 0px 0px 4px 4px #dfdfdf;
}
.wrapper ol {
position: relative;
padding: 0;
margin: 0.25em 0.125em;
width: 100%;
background: #ffffff;
padding: 20px;
box-shadow: 0px 0px 4px 4px #dfdfdf;
}
.choices {
position: relative;
list-style-type: none;
width: 100%;
}
.choices:first-child {
text-align: left;
color: #565656;
}
.choices:not(:last-child) {
margin-bottom: 10px;
}
.add {
text-align: left;
}
#plus svg {
z-index: 3;
transform: translate(20px, 7px);
fill: #565656;
cursor: pointer;
}
#addChoice, #applyChanges {
position: relative;
height: 40px;
padding: 0 .8em;
background: #ffffff;
border: 0;
font-size: 1.2em;
color: #565656;
cursor: pointer;
margin-top: 10px;
box-sizing: border-box;
border: 1px solid #ffffff;
box-shadow: 0px 0px 5px 3px #dfdfdf;
transition: 0.2s all ease-in;
}
#addChoice {
margin-left: -32px;
padding: 0 .8em 0 2.2em;
}
#applyChanges {
width: 100%;
}
#plus:hover + #addChoice, #addChoice:hover, #applyChanges:hover {
border: 1px solid #a8ab0a;
box-shadow: 0px 0px 5px 4px #d1d1d1;
}
.choiceInput {
width: 100%;
height: 40px;
background: #ffffff;
padding: 0 .4em;
color: #565656;
font-size: 1.2em;
border: 1px solid #cfcfcf;
transition: border .2s ease-in, box-shadow .2s ease-in;
}
.choiceInput:hover {
border: 1px solid #c6c85f;
box-shadow: inset 0 0 5px 1px #cfcfcf;
}
.choiceInput:focus {
border: 1px solid #a8ab0a;
box-shadow: inset 0 0 5px 1px #cfcfcf;
}
.cross {
position: absolute;
height: 40px;
right: 0;
top: 0;
}
.cross svg {
transform: translate(50%, 0);
}
.cross path {
cursor: pointer;
}
svg g .outline {
stroke:#c2c2c2;
fill:#ffffff;
}
svg g .x {
fill:none;
stroke:#c4c4c4;
stroke-width:2;
stroke-linecap:round;
}
.cross g:hover path {
stroke: #e75141;
}
@media only screen and (max-width: 480px) {
.toast p, .toast span, .spinBtn {
font-size: 18px;
line-height: 18px;
}
.wheelBuilder {
top: 120vmin;
}
.wheelBuilder .wrapper {
max-width: 90%;
}
.wrapper ol li {
padding: 0;
}
}
<div class="wheelBuilder">
<div class="wrapper">
<h2 class="title">
<span id="gears">
<svg xmlns="http://www.w3.org/2000/svg" width="30" viewBox="0 0 24 24">
<path d="M 16.064453 2 C 15.935453 2 15.8275 2.0966094 15.8125 2.2246094 L 15.695312 3.2363281 C 15.211311 3.4043017 14.773896 3.6598036 14.394531 3.9882812 L 13.457031 3.5839844 C 13.339031 3.5329844 13.202672 3.5774531 13.138672 3.6894531 L 12.201172 5.3105469 C 12.136172 5.4215469 12.166531 5.563625 12.269531 5.640625 L 13.078125 6.2402344 C 13.030702 6.4865104 13 6.7398913 13 7 C 13 7.2601087 13.030702 7.5134896 13.078125 7.7597656 L 12.269531 8.359375 C 12.166531 8.435375 12.137172 8.5774531 12.201172 8.6894531 L 13.138672 10.310547 C 13.202672 10.422547 13.339031 10.468969 13.457031 10.417969 L 14.394531 10.011719 C 14.773896 10.340196 15.211311 10.595698 15.695312 10.763672 L 15.8125 11.775391 C 15.8275 11.903391 15.935453 12 16.064453 12 L 17.935547 12 C 18.064547 12 18.1725 11.903391 18.1875 11.775391 L 18.304688 10.763672 C 18.789173 10.59553 19.227802 10.340666 19.607422 10.011719 L 20.542969 10.414062 C 20.660969 10.465063 20.797328 10.420594 20.861328 10.308594 L 21.798828 8.6875 C 21.863828 8.5765 21.833469 8.4344219 21.730469 8.3574219 L 20.923828 7.7578125 C 20.970992 7.5121818 21 7.2593796 21 7 C 21 6.7398913 20.969298 6.4865104 20.921875 6.2402344 L 21.730469 5.640625 C 21.833469 5.564625 21.862828 5.4225469 21.798828 5.3105469 L 20.861328 3.6894531 C 20.797328 3.5774531 20.660969 3.5310312 20.542969 3.5820312 L 19.605469 3.9882812 C 19.226104 3.6598036 18.788689 3.4043017 18.304688 3.2363281 L 18.1875 2.2246094 C 18.1725 2.0966094 18.064547 2 17.935547 2 L 16.064453 2 z M 17 5.25 C 17.966 5.25 18.75 6.034 18.75 7 C 18.75 7.967 17.966 8.75 17 8.75 C 16.034 8.75 15.25 7.967 15.25 7 C 15.25 6.034 16.034 5.25 17 5.25 z M 7.0644531 9 C 6.9354531 9 6.8275 9.0966094 6.8125 9.2246094 L 6.6386719 10.710938 C 5.8314079 10.940599 5.1026855 11.35237 4.5175781 11.921875 L 3.1582031 11.335938 C 3.0402031 11.284937 2.9038438 11.329406 2.8398438 11.441406 L 1.9023438 13.0625 C 1.8373437 13.1735 1.8677031 13.315578 1.9707031 13.392578 L 3.1679688 14.279297 C 3.0687954 14.672064 3 15.076469 3 15.5 C 3 15.923531 3.0687954 16.327936 3.1679688 16.720703 L 1.9707031 17.609375 C 1.8677031 17.685375 1.8383437 17.827453 1.9023438 17.939453 L 2.8398438 19.560547 C 2.9038438 19.672547 3.0402031 19.717016 3.1582031 19.666016 L 4.5175781 19.078125 C 5.1026855 19.64763 5.8314079 20.059401 6.6386719 20.289062 L 6.8125 21.775391 C 6.8275 21.903391 6.9354531 22 7.0644531 22 L 8.9355469 22 C 9.0645469 22 9.1725 21.903391 9.1875 21.775391 L 9.3613281 20.289062 C 10.168592 20.059401 10.897314 19.64763 11.482422 19.078125 L 12.841797 19.664062 C 12.959797 19.715062 13.096156 19.670594 13.160156 19.558594 L 14.097656 17.9375 C 14.162656 17.8265 14.132297 17.684422 14.029297 17.607422 L 12.832031 16.720703 C 12.931205 16.327936 13 15.923531 13 15.5 C 13 15.076469 12.931205 14.672064 12.832031 14.279297 L 14.029297 13.390625 C 14.132297 13.314625 14.161656 13.172547 14.097656 13.060547 L 13.160156 11.439453 C 13.096156 11.327453 12.959797 11.282984 12.841797 11.333984 L 11.482422 11.921875 C 10.897314 11.35237 10.168592 10.940599 9.3613281 10.710938 L 9.1875 9.2246094 C 9.1725 9.0966094 9.0645469 9 8.9355469 9 L 7.0644531 9 z M 8 13.5 C 9.105 13.5 10 14.395 10 15.5 C 10 16.605 9.105 17.5 8 17.5 C 6.895 17.5 6 16.605 6 15.5 C 6 14.395 6.895 13.5 8 13.5 z"></path>
</svg>
</span> Wheel Builder
</h2>
<ol>
<li class="choices">Choices (enter up to 50 choices):</li>
<li class="choices input">
<input class="choiceInput" type="text" autocomplete="off" value="" />
<span class="cross">
<svg xmlns="http://www.w3.org/2000/svg" height="40" viewBox="0 0 60 40" version="1.1">
<g>
<path class="outline" d="M10,20 l12,-20 h38 v40 h-38z" />
<path class="x" d="M40,20 m-7,-7 l14,14 m0,-14 l-14,14" />
</g>
</svg>
</span>
</li>
<li class="choices input">
<input class="choiceInput" type="text" autocomplete="off" value="" />
<span class="cross">
<svg xmlns="http://www.w3.org/2000/svg" height="40" viewBox="0 0 60 40" version="1.1">
<g>
<path class="outline" d="M10,20 l12,-20 h38 v40 h-38z" />
<path class="x" d="M40,20 m-7,-7 l14,14 m0,-14 l-14,14" />
</g>
</svg>
</span>
</li>
<li class="choices input">
<input class="choiceInput" type="text" autocomplete="off" value="" />
<span class="cross">
<svg xmlns="http://www.w3.org/2000/svg" height="40" viewBox="0 0 60 40" version="1.1">
<g>
<path class="outline" d="M10,20 l12,-20 h38 v40 h-38z" />
<path class="x" d="M40,20 m-7,-7 l14,14 m0,-14 l-14,14" />
</g>
</svg>
</span>
</li>
<li class="choices input">
<input class="choiceInput" type="text" autocomplete="off" value="" />
<span class="cross">
<svg xmlns="http://www.w3.org/2000/svg" height="40" viewBox="0 0 60 40" version="1.1">
<g>
<path class="outline" d="M10,20 l12,-20 h38 v40 h-38z" />
<path class="x" d="M40,20 m-7,-7 l14,14 m0,-14 l-14,14" />
</g>
</svg>
</span>
</li>
<li class="choices input">
<input class="choiceInput" type="text" autocomplete="off" value="" />
<span class="cross">
<svg xmlns="http://www.w3.org/2000/svg" height="40" viewBox="0 0 60 40" version="1.1">
<g>
<path class="outline" d="M10,20 l12,-20 h38 v40 h-38z" />
<path class="x" d="M40,20 m-7,-7 l14,14 m0,-14 l-14,14" />
</g>
</svg>
</span>
</li>
<li class="choices add">
<span id="plus">
<svg xmlns="http://www.w3.org/2000/svg" height="28" viewBox="0 0 32 32" version="1.1">
<path d="M 16 3 C 8.832031 3 3 8.832031 3 16 C 3 23.167969 8.832031 29 16 29 C 23.167969 29 29 23.167969 29 16 C 29 8.832031 23.167969 3 16 3 Z M 16 5 C 22.085938 5 27 9.914063 27 16 C 27 22.085938 22.085938 27 16 27 C 9.914063 27 5 22.085938 5 16 C 5 9.914063 9.914063 5 16 5 Z M 15 10 L 15 15 L 10 15 L 10 17 L 15 17 L 15 22 L 17 22 L 17 17 L 22 17 L 22 15 L 17 15 L 17 10 Z "></path>
</svg>
</span>
<input id="addChoice" type="button" name="addChoice" value="Add Choice..." />
</li>
<li class="choices">
<input id="applyChanges" type="button" name="applyChanges" value="Apply Wheel Changes" />
</li>
</ol>
</div>
</div>
I'd appreciate any help.
Now if you click on the second(cloned) input element, it doesn't clone. I get the following error in the console.
The issue is in your addIfLastInput
method. el.parentNode.nextSibling.nextSibling.classList.contains('input')
is a fragile code. Small changes in tree structure (as you can see) can crash your application. It seems you want to check if the parent element of the clicked element is the last li
that has .choices.input
class names. For that, you can simply code:
addIfLastInput: function(e) {
var el = e.target,
inputs = wheelBuilder.getNodes('.choices.input'),
isLast = el.parentNode === wheelBuilder.getLast(inputs);
if (isLast) {
/// ...
}
},
Also, how would I go about attaching a focus event listener to the input element and trigger the cloning process without conflicting with the click event.
I'd just listen to focus
event instead of click
.
wheelBuilder.getNodes('.choiceInput').forEach(function(el, _) {
el.addEventListener('focus', wheelBuilder.addIfLastInput);
});
And:
clone: function() {
// ...
lastChoiceInput.addEventListener('focus', wheelBuilder.addIfLastInput);
},