I am creating a very basic shopping cart.
It has dependent drop down menus and a button "Add more products"
which will add one more row of the same drop down menus.
There are 2 drop down menus 2nd menu must remain disabled
until an option is selected in 1st menu. The quantity input must be disabled
until an option is selected in 2nd menu. The Add more products
is enabled quantity is add
I am using cloneNode()
to add code for new row.
Since it works only once I am creating clone each time "Add more products"
button is clicked which calls new_products();
I am using last added row for creating new Clone
The new row gets added but the problem is 2nd menu and quantity input in this row are already enabled
.
Please try giving a solution in Vanilla(pure) JavaScript.
EDIT 1: I have came half way.
Before appending the clone I tried to access those elements and change the disabled
attribute value.
In function new_products()
:
var order = document.getElementById('order_now');
var product = document.getElementsByClassName('product');
var clone = product[no_of_products-1].cloneNode(true);
clone.getElementsByClassName('second_select')[0].disabled=true;
clone.getElementsByClassName('add_btn')[0].disabled=true;
But this only worked for 2nd drop down menu.
It's not working for quantity input
control.
Code snippet:
var productsByCategory = { A: ["Select sub-product", "CNC 1", "CNC 2", "CNC 3", "CNC 4"], B: ["Select sub-product", "LASER 1", "LASER 2", "LASER 3", "LASER 4"], C: ["Select sub-product", "RUBBER 1", "RUBBER 2", "RUBBER 3", "RUBBER 4", "RUBBER 5"], D: ["Select sub-product", "PRECISION 1", "PRECISION 2", "PRECISION 3"] } var valuesByCategory = { A: ["", "A1", "A2", "A3", "A4"], B: ["", "B1", "B2", "B3", "B4"], C: ["", "C1", "C2", "C3", "C4", "C5"], D: ["", "D1", "D2", "D3"] } var no_of_products = 1; function dropdown() { var select = document.getElementsByClassName('first_select'); var selected = select[no_of_products - 1].value; var target = document.getElementsByClassName('second_select'); var targetLength = target[no_of_products - 1].length /*console.log("Length"+target.length);*/ for (var i = targetLength; i >= 0; i--) { /*console.log(i);*/ target[no_of_products - 1].remove(i); } if (selected == 0) { var option = document.createElement("option"); option.text = "Select Product first"; option.value = ""; target[no_of_products - 1].add(option); target[no_of_products - 1].disabled = true; } if (selected == 1) { for (var i in productsByCategory['A']) { var option = document.createElement("option"); //If this is outside the lopp then only last option gets included. option.text = productsByCategory['A'][i]; option.value = valuesByCategory['A'][i]; target[no_of_products - 1].add(option); target[no_of_products - 1].disabled = false; } } else if (selected == 2) { for (var i in productsByCategory['B']) { var option = document.createElement("option"); option.text = productsByCategory['B'][i]; option.value = valuesByCategory['B'][i]; target[no_of_products - 1].add(option); target[no_of_products - 1].disabled = false; } } else if (selected == 3) { for (var i in productsByCategory['C']) { var option = document.createElement("option"); option.text = productsByCategory['C'][i]; option.value = valuesByCategory['C'][i]; target[no_of_products - 1].add(option); target[no_of_products - 1].disabled = false; } } else { for (var i in productsByCategory['D']) { var option = document.createElement("option"); option.text = productsByCategory['D'][i]; option.value = valuesByCategory['D'][i]; target[no_of_products - 1].add(option); target[no_of_products - 1].disabled = false; } } } function dropdown2() { var select = document.getElementsByClassName('second_select'); var selected = select[no_of_products - 1].value; /*console.log(selected);*/ var submit = document.getElementsByClassName('s_btn'); submit[no_of_products - 1].disabled = false; var add = document.getElementById('add_button'); add.disabled = false; } function new_products() { var order = document.getElementById('order_now'); var product = document.getElementsByClassName('product'); var clone = product[no_of_products - 1].cloneNode(true); clone.getElementsByClassName('second_select')[0].disabled = true; clone.getElementsByClassName('add_btn')[0].disabled = true; var add = document.getElementById('add_button'); product[no_of_products - 1].removeChild(add); /*console.log(clone);*/ order.appendChild(clone); no_of_products += 1; }
body { height: 100vh; margin: 0px; overflow-y: auto; font-family: 'Roboto'; } #clear { clear: both; } .content { display: flex; background-color: white; height: auto; margin-top: 0px; font-family: 'Roboto'; z-index: -1; min-height: 88%; } .link-contents { position: relative; display: block; float: left; left: 0px; width: 100%; } .option-links { display: block; font-size: 30px; cursor: pointer; } #op1 { background-color: #cccccc; } select, button, input { position: relative; top: 5em; display: block; width: 12em; height: 2em; } button { width: 8em; } .first_select { position: relative; float: left; left: 10%; } .second_select { position: relative; float: left; left: 20%; } .s_btn { position: relative; float: left; left: 30%; } .add_btn { float: left; top: 6em; width: 10em; left: 5em; } .footer { display: block; max-height: 4%; } .option-contents { display: none; } #order_now { display: block; }
<!DOCTYPE html> <html> <head> <link href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet'> <link rel="stylesheet" type="text/css" href="profile.css"> <title></title> </head> <body> <div class="content"> <div class="link-contents"> <div class="option-contents" id="order_now"> <div class="product"> <select class="first_select" onchange="dropdown();"> <option value="0">Select</option> <option value="1">CNS</option> <option value="2">Laser Cut</option> <option value="3">Rubber roller</option> <option value="4">Fixture</option> </select> <select class="second_select" onchange="dropdown2();" disabled> <option>Select Product first</option> </select> <input class="s_btn" type="number" min='1' value="1" disabled /> <br/> <button class="add_btn" id="add_button" onclick="new_products();" disabled>Add more products</button> <div id="clear"></div> </div> </div> </div> <div id="clear"></div> </div> <div class="footer"> A big thank you to all of you. </div> </body> <script type="text/javascript" src="profile.js"></script> </html>
In order to append a clone of div.product
each time the 'Add more products' button.add_btn
is clicked, you need to create a global variable, that holds a clone of the div.product
that shows up when the page loads(I mean the div.product
that has the select.second_select
, the input.s_btn
and the button.add_btn
that are initially disabled), then when the button.add_btn
is clicked we'll create a new div
element, give it a class of product
, then append that div
to div#order_now
, then we'll use the innerHTML
attribute of the cloned element and assign it to the newly created div
.
Some remarks and recommendations
I'll add a runnable snippet in the end of my answer, but before that, I want to tell you some points:
You are using inline event listeners
which is not a wise choice, you should use addEventListener
method instead. With addEventListener
method, you can attach many events at the same time(of course when these events share the same handling logic), you can also attach as many handers as you want to the same event(the only matter is the client-side memory and performance concerns). Another important feature of addEventListener
method is the final parameter, which controls how the listener reacts to bubbling events, there is no equivalent when using inline events.
Learn more about
addEventListener
methodLearn more about
events bubbling
.
To allow the dynamically created elements from being capturable by the events, such as the 'change' event on the select
elements, we need to attach the events to the document
and check which handler function should execute. In other words, we'll be using Event Delegqtion
.
Learn more about
Event Delegation
, that Stackoverflow post may help you.
In my answer, some variables from your code will no longer be used as no_of_products
variable and some others(you'll notice them by yourself I hope).
I removed all the redundant ID
s attribute, specically on the button
, the input
and the select
s, from the div.product
elements, because an ID
must be unique in the page. Also, I removed all the inline event handlers as we'll be using the addEventListener
method.
As I said, the inline event handlers will be replaced by the addEventListener
method, and some variables are no longer useful, to check which handler function should be executed, we'll rely on the Event
's target
attribute to see which element is the target of the current event, thus we'll know which handler function should execute.
Learn more about
Event.target
.
To allow more control over our code(the handler functions particulary), will be using the this
keyword to reference the current event's target(like so, no need for no_of_products
variable because each element will be referenced using the this
keyword when it's the current event's target, even the dynamically created elements will be supported. ie: In the new_products
function, this
refers to the clicked button.add_btn
element). To do so, we'll be using the call
method on the handler functions, which allows us to specify which element is referenced by the this
keyword in the handler functions.
Learn more about
call
method.
With all that being said here's a runnable snippet to illustrate:
var productsByCategory = { A: ["Select sub-product", "CNC 1", "CNC 2", "CNC 3", "CNC 4"], B: ["Select sub-product", "LASER 1", "LASER 2", "LASER 3", "LASER 4"], C: ["Select sub-product", "RUBBER 1", "RUBBER 2", "RUBBER 3", "RUBBER 4", "RUBBER 5"], D: ["Select sub-product", "PRECISION 1", "PRECISION 2", "PRECISION 3"] }, valuesByCategory = { A: ["", "A1", "A2", "A3", "A4"], B: ["", "B1", "B2", "B3", "B4"], C: ["", "C1", "C2", "C3", "C4", "C5"], D: ["", "D1", "D2", "D3"] }, /** * create a clone element using 'querySelector' method * which DOESN'T return a 'live node' (it returns a 'static node'), thus gain performance. **/ clone = document.querySelector('.product').cloneNode(true); /** * add event listeners to the body rather than the specific elements * thus the dynamically created elements are also supported and catchable by the events. * using some checking to get the desired handler function to be called. * using the 'call' method, we specify to which element in the handler functions the 'this' keyword refers to. **/ document.addEventListener('change', function(e) { (e.target instanceof HTMLSelectElement && ((e.target.classList.contains('first_select') && dropdown.call(e.target)) || (e.target.classList.contains('second_select') && dropdown2.call(e.target)))); /** * the above code is the same as the next but it's faster. if (e.target instanceof HTMLSelectElement && e.target.classList.contains('first_select')) { dropdown.call(e.target) } else if (e.target instanceof HTMLSelectElement && e.target.classList.contains('second_select')) { dropdown2.call(e.target) } **/ }); document.addEventListener('click', function(e) { (e.target instanceof HTMLButtonElement && e.target.classList.contains('add_btn') && new_products.call(e.target)); /** * the above code is the same as the next but it's faster. if(e.target instanceof HTMLButtonElement && e.target.classList.contains('add_btn')) { new_products.call(e.target); } **/ }) /** * from now on, the handler function use the 'this' keyword to reference the desired element, even the dynamically created ones are supporyted. * So, 'this' === the argument that passed to the 'call' method. **/ function dropdown() { var selected = this.value; var target = this.parentNode.getElementsByClassName('second_select')[0]; var targetLength = target.length; for (var i = targetLength; i >= 0; i--) { target.remove(i); } if (selected == 0) { var option = document.createElement("option"); option.text = "Select Product first"; option.value = ""; target.add(option); /** * you missed to disable the 'button' and the 'input' if the selected value is '0'. **/ this.parentNode.querySelector('.s_btn').disabled = true; this.parentNode.querySelector('.add_btn').disabled = true; target.disabled = true; } else if (selected == 1) { for (var i in productsByCategory['A']) { var option = document.createElement("option"); option.text = productsByCategory['A'][i]; option.value = valuesByCategory['A'][i]; target.add(option); target.disabled = false; } } else if (selected == 2) { for (var i in productsByCategory['B']) { var option = document.createElement("option"); option.text = productsByCategory['B'][i]; option.value = valuesByCategory['B'][i]; target.add(option); target.disabled = false; } } else if (selected == 3) { for (var i in productsByCategory['C']) { var option = document.createElement("option"); option.text = productsByCategory['C'][i]; option.value = valuesByCategory['C'][i]; target.add(option); target.disabled = false; } } else { for (var i in productsByCategory['D']) { var option = document.createElement("option"); option.text = productsByCategory['D'][i]; option.value = valuesByCategory['D'][i]; target.add(option); target.disabled = false; } } } function dropdown2() { this.parentNode.getElementsByClassName('s_btn')[0].disabled = false; this.parentNode.getElementsByClassName('add_btn')[0].disabled = false; } function new_products() { var order = document.getElementById('order_now'), /** * create a 'div' element which will hold the cloned element's 'innerHTML'. **/ product = document.createElement('div'); /** * give that 'div' element the 'product' class. **/ product.className = 'product'; /** * append that 'div' to the 'div#order_now' element. * it's now the last child of the 'div#order_now' element. **/ order.appendChild(product); /** * assign the cloned element's 'innerHTML' to the newly created 'div' using the 'lastChild' attribute. * with that we eliminate the possibility of directly appending the cloned element to 'div#order_now' to run only once. **/ order.lastChild.innerHTML = clone.innerHTML; this.parentNode.removeChild(this) }
body { height: 100vh; margin: 0px; overflow-y: auto; font-family: 'Roboto'; } #clear { clear: both; } .content { display: flex; background-color: white; height: auto; margin-top: 0px; font-family: 'Roboto'; z-index: -1; min-height: 88%; } .link-contents { position: relative; display: block; float: left; left: 0px; width: 100%; } .option-links { display: block; font-size: 30px; cursor: pointer; } #op1 { background-color: #cccccc; } select, button, input { position: relative; top: 5em; display: block; width: 12em; height: 2em; } button { width: 8em; } .first_select { position: relative; float: left; left: 10%; } .second_select { position: relative; float: left; left: 20%; } .s_btn { position: relative; float: left; left: 30%; } .add_btn { float: left; top: 6em; width: 10em; left: 5em; } .footer { display: block; max-height: 4%; } .option-contents { display: none; } #order_now { display: block; } select, input { display: block !important; }
<div class="content"> <div class="link-contents"> <div class="option-contents" id="order_now"> <div class="product"> <select class="first_select"> <option value="0">Select</option> <option value="1">CNS</option> <option value="2">Laser Cut</option> <option value="3">Rubber roller</option> <option value="4">Fixture</option> </select> <select class="second_select" disabled> <option>Select Product first</option> </select> <input class="s_btn" type="number" min='1' value="1" disabled /> <br/> <button type="button" class="add_btn" disabled>Add more products</button> <div id="clear"></div> </div> </div> </div> <div id="clear"></div>
This Stackoverflow post may help you to learn more about
live nodes
andstatic nodes
.
Hope I pushed you further.
Unfortunately you have to much mistakes in your code. Because of this I will describe only important mistakes:
id
attribute from an element must be unique in a full HTML page. If you clone some element and it has an id attribute then you have to create a new id. And because your wish is to remove id
attribute from the button – so I did it for you. element.disable
to disable an element. The most of browsers support it, but it is not standart. If you see the documentation for Element
and for Node
then you will see that they do not have this property. Some browser would not support it. Use for this case Element.setAttribute()
and Element.removeAttribute()
functions. float: left
if you do not need it. In your case you do not need it because you have inline-block
elements. I have changed a lot of your CSS code too. addEventListener
method instead of inline event listeners, but in your case it is disputable and because of this I do not use addEventListener
– so you can better understand my code. But I add one parameter in your functions – note it. I wrote the new code so that you do not have any type of errors. Enjoy it!
Complete solution
var productsByCategory = { A: ["Select sub-product", "CNC 1", "CNC 2", "CNC 3", "CNC 4"], B: ["Select sub-product", "LASER 1", "LASER 2", "LASER 3", "LASER 4"], C: ["Select sub-product", "RUBBER 1", "RUBBER 2", "RUBBER 3", "RUBBER 4", "RUBBER 5"], D: ["Select sub-product", "PRECISION 1", "PRECISION 2", "PRECISION 3"] }; var valuesByCategory = { A: ["", "A1", "A2", "A3", "A4"], B: ["", "B1", "B2", "B3", "B4"], C: ["", "C1", "C2", "C3", "C4", "C5"], D: ["", "D1", "D2", "D3"] }; //var no_of_products = 1; //WE DO NOT NEED IT function selectHelper(category, targetObj) { for(var i in productsByCategory[category]) { var option = document.createElement("option"); option.text = productsByCategory[category][i]; option.value = valuesByCategory[category][i]; targetObj.add(option); } setEnabled(targetObj); } function dropdown(obj) { var selected = obj.value, //second select: target = obj.nextElementSibling; for(var i = target.length; i--; ) target.remove(i); if(selected == 0) { var option = document.createElement("option"); option.text = "Select Product first"; option.value = ""; target.add(option); setDisabled(target); //set disabled input field: setDisabled(target.nextElementSibling) } else { if(selected == 1) selectHelper('A', target); else if(selected == 2) selectHelper('B', target); else if(selected == 3) selectHelper('C', target); else selectHelper('D', target) } } function dropdown2(obj) { setEnabled(obj.nextElementSibling); setEnabled(document.getElementsByClassName('add_btn')[0]); } function new_products() { var allProducts = document.getElementsByClassName('product'); lastProduct = allProducts[allProducts.length - 1], clone = lastProduct.cloneNode(true); setDisabled(clone.getElementsByClassName('second_select')[0]); //set disabled input field: setDisabled(clone.getElementsByClassName('s_btn')[0]); setDisabled(document.getElementsByClassName('add_btn')[0]); //may be "insertBefore" is weird, but we do here insert new product after the last product: document.getElementById('order_now').insertBefore(clone, lastProduct.nextSibling); } function setDisabled(obj) { obj.setAttribute("disabled", "disabled"); } function setEnabled(obj) { obj.removeAttribute("disabled"); }
body { height: 100vh; margin: 0px; overflow-y: auto; font-family: 'Roboto'; } .content { background-color: white; height: auto; margin-top: 0px; z-index: -1; min-height: 88%; } .link-contents { position: relative; left: 0px; width: 100%; } .option-links { display: block; font-size: 30px; cursor: pointer; } #op1 {background-color: #cccccc} select, button, input { position: relative; top: 5em; width: 12em; height: 2em; } button {width: 8em} .first_select { position: relative; left: 10%; } .second_select { position: relative; left: 20%; } .s_btn { position: relative; left: 30%; } .add_btn { top: 6em; width: 10em; } .footer { display: block; max-height: 4%; } .option-contents {display: none} #order_now {display: block}
<link href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet'> <link rel="stylesheet" type="text/css" href="profile.css"> <div class="content"> <div class="link-contents"> <div class="option-contents" id="order_now"> <div class="product"> <select class="first_select" onchange="dropdown(this)"> <option value="0">Select</option> <option value="1">CNS</option> <option value="2">Laser Cut</option> <option value="3">Rubber roller</option> <option value="4">Fixture</option> </select> <select class="second_select" onchange="dropdown2(this)" disabled> <option>Select Product first</option> </select> <input class="s_btn" type="number" min='1' value="1" disabled /> </div> <center><button class="add_btn" onclick="new_products()" disabled>Add more products</button></center> </div> </div> </div> <div class="footer">A big thank you to all of you.</div>
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.