JavaScript验证失败后如何将激活标签页恢复为未激活状态
问题解决:点击Result标签验证失败后自动恢复未激活样式
问题描述
我有一个带下拉菜单切换表单的Details标签页,还有一个展示图表结果的Result标签页。希望用户点击Result标签时,先完成数据验证再切换到结果页面。
实际情况是:点击Result标签后,它会立即变成激活状态并变色,然后进入displayResultContent()函数做验证。如果数据无效,表单会显示错误提示,但Result标签仍保持激活样式,得点击屏幕才会恢复未激活状态。请问有没有办法不用点击屏幕,就让标签页自动切回未激活样式?
PS:还没加媒体查询,宽屏显示最佳,窄屏标签会上下排列但功能正常。
相关代码
JavaScript代码
const DATE_TYPE_PAST = 0; const DATE_TYPE_FUTURE = 1; const SUCCESS = 0; const ERROR = 1; $(function() { $('#details-tab').click(displayDetails); $('#result-tab').click(displayResultContent); $("#your-details-tab").click(displayYourDetailsTab); $("#your-superannuation-tab").click(displayYourSuperannuationTab); }); function displayYourDetailsTab() { removeAllForms(); var form = $("#your-details-form"); form.show(); $('#details-tab').html("Your Details"); } function displayYourSuperannuationTab() { removeAllForms(); var form = document.getElementById("your-superannuation-form"); form.style.display = 'block'; $('#details-tab').html("Your Superannuation"); } function removeAllForms() { var forms = document.getElementsByTagName('form'); for (var i = 0; i < forms.length; i++) { forms[i].style.display = "none"; } } function displayDetails() { $('#details').show(); $('#result').hide(); $('#result-tab').removeClass('active'); $('#details-tab').addClass('active'); } function displayResultContent() { // FIRST CHECK DATA ENTRY const dateResult = checkDate(document.getElementById("date-of-birth"), DATE_TYPE_PAST); const rentResult = checkMandatoryWholeNumber(document.getElementById("fortnightly-rent"), "Fortnightly rent ", 0, 999); if (dateResult === SUCCESS && rentResult === SUCCESS) { $('#result').show(); $('#details').hide(); $('#result-tab').addClass('active'); $('#details-tab').removeClass('active'); }else { $('#result-tab').removeClass('active'); } } const showError = (input, message) => { // get the form-field element let formField = input.closest(".form-field"); // add the error class formField.classList.remove('success'); formField.classList.add('error'); // show the error message const error = formField.querySelector('small'); error.textContent = message; }; const showSuccess = (input) => { // get the form-field element let formField = input.closest(".form-field"); // remove the error class formField.classList.remove('error'); formField.classList.add('success'); // hide the error message const error = formField.querySelector('small'); error.textContent = ''; }; const yourDetailsForm = document.getElementById("your-details-form"); if (yourDetailsForm != null) { yourDetailsForm.addEventListener('input', function(e) { switch (e.target.id) { case 'date-of-birth': checkDate(document.getElementById(e.target.id), DATE_TYPE_PAST); break; case 'fortnightly-rent': checkMandatoryWholeNumber(document.getElementById(e.target.id), "Fortnightly rent ", 0, 999); break; } }); } const isRequired = value => value !== ''; const isValidDate = function(date) { return (date !== "Invalid Date") && date !== ("Nan"); } function checkDate(dateElement, dateType) { const val = dateElement.value; const newDate = new Date(val); if (isValidDate(newDate)) { const today = new Date(); today.setHours(0,0,0,0); if (dateType === DATE_TYPE_PAST) { if (newDate < today) { // okay showSuccess(dateElement); return SUCCESS; }else { // error showError(dateElement, "date must be in the past"); return ERROR; } } if (dateType === DATE_TYPE_FUTURE) { if (newDate >= today) { // okay showSuccess(dateElement); return SUCCESS; }else { showError(dateElement, "date must be in the future"); return ERROR; } } }else { showError(dateElement, "date is mandatory"); return ERROR; } } $(document).on("keydown", ".whole-number", function (e) { const invalidChars = [ "-", "+", "e", ".", ]; if (invalidChars.includes(e.key)) { e.preventDefault(); } }); function checkMandatoryWholeNumber(element, prefix, min, max) { if (!isRequired(element.value)) { showError(element, prefix + " is mandatory"); return ERROR; } const val = parseInt(element.value); if (val < min || val > max) { showError(element, prefix + " must be between " + min + " and " + max); return ERROR; } showSuccess(element); return SUCCESS; }
CSS代码
html, body { height: 100%; margin: 0; overflow:hidden; } .content { height:100%; margin-left: 15%; margin-right: 15%; display:flex; flex-flow: column; } .content .result-content { background-color: #FFF; flex: 1 1 auto; margin-left:1%; margin-right:1%; display: none; } #tabs { width: 70%; margin: 0 auto; padding: 0; border-bottom: 7px solid #FE6D73; margin-top: 50px; } .form-container { width: 70%; height: 98%; background-color: #FFF; margin: 0 auto; overflow-y: auto; overflow-x: hidden; padding-bottom: 7px; box-sizing: border-box; padding-top: 0px; } li { list-style: none; } /*******************************************************************/ /* NAV TABS */ /******************************************************************/ .nav-link { border-radius: 15px 15px 0px 0px !important; text-decoration: none; height: 40px; font-size: 80%; background-color: #eeeeee !important; color: #aaaaaa; } .nav-link.active, .nav-link:active, .nav-link:hover, .nav-link:focus { border-radius: 15px 15px 0px 0px !important; background-color: #FE6D73 !important; color: #FEF9EF !important; } .nav-link-left { width: 255px; } .nav-link-right { width: 100px; } .nav-tabs>li>ul>li>a { width: 200px; } .dropdown-menu { z-index: 999999; } .nav-tabs>li>ul { background-color: #FE6D73 !important; } /* Remove border from tabs */ .nav-tabs .nav-link { border: 0px solid transparent; } /* This is the dropdown link */ .dropdown-item { background-color: #FE6D73 !important; color: #FEF9EF !important; } /* Border at bottom of each item in the drop down list */ .dropdown-menu>li { border-bottom: 1px solid #FEF9EF !important; } .nav-tabs>li>ul>li.active>a, .nav-tabs>li>ul>li.active>a:focus, .nav-tabs>li>ul>li>a:hover { background-color: #FEF9EF !important; color: #FE6D73 !important; } .dropdown-menu { border-top: 1px solid #FEF9EF; } /*******************************************************************/ /* FORMS */ /******************************************************************/ input[type="number"] { /*80px*/ width: 5em; font-size: 80%; } input[type="date"] { border: 1px solid #cccccc; } input { border: 1px solid #cccccc; } #your-superannuation-form { display: none; } form { margin-top: 20px; background-color: beige; } .form-group { margin-top: 20px; } .disabled-link { cursor: not-allowed; opacity: 0.5; } .disabled-text { opacity: 0.5; } .enabled-text { opacity: 1.0; } .enabled-link { cursor: pointer; opacity: 1.0; } .pointer-not-allowed{ cursor: not-allowed; } .pointer-allowed { cursor: pointer; } form label { font-family: 'opensansreg', 'Verdana', 'Arial'; } .form-text { font-family: Georgia; align-self: start; } .button-text { font-family: Georgia; font-size:80%; } .input-text { font-family: Consolas, Lucida Console, monospace; } :root { --error-color: #dc3545; --success-color: #cccccc; --warning-color: #ffc107; } .form-field input:focus { outline: none; } .form-field.error input { /* border-color: var(--error-color); */ border: 1px solid #dc3545; } .form-field.success input { /* border-color: var(--success-color); */ border: 1px solid #cccccc; } .form-field small { color: var(--error-color); font-size: 70%; display: block; }
HTML代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"> <link rel="stylesheet" href="style.css"/> </head> <body> <div id="tabs"> <ul class="nav nav-tabs"> <li class="dropdown nav-item"> <a class="nav-link nav-link-left dropdown-toggle active" id="details-tab" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">Details</a> <ul class="dropdown-menu"> <li><a class="dropdown-item" id="your-details-tab" href="#">Your Details</a></li> <li><a class="dropdown-item" id="your-superannuation-tab" href="#">Your Superannuation</a></li> </ul> </li> <li class="nav-item"> <a class="nav-link nav-link-right" id="result-tab" href="#" tabindex="-1">Result</a> </li> </ul> </div> <div class="content"> <div class="form-container" id="details"> <form id="your-details-form"> <div class="form-group"> <label for="date-of-birth" class="form-text">Date of Birth</label> <div class="form-field"> <input type="date" class="form-text" id="date-of-birth" name="date-of-birth"> <small></small> </div> </div> <div class="form-group"> <label for="fortnightly-rent" class="form-text">Fortnightly Rent</label> <div class="form-field"> <input type="text" class="input-text" size="4" id="fortnightly-rent" name="fortnightly-rent" onKeyDown="if (this.value.length == 4) this.value = this.value.slice(0, -1);"/> <small></small> </div> </div> </form> <form id="your-superannuation-form"> THIS IS YOUR SUPERANNUATION FORM </form> </div> <!-- end form-container--> <div class="result-content" id="result"> THIS IS THE RESULT </div> </div> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js@3.8.0/dist/chart.min.js"></script> <script type="text/javascript" src="script.js"></script> </body> </html>
解决方案
问题根源是Bootstrap标签组件会自动处理点击事件添加active类,你的代码移除active的操作被Bootstrap的默认行为覆盖,导致验证失败后标签仍保持激活状态。
修改步骤
- 阻止Bootstrap默认标签切换行为:在验证函数中先阻止事件默认行为,避免标签被自动激活。
- 手动完全控制标签状态:验证通过时切换标签激活状态和内容;失败时强制恢复Details标签的激活状态,保持表单显示。
修改后的JavaScript代码片段
首先更新事件绑定,传递事件对象:
$(function() { $('#details-tab').click(displayDetails); // 传递事件对象给验证函数 $('#result-tab').click(function(e) { displayResultContent(e); }); $("#your-details-tab").click(displayYourDetailsTab); $("#your-superannuation-tab").click(displayYourSuperannuationTab); });
然后修改displayResultContent函数:
function displayResultContent(e) { // 阻止Bootstrap默认的标签激活行为 e.preventDefault(); // FIRST CHECK DATA ENTRY const dateResult = checkDate(document.getElementById("date-of-birth"), DATE_TYPE_PAST); const rentResult = checkMandatoryWholeNumber(document.getElementById("fortnightly-rent"), "Fortnightly rent ", 0, 999); if (dateResult === SUCCESS && rentResult === SUCCESS) { $('#result').show(); $('#details').hide(); $('#result-tab').addClass('active'); $('#details-tab').removeClass('active'); }else { // 强制恢复Details标签激活状态,Result标签取消激活 $('#result-tab').removeClass('active'); $('#details-tab').addClass('active'); // 确保表单内容保持显示 $('#details').show(); $('#result').hide(); } }
效果说明
修改后,点击Result标签时会先执行验证:
- 验证通过:正常切换到Result标签和结果页面
- 验证失败:Result标签不会激活,Details标签保持激活状态,表单错误提示正常显示,无需用户点击屏幕恢复
内容的提问来源于stack exchange,提问作者Lorna from Perth
相关产品推荐
相关产品推荐

