Bootstrap 5 模态和 jQuery 验证

Bootstrap 5 Modal and jQuery Validation

我正在尝试将项目添加到动态拖放列表(来自 here 的 Nestable 2,我已经很清楚了。

我正在使用 Bootstrap 5 模式来获取输入。也在工作。

现在我在表单输入中添加 jQuery validation。就验证而言,这也有效。

但是,当我添加第二个项目时,我添加了两个新项目。第三次尝试时,我添加了三个项目。

这是我的表单提交代码:

// Enable drag and drop nest structure
$('#navTree').nestable({maxDepth: 2}).on();

// Last ID of current nav tree - needed to add new nav items - passed from server side as hidden input value
var lastID = $('#lastID').val();

console.log('Start ID: ' + lastID);

// jQuery Validator - Validation Settings for AddNav form
var validatorAddNav = $("#frmAddNav").validate(
{
    rules:
    {
        formPage:
        {
            required: true,
            letterswithbasicpunc: true,
            rangelength: [3, 20]
        }
    },
    errorPlacement: function(error, element)
    {
      errorInsert = "#" + element.attr("name") + "Error";
      error.appendTo(errorInsert);
    },
    highlight: function(element){$(element).addClass("is-invalid").removeClass("is-valid");},
    unhighlight: function(element){$(element).addClass("is-valid").removeClass("is-invalid");}
});

// AddNav Modal Activated
$("#modalAddNav").on('shown.bs.modal',function()
{
    console.log('AddNav Modal Shown');

    // Set AddNav form focus
    $(this).find('#formPage').focus();

    $('#btnAddNav').click(function(e)
    {
        e.preventDefault(); // Prevent default POST submit

        if ($("#frmAddNav").valid())
        {
            var newID   = ++lastID;
            var newPage = $('#formPage').val();
            
            console.log('Add Button on ID: ' + newID);

            var newLI = 
            '<li id="navItem' + newID + '" class="dd-item dd3-item" data-file="file' + newID + '.php" data-nav="Nav ' + newID + '" data-page="Page ' + newID + '" data-id="' + newID + '">' +
            '  <div class="dd-handle dd3-handle">Drag</div>' +
            '  <div class="dd3-content"><div id="entry1">' + newPage + ' ' + newID + '</div></div>' +
            '</li>';

            $('#modalAddNav').modal('hide'); // Close Modal
            validatorAddNav.resetForm();

            $('#navTree > .dd-list').append(newLI); // Add Nav Item to Tree
        };
    });
});

// Save Nav Tree - this is where form validation and AJAX will need to go
$('#ajaxSave').click(function(e)
{
  var navTree = JSON.stringify($('#navTree').nestable('serialize'));
  console.log('Nav Tree:\n' + navTree + '\n\n');
});
.dd 
{
    max-width: 700px;
}
/** Nestable Draggable Handles */
.dd3-content 
{
    display: block;
    height: 30px;
    margin: 5px 0;
    padding: 5px 10px 5px 40px;
    color: #333;
    text-decoration: none;
    font-weight: bold;
    border: 1px solid #ccc;
    background: #fafafa;
    background: -webkit-linear-gradient(top, #fafafa 0%, #eee 100%);
    background: -moz-linear-gradient(top, #fafafa 0%, #eee 100%);
    background: linear-gradient(top, #fafafa 0%, #eee 100%);
    -webkit-border-radius: 3px;
    border-radius: 3px;
    box-sizing: border-box;
    -moz-box-sizing: border-box;
}
.dd3-content:hover 
{
    color: #2ea8e5;
    background: #fff;
}
.dd-dragel > .dd3-item > .dd3-content 
{
    margin: 0;
}
.dd3-item > button 
{
    margin-left: 30px;
}
.dd3-handle 
{
    position: absolute;
    margin: 0;
    left: 0;
    top: 0;
    cursor: pointer;
    width: 30px;
    text-indent: 30px;
    white-space: nowrap;
    overflow: hidden;
    border: 1px solid #aaa;
    background: #ddd;
    background: -webkit-linear-gradient(top, #ddd 0%, #bbb 100%);
    background: -moz-linear-gradient(top, #ddd 0%, #bbb 100%);
    background: linear-gradient(top, #ddd 0%, #bbb 100%);
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}
.dd3-handle:before 
{
    content: '≡';
    display: block;
    position: absolute;
    left: 0;
    top: 3px;
    width: 100%;
    text-align: center;
    text-indent: 0;
    color: #fff;
    font-size: 20px;
    font-weight: normal;
}
.dd3-handle:hover 
{
    background: #ddd;
}
<html lang="en" class="h-100">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.css" integrity="sha512-aOG0c6nPNzGk+5zjwyJaoRUgCdOrfSDhmMID2u4+OIslr0GjpLKo7Xm0Ao3xmpM4T8AmIouRkqwj1nrdVsLKEQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/nestable2/1.6.0/jquery.nestable.min.css" integrity="sha512-yOW3WV01iPnrQrlHYlpnfVooIAQl/hujmnCmiM3+u8F/6cCgA3BdFjqQfu8XaOtPilD/yYBJR3Io4PO8QUQKWA==" crossorigin="anonymous" referrerpolicy="no-referrer" />

    <!-- Include JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js" integrity="sha512-uto9mlQzrs59VwILcLiRYeLKPPbS/bT71da/OEBYEwcdNUk8jYIy+D176RYoop1Da+f9mvkYrmj5MCLZWEtQuA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/nestable2/1.6.0/jquery.nestable.min.js" integrity="sha512-7bS2beHr26eBtIOb/82sgllyFc1qMsDcOOkGK3NLrZ34yTbZX8uJi5sE0NNDYFNflwx1TtnDKkEq+k2DCGfb5w==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/additional-methods.min.js"></script>
    <title>PoC</title>
</head>
<body class="d-flex flex-column h-100">

<main class="flex-shrink-0">
  <div class="container-fluid">
    <menu id="navTreeMenu">
        <hr />
            <button type="button" class="btn btn-success btn-sm" data-bs-toggle="modal" data-bs-target="#modalAddNav">Add Nav Item</button>
          <button id="ajaxSave" type="button" class="btn btn-success btn-sm">Save</button>
        <hr />
      </menu>
      <div class="dd" id="navTree">
      <ol class="dd-list">
        <li id="navItem1" class="dd-item dd3-item" data-file="file1.php" data-nav="Nav 1" data-page="Page 1" data-id="1">
          <div class="dd-handle dd3-handle">Drag</div>
          <div class="dd3-content"><div id="entry1">Item 1</div></div>
        </li>
      </ol>
    </div>
    <input type="hidden" id="lastID" value="2">
  </div>
</main>

<!-- Start: Add Nav Item Modal -->
<form name="frmAddNav" id="frmAddNav" action="" method="post">
    <div class="modal fade" id="modalAddNav" tabindex="-1" aria-labelledby="modalAddNavTitle" aria-hidden="true">
        <div class="modal-dialog modal-dialog-centered">
            <div class="modal-content">
                <div class="modal-header alert-secondary">
                    <h5 class="modal-title" id="modalAddNavTitle">Add Navigation Item</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <div class="form-row my-1">
                        <div class="col">
                            <input type="text" name="formPage" id="formPage" class="form-control" placeholder="Page Title">
                            <div id="formPageError" class="d-flex justify-content-start">
                                <label id="formPage-error" class="error text-dark flex-fill bg-warning mt-1 rounded" for="formPage"></label>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="modal-footer text-right alert-secondary">
                    <button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
                    <input type="submit" name="btnAddNav" id="btnAddNav" class="btn btn-primary btn-sm" value="Add New">
                </div>
            </div>
        </div>
    </div>
</form>
<!-- End: Add Nav Item Modal -->

这里有一个 jsfiddle 显示了这种行为。

我哪里错了?

https://jsfiddle.net/glacierdave/5gron16s/7/

您的代码中的问题是每次出现模式时,您都会向其添加另一个点击事件。您只需要将点击事件移到 $("#modalAddNav").on('shown.bs.modal'....

之外

        // Enable drag and drop nest structure
        $('#navTree').nestable({ maxDepth: 2 }).on();

        // Last ID of current nav tree - needed to add new nav items - passed from server side as hidden input value
        var lastID = $('#lastID').val();

        console.log('Start ID: ' + lastID);

        // jQuery Validator - Validation Settings for AddNav form
        var validatorAddNav = $("#frmAddNav").validate(
            {
                rules:
                {
                    formPage:
                    {
                        required: true,
                        letterswithbasicpunc: true,
                        rangelength: [3, 20]
                    }
                },
                errorPlacement: function (error, element) {
                    errorInsert = "#" + element.attr("name") + "Error";
                    error.appendTo(errorInsert);
                },
                highlight: function (element) { $(element).addClass("is-invalid").removeClass("is-valid"); },
                unhighlight: function (element) { $(element).addClass("is-valid").removeClass("is-invalid"); }
            });

        // AddNav Modal Activated
        $("#modalAddNav").on('shown.bs.modal', function () {
            console.log('AddNav Modal Shown');

            // Set AddNav form focus
            $(this).find('#formPage').focus();
        });

        $('#btnAddNav').click(function (e) {
            e.preventDefault(); // Prevent default POST submit

            if ($("#frmAddNav").valid()) {
                var newID = ++lastID;
                var newPage = $('#formPage').val();

                console.log('Add Button on ID: ' + newID);

                var newLI =
                    '<li id="navItem' + newID + '" class="dd-item dd3-item" data-file="file' + newID + '.php" data-nav="Nav ' + newID + '" data-page="Page ' + newID + '" data-id="' + newID + '">' +
                    '  <div class="dd-handle dd3-handle">Drag</div>' +
                    '  <div class="dd3-content"><div id="entry1">' + newPage + ' ' + newID + '</div></div>' +
                    '</li>';

                $('#modalAddNav').modal('hide'); // Close Modal
                validatorAddNav.resetForm();

                $('#navTree > .dd-list').append(newLI); // Add Nav Item to Tree
            };
        });

        // Save Nav Tree - this is where form validation and AJAX will need to go
        $('#ajaxSave').click(function (e) {
            var navTree = JSON.stringify($('#navTree').nestable('serialize'));
            console.log('Nav Tree:\n' + navTree + '\n\n');
        });
.dd 
{
    max-width: 700px;
}
/** Nestable Draggable Handles */
.dd3-content 
{
    display: block;
    height: 30px;
    margin: 5px 0;
    padding: 5px 10px 5px 40px;
    color: #333;
    text-decoration: none;
    font-weight: bold;
    border: 1px solid #ccc;
    background: #fafafa;
    background: -webkit-linear-gradient(top, #fafafa 0%, #eee 100%);
    background: -moz-linear-gradient(top, #fafafa 0%, #eee 100%);
    background: linear-gradient(top, #fafafa 0%, #eee 100%);
    -webkit-border-radius: 3px;
    border-radius: 3px;
    box-sizing: border-box;
    -moz-box-sizing: border-box;
}
.dd3-content:hover 
{
    color: #2ea8e5;
    background: #fff;
}
.dd-dragel > .dd3-item > .dd3-content 
{
    margin: 0;
}
.dd3-item > button 
{
    margin-left: 30px;
}
.dd3-handle 
{
    position: absolute;
    margin: 0;
    left: 0;
    top: 0;
    cursor: pointer;
    width: 30px;
    text-indent: 30px;
    white-space: nowrap;
    overflow: hidden;
    border: 1px solid #aaa;
    background: #ddd;
    background: -webkit-linear-gradient(top, #ddd 0%, #bbb 100%);
    background: -moz-linear-gradient(top, #ddd 0%, #bbb 100%);
    background: linear-gradient(top, #ddd 0%, #bbb 100%);
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}
.dd3-handle:before 
{
    content: '≡';
    display: block;
    position: absolute;
    left: 0;
    top: 3px;
    width: 100%;
    text-align: center;
    text-indent: 0;
    color: #fff;
    font-size: 20px;
    font-weight: normal;
}
.dd3-handle:hover 
{
    background: #ddd;
}
<html lang="en" class="h-100">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.css" integrity="sha512-aOG0c6nPNzGk+5zjwyJaoRUgCdOrfSDhmMID2u4+OIslr0GjpLKo7Xm0Ao3xmpM4T8AmIouRkqwj1nrdVsLKEQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/nestable2/1.6.0/jquery.nestable.min.css" integrity="sha512-yOW3WV01iPnrQrlHYlpnfVooIAQl/hujmnCmiM3+u8F/6cCgA3BdFjqQfu8XaOtPilD/yYBJR3Io4PO8QUQKWA==" crossorigin="anonymous" referrerpolicy="no-referrer" />

    <!-- Include JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js" integrity="sha512-uto9mlQzrs59VwILcLiRYeLKPPbS/bT71da/OEBYEwcdNUk8jYIy+D176RYoop1Da+f9mvkYrmj5MCLZWEtQuA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/nestable2/1.6.0/jquery.nestable.min.js" integrity="sha512-7bS2beHr26eBtIOb/82sgllyFc1qMsDcOOkGK3NLrZ34yTbZX8uJi5sE0NNDYFNflwx1TtnDKkEq+k2DCGfb5w==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/additional-methods.min.js"></script>
    <title>PoC</title>
</head>
<body class="d-flex flex-column h-100">

<main class="flex-shrink-0">
  <div class="container-fluid">
    <menu id="navTreeMenu">
        <hr />
            <button type="button" class="btn btn-success btn-sm" data-bs-toggle="modal" data-bs-target="#modalAddNav">Add Nav Item</button>
          <button id="ajaxSave" type="button" class="btn btn-success btn-sm">Save</button>
        <hr />
      </menu>
      <div class="dd" id="navTree">
      <ol class="dd-list">
        <li id="navItem1" class="dd-item dd3-item" data-file="file1.php" data-nav="Nav 1" data-page="Page 1" data-id="1">
          <div class="dd-handle dd3-handle">Drag</div>
          <div class="dd3-content"><div id="entry1">Item 1</div></div>
        </li>
      </ol>
    </div>
    <input type="hidden" id="lastID" value="2">
  </div>
</main>

<!-- Start: Add Nav Item Modal -->
<form name="frmAddNav" id="frmAddNav" action="" method="post">
    <div class="modal fade" id="modalAddNav" tabindex="-1" aria-labelledby="modalAddNavTitle" aria-hidden="true">
        <div class="modal-dialog modal-dialog-centered">
            <div class="modal-content">
                <div class="modal-header alert-secondary">
                    <h5 class="modal-title" id="modalAddNavTitle">Add Navigation Item</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <div class="form-row my-1">
                        <div class="col">
                            <input type="text" name="formPage" id="formPage" class="form-control" placeholder="Page Title">
                            <div id="formPageError" class="d-flex justify-content-start">
                                <label id="formPage-error" class="error text-dark flex-fill bg-warning mt-1 rounded" for="formPage"></label>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="modal-footer text-right alert-secondary">
                    <button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
                    <input type="submit" name="btnAddNav" id="btnAddNav" class="btn btn-primary btn-sm" value="Add New">
                </div>
            </div>
        </div>
    </div>
</form>
<!-- End: Add Nav Item Modal -->