You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的默认行为覆盖,导致验证失败后标签仍保持激活状态。

修改步骤

  1. 阻止Bootstrap默认标签切换行为:在验证函数中先阻止事件默认行为,避免标签被自动激活。
  2. 手动完全控制标签状态:验证通过时切换标签激活状态和内容;失败时强制恢复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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 00:15:44