如何使用Bootstrap模态框和按钮创建可重复使用的确认控件?

4

目前我正在尝试使用Bootstrap模态框实现用户确认某些操作,并将这些相同的模态框用于不同的功能。在本问题中,我将使用“计算器”元素作为附加功能的示例。目前情况下,除非我在targetModal.on("hidden.bs.modal", function (e) {});中添加JQuery的$target.off();,否则确认将无法生效。

当我添加这段JQuery代码时,它会导致我在此页面上使用的任何其他功能(关于Bootstrap模态框)也出现故障,这意味着我需要为任何这些功能添加额外的代码。我不希望发生这种情况。如何使这些确认正常工作,同时保持其他功能正常工作而不为这些功能添加额外的代码?

应该发生什么:

  1. 当选择“否”时,“否”按钮(显然是)应保持相同颜色,但相反的按钮(“是”按钮)应变为灰色,并且“确认?”按钮应变为禁用状态(如果尚未)。如果可见,则应隐藏<button class="btn btn-warning pending">Pending</button>
  2. 当选择“是”时,“是”按钮应保持相同颜色,但相反的按钮(“否”按钮)应变为灰色,并且“确认?”按钮应变为启用状态。
  3. 单击“确认?”按钮时,应使用与确认有关的任何内容填充模态框并打开给定模态框。
  4. 在确认期间未完成确认时(退出/关闭模态框),应执行WillClose()函数(这将更改给定确认控件的待处理按钮文本为“重试”)。
  5. 成功执行确认后(通过单击给定模态框内的“确认”按钮),模态框应隐藏/消失(并重置其中的内容)并执行ConfirmModal()函数(这将禁用给定确认控件的所有按钮并将待处理按钮文本更改为“已确认”)。
  6. 任何这些确认都应独立工作。

正在发生的情况:

确认元素触发彼此的待处理按钮,除非我向模态框添加$target.off()(例如targetModal.off())。然而,如果这样做,其他功能将会“出现故障”。我的意思是,无论将要在模态框中“填充”、“插入”或“克隆”什么(你想如何称呼它),它都会被放置在模态框中多次(好像模态框没有重置一样,明白吗?)。

如何使以下内容正常工作?

//Fields:

//Yes selector
const positiveSelector = ".positive";
//No selector
const negativeSelector = ".negative";
//Confirm? selector
const confirmSelector = ".init-confirm";
//Pending selector
const pendingSelector = ".pending";
//calTrigger selector
const calcTriggerSelector = ".calc-trigger > button";
//Yes elements
const positiveNodes = document.querySelectorAll(positiveSelector);
//No elements
const negativeNodes = document.querySelectorAll(negativeSelector);
//Confirm? elements
const confirmNodes = document.querySelectorAll(confirmSelector);
//Pending elements
const pendingNodes = document.querySelectorAll(pendingSelector);
//calcTrigger elements
const calcTriggerNodes = document.querySelectorAll(calcTriggerSelector);

//Modal
const targetModalSelector = "#bs-modal-xl";
const targetModal = $(targetModalSelector);
const $modalInit = targetModal.html();

//Eventlisteners:

positiveNodes.forEach(node => node.addEventListener("click", function () {
  EnableConfirmBtn(this);
}));

negativeNodes.forEach(node => node.addEventListener("click", function () {
  DisableConfirmBtn(this);
}));

confirmNodes.forEach(node => node.addEventListener("click", function () {
  OpenConfirmModal(this);
}));

calcTriggerNodes.forEach(node => node.addEventListener("click", calcTrigger));

//Reset modal when closing
targetModal.on("hidden.bs.modal", function () {
  targetModal.html($modalInit);
});

//Methods:

function EnableConfirmBtn(ele) {
  ele.classList.add("btn-success");
  ele.parentNode.querySelectorAll(negativeSelector).forEach(node => node.classList.remove("btn-warning"));
  ele.parentNode.parentNode.querySelectorAll(confirmSelector).forEach(node => node.removeAttribute("disabled"));
}

function DisableConfirmBtn(ele) {
  ele.classList.add("btn-warning");
  ele.parentNode.querySelectorAll(positiveSelector).forEach(node => node.classList.remove("btn-success"));
  ele.parentNode.parentNode.querySelectorAll(confirmSelector).forEach(node => node.setAttribute("disabled", ""));
}

function OpenConfirmModal(ele) {
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.style.display = "inline-block");
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.classList.remove("btn-warning"));
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.classList.add("btn-danger"));
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.textContent = "Pending");

  $(targetModalSelector + " .modal-body").html($(".clone-one").clone());
  $(targetModalSelector + " .clone-one").show();
  $(targetModalSelector + " h4.modal-title").text("");
  $(targetModalSelector + " .modal-content .modal-footer").html("");
  targetModal.modal();

  targetModal.on("click", ".clone-one", function () {
    targetModal.modal("hide");
    ConfirmModal(ele);
  });

  targetModal.on("hidden.bs.modal", function (e) {
    WillClose(ele);
    //Make use of targetModal.off(); here? <--
    //targetModal.off();
    targetModal.html($modalInit);
  });
}

function ConfirmModal(ele) {
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.textContent = "Confirmed");
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.classList.remove("btn-danger", "btn-warning"));
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.classList.add("btn-success"));
  ele.parentNode.querySelectorAll(confirmSelector).forEach(node => node.style.display = "none");
  ele.parentNode.querySelectorAll(".btn-group > button").forEach(node => node.setAttribute("disabled", ""));
}

function WillClose(ele) {
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.textContent = "Try again");
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.classList.remove("btn-danger"));
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.classList.add("btn-warning"));
}

function calcTrigger() {
  ModalHandler($(".calc").clone(), "", "", true, true);
  $(targetModalSelector + " .calc").show();
  targetModal.modal();

  document.querySelectorAll(targetModalSelector + " .calc #number-one-btn").forEach(node => node.addEventListener("click", function () {
    document.querySelectorAll(targetModalSelector + " .calc .result-container input").forEach(node => node.value += "1");
  }));

  document.querySelectorAll(targetModalSelector + " .calc #number-two-btn").forEach(node => node.addEventListener("click", function () {
    document.querySelectorAll(targetModalSelector + " .calc .result-container input").forEach(node => node.value += "2");
  }));

  document.querySelectorAll(targetModalSelector + " .calc #number-three-btn").forEach(node => node.addEventListener("click", function () {
    document.querySelectorAll(targetModalSelector + " .calc .result-container input").forEach(node => node.value += "3");
  }));
  
  //I would not want to be using something like this:

  //Reset modal when closing
  //targetModal.on("hidden.bs.modal", function () {
  //targetModal.off();
  //targetModal.html($modalInit);
  //});
}

//Modal handling (not required when not using Modal):
function ModalHandler(content, title, footer = "", bigCloseBtn = false, emptyFooter = false) {
  $(targetModalSelector + " h4.modal-title").text(title);
  $(targetModalSelector + " .modal-body").html(content);

  if (footer != "" && footer != undefined) {
    $(targetModalSelector + " .modal-footer").html(footer);
  }

  if (bigCloseBtn) {
    $(targetModalSelector + " .modal-content .modal-header button.close").css("float", "right");
    $(targetModalSelector + " .modal-content .modal-header button.close").addClass("btn btn-lg btn-danger");
    //$(".modal .modal-content .modal-header button.close").html("close");
    $(targetModalSelector + " .modal-content .modal-header button.close").removeClass("close");
  }

  if (emptyFooter) {
    $(targetModalSelector + " .modal-content .modal-footer").html("");
  }
}
#foo-container {
    padding: 5px;
  }

  .pending {
    display: none;
  }

  .clone-one, .calc {
    display: none;
  }

  .calc {
    width: 100%;
  }

  .calc button, .calc .result-container {
    margin-top: 3px;
    margin-bottom: 3px;
  }

  .calc [class*="col-"] {
    padding-left: 3px;
    padding-right: 3px;
  }
<link href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<div class="modal" id="bs-modal-xl" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel">
  <div class="modal-dialog modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <h4 class="modal-title">Modal title</h4>
      </div>
      <div class="modal-body">
        <p>One fine body&hellip;</p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
        <button type="button" class="btn btn-primary">Save changes</button>
      </div>
    </div>
  </div>
</div>

<div id="calc" class="calc">
  <form>
    <div class="row">
      <div class="col-xs-9">
        <div class="result-container">
          <input type="text" class="form-control" disabled>
        </div>
      </div>
      <div class="col-xs-3">
        <button type="button" class="btn btn-default btn-block" value="x" id="">
          <span class="glyphicon glyphicon-remove"></span>
        </button>
      </div>
    </div>
    <div class="row">
      <div class="col-xs-3">
        <button type="button" class="btn btn-default btn-block" value="1" id="number-one-btn">
          1
        </button>
      </div>
      <div class="col-xs-3">
        <button type="button" class="btn btn-default btn-block" value="2" id="number-two-btn">
          2
        </button>
      </div>
      <div class="col-xs-3">
        <button type="button" class="btn btn-default btn-block" value="3" id="number-three-btn">
          3
        </button>
      </div>
      <div class="col-xs-3">
        <button type="button" class="btn btn-default btn-block" value="" id="number-one-btn">
          <span class="glyphicon glyphicon-arrow-left"></span>
        </button>
      </div>
    </div>
  </form>
</div>

<div class="container">
  <div id="foo-container">
    <div class="confirmation-box">
      <div class="btn-group btn-group-lg" role="group" aria-label="...">
        <button type="button" class="btn btn-success positive">Yes</button>
        <button type="button" class="btn btn-warning negative">No</button>
      </div>
      <button type="button" class="btn btn-lg btn-danger init-confirm" disabled>Confirm?</button>
      <button type="button" class="btn btn-lg btn-danger pending" disabled>Pending</button>
    </div>

    <br />

    <div class="confirmation-box">
      <div class="btn-group btn-group-lg" role="group" aria-label="...">
        <button type="button" class="btn btn-success positive">Yes</button>
        <button type="button" class="btn btn-warning negative">No</button>
      </div>
      <button type="button" class="btn btn-lg btn-danger init-confirm" disabled>Confirm?</button>
      <button type="button" class="btn btn-lg btn-danger pending" disabled>Pending</button>
    </div>

    <br />

    <div class="calc-trigger">
      <button class="btn btn-lg btn-default">
        Calc trigger
      </button>
    </div>
  </div>

  <div class="clone-one">
    <button type="button" class="btn btn-lg btn-success">Clicky</button>
  </div>
</div>

JSFiddle

我只是希望找到一种尽可能少写代码的解决方案。如果所给出的示例可以重写得更小,请告诉我。

编辑:我知道上面提供的代码每次单击某些控件时都会添加多个事件侦听器,从而引起问题。因此,我正在寻找一种(尽可能简单)的解决方案,以使所有这些控件可以独立工作,同时保持脚本不显眼,并尽量保留上面提供的代码格式。

1个回答

4

目前的问题:当您使用

targetModal.on("hidden.bs.modal", function (e) {
  WillClose(ele);
  //Make use of targetModal.off(); here? <--
  //targetModal.off();
  targetModal.html($modalInit);
});

OpenConfirmModal函数中,每次打开对话框都会附加一个新的事件处理程序。这导致以下情况:
  1. 对话框被打开,并且事件处理程序被附加。
  2. 对话框被关闭并执行了一个事件处理程序。
  3. 对话框被打开,并附加另一个事件处理程序。
  4. 对话框被关闭并执行了两个事件处理程序。
  5. 对话框被打开,并附加另一个事件处理程序。
  6. 对话框被关闭并执行了三个事件处理程序。

类似的问题也存在于确认框中。我已经修改了代码以添加控制台日志来演示这一点 - 打开和关闭会在控制台上产生越来越多的日志:

//Fields:

//Yes selector
const positiveSelector = ".positive";
//No selector
const negativeSelector = ".negative";
//Confirm? selector
const confirmSelector = ".init-confirm";
//Pending selector
const pendingSelector = ".pending";
//calTrigger selector
const calcTriggerSelector = ".calc-trigger > button";
//Yes elements
const positiveNodes = document.querySelectorAll(positiveSelector);
//No elements
const negativeNodes = document.querySelectorAll(negativeSelector);
//Confirm? elements
const confirmNodes = document.querySelectorAll(confirmSelector);
//Pending elements
const pendingNodes = document.querySelectorAll(pendingSelector);
//calcTrigger elements
const calcTriggerNodes = document.querySelectorAll(calcTriggerSelector);

//Modal
const targetModalSelector = "#bs-modal-xl";
const targetModal = $(targetModalSelector);
const $modalInit = targetModal.html();

//Eventlisteners:

positiveNodes.forEach(node => node.addEventListener("click", function () {
  EnableConfirmBtn(this);
}));

negativeNodes.forEach(node => node.addEventListener("click", function () {
  DisableConfirmBtn(this);
}));

confirmNodes.forEach(node => node.addEventListener("click", function () {
  OpenConfirmModal(this);
}));

calcTriggerNodes.forEach(node => node.addEventListener("click", calcTrigger));

//Reset modal when closing
targetModal.on("hidden.bs.modal", function () {
  targetModal.html($modalInit);
});

//Methods:

function EnableConfirmBtn(ele) {
  ele.classList.add("btn-success");
  ele.parentNode.querySelectorAll(negativeSelector).forEach(node => node.classList.remove("btn-warning"));
  ele.parentNode.parentNode.querySelectorAll(confirmSelector).forEach(node => node.removeAttribute("disabled"));
}

function DisableConfirmBtn(ele) {
  ele.classList.add("btn-warning");
  ele.parentNode.querySelectorAll(positiveSelector).forEach(node => node.classList.remove("btn-success"));
  ele.parentNode.parentNode.querySelectorAll(confirmSelector).forEach(node => node.setAttribute("disabled", ""));
}

function OpenConfirmModal(ele) {
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.style.display = "inline-block");
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.classList.remove("btn-warning"));
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.classList.add("btn-danger"));
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.textContent = "Pending");

  $(targetModalSelector + " .modal-body").html($(".clone-one").clone());
  $(targetModalSelector + " .clone-one").show();
  $(targetModalSelector + " h4.modal-title").text("");
  $(targetModalSelector + " .modal-content .modal-footer").html("");
  targetModal.modal();

  targetModal.on("click", ".clone-one", function () {
    targetModal.modal("hide");
    console.log("hiding")
    ConfirmModal(ele);
  });

  targetModal.on("hidden.bs.modal", function (e) {
    WillClose(ele);
    console.log("closing");
    //Make use of targetModal.off(); here? <--
    //targetModal.off();
    targetModal.html($modalInit);
  });
}

function ConfirmModal(ele) {
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.textContent = "Confirmed");
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.classList.remove("btn-danger", "btn-warning"));
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.classList.add("btn-success"));
  ele.parentNode.querySelectorAll(confirmSelector).forEach(node => node.style.display = "none");
  ele.parentNode.querySelectorAll(".btn-group > button").forEach(node => node.setAttribute("disabled", ""));
}

function WillClose(ele) {
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.textContent = "Try again");
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.classList.remove("btn-danger"));
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.classList.add("btn-warning"));
}

function calcTrigger() {
  ModalHandler($(".calc").clone(), "", "", true, true);
  $(targetModalSelector + " .calc").show();
  targetModal.modal();

  document.querySelectorAll(targetModalSelector + " .calc #number-one-btn").forEach(node => node.addEventListener("click", function () {
    document.querySelectorAll(targetModalSelector + " .calc .result-container input").forEach(node => node.value += "1");
  }));

  document.querySelectorAll(targetModalSelector + " .calc #number-two-btn").forEach(node => node.addEventListener("click", function () {
    document.querySelectorAll(targetModalSelector + " .calc .result-container input").forEach(node => node.value += "2");
  }));

  document.querySelectorAll(targetModalSelector + " .calc #number-three-btn").forEach(node => node.addEventListener("click", function () {
    document.querySelectorAll(targetModalSelector + " .calc .result-container input").forEach(node => node.value += "3");
  }));
  
  //I would not want to be using something like this:

  //Reset modal when closing
  //targetModal.on("hidden.bs.modal", function () {
  //targetModal.off();
  //targetModal.html($modalInit);
  //});
}

//Modal handling (not required when not using Modal):
function ModalHandler(content, title, footer = "", bigCloseBtn = false, emptyFooter = false) {
  $(targetModalSelector + " h4.modal-title").text(title);
  $(targetModalSelector + " .modal-body").html(content);

  if (footer != "" && footer != undefined) {
    $(targetModalSelector + " .modal-footer").html(footer);
  }

  if (bigCloseBtn) {
    $(targetModalSelector + " .modal-content .modal-header button.close").css("float", "right");
    $(targetModalSelector + " .modal-content .modal-header button.close").addClass("btn btn-lg btn-danger");
    //$(".modal .modal-content .modal-header button.close").html("close");
    $(targetModalSelector + " .modal-content .modal-header button.close").removeClass("close");
  }

  if (emptyFooter) {
    $(targetModalSelector + " .modal-content .modal-footer").html("");
  }
}
#foo-container {
    padding: 5px;
  }

  .pending {
    display: none;
  }

  .clone-one, .calc {
    display: none;
  }

  .calc {
    width: 100%;
  }

  .calc button, .calc .result-container {
    margin-top: 3px;
    margin-bottom: 3px;
  }

  .calc [class*="col-"] {
    padding-left: 3px;
    padding-right: 3px;
  }
<link href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<div class="modal" id="bs-modal-xl" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel">
  <div class="modal-dialog modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <h4 class="modal-title">Modal title</h4>
      </div>
      <div class="modal-body">
        <p>One fine body&hellip;</p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
        <button type="button" class="btn btn-primary">Save changes</button>
      </div>
    </div>
  </div>
</div>

<div id="calc" class="calc">
  <form>
    <div class="row">
      <div class="col-xs-9">
        <div class="result-container">
          <input type="text" class="form-control" disabled>
        </div>
      </div>
      <div class="col-xs-3">
        <button type="button" class="btn btn-default btn-block" value="x" id="">
          <span class="glyphicon glyphicon-remove"></span>
        </button>
      </div>
    </div>
    <div class="row">
      <div class="col-xs-3">
        <button type="button" class="btn btn-default btn-block" value="1" id="number-one-btn">
          1
        </button>
      </div>
      <div class="col-xs-3">
        <button type="button" class="btn btn-default btn-block" value="2" id="number-two-btn">
          2
        </button>
      </div>
      <div class="col-xs-3">
        <button type="button" class="btn btn-default btn-block" value="3" id="number-three-btn">
          3
        </button>
      </div>
      <div class="col-xs-3">
        <button type="button" class="btn btn-default btn-block" value="" id="number-one-btn">
          <span class="glyphicon glyphicon-arrow-left"></span>
        </button>
      </div>
    </div>
  </form>
</div>

<div class="container">
  <div id="foo-container">
    <div class="confirmation-box">
      <div class="btn-group btn-group-lg" role="group" aria-label="...">
        <button type="button" class="btn btn-success positive">Yes</button>
        <button type="button" class="btn btn-warning negative">No</button>
      </div>
      <button type="button" class="btn btn-lg btn-danger init-confirm" disabled>Confirm?</button>
      <button type="button" class="btn btn-lg btn-danger pending" disabled>Pending</button>
    </div>

    <br />

    <div class="confirmation-box">
      <div class="btn-group btn-group-lg" role="group" aria-label="...">
        <button type="button" class="btn btn-success positive">Yes</button>
        <button type="button" class="btn btn-warning negative">No</button>
      </div>
      <button type="button" class="btn btn-lg btn-danger init-confirm" disabled>Confirm?</button>
      <button type="button" class="btn btn-lg btn-danger pending" disabled>Pending</button>
    </div>

    <br />

    <div class="calc-trigger">
      <button class="btn btn-lg btn-default">
        Calc trigger
      </button>
    </div>
  </div>

  <div class="clone-one">
    <button type="button" class="btn btn-lg btn-success">Clicky</button>
  </div>
</div>

你可以使用.off来避免这种情况,但是这并不是最好的选择,因为它实际上使代码更加笨拙。

  • 当调用.off时,您只需要指定一个事件,但是这将删除该事件的所有处理程序。 targetModal.off("hidden.bs.modal");将适用于简单情况,但您可能会从其他地方添加第二个处理程序,该处理程序可以正确运行(它不会不断重新添加),并且也将被清除。
  • 如果使用特定处理程序调用.off来删除,则需要对处理程序进行引用。这更有用,但更加笨拙 - 你的代码将实际上看起来像这样:
var handler = function (e) {
//  ^^^^^^^------------------------------------------------------------------ <-   
  WillClose(ele);                                  //                          |
  targetModal.off("hidden.bs.modal", "*" handler); // `handler` references -> -^
//                                    ^--------------
  targetModal.html($modalInit);//                   |  these need to match because 
}//                                                 |  .off() requires a second parameter
//                                                  |  parameter to use the handler ref
targetModal.on("hidden.bs.modal", "*", handler);//  |  this mandates that .on() also
//                                 ^ ----------------  uses the second parmeter

不要使用.on方法,而应该使用.one方法。它将.on和隐式的.off结合起来,将回调函数附加为事件处理程序,然后在执行一次后将其删除。这样,您就不必自己调用.off

下面是使用.one的代码示例 - 现在,如果您重复打开和关闭对话框,控制台只会记录一个日志,而不是基于您打开对话框的次数(n+1):

//Fields:

//Yes selector
const positiveSelector = ".positive";
//No selector
const negativeSelector = ".negative";
//Confirm? selector
const confirmSelector = ".init-confirm";
//Pending selector
const pendingSelector = ".pending";
//calTrigger selector
const calcTriggerSelector = ".calc-trigger > button";
//Yes elements
const positiveNodes = document.querySelectorAll(positiveSelector);
//No elements
const negativeNodes = document.querySelectorAll(negativeSelector);
//Confirm? elements
const confirmNodes = document.querySelectorAll(confirmSelector);
//Pending elements
const pendingNodes = document.querySelectorAll(pendingSelector);
//calcTrigger elements
const calcTriggerNodes = document.querySelectorAll(calcTriggerSelector);

//Modal
const targetModalSelector = "#bs-modal-xl";
const targetModal = $(targetModalSelector);
const $modalInit = targetModal.html();

//Eventlisteners:

positiveNodes.forEach(node => node.addEventListener("click", function () {
  EnableConfirmBtn(this);
}));

negativeNodes.forEach(node => node.addEventListener("click", function () {
  DisableConfirmBtn(this);
}));

confirmNodes.forEach(node => node.addEventListener("click", function () {
  OpenConfirmModal(this);
}));

calcTriggerNodes.forEach(node => node.addEventListener("click", calcTrigger));

//Reset modal when closing
targetModal.on("hidden.bs.modal", function () {
  targetModal.html($modalInit);
});

//Methods:

function EnableConfirmBtn(ele) {
  ele.classList.add("btn-success");
  ele.parentNode.querySelectorAll(negativeSelector).forEach(node => node.classList.remove("btn-warning"));
  ele.parentNode.parentNode.querySelectorAll(confirmSelector).forEach(node => node.removeAttribute("disabled"));
}

function DisableConfirmBtn(ele) {
  ele.classList.add("btn-warning");
  ele.parentNode.querySelectorAll(positiveSelector).forEach(node => node.classList.remove("btn-success"));
  ele.parentNode.parentNode.querySelectorAll(confirmSelector).forEach(node => node.setAttribute("disabled", ""));
}

function OpenConfirmModal(ele) {
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.style.display = "inline-block");
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.classList.remove("btn-warning"));
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.classList.add("btn-danger"));
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.textContent = "Pending");

  $(targetModalSelector + " .modal-body").html($(".clone-one").clone());
  $(targetModalSelector + " .clone-one").show();
  $(targetModalSelector + " h4.modal-title").text("");
  $(targetModalSelector + " .modal-content .modal-footer").html("");
  targetModal.modal();
  
  targetModal.one("click", ".clone-one", function () {
  //one --------^
    targetModal.modal("hide");
    console.log("hide")
    ConfirmModal(ele);
  });

  targetModal.one("hidden.bs.modal", function (e) {
  //one --------^
    WillClose(ele);
    console.log("will close")
    targetModal.html($modalInit);
  });
}

function ConfirmModal(ele) {
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.textContent = "Confirmed");
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.classList.remove("btn-danger", "btn-warning"));
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.classList.add("btn-success"));
  ele.parentNode.querySelectorAll(confirmSelector).forEach(node => node.style.display = "none");
  ele.parentNode.querySelectorAll(".btn-group > button").forEach(node => node.setAttribute("disabled", ""));
}

function WillClose(ele) {
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.textContent = "Try again");
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.classList.remove("btn-danger"));
  ele.parentNode.querySelectorAll(pendingSelector).forEach(node => node.classList.add("btn-warning"));
}

function calcTrigger() {
  ModalHandler($(".calc").clone(), "", "", true, true);
  $(targetModalSelector + " .calc").show();
  targetModal.modal();

  document.querySelectorAll(targetModalSelector + " .calc #number-one-btn").forEach(node => node.addEventListener("click", function () {
    document.querySelectorAll(targetModalSelector + " .calc .result-container input").forEach(node => node.value += "1");
  }));

  document.querySelectorAll(targetModalSelector + " .calc #number-two-btn").forEach(node => node.addEventListener("click", function () {
    document.querySelectorAll(targetModalSelector + " .calc .result-container input").forEach(node => node.value += "2");
  }));

  document.querySelectorAll(targetModalSelector + " .calc #number-three-btn").forEach(node => node.addEventListener("click", function () {
    document.querySelectorAll(targetModalSelector + " .calc .result-container input").forEach(node => node.value += "3");
  }));
  
  //I would not want to be using something like this:

  //Reset modal when closing
  //targetModal.on("hidden.bs.modal", function () {
  //targetModal.off();
  //targetModal.html($modalInit);
  //});
}

//Modal handling (not required when not using Modal):
function ModalHandler(content, title, footer = "", bigCloseBtn = false, emptyFooter = false) {
  $(targetModalSelector + " h4.modal-title").text(title);
  $(targetModalSelector + " .modal-body").html(content);

  if (footer != "" && footer != undefined) {
    $(targetModalSelector + " .modal-footer").html(footer);
  }

  if (bigCloseBtn) {
    $(targetModalSelector + " .modal-content .modal-header button.close").css("float", "right");
    $(targetModalSelector + " .modal-content .modal-header button.close").addClass("btn btn-lg btn-danger");
    //$(".modal .modal-content .modal-header button.close").html("close");
    $(targetModalSelector + " .modal-content .modal-header button.close").removeClass("close");
  }

  if (emptyFooter) {
    $(targetModalSelector + " .modal-content .modal-footer").html("");
  }
}
#foo-container {
    padding: 5px;
  }

  .pending {
    display: none;
  }

  .clone-one, .calc {
    display: none;
  }

  .calc {
    width: 100%;
  }

  .calc button, .calc .result-container {
    margin-top: 3px;
    margin-bottom: 3px;
  }

  .calc [class*="col-"] {
    padding-left: 3px;
    padding-right: 3px;
  }
<link href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<div class="modal" id="bs-modal-xl" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel">
  <div class="modal-dialog modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <h4 class="modal-title">Modal title</h4>
      </div>
      <div class="modal-body">
        <p>One fine body&hellip;</p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
        <button type="button" class="btn btn-primary">Save changes</button>
      </div>
    </div>
  </div>
</div>

<div id="calc" class="calc">
  <form>
    <div class="row">
      <div class="col-xs-9">
        <div class="result-container">
          <input type="text" class="form-control" disabled>
        </div>
      </div>
      <div class="col-xs-3">
        <button type="button" class="btn btn-default btn-block" value="x" id="">
          <span class="glyphicon glyphicon-remove"></span>
        </button>
      </div>
    </div>
    <div class="row">
      <div class="col-xs-3">
        <button type="button" class="btn btn-default btn-block" value="1" id="number-one-btn">
          1
        </button>
      </div>
      <div class="col-xs-3">
        <button type="button" class="btn btn-default btn-block" value="2" id="number-two-btn">
          2
        </button>
      </div>
      <div class="col-xs-3">
        <button type="button" class="btn btn-default btn-block" value="3" id="number-three-btn">
          3
        </button>
      </div>
      <div class="col-xs-3">
        <button type="button" class="btn btn-default btn-block" value="" id="number-one-btn">
          <span class="glyphicon glyphicon-arrow-left"></span>
        </button>
      </div>
    </div>
  </form>
</div>

<div class="container">
  <div id="foo-container">
    <div class="confirmation-box">
      <div class="btn-group btn-group-lg" role="group" aria-label="...">
        <button type="button" class="btn btn-success positive">Yes</button>
        <button type="button" class="btn btn-warning negative">No</button>
      </div>
      <button type="button" class="btn btn-lg btn-danger init-confirm" disabled>Confirm?</button>
      <button type="button" class="btn btn-lg btn-danger pending" disabled>Pending</button>
    </div>

    <br />

    <div class="confirmation-box">
      <div class="btn-group btn-group-lg" role="group" aria-label="...">
        <button type="button" class="btn btn-success positive">Yes</button>
        <button type="button" class="btn btn-warning negative">No</button>
      </div>
      <button type="button" class="btn btn-lg btn-danger init-confirm" disabled>Confirm?</button>
      <button type="button" class="btn btn-lg btn-danger pending" disabled>Pending</button>
    </div>

    <br />

    <div class="calc-trigger">
      <button class="btn btn-lg btn-default">
        Calc trigger
      </button>
    </div>
  </div>

  <div class="clone-one">
    <button type="button" class="btn btn-lg btn-success">Clicky</button>
  </div>
</div>


但是为什么在确认任何确认但第一个之外的确认时,它会将第一个待处理按钮文本从“重试”更改为“已确认”?我觉得我仍然需要添加一些代码来保持这些分开:[步骤1](https://i.imgur.com/JOiIWo0.png) - > [步骤2](https://i.imgur.com/yEJ7sAl.png) - > [步骤3](https://i.imgur.com/3dbXYSF.png) - Barrosy
@Barrosy 因为你使用了 ele.parentNode.querySelectorAll(pendingSelector),这会捕获所有的按钮。你需要有一种方法来排除当前不需要的按钮。 - VLAZ
我尝试使用点击的元素(也称为 this)并查找其父容器,然后在该容器内查找挂起按钮。那么为什么上面的代码会从“不同的父容器”获取挂起按钮呢? - Barrosy
@Barrosy 很遗憾,我现在没有时间,最早可能要到周一才能看一下。我必须重新设计我的上一个解决方案,因为我记不起来它是什么了。问题就像我上面描述的那样 - 你会得到已附加但未触发的事件处理程序,所以即使它们执行了一次,它们会挂起并且你会得到多个处理程序,每个执行一次并销毁自己。我记不起来我做了什么,但你必须管理这些处理程序,如果它们没有触发就要将其删除。也许在我回来之前,这会帮助你找到一个解决方案。 - VLAZ
是的,我正在尝试我所能做的一切来解决这个问题,但即使有你提供的帮助,我似乎还是很困难。我尝试解决这个问题,但却导致另一个问题出现。看起来这是一个简单的确认,但却奇怪地很难让它无故障运行。由于某种原因,代码将“OpenConfirmModal”中定义的“ele”参数设置为先前点击的确认按钮,我真的不知道为什么会发生这种情况,但我会尝试调试看看是否可以找到任何问题。 - Barrosy
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接