簡體   English   中英

Vanilla Javascript:如何多次使用克隆的DOM節點?

[英]Vanilla Javascript: How to use cloned DOM node multiple times?

我正在創建一個非常基本的購物車。

它具有相關的下拉菜單和一個"Add more products"按鈕,將在同一下拉菜單中再添加一行。

有2個下拉菜單第2個菜單必須保持disabled狀態,直到在第1個菜單中選擇了一個選項。 在第二個菜單中選擇一個選項之前,必須disabled數量輸入。 Add more products已啟用數量被添加

我正在使用cloneNode()為新行添加代碼。

因為它僅在我每次單擊"Add more products"按鈕時都會創建克隆創建,所以調用new_products();

我正在使用最后添加的行來創建新的Clone

新行被添加,但是問題是第二菜單,並且該行中的數量輸入已enabled

請嘗試使用Vanilla(純)JavaScript提供解決方案。

編輯1:我來了一半。

在追加克隆之前,我嘗試訪問那些元素並更改disabled屬性值。

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;

但這僅適用於第二個下拉菜單。

它不適用於數量input控制。

程式碼片段:

 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> 

為了在每次點擊“添加更多產品” button.add_btn添加div.product的克隆,您需要創建一個全局變量,該變量保存div.product的克隆,該克隆在頁面加載時顯示(I意味着div.product具有select.second_selectinput.s_btnbutton.add_btn是開始禁用),那么當button.add_btn被點擊,我們將創建一個新div元素,給它一個類的product ,然后將該div附加到div#order_now ,然后我們將使用克隆的元素的innerHTML屬性並將其分配給新創建的div

一些意見和建議

我將在答案的末尾添加一個可運行的代碼段,但在此之前,我想告訴您一些要點:

  • 您正在使用inline event listeners ,這不是明智的選擇,應改用addEventListener方法。 使用addEventListener方法,您可以同時附加多個事件(當然,當這些事件共享相同的處理邏輯時),還可以根據需要將任意數量的處理程序附加到同一事件(唯一的問題是客戶端內存)和性能問題)。 addEventListener方法的另一個重要功能是final參數,該參數控制偵聽器對冒泡事件的反應,使用內聯事件時沒有等效的參數。

    了解有關addEventListener方法的更多信息

    了解有關events bubbling 更多信息。

  • 為了使動態創建的元素不受事件(例如select元素上的“ change”事件)的捕獲,我們需要將事件附加到document並檢查應執行哪個處理程序函數。 換句話說,我們將使用Event Delegqtion

    了解有關Event Delegation 更多信息,Stackoverflow的帖子可能會對您有所幫助。

  • 在我的回答中,您代碼中的某些變量將不再用作no_of_products變量,而其他變量(希望您會注意到它們自己)。

  • 我從div.product元素中刪除了所有冗余ID的屬性,特別是在buttoninputselect ,因為ID在頁面中必須是唯一的。 另外,我將刪除所有內聯事件處理程序,因為我們將使用addEventListener方法。

  • 正如我所說,內聯事件處理程序將由addEventListener方法代替,並且某些變量不再有用,以檢查應執行哪個處理程序功能,我們將依靠Eventtarget屬性來查看哪個元素是當前事件的目標,因此我們將知道應執行哪個處理函數。

    了解有關Event.target 更多信息。

  • 為了讓在我們的代碼(處理函數格外)更多的控制,將使用this關鍵字來引用當前事件的目標(像這樣,無需no_of_products變量,因為每個元素將引用使用this關鍵字時,它是當前事件的目標,甚至將支持動態創建的元素,即:在new_products函數中, this是指單擊的button.add_btn元素)。 為此,我們將在處理程序函數上使用call方法,該方法允許我們指定處理程序函數中this關鍵字引用的元素。

    了解有關call方法的更多信息。

話雖如此,下面是一個可運行的代碼片段,用以說明:

 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> 

這個Stackoverflow帖子可以幫助您了解有關live nodesstatic nodes 更多信息。

希望我能進一步推動您。

不幸的是,您在代碼中犯了很多錯誤。 因此,我將僅描述重要的錯誤:

  1. 元素的id屬性在完整的HTML頁面中必須是唯一的。 如果克隆某個元素並且它具有id屬性,則必須創建一個新的id。 並且因為您希望從按鈕中刪除id屬性-所以我為您做了。
  2. 不要使用element.disable禁用元素。 大多數瀏覽器都支持它,但這不是標准。 如果查看ElementNode的文檔,則將看到它們不具有此屬性。 某些瀏覽器不支持。 在這種情況下,使用Element.setAttribute()Element.removeAttribute()函數。
  3. 添加下一個產品行的按鈕必須不在產品行中。
  4. 不要使用CSS屬性float: left如果不需要,請使用float: left 在您的情況下,您不需要它,因為您有inline-block元素。 我也更改了很多CSS代碼。
  5. 不要一次又一次地重復執行代碼 –這是非常糟糕的編程風格。 閱讀最后一個鏈接下的文章。 因此,我縮短了您的代碼–我編寫了一些帶有參數的函數。
  6. 通常,最好使用addEventListener方法而不是內聯事件偵聽器,但在您的情況下,這是有爭議的,因此,我不使用addEventListener ,因此您可以更好地理解我的代碼。 但是我在您的函數中添加了一個參數–請注意。

我編寫了新代碼,以便您沒有任何類型的錯誤。 好好享受!

完整的解決方案

 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> 

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM