如何使用 Bootstrap 模态和按钮制作可重复使用的确认控件?

Posted

技术标签:

【中文标题】如何使用 Bootstrap 模态和按钮制作可重复使用的确认控件?【英文标题】:How could I make a reusable confirmation control using Bootstrap Modals and buttons? 【发布时间】:2019-10-08 22:41:41 【问题描述】:

目前,我正在尝试通过打开 Bootstrap 模态以及将这些相同的模态用于不同的功能来使用户能够确认某些内容。在这个问题中,我将使用“计算器”类型的元素作为附加功能的示例。就目前而言,除非我将 JQuery 的 $target.off(); 添加到 targetModal.on("hidden.bs.modal", function (e) );,否则确认将不起作用。

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

应该发生什么:

    选择“否”时,按钮(用于“No”显然)应保持相同的颜色,但相反的按钮(“是”按钮)应转动灰色和“确认?”按钮应该被禁用(如果还没有)。此外,如果 <button class="btn btn-warning pending">Pending</button> 可见,则应将其隐藏。 选择“是”时,按钮(对于“是”)应保持相同的颜色,但相反的按钮(“否”按钮)应该变灰,“确认?”按钮应启用。 什么时候“确认?”单击按钮时,它应该使用与确认相关的任何内容填充模态并打开给定的模态。 当模式退出/关闭确认没有完成确认时,函数WillClose()(这反过来应该改变给定的待定按钮文本应执行“重试”的确认控制)。 成功执行确认后(通过单击给定模式中的“确认”按钮),模式应隐藏/消失(并重置内部内容)并执行ConfirmModal() 功能(这反过来应该禁用所有按钮对于给定的确认控件并将待定按钮文本更改为“已确认”)。 这些确认中的任何一个都应该彼此独立工作。

发生了什么:

除非我将$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】:

问题现在:当你使用时

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

OpenConfirmModal 函数中,每次打开对话框时都会附加一个新的事件处理程序。这会导致以下情况:

    对话框已打开并附加了事件处理程序。 对话框关闭并执行一个事件处理程序。 对话框打开并附加了另一个事件处理程序。 对话框关闭并执行两个事件处理程序。 对话框打开并附加了另一个事件处理程序。 对话框关闭并执行三个事件处理程序。

等等。确认也有类似的问题。我已经修改了代码,只添加了一个控制台日志来演示这一点——打开和关闭使控制台的日志越来越多:

//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

改为使用.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>

【讨论】:

但是为什么它在确认任何确认但首先确认时将第一个待处理按钮文本从“重试”更改为确认?我觉得我仍然需要添加一些代码来将它们分开:Step 1 -> Step 2 -> Step 3 @Barrosy 因为你做了ele.parentNode.querySelectorAll(pendingSelector) 并且抓住了所有的按钮。您需要通过某种方式取消现在不需要的按钮的资格。 我尝试使用单击的元素(又名this),然后查找其父容器并在该容器中找到挂起的按钮。那么为什么在上面的代码中它会从“不同的父容器”中获取一个挂起的按钮呢? @Barrosy 啊,我现在想起来了。还有一个问题是处理程序会被附加,但如果它没有被执行,就会留下来。我见过一次,直到现在我才意识到。发生的情况是您打开 -> 使用 .one 附加 confirmation -> 关闭而不确认,因此处理程序保持不变 -> 打开对话框 -> 使用 .one 附加另一个处理程序 -> 如果您点击“确认”,现在两者都将被执行。 @Barrosy 呃,不幸的是我现在没有时间,我最早可能会在星期一回来看看这个。我必须在这里重新设计我以前的解决方案,因为我不记得它是什么了。问题正如我上面所描述的 - 你会得到附加的事件处理程序但没有被触发,所以即使它们会执行一次,它们也会徘徊,你会得到多个处理程序,每个处理程序执行一次并销毁他们自己。我不记得我做了什么,但你必须管理处理程序并在它们不触发时删除它们。也许这会帮助您在我回来之前找到解决方案。

以上是关于如何使用 Bootstrap 模态和按钮制作可重复使用的确认控件?的主要内容,如果未能解决你的问题,请参考以下文章

bootstrap模态框背景灰色,按钮全部无法点击,求解决办法

简单的bootstrap模态框问题

如何在 Bootstrap 4 模态表单中重置单选按钮和复选框? [复制]

我如何使用 Bootstrap4 模态在 Django 中制作功能正常的删除确认弹出窗口

C# 模态框传值

可重复使用的远程模态 Rails