-2

我正在创建一个非常基本的购物车。

它具有相关的下拉菜单和一个按钮,该按钮"Add more products"将再添加一行相同的下拉菜单。

有 2 个下拉菜单第二个菜单必须保留disabled,直到在第一个菜单中选择一个选项。数量输入必须disabled直到在第二个菜单中选择了一个选项。启用的Add more products数量是添加的

我正在使用cloneNode()为新行添加代码。

因为它只在我每次"Add more products"单击按钮时创建克隆时才起作用new_products();

我正在使用最后添加的行来创建新的Clone

添加了新行,但问题是第二个菜单,并且该行中的数量输入已经存在enabled

请尝试在 Vanilla(pure) 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>

4

2 回答 2

2

为了在div.product每次单击“添加更多产品”时附加一个克隆button.add_btn,您需要创建一个全局变量,该变量包含一个div.product在页面加载时div.product显示select.second_select的最初是禁用的),然后当单击 时,我们将创建一个新元素,给它一个类,然后将其附加到,然后我们将使用克隆元素的属性并将其分配给新创建的。input.s_btnbutton.add_btnbutton.add_btndivproductdivdiv#order_nowinnerHTMLdiv

一些评论和建议

我将在答案的末尾添加一个可运行的片段,但在此之前,我想告诉你一些要点:

  • 您正在使用inline event listeners这不是一个明智的选择,您应该使用addEventListener方法来代替。使用addEventListener方法,您可以同时附加多个事件(当然当这些事件共享相同的处理逻辑时),您还可以将任意数量的处理程序附加到同一个事件(唯一的问题是客户端内存和性能问题)。方法的另一个重要特性addEventListener是 final 参数,它控制侦听器对冒泡事件的反应,使用内联事件时没有等效项。

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

    了解更多关于events bubbling.

  • 为了允许动态创建的元素被事件捕获,例如select元素上的“更改”事件,我们需要将事件附加到document并检查应该执行哪个处理函数。换句话说,我们将使用Event Delegqtion.

    了解有关 的更多信息Event Delegation,该 Stackoverflow 帖子可能会对您有所帮助。

  • 在我的回答中,您代码中的一些变量将不再用作no_of_products变量,而其他一些变量(我希望您自己会注意到它们)。

  • 我从元素中删除了所有多余ID的 s 属性,特别是在button、 theinput和s 上,因为 an在页面中必须是唯一的。此外,我删除了所有内联事件处理程序,因为我们将使用该方法。selectdiv.productIDaddEventListener

  • 正如我所说,内联事件处理程序将被addEventListener方法替换,并且一些变量不再有用,要检查应该执行哪个处理程序函数,我们将依赖Event'target属性来查看哪个元素是目标当前事件,因此我们将知道应该执行哪个处理函数。

    了解更多关于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.

希望我把你推得更远。

于 2018-09-09T15:29:45.097 回答
2

不幸的是,您的代码中有很多错误。因此,我将仅描述重要的错误:

  1. 元素的id属性在完整的 HTML 页面中必须是唯一的。如果你克隆了一些元素并且它有一个 id 属性,那么你必须创建一个新的 id。因为你希望id从按钮中删除属性——所以我为你做了。
  2. 不要element.disable用于禁用元素。大多数浏览器都支持它,但它不是标准的。如果您查看 forElement和 for的文档,Node您将看到它们没有此属性。有些浏览器不支持它。用于这种情况Element.setAttribute()Element.removeAttribute()功能。
  3. 下一个产品行的添加按钮不能在产品行中。
  4. float: left如果不需要,请不要使用 CSS 属性。在您的情况下,您不需要它,因为您有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>

于 2018-09-09T20:57:36.033 回答